root 651cc9e1fd D121: xiaohongshu-cover module v5 complete
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
2026-06-01 16:36:00 +08:00

89 lines
2.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ═══════════════════════════════════════════════════
* 铸渊图片工作室 · 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
}