diff --git a/src/server/services/PdfService.ts b/src/server/services/PdfService.ts index 74587ce..d9eb71c 100644 --- a/src/server/services/PdfService.ts +++ b/src/server/services/PdfService.ts @@ -343,9 +343,11 @@ export class PdfService { color, align: string, maxWidth: number, - indent: boolean + indent: boolean, + topMargin: number, + bottomMargin: number, + pageHeight: number ) { - // Divide o texto em tokens de formatação (, , , etc) let regex = /(<\/?(b|strong|i|em|u)>)/gi; let tokens: { text: string, bold: boolean, italic: boolean, underline: boolean }[] = []; let lastIndex = 0; @@ -377,14 +379,12 @@ export class PdfService { }); } - // Junta todos os tokens em uma lista de palavras com formatação let words: { text: string, font: any, underline: boolean, bold: boolean, italic: boolean }[] = []; for (const token of tokens) { let usedFont = font; if (token.bold && token.italic) usedFont = fontBoldItalic; else if (token.bold) usedFont = fontBold; else if (token.italic) usedFont = fontItalic; - // Divide token.text em palavras, mantendo espaços const split = token.text.split(/(\s+)/); for (const part of split) { if (part.length > 0) { @@ -393,12 +393,10 @@ export class PdfService { } } - // Monta linhas respeitando largura máxima let lines: { tokens: { text: string, font: any, underline: boolean, bold: boolean, italic: boolean }[] }[] = []; let currentTokens: { text: string, font: any, underline: boolean, bold: boolean, italic: boolean }[] = []; let currentWidth = 0; for (const [i, word] of words.entries()) { - // Aplica recuo de parágrafo na primeira linha let recuo = 0; if (indent && lines.length === 0 && currentTokens.length === 0) { recuo = fontSize * 2; @@ -417,26 +415,23 @@ export class PdfService { lines.push({ tokens: currentTokens }); } - // Renderiza cada linha for (let i = 0; i < lines.length; i++) { + if (y < bottomMargin + fontSize * 1.5) { + return null; + } let line = lines[i]; let drawX = x; let totalWidth = line.tokens.reduce((acc, t) => acc + t.font.widthOfTextAtSize(t.text, fontSize), 0); - // Aplica recuo de parágrafo na primeira linha let recuo = 0; if (indent && i === 0) { recuo = fontSize * 2; drawX += recuo; } - // Centralizado if (align === 'center') { drawX = x + (maxWidth - totalWidth) / 2; - } - // Justificado (exceto última linha) - else if (align === 'justify' && i < lines.length - 1) { - // Conta espaços + } else if (align === 'justify' && i < lines.length - 1) { const spaceCount = line.tokens.filter(t => /^\s+$/.test(t.text)).length; const extraSpace = spaceCount > 0 ? (maxWidth - totalWidth) / spaceCount : 0; for (const t of line.tokens) { @@ -447,7 +442,6 @@ export class PdfService { font: t.font, color }); - // Sublinhado if (t.underline && t.text.trim() !== '') { const underlineY = y - 1.5; page.drawLine({ @@ -466,7 +460,6 @@ export class PdfService { continue; } - // Esquerda ou última linha justificada for (const t of line.tokens) { page.drawText(t.text, { x: drawX, @@ -475,7 +468,6 @@ export class PdfService { font: t.font, color }); - // Sublinhado if (t.underline && t.text.trim() !== '') { const underlineY = y - 1.5; page.drawLine({ @@ -492,17 +484,13 @@ export class PdfService { return y; } - // Processar variáveis do template e decodificar entidades HTML - // Mantém tags , , , , ,
,

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

- // Marca recuo de parágrafo + .replace(/]*>/gi, '') .replace(/]*text-indent[^>]*>/gi, '§§INDENT§§') - // Marca centralização .replace(/]*text-align\s*:\s*center[^>]*>/gi, '§§CENTER§§') .replace(/<\/span>/gi, '') .replace(/\r\n/g, '\n') @@ -512,10 +500,8 @@ export class PdfService { ) ); - // 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); - // Carregar o timbrado (se houver) e criar o PDF final let timbradoPdf: PDFDocument | null = null; let timbradoPageCount = 0; let timbradoPages: any[] = []; @@ -530,10 +516,8 @@ export class PdfService { } } - // Quebrar o texto em páginas let currentPar = 0; let pageIndex = 0; - while (currentPar < paragrafos.length) { let page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]); if (timbradoPages.length > 0) { @@ -552,14 +536,11 @@ export class PdfService { let align = center ? 'center' : 'justify'; let text = paragrafos[currentPar].replace(/^§§INDENT§§/, '').replace(/^§§CENTER§§/, '').trim(); - // Aplica decode de entidades HTML e símbolos text = decodeHtmlEntities(text); - // Renderiza o parágrafo inteiro com recuo (não só a primeira linha) let x = margens.left; let parWidth = MAX_WIDTH; - // Renderiza texto com formatação inline (negrito, itálico, sublinhado, ambos) e controle de linhas let yAfter = renderInlineFormattedParagraph( page, text, @@ -573,10 +554,16 @@ export class PdfService { rgb(0, 0, 0), align, parWidth, - indent + indent, + margens.top, + margens.bottom, + PAGE_HEIGHT ); + if (yAfter === null) { + pageIndex++; + break; + } y = yAfter - FONT_SIZE * 0.5; - currentPar++; if (y < margens.bottom + FONT_SIZE * 1.5) break; }