Files
MorphDoc/packages/docx-theme-engine/src/tokens.ts
T
SkyJourney b275c671fc fix: 修复DOCX独立封面视觉门禁
统一 Chromium 与 DOCX 的受限高度封面布局,补齐可编辑边框、装饰边和 Office 页面舍入适配。

按四套独立封面主题、纵横方向及五组页边距执行 40 个 Word/WPS 真实视觉场景,严格失败为 0,并保留正文诊断供 D4 修复。
2026-08-03 18:15:21 +08:00

174 lines
4.8 KiB
TypeScript

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(),
outline: docxBorderTokenSchema.optional(),
outlineOffsetPt: z.number().min(-1000).max(1000).optional(),
widthPercent: z.number().min(0).max(100).optional(),
minimumWidthPercent: z.number().min(0).max(100).optional(),
minimumHeightPt: z.number().min(0).max(10000).optional(),
contentPaddingLeftPt: z.number().min(0).max(1000).optional(),
verticalAlignment: z
.enum(["top", "center", "bottom"])
.optional(),
childAlignment: z
.enum(["left", "center", "right", "stretch"])
.optional(),
selfAlignment: z
.enum(["left", "center", "right", "stretch"])
.optional(),
hidden: z.boolean().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
>;