MODULE-COVER-001: full package for external AI consumption - server.js + renderer.js + generate.js + config.js - templates/ (5 templates: xiaohongshu + dynamic + jike + poster + registry) - package.json + README.md + MODULE.hdlp - INDEX.hdlp + SYSTEM.hdlp (HLDP declarations) - Fonts: Noto Sans CJK SC priority (no tofu blocks) - No domain watermark - Updated module-registry.json - Copyright 2026-A-00037559
89 lines
2.7 KiB
JavaScript
89 lines
2.7 KiB
JavaScript
/**
|
||
* ═══════════════════════════════════════════════════
|
||
* 铸渊图片工作室 · 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<string>} 输出文件路径
|
||
*/
|
||
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<string[]>} 输出文件路径数组
|
||
*/
|
||
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
|
||
}
|