diff --git a/src/server/services/PdfService.ts b/src/server/services/PdfService.ts index fc33479..5878380 100644 --- a/src/server/services/PdfService.ts +++ b/src/server/services/PdfService.ts @@ -341,11 +341,51 @@ export class PdfService { } // Fonte padrão do pdf-lib: TimesRoman, TimesBold, TimesItalic - // Usa apenas as fontes padrão do pdf-lib, sem try/catch, pois são SEMPRE válidas - const font = await pdfDoc.embedFont(StandardFonts.TimesRoman); - const fontBold = await pdfDoc.embedFont(StandardFonts.TimesBold); - const fontItalic = await pdfDoc.embedFont(StandardFonts.TimesItalic); - const fontBoldItalic = fontBold; // pdf-lib não tem TimesBoldItalic, usa bold + // Usa apenas as fontes padrão do pdf-lib, sem try/catch, e SEM VARIÁVEL UNDEFINED + const font = await pdfDoc.embedFont(StandardFonts.TimesRoman as string); + const fontBold = await pdfDoc.embedFont(StandardFonts.TimesBold as string); + const fontItalic = await pdfDoc.embedFont(StandardFonts.TimesItalic as string); + const fontBoldItalic = fontBold; + + // Função para quebrar texto em linhas respeitando largura máxima e manter quebras de linha simples + function wrapTextStyled(text: string, font: any, fontBold: any, fontItalic: any, fontBoldItalic: any, fontSize: number, maxWidth: number, indent: boolean, align: string, bold: boolean, italic: boolean): { text: string, font: any, indent: boolean, align: string, bold: boolean, italic: boolean }[] { + // Mantém quebras de linha simples para espaçamento + const rawLines = text.split('\n'); + const lines: { text: string, font: any, indent: boolean, align: string, bold: boolean, italic: boolean }[] = []; + for (const rawLine of rawLines) { + let line = rawLine; + // Detecta negrito e itálico inline + let isBold = bold, isItalic = italic; + if (/^|^/.test(line)) { isBold = true; line = line.replace(/^|^/, ''); } + if (/^|^/.test(line)) { isItalic = true; line = line.replace(/^|^/, ''); } + if (/<\/b>$|<\/strong>$/.test(line)) { isBold = true; line = line.replace(/<\/b>$|<\/strong>$/, ''); } + if (/<\/i>$|<\/em>$/.test(line)) { isItalic = true; line = line.replace(/<\/i>$|<\/em>$/, ''); } + line = line.replace(/<\/?(b|strong|i|em)>/gi, ''); + + // Seleciona fonte correta + let usedFont = font; + if (isBold && isItalic && fontBoldItalic) usedFont = fontBoldItalic; + else if (isBold && fontBold) usedFont = fontBold; + else if (isItalic && fontItalic) usedFont = fontItalic; + + // Quebra em palavras para justificar + const words = line.split(' '); + let currentLine = ''; + for (let i = 0; i < words.length; i++) { + const word = words[i]; + const testLine = currentLine ? currentLine + ' ' + word : word; + const width = usedFont.widthOfTextAtSize(testLine, fontSize); + if (width > maxWidth && currentLine) { + lines.push({ text: currentLine, font: usedFont, indent, align, bold: isBold, italic: isItalic }); + currentLine = word; + } else { + currentLine = testLine; + } + } + if (currentLine) lines.push({ text: currentLine, font: usedFont, indent, align, bold: isBold, italic: isItalic }); + } + return lines; + } // Quebrar o texto em páginas let currentPar = 0; @@ -366,6 +406,7 @@ export class PdfService { let y = PAGE_HEIGHT - margens.top; let firstLine = true; while (currentPar < paragrafos.length) { + // Detecta recuo e alinhamento por marcação let indent = paragrafos[currentPar].startsWith('§§INDENT§§'); let center = paragrafos[currentPar].startsWith('§§CENTER§§'); let align = center ? 'center' : 'justify'; @@ -375,45 +416,22 @@ export class PdfService { let bold = /^|^/.test(text); let italic = /^|^/.test(text); - // Remove tags de formatação para o texto puro - let cleanText = text.replace(/<\/?(b|strong|i|em)>/gi, ''); - - // Seleciona fonte correta - let usedFont = font; - if (bold && italic) usedFont = fontBoldItalic; - else if (bold) usedFont = fontBold; - else if (italic) usedFont = fontItalic; - - // Quebra em linhas respeitando largura máxima - const words = cleanText.split(' '); - let lines: string[] = []; - let current = ''; - for (let i = 0; i < words.length; i++) { - const word = words[i]; - const testLine = current ? current + ' ' + word : word; - const width = usedFont.widthOfTextAtSize(testLine, FONT_SIZE); - if (width > MAX_WIDTH - (indent ? FONT_SIZE * 2 : 0) && current) { - lines.push(current); - current = word; - } else { - current = testLine; - } - } - if (current) lines.push(current); - - for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) { + const styledLines = wrapTextStyled(text, font, fontBold, fontItalic, fontBoldItalic, FONT_SIZE, MAX_WIDTH - (indent ? FONT_SIZE * 2 : 0), indent, align, bold, italic); + let lineIdx = 0; + if (currentLine > 0) lineIdx = currentLine; + for (; lineIdx < styledLines.length; lineIdx++) { if (y < margens.bottom + LINE_HEIGHT) { currentLine = lineIdx; break; } - let line = lines[lineIdx]; + let { text, font: usedFont, indent, align, bold, italic } = styledLines[lineIdx]; let x = margens.left + (indent ? FONT_SIZE * 2 : 0); // Centralizado if (align === 'center') { - const textWidth = usedFont.widthOfTextAtSize(line, FONT_SIZE); + const textWidth = usedFont.widthOfTextAtSize(text, FONT_SIZE); x = margens.left + ((MAX_WIDTH - (indent ? FONT_SIZE * 2 : 0) - textWidth) / 2) + (indent ? FONT_SIZE * 2 : 0); - page.drawText(line, { + page.drawText(text, { x, y, size: FONT_SIZE, @@ -422,39 +440,39 @@ export class PdfService { }); } // Justificado (exceto última linha do parágrafo e centralizado) - else if (line.trim().length > 0 && lineIdx < lines.length - 1 && align === 'justify') { - const lineWords = line.split(' '); - if (lineWords.length > 1) { - const textWidth = usedFont.widthOfTextAtSize(line, FONT_SIZE); + else if (text.trim().length > 0 && lineIdx < styledLines.length - 1 && align === 'justify') { + const words = text.split(' '); + if (words.length > 1) { + const textWidth = usedFont.widthOfTextAtSize(text, FONT_SIZE); const spaceWidth = usedFont.widthOfTextAtSize(' ', FONT_SIZE); - const totalSpaces = lineWords.length - 1; + const totalSpaces = words.length - 1; const extraSpace = (MAX_WIDTH - (indent ? FONT_SIZE * 2 : 0) - textWidth) / totalSpaces; let cursor = x; - for (let w = 0; w < lineWords.length; w++) { - page.drawText(lineWords[w], { + for (let w = 0; w < words.length; w++) { + page.drawText(words[w], { x: cursor, y, size: FONT_SIZE, font: usedFont }); - if (w < lineWords.length - 1) { - cursor += usedFont.widthOfTextAtSize(lineWords[w], FONT_SIZE) + spaceWidth + extraSpace; + if (w < words.length - 1) { + cursor += usedFont.widthOfTextAtSize(words[w], FONT_SIZE) + spaceWidth + extraSpace; } } } else { - page.drawText(line, { x, y, size: FONT_SIZE, font: usedFont }); + page.drawText(text, { x, y, size: FONT_SIZE, font: usedFont }); } } else { - page.drawText(line, { x, y, size: FONT_SIZE, font: usedFont }); + page.drawText(text, { x, y, size: FONT_SIZE, font: usedFont }); } y -= FONT_SIZE * 1.25; // espaçamento entre linhas 1,25 currentLine = 0; firstLine = false; } - if (lines.length > 0) { + if (lineIdx >= styledLines.length) { y -= FONT_SIZE * 0.5; } - if (currentLine > 0) { + if (lineIdx < styledLines.length) { break; } currentPar++;