From 3a3c64f1b32d779dc97ba853deecd606c3f1ab4e Mon Sep 17 00:00:00 2001 From: Rodrigo Martins Date: Mon, 21 Apr 2025 19:46:16 -0300 Subject: [PATCH] tenta fix pdfservice.js --- src/server/services/PdfService.ts | 123 ++++++++++++++++++------------ 1 file changed, 76 insertions(+), 47 deletions(-) diff --git a/src/server/services/PdfService.ts b/src/server/services/PdfService.ts index 6ac0929..f4648df 100644 --- a/src/server/services/PdfService.ts +++ b/src/server/services/PdfService.ts @@ -251,48 +251,48 @@ export class PdfService { function decodeHtmlEntities(str: string): string { if (!str) return ''; return str - .replace(/á/gi, 'á') - .replace(/é/gi, 'é') - .replace(/í/gi, 'í') - .replace(/ó/gi, 'ó') - .replace(/ú/gi, 'ú') - .replace(/Á/g, 'Á') - .replace(/É/g, 'É') - .replace(/Í/g, 'Í') - .replace(/Ó/g, 'Ó') - .replace(/Ú/g, 'Ú') - .replace(/ã/gi, 'ã') - .replace(/õ/gi, 'õ') - .replace(/ñ/gi, 'ñ') - .replace(/ç/gi, 'ç') - .replace(/Ç/g, 'Ç') - .replace(/â/gi, 'â') - .replace(/ê/gi, 'ê') - .replace(/î/gi, 'î') - .replace(/ô/gi, 'ô') - .replace(/û/gi, 'û') - .replace(/à/gi, 'à') - .replace(/è/gi, 'è') - .replace(/ì/gi, 'ì') - .replace(/ò/gi, 'ò') - .replace(/ù/gi, 'ù') - .replace(/ü/gi, 'ü') - .replace(/Ü/g, 'Ü') + // Acentos minúsculos e maiúsculos + .replace(/Á/g, 'Á').replace(/á/g, 'á') + .replace(/É/g, 'É').replace(/é/g, 'é') + .replace(/Í/g, 'Í').replace(/í/g, 'í') + .replace(/Ó/g, 'Ó').replace(/ó/g, 'ó') + .replace(/Ú/g, 'Ú').replace(/ú/g, 'ú') + .replace(/À/g, 'À').replace(/à/g, 'à') + .replace(/È/g, 'È').replace(/è/g, 'è') + .replace(/Ì/g, 'Ì').replace(/ì/g, 'ì') + .replace(/Ò/g, 'Ò').replace(/ò/g, 'ò') + .replace(/Ù/g, 'Ù').replace(/ù/g, 'ù') + .replace(/Â/g, 'Â').replace(/â/g, 'â') + .replace(/Ê/g, 'Ê').replace(/ê/g, 'ê') + .replace(/Î/g, 'Î').replace(/î/g, 'î') + .replace(/Ô/g, 'Ô').replace(/ô/g, 'ô') + .replace(/Û/g, 'Û').replace(/û/g, 'û') + .replace(/Ã/g, 'Ã').replace(/ã/g, 'ã') + .replace(/Õ/g, 'Õ').replace(/õ/g, 'õ') + .replace(/Ç/g, 'Ç').replace(/ç/g, 'ç') + .replace(/Ñ/g, 'Ñ').replace(/ñ/g, 'ñ') + .replace(/Ü/g, 'Ü').replace(/ü/g, 'ü') + // Outras entidades comuns .replace(/ /gi, ' ') .replace(/&/gi, '&') .replace(/"/gi, '"') .replace(/'/g, "'") .replace(/</gi, '<') .replace(/>/gi, '>') + .replace(/–/gi, '–') + .replace(/—/gi, '—') .replace(/&#(\d+);/g, (m, code) => String.fromCharCode(code)); } // Processar variáveis do template e decodificar entidades HTML + // Mantém tags , , , , ,
,

, const texto = decodeHtmlEntities( this.processarTemplate(conteudo, escritorio) .replace(//gi, '\n') .replace(/<\/p>/gi, '\n\n') - .replace(/<[^>]+>/g, '') // Remove tags HTML + .replace(/]*>/gi, '') // Remove apenas a tag de abertura

+ .replace(/]*text-indent[^>]*>/gi, '§§INDENT§§') // Marca recuo de parágrafo + .replace(/<\/span>/gi, '') .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .replace(/\n{3,}/g, '\n\n') @@ -303,24 +303,41 @@ export class PdfService { const paragrafos = texto.split(/\n{2,}/).map(p => p.trim()).filter(Boolean); // Função para quebrar texto em linhas respeitando largura máxima e manter quebras de linha simples - function wrapText(text: string, font: any, fontSize: number, maxWidth: number): string[] { + function wrapTextStyled(text: string, font: any, fontSize: number, maxWidth: number) { // Mantém quebras de linha simples para espaçamento const rawLines = text.split('\n'); - const lines: string[] = []; + const lines: { text: string, bold: boolean, italic: boolean, underline: boolean, indent: boolean }[] = []; for (const rawLine of rawLines) { - const words = rawLine.split(' '); + let line = rawLine; + let bold = false, italic = false, underline = false, indent = false; + // Detecta recuo de parágrafo + if (line.startsWith('§§INDENT§§')) { + indent = true; + line = line.replace('§§INDENT§§', '').trimStart(); + } + // Detecta negrito, itálico, sublinhado (apenas no início e fim da linha) + if (/^|^/.test(line)) { bold = true; line = line.replace(/^|^/, ''); } + if (/^|^/.test(line)) { italic = true; line = line.replace(/^|^/, ''); } + if (/^/.test(line)) { underline = true; line = line.replace(/^/, ''); } + if (/<\/b>$|<\/strong>$/.test(line)) { bold = true; line = line.replace(/<\/b>$|<\/strong>$/, ''); } + if (/<\/i>$|<\/em>$/.test(line)) { italic = true; line = line.replace(/<\/i>$|<\/em>$/, ''); } + if (/<\/u>$/.test(line)) { underline = true; line = line.replace(/<\/u>$/, ''); } + // Remove tags intermediárias (não suporta aninhamento) + line = line.replace(/<\/?(b|strong|i|em|u)>/gi, ''); + // Quebra em palavras para justificar + const words = line.split(' '); let currentLine = ''; for (const word of words) { const testLine = currentLine ? currentLine + ' ' + word : word; const width = font.widthOfTextAtSize(testLine, fontSize); if (width > maxWidth && currentLine) { - lines.push(currentLine); + lines.push({ text: currentLine, bold, italic, underline, indent }); currentLine = word; } else { currentLine = testLine; } } - if (currentLine) lines.push(currentLine); + if (currentLine) lines.push({ text: currentLine, bold, italic, underline, indent }); } return lines; } @@ -370,41 +387,43 @@ export class PdfService { let firstLine = true; // Escrever texto na página while (currentPar < paragrafos.length) { - const lines = wrapText(paragrafos[currentPar], font, FONT_SIZE, MAX_WIDTH); + const styledLines = wrapTextStyled(paragrafos[currentPar], font, FONT_SIZE, MAX_WIDTH); let lineIdx = 0; if (currentLine > 0) lineIdx = currentLine; - for (; lineIdx < lines.length; lineIdx++) { + for (; lineIdx < styledLines.length; lineIdx++) { if (y < margens.bottom + LINE_HEIGHT) { currentLine = lineIdx; break; } // Justificado: calcula espaçamento extra entre palavras (exceto última linha do parágrafo) - let text = lines[lineIdx]; - let x = margens.left; + let { text, bold, italic, underline, indent } = styledLines[lineIdx]; + let x = margens.left + (indent ? FONT_SIZE * 2 : 0); let options: any = { x, y, size: FONT_SIZE, - font + font: bold ? await pdfDoc.embedFont(StandardFonts.HelveticaBold) + : italic ? await pdfDoc.embedFont(StandardFonts.HelveticaOblique) + : font }; - if (text.trim().length > 0 && lineIdx < lines.length - 1) { - // Justificar (exceto última linha do parágrafo) + // Justificar (exceto última linha do parágrafo) + if (text.trim().length > 0 && lineIdx < styledLines.length - 1) { const words = text.split(' '); if (words.length > 1) { - const textWidth = font.widthOfTextAtSize(text, FONT_SIZE); - const spaceWidth = font.widthOfTextAtSize(' ', FONT_SIZE); + const textWidth = options.font.widthOfTextAtSize(text, FONT_SIZE); + const spaceWidth = options.font.widthOfTextAtSize(' ', FONT_SIZE); const totalSpaces = words.length - 1; - const extraSpace = (MAX_WIDTH - textWidth) / totalSpaces; + const extraSpace = (MAX_WIDTH - (indent ? FONT_SIZE * 2 : 0) - textWidth) / totalSpaces; let cursor = x; for (let w = 0; w < words.length; w++) { page.drawText(words[w], { x: cursor, y, size: FONT_SIZE, - font + font: options.font }); if (w < words.length - 1) { - cursor += font.widthOfTextAtSize(words[w], FONT_SIZE) + spaceWidth + extraSpace; + cursor += options.font.widthOfTextAtSize(words[w], FONT_SIZE) + spaceWidth + extraSpace; } } } else { @@ -413,15 +432,25 @@ export class PdfService { } else { page.drawText(text, options); } + // Sublinhado + if (underline) { + const textWidth = options.font.widthOfTextAtSize(text, FONT_SIZE); + page.drawLine({ + start: { x, y: y - 2 }, + end: { x: x + textWidth, y: y - 2 }, + thickness: 0.7, + color: rgb(0, 0, 0) + }); + } y -= LINE_HEIGHT; currentLine = 0; firstLine = false; } // Espaço extra entre parágrafos - if (lineIdx >= lines.length) { + if (lineIdx >= styledLines.length) { y -= LINE_HEIGHT * 0.5; } - if (lineIdx < lines.length) { + if (lineIdx < styledLines.length) { break; } currentPar++;