@json-to-office/core-docx 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/generateFromIr.d.ts +5 -1
- package/dist/core/generateFromIr.d.ts.map +1 -1
- package/dist/core/generator.d.ts +4 -0
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/structure.d.ts +13 -0
- package/dist/core/structure.d.ts.map +1 -1
- package/dist/core/tableModel.d.ts +22 -0
- package/dist/core/tableModel.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1218 -214
- package/dist/index.js.map +1 -1
- package/dist/plugin/example/index.js +612 -117
- package/dist/plugin/example/index.js.map +1 -1
- package/dist/quality/facts.d.ts +112 -0
- package/dist/quality/facts.d.ts.map +1 -0
- package/dist/quality/preflight.d.ts +15 -0
- package/dist/quality/preflight.d.ts.map +1 -0
- package/dist/quality/rules.d.ts +39 -0
- package/dist/quality/rules.d.ts.map +1 -0
- package/dist/quality/text-metrics.d.ts +34 -0
- package/dist/quality/text-metrics.d.ts.map +1 -0
- package/dist/templates/documents/proposal.docx.json +277 -42
- package/dist/templates/documents/technical-guide.docx.json +273 -39
- package/dist/templates/themes/index.d.ts.map +1 -1
- package/dist/templates/themes/vermilion.docx.theme.json +130 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/imageUtils.d.ts.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -937,6 +937,143 @@ var init_devportal_docx_theme = __esm({
|
|
|
937
937
|
}
|
|
938
938
|
});
|
|
939
939
|
|
|
940
|
+
// src/templates/themes/vermilion.docx.theme.json
|
|
941
|
+
var vermilion_docx_theme_default;
|
|
942
|
+
var init_vermilion_docx_theme = __esm({
|
|
943
|
+
"src/templates/themes/vermilion.docx.theme.json"() {
|
|
944
|
+
vermilion_docx_theme_default = {
|
|
945
|
+
name: "vermilion",
|
|
946
|
+
displayName: "Vermilion Editorial",
|
|
947
|
+
description: "Poster-red editorial system \u2014 vermilion display headings, ink text, warm creams and hairline rules",
|
|
948
|
+
version: "1.0.0",
|
|
949
|
+
colors: {
|
|
950
|
+
primary: "#282829",
|
|
951
|
+
secondary: "#58595B",
|
|
952
|
+
accent: "#EF4130",
|
|
953
|
+
text: "#282829",
|
|
954
|
+
background: "#FFFFFF",
|
|
955
|
+
border: "#E8E2DC",
|
|
956
|
+
textPrimary: "#282829",
|
|
957
|
+
textSecondary: "#58595B",
|
|
958
|
+
textMuted: "#939598",
|
|
959
|
+
borderPrimary: "#C7C8CA",
|
|
960
|
+
borderSecondary: "#EFEAE4",
|
|
961
|
+
backgroundPrimary: "#FFFFFF",
|
|
962
|
+
backgroundSecondary: "#FAF7F5"
|
|
963
|
+
},
|
|
964
|
+
fonts: {
|
|
965
|
+
heading: {
|
|
966
|
+
family: "Arial",
|
|
967
|
+
size: 24
|
|
968
|
+
},
|
|
969
|
+
body: {
|
|
970
|
+
family: "Calibri",
|
|
971
|
+
size: 10.5
|
|
972
|
+
},
|
|
973
|
+
mono: {
|
|
974
|
+
family: "Consolas",
|
|
975
|
+
size: 10
|
|
976
|
+
},
|
|
977
|
+
light: {
|
|
978
|
+
family: "Arial",
|
|
979
|
+
size: 24
|
|
980
|
+
}
|
|
981
|
+
},
|
|
982
|
+
page: {
|
|
983
|
+
size: "A4",
|
|
984
|
+
margins: {
|
|
985
|
+
top: 1360,
|
|
986
|
+
bottom: 1360,
|
|
987
|
+
left: 1180,
|
|
988
|
+
right: 1180,
|
|
989
|
+
header: 620,
|
|
990
|
+
footer: 620,
|
|
991
|
+
gutter: 0
|
|
992
|
+
}
|
|
993
|
+
},
|
|
994
|
+
styles: {
|
|
995
|
+
normal: {
|
|
996
|
+
font: "body",
|
|
997
|
+
color: "textPrimary",
|
|
998
|
+
lineSpacing: {
|
|
999
|
+
type: "multiple",
|
|
1000
|
+
value: 1.3
|
|
1001
|
+
},
|
|
1002
|
+
alignment: "left",
|
|
1003
|
+
spacing: {
|
|
1004
|
+
after: 8
|
|
1005
|
+
}
|
|
1006
|
+
},
|
|
1007
|
+
heading1: {
|
|
1008
|
+
font: "heading",
|
|
1009
|
+
size: 25,
|
|
1010
|
+
bold: false,
|
|
1011
|
+
color: "accent",
|
|
1012
|
+
characterSpacing: {
|
|
1013
|
+
type: "expanded",
|
|
1014
|
+
value: 50
|
|
1015
|
+
},
|
|
1016
|
+
spacing: {
|
|
1017
|
+
before: 22,
|
|
1018
|
+
after: 12
|
|
1019
|
+
},
|
|
1020
|
+
keepNext: true,
|
|
1021
|
+
alignment: "left"
|
|
1022
|
+
},
|
|
1023
|
+
heading2: {
|
|
1024
|
+
font: "heading",
|
|
1025
|
+
size: 12.5,
|
|
1026
|
+
bold: true,
|
|
1027
|
+
color: "accent",
|
|
1028
|
+
characterSpacing: {
|
|
1029
|
+
type: "expanded",
|
|
1030
|
+
value: 30
|
|
1031
|
+
},
|
|
1032
|
+
spacing: {
|
|
1033
|
+
before: 16,
|
|
1034
|
+
after: 7
|
|
1035
|
+
},
|
|
1036
|
+
keepNext: true,
|
|
1037
|
+
alignment: "left"
|
|
1038
|
+
},
|
|
1039
|
+
heading3: {
|
|
1040
|
+
font: "body",
|
|
1041
|
+
size: 11,
|
|
1042
|
+
bold: true,
|
|
1043
|
+
color: "textPrimary",
|
|
1044
|
+
spacing: {
|
|
1045
|
+
before: 12,
|
|
1046
|
+
after: 5
|
|
1047
|
+
},
|
|
1048
|
+
keepNext: true,
|
|
1049
|
+
alignment: "left"
|
|
1050
|
+
},
|
|
1051
|
+
title: {
|
|
1052
|
+
font: "heading",
|
|
1053
|
+
size: 38,
|
|
1054
|
+
bold: true,
|
|
1055
|
+
color: "primary",
|
|
1056
|
+
alignment: "left",
|
|
1057
|
+
spacing: {
|
|
1058
|
+
before: 0,
|
|
1059
|
+
after: 14
|
|
1060
|
+
}
|
|
1061
|
+
},
|
|
1062
|
+
subtitle: {
|
|
1063
|
+
font: "body",
|
|
1064
|
+
size: 14,
|
|
1065
|
+
color: "textSecondary",
|
|
1066
|
+
alignment: "left",
|
|
1067
|
+
spacing: {
|
|
1068
|
+
before: 0,
|
|
1069
|
+
after: 10
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
|
|
940
1077
|
// src/templates/themes/index.ts
|
|
941
1078
|
import fs from "fs";
|
|
942
1079
|
import path from "path";
|
|
@@ -953,7 +1090,8 @@ function loadThemesFromJson() {
|
|
|
953
1090
|
corporate: ensureThemeDefaults(corporate_docx_theme_default),
|
|
954
1091
|
modern: ensureThemeDefaults(modern_docx_theme_default),
|
|
955
1092
|
apex: ensureThemeDefaults(apex_docx_theme_default),
|
|
956
|
-
devportal: ensureThemeDefaults(devportal_docx_theme_default)
|
|
1093
|
+
devportal: ensureThemeDefaults(devportal_docx_theme_default),
|
|
1094
|
+
vermilion: ensureThemeDefaults(vermilion_docx_theme_default)
|
|
957
1095
|
};
|
|
958
1096
|
try {
|
|
959
1097
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -1001,6 +1139,7 @@ var init_themes = __esm({
|
|
|
1001
1139
|
init_modern_docx_theme();
|
|
1002
1140
|
init_apex_docx_theme();
|
|
1003
1141
|
init_devportal_docx_theme();
|
|
1142
|
+
init_vermilion_docx_theme();
|
|
1004
1143
|
_themesCache = null;
|
|
1005
1144
|
themes = loadThemesFromJson();
|
|
1006
1145
|
getTheme = (themeName) => {
|
|
@@ -1746,6 +1885,14 @@ async function rasterizeSvgFallback(svg, transformation) {
|
|
|
1746
1885
|
}
|
|
1747
1886
|
try {
|
|
1748
1887
|
const markup = svg.toString("utf-8");
|
|
1888
|
+
if (/<(?:[A-Za-z][\w.-]*:)?text[\s>/]/.test(markup)) {
|
|
1889
|
+
reportWarning(
|
|
1890
|
+
"image",
|
|
1891
|
+
"IMAGE_SVG_RASTER_SKIPPED",
|
|
1892
|
+
"Inline SVG contains <text>, which would rasterize with machine-dependent fonts, so its fallback only renders in Word 2016+. Convert the text to paths for a portable fallback."
|
|
1893
|
+
);
|
|
1894
|
+
return void 0;
|
|
1895
|
+
}
|
|
1749
1896
|
const probeImage = new Resvg(markup);
|
|
1750
1897
|
const wide = probeImage.width / probeImage.height > transformation.width / transformation.height;
|
|
1751
1898
|
const mode = wide ? "height" : "width";
|
|
@@ -5037,88 +5184,6 @@ async function prerasterizeVisuals(root, serviceConfig, options = {}) {
|
|
|
5037
5184
|
return map;
|
|
5038
5185
|
}
|
|
5039
5186
|
|
|
5040
|
-
// src/json/normalizer.ts
|
|
5041
|
-
function normalizeComponent(component) {
|
|
5042
|
-
if (component.name === "columns") {
|
|
5043
|
-
return normalizeColumnsComponent(component);
|
|
5044
|
-
}
|
|
5045
|
-
if ("children" in component && component.children) {
|
|
5046
|
-
return {
|
|
5047
|
-
...component,
|
|
5048
|
-
// Preserve ALL properties from validated component
|
|
5049
|
-
children: component.children.map(normalizeComponent)
|
|
5050
|
-
};
|
|
5051
|
-
}
|
|
5052
|
-
if (component.name === "section") {
|
|
5053
|
-
return normalizeSectionComponent(component);
|
|
5054
|
-
}
|
|
5055
|
-
return component;
|
|
5056
|
-
}
|
|
5057
|
-
function normalizeColumnsComponent(component) {
|
|
5058
|
-
const cfg = component.props || {};
|
|
5059
|
-
const applyTopLevelGap = (cols, topGap) => cols.map((c, i) => {
|
|
5060
|
-
const isLast = i === cols.length - 1;
|
|
5061
|
-
if (!isLast && c.gap === void 0 && topGap !== void 0) {
|
|
5062
|
-
return { ...c, gap: topGap };
|
|
5063
|
-
}
|
|
5064
|
-
return c;
|
|
5065
|
-
});
|
|
5066
|
-
const normalizeAutoWidth = (cols) => cols.map((c) => ({
|
|
5067
|
-
...c,
|
|
5068
|
-
width: c.width === "auto" ? void 0 : c.width
|
|
5069
|
-
}));
|
|
5070
|
-
let columnsArray;
|
|
5071
|
-
if (typeof cfg.columns === "number" && cfg.columns > 0) {
|
|
5072
|
-
const n = cfg.columns;
|
|
5073
|
-
const base = Array.from(
|
|
5074
|
-
{ length: n },
|
|
5075
|
-
() => ({})
|
|
5076
|
-
);
|
|
5077
|
-
const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
|
|
5078
|
-
columnsArray = applyTopLevelGap(base, topLevelGap);
|
|
5079
|
-
} else if (Array.isArray(cfg.columns)) {
|
|
5080
|
-
columnsArray = cfg.columns;
|
|
5081
|
-
const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
|
|
5082
|
-
columnsArray = applyTopLevelGap(columnsArray, topLevelGap);
|
|
5083
|
-
columnsArray = normalizeAutoWidth(columnsArray);
|
|
5084
|
-
} else {
|
|
5085
|
-
columnsArray = [{}];
|
|
5086
|
-
}
|
|
5087
|
-
const normalizedProps = {
|
|
5088
|
-
columns: columnsArray
|
|
5089
|
-
};
|
|
5090
|
-
return {
|
|
5091
|
-
...component,
|
|
5092
|
-
// Preserve any additional properties
|
|
5093
|
-
name: "columns",
|
|
5094
|
-
props: normalizedProps,
|
|
5095
|
-
children: component.children ? component.children.map(normalizeComponent) : void 0
|
|
5096
|
-
};
|
|
5097
|
-
}
|
|
5098
|
-
function normalizeSectionComponent(component) {
|
|
5099
|
-
const props = component.props || {};
|
|
5100
|
-
return {
|
|
5101
|
-
...component,
|
|
5102
|
-
// Preserve ALL properties
|
|
5103
|
-
props: {
|
|
5104
|
-
...props,
|
|
5105
|
-
// Preserve ALL props properties
|
|
5106
|
-
header: props.header ? props.header === "linkToPrevious" ? "linkToPrevious" : props.header.map((m) => normalizeComponent(m)) : void 0,
|
|
5107
|
-
footer: props.footer ? props.footer === "linkToPrevious" ? "linkToPrevious" : props.footer.map((m) => normalizeComponent(m)) : void 0
|
|
5108
|
-
},
|
|
5109
|
-
children: component.children ? component.children.map(normalizeComponent) : void 0
|
|
5110
|
-
};
|
|
5111
|
-
}
|
|
5112
|
-
function normalizeDocument(document) {
|
|
5113
|
-
if (document.name !== "docx") {
|
|
5114
|
-
throw new Error(
|
|
5115
|
-
'Top-level document must be a docx component with name="docx"'
|
|
5116
|
-
);
|
|
5117
|
-
}
|
|
5118
|
-
const normalized = normalizeComponent(document);
|
|
5119
|
-
return [normalized];
|
|
5120
|
-
}
|
|
5121
|
-
|
|
5122
5187
|
// src/core/desugarExternals.ts
|
|
5123
5188
|
import {
|
|
5124
5189
|
clampVisualDpi as clampVisualDpi2,
|
|
@@ -6892,6 +6957,17 @@ function mergeBorderSizePerSide(...borders3) {
|
|
|
6892
6957
|
left: pick("left")
|
|
6893
6958
|
};
|
|
6894
6959
|
}
|
|
6960
|
+
function statedSides(colors, sizes) {
|
|
6961
|
+
const namedColors = colors.filter((value) => typeof value === "object").map(normalizeBorderColor);
|
|
6962
|
+
const namedSizes = sizes.filter((value) => typeof value === "object").map(normalizeBorderSize);
|
|
6963
|
+
const stated = (side) => namedColors.some((c) => c !== void 0 && c[side] !== UNSET_COLOR) || namedSizes.some((s) => s !== void 0 && s[side] !== UNSET_SIZE);
|
|
6964
|
+
return {
|
|
6965
|
+
top: stated("top"),
|
|
6966
|
+
right: stated("right"),
|
|
6967
|
+
bottom: stated("bottom"),
|
|
6968
|
+
left: stated("left")
|
|
6969
|
+
};
|
|
6970
|
+
}
|
|
6895
6971
|
function mergePaddingPerSide(...paddings) {
|
|
6896
6972
|
const normalized = paddings.map(normalizePadding);
|
|
6897
6973
|
if (!normalized.some((p) => p !== void 0)) return void 0;
|
|
@@ -7031,6 +7107,10 @@ function mergeCellDefaults(tableDef, columnDef, cellDef, position, tableOuterBor
|
|
|
7031
7107
|
tableDef?.borderSize,
|
|
7032
7108
|
tableOuterBorder.borderSize
|
|
7033
7109
|
),
|
|
7110
|
+
borderExplicit: statedSides(
|
|
7111
|
+
[cellDef?.borderColor, columnDef?.borderColor],
|
|
7112
|
+
[cellDef?.borderSize, columnDef?.borderSize]
|
|
7113
|
+
),
|
|
7034
7114
|
padding: mergePaddingPerSide(
|
|
7035
7115
|
cellDef?.padding,
|
|
7036
7116
|
columnDef?.padding,
|
|
@@ -7088,6 +7168,10 @@ function mergeHeaderCellDefaults(tableDef, headerTableDef, columnDef, headerDef,
|
|
|
7088
7168
|
tableDef?.borderSize,
|
|
7089
7169
|
tableOuterBorder.borderSize
|
|
7090
7170
|
),
|
|
7171
|
+
borderExplicit: statedSides(
|
|
7172
|
+
[headerDef?.borderColor, columnDef?.borderColor],
|
|
7173
|
+
[headerDef?.borderSize, columnDef?.borderSize]
|
|
7174
|
+
),
|
|
7091
7175
|
padding: mergePaddingPerSide(
|
|
7092
7176
|
headerDef?.padding,
|
|
7093
7177
|
headerTableDef?.padding,
|
|
@@ -7118,28 +7202,20 @@ function resolveTableModel(source, theme, themeName, options = {}) {
|
|
|
7118
7202
|
return position.isLastCol ? hidden.right : hidden.insideVertical;
|
|
7119
7203
|
}
|
|
7120
7204
|
};
|
|
7121
|
-
const borders3 = (merged, position) =>
|
|
7122
|
-
|
|
7123
|
-
size: merged.borderSize
|
|
7124
|
-
color: merged.borderColor
|
|
7125
|
-
hidden: isHidden(
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
hidden: isHidden("bottom", position)
|
|
7136
|
-
},
|
|
7137
|
-
left: {
|
|
7138
|
-
size: merged.borderSize.left,
|
|
7139
|
-
color: merged.borderColor.left,
|
|
7140
|
-
hidden: isHidden("left", position)
|
|
7141
|
-
}
|
|
7142
|
-
});
|
|
7205
|
+
const borders3 = (merged, position) => {
|
|
7206
|
+
const side = (name) => ({
|
|
7207
|
+
size: merged.borderSize[name],
|
|
7208
|
+
color: merged.borderColor[name],
|
|
7209
|
+
hidden: isHidden(name, position) && !merged.borderExplicit[name],
|
|
7210
|
+
explicit: merged.borderExplicit[name]
|
|
7211
|
+
});
|
|
7212
|
+
return {
|
|
7213
|
+
top: side("top"),
|
|
7214
|
+
right: side("right"),
|
|
7215
|
+
bottom: side("bottom"),
|
|
7216
|
+
left: side("left")
|
|
7217
|
+
};
|
|
7218
|
+
};
|
|
7143
7219
|
const tallest = (heights) => heights.reduce(
|
|
7144
7220
|
(max, height) => height === void 0 ? max : max === void 0 ? height : Math.max(max, height),
|
|
7145
7221
|
void 0
|
|
@@ -7249,6 +7325,7 @@ function resolveTableModel(source, theme, themeName, options = {}) {
|
|
|
7249
7325
|
};
|
|
7250
7326
|
return row;
|
|
7251
7327
|
});
|
|
7328
|
+
adjudicateInteriorEdges([header, ...rows]);
|
|
7252
7329
|
return {
|
|
7253
7330
|
...resolveWidths(source, columns, theme, themeName),
|
|
7254
7331
|
header,
|
|
@@ -7256,6 +7333,54 @@ function resolveTableModel(source, theme, themeName, options = {}) {
|
|
|
7256
7333
|
repeatHeader: source.repeatHeaderOnPageBreak ?? true
|
|
7257
7334
|
};
|
|
7258
7335
|
}
|
|
7336
|
+
function drawsNothing(border3) {
|
|
7337
|
+
return border3.hidden || border3.size <= 0;
|
|
7338
|
+
}
|
|
7339
|
+
function channels(hex) {
|
|
7340
|
+
const digits = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
7341
|
+
return [
|
|
7342
|
+
parseInt(digits.slice(0, 2), 16) || 0,
|
|
7343
|
+
parseInt(digits.slice(2, 4), 16) || 0,
|
|
7344
|
+
parseInt(digits.slice(4, 6), 16) || 0
|
|
7345
|
+
];
|
|
7346
|
+
}
|
|
7347
|
+
function outweighs(a, b) {
|
|
7348
|
+
if (drawsNothing(a)) return false;
|
|
7349
|
+
if (drawsNothing(b)) return true;
|
|
7350
|
+
if (a.size !== b.size) return a.size > b.size;
|
|
7351
|
+
const [redA, greenA, blueA] = channels(a.color);
|
|
7352
|
+
const [redB, greenB, blueB] = channels(b.color);
|
|
7353
|
+
const brightness = [
|
|
7354
|
+
[redA + blueA + 2 * greenA, redB + blueB + 2 * greenB],
|
|
7355
|
+
[blueA + 2 * greenA, blueB + 2 * greenB],
|
|
7356
|
+
[greenA, greenB]
|
|
7357
|
+
];
|
|
7358
|
+
for (const [ours, theirs] of brightness) {
|
|
7359
|
+
if (ours !== theirs) return ours < theirs;
|
|
7360
|
+
}
|
|
7361
|
+
return false;
|
|
7362
|
+
}
|
|
7363
|
+
function adjudicateInteriorEdges(rows) {
|
|
7364
|
+
const settle = (earlier, later) => {
|
|
7365
|
+
if (drawsNothing(earlier) && drawsNothing(later)) return;
|
|
7366
|
+
if (earlier.hidden === later.hidden && earlier.size === later.size && earlier.color === later.color) {
|
|
7367
|
+
return;
|
|
7368
|
+
}
|
|
7369
|
+
const winner = earlier.explicit !== later.explicit ? earlier.explicit ? earlier : later : outweighs(later, earlier) ? later : earlier;
|
|
7370
|
+
const loser = winner === earlier ? later : earlier;
|
|
7371
|
+
loser.size = winner.size;
|
|
7372
|
+
loser.color = winner.color;
|
|
7373
|
+
loser.hidden = winner.hidden;
|
|
7374
|
+
};
|
|
7375
|
+
rows.forEach((row, rowIndex) => {
|
|
7376
|
+
row.cells.forEach((cell, colIndex) => {
|
|
7377
|
+
const right = row.cells[colIndex + 1];
|
|
7378
|
+
if (right) settle(cell.borders.right, right.borders.left);
|
|
7379
|
+
const below = rows[rowIndex + 1]?.cells[colIndex];
|
|
7380
|
+
if (below) settle(cell.borders.bottom, below.borders.top);
|
|
7381
|
+
});
|
|
7382
|
+
});
|
|
7383
|
+
}
|
|
7259
7384
|
function resolveWidths(source, columns, theme, themeName) {
|
|
7260
7385
|
const hasExplicitWidths = columns.some((col) => col.width !== void 0);
|
|
7261
7386
|
if (!hasExplicitWidths) {
|
|
@@ -10272,6 +10397,7 @@ function compileTableRow(row, spacing, baseStyle, scope) {
|
|
|
10272
10397
|
};
|
|
10273
10398
|
}
|
|
10274
10399
|
function compileTableCell(cell, spacing, baseStyle, keepNext, scope, rowRevision, markRevision) {
|
|
10400
|
+
const cellLine = compileLineSpacing(cell.font?.lineSpacing);
|
|
10275
10401
|
const paragraph2 = {
|
|
10276
10402
|
kind: "paragraph",
|
|
10277
10403
|
id: scope.id,
|
|
@@ -10285,7 +10411,10 @@ function compileTableCell(cell, spacing, baseStyle, keepNext, scope, rowRevision
|
|
|
10285
10411
|
spacing: {
|
|
10286
10412
|
beforeTwips: spacing.before,
|
|
10287
10413
|
afterTwips: spacing.after,
|
|
10288
|
-
...
|
|
10414
|
+
...cellLine ? {
|
|
10415
|
+
...cellLine.lineTwips !== void 0 ? { lineTwips: cellLine.lineTwips } : {},
|
|
10416
|
+
lineRule: cellLine.lineRule
|
|
10417
|
+
} : spacing.line !== void 0 ? { lineTwips: spacing.line, lineRule: lineRule(spacing.lineRule) } : {}
|
|
10289
10418
|
},
|
|
10290
10419
|
...keepNext ? { keepNext: true } : {}
|
|
10291
10420
|
},
|
|
@@ -10568,102 +10697,7 @@ function isDocxRendererId(value) {
|
|
|
10568
10697
|
|
|
10569
10698
|
// src/core/generateFromIr.ts
|
|
10570
10699
|
init_packageDocument();
|
|
10571
|
-
|
|
10572
|
-
// src/core/generationContext.ts
|
|
10573
|
-
import { applyExportMode } from "@json-to-office/shared";
|
|
10574
|
-
|
|
10575
|
-
// src/styles/theme-resolver.ts
|
|
10576
|
-
init_themes();
|
|
10577
|
-
function resolveBuiltInTheme(themeName, options = {}) {
|
|
10578
|
-
if (!hasTheme(themeName)) {
|
|
10579
|
-
const available = [
|
|
10580
|
-
...getThemeNames(),
|
|
10581
|
-
...Object.keys(options.customThemes ?? {})
|
|
10582
|
-
].sort();
|
|
10583
|
-
options.warnings?.push({
|
|
10584
|
-
component: "theme",
|
|
10585
|
-
message: `Theme "${themeName}" not found; falling back to "minimal". Available themes: ${available.join(", ")}.`,
|
|
10586
|
-
severity: "warning",
|
|
10587
|
-
context: { code: "theme_not_found", requested: themeName, available }
|
|
10588
|
-
});
|
|
10589
|
-
}
|
|
10590
|
-
return getThemeWithFallback(themeName);
|
|
10591
|
-
}
|
|
10592
|
-
|
|
10593
|
-
// src/themes/overrides.ts
|
|
10594
|
-
function applyThemeOverrides(theme, overrides) {
|
|
10595
|
-
if (!overrides) return theme;
|
|
10596
|
-
const fonts = { ...theme.fonts };
|
|
10597
|
-
for (const [role, def] of Object.entries(overrides.fonts ?? {})) {
|
|
10598
|
-
const key = role;
|
|
10599
|
-
fonts[key] = { ...theme.fonts[key], ...def };
|
|
10600
|
-
}
|
|
10601
|
-
const styles = { ...theme.styles ?? {} };
|
|
10602
|
-
for (const [name, def] of Object.entries(overrides.styles ?? {})) {
|
|
10603
|
-
styles[name] = { ...styles[name], ...def };
|
|
10604
|
-
}
|
|
10605
|
-
return {
|
|
10606
|
-
...theme,
|
|
10607
|
-
colors: { ...theme.colors, ...overrides.colors ?? {} },
|
|
10608
|
-
fonts,
|
|
10609
|
-
...Object.keys(styles).length > 0 && { styles }
|
|
10610
|
-
};
|
|
10611
|
-
}
|
|
10612
|
-
|
|
10613
|
-
// src/core/generationContext.ts
|
|
10614
|
-
function defaultNamedThemeLookup(themeName, customThemes, warnings) {
|
|
10615
|
-
if (customThemes) {
|
|
10616
|
-
if (customThemes[themeName]) return customThemes[themeName];
|
|
10617
|
-
const themeNameLower = themeName.toLowerCase();
|
|
10618
|
-
const matchingThemeKey = Object.keys(customThemes).find(
|
|
10619
|
-
(key) => key.toLowerCase() === themeNameLower
|
|
10620
|
-
);
|
|
10621
|
-
if (matchingThemeKey) return customThemes[matchingThemeKey];
|
|
10622
|
-
return resolveBuiltInTheme(themeName, { customThemes, warnings });
|
|
10623
|
-
}
|
|
10624
|
-
return resolveBuiltInTheme(themeName, { warnings });
|
|
10625
|
-
}
|
|
10626
|
-
function resolveThemeContext(documentIn, options = {}) {
|
|
10627
|
-
const { customThemes, fonts, warnings, resolveNamedTheme } = options;
|
|
10628
|
-
if (documentIn.props === null) {
|
|
10629
|
-
throw new Error(
|
|
10630
|
-
"Document `props` is null. Omit it, or provide an object \u2014 a null props cannot carry a theme."
|
|
10631
|
-
);
|
|
10632
|
-
}
|
|
10633
|
-
const document = documentIn.props === void 0 ? { ...documentIn, props: {} } : documentIn;
|
|
10634
|
-
const authoredThemeName = document.props.theme || void 0;
|
|
10635
|
-
const baseThemeName = authoredThemeName ?? "minimal";
|
|
10636
|
-
let theme = resolveNamedTheme ? resolveNamedTheme(
|
|
10637
|
-
baseThemeName,
|
|
10638
|
-
warnings,
|
|
10639
|
-
authoredThemeName !== void 0
|
|
10640
|
-
) : defaultNamedThemeLookup(baseThemeName, customThemes, warnings);
|
|
10641
|
-
const themeOverrides = document.props.themeOverrides;
|
|
10642
|
-
if (themeOverrides) {
|
|
10643
|
-
theme = applyThemeOverrides(theme, themeOverrides);
|
|
10644
|
-
}
|
|
10645
|
-
const mode = applyExportMode({ doc: document, theme, fonts });
|
|
10646
|
-
for (const w of mode.warnings) {
|
|
10647
|
-
if (warnings) {
|
|
10648
|
-
warnings.push({
|
|
10649
|
-
component: "fontRegistry",
|
|
10650
|
-
message: w.message,
|
|
10651
|
-
severity: "warning",
|
|
10652
|
-
context: { code: w.code }
|
|
10653
|
-
});
|
|
10654
|
-
} else {
|
|
10655
|
-
console.warn(`[json-to-docx] [${w.code}] ${w.message}`);
|
|
10656
|
-
}
|
|
10657
|
-
}
|
|
10658
|
-
return {
|
|
10659
|
-
document: mode.doc,
|
|
10660
|
-
theme: mode.theme,
|
|
10661
|
-
themeName: baseThemeName
|
|
10662
|
-
};
|
|
10663
|
-
}
|
|
10664
|
-
|
|
10665
|
-
// src/core/generateFromIr.ts
|
|
10666
|
-
init_generationContext();
|
|
10700
|
+
init_generationContext();
|
|
10667
10701
|
|
|
10668
10702
|
// src/core/layout.ts
|
|
10669
10703
|
init_styles();
|
|
@@ -11116,8 +11150,7 @@ function resolveComponentTree(components, theme) {
|
|
|
11116
11150
|
}
|
|
11117
11151
|
|
|
11118
11152
|
// src/core/structure.ts
|
|
11119
|
-
|
|
11120
|
-
const metadata = createDocumentMetadata(document.props, generationDate);
|
|
11153
|
+
function resolveDocumentTree(document, theme) {
|
|
11121
11154
|
const docDefaults = document.props.componentDefaults;
|
|
11122
11155
|
const mergedComponentDefaults = docDefaults ? mergeWithDefaults2(docDefaults, theme.componentDefaults || {}) : void 0;
|
|
11123
11156
|
const docNoProofWords = document.props.noProofWords;
|
|
@@ -11131,6 +11164,14 @@ async function processDocument(document, theme, themeName, generationDate) {
|
|
|
11131
11164
|
},
|
|
11132
11165
|
...mergedNoProofWords && { noProofWords: mergedNoProofWords }
|
|
11133
11166
|
} : theme;
|
|
11167
|
+
return {
|
|
11168
|
+
theme: effectiveTheme,
|
|
11169
|
+
children: resolveComponentTree(document.children || [], effectiveTheme)
|
|
11170
|
+
};
|
|
11171
|
+
}
|
|
11172
|
+
async function processResolvedDocument(document, resolved, themeName, generationDate) {
|
|
11173
|
+
const metadata = createDocumentMetadata(document.props, generationDate);
|
|
11174
|
+
const effectiveTheme = resolved.theme;
|
|
11134
11175
|
const context = createRenderContext(
|
|
11135
11176
|
{
|
|
11136
11177
|
metadata,
|
|
@@ -11141,11 +11182,7 @@ async function processDocument(document, theme, themeName, generationDate) {
|
|
|
11141
11182
|
effectiveTheme,
|
|
11142
11183
|
themeName
|
|
11143
11184
|
);
|
|
11144
|
-
const
|
|
11145
|
-
document.children || [],
|
|
11146
|
-
effectiveTheme
|
|
11147
|
-
);
|
|
11148
|
-
const sections = await extractSections(resolvedChildren, context);
|
|
11185
|
+
const sections = await extractSections(resolved.children, context);
|
|
11149
11186
|
return {
|
|
11150
11187
|
metadata,
|
|
11151
11188
|
sections,
|
|
@@ -11245,6 +11282,458 @@ function createRenderContext(document, theme, themeName) {
|
|
|
11245
11282
|
};
|
|
11246
11283
|
}
|
|
11247
11284
|
|
|
11285
|
+
// src/quality/facts.ts
|
|
11286
|
+
import { DEFAULT_DOCX_RENDERER_ID as DEFAULT_DOCX_RENDERER_ID2 } from "@json-to-office/shared-docx";
|
|
11287
|
+
|
|
11288
|
+
// src/core/generationContext.ts
|
|
11289
|
+
import { applyExportMode } from "@json-to-office/shared";
|
|
11290
|
+
|
|
11291
|
+
// src/styles/theme-resolver.ts
|
|
11292
|
+
init_themes();
|
|
11293
|
+
function resolveBuiltInTheme(themeName, options = {}) {
|
|
11294
|
+
if (!hasTheme(themeName)) {
|
|
11295
|
+
const available = [
|
|
11296
|
+
...getThemeNames(),
|
|
11297
|
+
...Object.keys(options.customThemes ?? {})
|
|
11298
|
+
].sort();
|
|
11299
|
+
options.warnings?.push({
|
|
11300
|
+
component: "theme",
|
|
11301
|
+
message: `Theme "${themeName}" not found; falling back to "minimal". Available themes: ${available.join(", ")}.`,
|
|
11302
|
+
severity: "warning",
|
|
11303
|
+
context: { code: "theme_not_found", requested: themeName, available }
|
|
11304
|
+
});
|
|
11305
|
+
}
|
|
11306
|
+
return getThemeWithFallback(themeName);
|
|
11307
|
+
}
|
|
11308
|
+
|
|
11309
|
+
// src/themes/overrides.ts
|
|
11310
|
+
function applyThemeOverrides(theme, overrides) {
|
|
11311
|
+
if (!overrides) return theme;
|
|
11312
|
+
const fonts = { ...theme.fonts };
|
|
11313
|
+
for (const [role, def] of Object.entries(overrides.fonts ?? {})) {
|
|
11314
|
+
const key = role;
|
|
11315
|
+
fonts[key] = { ...theme.fonts[key], ...def };
|
|
11316
|
+
}
|
|
11317
|
+
const styles = { ...theme.styles ?? {} };
|
|
11318
|
+
for (const [name, def] of Object.entries(overrides.styles ?? {})) {
|
|
11319
|
+
styles[name] = { ...styles[name], ...def };
|
|
11320
|
+
}
|
|
11321
|
+
return {
|
|
11322
|
+
...theme,
|
|
11323
|
+
colors: { ...theme.colors, ...overrides.colors ?? {} },
|
|
11324
|
+
fonts,
|
|
11325
|
+
...Object.keys(styles).length > 0 && { styles }
|
|
11326
|
+
};
|
|
11327
|
+
}
|
|
11328
|
+
|
|
11329
|
+
// src/core/generationContext.ts
|
|
11330
|
+
function defaultNamedThemeLookup(themeName, customThemes, warnings) {
|
|
11331
|
+
if (customThemes) {
|
|
11332
|
+
if (customThemes[themeName]) return customThemes[themeName];
|
|
11333
|
+
const themeNameLower = themeName.toLowerCase();
|
|
11334
|
+
const matchingThemeKey = Object.keys(customThemes).find(
|
|
11335
|
+
(key) => key.toLowerCase() === themeNameLower
|
|
11336
|
+
);
|
|
11337
|
+
if (matchingThemeKey) return customThemes[matchingThemeKey];
|
|
11338
|
+
return resolveBuiltInTheme(themeName, { customThemes, warnings });
|
|
11339
|
+
}
|
|
11340
|
+
return resolveBuiltInTheme(themeName, { warnings });
|
|
11341
|
+
}
|
|
11342
|
+
function resolveThemeContext(documentIn, options = {}) {
|
|
11343
|
+
const { customThemes, fonts, warnings, resolveNamedTheme } = options;
|
|
11344
|
+
if (documentIn.props === null) {
|
|
11345
|
+
throw new Error(
|
|
11346
|
+
"Document `props` is null. Omit it, or provide an object \u2014 a null props cannot carry a theme."
|
|
11347
|
+
);
|
|
11348
|
+
}
|
|
11349
|
+
const document = documentIn.props === void 0 ? { ...documentIn, props: {} } : documentIn;
|
|
11350
|
+
const authoredThemeName = document.props.theme || void 0;
|
|
11351
|
+
const baseThemeName = authoredThemeName ?? "minimal";
|
|
11352
|
+
let theme = resolveNamedTheme ? resolveNamedTheme(
|
|
11353
|
+
baseThemeName,
|
|
11354
|
+
warnings,
|
|
11355
|
+
authoredThemeName !== void 0
|
|
11356
|
+
) : defaultNamedThemeLookup(baseThemeName, customThemes, warnings);
|
|
11357
|
+
const themeOverrides = document.props.themeOverrides;
|
|
11358
|
+
if (themeOverrides) {
|
|
11359
|
+
theme = applyThemeOverrides(theme, themeOverrides);
|
|
11360
|
+
}
|
|
11361
|
+
const mode = applyExportMode({ doc: document, theme, fonts });
|
|
11362
|
+
for (const w of mode.warnings) {
|
|
11363
|
+
if (warnings) {
|
|
11364
|
+
warnings.push({
|
|
11365
|
+
component: "fontRegistry",
|
|
11366
|
+
message: w.message,
|
|
11367
|
+
severity: "warning",
|
|
11368
|
+
context: { code: w.code }
|
|
11369
|
+
});
|
|
11370
|
+
} else {
|
|
11371
|
+
console.warn(`[json-to-docx] [${w.code}] ${w.message}`);
|
|
11372
|
+
}
|
|
11373
|
+
}
|
|
11374
|
+
return {
|
|
11375
|
+
document: mode.doc,
|
|
11376
|
+
theme: mode.theme,
|
|
11377
|
+
themeName: baseThemeName
|
|
11378
|
+
};
|
|
11379
|
+
}
|
|
11380
|
+
|
|
11381
|
+
// src/json/normalizer.ts
|
|
11382
|
+
function normalizeComponent(component) {
|
|
11383
|
+
if (component.name === "columns") {
|
|
11384
|
+
return normalizeColumnsComponent(component);
|
|
11385
|
+
}
|
|
11386
|
+
if ("children" in component && component.children) {
|
|
11387
|
+
return {
|
|
11388
|
+
...component,
|
|
11389
|
+
// Preserve ALL properties from validated component
|
|
11390
|
+
children: component.children.map(normalizeComponent)
|
|
11391
|
+
};
|
|
11392
|
+
}
|
|
11393
|
+
if (component.name === "section") {
|
|
11394
|
+
return normalizeSectionComponent(component);
|
|
11395
|
+
}
|
|
11396
|
+
return component;
|
|
11397
|
+
}
|
|
11398
|
+
function normalizeColumnsComponent(component) {
|
|
11399
|
+
const cfg = component.props || {};
|
|
11400
|
+
const applyTopLevelGap = (cols, topGap) => cols.map((c, i) => {
|
|
11401
|
+
const isLast = i === cols.length - 1;
|
|
11402
|
+
if (!isLast && c.gap === void 0 && topGap !== void 0) {
|
|
11403
|
+
return { ...c, gap: topGap };
|
|
11404
|
+
}
|
|
11405
|
+
return c;
|
|
11406
|
+
});
|
|
11407
|
+
const normalizeAutoWidth = (cols) => cols.map((c) => ({
|
|
11408
|
+
...c,
|
|
11409
|
+
width: c.width === "auto" ? void 0 : c.width
|
|
11410
|
+
}));
|
|
11411
|
+
let columnsArray;
|
|
11412
|
+
if (typeof cfg.columns === "number" && cfg.columns > 0) {
|
|
11413
|
+
const n = cfg.columns;
|
|
11414
|
+
const base = Array.from(
|
|
11415
|
+
{ length: n },
|
|
11416
|
+
() => ({})
|
|
11417
|
+
);
|
|
11418
|
+
const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
|
|
11419
|
+
columnsArray = applyTopLevelGap(base, topLevelGap);
|
|
11420
|
+
} else if (Array.isArray(cfg.columns)) {
|
|
11421
|
+
columnsArray = cfg.columns;
|
|
11422
|
+
const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
|
|
11423
|
+
columnsArray = applyTopLevelGap(columnsArray, topLevelGap);
|
|
11424
|
+
columnsArray = normalizeAutoWidth(columnsArray);
|
|
11425
|
+
} else {
|
|
11426
|
+
columnsArray = [{}];
|
|
11427
|
+
}
|
|
11428
|
+
const normalizedProps = {
|
|
11429
|
+
columns: columnsArray
|
|
11430
|
+
};
|
|
11431
|
+
return {
|
|
11432
|
+
...component,
|
|
11433
|
+
// Preserve any additional properties
|
|
11434
|
+
name: "columns",
|
|
11435
|
+
props: normalizedProps,
|
|
11436
|
+
children: component.children ? component.children.map(normalizeComponent) : void 0
|
|
11437
|
+
};
|
|
11438
|
+
}
|
|
11439
|
+
function normalizeSectionComponent(component) {
|
|
11440
|
+
const props = component.props || {};
|
|
11441
|
+
return {
|
|
11442
|
+
...component,
|
|
11443
|
+
// Preserve ALL properties
|
|
11444
|
+
props: {
|
|
11445
|
+
...props,
|
|
11446
|
+
// Preserve ALL props properties
|
|
11447
|
+
header: props.header ? props.header === "linkToPrevious" ? "linkToPrevious" : props.header.map((m) => normalizeComponent(m)) : void 0,
|
|
11448
|
+
footer: props.footer ? props.footer === "linkToPrevious" ? "linkToPrevious" : props.footer.map((m) => normalizeComponent(m)) : void 0
|
|
11449
|
+
},
|
|
11450
|
+
children: component.children ? component.children.map(normalizeComponent) : void 0
|
|
11451
|
+
};
|
|
11452
|
+
}
|
|
11453
|
+
function normalizeDocument(document) {
|
|
11454
|
+
if (document.name !== "docx") {
|
|
11455
|
+
throw new Error(
|
|
11456
|
+
'Top-level document must be a docx component with name="docx"'
|
|
11457
|
+
);
|
|
11458
|
+
}
|
|
11459
|
+
const normalized = normalizeComponent(document);
|
|
11460
|
+
return [normalized];
|
|
11461
|
+
}
|
|
11462
|
+
|
|
11463
|
+
// src/quality/facts.ts
|
|
11464
|
+
init_widthUtils();
|
|
11465
|
+
function asRecord(value) {
|
|
11466
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
11467
|
+
}
|
|
11468
|
+
function pageBox(theme, themeName, pageOverride) {
|
|
11469
|
+
const page = createSectionProperties(
|
|
11470
|
+
getColumnSettings("single"),
|
|
11471
|
+
theme,
|
|
11472
|
+
themeName,
|
|
11473
|
+
"nextPage",
|
|
11474
|
+
pageOverride
|
|
11475
|
+
).page;
|
|
11476
|
+
return {
|
|
11477
|
+
availableWidthTwips: Math.max(
|
|
11478
|
+
0,
|
|
11479
|
+
page.size.width - page.margin.left - page.margin.right
|
|
11480
|
+
),
|
|
11481
|
+
pageBottomTwips: page.size.height
|
|
11482
|
+
};
|
|
11483
|
+
}
|
|
11484
|
+
function tableFact(props, path4, availableWidthTwips) {
|
|
11485
|
+
const columns = Array.isArray(props.columns) ? props.columns : void 0;
|
|
11486
|
+
if (!columns) return void 0;
|
|
11487
|
+
let totalWidthTwips = 0;
|
|
11488
|
+
let hasExplicitWidth = false;
|
|
11489
|
+
let pointSum = 0;
|
|
11490
|
+
let percentSum = 0;
|
|
11491
|
+
const explicitWidths = [];
|
|
11492
|
+
columns.forEach((column, index) => {
|
|
11493
|
+
const width = asRecord(column)?.width;
|
|
11494
|
+
if (typeof width === "number" && Number.isFinite(width)) {
|
|
11495
|
+
hasExplicitWidth = true;
|
|
11496
|
+
explicitWidths.push({ index, width });
|
|
11497
|
+
totalWidthTwips += relativeLengthToTwips(width, availableWidthTwips);
|
|
11498
|
+
pointSum += width;
|
|
11499
|
+
} else if (typeof width === "string") {
|
|
11500
|
+
hasExplicitWidth = true;
|
|
11501
|
+
const percent = width.trim().endsWith("%") ? Number(width.trim().slice(0, -1)) : Number.NaN;
|
|
11502
|
+
if (Number.isFinite(percent)) {
|
|
11503
|
+
explicitWidths.push({ index, width });
|
|
11504
|
+
percentSum += percent;
|
|
11505
|
+
totalWidthTwips += Math.max(
|
|
11506
|
+
0,
|
|
11507
|
+
Math.round(availableWidthTwips * percent / 100)
|
|
11508
|
+
);
|
|
11509
|
+
}
|
|
11510
|
+
}
|
|
11511
|
+
});
|
|
11512
|
+
return {
|
|
11513
|
+
id: `docx:table-width:${path4}`,
|
|
11514
|
+
kind: "docx/table-width",
|
|
11515
|
+
path: `${path4}/props/columns`,
|
|
11516
|
+
totalWidthTwips,
|
|
11517
|
+
availableWidthTwips,
|
|
11518
|
+
hasExplicitWidth,
|
|
11519
|
+
allColumnsExplicit: explicitWidths.length === columns.length,
|
|
11520
|
+
pointSum,
|
|
11521
|
+
percentSum,
|
|
11522
|
+
explicitWidths
|
|
11523
|
+
};
|
|
11524
|
+
}
|
|
11525
|
+
var TWIPS_PER_POINT3 = 20;
|
|
11526
|
+
var SINGLE_LINE_RATIO = 1.2;
|
|
11527
|
+
function finiteNumber(value) {
|
|
11528
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
11529
|
+
}
|
|
11530
|
+
function lineHeightPt(fontSizePt, spacing) {
|
|
11531
|
+
const rule = asRecord(spacing);
|
|
11532
|
+
const value = finiteNumber(rule?.value);
|
|
11533
|
+
switch (rule?.type) {
|
|
11534
|
+
// `exactly` is an absolute line box, and display type routinely sets it
|
|
11535
|
+
// below the font size — reading it as a multiplier would inflate every
|
|
11536
|
+
// estimate on exactly the headings that matter most.
|
|
11537
|
+
case "exactly":
|
|
11538
|
+
return value ?? fontSizePt * SINGLE_LINE_RATIO;
|
|
11539
|
+
case "atLeast":
|
|
11540
|
+
return Math.max(value ?? 0, fontSizePt * SINGLE_LINE_RATIO);
|
|
11541
|
+
case "multiple":
|
|
11542
|
+
return fontSizePt * SINGLE_LINE_RATIO * (value ?? 1);
|
|
11543
|
+
default:
|
|
11544
|
+
return fontSizePt * SINGLE_LINE_RATIO;
|
|
11545
|
+
}
|
|
11546
|
+
}
|
|
11547
|
+
function characterSpacingPt(spacing) {
|
|
11548
|
+
const rule = asRecord(spacing);
|
|
11549
|
+
const value = finiteNumber(rule?.value);
|
|
11550
|
+
if (rule === void 0 || value === void 0) return 0;
|
|
11551
|
+
return rule.type === "condensed" ? -value / TWIPS_PER_POINT3 : value / TWIPS_PER_POINT3;
|
|
11552
|
+
}
|
|
11553
|
+
function frameSignature(floating) {
|
|
11554
|
+
const horizontal = asRecord(floating.horizontalPosition);
|
|
11555
|
+
const vertical = asRecord(floating.verticalPosition);
|
|
11556
|
+
return JSON.stringify([
|
|
11557
|
+
floating.width ?? null,
|
|
11558
|
+
floating.height ?? null,
|
|
11559
|
+
horizontal?.offset ?? null,
|
|
11560
|
+
horizontal?.relative ?? null,
|
|
11561
|
+
horizontal?.align ?? null,
|
|
11562
|
+
vertical?.offset ?? null,
|
|
11563
|
+
vertical?.relative ?? null,
|
|
11564
|
+
vertical?.align ?? null,
|
|
11565
|
+
asRecord(floating.wrap)?.type ?? null
|
|
11566
|
+
]);
|
|
11567
|
+
}
|
|
11568
|
+
function frameTextFact(props, path4, page, frameChainId, flowIndex) {
|
|
11569
|
+
const floating = asRecord(props.floating);
|
|
11570
|
+
const frameWidthTwips = finiteNumber(floating?.width);
|
|
11571
|
+
if (frameWidthTwips === void 0) return void 0;
|
|
11572
|
+
const text = typeof props.text === "string" ? props.text : "";
|
|
11573
|
+
if (text.trim() === "") return void 0;
|
|
11574
|
+
const font = asRecord(props.font);
|
|
11575
|
+
const fontSizePt = finiteNumber(font?.size);
|
|
11576
|
+
if (fontSizePt === void 0) return void 0;
|
|
11577
|
+
const longestWord = text.split(/\s+/).reduce(
|
|
11578
|
+
(longest, word) => word.length > longest.length ? word : longest,
|
|
11579
|
+
""
|
|
11580
|
+
);
|
|
11581
|
+
const horizontal = asRecord(floating?.horizontalPosition);
|
|
11582
|
+
const vertical = asRecord(floating?.verticalPosition);
|
|
11583
|
+
const offsetX = finiteNumber(horizontal?.offset);
|
|
11584
|
+
const offsetY = finiteNumber(vertical?.offset);
|
|
11585
|
+
const absolutelyPinned = horizontal?.offset !== void 0 || vertical?.offset !== void 0;
|
|
11586
|
+
const resolvable = (horizontal?.offset === void 0 || offsetX !== void 0) && (vertical?.offset === void 0 || offsetY !== void 0);
|
|
11587
|
+
return {
|
|
11588
|
+
id: `docx:frame-text:${path4}`,
|
|
11589
|
+
kind: "docx/frame-text",
|
|
11590
|
+
path: path4,
|
|
11591
|
+
text,
|
|
11592
|
+
fontSizePt,
|
|
11593
|
+
lineHeightPt: lineHeightPt(fontSizePt, font?.lineSpacing),
|
|
11594
|
+
characterSpacingPt: characterSpacingPt(font?.characterSpacing),
|
|
11595
|
+
frameWidthTwips,
|
|
11596
|
+
...finiteNumber(floating?.height) !== void 0 && {
|
|
11597
|
+
frameHeightTwips: finiteNumber(floating?.height)
|
|
11598
|
+
},
|
|
11599
|
+
...offsetY !== void 0 && { offsetYTwips: offsetY },
|
|
11600
|
+
pageBottomTwips: page.pageBottomTwips,
|
|
11601
|
+
longestWord,
|
|
11602
|
+
...absolutelyPinned && resolvable && {
|
|
11603
|
+
absoluteOffsetTwips: { x: offsetX ?? 0, y: offsetY ?? 0 }
|
|
11604
|
+
},
|
|
11605
|
+
anchorHorizontal: typeof horizontal?.relative === "string" ? horizontal.relative : "page",
|
|
11606
|
+
anchorVertical: typeof vertical?.relative === "string" ? vertical.relative : "page",
|
|
11607
|
+
frameChainId,
|
|
11608
|
+
flowIndex
|
|
11609
|
+
};
|
|
11610
|
+
}
|
|
11611
|
+
var SVG_VIEWBOX = /viewBox\s*=\s*"\s*(-?[\d.]+)[\s,]+(-?[\d.]+)[\s,]+(-?[\d.]+)[\s,]+(-?[\d.]+)\s*"/;
|
|
11612
|
+
var SVG_TEXT = /<text\b([^>]*)>([^<]*)<\/text>/g;
|
|
11613
|
+
var SVG_ATTR = (name) => new RegExp(`\\b${name}\\s*=\\s*"(-?[\\d.]+)"`);
|
|
11614
|
+
function svgTextFacts(props, path4) {
|
|
11615
|
+
const svg = typeof props.svg === "string" ? props.svg : void 0;
|
|
11616
|
+
if (!svg) return [];
|
|
11617
|
+
const box = SVG_VIEWBOX.exec(svg);
|
|
11618
|
+
if (!box) return [];
|
|
11619
|
+
const viewBoxMinY = Number(box[2]);
|
|
11620
|
+
const viewBoxHeight = Number(box[4]);
|
|
11621
|
+
if (!Number.isFinite(viewBoxMinY) || !Number.isFinite(viewBoxHeight)) {
|
|
11622
|
+
return [];
|
|
11623
|
+
}
|
|
11624
|
+
const facts = [];
|
|
11625
|
+
let match;
|
|
11626
|
+
SVG_TEXT.lastIndex = 0;
|
|
11627
|
+
let index = 0;
|
|
11628
|
+
while ((match = SVG_TEXT.exec(svg)) !== null) {
|
|
11629
|
+
const [, attributes, content] = match;
|
|
11630
|
+
const baselineY = Number(SVG_ATTR("y").exec(attributes)?.[1]);
|
|
11631
|
+
if (!Number.isFinite(baselineY)) {
|
|
11632
|
+
index += 1;
|
|
11633
|
+
continue;
|
|
11634
|
+
}
|
|
11635
|
+
const fontSizeUnits = Number(
|
|
11636
|
+
SVG_ATTR("font-size").exec(attributes)?.[1] ?? 0
|
|
11637
|
+
);
|
|
11638
|
+
facts.push({
|
|
11639
|
+
id: `docx:svg-text:${path4}:${index}`,
|
|
11640
|
+
kind: "docx/svg-text",
|
|
11641
|
+
path: `${path4}/props/svg`,
|
|
11642
|
+
content: content.trim(),
|
|
11643
|
+
baselineY,
|
|
11644
|
+
fontSizeUnits: Number.isFinite(fontSizeUnits) ? fontSizeUnits : 0,
|
|
11645
|
+
viewBoxMinY,
|
|
11646
|
+
viewBoxHeight
|
|
11647
|
+
});
|
|
11648
|
+
index += 1;
|
|
11649
|
+
}
|
|
11650
|
+
return facts;
|
|
11651
|
+
}
|
|
11652
|
+
function walkActive(node, path4, page, visit) {
|
|
11653
|
+
const rec = asRecord(node);
|
|
11654
|
+
if (!rec || rec.enabled === false) return;
|
|
11655
|
+
visit(rec, path4, page);
|
|
11656
|
+
const children = Array.isArray(rec.children) ? rec.children : [];
|
|
11657
|
+
children.forEach(
|
|
11658
|
+
(child, index) => walkActive(child, `${path4}/children/${index}`, page, visit)
|
|
11659
|
+
);
|
|
11660
|
+
}
|
|
11661
|
+
function prepareDocxQualityDocument(document, options = {}) {
|
|
11662
|
+
const context = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
|
|
11663
|
+
customThemes: options.customThemes,
|
|
11664
|
+
fonts: options.fonts,
|
|
11665
|
+
warnings: options.warnings
|
|
11666
|
+
});
|
|
11667
|
+
const resolved = resolveDocumentTree(context.document, context.theme);
|
|
11668
|
+
const basePage = pageBox(resolved.theme, context.themeName);
|
|
11669
|
+
const facts = [];
|
|
11670
|
+
const provenance = {};
|
|
11671
|
+
let previousHeadingLevel;
|
|
11672
|
+
const addFact = (fact) => {
|
|
11673
|
+
facts.push(fact);
|
|
11674
|
+
provenance[fact.id] = {
|
|
11675
|
+
path: fact.path,
|
|
11676
|
+
...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
|
|
11677
|
+
};
|
|
11678
|
+
};
|
|
11679
|
+
let previousVisitPath;
|
|
11680
|
+
let lastFrame;
|
|
11681
|
+
let flowIndex = 0;
|
|
11682
|
+
const parentOf = (path4) => path4.slice(0, path4.lastIndexOf("/"));
|
|
11683
|
+
const visit = (node, path4, page) => {
|
|
11684
|
+
const props = asRecord(node.props) ?? {};
|
|
11685
|
+
if (node.name === "table") {
|
|
11686
|
+
const fact = tableFact(props, path4, page.availableWidthTwips);
|
|
11687
|
+
if (fact) addFact(fact);
|
|
11688
|
+
}
|
|
11689
|
+
if (node.name === "paragraph" || node.name === "text-box") {
|
|
11690
|
+
const floating = asRecord(props.floating);
|
|
11691
|
+
if (floating) {
|
|
11692
|
+
const signature = frameSignature(floating);
|
|
11693
|
+
const chainId = lastFrame !== void 0 && previousVisitPath === lastFrame.path && parentOf(lastFrame.path) === parentOf(path4) && lastFrame.signature === signature ? lastFrame.chainId : `docx:frame-chain:${path4}`;
|
|
11694
|
+
lastFrame = { path: path4, signature, chainId };
|
|
11695
|
+
const fact = frameTextFact(props, path4, page, chainId, flowIndex);
|
|
11696
|
+
if (fact) addFact(fact);
|
|
11697
|
+
}
|
|
11698
|
+
}
|
|
11699
|
+
if (node.name === "image" || node.name === "visual") {
|
|
11700
|
+
for (const fact of svgTextFacts(props, path4)) addFact(fact);
|
|
11701
|
+
}
|
|
11702
|
+
if (node.name === "heading") {
|
|
11703
|
+
const level = typeof props.level === "number" && Number.isFinite(props.level) ? props.level : 1;
|
|
11704
|
+
addFact({
|
|
11705
|
+
id: `docx:heading:${path4}`,
|
|
11706
|
+
kind: "docx/heading",
|
|
11707
|
+
path: `${path4}/props/level`,
|
|
11708
|
+
level,
|
|
11709
|
+
...previousHeadingLevel !== void 0 && {
|
|
11710
|
+
previousLevel: previousHeadingLevel
|
|
11711
|
+
}
|
|
11712
|
+
});
|
|
11713
|
+
previousHeadingLevel = level;
|
|
11714
|
+
}
|
|
11715
|
+
previousVisitPath = path4;
|
|
11716
|
+
};
|
|
11717
|
+
resolved.children.forEach((component, index) => {
|
|
11718
|
+
const rec = component;
|
|
11719
|
+
if (rec.name === "section") flowIndex += 1;
|
|
11720
|
+
const page = rec.name === "section" ? pageBox(resolved.theme, context.themeName, rec.props?.page) : basePage;
|
|
11721
|
+
walkActive(component, `/children/${index}`, page, visit);
|
|
11722
|
+
});
|
|
11723
|
+
return {
|
|
11724
|
+
format: "docx",
|
|
11725
|
+
model: {
|
|
11726
|
+
authored: document,
|
|
11727
|
+
context,
|
|
11728
|
+
document: resolved,
|
|
11729
|
+
themeName: context.themeName
|
|
11730
|
+
},
|
|
11731
|
+
facts,
|
|
11732
|
+
provenance,
|
|
11733
|
+
renderer: options.renderer ?? DEFAULT_DOCX_RENDERER_ID2
|
|
11734
|
+
};
|
|
11735
|
+
}
|
|
11736
|
+
|
|
11248
11737
|
// src/core/generateFromIr.ts
|
|
11249
11738
|
var UncompiledComponentError = class extends Error {
|
|
11250
11739
|
code = "UNCOMPILED_COMPONENT";
|
|
@@ -11272,11 +11761,14 @@ async function compileDocumentToIr(document, options = {}, collector) {
|
|
|
11272
11761
|
);
|
|
11273
11762
|
}
|
|
11274
11763
|
async function compileDocumentScoped(document, options, warnings) {
|
|
11275
|
-
const
|
|
11764
|
+
const prepared = options.prepared ?? prepareDocxQualityDocument(document, {
|
|
11276
11765
|
customThemes: options.customThemes,
|
|
11277
11766
|
fonts: options.fonts,
|
|
11278
|
-
warnings
|
|
11767
|
+
warnings,
|
|
11768
|
+
context: options.context,
|
|
11769
|
+
renderer: options.renderer
|
|
11279
11770
|
});
|
|
11771
|
+
const { context } = prepared.model;
|
|
11280
11772
|
const hasVisual = collectVisualProps(context.document).length > 0;
|
|
11281
11773
|
const resolvedFonts = await resolveDocumentFonts(
|
|
11282
11774
|
context.document,
|
|
@@ -11292,9 +11784,10 @@ async function compileDocumentScoped(document, options, warnings) {
|
|
|
11292
11784
|
...options.baseDir !== void 0 ? { baseDir: options.baseDir } : {},
|
|
11293
11785
|
...visualFonts.length > 0 ? { visualFonts } : {}
|
|
11294
11786
|
});
|
|
11295
|
-
const
|
|
11787
|
+
const resolved = desugared === context.document ? prepared.model.document : resolveDocumentTree(desugared, context.theme);
|
|
11788
|
+
const structure = await processResolvedDocument(
|
|
11296
11789
|
desugared,
|
|
11297
|
-
|
|
11790
|
+
resolved,
|
|
11298
11791
|
context.themeName,
|
|
11299
11792
|
resolveGenerationDate(options)
|
|
11300
11793
|
);
|
|
@@ -11459,7 +11952,8 @@ async function generateBufferWithWarnings(jsonConfig, options) {
|
|
|
11459
11952
|
...options?.baseDir !== void 0 ? { baseDir: options.baseDir } : {},
|
|
11460
11953
|
...options?.deterministic !== void 0 ? { deterministic: options.deterministic } : {},
|
|
11461
11954
|
...options?.generatedAt !== void 0 ? { generatedAt: options.generatedAt } : {},
|
|
11462
|
-
...renderer ? { renderer } : {}
|
|
11955
|
+
...renderer ? { renderer } : {},
|
|
11956
|
+
...options?.prepared ? { prepared: options.prepared } : {}
|
|
11463
11957
|
});
|
|
11464
11958
|
}
|
|
11465
11959
|
function parseDocument(jsonConfig) {
|
|
@@ -11600,6 +12094,509 @@ async function rasterizeVisual(obj, ctx) {
|
|
|
11600
12094
|
});
|
|
11601
12095
|
}
|
|
11602
12096
|
|
|
12097
|
+
// src/quality/preflight.ts
|
|
12098
|
+
import {
|
|
12099
|
+
assertValidQualityPolicy,
|
|
12100
|
+
assertValidQualityProfile
|
|
12101
|
+
} from "@json-to-office/quality";
|
|
12102
|
+
|
|
12103
|
+
// src/quality/rules.ts
|
|
12104
|
+
import {
|
|
12105
|
+
mergeQualityProfiles,
|
|
12106
|
+
QUALITY_CODES,
|
|
12107
|
+
QualityEngine
|
|
12108
|
+
} from "@json-to-office/quality";
|
|
12109
|
+
|
|
12110
|
+
// src/quality/text-metrics.ts
|
|
12111
|
+
var DEFAULT_ADVANCE = 556;
|
|
12112
|
+
var ADVANCE = {
|
|
12113
|
+
" ": 278,
|
|
12114
|
+
A: 667,
|
|
12115
|
+
B: 667,
|
|
12116
|
+
C: 722,
|
|
12117
|
+
D: 722,
|
|
12118
|
+
E: 667,
|
|
12119
|
+
F: 611,
|
|
12120
|
+
G: 778,
|
|
12121
|
+
H: 722,
|
|
12122
|
+
I: 278,
|
|
12123
|
+
J: 500,
|
|
12124
|
+
K: 667,
|
|
12125
|
+
L: 556,
|
|
12126
|
+
M: 833,
|
|
12127
|
+
N: 722,
|
|
12128
|
+
O: 778,
|
|
12129
|
+
P: 667,
|
|
12130
|
+
Q: 778,
|
|
12131
|
+
R: 722,
|
|
12132
|
+
S: 667,
|
|
12133
|
+
T: 611,
|
|
12134
|
+
U: 722,
|
|
12135
|
+
V: 667,
|
|
12136
|
+
W: 944,
|
|
12137
|
+
X: 667,
|
|
12138
|
+
Y: 667,
|
|
12139
|
+
Z: 611,
|
|
12140
|
+
a: 556,
|
|
12141
|
+
b: 556,
|
|
12142
|
+
c: 500,
|
|
12143
|
+
d: 556,
|
|
12144
|
+
e: 556,
|
|
12145
|
+
f: 278,
|
|
12146
|
+
g: 556,
|
|
12147
|
+
h: 556,
|
|
12148
|
+
i: 222,
|
|
12149
|
+
j: 222,
|
|
12150
|
+
k: 500,
|
|
12151
|
+
l: 222,
|
|
12152
|
+
m: 833,
|
|
12153
|
+
n: 556,
|
|
12154
|
+
o: 556,
|
|
12155
|
+
p: 556,
|
|
12156
|
+
q: 556,
|
|
12157
|
+
r: 333,
|
|
12158
|
+
s: 500,
|
|
12159
|
+
t: 278,
|
|
12160
|
+
u: 556,
|
|
12161
|
+
v: 500,
|
|
12162
|
+
w: 722,
|
|
12163
|
+
x: 500,
|
|
12164
|
+
y: 500,
|
|
12165
|
+
z: 500,
|
|
12166
|
+
".": 278,
|
|
12167
|
+
",": 278,
|
|
12168
|
+
":": 278,
|
|
12169
|
+
";": 278,
|
|
12170
|
+
"!": 278,
|
|
12171
|
+
"?": 556,
|
|
12172
|
+
"'": 191,
|
|
12173
|
+
'"': 355,
|
|
12174
|
+
"-": 333,
|
|
12175
|
+
"\u2013": 556,
|
|
12176
|
+
"\u2014": 1e3,
|
|
12177
|
+
"&": 667,
|
|
12178
|
+
"%": 889,
|
|
12179
|
+
"(": 333,
|
|
12180
|
+
")": 333,
|
|
12181
|
+
"/": 278,
|
|
12182
|
+
"@": 1015,
|
|
12183
|
+
"#": 556,
|
|
12184
|
+
"+": 584,
|
|
12185
|
+
"=": 584
|
|
12186
|
+
};
|
|
12187
|
+
function estimateTextWidthPt(text, fontSizePt, trackingPt = 0) {
|
|
12188
|
+
let units = 0;
|
|
12189
|
+
for (const character of text) {
|
|
12190
|
+
units += ADVANCE[character] ?? DEFAULT_ADVANCE;
|
|
12191
|
+
}
|
|
12192
|
+
const width = units / 1e3 * fontSizePt + text.length * trackingPt;
|
|
12193
|
+
return Math.max(0, width);
|
|
12194
|
+
}
|
|
12195
|
+
function estimateWrappedLines(text, widthPt, fontSizePt, trackingPt = 0) {
|
|
12196
|
+
if (widthPt <= 0) return 1;
|
|
12197
|
+
let lines = 0;
|
|
12198
|
+
for (const paragraph2 of text.split("\n")) {
|
|
12199
|
+
const words = paragraph2.split(/\s+/).filter(Boolean);
|
|
12200
|
+
if (words.length === 0) {
|
|
12201
|
+
lines += 1;
|
|
12202
|
+
continue;
|
|
12203
|
+
}
|
|
12204
|
+
let current = "";
|
|
12205
|
+
let used = 0;
|
|
12206
|
+
for (const word of words) {
|
|
12207
|
+
const candidate = current === "" ? word : `${current} ${word}`;
|
|
12208
|
+
if (estimateTextWidthPt(candidate, fontSizePt, trackingPt) <= widthPt) {
|
|
12209
|
+
current = candidate;
|
|
12210
|
+
} else {
|
|
12211
|
+
if (current !== "") used += 1;
|
|
12212
|
+
current = word;
|
|
12213
|
+
}
|
|
12214
|
+
}
|
|
12215
|
+
lines += used + (current === "" ? 0 : 1);
|
|
12216
|
+
}
|
|
12217
|
+
return Math.max(1, lines);
|
|
12218
|
+
}
|
|
12219
|
+
|
|
12220
|
+
// src/quality/rules.ts
|
|
12221
|
+
var WIDTH_TOLERANCE_TWIPS = 10;
|
|
12222
|
+
function numberParameter(parameters, name, fallback) {
|
|
12223
|
+
const value = parameters[name];
|
|
12224
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
12225
|
+
}
|
|
12226
|
+
var docxTableWidthRule = {
|
|
12227
|
+
id: "docx/table-width",
|
|
12228
|
+
code: QUALITY_CODES.TABLE_WIDTH_OVERFLOW,
|
|
12229
|
+
category: "integrity",
|
|
12230
|
+
defaultSeverity: "warning",
|
|
12231
|
+
defaultCertainty: "deterministic",
|
|
12232
|
+
formats: ["docx"],
|
|
12233
|
+
defaultParameters: { toleranceTwips: WIDTH_TOLERANCE_TWIPS },
|
|
12234
|
+
evaluate: ({ facts, configuration }) => {
|
|
12235
|
+
const toleranceTwips = numberParameter(
|
|
12236
|
+
configuration.parameters,
|
|
12237
|
+
"toleranceTwips",
|
|
12238
|
+
WIDTH_TOLERANCE_TWIPS
|
|
12239
|
+
);
|
|
12240
|
+
return facts.filter(
|
|
12241
|
+
(fact) => fact.kind === "docx/table-width"
|
|
12242
|
+
).filter(
|
|
12243
|
+
(fact) => fact.hasExplicitWidth && fact.totalWidthTwips > fact.availableWidthTwips + toleranceTwips
|
|
12244
|
+
).map((fact) => {
|
|
12245
|
+
const totalPt = Math.round(fact.totalWidthTwips / 20 * 10) / 10;
|
|
12246
|
+
const availablePt = Math.round(fact.availableWidthTwips / 20 * 10) / 10;
|
|
12247
|
+
const scale = fact.availableWidthTwips / fact.totalWidthTwips;
|
|
12248
|
+
const fixes = fact.allColumnsExplicit ? fact.explicitWidths.map(({ index, width }) => ({
|
|
12249
|
+
op: "replace",
|
|
12250
|
+
path: `${fact.path}/${index}/width`,
|
|
12251
|
+
value: typeof width === "number" ? Math.floor(width * scale * 10) / 10 : `${Math.floor(Number(width.trim().slice(0, -1)) * scale * 10) / 10}%`
|
|
12252
|
+
})) : [];
|
|
12253
|
+
return {
|
|
12254
|
+
message: `Column widths use ${totalPt}pt, but this section has ${availablePt}pt available \u2014 the table will spill off the right edge.`,
|
|
12255
|
+
path: fact.path,
|
|
12256
|
+
suggestion: "Reduce fixed/percentage widths, widen the page, or leave columns unsized so they share the remainder.",
|
|
12257
|
+
context: {
|
|
12258
|
+
totalWidthPt: totalPt,
|
|
12259
|
+
availableWidthPt: availablePt,
|
|
12260
|
+
pointSum: fact.pointSum,
|
|
12261
|
+
percentSum: fact.percentSum
|
|
12262
|
+
},
|
|
12263
|
+
evidence: {
|
|
12264
|
+
actual: totalPt,
|
|
12265
|
+
expected: availablePt,
|
|
12266
|
+
unit: "pt"
|
|
12267
|
+
},
|
|
12268
|
+
...fixes.length > 0 && { fixes }
|
|
12269
|
+
};
|
|
12270
|
+
});
|
|
12271
|
+
}
|
|
12272
|
+
};
|
|
12273
|
+
var docxHeadingHierarchyRule = {
|
|
12274
|
+
id: "docx/heading-hierarchy",
|
|
12275
|
+
code: QUALITY_CODES.HEADING_SKIP,
|
|
12276
|
+
category: "hierarchy",
|
|
12277
|
+
defaultSeverity: "info",
|
|
12278
|
+
defaultCertainty: "deterministic",
|
|
12279
|
+
formats: ["docx"],
|
|
12280
|
+
evaluate: ({ facts }) => facts.filter((fact) => fact.kind === "docx/heading").flatMap((fact) => {
|
|
12281
|
+
const previousLevel = fact.previousLevel;
|
|
12282
|
+
if (previousLevel === void 0 || fact.level <= previousLevel + 1) {
|
|
12283
|
+
return [];
|
|
12284
|
+
}
|
|
12285
|
+
return [
|
|
12286
|
+
{
|
|
12287
|
+
message: `Heading level ${fact.level} follows level ${previousLevel} \u2014 the skipped level breaks the document outline.`,
|
|
12288
|
+
path: fact.path,
|
|
12289
|
+
suggestion: `Use level ${previousLevel + 1}, or promote this heading's section.`,
|
|
12290
|
+
context: { level: fact.level, previousLevel },
|
|
12291
|
+
evidence: {
|
|
12292
|
+
actual: fact.level,
|
|
12293
|
+
expected: previousLevel + 1
|
|
12294
|
+
},
|
|
12295
|
+
// The fact path already addresses `.../props/level`; RFC 6902
|
|
12296
|
+
// `add` replaces an existing member, so this works whether the
|
|
12297
|
+
// level was explicit or defaulted.
|
|
12298
|
+
fixes: [
|
|
12299
|
+
{
|
|
12300
|
+
op: "add",
|
|
12301
|
+
path: fact.path,
|
|
12302
|
+
value: previousLevel + 1
|
|
12303
|
+
}
|
|
12304
|
+
]
|
|
12305
|
+
}
|
|
12306
|
+
];
|
|
12307
|
+
})
|
|
12308
|
+
};
|
|
12309
|
+
var WIDTH_MODEL_TOLERANCE = 1.08;
|
|
12310
|
+
var TWIPS_PER_POINT4 = 20;
|
|
12311
|
+
function frameTextFacts(facts) {
|
|
12312
|
+
return facts.filter(
|
|
12313
|
+
(fact) => fact.kind === "docx/frame-text"
|
|
12314
|
+
);
|
|
12315
|
+
}
|
|
12316
|
+
var docxTextFitRule = {
|
|
12317
|
+
id: "docx/text-fit",
|
|
12318
|
+
code: QUALITY_CODES.TEXT_OVERFLOW,
|
|
12319
|
+
category: "integrity",
|
|
12320
|
+
defaultSeverity: "warning",
|
|
12321
|
+
defaultCertainty: "estimated",
|
|
12322
|
+
formats: ["docx"],
|
|
12323
|
+
defaultParameters: { widthTolerance: WIDTH_MODEL_TOLERANCE },
|
|
12324
|
+
evaluate: ({ facts, configuration }) => {
|
|
12325
|
+
const tolerance = numberParameter(
|
|
12326
|
+
configuration.parameters,
|
|
12327
|
+
"widthTolerance",
|
|
12328
|
+
WIDTH_MODEL_TOLERANCE
|
|
12329
|
+
);
|
|
12330
|
+
const findings = [];
|
|
12331
|
+
for (const fact of frameTextFacts(facts)) {
|
|
12332
|
+
const frameWidthPt = fact.frameWidthTwips / TWIPS_PER_POINT4;
|
|
12333
|
+
const longestWordPt = estimateTextWidthPt(
|
|
12334
|
+
fact.longestWord,
|
|
12335
|
+
fact.fontSizePt,
|
|
12336
|
+
fact.characterSpacingPt
|
|
12337
|
+
);
|
|
12338
|
+
if (longestWordPt > frameWidthPt * tolerance) {
|
|
12339
|
+
findings.push({
|
|
12340
|
+
message: `"${fact.longestWord}" is estimated at ${Math.round(longestWordPt)}pt in a ${Math.round(frameWidthPt)}pt frame \u2014 with no wrap point it will break mid-word.`,
|
|
12341
|
+
path: `${fact.path}/props/floating/width`,
|
|
12342
|
+
suggestion: "Widen the frame, reduce fontSize, or condense the run further.",
|
|
12343
|
+
context: {
|
|
12344
|
+
longestWord: fact.longestWord,
|
|
12345
|
+
estimatedWidthPt: Math.round(longestWordPt),
|
|
12346
|
+
frameWidthPt: Math.round(frameWidthPt),
|
|
12347
|
+
fontSizePt: fact.fontSizePt
|
|
12348
|
+
},
|
|
12349
|
+
evidence: {
|
|
12350
|
+
actual: Math.round(longestWordPt),
|
|
12351
|
+
expected: Math.round(frameWidthPt),
|
|
12352
|
+
unit: "pt"
|
|
12353
|
+
}
|
|
12354
|
+
});
|
|
12355
|
+
continue;
|
|
12356
|
+
}
|
|
12357
|
+
if (fact.offsetYTwips === void 0) continue;
|
|
12358
|
+
const lines = estimateWrappedLines(
|
|
12359
|
+
fact.text,
|
|
12360
|
+
frameWidthPt,
|
|
12361
|
+
fact.fontSizePt,
|
|
12362
|
+
fact.characterSpacingPt
|
|
12363
|
+
);
|
|
12364
|
+
const heightTwips = lines * fact.lineHeightPt * TWIPS_PER_POINT4;
|
|
12365
|
+
const bottomTwips = fact.offsetYTwips + heightTwips;
|
|
12366
|
+
const overrunTwips = bottomTwips - fact.pageBottomTwips;
|
|
12367
|
+
if (overrunTwips <= fact.lineHeightPt * TWIPS_PER_POINT4) continue;
|
|
12368
|
+
findings.push({
|
|
12369
|
+
message: `Frame starts at ${fact.offsetYTwips} twips and needs ${Math.round(heightTwips)} more for ${lines} line${lines === 1 ? "" : "s"} of ${fact.fontSizePt}pt \u2014 it runs ${Math.round(overrunTwips)} twips past the page, so the text breaks onto the next one.`,
|
|
12370
|
+
path: `${fact.path}/props/floating/verticalPosition/offset`,
|
|
12371
|
+
suggestion: "Raise the frame, shorten the text, or reduce fontSize so the block finishes on the page.",
|
|
12372
|
+
context: {
|
|
12373
|
+
offsetYTwips: fact.offsetYTwips,
|
|
12374
|
+
estimatedHeightTwips: Math.round(heightTwips),
|
|
12375
|
+
pageBottomTwips: fact.pageBottomTwips,
|
|
12376
|
+
lines
|
|
12377
|
+
},
|
|
12378
|
+
evidence: {
|
|
12379
|
+
actual: Math.round(bottomTwips),
|
|
12380
|
+
expected: fact.pageBottomTwips,
|
|
12381
|
+
unit: "twips"
|
|
12382
|
+
}
|
|
12383
|
+
});
|
|
12384
|
+
}
|
|
12385
|
+
return findings;
|
|
12386
|
+
}
|
|
12387
|
+
};
|
|
12388
|
+
var FRAME_COLLISION_MIN_WIDTH_TWIPS = 240;
|
|
12389
|
+
var docxFrameCollisionRule = {
|
|
12390
|
+
id: "docx/frame-collision",
|
|
12391
|
+
code: QUALITY_CODES.FRAME_COLLISION,
|
|
12392
|
+
category: "integrity",
|
|
12393
|
+
defaultSeverity: "warning",
|
|
12394
|
+
defaultCertainty: "estimated",
|
|
12395
|
+
formats: ["docx"],
|
|
12396
|
+
defaultParameters: {
|
|
12397
|
+
minOverlapWidthTwips: FRAME_COLLISION_MIN_WIDTH_TWIPS,
|
|
12398
|
+
minOverlapLines: 1
|
|
12399
|
+
},
|
|
12400
|
+
evaluate: ({ facts, configuration }) => {
|
|
12401
|
+
const minOverlapWidthTwips = numberParameter(
|
|
12402
|
+
configuration.parameters,
|
|
12403
|
+
"minOverlapWidthTwips",
|
|
12404
|
+
FRAME_COLLISION_MIN_WIDTH_TWIPS
|
|
12405
|
+
);
|
|
12406
|
+
const minOverlapLines = numberParameter(
|
|
12407
|
+
configuration.parameters,
|
|
12408
|
+
"minOverlapLines",
|
|
12409
|
+
1
|
|
12410
|
+
);
|
|
12411
|
+
const chains = /* @__PURE__ */ new Map();
|
|
12412
|
+
for (const fact of frameTextFacts(facts)) {
|
|
12413
|
+
if (!fact.absoluteOffsetTwips || fact.anchorHorizontal === "text" || fact.anchorVertical === "text") {
|
|
12414
|
+
continue;
|
|
12415
|
+
}
|
|
12416
|
+
const lines = estimateWrappedLines(
|
|
12417
|
+
fact.text,
|
|
12418
|
+
fact.frameWidthTwips / TWIPS_PER_POINT4,
|
|
12419
|
+
fact.fontSizePt,
|
|
12420
|
+
fact.characterSpacingPt
|
|
12421
|
+
);
|
|
12422
|
+
const heightTwips = lines * fact.lineHeightPt * TWIPS_PER_POINT4;
|
|
12423
|
+
const lineTwips = fact.lineHeightPt * TWIPS_PER_POINT4;
|
|
12424
|
+
const chain = chains.get(fact.frameChainId);
|
|
12425
|
+
if (chain) {
|
|
12426
|
+
chain.heightTwips += heightTwips;
|
|
12427
|
+
chain.lineTwips = Math.max(chain.lineTwips, lineTwips);
|
|
12428
|
+
continue;
|
|
12429
|
+
}
|
|
12430
|
+
chains.set(fact.frameChainId, {
|
|
12431
|
+
path: fact.path,
|
|
12432
|
+
offsetYAuthored: fact.offsetYTwips !== void 0,
|
|
12433
|
+
x: fact.absoluteOffsetTwips.x,
|
|
12434
|
+
y: fact.absoluteOffsetTwips.y,
|
|
12435
|
+
widthTwips: fact.frameWidthTwips,
|
|
12436
|
+
heightTwips,
|
|
12437
|
+
lineTwips,
|
|
12438
|
+
// Offsets only compare within one page flow and one anchor pair.
|
|
12439
|
+
groupKey: `${fact.flowIndex}:${fact.anchorHorizontal}:${fact.anchorVertical}`
|
|
12440
|
+
});
|
|
12441
|
+
}
|
|
12442
|
+
const groups = /* @__PURE__ */ new Map();
|
|
12443
|
+
for (const chain of chains.values()) {
|
|
12444
|
+
const group = groups.get(chain.groupKey);
|
|
12445
|
+
if (group) group.push(chain);
|
|
12446
|
+
else groups.set(chain.groupKey, [chain]);
|
|
12447
|
+
}
|
|
12448
|
+
const findings = [];
|
|
12449
|
+
for (const group of groups.values()) {
|
|
12450
|
+
for (let i = 0; i < group.length; i += 1) {
|
|
12451
|
+
for (let j = i + 1; j < group.length; j += 1) {
|
|
12452
|
+
const a = group[i];
|
|
12453
|
+
const b = group[j];
|
|
12454
|
+
const overlapWidth = Math.min(a.x + a.widthTwips, b.x + b.widthTwips) - Math.max(a.x, b.x);
|
|
12455
|
+
if (overlapWidth <= minOverlapWidthTwips) continue;
|
|
12456
|
+
const overlapHeight = Math.min(a.y + a.heightTwips, b.y + b.heightTwips) - Math.max(a.y, b.y);
|
|
12457
|
+
const floorTwips = Math.max(a.lineTwips, b.lineTwips) * minOverlapLines;
|
|
12458
|
+
if (overlapHeight <= floorTwips) continue;
|
|
12459
|
+
const overlapHeightPt = Math.round(overlapHeight / TWIPS_PER_POINT4);
|
|
12460
|
+
const overlapWidthPt = Math.round(overlapWidth / TWIPS_PER_POINT4);
|
|
12461
|
+
findings.push({
|
|
12462
|
+
message: `This floating frame and an earlier one on the same page overlap by an estimated ${overlapHeightPt}pt of text across ${overlapWidthPt}pt of width \u2014 the two blocks paint on top of each other.`,
|
|
12463
|
+
path: b.offsetYAuthored ? `${b.path}/props/floating/verticalPosition/offset` : `${b.path}/props/floating`,
|
|
12464
|
+
suggestion: "Move one frame, shorten its text, or reduce fontSize until the blocks clear each other.",
|
|
12465
|
+
context: {
|
|
12466
|
+
partnerPath: a.path,
|
|
12467
|
+
overlapWidthTwips: Math.round(overlapWidth),
|
|
12468
|
+
overlapHeightTwips: Math.round(overlapHeight),
|
|
12469
|
+
frameTopTwips: b.y,
|
|
12470
|
+
partnerTopTwips: a.y
|
|
12471
|
+
},
|
|
12472
|
+
relatedPaths: [a.path],
|
|
12473
|
+
evidence: {
|
|
12474
|
+
actual: Math.round(overlapHeight),
|
|
12475
|
+
expected: Math.round(floorTwips),
|
|
12476
|
+
unit: "twips"
|
|
12477
|
+
}
|
|
12478
|
+
});
|
|
12479
|
+
}
|
|
12480
|
+
}
|
|
12481
|
+
}
|
|
12482
|
+
return findings;
|
|
12483
|
+
}
|
|
12484
|
+
};
|
|
12485
|
+
var docxSvgTextBoundsRule = {
|
|
12486
|
+
id: "docx/svg-text-bounds",
|
|
12487
|
+
code: QUALITY_CODES.SVG_TEXT_CLIPPED,
|
|
12488
|
+
category: "integrity",
|
|
12489
|
+
defaultSeverity: "warning",
|
|
12490
|
+
defaultCertainty: "deterministic",
|
|
12491
|
+
formats: ["docx"],
|
|
12492
|
+
evaluate: ({ facts }) => facts.filter((fact) => fact.kind === "docx/svg-text").flatMap((fact) => {
|
|
12493
|
+
const canvasBottom = fact.viewBoxMinY + fact.viewBoxHeight;
|
|
12494
|
+
if (fact.baselineY <= canvasBottom) return [];
|
|
12495
|
+
const overflow = Math.round((fact.baselineY - canvasBottom) * 10) / 10;
|
|
12496
|
+
return [
|
|
12497
|
+
{
|
|
12498
|
+
message: `SVG text "${fact.content}" sits ${overflow} units below the viewBox \u2014 it is clipped away and never reaches the document's text layer.`,
|
|
12499
|
+
path: fact.path,
|
|
12500
|
+
suggestion: `Move the baseline above ${Math.round(canvasBottom)}, or grow the viewBox height.`,
|
|
12501
|
+
context: {
|
|
12502
|
+
content: fact.content,
|
|
12503
|
+
baselineY: fact.baselineY,
|
|
12504
|
+
canvasBottom,
|
|
12505
|
+
overflowUnits: overflow
|
|
12506
|
+
},
|
|
12507
|
+
evidence: {
|
|
12508
|
+
actual: fact.baselineY,
|
|
12509
|
+
expected: canvasBottom,
|
|
12510
|
+
unit: "viewBox units"
|
|
12511
|
+
}
|
|
12512
|
+
}
|
|
12513
|
+
];
|
|
12514
|
+
})
|
|
12515
|
+
};
|
|
12516
|
+
var DOCX_QUALITY_RULES = {
|
|
12517
|
+
id: "docx/default",
|
|
12518
|
+
rules: [
|
|
12519
|
+
docxTableWidthRule,
|
|
12520
|
+
docxHeadingHierarchyRule,
|
|
12521
|
+
docxTextFitRule,
|
|
12522
|
+
docxFrameCollisionRule,
|
|
12523
|
+
docxSvgTextBoundsRule
|
|
12524
|
+
]
|
|
12525
|
+
};
|
|
12526
|
+
var DOCX_QUALITY_PROFILES = {
|
|
12527
|
+
"executive-report": {
|
|
12528
|
+
id: "executive-report",
|
|
12529
|
+
formats: ["docx"],
|
|
12530
|
+
description: "Short decision document with strict outline continuity.",
|
|
12531
|
+
rules: {
|
|
12532
|
+
"docx/heading-hierarchy": { severity: "warning" }
|
|
12533
|
+
}
|
|
12534
|
+
},
|
|
12535
|
+
"technical-report": {
|
|
12536
|
+
id: "technical-report",
|
|
12537
|
+
formats: ["docx"],
|
|
12538
|
+
description: "Portable professional report defaults."
|
|
12539
|
+
},
|
|
12540
|
+
"legal-appendix": {
|
|
12541
|
+
id: "legal-appendix",
|
|
12542
|
+
formats: ["docx"],
|
|
12543
|
+
description: "Dense appendix: preserve integrity without editorial taste."
|
|
12544
|
+
}
|
|
12545
|
+
};
|
|
12546
|
+
var DOCX_DEFAULT_QUALITY_PROFILE = DOCX_QUALITY_PROFILES["technical-report"];
|
|
12547
|
+
var DOCX_PROFILES_BY_ID = DOCX_QUALITY_PROFILES;
|
|
12548
|
+
function resolveDocxQualityProfile(requested) {
|
|
12549
|
+
if (!requested) return void 0;
|
|
12550
|
+
const registered = DOCX_PROFILES_BY_ID[requested.id];
|
|
12551
|
+
if (!registered) return requested;
|
|
12552
|
+
return mergeQualityProfiles(registered, requested);
|
|
12553
|
+
}
|
|
12554
|
+
var docxQualityEngine = new QualityEngine(DOCX_QUALITY_RULES.rules);
|
|
12555
|
+
|
|
12556
|
+
// src/quality/preflight.ts
|
|
12557
|
+
var PREPARE_RULE_ID = "quality/prepare";
|
|
12558
|
+
function emptyAnalysis(ruleErrors = [], blocked = false) {
|
|
12559
|
+
return {
|
|
12560
|
+
diagnostics: [],
|
|
12561
|
+
counts: { error: 0, warning: 0, info: 0 },
|
|
12562
|
+
blocked,
|
|
12563
|
+
truncated: false,
|
|
12564
|
+
suppressedCount: 0,
|
|
12565
|
+
evaluatedRuleIds: [],
|
|
12566
|
+
ruleErrors
|
|
12567
|
+
};
|
|
12568
|
+
}
|
|
12569
|
+
function analyzeDocxQuality(doc, options = {}) {
|
|
12570
|
+
assertValidQualityPolicy(options.policy);
|
|
12571
|
+
assertValidQualityProfile(options.profile);
|
|
12572
|
+
if (typeof doc !== "object" || doc === null || Array.isArray(doc) || doc.name !== "docx") {
|
|
12573
|
+
return emptyAnalysis();
|
|
12574
|
+
}
|
|
12575
|
+
let prepared = options.prepared;
|
|
12576
|
+
try {
|
|
12577
|
+
prepared ??= prepareDocxQualityDocument(
|
|
12578
|
+
doc,
|
|
12579
|
+
options
|
|
12580
|
+
);
|
|
12581
|
+
} catch (error) {
|
|
12582
|
+
if (options.policy?.onRuleError === "throw") throw error;
|
|
12583
|
+
const gate = options.policy?.gate;
|
|
12584
|
+
return emptyAnalysis(
|
|
12585
|
+
[
|
|
12586
|
+
{
|
|
12587
|
+
ruleId: PREPARE_RULE_ID,
|
|
12588
|
+
message: error instanceof Error ? error.message : String(error)
|
|
12589
|
+
}
|
|
12590
|
+
],
|
|
12591
|
+
gate !== void 0 && gate !== "none"
|
|
12592
|
+
);
|
|
12593
|
+
}
|
|
12594
|
+
return docxQualityEngine.analyzeSync(prepared, {
|
|
12595
|
+
profile: resolveDocxQualityProfile(options.profile) ?? DOCX_DEFAULT_QUALITY_PROFILE,
|
|
12596
|
+
policy: options.policy
|
|
12597
|
+
});
|
|
12598
|
+
}
|
|
12599
|
+
|
|
11603
12600
|
// src/utils/warningsDocument.ts
|
|
11604
12601
|
function generateWarningsDocument(warnings) {
|
|
11605
12602
|
if (!warnings || warnings.length === 0) {
|
|
@@ -12526,17 +13523,22 @@ export {
|
|
|
12526
13523
|
ComponentValidationError2 as ComponentValidationError,
|
|
12527
13524
|
DocumentGenerator as CoreDocumentGenerator,
|
|
12528
13525
|
DEFAULT_DOCX_RENDERER_ID,
|
|
13526
|
+
DOCX_DEFAULT_QUALITY_PROFILE,
|
|
13527
|
+
DOCX_QUALITY_PROFILES,
|
|
13528
|
+
DOCX_QUALITY_RULES,
|
|
12529
13529
|
ThemeFileError,
|
|
12530
13530
|
ThemeParseError,
|
|
12531
13531
|
ThemeValidationError,
|
|
12532
13532
|
UncompiledComponentError,
|
|
12533
13533
|
UnknownPreservedComponentError,
|
|
13534
|
+
analyzeDocxQuality,
|
|
12534
13535
|
cleanComponentProps,
|
|
12535
13536
|
corporateTheme,
|
|
12536
13537
|
createComponent,
|
|
12537
13538
|
createDocumentGenerator,
|
|
12538
13539
|
createMinimalTheme,
|
|
12539
13540
|
createVersion,
|
|
13541
|
+
docxQualityEngine,
|
|
12540
13542
|
docxRendererIds,
|
|
12541
13543
|
docxRendererStatuses,
|
|
12542
13544
|
examples,
|
|
@@ -12579,7 +13581,9 @@ export {
|
|
|
12579
13581
|
mergeSchemas,
|
|
12580
13582
|
minimalTheme,
|
|
12581
13583
|
modernTheme,
|
|
13584
|
+
prepareDocxQualityDocument,
|
|
12582
13585
|
resetVisualPrepassStats,
|
|
13586
|
+
resolveDocxQualityProfile,
|
|
12583
13587
|
runExample,
|
|
12584
13588
|
themes,
|
|
12585
13589
|
validateComponentProps,
|