tenta fix pdfservice.js

This commit is contained in:
rodrigo committed 2025-04-21 20:24:54 -03:00
1 parent f3ff4f8939
commit 7e16bc8919
1 file changed
+74 -82
+74 -82
View File
@@ -287,96 +287,72 @@ export class PdfService {
if (!fontItalic) fontItalic = await pdfDoc.embedFont(StandardFonts.HelveticaOblique);
if (!fontBoldItalic) fontBoldItalic = fontBold;
// --- NOVO PARSER HTML TinyMCE PARA BLOCOS ---
// Usa jsdom para manter estrutura de blocos e estilos
const { JSDOM } = require('jsdom');
const dom = new JSDOM(`<body>${conteudo}</body>`);
const body = dom.window.document.body;
// Função auxiliar para obter estilos relevantes
function getBlockStyles(el: HTMLElement) {
const styles: any = {};
const computed = el.getAttribute('style') || '';
if (/font-weight:\s*bold/i.test(computed) || el.tagName === 'B' || el.tagName === 'STRONG') styles.bold = true;
if (/font-style:\s*italic/i.test(computed) || el.tagName === 'I' || el.tagName === 'EM') styles.italic = true;
if (/text-decoration:\s*underline/i.test(computed) || el.tagName === 'U') styles.underline = true;
if (/text-align:\s*center/i.test(computed) || el.align === 'center' || el.tagName === 'CENTER') styles.align = 'center';
if (/text-align:\s*right/i.test(computed) || el.align === 'right') styles.align = 'right';
if (/text-indent:\s*2em/i.test(computed)) styles.indent = true;
if (/font-family:\s*['"]?Fira Sans/i.test(computed)) styles.fontFamily = 'Fira Sans';
if (el.tagName.match(/^H[1-6]$/)) styles.heading = el.tagName.toLowerCase();
return styles;
}
// Função para quebrar texto em linhas respeitando largura máxima
function breakLines(text: string, fontObj: any, fontSize: number, maxWidth: number) {
const words = text.split(' ');
const lines: string[] = [];
let currentLine = '';
for (const word of words) {
const testLine = currentLine ? currentLine + ' ' + word : word;
const width = fontObj.widthOfTextAtSize(testLine, fontSize);
if (width > maxWidth && currentLine) {
lines.push(currentLine);
currentLine = word;
} else {
currentLine = testLine;
}
}
if (currentLine) lines.push(currentLine);
return lines;
}
// Função para processar blocos do HTML
function parseBlocks(node: HTMLElement, parentStyles: any = {}) {
// --- PARSER HTML TinyMCE SEM DEPENDÊNCIAS EXTERNAS ---
// Suporte básico a blocos, alinhamento, recuo, negrito, itálico, sublinhado, listas, títulos
function simpleHtmlToBlocks(html: string): any[] {
// Remove tags não suportadas e preserva apenas blocos essenciais
html = html.replace(/<div[^>]*class=["'][^"']*MsoNormal[^"']*["'][^>]*>/gi, '<p>');
html = html.replace(/<div[^>]*>/gi, '<p>');
html = html.replace(/<\/div>/gi, '</p>');
html = html.replace(/<br\s*\/?>/gi, '\n');
html = html.replace(/<\/li>/gi, '</li>\n');
html = html.replace(/<\/p>/gi, '</p>\n');
html = html.replace(/<\/h[1-6]>/gi, m => m + '\n');
html = html.replace(/<\/ul>/gi, '</ul>\n');
html = html.replace(/<\/ol>/gi, '</ol>\n');
// Divide por linhas
const lines = html.split('\n');
const blocks: any[] = [];
for (const el of Array.from(node.childNodes)) {
if (el.nodeType === 3) { // texto puro
let text = el.textContent || '';
if (text.trim() !== '') {
blocks.push({ text, tag: parentStyles.tag || 'span', styles: { ...parentStyles } });
}
continue;
}
if (el.nodeType !== 1) continue;
const elem = el as HTMLElement;
let styles = { ...parentStyles, ...getBlockStyles(elem) };
let tag = elem.tagName.toLowerCase();
if (['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'center'].includes(tag)) {
let blockText = '';
elem.childNodes.forEach(child => {
if (child.nodeType === 3) blockText += child.textContent;
});
blocks.push({ text: blockText, tag, styles: { ...styles } });
blocks.push(...parseBlocks(elem, styles));
blocks.push({ tag: 'br', text: '', styles: {} });
continue;
}
if (tag === 'br') {
blocks.push({ tag: 'br', text: '', styles: { ...styles } });
continue;
}
if (['b', 'strong', 'i', 'em', 'u', 'span'].includes(tag)) {
blocks.push(...parseBlocks(elem, styles));
continue;
}
let inList = false, listType = '';
for (let raw of lines) {
let line = raw.trim();
if (!line) continue;
// Listas
if (tag === 'ul' || tag === 'ol') {
elem.childNodes.forEach(li => {
if (li.nodeType === 1 && (li as HTMLElement).tagName === 'LI') {
blocks.push({ text: '• ' + (li.textContent || ''), tag: 'li', styles: { ...styles } });
blocks.push({ tag: 'br', text: '', styles: {} });
}
});
if (line.match(/^<ul[^>]*>/i)) { inList = true; listType = 'ul'; continue; }
if (line.match(/^<ol[^>]*>/i)) { inList = true; listType = 'ol'; continue; }
if (line.match(/^<\/ul>/i) || line.match(/^<\/ol>/i)) { inList = false; listType = ''; continue; }
if (line.match(/^<li[^>]*>/i)) {
let text = line.replace(/^<li[^>]*>/i, '').replace(/<\/li>$/i, '');
blocks.push({ text: (listType === 'ol' ? '1. ' : '• ') + text, tag: 'li', styles: {} });
continue;
}
// Títulos
let headingMatch = line.match(/^<h([1-6])[^>]*>(.*?)<\/h\1>/i);
if (headingMatch) {
blocks.push({ text: headingMatch[2], tag: `h${headingMatch[1]}`, styles: { heading: `h${headingMatch[1]}` } });
continue;
}
// Parágrafo
let pMatch = line.match(/^<p([^>]*)>(.*?)<\/p>$/i);
if (pMatch) {
let styles: any = {};
let styleAttr = pMatch[1];
if (/text-align:\s*center/i.test(styleAttr)) styles.align = 'center';
if (/text-align:\s*right/i.test(styleAttr)) styles.align = 'right';
if (/text-indent:\s*2em/i.test(styleAttr)) styles.indent = true;
let text = pMatch[2];
// Inline 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;
}
// Centralizado
let centerMatch = line.match(/^<center[^>]*>(.*?)<\/center>$/i);
if (centerMatch) {
blocks.push({ text: centerMatch[1], tag: 'p', styles: { align: 'center' } });
continue;
}
// Texto puro
blocks.push({ text: line.replace(/<\/?[^>]+>/g, ''), tag: 'span', styles: {} });
}
return blocks;
}
// Gera os blocos do HTML do TinyMCE
const blocks = parseBlocks(body);
const blocks = simpleHtmlToBlocks(conteudo);
// Renderização dos blocos no PDF
let pageIndex = 0;
@@ -411,7 +387,23 @@ export class PdfService {
const cleanText = (block.text || '').replace(/\r?\n/g, ' ');
// Quebra em múltiplas linhas se necessário
const lines = breakLines(cleanText, usedFont, blockFontSize, MAX_WIDTH);
const lines = (() => {
const words = cleanText.split(' ');
const result: string[] = [];
let currentLine = '';
for (const word of words) {
const testLine = currentLine ? currentLine + ' ' + word : word;
const width = usedFont.widthOfTextAtSize(testLine, blockFontSize);
if (width > MAX_WIDTH && currentLine) {
result.push(currentLine);
currentLine = word;
} else {
currentLine = testLine;
}
}
if (currentLine) result.push(currentLine);
return result;
})();
for (const line of lines) {
let x = margens.left + (block.styles.indent ? FONT_SIZE * 2 : 0);