tenta fix pdfservice.js

This commit is contained in:
rodrigo committed 2025-04-22 10:25:03 -03:00
1 parent 92239d0a99
commit 79db5cc065
1 file changed
+172 -393
+172 -393
View File
@@ -266,36 +266,36 @@ export class PdfService {
.replace(/style="[^"]*?text-indent:\s*(\d+(?:\.\d+)?(?:pt|px|em))[^"]*?"/gi, 'data-indent="$1"')
.replace(/style="[^"]*?text-align:\s*(center|justify|right)[^"]*?"/gi, 'data-align="$1"')
// Remove todos os outros atributos de estilo
.replace(/\s+style="[^"]*?"/gi, '')
.replace(/\s+style="[^"]*?"/gi, '')\s*(\d+(?:\.\d+)?(?:pt|px|em))[^"]*?"/gi, 'data-indent="$1"')
// Substitui classes específicas por atributos de dados para preservar formatação
.replace(/class="[^"]*?MsoListParagraph[^"]*?"/gi, 'data-list-para="true"')
// Normaliza blockquote e indentações
.replace(/<blockquote[^>]*>/gi, '<p data-indent="40pt">')
.replace(/<\/blockquote>/gi, '</p>')
// Trata listas
// Normaliza blockquote e indentaçõesatributos de dados para preservar formatação
.replace(/<blockquote[^>]*>/gi, '<p data-indent="40pt">')list-para="true"')
.replace(/<\/blockquote>/gi, '</p>')s
// Trata listaskquote[^>]*>/gi, '<p data-indent="40pt">')
.replace(/<li[^>]*>/gi, '<p data-list-item="true">• ')
.replace(/<\/li>/gi, '</p>');
.replace(/<li[^>]*>/gi, '<p data-list-item="true">• ')
// Remove todas as tags, exceto <b>, <strong>, <i>, <em>, <u>, <br>, <p>, <span>
// e mantém os atributos data-* que acabamos de criar para formatação
return simplified
.replace(/<(b|strong|i|em|u|p|span|br)([^>]*)>/gi, (match, tag, attrs) => {
return simplifiedom alinhamento para nosso formato especial
.replace(/<(b|strong|i|em|u|p|span|br)([^>]*)>/gi, (match, tag, attrs) => {>]*>/gi, '<p>§§ALIGN=$1§§');
// Preserva apenas data-* atributos
const dataAttrs = attrs.match(/\s+data-[^=]+="[^"]*"/gi) || [];
return `<${tag}${dataAttrs.join('')}>`;
return `<${tag}${dataAttrs.join('')}>`;a-indent="([^"]+)"[^>]*>/gi, '<p>§§INDENT=$1§§');
})
// Remove todas as outras tags não suportadas
// Remove todas as outras tags não suportadasto interno
.replace(/<(?!\/?(?:b|strong|i|em|u|br|p|span))[^>]+>/gi, '')
// Converte números seguidos de parêntese (como em listas numeradas) em formato padronizado
.replace(/(\d+\))\s+/g, '$1 ');
}
.replace(/(\d+\))\s+/g, '$1 '); '**')
} .replace(/<\/?(?:em|i)>/gi, '_')
.replace(/<\/?u>/gi, '_')
// Função para decodificar entidades HTML comuns (acentuação, símbolos, º, etc)
function decodeHtmlEntities(str: string): string {
if (!str) return '';
if (!str) return '';]*>/gi, '')
// Remove todas as outras tags
// Como estamos em Node.js, usamos apenas o método de substituição
return str
return str(/\r\n/g, '\n')
// Acentos minúsculos e maiúsculos
.replace(/&Aacute;/g, 'Á').replace(/&aacute;/g, 'á')
.replace(/&Eacute;/g, 'É').replace(/&eacute;/g, 'é')
@@ -303,429 +303,208 @@ export class PdfService {
.replace(/&Oacute;/g, 'Ó').replace(/&oacute;/g, 'ó')
.replace(/&Uacute;/g, 'Ú').replace(/&uacute;/g, 'ú')
.replace(/&Agrave;/g, 'À').replace(/&agrave;/g, 'à')
.replace(/&Egrave;/g, 'È').replace(/&egrave;/g, 'è')
.replace(/&Egrave;/g, 'È').replace(/&egrave;/g, 'è')ação, símbolos, º, etc)
.replace(/&Igrave;/g, 'Ì').replace(/&igrave;/g, 'ì')
.replace(/&Ograve;/g, 'Ò').replace(/&ograve;/g, 'ò')
.replace(/&Ugrave;/g, 'Ù').replace(/&ugrave;/g, 'ù')
.replace(/&Acirc;/g, 'Â').replace(/&acirc;/g, 'â')
.replace(/&Acirc;/g, 'Â').replace(/&acirc;/g, 'â')e substituição
.replace(/&Ecirc;/g, 'Ê').replace(/&ecirc;/g, 'ê')
.replace(/&Icirc;/g, 'Î').replace(/&icirc;/g, 'î')
.replace(/&Ocirc;/g, 'Ô').replace(/&ocirc;/g, 'ô')
.replace(/&Ucirc;/g, 'Û').replace(/&ucirc;/g, 'û')
.replace(/&Ocirc;/g, 'Ô').replace(/&ocirc;/g, 'ô')')
.replace(/&Ucirc;/g, 'Û').replace(/&ucirc;/g, 'û')')
.replace(/&Atilde;/g, 'Ã').replace(/&atilde;/g, 'ã')
.replace(/&Otilde;/g, 'Õ').replace(/&otilde;/g, 'õ')
.replace(/&Ccedil;/g, 'Ç').replace(/&ccedil;/g, 'ç')
.replace(/&Ntilde;/g, 'Ñ').replace(/&ntilde;/g, 'ñ')
.replace(/&Uuml;/g, 'Ü').replace(/&uuml;/g, 'ü')
// Símbolos comuns
.replace(/&ordm;/g, 'º')
.replace(/&ordf;/g, 'ª')
.replace(/&sect;/g, '§')
.replace(/&deg;/g, '°')
.replace(/&copy;/g, '©')
.replace(/&reg;/g, '®')
.replace(/&para;/g, '¶')
.replace(/&bull;/g, '•')
.replace(/&ndash;/g, '–')
.replace(/&mdash;/g, '—')
.replace(/&laquo;/g, '«')
.replace(/&raquo;/g, '»')
.replace(/&Uuml;/g, 'Ü').replace(/&uuml;/g, 'ü')'è')
// Símbolos comuns/g, 'Ì').replace(/&igrave;/g, 'ì')
.replace(/&ordm;/g, 'º')').replace(/&ograve;/g, 'ò')
.replace(/&ordf;/g, 'ª')').replace(/&ugrave;/g, 'ù')
.replace(/&sect;/g, '§')).replace(/&acirc;/g, 'â')
.replace(/&deg;/g, '°')').replace(/&ecirc;/g, 'ê')
.replace(/&copy;/g, '©')).replace(/&icirc;/g, 'î')
.replace(/&reg;/g, '®')').replace(/&ocirc;/g, 'ô')
.replace(/&para;/g, '')).replace(/&ucirc;/g, 'û')
.replace(/&bull;/g, '•')').replace(/&atilde;/g, 'ã')
.replace(/&ndash;/g, '–')).replace(/&otilde;/g, 'õ')
.replace(/&mdash;/g, '—')).replace(/&ccedil;/g, 'ç')
.replace(/&laquo;/g, '«')).replace(/&ntilde;/g, 'ñ')
.replace(/&raquo;/g, '»')replace(/&uuml;/g, 'ü')
.replace(/&lsquo;/g, '‘')
.replace(/&rsquo;/g, '’')
.replace(/&ldquo;/g, '“')
.replace(/&rdquo;/g, '”')
.replace(/&euro;/g, '€')
.replace(/&yen;/g, '¥')
.replace(/&yen;/g, '¥'))
.replace(/&pound;/g, '£')
.replace(/&cent;/g, '¢')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
// Numéricos
.replace(/&amp;/g, '&')')
.replace(/&quot;/g, '"'))
.replace(/&#39;/g, "'")')
.replace(/&lt;/g, '<')»')
.replace(/&gt;/g, '>')‘')
// Numéricossquo;/g, '’')
.replace(/&#(\d+);/g, (m, code) => String.fromCharCode(code));
}
/**
* Renderiza um parágrafo com formatação inline (negrito, itálico, etc)
* Retorna a nova posição Y após renderizar o parágrafo, ou null se precisar de nova página
*/
function renderInlineFormattedParagraph(
page: any,
text: string,
x: number,
y: number,
font: any,
fontBold: any,
fontItalic: any,
fontBoldItalic: any,
fontSize: number,
color: any,
align: 'left' | 'center' | 'justify' = 'left',
maxWidth: number,
forceNewPage: boolean = false,
marginTop: number,
marginBottom: number,
pageHeight: number
): number | null {
if (forceNewPage || y < marginBottom + fontSize * 2) {
return null; // Indica necessidade de nova página
}
// Dividir o texto em palavras preservando as tags
const segments: { text: string; isBold: boolean; isItalic: boolean }[] = [];
let currentSegment = { text: '', isBold: false, isItalic: false };
// Regex para encontrar tags <b>, </b>, <i>, </i>, <strong>, </strong>, <em>, </em>
const regex = /<\/?(?:b|strong|i|em)>/gi;
let lastIndex = 0;
let match;
// Clone do texto para manipulação
let textToProcess = text;
// Processar tags de indentação e alinhamento
let indentPt = 0;
let textAlign = align;
const indentMatch = textToProcess.match(/^§§INDENT=([0-9.]+(?:pt|px|em))§§/);
if (indentMatch) {
const indentValue = indentMatch[1];
indentPt = parseInt(indentValue);
if (isNaN(indentPt)) indentPt = 20; // Padrão: 20pt
textToProcess = textToProcess.substring(indentMatch[0].length);
x += indentPt;
maxWidth -= indentPt;
}
const alignMatch = textToProcess.match(/^§§ALIGN=(center|justify|right)§§/);
if (alignMatch) {
textAlign = alignMatch[1] as 'center' | 'justify' | 'left';
textToProcess = textToProcess.substring(alignMatch[0].length);
}
while ((match = regex.exec(textToProcess)) !== null) {
// Adiciona texto entre as tags
if (match.index > lastIndex) {
currentSegment.text += textToProcess.substring(lastIndex, match.index);
}
// Processa a tag
const tag = match[0].toLowerCase();
if (tag === '<b>' || tag === '<strong>') {
// Finaliza o segmento atual se não vazio
if (currentSegment.text) {
segments.push({ ...currentSegment });
currentSegment.text = '';
}
currentSegment.isBold = true;
} else if (tag === '</b>' || tag === '</strong>') {
// Finaliza o segmento atual se não vazio
if (currentSegment.text) {
segments.push({ ...currentSegment });
currentSegment.text = '';
}
currentSegment.isBold = false;
} else if (tag === '<i>' || tag === '<em>') {
// Finaliza o segmento atual se não vazio
if (currentSegment.text) {
segments.push({ ...currentSegment });
currentSegment.text = '';
}
currentSegment.isItalic = true;
} else if (tag === '</i>' || tag === '</em>') {
// Finaliza o segmento atual se não vazio
if (currentSegment.text) {
segments.push({ ...currentSegment });
currentSegment.text = '';
}
currentSegment.isItalic = false;
}
lastIndex = match.index + match[0].length;
}
// Adiciona o texto restante
if (lastIndex < textToProcess.length) {
currentSegment.text += textToProcess.substring(lastIndex);
}
// Adiciona o último segmento se não estiver vazio
if (currentSegment.text) {
segments.push(currentSegment);
}
// Processa cada segmento e divide em linhas
const lines: { segments: { text: string; isBold: boolean; isItalic: boolean; width: number; font: any }[]; totalWidth: number }[] = [];
let currentLine: { segments: { text: string; isBold: boolean; isItalic: boolean; width: number; font: any }[]; totalWidth: number } = {
segments: [],
totalWidth: 0
};
for (const segment of segments) {
const words = segment.text.split(/\s+/);
for (let i = 0; i < words.length; i++) {
const word = words[i];
if (!word) continue;
// Adiciona espaço antes se não for a primeira palavra da linha
const wordWithSpace = currentLine.segments.length > 0 ? ' ' + word : word;
// Seleciona a fonte correta com base na formatação
let segmentFont;
if (segment.isBold && segment.isItalic) {
segmentFont = fontBoldItalic;
} else if (segment.isBold) {
segmentFont = fontBold;
} else if (segment.isItalic) {
segmentFont = fontItalic;
} else {
segmentFont = font;
}
const wordWidth = segmentFont.widthOfTextAtSize(wordWithSpace, fontSize);
// Se a palavra cabe na linha atual
if (currentLine.totalWidth + wordWidth <= maxWidth) {
currentLine.segments.push({
text: wordWithSpace,
isBold: segment.isBold,
isItalic: segment.isItalic,
width: wordWidth,
font: segmentFont
});
currentLine.totalWidth += wordWidth;
} else {
// Se a linha atual não está vazia, finalize-a
if (currentLine.segments.length > 0) {
lines.push(currentLine);
currentLine = { segments: [], totalWidth: 0 };
}
// Se a palavra for maior que a largura máxima, divida-a
if (segmentFont.widthOfTextAtSize(word, fontSize) > maxWidth) {
// Implementação simplificada: apenas adiciona a palavra muito longa
// em uma nova linha, mesmo que ultrapasse a largura máxima
currentLine.segments.push({
text: word,
isBold: segment.isBold,
isItalic: segment.isItalic,
width: segmentFont.widthOfTextAtSize(word, fontSize),
font: segmentFont
});
currentLine.totalWidth = segmentFont.widthOfTextAtSize(word, fontSize);
lines.push(currentLine);
currentLine = { segments: [], totalWidth: 0 };
} else {
// Caso contrário, adicione a palavra na nova linha
currentLine.segments.push({
text: word,
isBold: segment.isBold,
isItalic: segment.isItalic,
width: segmentFont.widthOfTextAtSize(word, fontSize),
font: segmentFont
});
currentLine.totalWidth = segmentFont.widthOfTextAtSize(word, fontSize);
}
}
}
}
// Adiciona a última linha se não estiver vazia
if (currentLine.segments.length > 0) {
lines.push(currentLine);
}
// Se não houver linhas após o processamento, retorne a posição Y atual
if (lines.length === 0) {
return y;
}
// Verifica se todas as linhas cabem na página atual
const totalHeight = lines.length * (fontSize * 1.2);
if (y - totalHeight < marginBottom) {
return null; // Precisa de uma nova página
}
// Renderiza as linhas
let currentY = y;
const lineHeight = fontSize * 1.2;
for (const line of lines) {
let currentX = x;
// Ajusta o X inicial com base no alinhamento
if (textAlign === 'center') {
currentX = x + (maxWidth - line.totalWidth) / 2;
} else if (textAlign === 'justify' && line !== lines[lines.length - 1] && line.segments.length > 1) {
// Para justificado, ajusta apenas linhas que não sejam a última e tenham mais de um segmento
const extraSpace = maxWidth - line.totalWidth;
const spaceBetweenSegments = extraSpace / (line.segments.length - 1);
for (const segment of line.segments) {
const font = segment.font;
page.drawText(segment.text, {
x: currentX,
y: currentY,
size: fontSize,
font: font,
color: color
});
currentX += segment.width + spaceBetweenSegments;
}
} else {
// Para alinhamento à esquerda ou a última linha de justificado
for (const segment of line.segments) {
const font = segment.font;
page.drawText(segment.text, {
x: currentX,
y: currentY,
size: fontSize,
font: font,
color: color
});
currentX += segment.width;
}
}
currentY -= lineHeight;
}
return currentY;
}
const texto = decodeHtmlEntities(
} .replace(/&rdquo;/g, '”')
.replace(/&euro;/g, '€')
const texto = decodeHtmlEntities( .replace(/&yen;/g, '¥')
stripUnsupportedTags(
this.processarTemplate(conteudo, escritorio)
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<p\s+data-indent="([^"]+)"[^>]*>/gi, '§§INDENT=$1§§')
.replace(/<p\s+data-align="([^"]+)"[^>]*>/gi, '§§ALIGN=$1§§')
.replace(/<p\s+data-list-item="true"[^>]*>/gi, '• ')
.replace(/<p\s+data-list-para="true"[^>]*>/gi, '§§INDENT=20pt§§')
.replace(/<\/p>/gi, '\n\n')
.replace(/<p[^>]*>/gi, '')
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
.replace(/\n{3,}/g, '\n\n')
.trim()
)
) .replace(/&nbsp;/g, ' ')
);
(/&quot;/g, '"')
// Process paragraph alignment and formatting#39;/g, "'")
const paragrafos = texto.split(/\n{2,}/).map(p => {(/&lt;/g, '<')
let paragraph = p.trim();(/&gt;/g, '>')
if (!paragraph) return '';icos
(\d+);/g, (m, code) => String.fromCharCode(code));
// Process paragraph alignment
let align = 'justify'; // default
const alignMatch = paragraph.match(/^§§ALIGN=(center|justify|right)§§/);
if (alignMatch) {um parágrafo com formatação inline (negrito, itálico, etc)
align = alignMatch[1];rágrafo, ou null se precisar de nova página
paragraph = paragraph.substring(alignMatch[0].length);
}ragraph(
// Process indentation
let indent = 0;
const indentMatch = paragraph.match(/^§§INDENT=([0-9.]+)(?:pt|px|em)§§/);
if (indentMatch) {
indent = parseFloat(indentMatch[1]);
paragraph = paragraph.substring(indentMatch[0].length);ontItalic: any,
} fontBoldItalic: any, if (forceNewPage || y < marginBottom + fontSize * 2) {
// Mark paragraph with alignment and indentation
return `§§ALIGN=${align}§§§§INDENT=${indent}§§${paragraph}`;
}).filter(Boolean); maxWidth: number, // Dividir o texto em palavras preservando as tags
const paragrafos = texto.split(/\n{2,}/).map(p => p.trim()).filter(Boolean);
let timbradoPdf: PDFDocument | null = null;
let timbradoPageCount = 0;
let timbradoPages: any[] = [];
if (timbradoExists) {
const timbradoBytes = await fs.readFile(timbradoPath);
timbradoPdf = await PDFDocument.load(timbradoBytes);
timbradoPageCount = timbradoPdf.getPageCount();
let timbradoPdf: PDFDocument | null = null;};
let timbradoPageCount = 0;er,
let timbradoPages: any[] = [];: numberara encontrar tags <b>, </b>, <i>, </i>, <strong>, </strong>, <em>, </em>
if (timbradoExists) { number | null {const regex = /<\/?(?:b|strong|i|em)>/gi;
const timbradoBytes = await fs.readFile(timbradoPath);om + fontSize * 2) {
timbradoPdf = await PDFDocument.load(timbradoBytes);necessidade de nova página
timbradoPageCount = timbradoPdf.getPageCount();}
timbradoPages = [];
for (let i = 0; i < timbradoPageCount; i++) {
const [embedded] = await pdfDoc.embedPages([timbradoPdf.getPage(i)]);
timbradoPages.push(embedded);
for (let i = 0; i < timbradoPageCount; i++) {o em palavras preservando as tags = text;
const [embedded] = await pdfDoc.embedPages([timbradoPdf.getPage(i)]);: string; isBold: boolean; isItalic: boolean }[] = [];
timbradoPages.push(embedded);let currentSegment = { text: '', isBold: false, isItalic: false };// Processar tags de indentação e alinhamento
}
}
let currentPar = 0;
}ntrar tags <b>, </b>, <i>, </i>, <strong>, </strong>, <em>, </em>ign;
/gi;
let currentPar = 0;[0-9.]+(?:pt|px|em))§§/);
let pageIndex = 0;
while (currentPar < paragrafos.length) {
let page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
if (timbradoPages.length > 0) {
const embedded = timbradoPages[pageIndex % timbradoPages.length];
page.drawPage(embedded, {
let page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);o para manipulaçãoseInt(indentValue);
if (timbradoPages.length > 0) {t;indentPt = 20; // Padrão: 20pt
const embedded = timbradoPages[pageIndex % timbradoPages.length];textToProcess = textToProcess.substring(indentMatch[0].length);
page.drawPage(embedded, {// Processar tags de indentação e alinhamento x += indentPt;
x: 0,
y: 0,
y: 0,lign;
width: PAGE_WIDTH,
height: PAGE_HEIGHT
});
}
height: PAGE_HEIGHT|px|em))§§/);|right)§§/);
});f (indentMatch) {f (alignMatch) {
} const indentValue = indentMatch[1]; textAlign = alignMatch[1] as 'center' | 'justify' | 'left';
let y = PAGE_HEIGHT - margens.top;
while (currentPar < paragrafos.length) {
let indent = paragrafos[currentPar].startsWith('§§INDENT§§');
let indent2 = paragrafos[currentPar].startsWith('§§INDENT2§§');
let center = paragrafos[currentPar].startsWith('§§CENTER§§');
let align: 'center' | 'justify' | 'left' = center ? 'center' : 'justify';
let text = paragrafos[currentPar]
.replace(/^§§INDENT2§§/, '')
.replace(/^§§INDENT§§/, '')
.replace(/^§§CENTER§§/, '')
while (currentPar < paragrafos.length) { 20; // Padrão: 20pt
const paraText = paragrafos[currentPar];substring(indentMatch[0].length);
// Extract alignmentaxWidth -= indentPt;/ Adiciona texto entre as tags
let align: 'left' | 'center' | 'justify' = 'left';f (match.index > lastIndex) {
const alignMatch = paraText.match(/^§§ALIGN=(center|justify|right)§§/);ss.substring(lastIndex, match.index);
if (alignMatch) {h(/^§§ALIGN=(center|justify|right)§§/);
align = alignMatch[1] as 'center' | 'justify' | 'left';
}stify' | 'left';
s.substring(alignMatch[0].length);Case();
// Extract indentation
let indent = 0;
const indentMatch = paraText.match(/§§INDENT=([0-9.]+)§§/); ((match = regex.exec(textToProcess)) !== null) {f (currentSegment.text) {
if (indentMatch) {egment });
indent = parseFloat(indentMatch[1]);
}tring(lastIndex, match.index);
// Clean text
let text = paraTextio
.replace(/^§§ALIGN=(center|justify|right)§§/, '')st tag = match[0].toLowerCase();f (currentSegment.text) {
.replace(/§§INDENT=[0-9.]+§§/, '')trong>') {gment });
.trim();
// Se o parágrafo for centralizado, força alinhamento center e ignora recuo
if (center) {
indent = false;
indent2 = false;
align = 'center';
}
text = decodeHtmlEntities(text);
let x = margens.left;
let parWidth = MAX_WIDTH;
if (indent2) {
// Recuo maior para blockquote/citação
x += 40;
parWidth -= 40;
} else if (indent) {
x += 20;
parWidth -= 20;
}
// Process bold/italic formattingntSegment });lse;
text = text
.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>')
.replace(/_(.*?)_/g, '<i>$1</i>');urrentSegment.isBold = true;f (currentSegment.text) {
=== '</strong>') {ment });
let x = margens.left + indent;
let parWidth = MAX_WIDTH - indent;
ntSegment });true;
let yAfter = renderInlineFormattedParagraph(
page,
text,
x,
y,
font,
text,urrentSegment.isBold = false;f (currentSegment.text) {
x,= '<em>') {ent });
y, // Finaliza o segmento atual se não vazio currentSegment.text = '';
font, if (currentSegment.text) { }
fontBold,
fontItalic,
fontBoldItalic,
FONT_SIZE,
rgb(0, 0, 0),
fontItalic, currentSegment.text = ''; }
fontBoldItalic, }
FONT_SIZE,= true;match[0].length;
rgb(0, 0, 0),'</em>') {
align,
parWidth,
false,
parWidth, if (currentSegment.text) {/ Adiciona o texto restante
false, segments.push({ ...currentSegment });if (lastIndex < textToProcess.length) {
margens.top,
margens.bottom,
PAGE_HEIGHT
);
if (yAfter === null) {
PAGE_HEIGHTlse;
); }/ Adiciona o último segmento se não estiver vazio
if (yAfter === null) { if (currentSegment.text) {
pageIndex++;
break;
}
y = yAfter - FONT_SIZE * 0.5;
currentPar++;
if (y < margens.bottom + FONT_SIZE * 1.5) break;
}
y = yAfter - FONT_SIZE * 0.5;xto restante segmento e divide em linhas
currentPar++;textToProcess.length) {egments: { text: string; isBold: boolean; isItalic: boolean; width: number; font: any }[]; totalWidth: number }[] = [];
if (y < margens.bottom + FONT_SIZE * 1.5) break;currentSegment.text += textToProcess.substring(lastIndex);t currentLine: { segments: { text: string; isBold: boolean; isItalic: boolean; width: number; font: any }[]; totalWidth: number } = {
}} segments: [],
pageIndex++;
}
}er vazio
(currentSegment.text) {
const pdfBytes = await pdfDoc.save();
return Buffer.from(pdfBytes);
} catch (error) {
console.error('Erro geral ao gerar PDF:', error);
throw error;
}
console.error('Erro geral ao gerar PDF:', error);rocessa cada segmento e divide em linhasr (let i = 0; i < words.length; i++) {
throw error; boolean; width: number; font: any }[]; totalWidth: number }[] = [];
}n; width: number; font: any }[]; totalWidth: number } = {
}
ntes se não for a primeira palavra da linha
private processarTemplate(template: string, dados: any): string {
return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
const keyParts = key.trim().split('.');
let value = dados;
const keyParts = key.trim().split('.');{
let value = dados;split(/\s+/);ment.isItalic) {
for (const part of keyParts) {
if (value && typeof value === 'object' && part in value) {
value = value[part];
} else {
value = undefined;
break;
}
for (const part of keyParts) {gth; i++) {{
if (value && typeof value === 'object' && part in value) {rd = words[i];tFont = fontBold;
value = value[part];talic) {
} else {segmentFont = fontItalic;
value = undefined;// Adiciona espaço antes se não for a primeira palavra da linha} else {
break;;
}// Seleciona a fonte correta com base na formatação}
}
return value !== undefined ? value : match;
});
}
ntSize);
return value !== undefined ? value : match;c;
});d) {linha atual
}wordWidth <= maxWidth) {
}
} } }); return value !== undefined ? value : match; } } break; value = undefined; } else { value = value[part]; if (value && typeof value === 'object' && part in value) { }
}