diff --git a/src/server/services/PdfService copy 3.ts b/src/server/services/PdfService copy 3.ts new file mode 100644 index 0000000..cdec045 --- /dev/null +++ b/src/server/services/PdfService copy 3.ts @@ -0,0 +1,219 @@ +import puppeteer from 'puppeteer'; +import { PDFDocument } from 'pdf-lib'; +import fs from 'fs/promises'; +import path from 'path'; +import { prisma } from '../prismaClient'; + +// Função utilitária para converter pixels em milímetros (1px ≈ 0.264583 mm) +function pxToMm(px: number | null | undefined): string { + if (!px) return '0mm'; + return (px * 0.264583).toFixed(2) + 'mm'; +} + +export class PdfService { + // Busca configuração do documento, usando apenas campos válidos do schema + private async getConfiguracao() { + try { + const config = await prisma.configuracaoDocumento.findFirst({ + orderBy: { criadoEm: 'desc' }, + select: { + id: true, + timbradoUrl: true, + timbradoMarginTop: true, + timbradoMarginBottom: true, + timbradoMarginLeft: true, + timbradoMarginRight: true, + templatePadrao: true, + margensTemplate: true, + criadoEm: true + } + }); + // Extrai recuo de primeira linha se existir em margensTemplate + let firstLineIndent = '0mm'; + if (config?.margensTemplate) { + try { + const margens = typeof config.margensTemplate === 'string' ? JSON.parse(config.margensTemplate) : config.margensTemplate; + if (margens && margens.firstLineIndent) { + firstLineIndent = `${margens.firstLineIndent}mm`; + } + } catch (e) { + // Se der erro, ignora e usa padrão + } + } + return { + timbradoUrl: config?.timbradoUrl || '', + timbradoMarginTop: config?.timbradoMarginTop ?? 100, + timbradoMarginBottom: config?.timbradoMarginBottom ?? 50, + timbradoMarginLeft: config?.timbradoMarginLeft ?? 50, + timbradoMarginRight: config?.timbradoMarginRight ?? 50, + templatePadrao: config?.templatePadrao || '', + firstLineIndent + }; + } catch (error) { + console.error('Erro ao buscar configuração:', error); + throw new Error('Falha ao buscar configuração'); + } + } + + // Gera HTML com timbrado e margens + private gerarHTML(content: string, config: any) { + // Limpa espaços, quebras de linha e tags vazias do final do conteúdo + let cleanContent = content + .replace(/(\s||

\s*<\/p>)+$/gi, '') + .replace(/(

\s*<\/p>)+/gi, ''); + const timbradoUrl = config.timbradoUrl + ? `${process.env.FRONTEND_URL || 'http://localhost:5000'}${config.timbradoUrl}` + : ''; + // Adiciona log para debug + if (timbradoUrl) { + console.log('[PDF] Timbrado inserido no HTML:', timbradoUrl); + } + return ` + + + + + + + + + ${timbradoUrl ? '

