Files
MorphDoc/apps/web/src/paged-preview-frame.ts
T
SkyJourney 2c5c1bd317 release: 发布 v0.6.1 DOCX 视觉一致性修复
新增通用 CSS 到 OOXML 翻译修复,统一字体、字距、精确行距、段落、列表、表格、引用、代码块与行内代码连续性,不引入按主题 ID 分支。

新增 MdTP Mono 并统一 Serif、Sans、Mono 三字体包的 Chromium 与 DOCX 使用链;字体声明、嵌入部件和 Word/WPS 实际采用均进入硬门禁。

重建封面整页及正文语义块视觉差分,14 套主题、纵横两个方向、五组页边距共 140 个真实场景全部通过,阻断失败和诊断失败均为零。

源码服务、Docker Web API 与实际安装 Desktop 的 red-briefing 导出均包含 5 个字体部件;Word/WPS 原生渲染和逐页复核通过。修复 Docker 构建上下文与运行层复用软链接,并完善 v0.6.1 版本、发行说明和发布归集。

验证:npm test(116 个文件、616 项测试)、npm run typecheck、npm run build、git diff --check 全部通过。Desktop 安装器与 ZIP、Docker v0.6.1 镜像已生成;Windows 产物仍为未签名内部发行。
2026-08-04 10:30:44 +08:00

210 lines
5.2 KiB
TypeScript

import echartsCss from "@md-to-pdf/markdown-echarts/styles.css?inline";
import {
isPagedPreviewRenderRequest,
PAGED_PREVIEW_MESSAGE_SCOPE,
PagedDocumentRuntime,
SHARED_HIGHLIGHT_CSS,
renderDocxMediaCapturePlan,
resolveMermaidOutputMode,
resolvePagedRenderTarget,
type PagedPreviewFrameMessage,
type PagedPreviewPayload
} from "@md-to-pdf/preview-engine";
import katexCss from "katex/dist/katex.min.css?inline";
import {
beginPreviewFrameRender,
completePreviewFrameRender
} from "./preview-frame-visibility";
import { withHiddenWindowAnimationFrames } from "./hidden-window-animation-frame";
function getPreviewRoot() {
const element = document.querySelector<HTMLElement>("#preview-root");
if (!element) {
throw new Error("分页预览容器不存在");
}
return element;
}
const previewRoot = getPreviewRoot();
const renderTarget = resolvePagedRenderTarget(window.location.search);
const mermaidOutput = resolveMermaidOutputMode(window.location.search);
const runtime = new PagedDocumentRuntime(previewRoot, {
highlightCss: SHARED_HIGHLIGHT_CSS,
katexCss,
echartsCss
});
document.documentElement.dataset.renderTarget = renderTarget;
let latestRequestId = 0;
let renderQueue = Promise.resolve();
function post(message: PagedPreviewFrameMessage) {
window.parent.postMessage(message, window.location.origin);
}
function measurePreviewContentHeight() {
const rootRect = previewRoot.getBoundingClientRect();
const documentRect =
document.documentElement.getBoundingClientRect();
return Math.ceil(
Math.max(
document.documentElement.scrollHeight,
document.body.scrollHeight,
previewRoot.scrollHeight,
rootRect.bottom - documentRect.top
)
);
}
document.addEventListener(
"click",
(event) => {
const target =
event.target instanceof Element
? event.target.closest<HTMLAnchorElement>("a[href]")
: undefined;
const href = target?.getAttribute("href")?.trim();
if (!href) {
return;
}
event.preventDefault();
event.stopPropagation();
post({
scope: PAGED_PREVIEW_MESSAGE_SCOPE,
type: "link",
href
});
},
{ capture: true }
);
async function renderPagedPreview(
requestId: number,
payload: PagedPreviewPayload,
layout: "paged" | "continuous"
) {
if (requestId !== latestRequestId) {
return;
}
beginPreviewFrameRender(document.documentElement, requestId);
post({
scope: PAGED_PREVIEW_MESSAGE_SCOPE,
type: "rendering",
requestId,
layout
});
try {
const result =
layout === "continuous"
? await runtime.renderContinuous(payload, {
shouldContinue: () => requestId === latestRequestId
})
: await runtime.render(payload, {
target: renderTarget,
mermaidOutput,
shouldContinue: () => requestId === latestRequestId
});
if (!result) {
return;
}
post({
scope: PAGED_PREVIEW_MESSAGE_SCOPE,
type: "rendered",
requestId,
layout,
contentHeight: measurePreviewContentHeight(),
...result
});
} finally {
if (requestId === latestRequestId) {
completePreviewFrameRender(document.documentElement, requestId);
}
}
}
window.__mdToPdfRender = async (
payload,
target = renderTarget
) => {
const result = await runtime.render(payload, {
target,
mermaidOutput
});
if (!result) {
throw new Error("分页文档渲染已取消");
}
return result;
};
window.__mdToPdfRenderDocxMedia = (payload, dimensions) =>
withHiddenWindowAnimationFrames(window, () =>
renderDocxMediaCapturePlan(
runtime,
previewRoot,
payload,
dimensions
)
);
document.documentElement.dataset.runtimeReady = "true";
const desktopPdfBridge = window.__mdToPdfDesktopPdf;
if (desktopPdfBridge) {
desktopPdfBridge.onRender(async (requestId, payload) => {
try {
const result = await withHiddenWindowAnimationFrames(
window,
async () => window.__mdToPdfRender?.(payload, "pdf")
);
if (!result) {
throw new Error("PDF 分页运行时不可用");
}
desktopPdfBridge.complete(requestId, result);
} catch (reason: unknown) {
desktopPdfBridge.fail(
requestId,
reason instanceof Error ? reason.message : "PDF 分页失败"
);
}
});
desktopPdfBridge.ready();
}
window.addEventListener("message", (event: MessageEvent<unknown>) => {
if (
event.origin !== window.location.origin ||
event.source !== window.parent ||
!isPagedPreviewRenderRequest(event.data)
) {
return;
}
latestRequestId = Math.max(latestRequestId, event.data.requestId);
const { requestId, layout, payload } = event.data;
renderQueue = renderQueue
.catch(() => undefined)
.then(async () => {
try {
await renderPagedPreview(requestId, payload, layout);
} catch (reason: unknown) {
if (requestId !== latestRequestId) {
return;
}
post({
scope: PAGED_PREVIEW_MESSAGE_SCOPE,
type: "error",
requestId,
message:
reason instanceof Error ? reason.message : "分页预览失败"
});
}
});
});
post({
scope: PAGED_PREVIEW_MESSAGE_SCOPE,
type: "ready"
});