diff --git a/src/server/services/PdfService.ts b/src/server/services/PdfService.ts
index 0ae8afa..6f523e3 100644
--- a/src/server/services/PdfService.ts
+++ b/src/server/services/PdfService.ts
@@ -284,35 +284,28 @@ export class PdfService {
}
}
- // Melhorar a função que remove tags HTML
function stripUnsupportedTags(html: string): string {
// Primeiro, substituir entidades e tags comuns
const cleanHtml = html
// Preserva quebras de linha HTML como marcadores especiais
.replace(/
|<\/p>
|<\/div>
]*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(/|/gi, '§§BOLD_START§§') - .replace(/<\/b>|<\/strong>/gi, '§§BOLD_END§§') - .replace(/|/gi, '§§ITALIC_START§§') - .replace(/<\/i>|<\/em>/gi, '§§ITALIC_END§§') - // Armazena alinhamento e indentação - .replace(/style="[^"]*text-align\s*:\s*(center|right|justify)[^"]*"/gi, (_, align) => `§§ALIGN=${align}§§`) - .replace(/style="[^"]*text-indent\s*:\s*(\d+(?:pt|px|em)[^"]*)"/gi, (_, indent) => `§§INDENT=${indent}§§`) - // Remove todas as outras tags HTML - .replace(/<[^>]+>/g, '') - // Restaura formatação de texto - .replace(/§§BOLD_START§§/g, '') - .replace(/§§BOLD_END§§/g, '') - .replace(/§§ITALIC_START§§/g, '') - .replace(/§§ITALIC_END§§/g, ''); - - return cleanHtml + .replace(/|/gi, '') + .replace(/<\/b>|<\/strong>/gi, '') + .replace(/|/gi, '') + .replace(/<\/i>|<\/em>/gi, '') // Normaliza espaços extras e quebras de linha .replace(/\n{3,}/g, '\n\n') .replace(/[ \t]+/g, ' '); + + return cleanHtml; } // Função para decodificar entidades HTML comuns @@ -355,264 +348,10 @@ export class PdfService { .replace(/>/g, '>'); } - /** - * Renderiza um parágrafo com formatação inline (negrito, itálico) - * @param page - Página do PDF - * @param text - Texto a ser renderizado - * @param x - Posição X inicial - * @param y - Posição Y inicial - * @param regularFont - Fonte normal - * @param boldFont - Fonte negrito - * @param italicFont - Fonte itálica - * @param boldItalicFont - Fonte negrito+itálica - * @param fontSize - Tamanho da fonte - * @param color - Cor do texto - * @param align - Alinhamento do texto - * @param maxWidth - Largura máxima - * @param debug - Modo debug - * @param marginTop - Margem superior - * @param marginBottom - Margem inferior - * @param pageHeight - Altura da página - * @returns Nova posição Y ou null se não couber na página - */ - function renderInlineFormattedParagraph( - page: any, - text: string, - x: number, - y: number, - regularFont: any, - boldFont: any, - italicFont: any, - boldItalicFont: any, - fontSize: number, - color: any, - align: 'left' | 'center' | 'right' | 'justify' = 'left', - maxWidth: number, - debug: boolean = false, - marginTop: number, - marginBottom: number, - pageHeight: number - ): number | null { - if (y < marginBottom || y > pageHeight - marginTop) { - return null; // Fora dos limites da página - } - - // Considera a linha inteira - const lineHeight = fontSize * 1.2; - - // Separa o texto em segmentos de formatação - const segments: { text: string; isBold: boolean; isItalic: boolean }[] = []; - let remaining = text; - - // Padrões para encontrar tags de formatação inline - const boldStartPattern = //i; - const boldEndPattern = /<\/b>/i; - const italicStartPattern = //i; - const italicEndPattern = /<\/i>/i; - - let boldActive = false; - let italicActive = false; - - // Extrai segmentos formatados do texto - while (remaining.length > 0) { - const boldStartMatch = remaining.search(boldStartPattern); - const boldEndMatch = remaining.search(boldEndPattern); - const italicStartMatch = remaining.search(italicStartPattern); - const italicEndMatch = remaining.search(italicEndPattern); - - // Encontra a próxima tag - let nextTagPos = -1; - let nextTagType = ''; - - if (boldStartMatch !== -1 && (nextTagPos === -1 || boldStartMatch < nextTagPos)) { - nextTagPos = boldStartMatch; - nextTagType = 'boldStart'; - } - - if (boldEndMatch !== -1 && (nextTagPos === -1 || boldEndMatch < nextTagPos)) { - nextTagPos = boldEndMatch; - nextTagType = 'boldEnd'; - } - - if (italicStartMatch !== -1 && (nextTagPos === -1 || italicStartMatch < nextTagPos)) { - nextTagPos = italicStartMatch; - nextTagType = 'italicStart'; - } - - if (italicEndMatch !== -1 && (nextTagPos === -1 || italicEndMatch < nextTagPos)) { - nextTagPos = italicEndMatch; - nextTagType = 'italicEnd'; - } - - if (nextTagPos === -1) { - // Não há mais tags, adicione o texto restante como um único segmento - segments.push({ - text: remaining, - isBold: boldActive, - isItalic: italicActive, - }); - break; - } else { - // Adiciona texto antes da tag - if (nextTagPos > 0) { - segments.push({ - text: remaining.substring(0, nextTagPos), - isBold: boldActive, - isItalic: italicActive, - }); - } - - // Atualiza o estado de formatação com base na tag - if (nextTagType === 'boldStart') { - boldActive = true; - remaining = remaining.substring(nextTagPos + 3); // tem 3 caracteres - } else if (nextTagType === 'boldEnd') { - boldActive = false; - remaining = remaining.substring(nextTagPos + 4); // tem 4 caracteres - } else if (nextTagType === 'italicStart') { - italicActive = true; - remaining = remaining.substring(nextTagPos + 3); // tem 3 caracteres - } else if (nextTagType === 'italicEnd') { - italicActive = false; - remaining = remaining.substring(nextTagPos + 4); // tem 4 caracteres - } - } - } - - // Função para quebrar um texto em palavras - const getWords = (text: string): string[] => { - return text.split(/(\s+)/).filter(word => word.length > 0); - }; - - // Calcular largura de texto - const getTextWidth = (text: string, font: any): number => { - return font.widthOfTextAtSize(text, fontSize); - }; - - // Organiza palavras em linhas, respeitando a formatação - const lines: { - segments: { text: string; isBold: boolean; isItalic: boolean }[]; - width: number; - }[] = []; - - let currentLine: { - segments: { text: string; isBold: boolean; isItalic: boolean }[]; - width: number; - } = { segments: [], width: 0 }; - - for (const segment of segments) { - const words = getWords(segment.text); - - for (const word of words) { - // Para espaços, apenas adicione-os diretamente - if (/^\s+$/.test(word)) { - if (currentLine.segments.length > 0) { - const lastSegment = currentLine.segments[currentLine.segments.length - 1]; - - // Apenas adicione espaço se não for o primeiro segmento da linha - lastSegment.text += word; - const font = getFont(lastSegment.isBold, lastSegment.isItalic); - currentLine.width += getTextWidth(word, font); - } - continue; - } - - const font = getFont(segment.isBold, segment.isItalic); - const wordWidth = getTextWidth(word, font); - - // Verifica se a palavra cabe na linha atual - if (currentLine.segments.length === 0 || currentLine.width + wordWidth <= maxWidth) { - // Tenta adicionar à última parte do mesmo tipo de formatação - if ( - currentLine.segments.length > 0 && - currentLine.segments[currentLine.segments.length - 1].isBold === segment.isBold && - currentLine.segments[currentLine.segments.length - 1].isItalic === segment.isItalic - ) { - currentLine.segments[currentLine.segments.length - 1].text += word; - } else { - // Adiciona novo segmento - currentLine.segments.push({ - text: word, - isBold: segment.isBold, - isItalic: segment.isItalic, - }); - } - - currentLine.width += wordWidth; - } else { - // A palavra não cabe, iniciar nova linha - lines.push(currentLine); - currentLine = { - segments: [ - { - text: word, - isBold: segment.isBold, - isItalic: segment.isItalic, - }, - ], - width: wordWidth, - }; - } - } - } - - // Adiciona a última linha se não estiver vazia - if (currentLine.segments.length > 0) { - lines.push(currentLine); - } - - // Função auxiliar para obter a fonte correta - function getFont(isBold: boolean, isItalic: boolean): any { - if (isBold && isItalic) return boldItalicFont; - if (isBold) return boldFont; - if (isItalic) return italicFont; - return regularFont; - } - - // Renderiza as linhas - let currentY = y; - - for (const line of lines) { - // Se não couber mais na página, retorna null - if (currentY - lineHeight < marginBottom) { - return null; - } - - let lineX = x; - - // Ajusta a posição X com base no alinhamento - if (align === 'center') { - lineX = x + (maxWidth - line.width) / 2; - } else if (align === 'right') { - lineX = x + maxWidth - line.width; - } - - // Renderiza cada segmento da linha - for (const segment of line.segments) { - const font = getFont(segment.isBold, segment.isItalic); - page.drawText(segment.text, { - x: lineX, - y: currentY, - font, - size: fontSize, - color, - }); - - lineX += getTextWidth(segment.text, font); - } - - // Move para a próxima linha - currentY -= lineHeight; - } - - return currentY; - } - // Processe o texto com as novas funções melhoradas const textoLimpo = stripUnsupportedTags( this.processarTemplate(conteudo, escritorio) ); - const texto = decodeHtmlEntities(textoLimpo); console.log('Texto processado para renderização:', texto.substring(0, 100) + '...'); @@ -661,10 +400,10 @@ export class PdfService { height: PAGE_HEIGHT }); } - + 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 paraText = paragrafos[currentPar]; @@ -688,7 +427,6 @@ export class PdfService { .replace(/^§§ALIGN=(center|justify|right)§§/, '') .replace(/§§INDENT=[0-9.]+§§/, '') .trim(); - let x = margens.left + indent; let parWidth = MAX_WIDTH - indent; @@ -718,7 +456,7 @@ export class PdfService { 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 @@ -749,7 +487,7 @@ export class PdfService { pageIndex++; } - + if (pageIndex >= MAX_PAGES) { console.warn(`AVISO: Número máximo de páginas (${MAX_PAGES}) atingido. Geração de PDF interrompida.`); }