tenta fix pdfservice.js

This commit is contained in:
rodrigo committed 2025-04-22 11:57:14 -03:00
1 parent 20b2f0e3c9
commit 19f0187227
1 file changed
+14 -276
+14 -276
View File
@@ -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(/<br\s*\/?>|<\/p><p>|<\/div><div>/gi, '\n')
// Substitui parágrafos, divs e blockquote por marcadores de bloco + quebra de linha
.replace(/<p[^>]*style="[^"]*text-align\s*:\s*(center|right|justify)[^"]*"[^>]*>/gi,
(match, align) => `<p style="text-align:${align}">`)
.replace(/<div[^>]*style="[^"]*text-align\s*:\s*(center|right|justify)[^"]*"[^>]*>/gi,
(match, align) => `<div style="text-align:${align}">`)
.replace(/<p[^>]*>|<div[^>]*>|<blockquote[^>]*>/gi, '')
.replace(/<\/p>|<\/div>|<\/blockquote>/gi, '\n\n')
// Marca formatação de texto
.replace(/<b>|<strong>/gi, '§§BOLD_START§§')
.replace(/<\/b>|<\/strong>/gi, '§§BOLD_END§§')
.replace(/<i>|<em>/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, '<b>')
.replace(/§§BOLD_END§§/g, '</b>')
.replace(/§§ITALIC_START§§/g, '<i>')
.replace(/§§ITALIC_END§§/g, '</i>');
return cleanHtml
.replace(/<b>|<strong>/gi, '<b>')
.replace(/<\/b>|<\/strong>/gi, '</b>')
.replace(/<i>|<em>/gi, '<i>')
.replace(/<\/i>|<\/em>/gi, '</i>')
// 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(/&gt;/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 = /<b>/i;
const boldEndPattern = /<\/b>/i;
const italicStartPattern = /<i>/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); // <b> tem 3 caracteres
} else if (nextTagType === 'boldEnd') {
boldActive = false;
remaining = remaining.substring(nextTagPos + 4); // </b> tem 4 caracteres
} else if (nextTagType === 'italicStart') {
italicActive = true;
remaining = remaining.substring(nextTagPos + 3); // <i> tem 3 caracteres
} else if (nextTagType === 'italicEnd') {
italicActive = false;
remaining = remaining.substring(nextTagPos + 4); // </i> 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.`);
}