tenta fix pdfservice.js
This commit is contained in:
1 parent
6fdfc99e7d
commit
597236c525
1 file changed
+292
-270
+292
-270
@@ -290,30 +290,30 @@ export class PdfService {
|
||||
// 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(/<p[^>]*style="[^"]*text-align\s*:\s*(center|right|justify)[^"]*"[^>]*>/gi, >]*align="(center|right|justify)"[^>]*>/gi,
|
||||
(match, align) => `<p style="text-align:${align}">`)yle || alignAttr}§§`)
|
||||
.replace(/<div[^>]*style="[^"]*text-align\s*:\s*(center|right|justify)[^"]*"[^>]*>/gi, [^>]*align="(center|right|justify)"[^>]*>/gi,
|
||||
(match, align) => `<div style="text-align:${align}">`)e || alignAttr}§§`)
|
||||
.replace(/<p[^>]*>|<div[^>]*>|<blockquote[^>]*>/gi, '')res de bloco + quebra de linha
|
||||
.replace(/<\/p>|<\/div>|<\/blockquote>/gi, '\n\n'), '')
|
||||
// Marca formatação de textolockquote>/gi, '\n\n')
|
||||
.replace(/<b>|<strong>/gi, '<b>')
|
||||
.replace(/<\/b>|<\/strong>/gi, '</b>')
|
||||
.replace(/<i>|<em>/gi, '<i>')
|
||||
.replace(/<i>|<em>/gi, '<i>'), '</b>')
|
||||
.replace(/<\/i>|<\/em>/gi, '</i>')
|
||||
// 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, '</i>')
|
||||
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 <u>, <span>, etc.
|
||||
text = text
|
||||
text = textctive = false;
|
||||
.replace(/<u>|<\/u>/gi, '') // Remover tags de sublinhados
|
||||
.replace(/<span[^>]*>|<\/span>/gi, '') // Remover tags span
|
||||
.replace(/<span[^>]*>|<\/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>|<\/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(/<b>|<strong>/i);
|
||||
const italicStartMatch = remainingText.match(/<i>|<em>/i);
|
||||
const italicStartMatch = remainingText.match(/<i>|<em>/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(/<b>|<strong>/i);
|
||||
// Encontrar a próxima tagemainingText.match(/<i>|<em>/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(/<b>|<strong>/i)) boldActive = true;
|
||||
else if (nextMatch[0].match(/<\/b>|<\/strong>/i)) boldActive = false;
|
||||
else if (nextMatch[0].match(/<i>|<em>/i)) italicActive = true;
|
||||
else if (nextMatch[0].match(/<\/i>|<\/em>/i)) italicActive = false;
|
||||
|
||||
// Remover o texto processado e a tag
|
||||
if (nextMatch[0].match(/<b>|<strong>/i)) boldActive = true;
|
||||
// Remover o texto processado e a tag/strong>/i)) boldActive = false;
|
||||
remainingText = remainingText.substring(matchIndex + nextMatch[0].length);
|
||||
}
|
||||
} else if (nextMatch[0].match(/<\/i>|<\/em>/i)) italicActive = false;
|
||||
|
||||
// Processar as partes extraídas
|
||||
for (const part of textParts) {
|
||||
// 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('<b>') && paragraph.includes('</b>');urto (como "Considerando:")e ponto (como "1. Título")
|
||||
const hasUnderline = paragraph.includes('<u>') && paragraph.includes('</u>');
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user