/**
* PhoneImageEngine - 手机图片排版引擎
*
* 将文章HTML内容自动分页并渲染为手机尺寸图片
* 依赖: jQuery, html2canvas (项目已有)
*
* 渲染宽度540px, html2canvas scale=2 输出1080px
* 小红书: 540x720 (输出1080x1440)
* 抖音: 540x960 (输出1080x1920)
*/
var PhoneImageEngine = (function () {
// ===== 配置 =====
var config = {
size: 'xiaohongshu',
fontSize: 14,
watermark: '',
pageAlignments: {}, // key=页码(1-based), value='top'|'center'
sizes: {
xiaohongshu: { width: 540, height: 720 },
douyin: { width: 540, height: 960 }
},
contentPadding: 20
};
// ===== 文章数据 =====
var postData = {
id: 0,
title: '',
desc: '',
coverText: '', // 封面文案
content_html: '',
poster: '',
author_name: '',
create_time: '',
category_name: ''
};
// ===== 分页结果 =====
var pages = [];
/**
* 确保隐藏渲染区域存在
*/
function ensureStaging() {
if ($('#render-staging').length === 0) {
$('
').css({
position: 'fixed',
left: '-9999px',
top: 0,
visibility: 'hidden'
}).appendTo('body');
}
}
/**
* 初始化引擎
* @param {Object} options - {postId, title, desc, coverText, contentHtml, poster, authorName, createTime, categoryName}
* @param {Object} userConfig - {size, fontSize, watermark}
*/
function init(options, userConfig) {
postData.id = options.postId || 0;
postData.title = options.title || '';
postData.desc = options.desc || '';
postData.coverText = options.coverText || '';
postData.content_html = options.contentHtml || '';
postData.poster = options.poster || '';
postData.author_name = options.authorName || '';
postData.create_time = options.createTime || '';
postData.category_name = options.categoryName || '';
if (userConfig) {
$.extend(config, userConfig);
}
// 内容流事件委托(只绑定一次)
$(document).off('click', '.break-inserter-btn');
$(document).off('click', '.remove-break-btn');
$(document).off('click', '.thumb-alignment-toggle');
$(document).on('click', '.break-inserter-btn', function () {
var afterIndex = parseInt($(this).parent().data('after-index'), 10);
insertPageBreak(afterIndex);
});
$(document).on('click', '.remove-break-btn', function () {
var index = parseInt($(this).data('index'), 10);
removePageBreak(index);
});
$(document).on('click', '.thumb-alignment-toggle', function () {
var pageNum = parseInt($(this).data('page-num'), 10);
var currentAlign = (config.pageAlignments && config.pageAlignments[pageNum]) || 'top';
var newAlign = currentAlign === 'top' ? 'center' : 'top';
setPageAlignment(pageNum, newAlign);
render();
});
}
/**
* 渲染排版预览 - 生成分页后的HTML
* 两遍遍历: 先算总页数, 再带页码生成
* @returns {jQuery Deferred} resolves with pages array
*/
function render() {
var deferred = $.Deferred();
pages = [];
var sizeConfig = config.sizes[config.size] || config.sizes.xiaohongshu;
var pageHeight = sizeConfig.height;
var contentAreaHeight = pageHeight - (config.contentPadding * 2);
var cleanHtml = preprocessContent(postData.content_html);
var blocks = parseHtmlToBlocks(cleanHtml);
// 封面页
pages.push(generateCoverPage(sizeConfig));
// 内容分页
var contentPages = paginateContent(blocks, contentAreaHeight, sizeConfig);
pages = pages.concat(contentPages);
// 尾页
pages.push(generateSummaryPage(sizeConfig, pages.length));
// 两遍遍历: 现在知道了总页数, 给每页补上 N/M 页码
var totalPages = pages.length;
for (var i = 0; i < pages.length; i++) {
if (pages[i].type === 'content') {
// 在页脚中添加 N/M 格式页码
pages[i].html = pages[i].html.replace(
'' + pages[i].pageNum + '',
'' + (i + 1) + '/' + totalPages + ''
);
}
}
// 渲染内容流(中间栏)- 同步
renderContentFlow(blocks);
// 渲染缩略图(右侧)- 异步截图
renderThumbnails(sizeConfig).then(function () {
deferred.resolve(pages);
}).catch(function (err) {
deferred.reject(err);
});
return deferred.promise();
}
/**
* 预处理HTML内容
*/
function preprocessContent(html) {
if (!html) return '';
html = html.replace(/