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 (!fontItalic) fontItalic = await pdfDoc.embedFont(StandardFonts.HelveticaOblique);
|
||||||
if (!fontBoldItalic) fontBoldItalic = fontBold;
|
if (!fontBoldItalic) fontBoldItalic = fontBold;
|
||||||
|
|
||||||
// --- NOVO PARSER HTML TinyMCE PARA BLOCOS ---
|
// --- PARSER HTML TinyMCE SEM DEPENDÊNCIAS EXTERNAS ---
|
||||||
// Usa jsdom para manter estrutura de blocos e estilos
|
// Suporte básico a blocos, alinhamento, recuo, negrito, itálico, sublinhado, listas, títulos
|
||||||
const { JSDOM } = require('jsdom');
|
function simpleHtmlToBlocks(html: string): any[] {
|
||||||
const dom = new JSDOM(`<body>${conteudo}</body>`);
|
// Remove tags não suportadas e preserva apenas blocos essenciais
|
||||||
const body = dom.window.document.body;
|
html = html.replace(/<div[^>]*class=["'][^"']*MsoNormal[^"']*["'][^>]*>/gi, '<p>');
|
||||||
|
html = html.replace(/<div[^>]*>/gi, '<p>');
|
||||||
// Função auxiliar para obter estilos relevantes
|
html = html.replace(/<\/div>/gi, '</p>');
|
||||||
function getBlockStyles(el: HTMLElement) {
|
html = html.replace(/<br\s*\/?>/gi, '\n');
|
||||||
const styles: any = {};
|
html = html.replace(/<\/li>/gi, '</li>\n');
|
||||||
const computed = el.getAttribute('style') || '';
|
html = html.replace(/<\/p>/gi, '</p>\n');
|
||||||
if (/font-weight:\s*bold/i.test(computed) || el.tagName === 'B' || el.tagName === 'STRONG') styles.bold = true;
|
html = html.replace(/<\/h[1-6]>/gi, m => m + '\n');
|
||||||
if (/font-style:\s*italic/i.test(computed) || el.tagName === 'I' || el.tagName === 'EM') styles.italic = true;
|
html = html.replace(/<\/ul>/gi, '</ul>\n');
|
||||||
if (/text-decoration:\s*underline/i.test(computed) || el.tagName === 'U') styles.underline = true;
|
html = html.replace(/<\/ol>/gi, '</ol>\n');
|
||||||
if (/text-align:\s*center/i.test(computed) || el.align === 'center' || el.tagName === 'CENTER') styles.align = 'center';
|
// Divide por linhas
|
||||||
if (/text-align:\s*right/i.test(computed) || el.align === 'right') styles.align = 'right';
|
const lines = html.split('\n');
|
||||||
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 = {}) {
|
|
||||||
const blocks: any[] = [];
|
const blocks: any[] = [];
|
||||||
for (const el of Array.from(node.childNodes)) {
|
let inList = false, listType = '';
|
||||||
if (el.nodeType === 3) { // texto puro
|
for (let raw of lines) {
|
||||||
let text = el.textContent || '';
|
let line = raw.trim();
|
||||||
if (text.trim() !== '') {
|
if (!line) continue;
|
||||||
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;
|
|
||||||
}
|
|
||||||
// Listas
|
// Listas
|
||||||
if (tag === 'ul' || tag === 'ol') {
|
if (line.match(/^<ul[^>]*>/i)) { inList = true; listType = 'ul'; continue; }
|
||||||
elem.childNodes.forEach(li => {
|
if (line.match(/^<ol[^>]*>/i)) { inList = true; listType = 'ol'; continue; }
|
||||||
if (li.nodeType === 1 && (li as HTMLElement).tagName === 'LI') {
|
if (line.match(/^<\/ul>/i) || line.match(/^<\/ol>/i)) { inList = false; listType = ''; continue; }
|
||||||
blocks.push({ text: '• ' + (li.textContent || ''), tag: 'li', styles: { ...styles } });
|
if (line.match(/^<li[^>]*>/i)) {
|
||||||
blocks.push({ tag: 'br', text: '', styles: {} });
|
let text = line.replace(/^<li[^>]*>/i, '').replace(/<\/li>$/i, '');
|
||||||
}
|
blocks.push({ text: (listType === 'ol' ? '1. ' : '• ') + text, tag: 'li', styles: {} });
|
||||||
});
|
|
||||||
continue;
|
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;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gera os blocos do HTML do TinyMCE
|
// Gera os blocos do HTML do TinyMCE
|
||||||
const blocks = parseBlocks(body);
|
const blocks = simpleHtmlToBlocks(conteudo);
|
||||||
|
|
||||||
// Renderização dos blocos no PDF
|
// Renderização dos blocos no PDF
|
||||||
let pageIndex = 0;
|
let pageIndex = 0;
|
||||||
@@ -411,7 +387,23 @@ export class PdfService {
|
|||||||
const cleanText = (block.text || '').replace(/\r?\n/g, ' ');
|
const cleanText = (block.text || '').replace(/\r?\n/g, ' ');
|
||||||
|
|
||||||
// Quebra em múltiplas linhas se necessário
|
// 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) {
|
for (const line of lines) {
|
||||||
let x = margens.left + (block.styles.indent ? FONT_SIZE * 2 : 0);
|
let x = margens.left + (block.styles.indent ? FONT_SIZE * 2 : 0);
|
||||||
|
|||||||
Reference in new issue
Block a user