tenta fix pdfservice.js
This commit is contained in:
1 parent
7e16bc8919
commit
76fa2c91ab
1 file changed
+111
-74
@@ -287,10 +287,10 @@ export class PdfService {
|
|||||||
if (!fontItalic) fontItalic = await pdfDoc.embedFont(StandardFonts.HelveticaOblique);
|
if (!fontItalic) fontItalic = await pdfDoc.embedFont(StandardFonts.HelveticaOblique);
|
||||||
if (!fontBoldItalic) fontBoldItalic = fontBold;
|
if (!fontBoldItalic) fontBoldItalic = fontBold;
|
||||||
|
|
||||||
// --- PARSER HTML TinyMCE SEM DEPENDÊNCIAS EXTERNAS ---
|
// --- PARSER HTML TinyMCE INLINE E BLOCOS (LEGADO MELHORADO) ---
|
||||||
// Suporte básico a blocos, alinhamento, recuo, negrito, itálico, sublinhado, listas, títulos
|
// Suporte a blocos, alinhamento, recuo, negrito, itálico, sublinhado, listas, títulos, font-family, centralização, tags inline
|
||||||
function simpleHtmlToBlocks(html: string): any[] {
|
function parseHtmlToBlocks(html: string): any[] {
|
||||||
// Remove tags não suportadas e preserva apenas blocos essenciais
|
// Normaliza blocos
|
||||||
html = html.replace(/<div[^>]*class=["'][^"']*MsoNormal[^"']*["'][^>]*>/gi, '<p>');
|
html = html.replace(/<div[^>]*class=["'][^"']*MsoNormal[^"']*["'][^>]*>/gi, '<p>');
|
||||||
html = html.replace(/<div[^>]*>/gi, '<p>');
|
html = html.replace(/<div[^>]*>/gi, '<p>');
|
||||||
html = html.replace(/<\/div>/gi, '</p>');
|
html = html.replace(/<\/div>/gi, '</p>');
|
||||||
@@ -300,7 +300,6 @@ export class PdfService {
|
|||||||
html = html.replace(/<\/h[1-6]>/gi, m => m + '\n');
|
html = html.replace(/<\/h[1-6]>/gi, m => m + '\n');
|
||||||
html = html.replace(/<\/ul>/gi, '</ul>\n');
|
html = html.replace(/<\/ul>/gi, '</ul>\n');
|
||||||
html = html.replace(/<\/ol>/gi, '</ol>\n');
|
html = html.replace(/<\/ol>/gi, '</ol>\n');
|
||||||
// Divide por linhas
|
|
||||||
const lines = html.split('\n');
|
const lines = html.split('\n');
|
||||||
const blocks: any[] = [];
|
const blocks: any[] = [];
|
||||||
let inList = false, listType = '';
|
let inList = false, listType = '';
|
||||||
@@ -313,13 +312,17 @@ export class PdfService {
|
|||||||
if (line.match(/^<\/ul>/i) || line.match(/^<\/ol>/i)) { inList = false; listType = ''; continue; }
|
if (line.match(/^<\/ul>/i) || line.match(/^<\/ol>/i)) { inList = false; listType = ''; continue; }
|
||||||
if (line.match(/^<li[^>]*>/i)) {
|
if (line.match(/^<li[^>]*>/i)) {
|
||||||
let text = line.replace(/^<li[^>]*>/i, '').replace(/<\/li>$/i, '');
|
let text = line.replace(/^<li[^>]*>/i, '').replace(/<\/li>$/i, '');
|
||||||
blocks.push({ text: (listType === 'ol' ? '1. ' : '• ') + text, tag: 'li', styles: {} });
|
blocks.push({ inlines: parseInline(text), tag: 'li', styles: {} });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Títulos
|
// Títulos
|
||||||
let headingMatch = line.match(/^<h([1-6])[^>]*>(.*?)<\/h\1>/i);
|
let headingMatch = line.match(/^<h([1-6])([^>]*)>(.*?)<\/h\1>/i);
|
||||||
if (headingMatch) {
|
if (headingMatch) {
|
||||||
blocks.push({ text: headingMatch[2], tag: `h${headingMatch[1]}`, styles: { heading: `h${headingMatch[1]}` } });
|
let styles: any = { heading: `h${headingMatch[1]}` };
|
||||||
|
if (/text-align:\s*center/i.test(headingMatch[2])) styles.align = 'center';
|
||||||
|
if (/text-align:\s*right/i.test(headingMatch[2])) styles.align = 'right';
|
||||||
|
if (/font-family:\s*['"]?Fira Sans/i.test(headingMatch[2])) styles.fontFamily = 'Fira Sans';
|
||||||
|
blocks.push({ inlines: parseInline(headingMatch[3]), tag: `h${headingMatch[1]}`, styles });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Parágrafo
|
// Parágrafo
|
||||||
@@ -330,29 +333,62 @@ export class PdfService {
|
|||||||
if (/text-align:\s*center/i.test(styleAttr)) styles.align = 'center';
|
if (/text-align:\s*center/i.test(styleAttr)) styles.align = 'center';
|
||||||
if (/text-align:\s*right/i.test(styleAttr)) styles.align = 'right';
|
if (/text-align:\s*right/i.test(styleAttr)) styles.align = 'right';
|
||||||
if (/text-indent:\s*2em/i.test(styleAttr)) styles.indent = true;
|
if (/text-indent:\s*2em/i.test(styleAttr)) styles.indent = true;
|
||||||
let text = pMatch[2];
|
if (/font-family:\s*['"]?Fira Sans/i.test(styleAttr)) styles.fontFamily = 'Fira Sans';
|
||||||
// Inline styles
|
blocks.push({ inlines: parseInline(pMatch[2]), tag: 'p', styles });
|
||||||
if (/<b>|<strong>/i.test(text)) styles.bold = true;
|
|
||||||
if (/<i>|<em>/i.test(text)) styles.italic = true;
|
|
||||||
if (/<u>/i.test(text)) styles.underline = true;
|
|
||||||
text = text.replace(/<\/?(b|strong|i|em|u)>/gi, '');
|
|
||||||
blocks.push({ text, tag: 'p', styles });
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Centralizado
|
// Centralizado
|
||||||
let centerMatch = line.match(/^<center[^>]*>(.*?)<\/center>$/i);
|
let centerMatch = line.match(/^<center[^>]*>(.*?)<\/center>$/i);
|
||||||
if (centerMatch) {
|
if (centerMatch) {
|
||||||
blocks.push({ text: centerMatch[1], tag: 'p', styles: { align: 'center' } });
|
blocks.push({ inlines: parseInline(centerMatch[1]), tag: 'p', styles: { align: 'center' } });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Texto puro
|
// Texto puro
|
||||||
blocks.push({ text: line.replace(/<\/?[^>]+>/g, ''), tag: 'span', styles: {} });
|
blocks.push({ inlines: parseInline(line.replace(/<\/?[^>]+>/g, '')), tag: 'span', styles: {} });
|
||||||
}
|
}
|
||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parser inline para negrito, itálico, sublinhado, font-family, combinações
|
||||||
|
function parseInline(html: string): any[] {
|
||||||
|
const result: any[] = [];
|
||||||
|
let regex = /(<(b|strong|i|em|u|span)[^>]*>|<\/(b|strong|i|em|u|span)>)/gi;
|
||||||
|
let stack: any[] = [];
|
||||||
|
let lastIndex = 0;
|
||||||
|
let match;
|
||||||
|
while ((match = regex.exec(html)) !== null) {
|
||||||
|
if (match.index > lastIndex) {
|
||||||
|
result.push({
|
||||||
|
text: html.substring(lastIndex, match.index),
|
||||||
|
...stack.reduce((acc, s) => ({ ...acc, ...s }), {})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (match[0][1] === '/') {
|
||||||
|
stack.pop();
|
||||||
|
} else {
|
||||||
|
let style: any = {};
|
||||||
|
if (/^<(b|strong)/i.test(match[0])) style.bold = true;
|
||||||
|
if (/^<(i|em)/i.test(match[0])) style.italic = true;
|
||||||
|
if (/^<u/i.test(match[0])) style.underline = true;
|
||||||
|
if (/^<span/i.test(match[0])) {
|
||||||
|
const fontMatch = match[0].match(/font-family:\s*['"]?([^;"']+)/i);
|
||||||
|
if (fontMatch) style.fontFamily = fontMatch[1];
|
||||||
|
}
|
||||||
|
stack.push(style);
|
||||||
|
}
|
||||||
|
lastIndex = regex.lastIndex;
|
||||||
|
}
|
||||||
|
if (lastIndex < html.length) {
|
||||||
|
result.push({
|
||||||
|
text: html.substring(lastIndex),
|
||||||
|
...stack.reduce((acc, s) => ({ ...acc, ...s }), {})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result.filter(i => i.text && i.text.replace(/\s+/g, '').length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
// Gera os blocos do HTML do TinyMCE
|
// Gera os blocos do HTML do TinyMCE
|
||||||
const blocks = simpleHtmlToBlocks(conteudo);
|
const blocks = parseHtmlToBlocks(conteudo);
|
||||||
|
|
||||||
// Renderização dos blocos no PDF
|
// Renderização dos blocos no PDF
|
||||||
let pageIndex = 0;
|
let pageIndex = 0;
|
||||||
@@ -373,87 +409,88 @@ export class PdfService {
|
|||||||
y -= LINE_HEIGHT * 0.5;
|
y -= LINE_HEIGHT * 0.5;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let usedFont = font;
|
|
||||||
if (block.styles.bold && block.styles.italic && fontBoldItalic) usedFont = fontBoldItalic;
|
|
||||||
else if (block.styles.bold && fontBold) usedFont = fontBold;
|
|
||||||
else if (block.styles.italic && fontItalic) usedFont = fontItalic;
|
|
||||||
|
|
||||||
let blockFontSize = FONT_SIZE;
|
let blockFontSize = FONT_SIZE;
|
||||||
if (block.styles.heading === 'h1') blockFontSize = 22;
|
if (block.styles.heading === 'h1') blockFontSize = 22;
|
||||||
if (block.styles.heading === 'h2') blockFontSize = 18;
|
if (block.styles.heading === 'h2') blockFontSize = 18;
|
||||||
if (block.styles.heading === 'h3') blockFontSize = 16;
|
if (block.styles.heading === 'h3') blockFontSize = 16;
|
||||||
|
|
||||||
// Remove quebras de linha do texto antes de medir e desenhar
|
let x = margens.left + (block.styles.indent ? FONT_SIZE * 2 : 0);
|
||||||
const cleanText = (block.text || '').replace(/\r?\n/g, ' ');
|
let line = '';
|
||||||
|
let lineWidth = 0;
|
||||||
|
for (const inline of block.inlines) {
|
||||||
|
let usedFont = font;
|
||||||
|
if (inline.bold && inline.italic && fontBoldItalic) usedFont = fontBoldItalic;
|
||||||
|
else if (inline.bold && fontBold) usedFont = fontBold;
|
||||||
|
else if (inline.italic && fontItalic) usedFont = fontItalic;
|
||||||
|
if (inline.fontFamily && inline.fontFamily.toLowerCase().includes('fira')) usedFont = font;
|
||||||
|
|
||||||
// Quebra em múltiplas linhas se necessário
|
const words = (inline.text || '').split(' ');
|
||||||
const lines = (() => {
|
for (let i = 0; i < words.length; i++) {
|
||||||
const words = cleanText.split(' ');
|
let word = words[i];
|
||||||
const result: string[] = [];
|
let testLine = line ? line + ' ' + word : word;
|
||||||
let currentLine = '';
|
let testWidth = usedFont.widthOfTextAtSize(testLine, blockFontSize);
|
||||||
for (const word of words) {
|
if (testWidth > MAX_WIDTH && line) {
|
||||||
const testLine = currentLine ? currentLine + ' ' + word : word;
|
let drawX = x;
|
||||||
const width = usedFont.widthOfTextAtSize(testLine, blockFontSize);
|
if (block.styles.align === 'center' || block.styles.heading === 'h1') {
|
||||||
if (width > MAX_WIDTH && currentLine) {
|
drawX = margens.left + ((MAX_WIDTH - lineWidth) / 2);
|
||||||
result.push(currentLine);
|
}
|
||||||
currentLine = word;
|
if (block.styles.align === 'right') {
|
||||||
|
drawX = margens.left + (MAX_WIDTH - lineWidth);
|
||||||
|
}
|
||||||
|
page.drawText(line, {
|
||||||
|
x: drawX,
|
||||||
|
y,
|
||||||
|
size: blockFontSize,
|
||||||
|
font: usedFont,
|
||||||
|
color: rgb(0, 0, 0)
|
||||||
|
});
|
||||||
|
if (inline.underline) {
|
||||||
|
page.drawLine({
|
||||||
|
start: { x: drawX, y: y - 2 },
|
||||||
|
end: { x: drawX + lineWidth, y: y - 2 },
|
||||||
|
thickness: 0.7,
|
||||||
|
color: rgb(0, 0, 0)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
y -= blockFontSize * 1.5;
|
||||||
|
line = word;
|
||||||
|
lineWidth = usedFont.widthOfTextAtSize(word, blockFontSize);
|
||||||
} else {
|
} else {
|
||||||
currentLine = testLine;
|
if (line) {
|
||||||
|
line += ' ' + word;
|
||||||
|
lineWidth = usedFont.widthOfTextAtSize(line, blockFontSize);
|
||||||
|
} else {
|
||||||
|
line = word;
|
||||||
|
lineWidth = usedFont.widthOfTextAtSize(line, blockFontSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (currentLine) result.push(currentLine);
|
}
|
||||||
return result;
|
if (line) {
|
||||||
})();
|
let drawX = x;
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
let x = margens.left + (block.styles.indent ? FONT_SIZE * 2 : 0);
|
|
||||||
let width = usedFont.widthOfTextAtSize(line, blockFontSize);
|
|
||||||
|
|
||||||
// Centralização
|
|
||||||
if (block.styles.align === 'center' || block.styles.heading === 'h1') {
|
if (block.styles.align === 'center' || block.styles.heading === 'h1') {
|
||||||
x = margens.left + ((MAX_WIDTH - width) / 2);
|
drawX = margens.left + ((MAX_WIDTH - lineWidth) / 2);
|
||||||
}
|
}
|
||||||
if (block.styles.align === 'right') {
|
if (block.styles.align === 'right') {
|
||||||
x = margens.left + (MAX_WIDTH - width);
|
drawX = margens.left + (MAX_WIDTH - lineWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quebra de página se necessário
|
|
||||||
if (y < margens.bottom + blockFontSize * 1.2) {
|
|
||||||
pageIndex++;
|
|
||||||
page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
||||||
if (timbradoPages && timbradoPages.length > 0) {
|
|
||||||
const embedded = timbradoPages[pageIndex % timbradoPages.length];
|
|
||||||
page.drawPage(embedded, {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: PAGE_WIDTH,
|
|
||||||
height: PAGE_HEIGHT
|
|
||||||
});
|
|
||||||
}
|
|
||||||
y = PAGE_HEIGHT - margens.top;
|
|
||||||
}
|
|
||||||
|
|
||||||
page.drawText(line, {
|
page.drawText(line, {
|
||||||
x,
|
x: drawX,
|
||||||
y,
|
y,
|
||||||
size: blockFontSize,
|
size: blockFontSize,
|
||||||
font: usedFont,
|
font: font,
|
||||||
color: rgb(0, 0, 0)
|
color: rgb(0, 0, 0)
|
||||||
});
|
});
|
||||||
|
if (block.inlines.some(i => i.underline)) {
|
||||||
// Sublinhado
|
|
||||||
if (block.styles.underline) {
|
|
||||||
page.drawLine({
|
page.drawLine({
|
||||||
start: { x, y: y - 2 },
|
start: { x: drawX, y: y - 2 },
|
||||||
end: { x: x + width, y: y - 2 },
|
end: { x: drawX + lineWidth, y: y - 2 },
|
||||||
thickness: 0.7,
|
thickness: 0.7,
|
||||||
color: rgb(0, 0, 0)
|
color: rgb(0, 0, 0)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
y -= blockFontSize * 1.5;
|
y -= blockFontSize * 1.5;
|
||||||
}
|
}
|
||||||
// Espaço extra entre parágrafos
|
|
||||||
if (block.tag === 'p' || block.styles.heading) {
|
if (block.tag === 'p' || block.styles.heading) {
|
||||||
y -= blockFontSize * 0.3;
|
y -= blockFontSize * 0.3;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user