diff --git a/src/server/services/PdfService.ts b/src/server/services/PdfService.ts index 819b56c..3937359 100644 --- a/src/server/services/PdfService.ts +++ b/src/server/services/PdfService.ts @@ -204,168 +204,150 @@ export class PdfService { } /** - * Gera um PDF a partir do conteúdo HTML e dados do escritório - * @param conteudo - Conteúdo HTML para o PDF - * @param escritorio - Dados do escritório para substituir no template - * @returns Buffer contendo o PDF gerado + * Gera um PDF a partir do conteúdo HTML simples (apenas texto) e dados do escritório, + * encaixando o texto sobre o timbrado (PDF de fundo) em todas as páginas. + * Não usa Puppeteer, apenas PDF-LIB. */ async gerarPDF(conteudo: string, escritorio: any = {}): Promise { try { console.log('Buscando configurações do documento...'); const config = await this.getConfiguracao(); - + // Verificar se temos um timbrado para usar const temTimbrado = !!config.timbradoUrl; - - console.log(`Iniciando geração de PDF ${temTimbrado ? 'com' : 'sem'} timbrado...`); - - // Se temos timbrado, vamos primeiro verificar se ele existe let timbradoPath = ''; let timbradoExists = false; - + if (temTimbrado) { if (config.timbradoUrl.startsWith('/uploads')) { timbradoPath = path.join(process.cwd(), 'public', config.timbradoUrl); } else { timbradoPath = path.join(process.cwd(), 'public', 'uploads', path.basename(config.timbradoUrl)); } - - console.log('Verificando timbrado em:', timbradoPath); - try { await fs.access(timbradoPath); timbradoExists = true; - console.log('Timbrado encontrado'); } catch (e) { - console.error('Timbrado não encontrado:', e); + timbradoExists = false; } } - - // Margens definidas no template ou padrão + + // Margens const margens = config.margensTemplate || { top: config.timbradoMarginTop || 100, left: config.timbradoMarginLeft || 50, right: config.timbradoMarginRight || 50, bottom: config.timbradoMarginBottom || 50 }; - - console.log('Usando margens:', margens); - - /** - * Gera o PDF final combinando o timbrado com o conteúdo - */ - const gerarPdfComTimbrado = async ( - content: string, - pageW: number, - pageH: number, - margins: any, - usesTimbrado: boolean, - timbradoFilePath: string - ): Promise => { - console.log('Gerando PDF com timbrado...'); - const browser = await puppeteer.launch({ - args: ['--no-sandbox', '--disable-setuid-sandbox'], - headless: true - }); + // Parâmetros de página + const PAGE_WIDTH = 595.28; // A4 + const PAGE_HEIGHT = 841.89; // A4 + const FONT_SIZE = 12; + const LINE_HEIGHT = FONT_SIZE * 1.5; + const MAX_WIDTH = PAGE_WIDTH - margens.left - margens.right; - try { - // Gerar primeiro o PDF do conteúdo - const page = await browser.newPage(); - await page.setContent(` - - - - - - - ${content} - - `, { waitUntil: 'networkidle0' }); + // Processar variáveis do template + const texto = this.processarTemplate(conteudo, escritorio) + .replace(/<[^>]+>/g, '') // Remove tags HTML + .replace(/ /g, ' ') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/\r\n/g, '\n') + .replace(/\r/g, '\n') + .replace(/\n{3,}/g, '\n\n') + .trim(); - const contentPdfBuffer = await page.pdf({ - format: 'A4', - printBackground: true - }); + // Quebra o texto em parágrafos + const paragrafos = texto.split(/\n{2,}/).map(p => p.trim()).filter(Boolean); - if (!usesTimbrado) { - return contentPdfBuffer; + // Função para quebrar texto em linhas respeitando largura máxima + function wrapText(text: string, font: any, fontSize: number, maxWidth: number): string[] { + const words = text.split(' '); + const lines: string[] = []; + let currentLine = ''; + for (const word of words) { + const testLine = currentLine ? currentLine + ' ' + word : word; + const width = font.widthOfTextAtSize(testLine, fontSize); + if (width > maxWidth && currentLine) { + lines.push(currentLine); + currentLine = word; + } else { + currentLine = testLine; } - - try { - // Carregar os PDFs - const timbradoPdf = await PDFDocument.load(await fs.readFile(timbradoFilePath)); - const contentPdf = await PDFDocument.load(contentPdfBuffer); - - // Criar documento final - const finalPdf = await PDFDocument.create(); - - // Obter a primeira página do timbrado como template - const [timbradoPage] = await finalPdf.copyPages(timbradoPdf, [0]); - const contentPages = await finalPdf.copyPages(contentPdf, contentPdf.getPageIndices()); - - // Para cada página do conteúdo - for (const contentPage of contentPages) { - // 1. Adicionar nova página - const newPage = finalPdf.addPage([pageW, pageH]); - - // 2. Copiar conteúdo do timbrado para a nova página - try { - newPage.drawPage(timbradoPage, { - x: 0, - y: 0, - width: pageW, - height: pageH - }); - - // 3. Copiar o conteúdo sobre o timbrado - newPage.drawPage(contentPage, { - x: margins.left, - y: margins.bottom, - width: pageW - margins.left - margins.right, - height: pageH - margins.top - margins.bottom - }); - } catch (pageError) { - console.error('Erro ao processar página:', pageError); - } - } - - // Salvar o documento final - const finalPdfBytes = await finalPdf.save(); - return Buffer.from(finalPdfBytes); - - } catch (mergeError) { - console.error('Erro ao mesclar PDFs:', mergeError); - return contentPdfBuffer; // Retorna conteúdo original em caso de erro - } - } finally { - await browser.close(); } - }; + if (currentLine) lines.push(currentLine); + return lines; + } - // Processar o conteúdo e gerar o PDF - const processedContent = this.processarTemplate(conteudo, escritorio); - const pageWidth = 595; - const pageHeight = 842; - const pdfBytes = await gerarPdfComTimbrado( - processedContent, - pageWidth, - pageHeight, - margens, - timbradoExists, - timbradoPath - ); - console.log(`PDF gerado com sucesso: ${pdfBytes.length} bytes`); - return pdfBytes; + // Carregar o timbrado (se houver) e criar o PDF final + let pdfDoc: PDFDocument; + let timbradoPages: any[] = []; + if (timbradoExists) { + const timbradoBytes = await fs.readFile(timbradoPath); + const timbradoPdf = await PDFDocument.load(timbradoBytes); + pdfDoc = await PDFDocument.create(); + timbradoPages = await pdfDoc.copyPages(timbradoPdf, Array.from({length: timbradoPdf.getPageCount()}, (_, i) => i)); + } else { + pdfDoc = await PDFDocument.create(); + } + + // Fonte + const font = await pdfDoc.embedFont(StandardFonts.Helvetica); + + // Quebrar o texto em páginas + let currentPar = 0; + let currentLine = 0; + let pageIndex = 0; + const pages: any[] = []; + + while (currentPar < paragrafos.length) { + // Adicionar página do timbrado ou em branco + let page; + if (timbradoPages.length > 0 && pageIndex < timbradoPages.length) { + page = pdfDoc.addPage(timbradoPages[pageIndex]); + } else if (timbradoPages.length > 0) { + // Se o timbrado tem só 1 página, repetir + page = pdfDoc.addPage(timbradoPages[0]); + } else { + page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]); + } + let y = PAGE_HEIGHT - margens.top; + // Escrever texto na página + while (currentPar < paragrafos.length) { + const lines = wrapText(paragrafos[currentPar], font, FONT_SIZE, MAX_WIDTH); + let lineIdx = 0; + if (currentLine > 0) lineIdx = currentLine; + for (; lineIdx < lines.length; lineIdx++) { + if (y < margens.bottom + LINE_HEIGHT) { + // Próxima página + currentLine = lineIdx; + break; + } + page.drawText(lines[lineIdx], { + x: margens.left, + y, + size: FONT_SIZE, + font + }); + y -= LINE_HEIGHT; + currentLine = 0; + } + if (lineIdx < lines.length) { + // Não coube tudo, continuar na próxima página + break; + } + currentPar++; + } + pageIndex++; + pages.push(page); + } + + const pdfBytes = await pdfDoc.save(); + return Buffer.from(pdfBytes); } catch (error) { console.error('Erro geral ao gerar PDF:', error); throw error;