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();