/**
* PNG图片生成器
* 将分析结果转换为PNG图片格式
* 使用Puppeteer将SVG转换为PNG
*/
const puppeteer = require('puppeteer');
const generatePNG = async (analysisData, analysisType, userName) => {
let browser;
try {
// 生成SVG内容
const svgContent = await generateImageData(analysisData, analysisType, userName);
// 创建包含SVG的HTML页面
const htmlContent = `
${svgContent}
`;
// 启动puppeteer浏览器
browser = await puppeteer.launch({
headless: 'new',
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--no-first-run',
'--disable-extensions',
'--disable-plugins'
],
timeout: 30000
});
const page = await browser.newPage();
// 设置页面内容
await page.setContent(htmlContent, {
waitUntil: 'networkidle0'
});
// 设置视口大小
await page.setViewport({ width: 800, height: 1200 });
// 截图生成PNG
const pngBuffer = await page.screenshot({
type: 'png',
fullPage: true,
omitBackground: false
});
// 确保返回的是Buffer对象
if (!Buffer.isBuffer(pngBuffer)) {
console.warn('Puppeteer返回的不是Buffer,正在转换:', typeof pngBuffer);
return Buffer.from(pngBuffer);
}
return pngBuffer;
} catch (error) {
console.error('生成PNG失败:', error);
throw error;
} finally {
if (browser) {
await browser.close();
}
}
};
/**
* 生成图片数据(SVG格式)
*/
const generateImageData = async (analysisData, analysisType, userName) => {
const timestamp = new Date().toLocaleString('zh-CN');
const analysisTypeLabel = getAnalysisTypeLabel(analysisType);
// 生成SVG内容
let svg = `
`;
return svg;
};
/**
* 添加八字命理内容
*/
const addBaziContent = (svg, analysisData, yOffset) => {
let content = '';
// 基本信息
if (analysisData.basic_info) {
content += `
📋 基本信息
`;
yOffset += 40;
if (analysisData.basic_info.personal_data) {
const personal = analysisData.basic_info.personal_data;
const genderText = personal.gender === 'male' ? '男' : personal.gender === 'female' ? '女' : personal.gender || '未提供';
content += `
姓名:
${personal.name || '未提供'}
性别:
${genderText}
`;
yOffset += 30;
content += `
出生日期:
${personal.birth_date || '未提供'}
`;
yOffset += 30;
content += `
出生时间:
${personal.birth_time || '未提供'}
`;
yOffset += 40;
}
// 八字信息
if (analysisData.basic_info.bazi_info) {
const bazi = analysisData.basic_info.bazi_info;
content += `
🔮 八字信息
`;
yOffset += 30;
// 表格头
content += `
`;
yOffset += 25;
// 表格内容
const pillars = [
{ name: '年柱', data: bazi.year, nayin: bazi.year_nayin },
{ name: '月柱', data: bazi.month, nayin: bazi.month_nayin },
{ name: '日柱', data: bazi.day, nayin: bazi.day_nayin },
{ name: '时柱', data: bazi.hour, nayin: bazi.hour_nayin }
];
pillars.forEach((pillar, index) => {
const bgColor = index % 2 === 0 ? '#f8f9fa' : 'white';
content += `
${pillar.name}
${pillar.data?.split('')[0] || '-'}
${pillar.data?.split('')[1] || '-'}
${pillar.nayin || '-'}
`;
yOffset += 25;
});
yOffset += 20;
}
}
// 五行分析
if (analysisData.wuxing_analysis && yOffset < 1000) {
content += `
🌟 五行分析
`;
yOffset += 40;
if (analysisData.wuxing_analysis.element_distribution) {
const elements = analysisData.wuxing_analysis.element_distribution;
const total = Object.values(elements).reduce((sum, count) => sum + (typeof count === 'number' ? count : 0), 0);
content += `
五行分布
`;
yOffset += 30;
// 五行分布图表
let xOffset = 120;
Object.entries(elements).forEach(([element, count]) => {
const numCount = typeof count === 'number' ? count : 0;
const percentage = total > 0 ? Math.round((numCount / total) * 100) : 0;
const barHeight = Math.max(numCount * 20, 5);
const elementColor = getElementColor(element);
// 柱状图
content += `
${element}
${numCount}
${percentage}%
`;
xOffset += 100;
});
yOffset += 150;
}
if (analysisData.wuxing_analysis.balance_analysis && yOffset < 1000) {
content += `
五行平衡分析
`;
yOffset += 25;
// 分析内容(截取前200字符)
const analysisText = analysisData.wuxing_analysis.balance_analysis.substring(0, 200) + (analysisData.wuxing_analysis.balance_analysis.length > 200 ? '...' : '');
const lines = wrapText(analysisText, 50);
lines.forEach(line => {
if (yOffset < 1000) {
content += `
${line}
`;
yOffset += 20;
}
});
yOffset += 20;
}
}
svg += content;
return yOffset;
};
/**
* 添加紫微斗数内容
*/
const addZiweiContent = (svg, analysisData, yOffset) => {
let content = '';
// 基本信息
if (analysisData.basic_info) {
content += `
📋 基本信息
`;
yOffset += 40;
if (analysisData.basic_info.ziwei_info) {
const ziwei = analysisData.basic_info.ziwei_info;
if (ziwei.ming_gong) {
content += `
命宫:
${ziwei.ming_gong}
`;
yOffset += 30;
}
if (ziwei.wuxing_ju) {
content += `
五行局:
${ziwei.wuxing_ju}
`;
yOffset += 30;
}
if (ziwei.main_stars) {
const starsText = Array.isArray(ziwei.main_stars) ? ziwei.main_stars.join('、') : ziwei.main_stars;
content += `
主星:
${starsText}
`;
yOffset += 40;
}
}
}
// 星曜分析
if (analysisData.star_analysis && yOffset < 1000) {
content += `
⭐ 星曜分析
`;
yOffset += 40;
if (analysisData.star_analysis.main_stars) {
content += `
主星分析
`;
yOffset += 30;
if (Array.isArray(analysisData.star_analysis.main_stars)) {
analysisData.star_analysis.main_stars.slice(0, 3).forEach(star => {
if (typeof star === 'object' && yOffset < 1000) {
content += `
${star.name || star.star}
`;
if (star.brightness) {
content += `
亮度:${star.brightness}
`;
}
if (star.influence) {
const influenceText = star.influence.substring(0, 60) + (star.influence.length > 60 ? '...' : '');
content += `
影响:${influenceText}
`;
}
yOffset += 80;
}
});
}
}
}
svg += content;
return yOffset;
};
/**
* 添加易经占卜内容
*/
const addYijingContent = (svg, analysisData, yOffset) => {
let content = '';
// 占卜问题
if (analysisData.question_analysis) {
content += `
❓ 占卜问题
`;
yOffset += 40;
if (analysisData.question_analysis.original_question) {
content += `
问题:
`;
const questionText = analysisData.question_analysis.original_question;
const questionLines = wrapText(questionText, 45);
questionLines.forEach((line, index) => {
content += `
${line}
`;
yOffset += 20;
});
yOffset += 10;
}
if (analysisData.question_analysis.question_type) {
content += `
问题类型:
${analysisData.question_analysis.question_type}
`;
yOffset += 40;
}
}
// 卦象信息
if (analysisData.hexagram_info && yOffset < 1000) {
content += `
🔮 卦象信息
`;
yOffset += 40;
if (analysisData.hexagram_info.main_hexagram) {
const main = analysisData.hexagram_info.main_hexagram;
content += `
主卦
卦名:
${main.name || '未知'}
卦象:
${main.symbol || ''}
`;
if (main.meaning) {
const meaningText = main.meaning.substring(0, 50) + (main.meaning.length > 50 ? '...' : '');
content += `
含义:
${meaningText}
`;
}
yOffset += 120;
}
}
svg += content;
return yOffset;
};
/**
* 获取五行颜色
*/
const getElementColor = (element) => {
const colors = {
'木': '#22c55e',
'火': '#ef4444',
'土': '#eab308',
'金': '#64748b',
'水': '#3b82f6'
};
return colors[element] || '#666';
};
/**
* 文本换行处理
*/
const wrapText = (text, maxLength) => {
const lines = [];
let currentLine = '';
for (let i = 0; i < text.length; i++) {
currentLine += text[i];
if (currentLine.length >= maxLength || text[i] === '\n') {
lines.push(currentLine.trim());
currentLine = '';
}
}
if (currentLine.trim()) {
lines.push(currentLine.trim());
}
return lines;
};
/**
* 获取分析类型标签
*/
const getAnalysisTypeLabel = (analysisType) => {
switch (analysisType) {
case 'bazi': return '八字命理';
case 'ziwei': return '紫微斗数';
case 'yijing': return '易经占卜';
default: return '命理';
}
};
/**
* 获取SVG样式
*/
const getSVGStyles = () => {
return `
.main-title {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 36px;
font-weight: bold;
}
.subtitle {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 16px;
}
.report-title {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 24px;
font-weight: bold;
}
.info-text {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 14px;
}
.section-title {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 20px;
font-weight: bold;
}
.subsection-title {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 16px;
font-weight: bold;
}
.info-label {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 14px;
font-weight: bold;
}
.info-value {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 14px;
}
.info-highlight {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 14px;
font-weight: bold;
}
.table-header {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 12px;
font-weight: bold;
}
.table-cell {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 12px;
}
.element-label {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 12px;
font-weight: bold;
}
.element-count {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 11px;
}
.element-percent {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 10px;
}
.analysis-text {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 12px;
}
.star-name {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 14px;
font-weight: bold;
}
.star-detail {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 11px;
}
.hexagram-name {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 16px;
font-weight: bold;
}
.hexagram-symbol {
font-family: monospace;
font-size: 14px;
font-weight: bold;
}
.footer-text {
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 10px;
}
`;
};
module.exports = {
generatePNG
};