diff --git a/src/server/services/PdfService.ts b/src/server/services/PdfService.ts index 1f384a4..7ef14a8 100644 --- a/src/server/services/PdfService.ts +++ b/src/server/services/PdfService.ts @@ -386,337 +386,149 @@ export class PdfService { // Simplificar texto antes de processar para evitar loops infinitos // Remove tags não suportadas como , , etc. text = text - .replace(/|<\/u>/gi, '') // Remover tags de sublinhados de alinhamento que não foram processados + .replace(/|<\/u>/gi, '') // Remover tags de sublinhados .replace(/]*>|<\/span>/gi, '') // Remover tags span .replace(/<[^>]*style=[^>]*>/gi, '') // Remover atributos style .replace(/<(?!\/?(?:b|i|strong|em)(?!\w)[^>]*>)[^>]*>/gi, ''); // Manter apenas b, i, strong, em - .replace(/<(?!\/?(?:b|i|strong|em)(?!\w)[^>]*>)[^>]*>/gi, ''); // Manter apenas b, i, strong, em + // Primeiro, extrair partes do texto com suas formatações - let i = 0;o, extrair partes do texto com suas formatações - while (i < text.length) { - // Limite de segurança para evitar loops infinitos - if (textParts.length > 1000) {itar loops infinitos - console.error('Limite de processamento de texto excedido'); - break;e.error('Limite de processamento de texto excedido'); - } break; - } - if (text.substring(i, i+3) === '' || text.substring(i, i+7) === '') { - boldActive = true;, i+3) === '' || text.substring(i, i+7) === '') { - currentFont = italicActive ? boldItalicFont : boldFont; - i += text.substring(i, i+3) === '' ? 3 : 7;boldFont; - } i += text.substring(i, i+3) === '' ? 3 : 7; - else if (text.substring(i, i+4) === '' || text.substring(i, i+8) === '') { - boldActive = false;ng(i, i+4) === '' || text.substring(i, i+8) === '') { - currentFont = italicActive ? italicFont : normalFont; - i += text.substring(i, i+4) === '' ? 4 : 8;lFont; - } i += text.substring(i, i+4) === '' ? 4 : 8; - else if (text.substring(i, i+3) === '' || text.substring(i, i+4) === '') { - italicActive = true;g(i, i+3) === '' || text.substring(i, i+4) === '') { - currentFont = boldActive ? boldItalicFont : italicFont; - i += text.substring(i, i+3) === '' ? 3 : 4;alicFont; - } i += text.substring(i, i+3) === '' ? 3 : 4; - else if (text.substring(i, i+4) === '' || text.substring(i, i+5) === '') { - italicActive = false;(i, i+4) === '' || text.substring(i, i+5) === '') { - currentFont = boldActive ? boldFont : normalFont; - i += text.substring(i, i+4) === '' ? 4 : 5;t; - } i += text.substring(i, i+4) === '' ? 4 : 5; - else { - // Encontrar o próximo início de tag - let nextTagPos = text.indexOf('<', i); - let nextTagPos = text.indexOf('<', i); - if (nextTagPos === -1) { - // Não há mais tags, pegar o restante do texto - textParts.push({ags, pegar o restante do texto - text: text.substring(i), - font: currentFonting(i), - });ont: currentFont - break; - } else { - // Pegar texto até a próxima tag - textParts.push({té a próxima tag - text: text.substring(i, nextTagPos), - font: currentFonting(i, nextTagPos), - });ont: currentFont - i = nextTagPos; - } i = nextTagPos; - } } - } } - } - // Processa partes de texto em palavras para quebra de linha - for (const part of textParts) {palavras para quebra de linha - const partWords = part.text.split(/\s+/); - const partWords = part.text.split(/\s+/); - for (let i = 0; i < partWords.length; i++) { - if (partWords[i].length === 0) continue; { - if (partWords[i].length === 0) continue; - const wordText = i < partWords.length - 1 ? partWords[i] + ' ' : partWords[i]; - const wordWidth = part.font.widthOfTextAtSize(wordText, fontSize);artWords[i]; - const wordWidth = part.font.widthOfTextAtSize(wordText, fontSize); - words.push({ - text: wordText, - font: part.font, - width: wordWidth - });idth: wordWidth - } }); - } } - } - // Processa palavras em linhas - for (let i = 0; i < words.length; i++) { - const word = words[i];s.length; i++) { - const word = words[i]; - if (currentLineWidth + word.width <= maxWidth) { - // A palavra cabe na linha atual<= maxWidth) { - currentLine.push(word);nha atual - currentLineWidth += word.width; - } else {tLineWidth += word.width; - // A palavra não cabe, desenha a linha atual e começa nova linha - // Primeiro verifica se há espaço vertical suficiente nova linha - if (y - lineHeight < marginBottom) {rtical suficiente - return null; // Não há espaço suficiente na página - } return null; // Não há espaço suficiente na página - } - // Desenha a linha com o alinhamento apropriado - y = drawFormattedLine(page, currentLine, x, y, maxWidth, align, fontSize, color); - y = drawFormattedLine(page, currentLine, x, y, maxWidth, align, fontSize, color); - // Começa uma nova linha com a palavra atual - currentLine = [word];nha com a palavra atual - currentLineWidth = word.width; - } currentLineWidth = word.width; - } } - } - // Desenha a última linha se houver palavras restantes - if (currentLine.length > 0) {houver palavras restantes - if (y - lineHeight < marginBottom) { - return null; // Não há espaço suficiente na página - } return null; // Não há espaço suficiente na página - } - y = drawFormattedLine(page, currentLine, x, y, maxWidth, align, fontSize, color); - } y = drawFormattedLine(page, currentLine, x, y, maxWidth, align, fontSize, color); - } - return y; - } return y; - } - /** - * Desenha uma linha formatada com alinhamento específico - */Desenha uma linha formatada com alinhamento específico - function drawFormattedLine( - page: any, FormattedLine( - line: Array<{text: string, font: any, width: number}>, - x: number,y<{text: string, font: any, width: number}>, - y: number, - maxWidth: number, - align: 'left' | 'center' | 'right' | 'justify', - fontSize: number,center' | 'right' | 'justify', - color: anynumber, - ): number {y - const lineHeight = fontSize * 1.5; - let currentX = x;= fontSize * 1.5; - let totalWidth = line.reduce((sum, word) => sum + word.width, 0); - let totalWidth = line.reduce((sum, word) => sum + word.width, 0); - // Calcula o espaço adicional entre palavras para justificação - let extraSpacing = 0;dicional entre palavras para justificação - if (align === 'justify' && line.length > 1 && totalWidth < maxWidth * 0.8) { - extraSpacing = (maxWidth - totalWidth) / (line.length - 1);xWidth * 0.8) { - } extraSpacing = (maxWidth - totalWidth) / (line.length - 1); - } - // Ajusta a posição x inicial baseada no alinhamento - if (align === 'center') {cial baseada no alinhamento - currentX = x + (maxWidth - totalWidth) / 2; - } else if (align === 'right') {talWidth) / 2; - currentX = x + maxWidth - totalWidth; - } currentX = x + maxWidth - totalWidth; - } - // Desenha cada palavra - for (let i = 0; i < line.length; i++) { - const word = line[i];e.length; i++) { - page.drawText(word.text, { - x: currentX,word.text, { - y: y,rrentX, - font: word.font, - size: fontSize,, - color: colorze, - });olor: color - }); - // Avança X para a próxima palavra - currentX += word.width;ima palavra - currentX += word.width; - // Adiciona espaço extra para justificação, exceto na última palavra - if (align === 'justify' && i < line.length - 1) {o na última palavra - currentX += extraSpacing;i < line.length - 1) { - } currentX += extraSpacing; - } } - } - // Retorna a nova posição Y após desenhar a linha - return y - lineHeight;ção Y após desenhar a linha - } return y - lineHeight; } + // Processe o texto com as novas funções melhoradas - const textoLimpo = stripUnsupportedTags( melhoradas + const textoLimpo = stripUnsupportedTags( this.processarTemplate(conteudo, escritorio) - );this.processarTemplate(conteudo, escritorio) + ); const texto = decodeHtmlEntities(textoLimpo); - const texto = decodeHtmlEntities(textoLimpo); - console.log('Texto processado para renderização:', texto.substring(0, 100) + '...'); 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 => {r melhor com formatações - let paragraph = p.trim();lit(/\n{2,}/).map(p => { + const paragrafos = texto.split(/\n{2,}/).map(p => { + let paragraph = p.trim(); if (!paragraph) return ''; - if (!paragraph) return ''; - // Extrair informações de formataçãoo a todos os parágrafos normais - let align = 'left'; // padrãorão é justificadomatação + // Extrair informações de formatação + let align = 'left'; // padrão é justificado const alignMatch = paragraph.match(/§§ALIGN=(center|right|justify)§§/); - if (alignMatch) { parece um título (começa com # ou tem menos de 50 caracteres e termina com :)= paragraph.match(/§§ALIGN=(center|right|justify)§§/); - align = alignMatch[1];('#') || + if (alignMatch) { + align = alignMatch[1]; paragraph = paragraph.replace(/§§ALIGN=(center|right|justify)§§/, ''); - } (paragraph.toUpperCase() === paragraph && paragraph.length < 100)) { paragraph = paragraph.replace(/§§ALIGN=(center|right|justify)§§/, ''); - align = 'center'; // centraliza títulos} - let indent = 0;aragraph.replace(/^#+\s*/, ''); // Remove marcadores de título # + } + let indent = 0; const indentMatch = paragraph.match(/§§INDENT=(\d+)(pt|px|em)§§/); - if (indentMatch) {/§§INDENT=(\d+)(pt|px|em)§§/); - indent = parseInt(indentMatch[1]);nhamento se estiverem presentes explicitamente + if (indentMatch) { + indent = parseInt(indentMatch[1]); if (indentMatch[2] === 'em') indent *= 12; // Converter em para pontos aproximadamente paragraph = paragraph.replace(/§§INDENT=\d+(pt|px|em)§§/, ''); - } align = alignMatch[1]; paragraph = paragraph.replace(/§§INDENT=\d+(pt|px|em)§§/, ''); - paragraph = paragraph.replace(/§§ALIGN=(center|right|justify|left)§§/, '');} + } return { text: paragraph.trim(), align, indent }; - }).filter(Boolean);=${indent}§§${paragraph.trim()}`; - let indent = 0;}).filter(Boolean); - let currentPar = 0; = paragraph.match(/§§INDENT=(\d+)(pt|px|em)?§§/); - let pageIndex = 0; {; + }).filter(Boolean); + let currentPar = 0; + let pageIndex = 0; console.log(`Gerando PDF com ${paragrafos.length} parágrafos`); - if (indentMatch[2] === 'em') indent *= 12; // Converter em para pontos aproximadamenteconsole.log(`Gerando PDF com ${paragrafos.length} parágrafos`); - // Define um contador máximo para evitar loops infinitos)?§§/, ''); - const MAX_PAGES = 100;loops infinitos + // Define um contador máximo para evitar loops infinitos + const MAX_PAGES = 100; 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]);+ 1} de ${paragrafos.length}`); - align: align as 'left' | 'center' | 'right' | 'justify',let page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]); + 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,Par = 0;awPage(timbradoPages[0], { - y: 0,ex = 0; - width: PAGE_WIDTH, com ${paragrafos.length} parágrafos`); + x: 0, + y: 0, + width: PAGE_WIDTH, height: PAGE_HEIGHT - });ne um contador máximo para evitar loops infinitoseight: PAGE_HEIGHT - }st MAX_PAGES = 100; }); - ile (currentPar < paragrafos.length && pageIndex < MAX_PAGES) {} - let y = PAGE_HEIGHT - margens.top;Index + 1}, processando parágrafo ${currentPar + 1} de ${paragrafos.length}`); - let processedAnyParagraph = false; // Flag para verificar se processamos algum parágrafo + }); + } + 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 while (currentPar < paragrafos.length) { - const paraObj = paragrafos[currentPar];ina ${pageIndex + 1}`); - page.drawPage(timbradoPages[0], {const paraText = paragrafos[currentPar]; - // Extrair propriedades do parágrafo + const paraObj = paragrafos[currentPar]; const text = paraObj.text; const align = paraObj.align || 'justify'; // Por padrão, justifica o texto - const indent = paraObj.indent || 0;IGHT= paraText.match(/^§§ALIGN=(center|justify|right)§§/); + const indent = paraObj.indent || 0; - let x = margens.left + indent;lign = alignMatch[1] as 'center' | 'justify' | 'left'; + let x = margens.left + indent; let parWidth = MAX_WIDTH - indent; - rgens.top; - // Se o texto for muito curto, avance para o próximo parágrafoaragraph = false; // Flag para verificar se processamos algum parágrafontation + // Se o texto for muito curto, avance para o próximo parágrafo if (text.length < 2) { - currentPar++;arágrafos na página atual= paraText.match(/§§INDENT=([0-9.]+)§§/); + currentPar++; processedAnyParagraph = true; - continue;onst paraText = paragrafos[currentPar]; indent = parseFloat(indentMatch[1]); - }} - ignment - console.log(`Tentando renderizar parágrafo ${currentPar + 1} em y=${y} com alinhamento ${align}`); 'center' | 'justify' = 'left'; - let yAfter = renderInlineFormattedParagraph(|justify|right)§§/); + continue; + } + console.log(`Tentando renderizar parágrafo ${currentPar + 1} em y=${y} com alinhamento ${align}`); + let yAfter = renderInlineFormattedParagraph( page, - text,alignMatch[1] as 'center' | 'justify' | 'left';(/§§INDENT=[0-9.]+§§/, '') + text, x, y, - font,// Extract indentationlet parWidth = MAX_WIDTH - indent; + font, fontBold, - fontItalic,raText.match(/§§INDENT=([0-9.]+)§§/);o curto, avance para o próximo parágrafo - fontBoldItalic,) { < 2) { - FONT_SIZE,ch[1]); - rgb(0, 0, 0),ph = true; - align,continue; - parWidth,// Clean text} + fontItalic, + fontBoldItalic, + FONT_SIZE, + rgb(0, 0, 0), + align, + parWidth, false, - margens.top,§/, ''){currentPar + 1} em y=${y}`); - margens.bottom,ace(/§§INDENT=[0-9.]+§§/, '')ter = renderInlineFormattedParagraph( - PAGE_HEIGHT(); - );x = margens.left + indent;xt, - parWidth = MAX_WIDTH - indent; + margens.top, + margens.bottom, + PAGE_HEIGHT + ); if (yAfter === null) { - // Se não conseguimos renderizar este parágrafo, vamos para a próxima páginato for muito curto, avance para o próximo parágrafo - // Mas só se já tivermos processado algum parágrafo nesta páginath < 2) { + // 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) { - console.log(`Página ${pageIndex + 1} está cheia, criando nova página`);nyParagraph = true;alic, + 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 infinitolog(`Tentando renderizar parágrafo ${currentPar + 1} em y=${y}`);th, - console.log(`Não foi possível processar o parágrafo ${currentPar + 1}, avançando`);enderInlineFormattedParagraph( + // 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; - }x,PAGE_HEIGHT - } else { y,); + } + } else { // Parágrafo foi renderizado com sucesso y = yAfter - FONT_SIZE * 0.5; currentPar++; - processedAnyParagraph = true; parágrafo nesta página + processedAnyParagraph = true; console.log(`Parágrafo ${currentPar} processado com sucesso, nova posição y=${y}`); - , 0),e.log(`Página ${pageIndex + 1} está cheia, criando nova página`); // Verifica se ainda temos espaço para pelo menos uma linha if (y < margens.bottom + FONT_SIZE * 2) { console.log(`Espaço insuficiente para próximo parágrafo, criando nova página`); break; - }Não foi possível processar o parágrafo ${currentPar + 1}, avançando`); + } } - }processedAnyParagraph = true; + } pageIndex++; - }zar este parágrafo, vamos para a próxima páginacom sucesso - já tivermos processado algum parágrafo nesta páginaFONT_SIZE * 0.5; + } if (pageIndex >= MAX_PAGES) { console.warn(`AVISO: Número máximo de páginas (${MAX_PAGES}) atingido. Geração de PDF interrompida.`); - } break;console.log(`Parágrafo ${currentPar} processado com sucesso, nova posição y=${y}`); + } - console.log(`PDF gerado com ${pageIndex} páginas`);parágrafo, algo está erradolo menos uma linha + console.log(`PDF gerado com ${pageIndex} páginas`); const pdfBytes = await pdfDoc.save(); - return Buffer.from(pdfBytes);e.log(`Não foi possível processar o parágrafo ${currentPar + 1}, avançando`);e.log(`Espaço insuficiente para próximo parágrafo, criando nova página`); - } catch (error) { currentPar++; break; - console.error('Erro geral ao gerar PDF:', error); processedAnyParagraph = true; } - throw error; } } - } } else {} - }rafo foi renderizado com sucesso - y = yAfter - FONT_SIZE * 0.5; pageIndex++; - private processarTemplate(template: string, dados: any): string { currentPar++;} - return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => { true; - const keyParts = key.trim().split('.'); - let value = dados; console.warn(`AVISO: Número máximo de páginas (${MAX_PAGES}) atingido. Geração de PDF interrompida.`); - for (const part of keyParts) { // Verifica se ainda temos espaço para pelo menos uma linha } - if (value && typeof value === 'object' && part in value) { - value = value[part];nte para próximo parágrafo, criando nova página`);ex} páginas`); - } else { - value = undefined;ytes); - break; - }eral ao gerar PDF:', error); - } throw error; - return value !== undefined ? value : match; pageIndex++; } - }); } } + return Buffer.from(pdfBytes); + } catch (error) { + console.error('Erro geral ao gerar PDF:', error); + throw error; + } } -} - - - - - - - - - - - - - -} } }); 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; \ No newline at end of file + 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; + } + } + return value !== undefined ? value : match; + }); + } +} \ No newline at end of file