/** * ═══════════════════════════════════════════════════ * 铸渊图片工作室 · Puppeteer 渲染引擎 * ═══════════════════════════════════════════════════ */ import puppeteer from 'puppeteer' import { existsSync, mkdirSync } from 'fs' import { join, dirname } from 'path' import { fileURLToPath } from 'url' const __dirname = dirname(fileURLToPath(import.meta.url)) const OUTPUT_DIR = join(__dirname, 'output') /** * 渲染 HTML 为图片 * @param {string} html - 完整 HTML 字符串 * @param {object} opts * @param {number} opts.width - 图片宽度 * @param {number} opts.height - 图片高度 * @param {string} opts.name - 输出文件名(不含扩展名) * @param {string} opts.format - 输出格式 png/webp/jpeg * @param {number} opts.quality- 图片质量(仅 webp/jpeg) * @returns {Promise} 输出文件路径 */ export async function renderToImage(html, opts = {}) { const { width = 1080, height = 1440, name = `card_${Date.now()}`, format = 'png', quality = 90, } = opts if (!existsSync(OUTPUT_DIR)) mkdirSync(OUTPUT_DIR, { recursive: true }) const browser = await puppeteer.launch({ headless: true, executablePath: '/usr/bin/google-chrome', args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-features=TranslateUI', '--font-render-hinting=none'], }) try { const page = await browser.newPage() await page.setViewport({ width, height }) // 注入完整的 HTML await page.setContent(html, { waitUntil: 'networkidle0' }) // 等待字体加载 await page.evaluate(() => document.fonts.ready) // 额外等一会儿让渲染稳定 await new Promise(r => setTimeout(r, 500)) const outputPath = join(OUTPUT_DIR, `${name}.${format}`) await page.screenshot({ path: outputPath, type: format, clip: { x: 0, y: 0, width, height }, quality: format !== 'png' ? quality : undefined, }) return outputPath } finally { await browser.close() } } /** * 批量生成多页(轮播图) * @param {string[]} pages - 每页的 HTML * @param {object} opts * @returns {Promise} 输出文件路径数组 */ export async function renderCarousel(pages, opts = {}) { const results = [] for (let i = 0; i < pages.length; i++) { const name = opts.baseName ? `${opts.baseName}_p${i + 1}` : `carousel_${Date.now()}_p${i + 1}` const path = await renderToImage(pages[i], { ...opts, name }) results.push(path) } return results }