Files
MorphDoc/scripts/docx-layout-visual-matrix-cases.mjs
T

204 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import fs from "node:fs";
import path from "node:path";
export const DOCX_LAYOUT_MATRIX_ORIENTATIONS = Object.freeze([
"portrait",
"landscape"
]);
export const DOCX_LAYOUT_MATRIX_MARGIN_SCENARIOS = Object.freeze([
Object.freeze({
id: "theme-default",
label: "主题默认边距",
marginMode: "theme"
}),
Object.freeze({
id: "standard",
label: "标准边距",
marginMode: "custom",
margins: Object.freeze({
top: "25.4mm",
right: "25.4mm",
bottom: "25.4mm",
left: "25.4mm"
})
}),
Object.freeze({
id: "compact",
label: "紧凑边距",
marginMode: "custom",
margins: Object.freeze({
top: "12.7mm",
right: "12.7mm",
bottom: "12.7mm",
left: "12.7mm"
})
}),
Object.freeze({
id: "wide",
label: "宽松边距",
marginMode: "custom",
margins: Object.freeze({
top: "31.8mm",
right: "31.8mm",
bottom: "31.8mm",
left: "31.8mm"
})
}),
Object.freeze({
id: "binding",
label: "非对称装订边距",
marginMode: "custom",
margins: Object.freeze({
top: "25.4mm",
right: "19.1mm",
bottom: "25.4mm",
left: "31.8mm"
})
})
]);
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
function hasIndependentCover(manifest) {
const profiles = Array.isArray(manifest.compatibleProfiles)
? manifest.compatibleProfiles
: [];
return profiles.includes("project-report") || profiles.includes("tender");
}
export function readBundledThemeMatrixDefinitions(themesDirectory) {
assert(
typeof themesDirectory === "string" && themesDirectory.length > 0,
"主题目录不能为空"
);
return fs.readdirSync(themesDirectory, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.flatMap((entry) => {
const manifestPath = path.join(themesDirectory, entry.name, "theme.json");
if (!fs.existsSync(manifestPath)) {
return [];
}
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
if (manifest.bundled !== true) {
return [];
}
assert(
typeof manifest.id === "string" && manifest.id.length > 0,
`内置主题清单缺少 id${manifestPath}`
);
return [{
id: manifest.id,
name:
typeof manifest.name === "string" && manifest.name.length > 0
? manifest.name
: manifest.id,
coverPageCount: hasIndependentCover(manifest) ? 1 : 0,
compatibleProfiles: Array.isArray(manifest.compatibleProfiles)
? [...manifest.compatibleProfiles]
: [],
themeDefaultMargins: manifest.pageDefaults?.margins
? structuredClone(manifest.pageDefaults.margins)
: undefined
}];
})
.sort((left, right) => left.id.localeCompare(right.id, "en"));
}
function createPaperConfig(orientation, marginScenario) {
return {
format: "A4",
orientation,
marginMode: marginScenario.marginMode,
...(marginScenario.marginMode === "custom"
? { margins: structuredClone(marginScenario.margins) }
: {})
};
}
export function createDocxLayoutVisualMatrixCases(themeDefinitions) {
assert(Array.isArray(themeDefinitions), "主题定义必须是数组");
const ids = new Set();
const cases = [];
for (const theme of themeDefinitions) {
assert(
theme && typeof theme.id === "string" && theme.id.length > 0,
"矩阵主题必须包含非空 id"
);
assert(!ids.has(theme.id), `矩阵主题 ID 重复:${theme.id}`);
ids.add(theme.id);
for (const orientation of DOCX_LAYOUT_MATRIX_ORIENTATIONS) {
for (const marginScenario of DOCX_LAYOUT_MATRIX_MARGIN_SCENARIOS) {
const paper = createPaperConfig(orientation, marginScenario);
cases.push({
id: `${theme.id}--${orientation}--${marginScenario.id}`,
themeId: theme.id,
themeName: theme.name,
orientation,
marginScenarioId: marginScenario.id,
marginScenarioLabel: marginScenario.label,
coverPageCount: theme.coverPageCount,
themeDefaultMargins: theme.themeDefaultMargins
? structuredClone(theme.themeDefaultMargins)
: undefined,
paper: { format: "A4", orientation },
config: { paper }
});
}
}
}
return cases;
}
export function summarizeDocxLayoutVisualMatrix(themeDefinitions, cases) {
const themeIds = themeDefinitions.map((theme) => theme.id);
const caseIds = cases.map((entry) => entry.id);
const expectedCaseCount =
themeIds.length *
DOCX_LAYOUT_MATRIX_ORIENTATIONS.length *
DOCX_LAYOUT_MATRIX_MARGIN_SCENARIOS.length;
assert(
new Set(caseIds).size === caseIds.length,
"DOCX 布局视觉矩阵存在重复场景 ID"
);
assert(
cases.length === expectedCaseCount,
`DOCX 布局视觉矩阵场景数错误:${cases.length},期望 ${expectedCaseCount}`
);
for (const themeId of themeIds) {
const themeCaseCount = cases.filter(
(entry) => entry.themeId === themeId
).length;
assert(
themeCaseCount ===
DOCX_LAYOUT_MATRIX_ORIENTATIONS.length *
DOCX_LAYOUT_MATRIX_MARGIN_SCENARIOS.length,
`主题 ${themeId} 的矩阵场景数错误:${themeCaseCount}`
);
}
return {
themeCount: themeIds.length,
themeIds,
orientationCount: DOCX_LAYOUT_MATRIX_ORIENTATIONS.length,
orientations: [...DOCX_LAYOUT_MATRIX_ORIENTATIONS],
marginScenarioCount: DOCX_LAYOUT_MATRIX_MARGIN_SCENARIOS.length,
marginScenarios: DOCX_LAYOUT_MATRIX_MARGIN_SCENARIOS.map((scenario) => ({
id: scenario.id,
label: scenario.label,
marginMode: scenario.marginMode,
...(scenario.margins
? { margins: structuredClone(scenario.margins) }
: {})
})),
expectedCaseCount,
coverThemeCount: themeDefinitions.filter(
(theme) => theme.coverPageCount === 1
).length,
coverCaseCount: cases.filter((entry) => entry.coverPageCount === 1).length
};
}