From 597236c525bef17933edd6b0055bd8c2aeb14dae Mon Sep 17 00:00:00 2001 From: Rodrigo Martins Date: Tue, 22 Apr 2025 14:49:30 -0300 Subject: [PATCH] tenta fix pdfservice.js --- src/server/services/PdfService.ts | 566 ++++++++++++++++-------------- 1 file changed, 294 insertions(+), 272 deletions(-) diff --git a/src/server/services/PdfService.ts b/src/server/services/PdfService.ts index 347e8ef..b3c2047 100644 --- a/src/server/services/PdfService.ts +++ b/src/server/services/PdfService.ts @@ -290,30 +290,30 @@ export class PdfService { // Preserva quebras de linha HTML como marcadores especiais .replace(/|<\/p>

|<\/div>

/gi, '\n') // Substitui parágrafos, divs e blockquote por marcadores de bloco + quebra de linha - .replace(/]*style="[^"]*text-align\s*:\s*(center|right|justify)[^"]*"[^>]*>/gi, - (match, align) => `

`) - .replace(/]*style="[^"]*text-align\s*:\s*(center|right|justify)[^"]*"[^>]*>/gi, - (match, align) => `

`) - .replace(/]*>|]*>|]*>/gi, '') - .replace(/<\/p>|<\/div>|<\/blockquote>/gi, '\n\n') - // Marca formatação de texto + .replace(/]*style="[^"]*text-align\s*:\s*(center|right|justify)[^"]*"[^>]*>/gi, >]*align="(center|right|justify)"[^>]*>/gi, + (match, align) => `

