const puppeteer = require('puppeteer'); const path = require('path'); const { exec } = require('child_process'); const fs = require('fs').promises; const gs = require('ghostscript4js'); const { PDFDocument, rgb } = require('pdf-lib'); const { Catalog } = require('../models/catalog'); const { MyPage } = require('../models/mypage'); const tools = require('../tools/general'); const shared_consts = require('../tools/shared_nodejs'); const { compress } = require('compress-pdf'); class GenPdf { constructor(idapp) { this.idapp = idapp; this.browser = null; } async launch() { this.browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'], }); } async close() { if (this.browser) { await this.browser.close(); this.browser = null; } } inchesToPixels(inches, stampa) { if (stampa) { return Math.floor(inches * 300); } else { return Math.floor(inches * 96); } } mmToInches(mm) { return mm / 25.4; } async autoScroll(page) { console.log('inizia a scrollare...'); // Esegui lo scroll fino a quando tutta la pagina non รจ stata scrollata await page.evaluate(async () => { await new Promise((resolve) => { let totalHeight = 0; const distance = 100; // distanza dello scroll ad ogni intervallo const delay = 40; // tempo di intervallo in ms const scroll = async () => { // Esegui lo scroll window.scrollBy(0, distance); totalHeight += distance; // Verifica se รจ stato scrollato tutto il corpo della pagina if (totalHeight < document.body.scrollHeight) { setTimeout(scroll, delay); // Se non รจ finito lo scroll, continua } else { resolve(); // Scroll terminato } }; scroll(); // Avvia lo scroll }); }); } async generatePdfFromUrl(url, filenameOut, options = {}) { if (!this.browser) { throw new Error('Browser non avviato. Chiama launch() prima.'); } const page = await this.browser.newPage(); const maxTentativi = 3; try { console.log(`caricamento pagina: ${url}`); await page.goto(url, { waitUntil: 'networkidle0' }); await page.waitForNavigation({ waitUntil: 'networkidle0' }).catch(() => {}); page.on('console', (msg) => { if (msg.type() === 'error') { console.error('Errore nella pagina:', msg.text()); } }); await tools.attendiNSecondi(6); let success = false; let numTentativi1 = 0; console.log(`Cerco .pdf-section...`); while (numTentativi1 < maxTentativi) { try { await page.waitForSelector('.pdf-section', { timeout: 10000 }); console.log(` .pdf-section trovato !...`); success = true; break; } catch (e) { console.log(`Tentativo ${numTentativi1 + 1}/${maxTentativi} fallito, ASPETTO DI PIU 10 secondi`); await tools.attendiNSecondi(10); } numTentativi1++; } await this.autoScroll(page); await tools.attendiNSecondi(5); // Seleziona tutte le sezioni da stampare let sectionHandles = await page.$$('.pdf-section'); let numTentativi = 0; while (sectionHandles.length === 0 && numTentativi < maxTentativi) { console.log( `Nessuna sezione .pdf-section trovata nella pagina, quindi ASPETTO DI PIU ${numTentativi + 1}/${maxTentativi}` ); await tools.attendiNSecondi(5); sectionHandles = await page.$$('.pdf-section'); numTentativi++; } if (sectionHandles.length === 0) { throw new Error(`Nessuna sezione .pdf-section trovata nella pagina dopo ${maxTentativi} tentativi`); } const pdfBuffers = []; for (const sectionHandle of sectionHandles) { // Nascondi tutte le sezioni await page.evaluate(() => { document.querySelectorAll('.pdf-section').forEach((el) => (el.style.display = 'none')); }); // Mostra solo la sezione corrente await sectionHandle.evaluate((el) => (el.style.display = 'block')); // Calcola dimensioni della sezione const { width, height } = await sectionHandle.evaluate((el) => ({ width: el.scrollWidth, // piccolo padding height: el.scrollHeight, // piccolo padding })); // console.log(`Larghezza: ${width}px, Altezza: ${height}px`); // Imposta viewport dinamico await page.setViewport({ width, height }); // Genera pdf buffer per questa pagina const pdfBuffer = await page.pdf({ printBackground: true, width: `${width}px`, height: `${height}px`, margin: { top: '0', bottom: '0', left: '0', right: '0' }, }); pdfBuffers.push(pdfBuffer); } // Unisci tutti i PDF const mergedPdf = await PDFDocument.create(); for (const pdfBytes of pdfBuffers) { const pdf = await PDFDocument.load(pdfBytes); const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices()); copiedPages.forEach((page) => mergedPdf.addPage(page)); } const mergedPdfFile = await mergedPdf.save(); const finalFilePath = path.resolve(process.cwd(), filenameOut); await fs.writeFile(finalFilePath, mergedPdfFile); // Ripristina tutte le sezioni visibili await page.evaluate(() => { document.querySelectorAll('.pdf-section').forEach((el) => (el.style.display = 'block')); }); console.log(`PDF finale generato: ${finalFilePath}`); return finalFilePath; } catch (error) { console.error('Errore durante generazione PDF:', error); throw error; } finally { await page.close(); } } async convertPDF_ChildProcess(inputFile, outputFile, widthpx, heightpx, compressionLevel = 'screen') { const { spawn } = require('child_process'); const path = require('path'); widthpx = widthpx * 2; heightpx = heightpx * 2; console.log('=== CONVERSIONE CON CHILD_PROCESS E COMPRESSIONE ==='); // Verifica input if (!(await tools.isFileExistsAsync(inputFile))) { throw new Error(`File input non trovato: ${inputFile}`); } // Assicurati che la directory output esista const outputDir = path.dirname(outputFile); const fs = require('fs').promises; try { await fs.mkdir(outputDir, { recursive: true }); } catch (error) { // Directory giร  esistente, ok } // Parametri di compressione ottimizzati const compressionSettings = { /*maximum: [ '-dPDFSETTINGS=/screen', '-dDownsampleColorImages=true', '-dColorImageResolution=72', '-dDownsampleGrayImages=true', '-dGrayImageResolution=72', '-dDownsampleMonoImages=true', '-dMonoImageResolution=72', ], high: [ '-dPDFSETTINGS=/ebook', '-dDownsampleColorImages=true', '-dColorImageResolution=150', '-dDownsampleGrayImages=true', '-dGrayImageResolution=150', ],*/ printer: ['-dPDFSETTINGS=/printer', '-dDownsampleColorImages=true', '-dColorImageResolution=300'], screen: [ '-dPDFSETTINGS=/screen', '-dDownsampleColorImages=true', '-dColorImageResolution=96', '-dDownsampleGrayImages=true', '-dGrayImageResolution=96', ], }; return new Promise((resolve, reject) => { const args = [ '-sDEVICE=pdfwrite', '-dCompatibilityLevel=1.4', '-dNOPAUSE', '-dQUIET', '-dBATCH', '-dSAFER', // Parametri di compressione ...(compressionSettings[compressionLevel] || compressionSettings['screen']), '-dCompressFonts=true', '-dSubsetFonts=true', '-dColorImageFilter=/DCTEncode', '-dGrayImageFilter=/DCTEncode', '-dEmbedAllFonts=true', // Dimensioni pagina `-g${widthpx}x${heightpx}`, '-dFIXEDMEDIA', // '-dPDFFitPage', // Output `-sOutputFile=${outputFile}`, inputFile, ]; console.log('Spawning gs with compression args:', args.join(' ')); const gsProcess = spawn('gs', args, { stdio: ['ignore', 'pipe', 'pipe'], shell: process.platform === 'win32', }); let stdout = ''; let stderr = ''; gsProcess.stdout.on('data', (data) => { stdout += data.toString(); if (stdout.length < 1000) { // Evita log troppo lunghi console.log('GS OUT:', data.toString().trim()); } }); gsProcess.stderr.on('data', (data) => { stderr += data.toString(); if (stderr.length < 1000) { console.log('GS ERR:', data.toString().trim()); } }); gsProcess.on('close', async (code) => { console.log(`GS process closed with code: ${code}`); if (code === 0) { // Attendi e verifica setTimeout(async () => { try { const exists = await tools.isFileExistsAsync(outputFile); if (exists) { // Verifica dimensioni per confermare compressione try { const originalStats = await tools.getFileStatsAsync(inputFile); const newStats = await tools.getFileStatsAsync(outputFile); const compressionRatio = (((originalStats.size - newStats.size) / originalStats.size) * 100).toFixed( 1 ); console.log(`๐Ÿ“ File originale: ${(originalStats.size / 1024 / 1024).toFixed(2)} MB`); console.log(`๐Ÿ“ File compresso: ${(newStats.size / 1024 / 1024).toFixed(2)} MB`); console.log(`๐Ÿ—œ๏ธ Compressione: ${compressionRatio}%`); console.log('โœ… SUCCESS: File generato e compresso'); } catch (statsError) { console.log('Warning: impossibile calcolare statistiche compressione'); } resolve(outputFile); } else { console.log('โŒ FAIL: File non generato nonostante exit code 0'); reject(new Error('File non generato nonostante successo processo')); } } catch (error) { reject(error); } }, 1000); } else { reject(new Error(`Ghostscript failed with code ${code}: ${stderr}`)); } }); gsProcess.on('error', (error) => { console.log('GS process error:', error); reject(new Error(`Failed to start Ghostscript: ${error.message}`)); }); }); } async generatePdfFromUrls(urls, outputFilename) { if (!this.browser) { throw new Error('Browser non avviato. Chiama launch() prima.'); } const pdfBuffers = []; outputFilename = tools.getdirByIdApp(idapp) + '/'; try { for (const url of urls) { // ............ } const mergedPdf = await PDFDocument.create(); for (const pdfBytes of pdfBuffers) { const pdf = await PDFDocument.load(pdfBytes); const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices()); copiedPages.forEach((page) => mergedPdf.addPage(page)); } const mergedPdfFile = await mergedPdf.save(); const outputPath = path.resolve(process.cwd(), outputFilename); await fs.writeFile(outputPath, mergedPdfFile); console.log(`PDF unito generato: ${outputPath}`); return outputPath; } catch (error) { console.error('Errore durante la generazione PDF:', error); throw error; } } async getPathByPage(idpage) { const mypage = await MyPage.findById(idpage); if (mypage) { return mypage.path; } return ''; } async generatePdfFromIdCatalog(options) { try { if (!options) { console.error('Opzioni non passate !'); return null; } let filenamerelative = ''; let fullnamepath = ''; let fullnamepath_compr = ''; let filenamerelative_compressed = ''; let stampa = options.stampa; /*if (stampa) { options.width = '227.3mm'; options.height = '313.5mm'; } else { options.width = '210mm'; options.height = '297mm'; }*/ const catalog = await Catalog.findById(options.idCatalog); if (catalog) { const url = tools.getHostByIdApp(this.idapp) + '/cataloghi?id=' + catalog._id + '&stampa=' + (stampa ? '1' : '0') + '&hideHeader=1'; if (url) { const myfilenameout = await this.getPathByPage(catalog.idPageAssigned); let addstr = stampa ? '_stampabile' : ''; filenamerelative = options.path + `${myfilenameout}${addstr}_generato.pdf`; fullnamepath = tools.getdirByIdApp(this.idapp) + '/' + filenamerelative; await this.generatePdfFromUrl(url, fullnamepath, options); if (options.comprimi) { filenamerelative_compressed = options.path + `${myfilenameout}${addstr}_generato_compressed.pdf`; fullnamepath_compr = tools.getdirByIdApp(this.idapp) + '/' + filenamerelative_compressed; await this.compressPdf(fullnamepath, fullnamepath_compr, options.compressione); } } } return { fileout: filenamerelative, fileout_compressed: filenamerelative_compressed, filesize: await tools.getSizeFile(fullnamepath), filesize_compressed: await tools.getSizeFile(fullnamepath_compr), error: '', }; } catch (error) { console.error('Errore durante la generazione del PDF dal catalogo ID:', error); return { fileout: '', filesize: 0, error: 'Errore durante la generazione del PDF dal catalogo ID:' + error?.message, }; } return { fileout: '', filesize: 0, error: '' }; } async compressPdf(inputPath, outputPath, compressione = 'printer') { return new Promise((resolve, reject) => { // Risolvi i percorsi assoluti const inputFile = path.resolve(inputPath); const outputFile = path.resolve(outputPath); const validQualities = ['screen', 'ebook', 'printer', 'prepress', 'default']; if (!validQualities.includes(compressione)) compressione = 'screen'; // Comando Ghostscript per compressione - impostazione per web (/screen) const gsCommand = `gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/${compressione} -dNOPAUSE -dBATCH -dQUIET -sOutputFile="${outputFile}" "${inputFile}"`; console.log('Eseguo comando:', gsCommand); exec(gsCommand, (error, stdout, stderr) => { if (error) { console.error(`Errore compressione PDF: ${error.message}`); return reject(error); } if (stderr) { console.error(`Ghostscript stderr: ${stderr}`); } console.log(`Compressione completata. File salvato in: ${outputFile}`); resolve(outputFile); }); }); } } module.exports = GenPdf;