' : ''} +
+ ${cleanContent} +
+ + + `; + } + + // Gera PDF a partir do HTML, depois sobrepõe o timbrado em cada página + async gerarPDF(conteudo: string) { + try { + const config = await this.getConfiguracao(); + // Usa templatePadrao se não houver conteúdo + const html = this.gerarHTML(conteudo || config.templatePadrao, config); + const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] }); + const page = await browser.newPage(); + await page.setContent(html, { + waitUntil: ['networkidle0', 'load'], + timeout: 30000 + }); + const margin = { + top: pxToMm(config.timbradoMarginTop), + bottom: pxToMm(config.timbradoMarginBottom), + left: pxToMm(config.timbradoMarginLeft), + right: pxToMm(config.timbradoMarginRight) + }; + const pdfBuffer = await page.pdf({ + format: 'A4', + printBackground: true, + margin, + preferCSSPageSize: true + }); + await browser.close(); + + // Se não houver timbrado, retorna direto + if (!config.timbradoUrl) { + return Buffer.from(pdfBuffer); + } + + // Caminho absoluto do PDF de timbrado + const timbradoPath = config.timbradoUrl.startsWith('/uploads') + ? path.join(process.cwd(), 'public', config.timbradoUrl) + : path.join(process.cwd(), 'public', 'uploads', path.basename(config.timbradoUrl)); + let timbradoBytes: Buffer; + try { + timbradoBytes = await fs.readFile(timbradoPath); + } catch (e) { + console.error('Timbrado não encontrado:', timbradoPath); + return Buffer.from(pdfBuffer); + } + + // Mescla cada página do PDF gerado com o timbrado + const pdfDoc = await PDFDocument.load(pdfBuffer); + const timbradoDoc = await PDFDocument.load(timbradoBytes); + const timbradoPage = timbradoDoc.getPage(0); + const [timbradoEmbed] = await pdfDoc.embedPages([timbradoPage]); + const pageCount = pdfDoc.getPageCount(); + for (let i = 0; i < pageCount; i++) { + const page = pdfDoc.getPage(i); + page.drawPage(timbradoEmbed, { x: 0, y: 0, width: page.getWidth(), height: page.getHeight() }); + } + // Remove última página se estiver vazia (sem conteúdo visível) + if (pdfDoc.getPageCount() > 1) { + const lastPage = pdfDoc.getPage(pdfDoc.getPageCount() - 1); + const textContent = lastPage.getTextContent ? await lastPage.getTextContent() : null; + // pdf-lib não tem getTextContent, então alternativa: verifica se altura do conteúdo é mínima + if (lastPage.getHeight && lastPage.getWidth && lastPage.getHeight() > 0 && lastPage.getWidth() > 0) { + // Heurística: verifica se a página tem apenas o timbrado (sem outros objetos) + if (lastPage.node.Contents().size() === 0) { + pdfDoc.removePage(pdfDoc.getPageCount() - 1); + } + } + } + const mergedPdf = await pdfDoc.save(); + return Buffer.from(mergedPdf); + } catch (error) { + console.error('Erro ao gerar PDF:', error); + throw new Error('Falha ao gerar PDF'); + } + } +} \ No newline at end of file diff --git a/src/server/services/PdfService.ts b/src/server/services/PdfService.ts index cdec045..76f21da 100644 --- a/src/server/services/PdfService.ts +++ b/src/server/services/PdfService.ts @@ -197,16 +197,17 @@ export class PdfService { const page = pdfDoc.getPage(i); page.drawPage(timbradoEmbed, { x: 0, y: 0, width: page.getWidth(), height: page.getHeight() }); } - // Remove última página se estiver vazia (sem conteúdo visível) + // Remove última página se ela for vazia (heurística: largura e altura padrão, nenhum objeto além do fundo) if (pdfDoc.getPageCount() > 1) { const lastPage = pdfDoc.getPage(pdfDoc.getPageCount() - 1); - const textContent = lastPage.getTextContent ? await lastPage.getTextContent() : null; - // pdf-lib não tem getTextContent, então alternativa: verifica se altura do conteúdo é mínima - if (lastPage.getHeight && lastPage.getWidth && lastPage.getHeight() > 0 && lastPage.getWidth() > 0) { - // Heurística: verifica se a página tem apenas o timbrado (sem outros objetos) - if (lastPage.node.Contents().size() === 0) { - pdfDoc.removePage(pdfDoc.getPageCount() - 1); - } + // Heurística: se a última página não tem annotations nem xObjects além do fundo, é vazia + const annots = lastPage.node.Annots(); + const xObjects = lastPage.node.Resources().XObject(); + const hasAnnots = annots && annots.size && annots.size() > 0; + const hasXObjects = xObjects && xObjects.size && xObjects.size() > 0; + // Se não tem anotações nem xObjects, remove + if (!hasAnnots && !hasXObjects) { + pdfDoc.removePage(pdfDoc.getPageCount() - 1); } } const mergedPdf = await pdfDoc.save();