feat: 实现 DOCX 主题样式采集

This commit is contained in:
SkyJourney
2026-07-30 22:09:30 +08:00
parent 6086762b4f
commit d2acc9c5ce
20 changed files with 1265 additions and 11 deletions
+53 -1
View File
@@ -3,7 +3,12 @@ import type {
DocxMediaCaptureAdapter,
DocxMediaCaptureRequest
} from "@md-to-pdf/application";
import type {
DocxThemeStyleCaptureAdapter,
DocxThemeStyleCaptureRequest
} from "@md-to-pdf/docx-theme-engine";
import { captureDocxMediaWithElectronWebContents } from "./electron-docx-media-capture.js";
import { captureDocxThemeStyleWithElectronWebContents } from "./electron-docx-theme-style.js";
import { isAllowedPdfRuntimeUrl } from "./pdf-contract.js";
export const DESKTOP_DOCX_PARTITION =
@@ -14,7 +19,7 @@ export interface ElectronDocxMediaEngineOptions {
}
export class ElectronDocxMediaEngine
implements DocxMediaCaptureAdapter
implements DocxMediaCaptureAdapter, DocxThemeStyleCaptureAdapter
{
private readonly renderUrl: string;
private windowPromise: Promise<BrowserWindow> | undefined;
@@ -42,6 +47,23 @@ export class ElectronDocxMediaEngine
return operation;
}
captureThemeStyle(
request: DocxThemeStyleCaptureRequest,
signal?: AbortSignal
) {
if (this.closed) {
return Promise.reject(new Error("桌面 DOCX 媒体引擎已关闭"));
}
const operation = this.queue.then(() =>
this.captureThemeStyleNow(request, signal)
);
this.queue = operation.then(
() => undefined,
() => undefined
);
return operation;
}
async close() {
this.closed = true;
await this.queue;
@@ -84,6 +106,36 @@ export class ElectronDocxMediaEngine
}
}
private async captureThemeStyleNow(
request: DocxThemeStyleCaptureRequest,
signal?: AbortSignal
) {
signal?.throwIfAborted();
if (
new URL(request.baseUrl).origin !==
new URL(this.renderUrl).origin
) {
throw new Error("桌面 DOCX 主题资源地址必须与渲染地址同源");
}
const window = await this.getWindow();
signal?.throwIfAborted();
const abort = () => {
if (!window.isDestroyed()) {
window.destroy();
}
};
signal?.addEventListener("abort", abort, { once: true });
try {
return await captureDocxThemeStyleWithElectronWebContents(
window.webContents,
request,
signal
);
} finally {
signal?.removeEventListener("abort", abort);
}
}
private getWindow() {
if (!this.windowPromise) {
this.windowPromise = this.createWindow().catch((error) => {