feat: 建立 DOCX 主题映射引擎

This commit is contained in:
SkyJourney
2026-07-30 20:28:47 +08:00
parent e7c688df6e
commit 6fed666f44
15 changed files with 851 additions and 25 deletions
+158
View File
@@ -0,0 +1,158 @@
import {
docxStylePresetSchema,
docxThemeStyleModeSchema
} from "@md-to-pdf/core";
import { z } from "zod";
import { docxStyleSlotNameSchema } from "./slots.js";
const colorSchema = z
.string()
.regex(/^#[0-9a-f]{6}$/iu, "使");
export const docxStyleValueSourceSchema = z.enum([
"computed-css",
"manifest-override",
"preset-fallback",
"engine-default"
]);
export type DocxStyleValueSource = z.infer<
typeof docxStyleValueSourceSchema
>;
export const docxStyleConfidenceSchema = z.enum([
"exact",
"approximate",
"fallback"
]);
export type DocxStyleConfidence = z.infer<
typeof docxStyleConfidenceSchema
>;
export const docxThemeDiagnosticSeveritySchema = z.enum([
"info",
"warning",
"error"
]);
export const docxThemeDiagnosticCodeSchema = z.enum([
"slot-not-found",
"css-value-invalid",
"css-property-unsupported",
"font-fallback-required",
"layout-approximated",
"preset-fallback-used",
"manifest-override-applied"
]);
export const docxThemeDiagnosticSchema = z.object({
severity: docxThemeDiagnosticSeveritySchema,
code: docxThemeDiagnosticCodeSchema,
message: z.string().min(1).max(500),
slot: docxStyleSlotNameSchema.optional(),
property: z.string().min(1).max(100).optional()
});
export type DocxThemeDiagnostic = z.infer<
typeof docxThemeDiagnosticSchema
>;
const docxBorderTokenSchema = z.object({
widthPt: z.number().min(0).max(20),
color: colorSchema,
style: z.enum([
"none",
"single",
"double",
"dotted",
"dashed"
])
});
export const docxSlotStyleTokenSchema = z.object({
fontCandidates: z.array(z.string().trim().min(1).max(100)).max(20),
fontSizePt: z.number().min(1).max(200).optional(),
bold: z.boolean().optional(),
italic: z.boolean().optional(),
underline: z.boolean().optional(),
strikethrough: z.boolean().optional(),
color: colorSchema.optional(),
backgroundColor: colorSchema.optional(),
lineSpacing: z.number().min(0.5).max(10).optional(),
letterSpacingPt: z.number().min(-20).max(100).optional(),
alignment: z
.enum(["left", "center", "right", "justify"])
.optional(),
firstLineIndentPt: z.number().min(-1000).max(1000).optional(),
leftIndentPt: z.number().min(-1000).max(1000).optional(),
rightIndentPt: z.number().min(-1000).max(1000).optional(),
spacingBeforePt: z.number().min(0).max(2000).optional(),
spacingAfterPt: z.number().min(0).max(2000).optional(),
paddingPt: z
.object({
top: z.number().min(0).max(1000),
right: z.number().min(0).max(1000),
bottom: z.number().min(0).max(1000),
left: z.number().min(0).max(1000)
})
.optional(),
borders: z
.object({
top: docxBorderTokenSchema.optional(),
right: docxBorderTokenSchema.optional(),
bottom: docxBorderTokenSchema.optional(),
left: docxBorderTokenSchema.optional()
})
.optional(),
widthPercent: z.number().min(0).max(100).optional(),
pageBreakBefore: z.boolean().optional(),
pageBreakAfter: z.boolean().optional(),
keepLines: z.boolean().optional(),
keepWithNext: z.boolean().optional()
});
export type DocxSlotStyleToken = z.infer<
typeof docxSlotStyleTokenSchema
>;
export const docxResolvedStyleSlotSchema = z.object({
slot: docxStyleSlotNameSchema,
source: docxStyleValueSourceSchema,
confidence: docxStyleConfidenceSchema,
style: docxSlotStyleTokenSchema
});
export type DocxResolvedStyleSlot = z.infer<
typeof docxResolvedStyleSlotSchema
>;
export const docxThemeTokenSetSchema = z
.object({
schemaVersion: z.literal(1),
themeId: z
.string()
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u),
themeFingerprint: z.string().regex(/^[a-f0-9]{64}$/u),
mode: docxThemeStyleModeSchema,
basePreset: docxStylePresetSchema,
slots: z.array(docxResolvedStyleSlotSchema),
diagnostics: z.array(docxThemeDiagnosticSchema)
})
.superRefine((tokens, context) => {
const names = new Set<string>();
for (const entry of tokens.slots) {
if (names.has(entry.slot)) {
context.addIssue({
code: "custom",
message: `DOCX 样式令牌包含重复槽位:${entry.slot}`,
path: ["slots"]
});
}
names.add(entry.slot);
}
});
export type DocxThemeTokenSet = z.infer<
typeof docxThemeTokenSetSchema
>;