From 287ed34abf378181eedd6f4b29647d476a3b020b Mon Sep 17 00:00:00 2001 From: Rodrigo Martins Date: Mon, 21 Apr 2025 22:04:31 -0300 Subject: [PATCH] tenta fix pdfservice.js --- src/server/services/PdfService.ts | 125 +++++++++++++++++------------- 1 file changed, 70 insertions(+), 55 deletions(-) diff --git a/src/server/services/PdfService.ts b/src/server/services/PdfService.ts index 8c17ece..25a2694 100644 --- a/src/server/services/PdfService.ts +++ b/src/server/services/PdfService.ts @@ -329,14 +329,11 @@ export class PdfService { .replace(//g, ''); } - // Função para renderizar texto com formatação inline (negrito, itálico, ambos) - function renderInlineFormattedText(page, text: string, x: number, y: number, font, fontBold, fontItalic, fontBoldItalic, fontSize: number, color, align: string, maxWidth: number) { - // Divide por tags , , , e combinações - // Exemplo: "Texto itálico negrito-itálico normal" - let cursor = x; - let tokens: { text: string, bold: boolean, italic: boolean }[] = []; + // Função para renderizar texto com formatação inline (negrito, itálico, ambos) e controle de quebra de linha + function renderInlineFormattedParagraph(page, text: string, x: number, y: number, font, fontBold, fontItalic, fontBoldItalic, fontSize: number, color, align: string, maxWidth: number) { + // Divide o texto em tokens de formatação (, , etc) let regex = /(<\/?(b|strong|i|em)>)/gi; - let stack: { bold: boolean, italic: boolean }[] = []; + let tokens: { text: string, bold: boolean, italic: boolean }[] = []; let lastIndex = 0; let match; let currentStyle = { bold: false, italic: false }; @@ -361,62 +358,80 @@ export class PdfService { italic: currentStyle.italic }); } - // Renderiza cada token com a fonte correta - let line = ''; - let lineTokens: { text: string, font: any }[] = []; + + // Junta todos os tokens em uma lista de palavras com formatação + let words: { text: string, font: any }[] = []; 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; - // Quebra em palavras para respeitar largura máxima - const words = token.text.split(' '); - for (let i = 0; i < words.length; i++) { - let word = words[i]; - let testLine = line ? line + ' ' + word : word; - let testWidth = 0; - let tempLineTokens = [...lineTokens, { text: (line ? ' ' : '') + word, font: usedFont }]; - for (const t of tempLineTokens) { - testWidth += t.font.widthOfTextAtSize(t.text, fontSize); - } - if (testWidth > maxWidth && line) { - // Renderiza a linha atual - let drawX = x; - if (align === 'center') { - drawX = x + (maxWidth - testWidth + usedFont.widthOfTextAtSize(word, fontSize)) / 2; - } - for (const t of lineTokens) { - page.drawText(t.text, { - x: drawX, - y, - size: fontSize, - font: t.font, - color - }); - drawX += t.font.widthOfTextAtSize(t.text, fontSize); - } - y -= fontSize * 1.25; - line = word; - lineTokens = [{ text: word, font: usedFont }]; - } else { - if (line) { - line += ' ' + word; - lineTokens.push({ text: ' ' + word, font: usedFont }); - } else { - line = word; - lineTokens.push({ text: word, font: usedFont }); - } + // Divide token.text em palavras, mantendo espaços + const split = token.text.split(/(\s+)/); + for (const part of split) { + if (part.length > 0) { + words.push({ text: part, font: usedFont }); } } } - // Renderiza última linha - if (line) { + + // Monta linhas respeitando largura máxima + let lines: { text: string, tokens: { text: string, font: any }[] }[] = []; + let currentLine = ''; + let currentTokens: { text: string, font: any }[] = []; + let currentWidth = 0; + for (const word of words) { + const wordWidth = word.font.widthOfTextAtSize(word.text, fontSize); + if (currentWidth + wordWidth > maxWidth && currentLine.trim() !== '') { + lines.push({ text: currentLine, tokens: currentTokens }); + currentLine = word.text; + currentTokens = [word]; + currentWidth = wordWidth; + } else { + currentLine += word.text; + currentTokens.push(word); + currentWidth += wordWidth; + } + } + if (currentLine.trim() !== '' || currentTokens.length > 0) { + lines.push({ text: currentLine, tokens: currentTokens }); + } + + // Renderiza cada linha + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; let drawX = x; - let totalWidth = lineTokens.reduce((acc, t) => acc + t.font.widthOfTextAtSize(t.text, fontSize), 0); + let totalWidth = line.tokens.reduce((acc, t) => acc + t.font.widthOfTextAtSize(t.text, fontSize), 0); + + // Centralizado if (align === 'center') { drawX = x + (maxWidth - totalWidth) / 2; } - for (const t of lineTokens) { + // Justificado (exceto última linha) + else if (align === 'justify' && i < lines.length - 1 && line.text.trim().length > 0) { + // Conta espaços + 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) { + page.drawText(t.text, { + x: drawX, + y, + size: fontSize, + font: t.font, + color + }); + if (/^\s+$/.test(t.text)) { + drawX += t.font.widthOfTextAtSize(t.text, fontSize) + extraSpace; + } else { + drawX += t.font.widthOfTextAtSize(t.text, fontSize); + } + } + y -= fontSize * 1.25; + continue; + } + + // Esquerda ou última linha justificada + for (const t of line.tokens) { page.drawText(t.text, { x: drawX, y, @@ -426,6 +441,7 @@ export class PdfService { }); drawX += t.font.widthOfTextAtSize(t.text, fontSize); } + y -= fontSize * 1.25; } return y; } @@ -470,7 +486,6 @@ export class PdfService { // Quebrar o texto em páginas let currentPar = 0; - let currentLine = 0; let pageIndex = 0; while (currentPar < paragrafos.length) { @@ -502,8 +517,8 @@ export class PdfService { parWidth -= FONT_SIZE * 2; } - // Renderiza texto com formatação inline (negrito, itálico, ambos) - y = renderInlineFormattedText( + // Renderiza texto com formatação inline (negrito, itálico, ambos) e controle de linhas + let yAfter = renderInlineFormattedParagraph( page, text, x, @@ -517,7 +532,7 @@ export class PdfService { align, parWidth ); - y -= FONT_SIZE * 0.5; + y = yAfter - FONT_SIZE * 0.5; currentPar++; if (y < margens.bottom + FONT_SIZE * 1.25) break;