fix: 修复预览与 PDF 渲染边界问题

This commit is contained in:
SkyJourney
2026-07-27 00:33:56 +08:00
parent d93728bb8b
commit b459def232
10 changed files with 413 additions and 85 deletions
+3
View File
@@ -608,6 +608,9 @@ export function App() {
}
function handlePreviewModeChange(mode: PreviewMode) {
if (mode === previewMode) {
return;
}
setPreviewMode(mode);
setPreviewFrameReady(false);
setPreviewPagination({ current: 0, total: 0 });
+22 -15
View File
@@ -1,5 +1,6 @@
import {
getPaperDimensionsMm,
lengthToMillimeters,
paperFormatLabels,
supportedMermaidLayouts,
supportedMermaidLooks,
@@ -76,10 +77,6 @@ const mermaidFontFamilies = [
"Consolas, Microsoft YaHei, monospace"
] as const;
function millimeters(value: string) {
return Number.parseFloat(value.replace(/mm$/i, ""));
}
function fitMarginPair(
first: number,
second: number,
@@ -103,16 +100,18 @@ function LengthInput({
maximum,
onChange
}: LengthInputProps) {
const [draft, setDraft] = useState(String(millimeters(value)));
const [draft, setDraft] = useState(
String(lengthToMillimeters(value))
);
useEffect(() => {
setDraft(String(millimeters(value)));
setDraft(String(lengthToMillimeters(value)));
}, [value]);
function commit() {
const number = Number.parseFloat(draft);
if (!Number.isFinite(number)) {
setDraft(String(millimeters(value)));
setDraft(String(lengthToMillimeters(value)));
return;
}
const normalized = Math.min(Math.max(number, 0), maximum);
@@ -231,13 +230,13 @@ export function ExportSettingsDrawer({
nextPaper.orientation
);
const [left, right] = fitMarginPair(
millimeters(nextPaper.margins.left),
millimeters(nextPaper.margins.right),
lengthToMillimeters(nextPaper.margins.left),
lengthToMillimeters(nextPaper.margins.right),
nextDimensions.width - 1
);
const [top, bottom] = fitMarginPair(
millimeters(nextPaper.margins.top),
millimeters(nextPaper.margins.bottom),
lengthToMillimeters(nextPaper.margins.top),
lengthToMillimeters(nextPaper.margins.bottom),
nextDimensions.height - 1
);
@@ -380,7 +379,9 @@ export function ExportSettingsDrawer({
label="上"
value={margins.top}
maximum={
dimensions.height - millimeters(margins.bottom) - verticalGap
dimensions.height -
lengthToMillimeters(margins.bottom) -
verticalGap
}
onChange={(value) => updateMargin("top", value)}
/>
@@ -388,7 +389,9 @@ export function ExportSettingsDrawer({
label="右"
value={margins.right}
maximum={
dimensions.width - millimeters(margins.left) - horizontalGap
dimensions.width -
lengthToMillimeters(margins.left) -
horizontalGap
}
onChange={(value) => updateMargin("right", value)}
/>
@@ -396,7 +399,9 @@ export function ExportSettingsDrawer({
label="下"
value={margins.bottom}
maximum={
dimensions.height - millimeters(margins.top) - verticalGap
dimensions.height -
lengthToMillimeters(margins.top) -
verticalGap
}
onChange={(value) => updateMargin("bottom", value)}
/>
@@ -404,7 +409,9 @@ export function ExportSettingsDrawer({
label="左"
value={margins.left}
maximum={
dimensions.width - millimeters(margins.right) - horizontalGap
dimensions.width -
lengthToMillimeters(margins.right) -
horizontalGap
}
onChange={(value) => updateMargin("left", value)}
/>
+82 -9
View File
@@ -2,10 +2,14 @@ import highlightCss from "highlight.js/styles/github.css?inline";
import katexCss from "katex/dist/katex.min.css?inline";
import { Previewer } from "pagedjs";
import { createMermaidSiteConfig } from "./mermaid-config";
import { fitOversizedMermaidToPage } from "./mermaid-page-fit";
import {
fitOversizedMermaidToPage,
getPageContentDimensions
} from "./mermaid-page-fit";
import { renderMermaidDefinitions } from "./mermaid-renderer";
import { replaceMermaidSvgWithImages } from "./mermaid-static-image";
import type { MermaidOutputMode } from "./mermaid-static-image";
import { enablePrintMediaForPreview } from "./preview-styles";
import "./paged-table-handler";
import {
buildPagedMediaCss,
@@ -70,6 +74,54 @@ function stylesheet(
};
}
function mountMeasurementContainer(
documentRef: Document,
content: DocumentFragment,
payload: PagedPreviewPayload,
target: PagedRenderTarget
) {
const pageContent = getPageContentDimensions(payload.exportConfig);
const style = documentRef.createElement("style");
const themeCss =
target === "preview"
? enablePrintMediaForPreview(payload.themeCss)
: payload.themeCss;
style.dataset.pagedMeasurementStyle = "true";
style.textContent = [
documentBaseCss,
highlightCss,
katexCss,
themeCss,
documentGeometryCss
].join("\n");
const host = documentRef.createElement("div");
host.dataset.pagedMeasurementHost = "true";
host.style.cssText = [
"position: fixed",
"left: -100000px",
"top: 0",
`width: ${pageContent.width}px`,
"height: auto",
"opacity: 0",
"pointer-events: none",
"contain: layout style paint"
].join(";");
documentRef.head.append(style);
host.append(content);
documentRef.body.append(host);
return {
host,
restore() {
content.append(...Array.from(host.childNodes));
host.remove();
style.remove();
}
};
}
function applyPageNumbers(
container: ParentNode,
payload: PagedPreviewPayload,
@@ -233,16 +285,37 @@ export class PagedDocumentRuntime {
const mermaidErrors = await this.renderMermaid(content, payload);
const mermaidMs = performance.now() - mermaidStartedAt;
const mermaidFitStartedAt = performance.now();
fitOversizedMermaidToPage(content, payload.exportConfig);
const mermaidFitMs = performance.now() - mermaidFitStartedAt;
let mermaidFitMs = 0;
let mermaidConversionMs = 0;
if (content.querySelector(".mermaid svg")) {
const measurement = mountMeasurementContainer(
documentRef,
content,
payload,
options.target
);
try {
await documentRef.fonts.ready;
await waitForImages(measurement.host);
const mermaidConversionStartedAt = performance.now();
if (options.mermaidOutput === "svg-image") {
replaceMermaidSvgWithImages(content);
const mermaidFitStartedAt = performance.now();
fitOversizedMermaidToPage(
measurement.host,
payload.exportConfig
);
mermaidFitMs = performance.now() - mermaidFitStartedAt;
const mermaidConversionStartedAt = performance.now();
if (options.mermaidOutput === "svg-image") {
replaceMermaidSvgWithImages(measurement.host);
await waitForImages(measurement.host);
}
mermaidConversionMs =
performance.now() - mermaidConversionStartedAt;
} finally {
measurement.restore();
}
}
const mermaidConversionMs =
performance.now() - mermaidConversionStartedAt;
if (!shouldContinue()) {
return undefined;