diff --git a/src/server/services/PdfService.ts b/src/server/services/PdfService.ts index 13937fe..6ac0929 100644 --- a/src/server/services/PdfService.ts +++ b/src/server/services/PdfService.ts @@ -247,41 +247,81 @@ export class PdfService { const LINE_HEIGHT = FONT_SIZE * 1.5; const MAX_WIDTH = PAGE_WIDTH - margens.left - margens.right; - // Processar variáveis do template - const texto = this.processarTemplate(conteudo, escritorio) - .replace(/<[^>]+>/g, '') // Remove tags HTML - .replace(/ /g, ' ') - .replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, "'") - .replace(/\r\n/g, '\n') - .replace(/\r/g, '\n') - .replace(/\n{3,}/g, '\n\n') - .trim(); + // Função para decodificar entidades HTML comuns (acentuação e outros) + 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, 'Ü') + .replace(/ /gi, ' ') + .replace(/&/gi, '&') + .replace(/"/gi, '"') + .replace(/'/g, "'") + .replace(/</gi, '<') + .replace(/>/gi, '>') + .replace(/&#(\d+);/g, (m, code) => String.fromCharCode(code)); + } - // Quebra o texto em parágrafos + // Processar variáveis do template e decodificar entidades HTML + const texto = decodeHtmlEntities( + this.processarTemplate(conteudo, escritorio) + .replace(//gi, '\n') + .replace(/<\/p>/gi, '\n\n') + .replace(/<[^>]+>/g, '') // Remove tags HTML + .replace(/\r\n/g, '\n') + .replace(/\r/g, '\n') + .replace(/\n{3,}/g, '\n\n') + .trim() + ); + + // Quebra o texto em parágrafos (mantendo quebras de linha duplas como parágrafo) const paragrafos = texto.split(/\n{2,}/).map(p => p.trim()).filter(Boolean); - // Função para quebrar texto em linhas respeitando largura máxima + // 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[] { - // Remove quebras de linha internas para evitar erro de encoding - const cleanText = text.replace(/\r?\n/g, ' '); - const words = cleanText.split(' '); + // Mantém quebras de linha simples para espaçamento + const rawLines = text.split('\n'); const lines: string[] = []; - 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); - currentLine = word; - } else { - currentLine = testLine; + for (const rawLine of rawLines) { + const words = rawLine.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); + currentLine = word; + } else { + currentLine = testLine; + } } + if (currentLine) lines.push(currentLine); } - if (currentLine) lines.push(currentLine); return lines; } @@ -327,6 +367,7 @@ export class PdfService { }); } let y = PAGE_HEIGHT - margens.top; + let firstLine = true; // Escrever texto na página while (currentPar < paragrafos.length) { const lines = wrapText(paragrafos[currentPar], font, FONT_SIZE, MAX_WIDTH); @@ -337,14 +378,48 @@ export class PdfService { currentLine = lineIdx; break; } - page.drawText(lines[lineIdx], { - x: margens.left, + // Justificado: calcula espaçamento extra entre palavras (exceto última linha do parágrafo) + let text = lines[lineIdx]; + let x = margens.left; + let options: any = { + x, y, size: FONT_SIZE, font - }); + }; + if (text.trim().length > 0 && lineIdx < lines.length - 1) { + // Justificar (exceto última linha do parágrafo) + const words = text.split(' '); + if (words.length > 1) { + const textWidth = font.widthOfTextAtSize(text, FONT_SIZE); + const spaceWidth = font.widthOfTextAtSize(' ', FONT_SIZE); + const totalSpaces = words.length - 1; + const extraSpace = (MAX_WIDTH - textWidth) / totalSpaces; + let cursor = x; + for (let w = 0; w < words.length; w++) { + page.drawText(words[w], { + x: cursor, + y, + size: FONT_SIZE, + font + }); + if (w < words.length - 1) { + cursor += font.widthOfTextAtSize(words[w], FONT_SIZE) + spaceWidth + extraSpace; + } + } + } else { + page.drawText(text, options); + } + } else { + page.drawText(text, options); + } y -= LINE_HEIGHT; currentLine = 0; + firstLine = false; + } + // Espaço extra entre parágrafos + if (lineIdx >= lines.length) { + y -= LINE_HEIGHT * 0.5; } if (lineIdx < lines.length) { break;