import puppeteer from 'puppeteer'; class ScreenshotService { async generateScreenshot(htmlContent, options = {}) { let browser = null; let pptDimensions = { width: 1000, height: 750 }; // 默认值 try { console.log('Starting Puppeteer browser...'); // 提取PPT的实际尺寸和长宽比 pptDimensions = this.extractPptDimensions(htmlContent); console.log('Extracted PPT dimensions:', pptDimensions); // 更适合容器环境的Puppeteer配置 browser = await puppeteer.launch({ headless: 'new', args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-accelerated-2d-canvas', '--no-first-run', '--no-zygote', '--disable-gpu', '--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', '--disable-web-security', '--disable-features=TranslateUI', '--disable-extensions', '--disable-component-extensions-with-background-pages', '--disable-default-apps', '--mute-audio', '--no-default-browser-check', '--autoplay-policy=user-gesture-required', '--disable-background-mode', '--disable-plugins', '--disable-translate', '--disable-ipc-flooding-protection', '--memory-pressure-off', '--max_old_space_size=4096' ], timeout: 30000, protocolTimeout: 30000 }); console.log('Browser launched successfully'); const page = await browser.newPage(); console.log('New page created'); // 设置页面视窗大小为PPT的精确尺寸 await page.setViewport({ width: pptDimensions.width, height: pptDimensions.height, deviceScaleFactor: 2 // 高分辨率 }); console.log(`Viewport set to exact PPT size: ${pptDimensions.width}x${pptDimensions.height}`); // 修改HTML内容,确保PPT容器精确匹配视窗尺寸,消除白边 const modifiedHtmlContent = this.modifyHtmlForScreenshot(htmlContent, pptDimensions); // 设置HTML内容 await page.setContent(modifiedHtmlContent, { waitUntil: 'domcontentloaded', timeout: 10000 }); console.log('HTML content set'); // 等待渲染完成 await page.waitForTimeout(1500); // 验证页面中的PPT容器尺寸是否精确匹配 const actualDimensions = await page.evaluate(() => { const container = document.getElementById('slideContainer'); const body = document.body; const html = document.documentElement; if (container) { const rect = container.getBoundingClientRect(); return { container: { width: rect.width, height: rect.height, left: rect.left, top: rect.top }, body: { width: body.offsetWidth, height: body.offsetHeight }, html: { width: html.offsetWidth, height: html.offsetHeight }, viewport: { width: window.innerWidth, height: window.innerHeight } }; } return null; }); console.log('Page dimensions verification:', actualDimensions); // 确保页面尺寸完全匹配PPT尺寸 if (actualDimensions && ( Math.abs(actualDimensions.viewport.width - pptDimensions.width) > 1 || Math.abs(actualDimensions.viewport.height - pptDimensions.height) > 1 )) { console.warn('Page dimensions do not match PPT dimensions exactly, adjusting...'); await page.setViewport({ width: pptDimensions.width, height: pptDimensions.height, deviceScaleFactor: 2 }); await page.waitForTimeout(500); } console.log('Taking precise screenshot...'); // 截取精确的PPT内容区域,不包含任何白边 const screenshot = await page.screenshot({ type: 'jpeg', quality: 95, // 使用clip精确截取PPT内容区域 clip: { x: 0, y: 0, width: pptDimensions.width, height: pptDimensions.height }, timeout: 10000 }); console.log('Screenshot taken successfully, size:', screenshot.length); return screenshot; } catch (error) { console.error('Screenshot generation error:', error); // 返回一个错误图片而不是抛出异常 return this.generateErrorImage(error.message, pptDimensions); } finally { if (browser) { try { await browser.close(); console.log('Browser closed'); } catch (closeError) { console.error('Error closing browser:', closeError); } } } } // 从HTML内容中提取PPT尺寸信息和长宽比 extractPptDimensions(htmlContent) { // 方法1:从JavaScript变量中提取尺寸 - 这是最准确的方法 const dimensionsMatch = htmlContent.match(/window\.PPT_DIMENSIONS\s*=\s*{\s*width:\s*(\d+),\s*height:\s*(\d+)\s*}/); if (dimensionsMatch) { const width = parseInt(dimensionsMatch[1]); const height = parseInt(dimensionsMatch[2]); console.log('Extracted dimensions from JavaScript:', { width, height }); return { width, height }; } // 方法2:从CSS样式中提取尺寸 const cssMatch = htmlContent.match(/\.slide-container\s*{\s*[^}]*width:\s*(\d+)px;[^}]*height:\s*(\d+)px;/s); if (cssMatch) { const width = parseInt(cssMatch[1]); const height = parseInt(cssMatch[2]); console.log('Extracted dimensions from CSS:', { width, height }); return { width, height }; } // 方法3:从内联样式中提取尺寸 const inlineMatch = htmlContent.match(/width:\s*(\d+)px[^;]*;\s*height:\s*(\d+)px/); if (inlineMatch) { const width = parseInt(inlineMatch[1]); const height = parseInt(inlineMatch[2]); console.log('Extracted dimensions from inline styles:', { width, height }); return { width, height }; } // 默认尺寸 - 使用标准PPT 16:9 比例 console.warn('Could not extract PPT dimensions from HTML, using default 16:9 ratio'); return { width: 1280, height: 720 }; // 16:9 标准比例 } // 修改HTML内容以适应截图需求,确保零白边 modifyHtmlForScreenshot(htmlContent, pptDimensions) { const { width, height } = pptDimensions; // 创建完全匹配PPT尺寸的HTML,消除所有可能的白边 let modifiedHtml = htmlContent; // 设置严格的CSS样式,确保页面尺寸完全匹配PPT尺寸 modifiedHtml = modifiedHtml.replace( /` ); // 移除所有可能影响尺寸的JavaScript,确保固定尺寸 modifiedHtml = modifiedHtml.replace( /` ); return modifiedHtml; } // 生成错误提示图片 - 使用动态尺寸 generateErrorImage(errorMessage = '截图生成失败', dimensions = { width: 1280, height: 720 }) { const { width, height } = dimensions; const canvas = ` 截图生成失败 ${errorMessage} `; return Buffer.from(canvas); } } export default new ScreenshotService();