新增通用 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 产物仍为未签名内部发行。
210 lines
5.2 KiB
TypeScript
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"
|
|
});
|