tenta fix pdfservice.js
This commit is contained in:
1 parent
f3ff4f8939
commit
7e16bc8919
1 file changed
+74
-82
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user