diff --git a/src/server/services/PdfService.ts b/src/server/services/PdfService.ts
index cff6ab9..11ab877 100644
--- a/src/server/services/PdfService.ts
+++ b/src/server/services/PdfService.ts
@@ -378,6 +378,253 @@ export class PdfService {
return `§§ALIGN=${align}§§§§INDENT=${indent}§§${paragraph}`;
}).filter(Boolean);
+ /**
+ * Renderiza um parágrafo com formatação inline (negrito, itálico, sublinhado)
+ */
+ const renderInlineFormattedParagraph = (
+ page: any,
+ text: string,
+ x: number,
+ y: number,
+ normalFont: any,
+ boldFont: any,
+ italicFont: any,
+ boldItalicFont: any,
+ fontSize: number,
+ color: any,
+ align: 'left' | 'center' | 'justify' = 'left',
+ maxWidth: number = MAX_WIDTH,
+ isContinuation: boolean = false,
+ marginTop: number = margens.top,
+ marginBottom: number = margens.bottom,
+ pageHeight: number = PAGE_HEIGHT
+ ): number | null => {
+ // Se não houver espaço suficiente na página atual para pelo menos uma linha de texto
+ if (y < marginBottom + fontSize) {
+ return null; // Indica que precisa criar nova página
+ }
+
+ const words = text.split(/\s+/);
+ let currentLine = '';
+ let currentX = x;
+ let currentY = y;
+ const lineHeight = fontSize * 1.2;
+
+ // Processamento de formatação inline básica
+ let formattedChunks: { text: string, isItalic: boolean, isBold: boolean }[] = [];
+
+ // Pré-processamento do texto para detectar formatação
+ let remaining = text;
+ let inBold = false;
+ let inItalic = false;
+ let currentChunk = '';
+
+ while (remaining.length > 0) {
+ if (remaining.startsWith('') || remaining.startsWith('')) {
+ // Adiciona o texto acumulado antes da tag
+ if (currentChunk) {
+ formattedChunks.push({ text: currentChunk, isItalic, isBold });
+ currentChunk = '';
+ }
+
+ inBold = true;
+ remaining = remaining.replace(/^<(b|strong)>/, '');
+ }
+ else if (remaining.startsWith('') || remaining.startsWith('')) {
+ // Adiciona o texto acumulado em negrito
+ if (currentChunk) {
+ formattedChunks.push({ text: currentChunk, isItalic, isBold: true });
+ currentChunk = '';
+ }
+
+ inBold = false;
+ remaining = remaining.replace(/^<\/(b|strong)>/, '');
+ }
+ else if (remaining.startsWith('') || remaining.startsWith('')) {
+ // Adiciona o texto acumulado antes da tag
+ if (currentChunk) {
+ formattedChunks.push({ text: currentChunk, isItalic, isBold });
+ currentChunk = '';
+ }
+
+ inItalic = true;
+ remaining = remaining.replace(/^<(i|em)>/, '');
+ }
+ else if (remaining.startsWith('') || remaining.startsWith('')) {
+ // Adiciona o texto acumulado em itálico
+ if (currentChunk) {
+ formattedChunks.push({ text: currentChunk, isItalic: true, isBold });
+ currentChunk = '';
+ }
+
+ inItalic = false;
+ remaining = remaining.replace(/^<\/(i|em)>/, '');
+ }
+ else if (remaining.startsWith('
') || remaining.startsWith('
') || remaining.startsWith('
')) {
+ // Adiciona o texto acumulado e então adiciona uma quebra de linha
+ if (currentChunk) {
+ formattedChunks.push({ text: currentChunk, isItalic, isBold });
+ currentChunk = '';
+ }
+
+ formattedChunks.push({ text: '\n', isItalic: false, isBold: false });
+ remaining = remaining.replace(/^
/i, '');
+ }
+ else {
+ // Acumula caractere a caractere
+ currentChunk += remaining[0];
+ remaining = remaining.substring(1);
+ }
+ }
+
+ // Adiciona qualquer texto restante
+ if (currentChunk) {
+ formattedChunks.push({ text: currentChunk, isItalic, isBold });
+ }
+
+ // Agora temos o texto dividido em chunks com formatação
+ let lines: { chunks: { text: string, isItalic: boolean, isBold: boolean }[], width: number }[] = [{ chunks: [], width: 0 }];
+ let currentLineIndex = 0;
+
+ for (const chunk of formattedChunks) {
+ if (chunk.text === '\n') {
+ // Forçar quebra de linha
+ lines.push({ chunks: [], width: 0 });
+ currentLineIndex++;
+ continue;
+ }
+
+ const words = chunk.text.split(/\s+/);
+
+ for (let i = 0; i < words.length; i++) {
+ const word = words[i];
+ if (!word) continue;
+
+ const font = chunk.isBold && chunk.isItalic ? boldItalicFont :
+ chunk.isBold ? boldFont :
+ chunk.isItalic ? italicFont : normalFont;
+
+ const wordWidth = font.widthOfTextAtSize(word, fontSize);
+ const spaceWidth = font.widthOfTextAtSize(' ', fontSize);
+
+ // Verificar se precisamos adicionar espaço antes da palavra
+ let wordWithSpace = word;
+ let totalWidth = wordWidth;
+
+ // Adicionar espaço se não for a primeira palavra da linha e não estivermos após uma quebra forçada
+ if (lines[currentLineIndex].chunks.length > 0) {
+ wordWithSpace = ' ' + word;
+ totalWidth = wordWidth + spaceWidth;
+ }
+
+ // Verificar se a palavra cabe na linha atual
+ if (lines[currentLineIndex].width + totalWidth <= maxWidth) {
+ // Adicionar à linha atual
+ lines[currentLineIndex].chunks.push({
+ text: wordWithSpace,
+ isItalic: chunk.isItalic,
+ isBold: chunk.isBold
+ });
+ lines[currentLineIndex].width += totalWidth;
+ } else {
+ // Criar nova linha
+ lines.push({
+ chunks: [{ text: word, isItalic: chunk.isItalic, isBold: chunk.isBold }],
+ width: wordWidth
+ });
+ currentLineIndex++;
+
+ // Verificar se temos espaço para mais uma linha
+ if (currentY - lineHeight < marginBottom) {
+ // Não há espaço suficiente para mais uma linha
+ return null; // Indica que precisa criar nova página
+ }
+ }
+ }
+ }
+
+ // Desenhar as linhas com formatação
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ currentY -= lineHeight;
+
+ // Verificar se temos espaço para esta linha
+ if (currentY < marginBottom) {
+ return null; // Indica que precisa criar nova página
+ }
+
+ // Ajustar posição X baseada no alinhamento
+ let startX = x;
+
+ if (align === 'center') {
+ startX = x + (maxWidth - line.width) / 2;
+ } else if (align === 'justify' && i < lines.length - 1 && line.chunks.length > 1) {
+ // Justificar texto (exceto última linha do parágrafo)
+ let wordCount = 0;
+ let textWidth = 0;
+
+ // Contar palavras e determinar largura total do texto
+ for (const chunk of line.chunks) {
+ const text = chunk.text.trim();
+ if (text) {
+ wordCount += text.split(/\s+/).length;
+ const font = chunk.isBold && chunk.isItalic ? boldItalicFont :
+ chunk.isBold ? boldFont :
+ chunk.isItalic ? italicFont : normalFont;
+ textWidth += font.widthOfTextAtSize(text, fontSize);
+ }
+ }
+
+ // Calcular espaçamento extra entre palavras para justificar
+ const totalSpaces = wordCount - 1;
+ const spaceWidth = totalSpaces > 0 ? (maxWidth - textWidth) / totalSpaces : 0;
+
+ // Desenhar cada chunk com espaçamento justificado
+ let runningX = x;
+ for (const chunk of line.chunks) {
+ const font = chunk.isBold && chunk.isItalic ? boldItalicFont :
+ chunk.isBold ? boldFont :
+ chunk.isItalic ? italicFont : normalFont;
+
+ // Desenhar texto do chunk
+ page.drawText(chunk.text, {
+ x: runningX,
+ y: currentY,
+ size: fontSize,
+ font: font,
+ color: color
+ });
+
+ // Avançar posição X
+ runningX += font.widthOfTextAtSize(chunk.text, fontSize) + spaceWidth;
+ }
+
+ // Pular para a próxima linha
+ continue;
+ }
+
+ // Desenhar cada chunk
+ let runningX = startX;
+ for (const chunk of line.chunks) {
+ const font = chunk.isBold && chunk.isItalic ? boldItalicFont :
+ chunk.isBold ? boldFont :
+ chunk.isItalic ? italicFont : normalFont;
+
+ page.drawText(chunk.text, {
+ x: runningX,
+ y: currentY,
+ size: fontSize,
+ font: font,
+ color: color
+ });
+
+ runningX += font.widthOfTextAtSize(chunk.text, fontSize);
+ }
+ }
+
+ return currentY; // Retorna a posição Y final
+ };
+
let timbradoPdf: PDFDocument | null = null;
let timbradoPageCount = 0;
let timbradoPages: any[] = [];