diff --git a/src/server/services/PdfService.ts b/src/server/services/PdfService.ts
index 6f523e3..8fd63a2 100644
--- a/src/server/services/PdfService.ts
+++ b/src/server/services/PdfService.ts
@@ -348,6 +348,193 @@ export class PdfService {
.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,
+ x: number,
+ startY: number,
+ normalFont: any,
+ boldFont: any,
+ italicFont: any,
+ boldItalicFont: any,
+ fontSize: number,
+ color: any,
+ align: 'left' | 'center' | 'right' | 'justify',
+ maxWidth: number,
+ debug: boolean = false,
+ marginTop: number,
+ marginBottom: number,
+ pageHeight: number
+ ): number | null {
+ const lineHeight = fontSize * 1.5;
+ let y = startY;
+ let currentX = x;
+ let words: Array<{text: string, font: any, width: number}> = [];
+ let currentLine: Array<{text: string, font: any, width: number}> = [];
+ let currentLineWidth = 0;
+ let textParts = [];
+
+ // Detectar formatação inline
+ let remainingText = text;
+ let currentFont = normalFont;
+ let boldActive = false;
+ let italicActive = false;
+
+ // Processa texto com formatação
+ while (remainingText.length > 0) {
+ if (remainingText.startsWith('')) {
+ // Começa negrito
+ boldActive = true;
+ remainingText = remainingText.substring(3);
+ currentFont = italicActive ? boldItalicFont : boldFont;
+ } else if (remainingText.startsWith('')) {
+ // Termina negrito
+ boldActive = false;
+ remainingText = remainingText.substring(4);
+ currentFont = italicActive ? italicFont : normalFont;
+ } else if (remainingText.startsWith('')) {
+ // Começa itálico
+ italicActive = true;
+ remainingText = remainingText.substring(3);
+ currentFont = boldActive ? boldItalicFont : italicFont;
+ } else if (remainingText.startsWith('')) {
+ // Termina itálico
+ italicActive = false;
+ remainingText = remainingText.substring(4);
+ currentFont = boldActive ? boldFont : normalFont;
+ } else {
+ // Texto normal - procura pela próxima tag de formatação
+ let nextTag = remainingText.search(/<\/?[bi]>/);
+ if (nextTag === -1) {
+ // Não há mais tags, adiciona todo o texto restante
+ textParts.push({
+ text: remainingText,
+ font: currentFont
+ });
+ remainingText = '';
+ } else {
+ // Adiciona texto até a próxima tag
+ textParts.push({
+ text: remainingText.substring(0, nextTag),
+ font: currentFont
+ });
+ remainingText = remainingText.substring(nextTag);
+ }
+ }
+ }
+
+ // Processa partes de texto em palavras para quebra de linha
+ for (const part of textParts) {
+ const partWords = part.text.split(/\s+/);
+
+ for (let i = 0; i < partWords.length; i++) {
+ if (partWords[i].length === 0) continue;
+
+ const wordText = i < partWords.length - 1 ? partWords[i] + ' ' : partWords[i];
+ const wordWidth = part.font.widthOfTextAtSize(wordText, fontSize);
+
+ words.push({
+ text: wordText,
+ font: part.font,
+ width: wordWidth
+ });
+ }
+ }
+
+ // Processa palavras em linhas
+ for (let i = 0; i < words.length; i++) {
+ const word = words[i];
+
+ if (currentLineWidth + word.width <= maxWidth) {
+ // A palavra cabe na linha atual
+ currentLine.push(word);
+ currentLineWidth += word.width;
+ } else {
+ // A palavra não cabe, desenha a linha atual e começa nova linha
+ // Primeiro verifica se há espaço vertical suficiente
+ if (y - lineHeight < marginBottom) {
+ return null; // Não há espaço suficiente na página
+ }
+
+ // Desenha a linha com o alinhamento apropriado
+ y = drawFormattedLine(page, currentLine, x, y, maxWidth, align, fontSize, color);
+
+ // Começa uma nova linha com a palavra atual
+ currentLine = [word];
+ currentLineWidth = word.width;
+ }
+ }
+
+ // Desenha a última linha se houver palavras restantes
+ if (currentLine.length > 0) {
+ if (y - lineHeight < marginBottom) {
+ return null; // Não há espaço suficiente na página
+ }
+
+ y = drawFormattedLine(page, currentLine, x, y, maxWidth, align, fontSize, color);
+ }
+
+ return y;
+ }
+
+ /**
+ * Desenha uma linha formatada com alinhamento específico
+ */
+ function drawFormattedLine(
+ page: any,
+ line: Array<{text: string, font: any, width: number}>,
+ x: number,
+ y: number,
+ maxWidth: number,
+ align: 'left' | 'center' | 'right' | 'justify',
+ fontSize: number,
+ color: any
+ ): number {
+ const lineHeight = fontSize * 1.5;
+ let currentX = x;
+ let totalWidth = line.reduce((sum, word) => sum + word.width, 0);
+
+ // Calcula o espaço adicional entre palavras para justificação
+ let extraSpacing = 0;
+ if (align === 'justify' && line.length > 1 && totalWidth < maxWidth * 0.8) {
+ extraSpacing = (maxWidth - totalWidth) / (line.length - 1);
+ }
+
+ // Ajusta a posição x inicial baseada no alinhamento
+ if (align === 'center') {
+ currentX = x + (maxWidth - totalWidth) / 2;
+ } else if (align === 'right') {
+ currentX = x + maxWidth - totalWidth;
+ }
+
+ // Desenha cada palavra
+ for (let i = 0; i < line.length; i++) {
+ const word = line[i];
+ page.drawText(word.text, {
+ x: currentX,
+ y: y,
+ font: word.font,
+ size: fontSize,
+ color: color
+ });
+
+ // Avança X para a próxima palavra
+ currentX += word.width;
+
+ // Adiciona espaço extra para justificação, exceto na última palavra
+ if (align === 'justify' && i < line.length - 1) {
+ currentX += extraSpacing;
+ }
+ }
+
+ // Retorna a nova posição Y após desenhar a linha
+ return y - lineHeight;
+ }
+
// Processe o texto com as novas funções melhoradas
const textoLimpo = stripUnsupportedTags(
this.processarTemplate(conteudo, escritorio)