feat: 完成 DOCX 通用结构映射与严格收口
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
import {
|
||||
randomUUID
|
||||
} from "node:crypto";
|
||||
import {
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
@@ -15,6 +18,7 @@ import {
|
||||
type ExportConfig,
|
||||
type MarkdownDocumentMetadata,
|
||||
type PreparedDocxMedia,
|
||||
type SemanticDocumentModel,
|
||||
type ThemeManifest
|
||||
} from "@md-to-pdf/core";
|
||||
import type { DocxThemeTokenSet } from "@md-to-pdf/docx-theme-engine";
|
||||
@@ -23,8 +27,10 @@ import {
|
||||
createDynamicReferenceDocx,
|
||||
type DynamicReferenceDocxResult
|
||||
} from "./reference-builder.js";
|
||||
import { finalizeGeneratedDocxStructure } from "./document-structure-transform.js";
|
||||
import type { DynamicReferenceDocxOptions } from "./reference-options.js";
|
||||
import { preparePandocMedia } from "./pandoc-media.js";
|
||||
import { createPandocStructurePlan } from "./pandoc-structure.js";
|
||||
import {
|
||||
runPandocProcess,
|
||||
type PandocProcessRunner
|
||||
@@ -42,6 +48,8 @@ const MAXIMUM_PANDOC_STDERR_BYTES = 64 * 1024;
|
||||
const DEFAULT_REFERENCE_CACHE_SIZE = 32;
|
||||
const temporaryDirectoryPrefix = "md-to-pdf-docx-";
|
||||
const mediaMapEnvironmentName = "MD_TO_PDF_DOCX_MEDIA_MAP";
|
||||
const structurePlanEnvironmentName =
|
||||
"MD_TO_PDF_DOCX_STRUCTURE_PLAN";
|
||||
const luaFilterUrl = new URL(
|
||||
"../assets/docx-media-filter.lua",
|
||||
import.meta.url
|
||||
@@ -60,6 +68,7 @@ export interface PandocDocxConversionInput {
|
||||
theme: ThemeManifest;
|
||||
themeTokens: DocxThemeTokenSet;
|
||||
metadata: MarkdownDocumentMetadata;
|
||||
semanticDocument: SemanticDocumentModel;
|
||||
media: PreparedDocxMedia;
|
||||
}
|
||||
|
||||
@@ -215,12 +224,18 @@ export class PandocDocxConverter {
|
||||
signal?: AbortSignal
|
||||
): Promise<PandocDocxConversionResult> {
|
||||
let preparedMedia;
|
||||
let structurePlan;
|
||||
try {
|
||||
preparedMedia = preparePandocMedia(input.media);
|
||||
structurePlan = createPandocStructurePlan(
|
||||
input.semanticDocument,
|
||||
input.themeTokens,
|
||||
{ markerSeed: randomUUID() }
|
||||
);
|
||||
} catch (error) {
|
||||
throw new PandocDocxConversionError(
|
||||
"DOCX_GENERATION_FAILED",
|
||||
"DOCX 媒体映射无效",
|
||||
"DOCX 媒体或语义结构映射无效",
|
||||
{ cause: error }
|
||||
);
|
||||
}
|
||||
@@ -251,6 +266,10 @@ export class PandocDocxConverter {
|
||||
"docx-media-filter.lua"
|
||||
),
|
||||
mediaMap: path.join(temporaryDirectory, "media-map.json"),
|
||||
structurePlan: path.join(
|
||||
temporaryDirectory,
|
||||
"structure-plan.json"
|
||||
),
|
||||
mediaDirectory: path.join(temporaryDirectory, "media"),
|
||||
dataDirectory: path.join(temporaryDirectory, "pandoc-data")
|
||||
};
|
||||
@@ -267,6 +286,11 @@ export class PandocDocxConverter {
|
||||
JSON.stringify(preparedMedia.map),
|
||||
"utf8"
|
||||
),
|
||||
writeFile(
|
||||
paths.structurePlan,
|
||||
JSON.stringify(structurePlan),
|
||||
"utf8"
|
||||
),
|
||||
...preparedMedia.files.map((file) =>
|
||||
writeFile(
|
||||
path.join(temporaryDirectory, file.relativePath),
|
||||
@@ -291,7 +315,8 @@ export class PandocDocxConverter {
|
||||
cwd: temporaryDirectory,
|
||||
env: {
|
||||
...(this.options.environment ?? process.env),
|
||||
[mediaMapEnvironmentName]: paths.mediaMap
|
||||
[mediaMapEnvironmentName]: paths.mediaMap,
|
||||
[structurePlanEnvironmentName]: paths.structurePlan
|
||||
},
|
||||
timeoutMs: this.timeoutMs,
|
||||
maxStdoutBytes: MAXIMUM_PANDOC_STDOUT_BYTES,
|
||||
@@ -333,9 +358,19 @@ export class PandocDocxConverter {
|
||||
"DOCX 输出大小无效"
|
||||
);
|
||||
}
|
||||
const docx = await readFile(paths.output);
|
||||
let docx: Uint8Array;
|
||||
let validation: DynamicReferenceValidation;
|
||||
try {
|
||||
const pandocDocx = await readFile(paths.output);
|
||||
const finalized = finalizeGeneratedDocxStructure(
|
||||
pandocDocx,
|
||||
structurePlan,
|
||||
input.themeTokens
|
||||
);
|
||||
docx = finalized.content;
|
||||
if (docx.byteLength > this.maximumOutputBytes) {
|
||||
throw new Error("DOCX 结构收口后的输出大小无效");
|
||||
}
|
||||
validation = validateGeneratedDocx(docx);
|
||||
} catch (error) {
|
||||
throw new PandocDocxConversionError(
|
||||
|
||||
Reference in New Issue
Block a user