`)yle || alignAttr}§§`) + .replace(/]*style="[^"]*text-align\s*:\s*(center|right|justify)[^"]*"[^>]*>/gi, [^>]*align="(center|right|justify)"[^>]*>/gi, + (match, align) => `

`)e || alignAttr}§§`) + .replace(/]*>|]*>|]*>/gi, '')res de bloco + quebra de linha + .replace(/<\/p>|<\/div>|<\/blockquote>/gi, '\n\n'), '') + // Marca formatação de textolockquote>/gi, '\n\n') .replace(/|/gi, '') .replace(/<\/b>|<\/strong>/gi, '') - .replace(/|/gi, '') + .replace(/|/gi, ''), '') .replace(/<\/i>|<\/em>/gi, '') // Normaliza espaços extras e quebras de linha - .replace(/\n{3,}/g, '\n\n') + .replace(/\n{3,}/g, '\n\n')bém + .replace(/[ \t]+/g, ' '); // Usamos itálico para representar sublinhado + .replace(/<\/u>/gi, '') + return cleanHtml;paços extras e quebras de linha + } .replace(/\n{3,}/g, '\n\n') .replace(/[ \t]+/g, ' '); - - return cleanHtml; - } - // Função para decodificar entidades HTML comuns function decodeHtmlEntities(str: string): string { if (!str) return ''; - return str - .replace(/\r\n/g, '\n') + return strra decodificar entidades HTML comuns + .replace(/\r\n/g, '\n')(str: string): string { // Acentos e caracteres especiais .replace(/Á/g, 'Á').replace(/á/g, 'á') .replace(/É/g, 'É').replace(/é/g, 'é') @@ -325,168 +325,168 @@ export class PdfService { .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, 'ü') - // Símbolos e caracteres especiais - .replace(/º/g, 'º') - .replace(/ª/g, 'ª') - .replace(/§/g, '§') - .replace(/°/g, '°') + .replace(/Ü/g, 'Ü').replace(/ü/g, 'ü')'ã') + // Símbolos e caracteres especiais(/õ/g, 'õ') + .replace(/º/g, 'º')').replace(/ç/g, 'ç') + .replace(/ª/g, 'ª')').replace(/ñ/g, 'ñ') + .replace(/§/g, '§').replace(/ü/g, 'ü') + .replace(/°/g, '°')s especiais .replace(/ /g, ' ') - .replace(/&/g, '&') + .replace(/&/g, '&')) .replace(/"/g, '"') .replace(/'/g, "'") - .replace(/</g, '<') + .replace(/</g, '<')') .replace(/>/g, '>'); - } - - /** + } .replace(/"/g, '"') + .replace(/'/g, "'") + /** .replace(/</g, '<') * Renderiza um parágrafo com formatação inline (negrito, itálico) * @returns A posição Y após renderizar ou null se não coube na página */ function renderInlineFormattedParagraph( - page: any, - text: string, + page: any, um parágrafo com formatação inline (negrito, itálico) + text: string,osição Y após renderizar ou null se não coube na página x: number, - startY: number, + startY: number,lineFormattedParagraph( normalFont: any, boldFont: any, italicFont: any, boldItalicFont: any, fontSize: number, - color: any, + color: any,ny, align: 'left' | 'center' | 'right' | 'justify', - maxWidth: number, + maxWidth: number,ny, debug: boolean = false, marginTop: number, - marginBottom: number, + marginBottom: number,er' | 'right' | 'justify', pageHeight: number - ): number | null { + ): number | null { false, const lineHeight = fontSize * 1.5; - let y = startY; - let currentX = x; + let y = startY;umber, + let currentX = x;r let words: Array<{text: string, font: any, width: number}> = []; let currentLine: Array<{text: string, font: any, width: number}> = []; let currentLineWidth = 0; - + let currentX = x; // Verifica se o texto parece ser um título (todo em maiúsculas ou curto) - const isTitle = text.toUpperCase() === text || text.length < 50; - + const isTitle = text.toUpperCase() === text || text.length < 50; = []; + let currentLineWidth = 0; // Se for título, centralizar independente do alinhamento fornecido - const effectiveAlign = isTitle ? 'center' : align; - + const effectiveAlign = isTitle ? 'center' : align;em maiúsculas ou curto) + const isTitle = text.toUpperCase() === text || text.length < 50; // Processa texto com formatação - remover tags HTML e aplicar estilos - let currentFont = normalFont; - let boldActive = false; + let currentFont = normalFont; independente do alinhamento fornecido + let boldActive = false;isTitle ? 'center' : align; let italicActive = false; - + // Processa texto com formatação - remover tags HTML e aplicar estilos // Simplificar texto antes de processar para evitar loops infinitos // Remove tags não suportadas como , , etc. - text = text + text = textctive = false; .replace(/|<\/u>/gi, '') // Remover tags de sublinhados - .replace(/]*>|<\/span>/gi, '') // Remover tags span + .replace(/]*>|<\/span>/gi, '') // Remover tags spaninitos .replace(/<[^>]*style=[^>]*>/gi, '') // Remover atributos style .replace(/<(?!\/?(?:b|i|strong|em)(?!\w)[^>]*>)[^>]*>/gi, ''); // Manter apenas b, i, strong, em + .replace(/|<\/u>/gi, '') // Remover tags de sublinhados + // Primeiro, extrair partes do texto com suas formataçõesspan + let remainingText = text;^>]*>/gi, '') // Remover atributos style + let textParts: Array<{text: string, bold: boolean, italic: boolean}> = []; apenas b, i, strong, em - // Primeiro, extrair partes do texto com suas formatações - let remainingText = text; - let textParts: Array<{text: string, bold: boolean, italic: boolean}> = []; - - while (remainingText.length > 0) { + while (remainingText.length > 0) {to com suas formatações const boldStartMatch = remainingText.match(/|/i); - const italicStartMatch = remainingText.match(/|/i); + const italicStartMatch = remainingText.match(/|/i);oolean}> = []; const boldEndMatch = remainingText.match(/<\/b>|<\/strong>/i); const italicEndMatch = remainingText.match(/<\/i>|<\/em>/i); - - // Encontrar a próxima tag + const boldStartMatch = remainingText.match(/|/i); + // Encontrar a próxima tagemainingText.match(/|/i); const matches = [boldStartMatch, italicStartMatch, boldEndMatch, italicEndMatch] - .filter(m => m !== null); + .filter(m => m !== null);iningText.match(/<\/i>|<\/em>/i); if (matches.length === 0) { - // Não há mais tags, adicionar o texto restante + // Não há mais tags, adicionar o texto restante, boldEndMatch, italicEndMatch] if (remainingText.length > 0) { textParts.push({ - text: remainingText, - bold: boldActive, - italic: italicActive + text: remainingText,{ + bold: boldActive,adicionar o texto restante + italic: italicActive > 0) { + });tParts.push({ + } text: remainingText, + break;ld: boldActive, + } italic: italicActive }); - } - break; - } - // Ordenar matches por índice matches.sort((a, b) => a!.index! - b!.index!); const nextMatch = matches[0]!; const matchIndex = nextMatch.index!; - - // Texto antes da próxima tag - if (matchIndex > 0) { - textParts.push({ + // Ordenar matches por índice + // Texto antes da próxima tagex! - b!.index!); + if (matchIndex > 0) {ches[0]!; + textParts.push({ nextMatch.index!; text: remainingText.substring(0, matchIndex), - bold: boldActive, + bold: boldActive,xima tag italic: italicActive - }); - } - + });tParts.push({ + } text: remainingText.substring(0, matchIndex), + bold: boldActive, // Atualizar estado de formatação if (nextMatch[0].match(/|/i)) boldActive = true; else if (nextMatch[0].match(/<\/b>|<\/strong>/i)) boldActive = false; else if (nextMatch[0].match(/|/i)) italicActive = true; else if (nextMatch[0].match(/<\/i>|<\/em>/i)) italicActive = false; - - // Remover o texto processado e a tag + if (nextMatch[0].match(/|/i)) boldActive = true; + // Remover o texto processado e a tag/strong>/i)) boldActive = false; remainingText = remainingText.substring(matchIndex + nextMatch[0].length); - } - - // Processar as partes extraídas - for (const part of textParts) { + } else if (nextMatch[0].match(/<\/i>|<\/em>/i)) italicActive = false; + + // Processar as partes extraídase a tag + for (const part of textParts) {.substring(matchIndex + nextMatch[0].length); // Determinar a fonte baseado na formatação let font = normalFont; if (part.bold && part.italic) font = boldItalicFont; else if (part.bold) font = boldFont; - else if (part.italic) font = italicFont; - - // Dividir em palavras + else if (part.italic) font = italicFont;ção + let font = normalFont; + // Dividir em palavrasitalic) font = boldItalicFont; const partWords = part.text.split(/\s+/); - + else if (part.italic) font = italicFont; for (const wordText of partWords) { if (!wordText) continue; - + nst partWords = part.text.split(/\s+/); const width = font.widthOfTextAtSize(wordText, fontSize); words.push({ text: wordText, font, width }); words.push({ text: ' ', font, width: font.widthOfTextAtSize(' ', fontSize) }); - } - - // Remove o último espaço adicionado - if (words.length > 0) { + } + const width = font.widthOfTextAtSize(wordText, fontSize); + // Remove o último espaço adicionado width }); + if (words.length > 0) {', font, width: font.widthOfTextAtSize(' ', fontSize) }); words.pop(); } - } - + } // Remove o último espaço adicionado + if (words.length > 0) { // Verificar se há espaço suficiente na página para pelo menos uma linha if (y - lineHeight < marginBottom) { return null; // Não há espaço suficiente para renderizar } - + // Verificar se há espaço suficiente na página para pelo menos uma linha // Aumentar ligeiramente o espaçamento de linhas para títulos const effectiveLineHeight = isTitle ? lineHeight * 1.3 : lineHeight; - + } // Aumentar o tamanho da fonte para títulos const effectiveFontSize = isTitle ? fontSize * 1.2 : fontSize; - + const effectiveLineHeight = isTitle ? lineHeight * 1.3 : lineHeight; // Se for um título, adicionar um espaço extra antes - if (isTitle && y < startY) { - y -= lineHeight * 0.5; + if (isTitle && y < startY) {te para títulos + y -= lineHeight * 0.5;= isTitle ? fontSize * 1.2 : fontSize; } - + // Se for um título, adicionar um espaço extra antes // Agora, renderizar as palavras em linhas for (let i = 0; i < words.length; i++) { const word = words[i]; @@ -495,280 +495,302 @@ export class PdfService { if (currentLineWidth + word.width <= maxWidth || currentLine.length === 0) { // Adicionar palavra à linha atual currentLine.push(word); - currentLineWidth += word.width; - } else { - // Renderizar a linha atual + currentLineWidth += word.width; linha atual + } else {entLineWidth + word.width <= maxWidth || currentLine.length === 0) { + // Renderizar a linha atuala atual if (y - effectiveLineHeight < marginBottom) { return null; // Não há mais espaço na página + }lse { + // Renderizar a linha atual + // Ajustar X baseado no alinhamentonBottom) { + let lineX = x; // Não há mais espaço na página } - - // Ajustar X baseado no alinhamento - let lineX = x; - // Para justificado, calcular espaçamentos extras entre palavras // Mas não justificar a última linha de um parágrafo ou títulos const isLastLine = i === words.length - 1; const shouldJustify = effectiveAlign === 'justify' && !isLastLine && !isTitle && currentLine.length > 1; - const extraSpacing = shouldJustify ? + const extraSpacing = shouldJustify ? entos extras entre palavras (maxWidth - currentLineWidth) / (currentLine.length - 1) : 0; - - if (effectiveAlign === 'center') { - lineX = x + (maxWidth - currentLineWidth) / 2; - } else if (effectiveAlign === 'right') { + const isLastLine = i === words.length - 1; + if (effectiveAlign === 'center') {gn === 'justify' && !isLastLine && !isTitle && currentLine.length > 1;lavras, mesmo para não justificado + lineX = x + (maxWidth - currentLineWidth) / 2;ntre palavras + } else if (effectiveAlign === 'right') {rentLine.length - 1) : 0; lineX = x + maxWidth - currentLineWidth; - } - - // Desenhar a linha - let posX = lineX; + }f (effectiveAlign === 'center') { lineX = x + (maxWidth - currentLineWidth) / 2; + lineX = x + (maxWidth - currentLineWidth) / 2;} else if (effectiveAlign === 'right') { + // Desenhar a linhaeAlign === 'right') {dth - currentLineWidth; + let posX = lineX;Width - currentLineWidth; for (let j = 0; j < currentLine.length; j++) { const item = currentLine[j]; const itemFontSize = isTitle ? effectiveFontSize : fontSize; - - page.drawText(item.text, { - x: posX, - y: y, + t posX = lineX;r (let j = 0; j < currentLine.length; j++) { + page.drawText(item.text, {ine.length; j++) {]; + x: posX, = currentLine[j];FontSize = isTitle ? effectiveFontSize : fontSize; + y: y,temFontSize = isTitle ? effectiveFontSize : fontSize; font: item.font, - size: itemFontSize, - color - }); - - // Avançar posição X + size: itemFontSize,xt, { + colorsX, + });: y,ont: item.font, + font: item.font, size: itemFontSize, + // Avançar posição X, posX += item.width; - + }); // Adicionar espaço extra se for justificado (exceto no final da linha) if (shouldJustify && j < currentLine.length - 1) { posX += extraSpacing; - } - } - - // Avançar para a próxima linha + } Adicionar espaço extra entre palavras + } // Adicionar espaço extra se for justificado (exceto no final da linha) if (j < currentLine.length - 1) { + if (shouldJustify && j < currentLine.length - 1) { // Usar espaçamento mínimo ou espaçamento justificado, o que for maior + // Avançar para a próxima linhay ? Math.max(extraSpacing, minSpacing) : minSpacing; y -= effectiveLineHeight; currentLine = [word]; currentLineWidth = word.width; - + // Avançar para a próxima linha // Verificar se ainda temos espaço if (y - effectiveLineHeight < marginBottom) { - return null; - } - } - } - + return null;th = word.width;[word]; + }rrentLineWidth = word.width; + } // Verificar se ainda temos espaço + } if (y - effectiveLineHeight < marginBottom) { // Verificar se ainda temos espaço + return null; if (y - effectiveLineHeight < marginBottom) { // Renderizar a última linha, se houver if (currentLine.length > 0) { // Ajustar X baseado no alinhamento (última linha nunca é justificada, exceto se for curta) let lineX = x; - + Renderizar a última linha, se houver // Se for título ou alinhado ao centro/direita, ajustar posição - if (effectiveAlign === 'center') { - lineX = x + (maxWidth - currentLineWidth) / 2; + if (effectiveAlign === 'center') {o (última linha nunca é justificada, exceto se for curta) + lineX = x + (maxWidth - currentLineWidth) / 2;or curta) } else if (effectiveAlign === 'right') { - lineX = x + maxWidth - currentLineWidth; - } - - // Desenhar a linha - let posX = lineX; + lineX = x + maxWidth - currentLineWidth;eita, ajustar posição + }f (effectiveAlign === 'center') {/ Se for título ou alinhado ao centro/direita, ajustar posição + lineX = x + (maxWidth - currentLineWidth) / 2;if (effectiveAlign === 'center') { + // Desenhar a linhaeAlign === 'right') {idth - currentLineWidth) / 2; + let posX = lineX;Width - currentLineWidth;iveAlign === 'right') { for (const item of currentLine) { const itemFontSize = isTitle ? effectiveFontSize : fontSize; - + Desenhar a linha page.drawText(item.text, { - x: posX, - y: y, - font: item.font, - size: itemFontSize, - color - }); + x: posX,tem of currentLine) {ineX; + y: y,temFontSize = isTitle ? effectiveFontSize : fontSize;t item of currentLine) { + font: item.font,tiveFontSize : fontSize; + size: itemFontSize,xt, { + colorsX,awText(item.text, { + });: y,: posX, posX += item.width; - } - + } size: itemFontSize, font: item.font, + color size: itemFontSize, y -= effectiveLineHeight; - } - + } posX += item.width; }); + } posX += item.width; // Se for título, adicionar um espaço extra depois - if (isTitle) { + if (isTitle) {veLineHeight; y -= lineHeight * 0.5; } - - return y; - } - + // Se for título, adicionar um espaço extra depois + return y;le) { título, adicionar um espaço extra depois + } y -= lineHeight * 0.5; if (isTitle) { + } y -= lineHeight * 0.5; // Processe o texto com as novas funções melhoradas const textoLimpo = stripUnsupportedTags( this.processarTemplate(conteudo, escritorio) ); - const texto = decodeHtmlEntities(textoLimpo); + const texto = decodeHtmlEntities(textoLimpo);oradas console.log('Texto processado para renderização:', texto.substring(0, 100) + '...'); // Corrigir o processamento de parágrafos para lidar melhor com formatações const paragrafos = texto.split(/\n{2,}/).map(p => { - let paragraph = p.trim(); - if (!paragraph) return ''; - - // Extrair informações de formatação - let align = 'justify'; // Padrão agora é justificado para texto normal - - // Verifica se o texto parece ser um título por diversos critérios: - // 1. Começa com '#' (estilo markdown) - // 2. Todo em maiúsculas e relativamente curto - // 3. Começa com números seguidos de ponto (como "1. Título") - // 4. Termina com dois pontos e é curto (como "Considerando:") - const isTitle = - paragraph.startsWith('#') || - (paragraph.toUpperCase() === paragraph && paragraph.length < 100) || - /^\d+\.\s+\S+/.test(paragraph) || - (paragraph.endsWith(':') && paragraph.length < 70); - - // Se for um título, centraliza - if (isTitle) { - align = 'center'; - paragraph = paragraph.replace(/^#+\s*/, ''); // Remove marcadores de título estilo markdown - } - - // Processa alinhamentos explícitos (têm precedência) + let paragraph = p.trim();ities(textoLimpo); + if (!paragraph) return '';o para renderização:', texto.substring(0, 100) + '...');ties(textoLimpo); + Corrigir o processamento de parágrafos para lidar melhor com formataçõesnsole.log('Texto processado para renderização:', texto.substring(0, 100) + '...'); + // Extrair informações de formatação/).map(p => {fos para lidar melhor com formatações + let align = 'left'; // Default align + if (!paragraph) return '';let paragraph = p.trim(); + // Verificar se há marcadores de alinhamento definidos pelo HTML const alignMatch = paragraph.match(/§§ALIGN=(center|right|justify|left)§§/); - if (alignMatch) { + if (alignMatch) {ficado para texto normal align = alignMatch[1]; - paragraph = paragraph.replace(/§§ALIGN=(center|right|justify|left)§§/, ''); + paragraph = paragraph.replace(/§§ALIGN=(center|right|justify|left)§§/, '');rios: + } '#' (estilo markdown) texto parece ser um título por diversos critérios: + ativamente curtoarkdown) + // Verifica se o texto tem formatação que indica um título + const hasStrongFormatting = paragraph.includes('') && paragraph.includes('');urto (como "Considerando:")e ponto (como "1. Título") + const hasUnderline = paragraph.includes('') && paragraph.includes(''); + paragraph.startsWith('#') ||const isTitle = + // Verifica se o texto parece ser um título por diversos critérios:paragraph && paragraph.length < 100) || + // 1. Começa com '#' (estilo markdown)+/.test(paragraph) ||oUpperCase() === paragraph && paragraph.length < 100) || + // 2. Todo em maiúsculas e relativamente curtoth(':') && paragraph.length < 70);st(paragraph) || + // 3. Começa com números seguidos de ponto (como "1. Título") + // 4. Termina com dois pontos e é curto (como "Considerando:")/ Se for um título, centraliza + // 5. Tem formatação especial como negrito ou sublinhado e é curtoif (isTitle) {// Se for um título, centraliza + const isTitle = + paragraph.startsWith('#') ||o estilo markdown + (paragraph.toUpperCase() === paragraph && paragraph.length < 100) ||^#+\s*/, ''); // Remove marcadores de título estilo markdown + /^\d+\.\s+\S+/.test(paragraph) || + (paragraph.endsWith(':') && paragraph.length < 70) || + ((hasStrongFormatting || hasUnderline) && paragraph.length < 150);onst alignMatch = paragraph.match(/§§ALIGN=(center|right|justify|left)§§/);/ Processa alinhamentos explícitos (têm precedência) + if (alignMatch) {const alignMatch = paragraph.match(/§§ALIGN=(center|right|justify|left)§§/); + // Se for um título e não tiver alinhamento definido explicitamente, centralizaMatch[1]; { + if (isTitle && !alignMatch) {)§§/, ''); + align = 'center';ALIGN=(center|right|justify|left)§§/, ''); } - let indent = 0; - const indentMatch = paragraph.match(/§§INDENT=(\d+)(pt|px|em)?§§/); - if (indentMatch) { + // Se for um texto normal sem alinhamento definido, justifica); + if (!isTitle && !alignMatch) {f (indentMatch) {onst indentMatch = paragraph.match(/§§INDENT=(\d+)(pt|px|em)?§§/); + align = 'justify'; indent = parseInt(indentMatch[1]);if (indentMatch) { + }entMatch[2] === 'em') indent *= 12; // Converter em para pontos aproximadamente= parseInt(indentMatch[1]); + place(/§§INDENT=\d+(pt|px|em)?§§/, '');em') indent *= 12; // Converter em para pontos aproximadamente + // Remove marcadores de título estilo markdown + paragraph = paragraph.replace(/^#+\s*/, ''); + + let indent = 0;text: paragraph.trim(), turn { + const indentMatch = paragraph.match(/§§INDENT=(\d+)(pt|px|em)?§§/);ft' | 'center' | 'right' | 'justify',m(), + if (indentMatch) {| 'center' | 'right' | 'justify', indent = parseInt(indentMatch[1]); if (indentMatch[2] === 'em') indent *= 12; // Converter em para pontos aproximadamente paragraph = paragraph.replace(/§§INDENT=\d+(pt|px|em)?§§/, ''); } return { - text: paragraph.trim(), - align: align as 'left' | 'center' | 'right' | 'justify', - indent, - isTitle - }; - }).filter(p => p.text); - let currentPar = 0; - let pageIndex = 0; - console.log(`Gerando PDF com ${paragrafos.length} parágrafos`); - // Define um contador máximo para evitar loops infinitos - const MAX_PAGES = 100; + text: paragraph.trim(), srafos`); + align: align as 'left' | 'center' | 'right' | 'justify',oops infinitos + indent,ngth && pageIndex < MAX_PAGES) { + isTitlefo ${currentPar + 1} de ${paragrafos.length}`); + };TH, PAGE_HEIGHT]);ndex + 1}, processando parágrafo ${currentPar + 1} de ${paragrafos.length}`); + }).filter(p => p.text);nar timbrado à página= pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]); + let currentPar = 0;adoPages.length > 0) {nar timbrado à página + let pageIndex = 0;ando timbrado na página ${pageIndex + 1}`);th > 0) { + console.log(`Gerando PDF com ${paragrafos.length} parágrafos`);oPages[0], {ndo timbrado na página ${pageIndex + 1}`); + // Define um contador máximo para evitar loops infinitos: 0,e.drawPage(timbradoPages[0], { + const MAX_PAGES = 100; y: 0, x: 0, while (currentPar < paragrafos.length && pageIndex < MAX_PAGES) { console.log(`Criando página ${pageIndex + 1}, processando parágrafo ${currentPar + 1} de ${paragrafos.length}`); let page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]); // Adicionar timbrado à página if (timbradoPages.length > 0) { - console.log(`Adicionando timbrado na página ${pageIndex + 1}`); - page.drawPage(timbradoPages[0], { - x: 0, + console.log(`Adicionando timbrado na página ${pageIndex + 1}`);false; // Flag para verificar se processamos algum parágrafos.top; + page.drawPage(timbradoPages[0], {na página atuallse; // Flag para verificar se processamos algum parágrafo + x: 0,) {na atual y: 0, - width: PAGE_WIDTH, + width: PAGE_WIDTH,const text = paraObj.text;const paraObj = paragrafos[currentPar]; height: PAGE_HEIGHT - }); - } + });; + }const isTitle = paraObj.isTitle || false;const indent = paraObj.indent || 0; let y = PAGE_HEIGHT - margens.top; - let processedAnyParagraph = false; // Flag para verificar se processamos algum parágrafo - // Tenta processar parágrafos na página atual + let processedAnyParagraph = false; // Flag para verificar se processamos algum parágrafo indent; + // Tenta processar parágrafos na página atualMAX_WIDTH - indent;.left + indent; while (currentPar < paragrafos.length) { - const paraObj = paragrafos[currentPar]; - const text = paraObj.text; - const align = paraObj.align; + const paraObj = paragrafos[currentPar];to for muito curto, avance para o próximo parágrafo + const text = paraObj.text;f (text.length < 2) {/ Se o texto for muito curto, avance para o próximo parágrafo + const align = paraObj.align; currentPar++;if (text.length < 2) { const indent = paraObj.indent || 0; const isTitle = paraObj.isTitle || false; - + } continue; let x = margens.left + indent; - let parWidth = MAX_WIDTH - indent; - - // Se o texto for muito curto, avance para o próximo parágrafo - if (text.length < 2) { - currentPar++; - processedAnyParagraph = true; - continue; - } - + let parWidth = MAX_WIDTH - indent;os + sTitle ? FONT_SIZE * 1.2 : FONT_SIZE;fonte para títulos + // Se o texto for muito curto, avance para o próximo parágrafot fontSizeToUse = isTitle ? FONT_SIZE * 1.2 : FONT_SIZE; + if (text.length < 2) {ágrafo da página) + currentPar++; o primeiro parágrafo da página) + processedAnyParagraph = true;T_SIZE * 0.8;e && processedAnyParagraph) { + continue;-= FONT_SIZE * 0.8; + } // Se não caber na página depois do espaço, vá para a próxima página + if (y < margens.bottom + fontSizeToUse * 2) { // Se não caber na página depois do espaço, vá para a próxima página // Ajuste o tamanho da fonte para títulos - const fontSizeToUse = isTitle ? FONT_SIZE * 1.2 : FONT_SIZE; + const fontSizeToUse = isTitle ? FONT_SIZE * 1.2 : FONT_SIZE; } break; // Adicionar espaço extra antes de títulos (exceto para o primeiro parágrafo da página) - if (isTitle && processedAnyParagraph) { - y -= FONT_SIZE * 0.8; - - // Se não caber na página depois do espaço, vá para a próxima página + if (isTitle && processedAnyParagraph) {.log(`Tentando renderizar parágrafo ${currentPar + 1} em y=${y} com alinhamento ${align}${isTitle ? ' (título)' : ''}`); + y -= FONT_SIZE * 0.8;log(`Tentando renderizar parágrafo ${currentPar + 1} em y=${y} com alinhamento ${align}${isTitle ? ' (título)' : ''}`); + yAfter = renderInlineFormattedParagraph( + // Se não caber na página depois do espaço, vá para a próxima páginater = renderInlineFormattedParagraph( if (y < margens.bottom + fontSizeToUse * 2) { break; } } - console.log(`Tentando renderizar parágrafo ${currentPar + 1} em y=${y} com alinhamento ${align}${isTitle ? ' (título)' : ''}`); - - let yAfter = renderInlineFormattedParagraph( - page, + console.log(`Tentando renderizar parágrafo ${currentPar + 1} em y=${y} com alinhamento ${align}${isTitle ? ' (título)' : ''}`);alic,ld, + talic,c, + let yAfter = renderInlineFormattedParagraph(zeToUse,ldItalic, + page,,e, text, x, - y, + y,false,parWidth, font, fontBold, fontItalic, fontBoldItalic, fontSizeToUse, - rgb(0, 0, 0), - align, + rgb(0, 0, 0),o conseguimos renderizar este parágrafo, vamos para a próxima página === null) { + align,ó se já tivermos processado algum parágrafo nesta páginao conseguimos renderizar este parágrafo, vamos para a próxima página parWidth, - false, + false,; margens.top, margens.bottom, - PAGE_HEIGHT - ); - if (yAfter === null) { - // Se não conseguimos renderizar este parágrafo, vamos para a próxima página - // Mas só se já tivermos processado algum parágrafo nesta página - if (processedAnyParagraph) { + PAGE_HEIGHTcessar um parágrafo, algo está errado + ); // Vamos avançar para o próximo parágrafo para evitar um loop infinito // Se não conseguimos nem processar um parágrafo, algo está errado + if (yAfter === null) {ole.log(`Não foi possível processar o parágrafo ${currentPar + 1}, avançando`);amos avançar para o próximo parágrafo para evitar um loop infinito + // Se não conseguimos renderizar este parágrafo, vamos para a próxima páginar + 1}, avançando`); + // Mas só se já tivermos processado algum parágrafo nesta páginae; + if (processedAnyParagraph) { true; console.log(`Página ${pageIndex + 1} está cheia, criando nova página`); break; } else { // Se não conseguimos nem processar um parágrafo, algo está errado // Vamos avançar para o próximo parágrafo para evitar um loop infinito - console.log(`Não foi possível processar o parágrafo ${currentPar + 1}, avançando`); - currentPar++; - processedAnyParagraph = true; - } - } else { + console.log(`Não foi possível processar o parágrafo ${currentPar + 1}, avançando`);log(`Parágrafo ${currentPar} processado com sucesso, nova posição y=${y}`);dAnyParagraph = true; + currentPar++;/ Verifica se ainda temos espaço para pelo menos uma linhaonsole.log(`Parágrafo ${currentPar} processado com sucesso, nova posição y=${y}`); + processedAnyParagraph = true; if (y < margens.bottom + FONT_SIZE * 2) { // Verifica se ainda temos espaço para pelo menos uma linha + } console.log(`Espaço insuficiente para próximo parágrafo, criando nova página`); if (y < margens.bottom + FONT_SIZE * 2) { + } else { break; console.log(`Espaço insuficiente para próximo parágrafo, criando nova página`); // Parágrafo foi renderizado com sucesso - y = yAfter - FONT_SIZE * 0.5; + y = yAfter - FONT_SIZE * 0.5; } } currentPar++; processedAnyParagraph = true; - console.log(`Parágrafo ${currentPar} processado com sucesso, nova posição y=${y}`); - // Verifica se ainda temos espaço para pelo menos uma linha + console.log(`Parágrafo ${currentPar} processado com sucesso, nova posição y=${y}`); pageIndex++; + // Verifica se ainda temos espaço para pelo menos uma linha } pageIndex++; if (y < margens.bottom + FONT_SIZE * 2) { - console.log(`Espaço insuficiente para próximo parágrafo, criando nova página`); - break; + console.log(`Espaço insuficiente para próximo parágrafo, criando nova página`);de páginas (${MAX_PAGES}) atingido. Geração de PDF interrompida.`); + break;GES}) atingido. Geração de PDF interrompida.`); } - } - } - - pageIndex++; - } + }); + }es = await pdfDoc.save();`PDF gerado com ${pageIndex} páginas`); + return Buffer.from(pdfBytes); const pdfBytes = await pdfDoc.save(); + pageIndex++; } catch (error) { return Buffer.from(pdfBytes); + } console.error('Erro geral ao gerar PDF:', error); } catch (error) { if (pageIndex >= MAX_PAGES) { console.warn(`AVISO: Número máximo de páginas (${MAX_PAGES}) atingido. Geração de PDF interrompida.`); } - console.log(`PDF gerado com ${pageIndex} páginas`); + console.log(`PDF gerado com ${pageIndex} páginas`);: string, dados: any): string { const pdfBytes = await pdfDoc.save(); - return Buffer.from(pdfBytes); - } catch (error) { - console.error('Erro geral ao gerar PDF:', error); - throw error; - } - } + return Buffer.from(pdfBytes);m().split('.');{\{([^}]+)\}\}/g, (match, key) => { + } catch (error) {= dados;arts = key.trim().split('.'); + console.error('Erro geral ao gerar PDF:', error);Parts) { + throw error;e && typeof value === 'object' && part in value) { part of keyParts) { + } value = value[part];f (value && typeof value === 'object' && part in value) { + } } else { value = value[part]; - private processarTemplate(template: string, dados: any): string { - return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => { - const keyParts = key.trim().split('.'); - let value = dados; - for (const part of keyParts) { - if (value && typeof value === 'object' && part in value) { - value = value[part]; - } else { - value = undefined; - break; - } - } + private processarTemplate(template: string, dados: any): string { break; value = undefined; + return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => { } break; + const keyParts = key.trim().split('.'); } } + + + + + + + + + + + + + +} } }); return value !== undefined ? value : match; } } break; value = undefined; } else { value = value[part]; if (value && typeof value === 'object' && part in value) { for (const part of keyParts) { let value = dados; + + + +} } }); return value !== undefined ? value : match; } return value !== undefined ? value : match; }); }