update at 2025-10-09 14:46:24

This commit is contained in:
douboer
2025-10-09 14:46:24 +08:00
parent cab675abcc
commit 52110c6024
14 changed files with 180 additions and 62 deletions

View File

@@ -22,13 +22,14 @@ function parseAspectRatio(ratio: string): { width: number; height: number } {
return { width: 3, height: 4 };
}
const PAGE_PADDING = 40; // 与 renderPage 保持一致的页面内边距
/**
* 计算目标页面高度
*/
function getTargetPageHeight(settings: NMPSettings): number {
const ratio = parseAspectRatio(settings.sliceImageAspectRatio);
const height = Math.round((settings.sliceImageWidth * ratio.height) / ratio.width);
console.log(`[paginator] 计算页面高度: 宽度=${settings.sliceImageWidth}, 比例=${settings.sliceImageAspectRatio} (${ratio.width}:${ratio.height}), 高度=${height}`);
return height;
}
@@ -56,17 +57,38 @@ export async function paginateArticle(
): Promise<PageInfo[]> {
const pageHeight = getTargetPageHeight(settings);
const pageWidth = settings.sliceImageWidth;
// 创建临时容器用于测量
const measureContainer = document.createElement('div');
measureContainer.style.cssText = `
// 创建临时测量容器:与实际页面一致的宽度与内边距
const measureHost = document.createElement('div');
measureHost.style.cssText = `
position: absolute;
left: -9999px;
top: 0;
width: ${pageWidth}px;
visibility: hidden;
box-sizing: border-box;
`;
document.body.appendChild(measureContainer);
document.body.appendChild(measureHost);
const measurePage = document.createElement('div');
measurePage.className = 'xhs-page';
measurePage.style.boxSizing = 'border-box';
measurePage.style.width = `${pageWidth}px`;
measurePage.style.padding = `${PAGE_PADDING}px`;
measurePage.style.background = 'white';
measurePage.style.position = 'relative';
measureHost.appendChild(measurePage);
const measureContent = document.createElement('div');
measureContent.className = 'xhs-page-content';
measurePage.appendChild(measureContent);
if (articleElement.classList.length > 0) {
measureContent.classList.add(...Array.from(articleElement.classList));
}
const measuredFontSize = window.getComputedStyle(articleElement).fontSize;
if (measuredFontSize) {
measureContent.style.fontSize = measuredFontSize;
}
const pages: PageInfo[] = [];
let currentPageContent: Element[] = [];
@@ -79,51 +101,45 @@ export async function paginateArticle(
for (const child of children) {
const childClone = child.cloneNode(true) as HTMLElement;
measureContainer.innerHTML = '';
measureContainer.appendChild(childClone);
// 等待浏览器完成渲染
await new Promise(resolve => setTimeout(resolve, 10));
const childHeight = childClone.offsetHeight;
const isIndivisible = isIndivisibleElement(child);
measureContent.appendChild(childClone);
// 判断是否需要换页
if (currentPageHeight + childHeight > pageHeight && currentPageContent.length > 0) {
// 如果是不可分割元素且加入后会超出,先保存当前页
if (isIndivisible) {
pages.push({
index: pageIndex++,
content: wrapPageContent(currentPageContent),
height: currentPageHeight
});
currentPageContent = [child];
currentPageHeight = childHeight;
} else {
// 可分割元素(段落等),尝试加入当前页
if (currentPageHeight + childHeight <= pageHeight * 1.1) {
// 允许 10% 的溢出容差
currentPageContent.push(child);
currentPageHeight += childHeight;
} else {
// 超出太多,换页
pages.push({
index: pageIndex++,
content: wrapPageContent(currentPageContent),
height: currentPageHeight
});
currentPageContent = [child];
currentPageHeight = childHeight;
}
}
} else {
// 加入当前页
await waitForLayout();
const totalHeight = measurePage.scrollHeight;
const isIndivisible = isIndivisibleElement(child);
const fitsCurrentPage =
totalHeight <= pageHeight ||
(!isIndivisible && totalHeight <= pageHeight * 1.1) ||
currentPageContent.length === 0;
if (fitsCurrentPage) {
currentPageContent.push(child);
currentPageHeight += childHeight;
currentPageHeight = totalHeight;
continue;
}
// 当前页已放不下:移除刚刚加入的克隆节点
measureContent.removeChild(childClone);
await waitForLayout();
if (currentPageContent.length > 0) {
pages.push({
index: pageIndex++,
content: wrapPageContent(currentPageContent),
height: currentPageHeight
});
}
currentPageContent = [child];
measureContent.innerHTML = '';
const firstClone = child.cloneNode(true) as HTMLElement;
measureContent.appendChild(firstClone);
await waitForLayout();
currentPageHeight = measurePage.scrollHeight;
// 不可分割元素即使超过高度也直接保留在新页
}
// 保存最后一页
if (currentPageContent.length > 0) {
pages.push({
index: pageIndex,
@@ -132,12 +148,15 @@ export async function paginateArticle(
});
}
// 清理临时容器
document.body.removeChild(measureContainer);
document.body.removeChild(measureHost);
return pages;
}
async function waitForLayout(): Promise<void> {
await new Promise<void>(resolve => requestAnimationFrame(() => resolve()));
}
/**
* 包装页面内容为完整的 HTML
*/
@@ -163,8 +182,6 @@ export function renderPage(
const actualPageWidth = settings.sliceImageWidth;
const actualPageHeight = getTargetPageHeight(settings);
console.log(`[renderPage] 渲染页面: 宽=${actualPageWidth}, 高=${actualPageHeight}`);
container.innerHTML = '';
// 直接设置为实际尺寸,用于切图
@@ -174,7 +191,7 @@ export function renderPage(
height: ${actualPageHeight}px;
overflow: hidden;
box-sizing: border-box;
padding: 40px;
padding: ${PAGE_PADDING}px;
background: white;
`;