@json-to-office/core-docx 1.3.0 → 1.5.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/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1084 -348
- package/dist/index.js.map +1 -1
- package/dist/ir/compiler.d.ts.map +1 -1
- package/dist/plugin/example/index.js +572 -251
- package/dist/plugin/example/index.js.map +1 -1
- package/dist/quality/facts.d.ts +82 -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 +38 -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/renderers/docxjs/index.d.ts.map +1 -1
- package/dist/renderers/office-open/index.d.ts.map +1 -1
- package/dist/renderers/registry.d.ts +18 -4
- package/dist/renderers/registry.d.ts.map +1 -1
- package/dist/styles/themeToStyles.d.ts +35 -1
- package/dist/styles/themeToStyles.d.ts.map +1 -1
- package/dist/templates/themes/apex.docx.theme.json +1 -2
- package/dist/templates/themes/corporate.docx.theme.json +1 -2
- package/dist/templates/themes/devportal.docx.theme.json +1 -2
- package/dist/templates/themes/minimal.docx.theme.json +1 -2
- package/dist/templates/themes/modern.docx.theme.json +1 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -14
package/dist/index.js
CHANGED
|
@@ -428,8 +428,7 @@ var init_minimal_docx_theme = __esm({
|
|
|
428
428
|
},
|
|
429
429
|
list: {
|
|
430
430
|
format: "bullet",
|
|
431
|
-
bullet: "\u2022"
|
|
432
|
-
indent: 3
|
|
431
|
+
bullet: "\u2022"
|
|
433
432
|
},
|
|
434
433
|
image: {
|
|
435
434
|
alignment: "left"
|
|
@@ -553,8 +552,7 @@ var init_corporate_docx_theme = __esm({
|
|
|
553
552
|
},
|
|
554
553
|
list: {
|
|
555
554
|
format: "bullet",
|
|
556
|
-
bullet: "\u2022"
|
|
557
|
-
indent: 3
|
|
555
|
+
bullet: "\u2022"
|
|
558
556
|
},
|
|
559
557
|
statistic: {
|
|
560
558
|
alignment: "center"
|
|
@@ -667,8 +665,7 @@ var init_modern_docx_theme = __esm({
|
|
|
667
665
|
},
|
|
668
666
|
list: {
|
|
669
667
|
format: "bullet",
|
|
670
|
-
bullet: "\u2013"
|
|
671
|
-
indent: 3
|
|
668
|
+
bullet: "\u2013"
|
|
672
669
|
},
|
|
673
670
|
image: {
|
|
674
671
|
alignment: "center"
|
|
@@ -792,8 +789,7 @@ var init_apex_docx_theme = __esm({
|
|
|
792
789
|
},
|
|
793
790
|
list: {
|
|
794
791
|
format: "bullet",
|
|
795
|
-
bullet: "\u2022"
|
|
796
|
-
indent: 3
|
|
792
|
+
bullet: "\u2022"
|
|
797
793
|
},
|
|
798
794
|
image: {
|
|
799
795
|
alignment: "center"
|
|
@@ -925,8 +921,7 @@ var init_devportal_docx_theme = __esm({
|
|
|
925
921
|
},
|
|
926
922
|
list: {
|
|
927
923
|
format: "bullet",
|
|
928
|
-
bullet: "\u2013"
|
|
929
|
-
indent: 3
|
|
924
|
+
bullet: "\u2013"
|
|
930
925
|
},
|
|
931
926
|
image: {
|
|
932
927
|
alignment: "center"
|
|
@@ -2779,99 +2774,6 @@ var init_styles2 = __esm({
|
|
|
2779
2774
|
}
|
|
2780
2775
|
});
|
|
2781
2776
|
|
|
2782
|
-
// src/ir/features.ts
|
|
2783
|
-
var DOCX_FEATURES, ALL_DOCX_FEATURES;
|
|
2784
|
-
var init_features = __esm({
|
|
2785
|
-
"src/ir/features.ts"() {
|
|
2786
|
-
"use strict";
|
|
2787
|
-
DOCX_FEATURES = [
|
|
2788
|
-
/** Paragraphs and text runs — the floor of the format. */
|
|
2789
|
-
"paragraphs",
|
|
2790
|
-
/** Named paragraph and character styles in `styles.xml`. */
|
|
2791
|
-
"styles",
|
|
2792
|
-
/** Numbering definitions and numbered paragraphs. */
|
|
2793
|
-
"numbering",
|
|
2794
|
-
/** Multiple sections with their own page setup. */
|
|
2795
|
-
"sections",
|
|
2796
|
-
/** Multi-column section layout. */
|
|
2797
|
-
"columns",
|
|
2798
|
-
/** Header and footer parts, including first/even variants. */
|
|
2799
|
-
"headers-footers",
|
|
2800
|
-
"tables",
|
|
2801
|
-
/** Merged table cells — column span or vertical merge. */
|
|
2802
|
-
"table-merged-cells",
|
|
2803
|
-
/** A table positioned out of the text flow. */
|
|
2804
|
-
"floating-tables",
|
|
2805
|
-
/** Inline images. */
|
|
2806
|
-
"images",
|
|
2807
|
-
/** Anchored images with text wrapping. */
|
|
2808
|
-
"floating-images",
|
|
2809
|
-
/** SVG images with a raster fallback. */
|
|
2810
|
-
"svg-images",
|
|
2811
|
-
/** A paragraph positioned as a floating frame. */
|
|
2812
|
-
"text-frames",
|
|
2813
|
-
/** Native shape text boxes. */
|
|
2814
|
-
"text-boxes",
|
|
2815
|
-
/**
|
|
2816
|
-
* A DrawingML group: shapes, text boxes and pictures sharing one child
|
|
2817
|
-
* coordinate space, drawn as a single anchored or inline object.
|
|
2818
|
-
*
|
|
2819
|
-
* Separate from `text-boxes` because a lone `wps:wsp` run is a far smaller
|
|
2820
|
-
* ask than `wpg:wgp` with child transforms, preset geometry and grouped
|
|
2821
|
-
* pictures — a backend can plausibly have the first and not the second.
|
|
2822
|
-
*/
|
|
2823
|
-
"drawing-groups",
|
|
2824
|
-
/**
|
|
2825
|
-
* A native chart part: a `c:chartSpace` with its own embedded workbook.
|
|
2826
|
-
*
|
|
2827
|
-
* Separate from `images` because a chart is not a picture with extra data —
|
|
2828
|
-
* it is its own part, its own relationship and its own workbook, and a
|
|
2829
|
-
* backend either writes all three or writes none. docx.js has no chart
|
|
2830
|
-
* primitive at all, which is what turns a `chart` component sent to that
|
|
2831
|
-
* backend into a named capability error rather than a document missing a
|
|
2832
|
-
* figure.
|
|
2833
|
-
*/
|
|
2834
|
-
"charts",
|
|
2835
|
-
/** A table-of-contents field. */
|
|
2836
|
-
"toc",
|
|
2837
|
-
/** TOC entries baked in so an unrefreshed reader still shows them. */
|
|
2838
|
-
"cached-toc",
|
|
2839
|
-
/** Arbitrary Word fields, e.g. PAGE, NUMPAGES, REF. */
|
|
2840
|
-
"fields",
|
|
2841
|
-
/** Field results cached for readers that never refresh. */
|
|
2842
|
-
"cached-fields",
|
|
2843
|
-
"hyperlinks",
|
|
2844
|
-
"bookmarks",
|
|
2845
|
-
/** Links and REF fields that target a bookmark. */
|
|
2846
|
-
"cross-references",
|
|
2847
|
-
"comments",
|
|
2848
|
-
/** Threaded and resolvable comments. */
|
|
2849
|
-
"comment-threads",
|
|
2850
|
-
"footnotes",
|
|
2851
|
-
"endnotes",
|
|
2852
|
-
/** Inserted and deleted content. */
|
|
2853
|
-
"revisions",
|
|
2854
|
-
/** Page, column and line breaks. */
|
|
2855
|
-
"breaks",
|
|
2856
|
-
/** Paragraph and table shading. */
|
|
2857
|
-
"shading",
|
|
2858
|
-
/** Paragraph, table and page borders. */
|
|
2859
|
-
"borders",
|
|
2860
|
-
/** Tab stops. */
|
|
2861
|
-
"tab-stops",
|
|
2862
|
-
/** Per-run proofing language and no-proof. */
|
|
2863
|
-
"proofing-language",
|
|
2864
|
-
/** Custom document properties. */
|
|
2865
|
-
"custom-properties",
|
|
2866
|
-
/** Right-to-left paragraph direction. */
|
|
2867
|
-
"rtl"
|
|
2868
|
-
];
|
|
2869
|
-
ALL_DOCX_FEATURES = new Set(
|
|
2870
|
-
DOCX_FEATURES
|
|
2871
|
-
);
|
|
2872
|
-
}
|
|
2873
|
-
});
|
|
2874
|
-
|
|
2875
2777
|
// src/utils/packageDocument.ts
|
|
2876
2778
|
import AdmZip from "adm-zip";
|
|
2877
2779
|
function resolveGenerationDate(options) {
|
|
@@ -3279,37 +3181,47 @@ function coreProperties(ir) {
|
|
|
3279
3181
|
...metadata.custom?.length ? { customProperties: metadata.custom } : {}
|
|
3280
3182
|
};
|
|
3281
3183
|
}
|
|
3282
|
-
var DOCXJS_RENDERER_ID,
|
|
3184
|
+
var DOCXJS_RENDERER_ID, DOCXJS_CAPABILITIES;
|
|
3283
3185
|
var init_docxjs = __esm({
|
|
3284
3186
|
"src/renderers/docxjs/index.ts"() {
|
|
3285
3187
|
"use strict";
|
|
3286
3188
|
init_styles2();
|
|
3287
3189
|
init_imageUtils();
|
|
3288
|
-
init_features();
|
|
3289
3190
|
init_packageDocument();
|
|
3290
3191
|
init_fixFloatingImageIds();
|
|
3291
3192
|
init_emit();
|
|
3292
3193
|
init_units();
|
|
3293
3194
|
DOCXJS_RENDERER_ID = "docxjs";
|
|
3294
|
-
|
|
3295
|
-
"
|
|
3296
|
-
"
|
|
3297
|
-
"
|
|
3298
|
-
"
|
|
3299
|
-
"
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
"
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
"
|
|
3195
|
+
DOCXJS_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
3196
|
+
"paragraphs",
|
|
3197
|
+
"styles",
|
|
3198
|
+
"numbering",
|
|
3199
|
+
"sections",
|
|
3200
|
+
"columns",
|
|
3201
|
+
"headers-footers",
|
|
3202
|
+
"tables",
|
|
3203
|
+
"floating-tables",
|
|
3204
|
+
"images",
|
|
3205
|
+
"floating-images",
|
|
3206
|
+
"svg-images",
|
|
3207
|
+
"text-frames",
|
|
3208
|
+
"text-boxes",
|
|
3209
|
+
"toc",
|
|
3210
|
+
"cached-toc",
|
|
3211
|
+
"fields",
|
|
3212
|
+
"hyperlinks",
|
|
3213
|
+
"bookmarks",
|
|
3214
|
+
"cross-references",
|
|
3215
|
+
"comments",
|
|
3216
|
+
"comment-threads",
|
|
3217
|
+
"footnotes",
|
|
3218
|
+
"endnotes",
|
|
3219
|
+
"revisions",
|
|
3220
|
+
"breaks",
|
|
3221
|
+
"tab-stops",
|
|
3222
|
+
"proofing-language",
|
|
3223
|
+
"custom-properties"
|
|
3309
3224
|
]);
|
|
3310
|
-
DOCXJS_CAPABILITIES = new Set(
|
|
3311
|
-
[...ALL_DOCX_FEATURES].filter((feature) => !NOT_YET_EMITTED.has(feature))
|
|
3312
|
-
);
|
|
3313
3225
|
}
|
|
3314
3226
|
});
|
|
3315
3227
|
|
|
@@ -4567,11 +4479,10 @@ function coreProperties2(ir) {
|
|
|
4567
4479
|
...metadata.custom?.length ? { customProperties: metadata.custom } : {}
|
|
4568
4480
|
};
|
|
4569
4481
|
}
|
|
4570
|
-
var OFFICE_OPEN_DOCX_RENDERER_ID, OFFICE_OPEN_DOCX,
|
|
4482
|
+
var OFFICE_OPEN_DOCX_RENDERER_ID, OFFICE_OPEN_DOCX, OFFICE_OPEN_CAPABILITIES;
|
|
4571
4483
|
var init_office_open = __esm({
|
|
4572
4484
|
"src/renderers/office-open/index.ts"() {
|
|
4573
4485
|
"use strict";
|
|
4574
|
-
init_features();
|
|
4575
4486
|
init_chartParts();
|
|
4576
4487
|
init_imageUtils();
|
|
4577
4488
|
init_packageDocument();
|
|
@@ -4579,17 +4490,37 @@ var init_office_open = __esm({
|
|
|
4579
4490
|
init_styles3();
|
|
4580
4491
|
OFFICE_OPEN_DOCX_RENDERER_ID = "office-open";
|
|
4581
4492
|
OFFICE_OPEN_DOCX = "@office-open/docx";
|
|
4582
|
-
|
|
4583
|
-
"
|
|
4584
|
-
"
|
|
4585
|
-
"
|
|
4586
|
-
"
|
|
4587
|
-
"
|
|
4588
|
-
"
|
|
4493
|
+
OFFICE_OPEN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
4494
|
+
"paragraphs",
|
|
4495
|
+
"styles",
|
|
4496
|
+
"numbering",
|
|
4497
|
+
"sections",
|
|
4498
|
+
"columns",
|
|
4499
|
+
"headers-footers",
|
|
4500
|
+
"tables",
|
|
4501
|
+
"floating-tables",
|
|
4502
|
+
"images",
|
|
4503
|
+
"floating-images",
|
|
4504
|
+
"svg-images",
|
|
4505
|
+
"text-frames",
|
|
4506
|
+
"text-boxes",
|
|
4507
|
+
"drawing-groups",
|
|
4508
|
+
"charts",
|
|
4509
|
+
"toc",
|
|
4510
|
+
"cached-toc",
|
|
4511
|
+
"fields",
|
|
4512
|
+
"hyperlinks",
|
|
4513
|
+
"bookmarks",
|
|
4514
|
+
"cross-references",
|
|
4515
|
+
"comments",
|
|
4516
|
+
"footnotes",
|
|
4517
|
+
"endnotes",
|
|
4518
|
+
"revisions",
|
|
4519
|
+
"breaks",
|
|
4520
|
+
"tab-stops",
|
|
4521
|
+
"proofing-language",
|
|
4522
|
+
"custom-properties"
|
|
4589
4523
|
]);
|
|
4590
|
-
OFFICE_OPEN_CAPABILITIES = new Set(
|
|
4591
|
-
[...ALL_DOCX_FEATURES].filter((feature) => !UNSUPPORTED.has(feature))
|
|
4592
|
-
);
|
|
4593
4524
|
}
|
|
4594
4525
|
});
|
|
4595
4526
|
|
|
@@ -5106,88 +5037,6 @@ async function prerasterizeVisuals(root, serviceConfig, options = {}) {
|
|
|
5106
5037
|
return map;
|
|
5107
5038
|
}
|
|
5108
5039
|
|
|
5109
|
-
// src/json/normalizer.ts
|
|
5110
|
-
function normalizeComponent(component) {
|
|
5111
|
-
if (component.name === "columns") {
|
|
5112
|
-
return normalizeColumnsComponent(component);
|
|
5113
|
-
}
|
|
5114
|
-
if ("children" in component && component.children) {
|
|
5115
|
-
return {
|
|
5116
|
-
...component,
|
|
5117
|
-
// Preserve ALL properties from validated component
|
|
5118
|
-
children: component.children.map(normalizeComponent)
|
|
5119
|
-
};
|
|
5120
|
-
}
|
|
5121
|
-
if (component.name === "section") {
|
|
5122
|
-
return normalizeSectionComponent(component);
|
|
5123
|
-
}
|
|
5124
|
-
return component;
|
|
5125
|
-
}
|
|
5126
|
-
function normalizeColumnsComponent(component) {
|
|
5127
|
-
const cfg = component.props || {};
|
|
5128
|
-
const applyTopLevelGap = (cols, topGap) => cols.map((c, i) => {
|
|
5129
|
-
const isLast = i === cols.length - 1;
|
|
5130
|
-
if (!isLast && c.gap === void 0 && topGap !== void 0) {
|
|
5131
|
-
return { ...c, gap: topGap };
|
|
5132
|
-
}
|
|
5133
|
-
return c;
|
|
5134
|
-
});
|
|
5135
|
-
const normalizeAutoWidth = (cols) => cols.map((c) => ({
|
|
5136
|
-
...c,
|
|
5137
|
-
width: c.width === "auto" ? void 0 : c.width
|
|
5138
|
-
}));
|
|
5139
|
-
let columnsArray;
|
|
5140
|
-
if (typeof cfg.columns === "number" && cfg.columns > 0) {
|
|
5141
|
-
const n = cfg.columns;
|
|
5142
|
-
const base = Array.from(
|
|
5143
|
-
{ length: n },
|
|
5144
|
-
() => ({})
|
|
5145
|
-
);
|
|
5146
|
-
const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
|
|
5147
|
-
columnsArray = applyTopLevelGap(base, topLevelGap);
|
|
5148
|
-
} else if (Array.isArray(cfg.columns)) {
|
|
5149
|
-
columnsArray = cfg.columns;
|
|
5150
|
-
const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
|
|
5151
|
-
columnsArray = applyTopLevelGap(columnsArray, topLevelGap);
|
|
5152
|
-
columnsArray = normalizeAutoWidth(columnsArray);
|
|
5153
|
-
} else {
|
|
5154
|
-
columnsArray = [{}];
|
|
5155
|
-
}
|
|
5156
|
-
const normalizedProps = {
|
|
5157
|
-
columns: columnsArray
|
|
5158
|
-
};
|
|
5159
|
-
return {
|
|
5160
|
-
...component,
|
|
5161
|
-
// Preserve any additional properties
|
|
5162
|
-
name: "columns",
|
|
5163
|
-
props: normalizedProps,
|
|
5164
|
-
children: component.children ? component.children.map(normalizeComponent) : void 0
|
|
5165
|
-
};
|
|
5166
|
-
}
|
|
5167
|
-
function normalizeSectionComponent(component) {
|
|
5168
|
-
const props = component.props || {};
|
|
5169
|
-
return {
|
|
5170
|
-
...component,
|
|
5171
|
-
// Preserve ALL properties
|
|
5172
|
-
props: {
|
|
5173
|
-
...props,
|
|
5174
|
-
// Preserve ALL props properties
|
|
5175
|
-
header: props.header ? props.header === "linkToPrevious" ? "linkToPrevious" : props.header.map((m) => normalizeComponent(m)) : void 0,
|
|
5176
|
-
footer: props.footer ? props.footer === "linkToPrevious" ? "linkToPrevious" : props.footer.map((m) => normalizeComponent(m)) : void 0
|
|
5177
|
-
},
|
|
5178
|
-
children: component.children ? component.children.map(normalizeComponent) : void 0
|
|
5179
|
-
};
|
|
5180
|
-
}
|
|
5181
|
-
function normalizeDocument(document) {
|
|
5182
|
-
if (document.name !== "docx") {
|
|
5183
|
-
throw new Error(
|
|
5184
|
-
'Top-level document must be a docx component with name="docx"'
|
|
5185
|
-
);
|
|
5186
|
-
}
|
|
5187
|
-
const normalized = normalizeComponent(document);
|
|
5188
|
-
return [normalized];
|
|
5189
|
-
}
|
|
5190
|
-
|
|
5191
5040
|
// src/core/desugarExternals.ts
|
|
5192
5041
|
import {
|
|
5193
5042
|
clampVisualDpi as clampVisualDpi2,
|
|
@@ -6023,6 +5872,13 @@ init_themes();
|
|
|
6023
5872
|
init_colorUtils();
|
|
6024
5873
|
init_styleHelpers();
|
|
6025
5874
|
var RIGHT_MARGIN_TAB_TWIPS = 9026 * 2;
|
|
5875
|
+
var STATISTIC_NUMBER_STYLE_ID = "StatisticNumber";
|
|
5876
|
+
var STATISTIC_DESCRIPTION_STYLE_ID = "StatisticDescription";
|
|
5877
|
+
var STATISTIC_SIZE_POINTS = {
|
|
5878
|
+
small: 20,
|
|
5879
|
+
medium: 28,
|
|
5880
|
+
large: 40
|
|
5881
|
+
};
|
|
6026
5882
|
function isValidStyleName(name) {
|
|
6027
5883
|
return [
|
|
6028
5884
|
"normal",
|
|
@@ -6661,6 +6517,67 @@ function createDocumentStyles(themeNameOrObject = "minimal", language) {
|
|
|
6661
6517
|
}
|
|
6662
6518
|
};
|
|
6663
6519
|
}
|
|
6520
|
+
function createStatisticStyles(theme) {
|
|
6521
|
+
const definitions = [
|
|
6522
|
+
{
|
|
6523
|
+
id: STATISTIC_NUMBER_STYLE_ID,
|
|
6524
|
+
name: "Statistic Number",
|
|
6525
|
+
font: "heading",
|
|
6526
|
+
sizePoints: STATISTIC_SIZE_POINTS.medium,
|
|
6527
|
+
color: theme.colors.primary,
|
|
6528
|
+
bold: true,
|
|
6529
|
+
// Space above separates the figure from whatever preceded it; none below,
|
|
6530
|
+
// because the description is the other half of the same block.
|
|
6531
|
+
spacing: { before: 12, after: 0 },
|
|
6532
|
+
next: STATISTIC_DESCRIPTION_STYLE_ID,
|
|
6533
|
+
// The figure and its caption are one unit; a page break between them
|
|
6534
|
+
// reads as a number with no label.
|
|
6535
|
+
keepNext: true
|
|
6536
|
+
},
|
|
6537
|
+
{
|
|
6538
|
+
id: STATISTIC_DESCRIPTION_STYLE_ID,
|
|
6539
|
+
name: "Statistic Description",
|
|
6540
|
+
font: "body",
|
|
6541
|
+
sizePoints: 10,
|
|
6542
|
+
color: theme.colors.textMuted,
|
|
6543
|
+
bold: false,
|
|
6544
|
+
spacing: { before: 0, after: 12 },
|
|
6545
|
+
next: "Normal",
|
|
6546
|
+
keepNext: false
|
|
6547
|
+
}
|
|
6548
|
+
];
|
|
6549
|
+
return definitions.filter(
|
|
6550
|
+
(definition) => !(theme.styles && Object.prototype.hasOwnProperty.call(theme.styles, definition.id))
|
|
6551
|
+
).map((definition) => {
|
|
6552
|
+
const fontProps = resolveFontProperties(theme, definition.font);
|
|
6553
|
+
return {
|
|
6554
|
+
id: definition.id,
|
|
6555
|
+
name: definition.name,
|
|
6556
|
+
basedOn: "Normal",
|
|
6557
|
+
next: definition.next,
|
|
6558
|
+
quickFormat: true,
|
|
6559
|
+
run: {
|
|
6560
|
+
fontFamily: fontProps.family || "Arial",
|
|
6561
|
+
// Pinned, not inherited: the whole point of the style is that the
|
|
6562
|
+
// figure is not body text, so a theme's body size must not decide it.
|
|
6563
|
+
sizeHalfPoints: definition.sizePoints * 2,
|
|
6564
|
+
color: { hex: resolveColor(definition.color, theme) },
|
|
6565
|
+
bold: definition.bold
|
|
6566
|
+
},
|
|
6567
|
+
paragraph: {
|
|
6568
|
+
spacing: {
|
|
6569
|
+
...resolveSpacing(definition.spacing),
|
|
6570
|
+
...irLineSpacing(fontProps.lineSpacing)
|
|
6571
|
+
},
|
|
6572
|
+
// Centred, matching the compiler's own alignment default — a KPI
|
|
6573
|
+
// reads as a block, and an author who wants otherwise says so on the
|
|
6574
|
+
// component, which overrides this on the paragraph.
|
|
6575
|
+
alignment: convertAlignment("center"),
|
|
6576
|
+
keepNext: definition.keepNext
|
|
6577
|
+
}
|
|
6578
|
+
};
|
|
6579
|
+
});
|
|
6580
|
+
}
|
|
6664
6581
|
|
|
6665
6582
|
// src/ir/compiler.ts
|
|
6666
6583
|
init_styleHelpers();
|
|
@@ -7971,6 +7888,7 @@ function compileDocument(structure, layout, warnings = [], images = /* @__PURE__
|
|
|
7971
7888
|
resourcesByHash: /* @__PURE__ */ new Map(),
|
|
7972
7889
|
nextBookmarkId: CONTENT_BOOKMARK_ID_BASE + 1,
|
|
7973
7890
|
bookmarkNames: /* @__PURE__ */ new Set(),
|
|
7891
|
+
usesStatistic: false,
|
|
7974
7892
|
styleIds: /* @__PURE__ */ new Set([
|
|
7975
7893
|
...styles.paragraph.map((s) => s.id),
|
|
7976
7894
|
...styles.character.map((s) => s.id)
|
|
@@ -8019,7 +7937,17 @@ function compileDocument(structure, layout, warnings = [], images = /* @__PURE__
|
|
|
8019
7937
|
trackRevisions: structure.trackRevisions === true,
|
|
8020
7938
|
...noProofWords(structure.theme) ? { noProofWords: noProofWords(structure.theme) } : {}
|
|
8021
7939
|
},
|
|
8022
|
-
styles
|
|
7940
|
+
// The statistic styles are appended rather than built with the rest: a
|
|
7941
|
+
// document that uses no statistic must keep the exact style set it had
|
|
7942
|
+
// before the component had styles, or every recorded golden moves for a
|
|
7943
|
+
// component the document does not contain.
|
|
7944
|
+
styles: ctx.usesStatistic ? {
|
|
7945
|
+
...styles,
|
|
7946
|
+
paragraph: [
|
|
7947
|
+
...styles.paragraph,
|
|
7948
|
+
...createStatisticStyles(structure.theme)
|
|
7949
|
+
]
|
|
7950
|
+
} : styles,
|
|
8023
7951
|
numbering: ctx.numbering,
|
|
8024
7952
|
resources: ctx.resources,
|
|
8025
7953
|
sections,
|
|
@@ -8034,6 +7962,21 @@ function compileDocument(structure, layout, warnings = [], images = /* @__PURE__
|
|
|
8034
7962
|
unsupported: ctx.unsupported
|
|
8035
7963
|
};
|
|
8036
7964
|
}
|
|
7965
|
+
var AFTER_TABLE_TWIPS = 120;
|
|
7966
|
+
var BODY_STYLE_IDS = /* @__PURE__ */ new Set([void 0, "Normal"]);
|
|
7967
|
+
function separateFromTables(blocks) {
|
|
7968
|
+
for (let i = 1; i < blocks.length; i += 1) {
|
|
7969
|
+
const block2 = blocks[i];
|
|
7970
|
+
if (blocks[i - 1].kind !== "table") continue;
|
|
7971
|
+
if (block2.kind !== "paragraph") continue;
|
|
7972
|
+
if (!BODY_STYLE_IDS.has(block2.styleId)) continue;
|
|
7973
|
+
if (block2.formatting?.spacing?.beforeTwips !== void 0) continue;
|
|
7974
|
+
block2.formatting = {
|
|
7975
|
+
...block2.formatting,
|
|
7976
|
+
spacing: { ...block2.formatting?.spacing, beforeTwips: AFTER_TABLE_TWIPS }
|
|
7977
|
+
};
|
|
7978
|
+
}
|
|
7979
|
+
}
|
|
8037
7980
|
function collectOutline(layout, warnings) {
|
|
8038
7981
|
try {
|
|
8039
7982
|
return collectDocumentOutline(layout.sections);
|
|
@@ -8110,6 +8053,7 @@ function compileSection(section2, index, ordinal, chrome, ctx) {
|
|
|
8110
8053
|
})
|
|
8111
8054
|
);
|
|
8112
8055
|
});
|
|
8056
|
+
separateFromTables(children);
|
|
8113
8057
|
const compiled = {
|
|
8114
8058
|
id: `s${index}`,
|
|
8115
8059
|
path: path4,
|
|
@@ -8880,35 +8824,83 @@ function compileToc(component, scope) {
|
|
|
8880
8824
|
});
|
|
8881
8825
|
return blocks;
|
|
8882
8826
|
}
|
|
8827
|
+
var TREND_GLYPHS = {
|
|
8828
|
+
up: "\u25B2",
|
|
8829
|
+
down: "\u25BC",
|
|
8830
|
+
neutral: "\u2013"
|
|
8831
|
+
};
|
|
8883
8832
|
function compileStatistic(component, scope) {
|
|
8884
|
-
const { path: path4 } = scope;
|
|
8833
|
+
const { ctx, path: path4 } = scope;
|
|
8885
8834
|
const props = component.props ?? {};
|
|
8886
8835
|
const alignment2 = compileAlignment(props.alignment) ?? "center";
|
|
8836
|
+
ctx.usesStatistic = true;
|
|
8887
8837
|
const spacing = props.spacing ? {
|
|
8888
8838
|
...props.spacing.before !== void 0 ? { beforeTwips: props.spacing.before } : {},
|
|
8889
8839
|
...props.spacing.after !== void 0 ? { afterTwips: props.spacing.after } : {}
|
|
8890
8840
|
} : void 0;
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
8841
|
+
if (props.format !== void 0) {
|
|
8842
|
+
warnOnce(
|
|
8843
|
+
ctx,
|
|
8844
|
+
"statistic",
|
|
8845
|
+
'statistic "format" is not implemented and was ignored \u2014 format the value yourself and pass the result as "number".',
|
|
8846
|
+
"W_STATISTIC_FORMAT_IGNORED"
|
|
8847
|
+
);
|
|
8848
|
+
}
|
|
8849
|
+
const text = (value) => normalizeUnicodeText(String(value ?? ""));
|
|
8850
|
+
const numberPoints = STATISTIC_SIZE_POINTS[String(props.size)] ?? STATISTIC_SIZE_POINTS.medium;
|
|
8851
|
+
const resized = numberPoints !== STATISTIC_SIZE_POINTS.medium;
|
|
8852
|
+
const suffixHalfPoints = Math.max(12, Math.round(numberPoints));
|
|
8853
|
+
const numberRuns = [];
|
|
8854
|
+
const numberText = text(props.number);
|
|
8855
|
+
if (numberText) {
|
|
8856
|
+
numberRuns.push({
|
|
8857
|
+
kind: "text",
|
|
8858
|
+
text: numberText,
|
|
8859
|
+
...resized ? { formatting: { sizeHalfPoints: numberPoints * 2 } } : {}
|
|
8860
|
+
});
|
|
8861
|
+
}
|
|
8862
|
+
const unitText = text(props.unit);
|
|
8863
|
+
if (unitText) {
|
|
8864
|
+
numberRuns.push({
|
|
8865
|
+
kind: "text",
|
|
8866
|
+
text: unitText,
|
|
8867
|
+
formatting: { sizeHalfPoints: suffixHalfPoints }
|
|
8868
|
+
});
|
|
8869
|
+
}
|
|
8870
|
+
const glyph = typeof props.trend === "string" ? TREND_GLYPHS[props.trend] : void 0;
|
|
8871
|
+
const trendValue = text(props.trendValue);
|
|
8872
|
+
if (glyph || trendValue) {
|
|
8873
|
+
numberRuns.push({
|
|
8874
|
+
kind: "text",
|
|
8875
|
+
text: `${numberRuns.length > 0 ? " " : ""}${[glyph, trendValue].filter(Boolean).join(" ")}`,
|
|
8876
|
+
formatting: {
|
|
8877
|
+
sizeHalfPoints: suffixHalfPoints,
|
|
8878
|
+
color: irColor(resolveColor(ctx.theme.colors.textMuted, ctx.theme))
|
|
8879
|
+
}
|
|
8880
|
+
});
|
|
8881
|
+
}
|
|
8882
|
+
const line = (children, styleId, suffix, formatting) => ({
|
|
8883
|
+
kind: "paragraph",
|
|
8884
|
+
id: `${scope.id}:${suffix}`,
|
|
8885
|
+
path: `${path4}.${suffix}`,
|
|
8886
|
+
styleId,
|
|
8887
|
+
formatting,
|
|
8888
|
+
// Nothing to say is not the same as saying nothing: an empty statistic
|
|
8889
|
+
// line is a styled blank paragraph, with no run inside it at all.
|
|
8890
|
+
children
|
|
8891
|
+
});
|
|
8892
|
+
const descriptionText = text(props.description);
|
|
8904
8893
|
return [
|
|
8905
|
-
line(
|
|
8894
|
+
line(numberRuns, STATISTIC_NUMBER_STYLE_ID, "number", {
|
|
8906
8895
|
alignment: alignment2,
|
|
8907
8896
|
...spacing ? { spacing } : {}
|
|
8908
8897
|
}),
|
|
8909
|
-
line(
|
|
8910
|
-
|
|
8911
|
-
|
|
8898
|
+
line(
|
|
8899
|
+
descriptionText ? [{ kind: "text", text: descriptionText }] : [],
|
|
8900
|
+
STATISTIC_DESCRIPTION_STYLE_ID,
|
|
8901
|
+
"description",
|
|
8902
|
+
{ alignment: alignment2 }
|
|
8903
|
+
)
|
|
8912
8904
|
];
|
|
8913
8905
|
}
|
|
8914
8906
|
function compileList(component, scope, referencePrefix = "list", explicitLevels) {
|
|
@@ -8981,12 +8973,12 @@ function compileList(component, scope, referencePrefix = "list", explicitLevels)
|
|
|
8981
8973
|
}
|
|
8982
8974
|
function itemSpacing(spacing, index, count) {
|
|
8983
8975
|
const out = {};
|
|
8984
|
-
if (index === 0 && spacing?.before) {
|
|
8976
|
+
if (index === 0 && spacing?.before !== void 0) {
|
|
8985
8977
|
out.beforeTwips = pointsToTwips2(spacing.before);
|
|
8986
8978
|
}
|
|
8987
|
-
if (index === count - 1 && spacing?.after) {
|
|
8979
|
+
if (index === count - 1 && spacing?.after !== void 0) {
|
|
8988
8980
|
out.afterTwips = pointsToTwips2(spacing.after);
|
|
8989
|
-
} else if (spacing?.item) {
|
|
8981
|
+
} else if (spacing?.item !== void 0) {
|
|
8990
8982
|
out.afterTwips = pointsToTwips2(spacing.item);
|
|
8991
8983
|
}
|
|
8992
8984
|
return out;
|
|
@@ -10457,7 +10449,9 @@ function warnOnce(ctx, component, message, code) {
|
|
|
10457
10449
|
}
|
|
10458
10450
|
|
|
10459
10451
|
// src/renderers/registry.ts
|
|
10460
|
-
import {
|
|
10452
|
+
import {
|
|
10453
|
+
RendererRegistry
|
|
10454
|
+
} from "@json-to-office/shared/rendering";
|
|
10461
10455
|
|
|
10462
10456
|
// src/renderers/types.ts
|
|
10463
10457
|
import {
|
|
@@ -10483,108 +10477,16 @@ function resolveDocxRenderer(id) {
|
|
|
10483
10477
|
function docxRendererIds() {
|
|
10484
10478
|
return registry.ids();
|
|
10485
10479
|
}
|
|
10480
|
+
function docxRendererStatuses() {
|
|
10481
|
+
return registry.statuses();
|
|
10482
|
+
}
|
|
10486
10483
|
function isDocxRendererId(value) {
|
|
10487
10484
|
return registry.has(value);
|
|
10488
10485
|
}
|
|
10489
10486
|
|
|
10490
10487
|
// src/core/generateFromIr.ts
|
|
10491
10488
|
init_packageDocument();
|
|
10492
|
-
|
|
10493
|
-
// src/core/generationContext.ts
|
|
10494
|
-
import { applyExportMode } from "@json-to-office/shared";
|
|
10495
|
-
|
|
10496
|
-
// src/styles/theme-resolver.ts
|
|
10497
|
-
init_themes();
|
|
10498
|
-
function resolveBuiltInTheme(themeName, options = {}) {
|
|
10499
|
-
if (!hasTheme(themeName)) {
|
|
10500
|
-
const available = [
|
|
10501
|
-
...getThemeNames(),
|
|
10502
|
-
...Object.keys(options.customThemes ?? {})
|
|
10503
|
-
].sort();
|
|
10504
|
-
options.warnings?.push({
|
|
10505
|
-
component: "theme",
|
|
10506
|
-
message: `Theme "${themeName}" not found; falling back to "minimal". Available themes: ${available.join(", ")}.`,
|
|
10507
|
-
severity: "warning",
|
|
10508
|
-
context: { code: "theme_not_found", requested: themeName, available }
|
|
10509
|
-
});
|
|
10510
|
-
}
|
|
10511
|
-
return getThemeWithFallback(themeName);
|
|
10512
|
-
}
|
|
10513
|
-
|
|
10514
|
-
// src/themes/overrides.ts
|
|
10515
|
-
function applyThemeOverrides(theme, overrides) {
|
|
10516
|
-
if (!overrides) return theme;
|
|
10517
|
-
const fonts = { ...theme.fonts };
|
|
10518
|
-
for (const [role, def] of Object.entries(overrides.fonts ?? {})) {
|
|
10519
|
-
const key = role;
|
|
10520
|
-
fonts[key] = { ...theme.fonts[key], ...def };
|
|
10521
|
-
}
|
|
10522
|
-
const styles = { ...theme.styles ?? {} };
|
|
10523
|
-
for (const [name, def] of Object.entries(overrides.styles ?? {})) {
|
|
10524
|
-
styles[name] = { ...styles[name], ...def };
|
|
10525
|
-
}
|
|
10526
|
-
return {
|
|
10527
|
-
...theme,
|
|
10528
|
-
colors: { ...theme.colors, ...overrides.colors ?? {} },
|
|
10529
|
-
fonts,
|
|
10530
|
-
...Object.keys(styles).length > 0 && { styles }
|
|
10531
|
-
};
|
|
10532
|
-
}
|
|
10533
|
-
|
|
10534
|
-
// src/core/generationContext.ts
|
|
10535
|
-
function defaultNamedThemeLookup(themeName, customThemes, warnings) {
|
|
10536
|
-
if (customThemes) {
|
|
10537
|
-
if (customThemes[themeName]) return customThemes[themeName];
|
|
10538
|
-
const themeNameLower = themeName.toLowerCase();
|
|
10539
|
-
const matchingThemeKey = Object.keys(customThemes).find(
|
|
10540
|
-
(key) => key.toLowerCase() === themeNameLower
|
|
10541
|
-
);
|
|
10542
|
-
if (matchingThemeKey) return customThemes[matchingThemeKey];
|
|
10543
|
-
return resolveBuiltInTheme(themeName, { customThemes, warnings });
|
|
10544
|
-
}
|
|
10545
|
-
return resolveBuiltInTheme(themeName, { warnings });
|
|
10546
|
-
}
|
|
10547
|
-
function resolveThemeContext(documentIn, options = {}) {
|
|
10548
|
-
const { customThemes, fonts, warnings, resolveNamedTheme } = options;
|
|
10549
|
-
if (documentIn.props === null) {
|
|
10550
|
-
throw new Error(
|
|
10551
|
-
"Document `props` is null. Omit it, or provide an object \u2014 a null props cannot carry a theme."
|
|
10552
|
-
);
|
|
10553
|
-
}
|
|
10554
|
-
const document = documentIn.props === void 0 ? { ...documentIn, props: {} } : documentIn;
|
|
10555
|
-
const authoredThemeName = document.props.theme || void 0;
|
|
10556
|
-
const baseThemeName = authoredThemeName ?? "minimal";
|
|
10557
|
-
let theme = resolveNamedTheme ? resolveNamedTheme(
|
|
10558
|
-
baseThemeName,
|
|
10559
|
-
warnings,
|
|
10560
|
-
authoredThemeName !== void 0
|
|
10561
|
-
) : defaultNamedThemeLookup(baseThemeName, customThemes, warnings);
|
|
10562
|
-
const themeOverrides = document.props.themeOverrides;
|
|
10563
|
-
if (themeOverrides) {
|
|
10564
|
-
theme = applyThemeOverrides(theme, themeOverrides);
|
|
10565
|
-
}
|
|
10566
|
-
const mode = applyExportMode({ doc: document, theme, fonts });
|
|
10567
|
-
for (const w of mode.warnings) {
|
|
10568
|
-
if (warnings) {
|
|
10569
|
-
warnings.push({
|
|
10570
|
-
component: "fontRegistry",
|
|
10571
|
-
message: w.message,
|
|
10572
|
-
severity: "warning",
|
|
10573
|
-
context: { code: w.code }
|
|
10574
|
-
});
|
|
10575
|
-
} else {
|
|
10576
|
-
console.warn(`[json-to-docx] [${w.code}] ${w.message}`);
|
|
10577
|
-
}
|
|
10578
|
-
}
|
|
10579
|
-
return {
|
|
10580
|
-
document: mode.doc,
|
|
10581
|
-
theme: mode.theme,
|
|
10582
|
-
themeName: baseThemeName
|
|
10583
|
-
};
|
|
10584
|
-
}
|
|
10585
|
-
|
|
10586
|
-
// src/core/generateFromIr.ts
|
|
10587
|
-
init_generationContext();
|
|
10489
|
+
init_generationContext();
|
|
10588
10490
|
|
|
10589
10491
|
// src/core/layout.ts
|
|
10590
10492
|
init_styles();
|
|
@@ -11037,8 +10939,7 @@ function resolveComponentTree(components, theme) {
|
|
|
11037
10939
|
}
|
|
11038
10940
|
|
|
11039
10941
|
// src/core/structure.ts
|
|
11040
|
-
|
|
11041
|
-
const metadata = createDocumentMetadata(document.props, generationDate);
|
|
10942
|
+
function resolveDocumentTree(document, theme) {
|
|
11042
10943
|
const docDefaults = document.props.componentDefaults;
|
|
11043
10944
|
const mergedComponentDefaults = docDefaults ? mergeWithDefaults2(docDefaults, theme.componentDefaults || {}) : void 0;
|
|
11044
10945
|
const docNoProofWords = document.props.noProofWords;
|
|
@@ -11052,6 +10953,14 @@ async function processDocument(document, theme, themeName, generationDate) {
|
|
|
11052
10953
|
},
|
|
11053
10954
|
...mergedNoProofWords && { noProofWords: mergedNoProofWords }
|
|
11054
10955
|
} : theme;
|
|
10956
|
+
return {
|
|
10957
|
+
theme: effectiveTheme,
|
|
10958
|
+
children: resolveComponentTree(document.children || [], effectiveTheme)
|
|
10959
|
+
};
|
|
10960
|
+
}
|
|
10961
|
+
async function processResolvedDocument(document, resolved, themeName, generationDate) {
|
|
10962
|
+
const metadata = createDocumentMetadata(document.props, generationDate);
|
|
10963
|
+
const effectiveTheme = resolved.theme;
|
|
11055
10964
|
const context = createRenderContext(
|
|
11056
10965
|
{
|
|
11057
10966
|
metadata,
|
|
@@ -11062,11 +10971,7 @@ async function processDocument(document, theme, themeName, generationDate) {
|
|
|
11062
10971
|
effectiveTheme,
|
|
11063
10972
|
themeName
|
|
11064
10973
|
);
|
|
11065
|
-
const
|
|
11066
|
-
document.children || [],
|
|
11067
|
-
effectiveTheme
|
|
11068
|
-
);
|
|
11069
|
-
const sections = await extractSections(resolvedChildren, context);
|
|
10974
|
+
const sections = await extractSections(resolved.children, context);
|
|
11070
10975
|
return {
|
|
11071
10976
|
metadata,
|
|
11072
10977
|
sections,
|
|
@@ -11166,6 +11071,419 @@ function createRenderContext(document, theme, themeName) {
|
|
|
11166
11071
|
};
|
|
11167
11072
|
}
|
|
11168
11073
|
|
|
11074
|
+
// src/quality/facts.ts
|
|
11075
|
+
import { DEFAULT_DOCX_RENDERER_ID as DEFAULT_DOCX_RENDERER_ID2 } from "@json-to-office/shared-docx";
|
|
11076
|
+
|
|
11077
|
+
// src/core/generationContext.ts
|
|
11078
|
+
import { applyExportMode } from "@json-to-office/shared";
|
|
11079
|
+
|
|
11080
|
+
// src/styles/theme-resolver.ts
|
|
11081
|
+
init_themes();
|
|
11082
|
+
function resolveBuiltInTheme(themeName, options = {}) {
|
|
11083
|
+
if (!hasTheme(themeName)) {
|
|
11084
|
+
const available = [
|
|
11085
|
+
...getThemeNames(),
|
|
11086
|
+
...Object.keys(options.customThemes ?? {})
|
|
11087
|
+
].sort();
|
|
11088
|
+
options.warnings?.push({
|
|
11089
|
+
component: "theme",
|
|
11090
|
+
message: `Theme "${themeName}" not found; falling back to "minimal". Available themes: ${available.join(", ")}.`,
|
|
11091
|
+
severity: "warning",
|
|
11092
|
+
context: { code: "theme_not_found", requested: themeName, available }
|
|
11093
|
+
});
|
|
11094
|
+
}
|
|
11095
|
+
return getThemeWithFallback(themeName);
|
|
11096
|
+
}
|
|
11097
|
+
|
|
11098
|
+
// src/themes/overrides.ts
|
|
11099
|
+
function applyThemeOverrides(theme, overrides) {
|
|
11100
|
+
if (!overrides) return theme;
|
|
11101
|
+
const fonts = { ...theme.fonts };
|
|
11102
|
+
for (const [role, def] of Object.entries(overrides.fonts ?? {})) {
|
|
11103
|
+
const key = role;
|
|
11104
|
+
fonts[key] = { ...theme.fonts[key], ...def };
|
|
11105
|
+
}
|
|
11106
|
+
const styles = { ...theme.styles ?? {} };
|
|
11107
|
+
for (const [name, def] of Object.entries(overrides.styles ?? {})) {
|
|
11108
|
+
styles[name] = { ...styles[name], ...def };
|
|
11109
|
+
}
|
|
11110
|
+
return {
|
|
11111
|
+
...theme,
|
|
11112
|
+
colors: { ...theme.colors, ...overrides.colors ?? {} },
|
|
11113
|
+
fonts,
|
|
11114
|
+
...Object.keys(styles).length > 0 && { styles }
|
|
11115
|
+
};
|
|
11116
|
+
}
|
|
11117
|
+
|
|
11118
|
+
// src/core/generationContext.ts
|
|
11119
|
+
function defaultNamedThemeLookup(themeName, customThemes, warnings) {
|
|
11120
|
+
if (customThemes) {
|
|
11121
|
+
if (customThemes[themeName]) return customThemes[themeName];
|
|
11122
|
+
const themeNameLower = themeName.toLowerCase();
|
|
11123
|
+
const matchingThemeKey = Object.keys(customThemes).find(
|
|
11124
|
+
(key) => key.toLowerCase() === themeNameLower
|
|
11125
|
+
);
|
|
11126
|
+
if (matchingThemeKey) return customThemes[matchingThemeKey];
|
|
11127
|
+
return resolveBuiltInTheme(themeName, { customThemes, warnings });
|
|
11128
|
+
}
|
|
11129
|
+
return resolveBuiltInTheme(themeName, { warnings });
|
|
11130
|
+
}
|
|
11131
|
+
function resolveThemeContext(documentIn, options = {}) {
|
|
11132
|
+
const { customThemes, fonts, warnings, resolveNamedTheme } = options;
|
|
11133
|
+
if (documentIn.props === null) {
|
|
11134
|
+
throw new Error(
|
|
11135
|
+
"Document `props` is null. Omit it, or provide an object \u2014 a null props cannot carry a theme."
|
|
11136
|
+
);
|
|
11137
|
+
}
|
|
11138
|
+
const document = documentIn.props === void 0 ? { ...documentIn, props: {} } : documentIn;
|
|
11139
|
+
const authoredThemeName = document.props.theme || void 0;
|
|
11140
|
+
const baseThemeName = authoredThemeName ?? "minimal";
|
|
11141
|
+
let theme = resolveNamedTheme ? resolveNamedTheme(
|
|
11142
|
+
baseThemeName,
|
|
11143
|
+
warnings,
|
|
11144
|
+
authoredThemeName !== void 0
|
|
11145
|
+
) : defaultNamedThemeLookup(baseThemeName, customThemes, warnings);
|
|
11146
|
+
const themeOverrides = document.props.themeOverrides;
|
|
11147
|
+
if (themeOverrides) {
|
|
11148
|
+
theme = applyThemeOverrides(theme, themeOverrides);
|
|
11149
|
+
}
|
|
11150
|
+
const mode = applyExportMode({ doc: document, theme, fonts });
|
|
11151
|
+
for (const w of mode.warnings) {
|
|
11152
|
+
if (warnings) {
|
|
11153
|
+
warnings.push({
|
|
11154
|
+
component: "fontRegistry",
|
|
11155
|
+
message: w.message,
|
|
11156
|
+
severity: "warning",
|
|
11157
|
+
context: { code: w.code }
|
|
11158
|
+
});
|
|
11159
|
+
} else {
|
|
11160
|
+
console.warn(`[json-to-docx] [${w.code}] ${w.message}`);
|
|
11161
|
+
}
|
|
11162
|
+
}
|
|
11163
|
+
return {
|
|
11164
|
+
document: mode.doc,
|
|
11165
|
+
theme: mode.theme,
|
|
11166
|
+
themeName: baseThemeName
|
|
11167
|
+
};
|
|
11168
|
+
}
|
|
11169
|
+
|
|
11170
|
+
// src/json/normalizer.ts
|
|
11171
|
+
function normalizeComponent(component) {
|
|
11172
|
+
if (component.name === "columns") {
|
|
11173
|
+
return normalizeColumnsComponent(component);
|
|
11174
|
+
}
|
|
11175
|
+
if ("children" in component && component.children) {
|
|
11176
|
+
return {
|
|
11177
|
+
...component,
|
|
11178
|
+
// Preserve ALL properties from validated component
|
|
11179
|
+
children: component.children.map(normalizeComponent)
|
|
11180
|
+
};
|
|
11181
|
+
}
|
|
11182
|
+
if (component.name === "section") {
|
|
11183
|
+
return normalizeSectionComponent(component);
|
|
11184
|
+
}
|
|
11185
|
+
return component;
|
|
11186
|
+
}
|
|
11187
|
+
function normalizeColumnsComponent(component) {
|
|
11188
|
+
const cfg = component.props || {};
|
|
11189
|
+
const applyTopLevelGap = (cols, topGap) => cols.map((c, i) => {
|
|
11190
|
+
const isLast = i === cols.length - 1;
|
|
11191
|
+
if (!isLast && c.gap === void 0 && topGap !== void 0) {
|
|
11192
|
+
return { ...c, gap: topGap };
|
|
11193
|
+
}
|
|
11194
|
+
return c;
|
|
11195
|
+
});
|
|
11196
|
+
const normalizeAutoWidth = (cols) => cols.map((c) => ({
|
|
11197
|
+
...c,
|
|
11198
|
+
width: c.width === "auto" ? void 0 : c.width
|
|
11199
|
+
}));
|
|
11200
|
+
let columnsArray;
|
|
11201
|
+
if (typeof cfg.columns === "number" && cfg.columns > 0) {
|
|
11202
|
+
const n = cfg.columns;
|
|
11203
|
+
const base = Array.from(
|
|
11204
|
+
{ length: n },
|
|
11205
|
+
() => ({})
|
|
11206
|
+
);
|
|
11207
|
+
const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
|
|
11208
|
+
columnsArray = applyTopLevelGap(base, topLevelGap);
|
|
11209
|
+
} else if (Array.isArray(cfg.columns)) {
|
|
11210
|
+
columnsArray = cfg.columns;
|
|
11211
|
+
const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
|
|
11212
|
+
columnsArray = applyTopLevelGap(columnsArray, topLevelGap);
|
|
11213
|
+
columnsArray = normalizeAutoWidth(columnsArray);
|
|
11214
|
+
} else {
|
|
11215
|
+
columnsArray = [{}];
|
|
11216
|
+
}
|
|
11217
|
+
const normalizedProps = {
|
|
11218
|
+
columns: columnsArray
|
|
11219
|
+
};
|
|
11220
|
+
return {
|
|
11221
|
+
...component,
|
|
11222
|
+
// Preserve any additional properties
|
|
11223
|
+
name: "columns",
|
|
11224
|
+
props: normalizedProps,
|
|
11225
|
+
children: component.children ? component.children.map(normalizeComponent) : void 0
|
|
11226
|
+
};
|
|
11227
|
+
}
|
|
11228
|
+
function normalizeSectionComponent(component) {
|
|
11229
|
+
const props = component.props || {};
|
|
11230
|
+
return {
|
|
11231
|
+
...component,
|
|
11232
|
+
// Preserve ALL properties
|
|
11233
|
+
props: {
|
|
11234
|
+
...props,
|
|
11235
|
+
// Preserve ALL props properties
|
|
11236
|
+
header: props.header ? props.header === "linkToPrevious" ? "linkToPrevious" : props.header.map((m) => normalizeComponent(m)) : void 0,
|
|
11237
|
+
footer: props.footer ? props.footer === "linkToPrevious" ? "linkToPrevious" : props.footer.map((m) => normalizeComponent(m)) : void 0
|
|
11238
|
+
},
|
|
11239
|
+
children: component.children ? component.children.map(normalizeComponent) : void 0
|
|
11240
|
+
};
|
|
11241
|
+
}
|
|
11242
|
+
function normalizeDocument(document) {
|
|
11243
|
+
if (document.name !== "docx") {
|
|
11244
|
+
throw new Error(
|
|
11245
|
+
'Top-level document must be a docx component with name="docx"'
|
|
11246
|
+
);
|
|
11247
|
+
}
|
|
11248
|
+
const normalized = normalizeComponent(document);
|
|
11249
|
+
return [normalized];
|
|
11250
|
+
}
|
|
11251
|
+
|
|
11252
|
+
// src/quality/facts.ts
|
|
11253
|
+
init_widthUtils();
|
|
11254
|
+
function asRecord(value) {
|
|
11255
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
11256
|
+
}
|
|
11257
|
+
function pageBox(theme, themeName, pageOverride) {
|
|
11258
|
+
const page = createSectionProperties(
|
|
11259
|
+
getColumnSettings("single"),
|
|
11260
|
+
theme,
|
|
11261
|
+
themeName,
|
|
11262
|
+
"nextPage",
|
|
11263
|
+
pageOverride
|
|
11264
|
+
).page;
|
|
11265
|
+
return {
|
|
11266
|
+
availableWidthTwips: Math.max(
|
|
11267
|
+
0,
|
|
11268
|
+
page.size.width - page.margin.left - page.margin.right
|
|
11269
|
+
),
|
|
11270
|
+
pageBottomTwips: page.size.height
|
|
11271
|
+
};
|
|
11272
|
+
}
|
|
11273
|
+
function tableFact(props, path4, availableWidthTwips) {
|
|
11274
|
+
const columns = Array.isArray(props.columns) ? props.columns : void 0;
|
|
11275
|
+
if (!columns) return void 0;
|
|
11276
|
+
let totalWidthTwips = 0;
|
|
11277
|
+
let hasExplicitWidth = false;
|
|
11278
|
+
let pointSum = 0;
|
|
11279
|
+
let percentSum = 0;
|
|
11280
|
+
const explicitWidths = [];
|
|
11281
|
+
columns.forEach((column, index) => {
|
|
11282
|
+
const width = asRecord(column)?.width;
|
|
11283
|
+
if (typeof width === "number" && Number.isFinite(width)) {
|
|
11284
|
+
hasExplicitWidth = true;
|
|
11285
|
+
explicitWidths.push({ index, width });
|
|
11286
|
+
totalWidthTwips += relativeLengthToTwips(width, availableWidthTwips);
|
|
11287
|
+
pointSum += width;
|
|
11288
|
+
} else if (typeof width === "string") {
|
|
11289
|
+
hasExplicitWidth = true;
|
|
11290
|
+
const percent = width.trim().endsWith("%") ? Number(width.trim().slice(0, -1)) : Number.NaN;
|
|
11291
|
+
if (Number.isFinite(percent)) {
|
|
11292
|
+
explicitWidths.push({ index, width });
|
|
11293
|
+
percentSum += percent;
|
|
11294
|
+
totalWidthTwips += Math.max(
|
|
11295
|
+
0,
|
|
11296
|
+
Math.round(availableWidthTwips * percent / 100)
|
|
11297
|
+
);
|
|
11298
|
+
}
|
|
11299
|
+
}
|
|
11300
|
+
});
|
|
11301
|
+
return {
|
|
11302
|
+
id: `docx:table-width:${path4}`,
|
|
11303
|
+
kind: "docx/table-width",
|
|
11304
|
+
path: `${path4}/props/columns`,
|
|
11305
|
+
totalWidthTwips,
|
|
11306
|
+
availableWidthTwips,
|
|
11307
|
+
hasExplicitWidth,
|
|
11308
|
+
allColumnsExplicit: explicitWidths.length === columns.length,
|
|
11309
|
+
pointSum,
|
|
11310
|
+
percentSum,
|
|
11311
|
+
explicitWidths
|
|
11312
|
+
};
|
|
11313
|
+
}
|
|
11314
|
+
var TWIPS_PER_POINT3 = 20;
|
|
11315
|
+
var SINGLE_LINE_RATIO = 1.2;
|
|
11316
|
+
function finiteNumber(value) {
|
|
11317
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
11318
|
+
}
|
|
11319
|
+
function lineHeightPt(fontSizePt, spacing) {
|
|
11320
|
+
const rule = asRecord(spacing);
|
|
11321
|
+
const value = finiteNumber(rule?.value);
|
|
11322
|
+
switch (rule?.type) {
|
|
11323
|
+
// `exactly` is an absolute line box, and display type routinely sets it
|
|
11324
|
+
// below the font size — reading it as a multiplier would inflate every
|
|
11325
|
+
// estimate on exactly the headings that matter most.
|
|
11326
|
+
case "exactly":
|
|
11327
|
+
return value ?? fontSizePt * SINGLE_LINE_RATIO;
|
|
11328
|
+
case "atLeast":
|
|
11329
|
+
return Math.max(value ?? 0, fontSizePt * SINGLE_LINE_RATIO);
|
|
11330
|
+
case "multiple":
|
|
11331
|
+
return fontSizePt * SINGLE_LINE_RATIO * (value ?? 1);
|
|
11332
|
+
default:
|
|
11333
|
+
return fontSizePt * SINGLE_LINE_RATIO;
|
|
11334
|
+
}
|
|
11335
|
+
}
|
|
11336
|
+
function characterSpacingPt(spacing) {
|
|
11337
|
+
const rule = asRecord(spacing);
|
|
11338
|
+
const value = finiteNumber(rule?.value);
|
|
11339
|
+
if (rule === void 0 || value === void 0) return 0;
|
|
11340
|
+
return rule.type === "condensed" ? -value / TWIPS_PER_POINT3 : value / TWIPS_PER_POINT3;
|
|
11341
|
+
}
|
|
11342
|
+
function frameTextFact(props, path4, page) {
|
|
11343
|
+
const floating = asRecord(props.floating);
|
|
11344
|
+
const frameWidthTwips = finiteNumber(floating?.width);
|
|
11345
|
+
if (frameWidthTwips === void 0) return void 0;
|
|
11346
|
+
const text = typeof props.text === "string" ? props.text : "";
|
|
11347
|
+
if (text.trim() === "") return void 0;
|
|
11348
|
+
const font = asRecord(props.font);
|
|
11349
|
+
const fontSizePt = finiteNumber(font?.size);
|
|
11350
|
+
if (fontSizePt === void 0) return void 0;
|
|
11351
|
+
const longestWord = text.split(/\s+/).reduce(
|
|
11352
|
+
(longest, word) => word.length > longest.length ? word : longest,
|
|
11353
|
+
""
|
|
11354
|
+
);
|
|
11355
|
+
const offsetY = finiteNumber(asRecord(floating?.verticalPosition)?.offset);
|
|
11356
|
+
return {
|
|
11357
|
+
id: `docx:frame-text:${path4}`,
|
|
11358
|
+
kind: "docx/frame-text",
|
|
11359
|
+
path: path4,
|
|
11360
|
+
text,
|
|
11361
|
+
fontSizePt,
|
|
11362
|
+
lineHeightPt: lineHeightPt(fontSizePt, font?.lineSpacing),
|
|
11363
|
+
characterSpacingPt: characterSpacingPt(font?.characterSpacing),
|
|
11364
|
+
frameWidthTwips,
|
|
11365
|
+
...finiteNumber(floating?.height) !== void 0 && {
|
|
11366
|
+
frameHeightTwips: finiteNumber(floating?.height)
|
|
11367
|
+
},
|
|
11368
|
+
...offsetY !== void 0 && { offsetYTwips: offsetY },
|
|
11369
|
+
pageBottomTwips: page.pageBottomTwips,
|
|
11370
|
+
longestWord
|
|
11371
|
+
};
|
|
11372
|
+
}
|
|
11373
|
+
var SVG_VIEWBOX = /viewBox\s*=\s*"\s*(-?[\d.]+)[\s,]+(-?[\d.]+)[\s,]+(-?[\d.]+)[\s,]+(-?[\d.]+)\s*"/;
|
|
11374
|
+
var SVG_TEXT = /<text\b([^>]*)>([^<]*)<\/text>/g;
|
|
11375
|
+
var SVG_ATTR = (name) => new RegExp(`\\b${name}\\s*=\\s*"(-?[\\d.]+)"`);
|
|
11376
|
+
function svgTextFacts(props, path4) {
|
|
11377
|
+
const svg = typeof props.svg === "string" ? props.svg : void 0;
|
|
11378
|
+
if (!svg) return [];
|
|
11379
|
+
const box = SVG_VIEWBOX.exec(svg);
|
|
11380
|
+
if (!box) return [];
|
|
11381
|
+
const viewBoxMinY = Number(box[2]);
|
|
11382
|
+
const viewBoxHeight = Number(box[4]);
|
|
11383
|
+
if (!Number.isFinite(viewBoxMinY) || !Number.isFinite(viewBoxHeight)) {
|
|
11384
|
+
return [];
|
|
11385
|
+
}
|
|
11386
|
+
const facts = [];
|
|
11387
|
+
let match;
|
|
11388
|
+
SVG_TEXT.lastIndex = 0;
|
|
11389
|
+
let index = 0;
|
|
11390
|
+
while ((match = SVG_TEXT.exec(svg)) !== null) {
|
|
11391
|
+
const [, attributes, content] = match;
|
|
11392
|
+
const baselineY = Number(SVG_ATTR("y").exec(attributes)?.[1]);
|
|
11393
|
+
if (!Number.isFinite(baselineY)) {
|
|
11394
|
+
index += 1;
|
|
11395
|
+
continue;
|
|
11396
|
+
}
|
|
11397
|
+
const fontSizeUnits = Number(
|
|
11398
|
+
SVG_ATTR("font-size").exec(attributes)?.[1] ?? 0
|
|
11399
|
+
);
|
|
11400
|
+
facts.push({
|
|
11401
|
+
id: `docx:svg-text:${path4}:${index}`,
|
|
11402
|
+
kind: "docx/svg-text",
|
|
11403
|
+
path: `${path4}/props/svg`,
|
|
11404
|
+
content: content.trim(),
|
|
11405
|
+
baselineY,
|
|
11406
|
+
fontSizeUnits: Number.isFinite(fontSizeUnits) ? fontSizeUnits : 0,
|
|
11407
|
+
viewBoxMinY,
|
|
11408
|
+
viewBoxHeight
|
|
11409
|
+
});
|
|
11410
|
+
index += 1;
|
|
11411
|
+
}
|
|
11412
|
+
return facts;
|
|
11413
|
+
}
|
|
11414
|
+
function walkActive(node, path4, page, visit) {
|
|
11415
|
+
const rec = asRecord(node);
|
|
11416
|
+
if (!rec || rec.enabled === false) return;
|
|
11417
|
+
visit(rec, path4, page);
|
|
11418
|
+
const children = Array.isArray(rec.children) ? rec.children : [];
|
|
11419
|
+
children.forEach(
|
|
11420
|
+
(child, index) => walkActive(child, `${path4}/children/${index}`, page, visit)
|
|
11421
|
+
);
|
|
11422
|
+
}
|
|
11423
|
+
function prepareDocxQualityDocument(document, options = {}) {
|
|
11424
|
+
const context = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
|
|
11425
|
+
customThemes: options.customThemes,
|
|
11426
|
+
fonts: options.fonts,
|
|
11427
|
+
warnings: options.warnings
|
|
11428
|
+
});
|
|
11429
|
+
const resolved = resolveDocumentTree(context.document, context.theme);
|
|
11430
|
+
const basePage = pageBox(resolved.theme, context.themeName);
|
|
11431
|
+
const facts = [];
|
|
11432
|
+
const provenance = {};
|
|
11433
|
+
let previousHeadingLevel;
|
|
11434
|
+
const addFact = (fact) => {
|
|
11435
|
+
facts.push(fact);
|
|
11436
|
+
provenance[fact.id] = {
|
|
11437
|
+
path: fact.path,
|
|
11438
|
+
...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
|
|
11439
|
+
};
|
|
11440
|
+
};
|
|
11441
|
+
const visit = (node, path4, page) => {
|
|
11442
|
+
const props = asRecord(node.props) ?? {};
|
|
11443
|
+
if (node.name === "table") {
|
|
11444
|
+
const fact = tableFact(props, path4, page.availableWidthTwips);
|
|
11445
|
+
if (fact) addFact(fact);
|
|
11446
|
+
}
|
|
11447
|
+
if (node.name === "paragraph" || node.name === "text-box") {
|
|
11448
|
+
const fact = frameTextFact(props, path4, page);
|
|
11449
|
+
if (fact) addFact(fact);
|
|
11450
|
+
}
|
|
11451
|
+
if (node.name === "image" || node.name === "visual") {
|
|
11452
|
+
for (const fact of svgTextFacts(props, path4)) addFact(fact);
|
|
11453
|
+
}
|
|
11454
|
+
if (node.name === "heading") {
|
|
11455
|
+
const level = typeof props.level === "number" && Number.isFinite(props.level) ? props.level : 1;
|
|
11456
|
+
addFact({
|
|
11457
|
+
id: `docx:heading:${path4}`,
|
|
11458
|
+
kind: "docx/heading",
|
|
11459
|
+
path: `${path4}/props/level`,
|
|
11460
|
+
level,
|
|
11461
|
+
...previousHeadingLevel !== void 0 && {
|
|
11462
|
+
previousLevel: previousHeadingLevel
|
|
11463
|
+
}
|
|
11464
|
+
});
|
|
11465
|
+
previousHeadingLevel = level;
|
|
11466
|
+
}
|
|
11467
|
+
};
|
|
11468
|
+
resolved.children.forEach((component, index) => {
|
|
11469
|
+
const rec = component;
|
|
11470
|
+
const page = rec.name === "section" ? pageBox(resolved.theme, context.themeName, rec.props?.page) : basePage;
|
|
11471
|
+
walkActive(component, `/children/${index}`, page, visit);
|
|
11472
|
+
});
|
|
11473
|
+
return {
|
|
11474
|
+
format: "docx",
|
|
11475
|
+
model: {
|
|
11476
|
+
authored: document,
|
|
11477
|
+
context,
|
|
11478
|
+
document: resolved,
|
|
11479
|
+
themeName: context.themeName
|
|
11480
|
+
},
|
|
11481
|
+
facts,
|
|
11482
|
+
provenance,
|
|
11483
|
+
renderer: options.renderer ?? DEFAULT_DOCX_RENDERER_ID2
|
|
11484
|
+
};
|
|
11485
|
+
}
|
|
11486
|
+
|
|
11169
11487
|
// src/core/generateFromIr.ts
|
|
11170
11488
|
var UncompiledComponentError = class extends Error {
|
|
11171
11489
|
code = "UNCOMPILED_COMPONENT";
|
|
@@ -11193,11 +11511,14 @@ async function compileDocumentToIr(document, options = {}, collector) {
|
|
|
11193
11511
|
);
|
|
11194
11512
|
}
|
|
11195
11513
|
async function compileDocumentScoped(document, options, warnings) {
|
|
11196
|
-
const
|
|
11514
|
+
const prepared = options.prepared ?? prepareDocxQualityDocument(document, {
|
|
11197
11515
|
customThemes: options.customThemes,
|
|
11198
11516
|
fonts: options.fonts,
|
|
11199
|
-
warnings
|
|
11517
|
+
warnings,
|
|
11518
|
+
context: options.context,
|
|
11519
|
+
renderer: options.renderer
|
|
11200
11520
|
});
|
|
11521
|
+
const { context } = prepared.model;
|
|
11201
11522
|
const hasVisual = collectVisualProps(context.document).length > 0;
|
|
11202
11523
|
const resolvedFonts = await resolveDocumentFonts(
|
|
11203
11524
|
context.document,
|
|
@@ -11213,9 +11534,10 @@ async function compileDocumentScoped(document, options, warnings) {
|
|
|
11213
11534
|
...options.baseDir !== void 0 ? { baseDir: options.baseDir } : {},
|
|
11214
11535
|
...visualFonts.length > 0 ? { visualFonts } : {}
|
|
11215
11536
|
});
|
|
11216
|
-
const
|
|
11537
|
+
const resolved = desugared === context.document ? prepared.model.document : resolveDocumentTree(desugared, context.theme);
|
|
11538
|
+
const structure = await processResolvedDocument(
|
|
11217
11539
|
desugared,
|
|
11218
|
-
|
|
11540
|
+
resolved,
|
|
11219
11541
|
context.themeName,
|
|
11220
11542
|
resolveGenerationDate(options)
|
|
11221
11543
|
);
|
|
@@ -11380,7 +11702,8 @@ async function generateBufferWithWarnings(jsonConfig, options) {
|
|
|
11380
11702
|
...options?.baseDir !== void 0 ? { baseDir: options.baseDir } : {},
|
|
11381
11703
|
...options?.deterministic !== void 0 ? { deterministic: options.deterministic } : {},
|
|
11382
11704
|
...options?.generatedAt !== void 0 ? { generatedAt: options.generatedAt } : {},
|
|
11383
|
-
...renderer ? { renderer } : {}
|
|
11705
|
+
...renderer ? { renderer } : {},
|
|
11706
|
+
...options?.prepared ? { prepared: options.prepared } : {}
|
|
11384
11707
|
});
|
|
11385
11708
|
}
|
|
11386
11709
|
function parseDocument(jsonConfig) {
|
|
@@ -11521,6 +11844,411 @@ async function rasterizeVisual(obj, ctx) {
|
|
|
11521
11844
|
});
|
|
11522
11845
|
}
|
|
11523
11846
|
|
|
11847
|
+
// src/quality/preflight.ts
|
|
11848
|
+
import {
|
|
11849
|
+
assertValidQualityPolicy,
|
|
11850
|
+
assertValidQualityProfile
|
|
11851
|
+
} from "@json-to-office/quality";
|
|
11852
|
+
|
|
11853
|
+
// src/quality/rules.ts
|
|
11854
|
+
import {
|
|
11855
|
+
mergeQualityProfiles,
|
|
11856
|
+
QUALITY_CODES,
|
|
11857
|
+
QualityEngine
|
|
11858
|
+
} from "@json-to-office/quality";
|
|
11859
|
+
|
|
11860
|
+
// src/quality/text-metrics.ts
|
|
11861
|
+
var DEFAULT_ADVANCE = 556;
|
|
11862
|
+
var ADVANCE = {
|
|
11863
|
+
" ": 278,
|
|
11864
|
+
A: 667,
|
|
11865
|
+
B: 667,
|
|
11866
|
+
C: 722,
|
|
11867
|
+
D: 722,
|
|
11868
|
+
E: 667,
|
|
11869
|
+
F: 611,
|
|
11870
|
+
G: 778,
|
|
11871
|
+
H: 722,
|
|
11872
|
+
I: 278,
|
|
11873
|
+
J: 500,
|
|
11874
|
+
K: 667,
|
|
11875
|
+
L: 556,
|
|
11876
|
+
M: 833,
|
|
11877
|
+
N: 722,
|
|
11878
|
+
O: 778,
|
|
11879
|
+
P: 667,
|
|
11880
|
+
Q: 778,
|
|
11881
|
+
R: 722,
|
|
11882
|
+
S: 667,
|
|
11883
|
+
T: 611,
|
|
11884
|
+
U: 722,
|
|
11885
|
+
V: 667,
|
|
11886
|
+
W: 944,
|
|
11887
|
+
X: 667,
|
|
11888
|
+
Y: 667,
|
|
11889
|
+
Z: 611,
|
|
11890
|
+
a: 556,
|
|
11891
|
+
b: 556,
|
|
11892
|
+
c: 500,
|
|
11893
|
+
d: 556,
|
|
11894
|
+
e: 556,
|
|
11895
|
+
f: 278,
|
|
11896
|
+
g: 556,
|
|
11897
|
+
h: 556,
|
|
11898
|
+
i: 222,
|
|
11899
|
+
j: 222,
|
|
11900
|
+
k: 500,
|
|
11901
|
+
l: 222,
|
|
11902
|
+
m: 833,
|
|
11903
|
+
n: 556,
|
|
11904
|
+
o: 556,
|
|
11905
|
+
p: 556,
|
|
11906
|
+
q: 556,
|
|
11907
|
+
r: 333,
|
|
11908
|
+
s: 500,
|
|
11909
|
+
t: 278,
|
|
11910
|
+
u: 556,
|
|
11911
|
+
v: 500,
|
|
11912
|
+
w: 722,
|
|
11913
|
+
x: 500,
|
|
11914
|
+
y: 500,
|
|
11915
|
+
z: 500,
|
|
11916
|
+
".": 278,
|
|
11917
|
+
",": 278,
|
|
11918
|
+
":": 278,
|
|
11919
|
+
";": 278,
|
|
11920
|
+
"!": 278,
|
|
11921
|
+
"?": 556,
|
|
11922
|
+
"'": 191,
|
|
11923
|
+
'"': 355,
|
|
11924
|
+
"-": 333,
|
|
11925
|
+
"\u2013": 556,
|
|
11926
|
+
"\u2014": 1e3,
|
|
11927
|
+
"&": 667,
|
|
11928
|
+
"%": 889,
|
|
11929
|
+
"(": 333,
|
|
11930
|
+
")": 333,
|
|
11931
|
+
"/": 278,
|
|
11932
|
+
"@": 1015,
|
|
11933
|
+
"#": 556,
|
|
11934
|
+
"+": 584,
|
|
11935
|
+
"=": 584
|
|
11936
|
+
};
|
|
11937
|
+
function estimateTextWidthPt(text, fontSizePt, trackingPt = 0) {
|
|
11938
|
+
let units = 0;
|
|
11939
|
+
for (const character of text) {
|
|
11940
|
+
units += ADVANCE[character] ?? DEFAULT_ADVANCE;
|
|
11941
|
+
}
|
|
11942
|
+
const width = units / 1e3 * fontSizePt + text.length * trackingPt;
|
|
11943
|
+
return Math.max(0, width);
|
|
11944
|
+
}
|
|
11945
|
+
function estimateWrappedLines(text, widthPt, fontSizePt, trackingPt = 0) {
|
|
11946
|
+
if (widthPt <= 0) return 1;
|
|
11947
|
+
let lines = 0;
|
|
11948
|
+
for (const paragraph2 of text.split("\n")) {
|
|
11949
|
+
const words = paragraph2.split(/\s+/).filter(Boolean);
|
|
11950
|
+
if (words.length === 0) {
|
|
11951
|
+
lines += 1;
|
|
11952
|
+
continue;
|
|
11953
|
+
}
|
|
11954
|
+
let current = "";
|
|
11955
|
+
let used = 0;
|
|
11956
|
+
for (const word of words) {
|
|
11957
|
+
const candidate = current === "" ? word : `${current} ${word}`;
|
|
11958
|
+
if (estimateTextWidthPt(candidate, fontSizePt, trackingPt) <= widthPt) {
|
|
11959
|
+
current = candidate;
|
|
11960
|
+
} else {
|
|
11961
|
+
if (current !== "") used += 1;
|
|
11962
|
+
current = word;
|
|
11963
|
+
}
|
|
11964
|
+
}
|
|
11965
|
+
lines += used + (current === "" ? 0 : 1);
|
|
11966
|
+
}
|
|
11967
|
+
return Math.max(1, lines);
|
|
11968
|
+
}
|
|
11969
|
+
|
|
11970
|
+
// src/quality/rules.ts
|
|
11971
|
+
var WIDTH_TOLERANCE_TWIPS = 10;
|
|
11972
|
+
function numberParameter(parameters, name, fallback) {
|
|
11973
|
+
const value = parameters[name];
|
|
11974
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
11975
|
+
}
|
|
11976
|
+
var docxTableWidthRule = {
|
|
11977
|
+
id: "docx/table-width",
|
|
11978
|
+
code: QUALITY_CODES.TABLE_WIDTH_OVERFLOW,
|
|
11979
|
+
category: "integrity",
|
|
11980
|
+
defaultSeverity: "warning",
|
|
11981
|
+
defaultCertainty: "deterministic",
|
|
11982
|
+
formats: ["docx"],
|
|
11983
|
+
defaultParameters: { toleranceTwips: WIDTH_TOLERANCE_TWIPS },
|
|
11984
|
+
evaluate: ({ facts, configuration }) => {
|
|
11985
|
+
const toleranceTwips = numberParameter(
|
|
11986
|
+
configuration.parameters,
|
|
11987
|
+
"toleranceTwips",
|
|
11988
|
+
WIDTH_TOLERANCE_TWIPS
|
|
11989
|
+
);
|
|
11990
|
+
return facts.filter(
|
|
11991
|
+
(fact) => fact.kind === "docx/table-width"
|
|
11992
|
+
).filter(
|
|
11993
|
+
(fact) => fact.hasExplicitWidth && fact.totalWidthTwips > fact.availableWidthTwips + toleranceTwips
|
|
11994
|
+
).map((fact) => {
|
|
11995
|
+
const totalPt = Math.round(fact.totalWidthTwips / 20 * 10) / 10;
|
|
11996
|
+
const availablePt = Math.round(fact.availableWidthTwips / 20 * 10) / 10;
|
|
11997
|
+
const scale = fact.availableWidthTwips / fact.totalWidthTwips;
|
|
11998
|
+
const fixes = fact.allColumnsExplicit ? fact.explicitWidths.map(({ index, width }) => ({
|
|
11999
|
+
op: "replace",
|
|
12000
|
+
path: `${fact.path}/${index}/width`,
|
|
12001
|
+
value: typeof width === "number" ? Math.floor(width * scale * 10) / 10 : `${Math.floor(Number(width.trim().slice(0, -1)) * scale * 10) / 10}%`
|
|
12002
|
+
})) : [];
|
|
12003
|
+
return {
|
|
12004
|
+
message: `Column widths use ${totalPt}pt, but this section has ${availablePt}pt available \u2014 the table will spill off the right edge.`,
|
|
12005
|
+
path: fact.path,
|
|
12006
|
+
suggestion: "Reduce fixed/percentage widths, widen the page, or leave columns unsized so they share the remainder.",
|
|
12007
|
+
context: {
|
|
12008
|
+
totalWidthPt: totalPt,
|
|
12009
|
+
availableWidthPt: availablePt,
|
|
12010
|
+
pointSum: fact.pointSum,
|
|
12011
|
+
percentSum: fact.percentSum
|
|
12012
|
+
},
|
|
12013
|
+
evidence: {
|
|
12014
|
+
actual: totalPt,
|
|
12015
|
+
expected: availablePt,
|
|
12016
|
+
unit: "pt"
|
|
12017
|
+
},
|
|
12018
|
+
...fixes.length > 0 && { fixes }
|
|
12019
|
+
};
|
|
12020
|
+
});
|
|
12021
|
+
}
|
|
12022
|
+
};
|
|
12023
|
+
var docxHeadingHierarchyRule = {
|
|
12024
|
+
id: "docx/heading-hierarchy",
|
|
12025
|
+
code: QUALITY_CODES.HEADING_SKIP,
|
|
12026
|
+
category: "hierarchy",
|
|
12027
|
+
defaultSeverity: "info",
|
|
12028
|
+
defaultCertainty: "deterministic",
|
|
12029
|
+
formats: ["docx"],
|
|
12030
|
+
evaluate: ({ facts }) => facts.filter((fact) => fact.kind === "docx/heading").flatMap((fact) => {
|
|
12031
|
+
const previousLevel = fact.previousLevel;
|
|
12032
|
+
if (previousLevel === void 0 || fact.level <= previousLevel + 1) {
|
|
12033
|
+
return [];
|
|
12034
|
+
}
|
|
12035
|
+
return [
|
|
12036
|
+
{
|
|
12037
|
+
message: `Heading level ${fact.level} follows level ${previousLevel} \u2014 the skipped level breaks the document outline.`,
|
|
12038
|
+
path: fact.path,
|
|
12039
|
+
suggestion: `Use level ${previousLevel + 1}, or promote this heading's section.`,
|
|
12040
|
+
context: { level: fact.level, previousLevel },
|
|
12041
|
+
evidence: {
|
|
12042
|
+
actual: fact.level,
|
|
12043
|
+
expected: previousLevel + 1
|
|
12044
|
+
},
|
|
12045
|
+
// The fact path already addresses `.../props/level`; RFC 6902
|
|
12046
|
+
// `add` replaces an existing member, so this works whether the
|
|
12047
|
+
// level was explicit or defaulted.
|
|
12048
|
+
fixes: [
|
|
12049
|
+
{
|
|
12050
|
+
op: "add",
|
|
12051
|
+
path: fact.path,
|
|
12052
|
+
value: previousLevel + 1
|
|
12053
|
+
}
|
|
12054
|
+
]
|
|
12055
|
+
}
|
|
12056
|
+
];
|
|
12057
|
+
})
|
|
12058
|
+
};
|
|
12059
|
+
var WIDTH_MODEL_TOLERANCE = 1.08;
|
|
12060
|
+
var TWIPS_PER_POINT4 = 20;
|
|
12061
|
+
function frameTextFacts(facts) {
|
|
12062
|
+
return facts.filter(
|
|
12063
|
+
(fact) => fact.kind === "docx/frame-text"
|
|
12064
|
+
);
|
|
12065
|
+
}
|
|
12066
|
+
var docxTextFitRule = {
|
|
12067
|
+
id: "docx/text-fit",
|
|
12068
|
+
code: QUALITY_CODES.TEXT_OVERFLOW,
|
|
12069
|
+
category: "integrity",
|
|
12070
|
+
defaultSeverity: "warning",
|
|
12071
|
+
defaultCertainty: "estimated",
|
|
12072
|
+
formats: ["docx"],
|
|
12073
|
+
defaultParameters: { widthTolerance: WIDTH_MODEL_TOLERANCE },
|
|
12074
|
+
evaluate: ({ facts, configuration }) => {
|
|
12075
|
+
const tolerance = numberParameter(
|
|
12076
|
+
configuration.parameters,
|
|
12077
|
+
"widthTolerance",
|
|
12078
|
+
WIDTH_MODEL_TOLERANCE
|
|
12079
|
+
);
|
|
12080
|
+
const findings = [];
|
|
12081
|
+
for (const fact of frameTextFacts(facts)) {
|
|
12082
|
+
const frameWidthPt = fact.frameWidthTwips / TWIPS_PER_POINT4;
|
|
12083
|
+
const longestWordPt = estimateTextWidthPt(
|
|
12084
|
+
fact.longestWord,
|
|
12085
|
+
fact.fontSizePt,
|
|
12086
|
+
fact.characterSpacingPt
|
|
12087
|
+
);
|
|
12088
|
+
if (longestWordPt > frameWidthPt * tolerance) {
|
|
12089
|
+
findings.push({
|
|
12090
|
+
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.`,
|
|
12091
|
+
path: `${fact.path}/props/floating/width`,
|
|
12092
|
+
suggestion: "Widen the frame, reduce fontSize, or condense the run further.",
|
|
12093
|
+
context: {
|
|
12094
|
+
longestWord: fact.longestWord,
|
|
12095
|
+
estimatedWidthPt: Math.round(longestWordPt),
|
|
12096
|
+
frameWidthPt: Math.round(frameWidthPt),
|
|
12097
|
+
fontSizePt: fact.fontSizePt
|
|
12098
|
+
},
|
|
12099
|
+
evidence: {
|
|
12100
|
+
actual: Math.round(longestWordPt),
|
|
12101
|
+
expected: Math.round(frameWidthPt),
|
|
12102
|
+
unit: "pt"
|
|
12103
|
+
}
|
|
12104
|
+
});
|
|
12105
|
+
continue;
|
|
12106
|
+
}
|
|
12107
|
+
if (fact.offsetYTwips === void 0) continue;
|
|
12108
|
+
const lines = estimateWrappedLines(
|
|
12109
|
+
fact.text,
|
|
12110
|
+
frameWidthPt,
|
|
12111
|
+
fact.fontSizePt,
|
|
12112
|
+
fact.characterSpacingPt
|
|
12113
|
+
);
|
|
12114
|
+
const heightTwips = lines * fact.lineHeightPt * TWIPS_PER_POINT4;
|
|
12115
|
+
const bottomTwips = fact.offsetYTwips + heightTwips;
|
|
12116
|
+
const overrunTwips = bottomTwips - fact.pageBottomTwips;
|
|
12117
|
+
if (overrunTwips <= fact.lineHeightPt * TWIPS_PER_POINT4) continue;
|
|
12118
|
+
findings.push({
|
|
12119
|
+
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.`,
|
|
12120
|
+
path: `${fact.path}/props/floating/verticalPosition/offset`,
|
|
12121
|
+
suggestion: "Raise the frame, shorten the text, or reduce fontSize so the block finishes on the page.",
|
|
12122
|
+
context: {
|
|
12123
|
+
offsetYTwips: fact.offsetYTwips,
|
|
12124
|
+
estimatedHeightTwips: Math.round(heightTwips),
|
|
12125
|
+
pageBottomTwips: fact.pageBottomTwips,
|
|
12126
|
+
lines
|
|
12127
|
+
},
|
|
12128
|
+
evidence: {
|
|
12129
|
+
actual: Math.round(bottomTwips),
|
|
12130
|
+
expected: fact.pageBottomTwips,
|
|
12131
|
+
unit: "twips"
|
|
12132
|
+
}
|
|
12133
|
+
});
|
|
12134
|
+
}
|
|
12135
|
+
return findings;
|
|
12136
|
+
}
|
|
12137
|
+
};
|
|
12138
|
+
var docxSvgTextBoundsRule = {
|
|
12139
|
+
id: "docx/svg-text-bounds",
|
|
12140
|
+
code: QUALITY_CODES.SVG_TEXT_CLIPPED,
|
|
12141
|
+
category: "integrity",
|
|
12142
|
+
defaultSeverity: "warning",
|
|
12143
|
+
defaultCertainty: "deterministic",
|
|
12144
|
+
formats: ["docx"],
|
|
12145
|
+
evaluate: ({ facts }) => facts.filter((fact) => fact.kind === "docx/svg-text").flatMap((fact) => {
|
|
12146
|
+
const canvasBottom = fact.viewBoxMinY + fact.viewBoxHeight;
|
|
12147
|
+
if (fact.baselineY <= canvasBottom) return [];
|
|
12148
|
+
const overflow = Math.round((fact.baselineY - canvasBottom) * 10) / 10;
|
|
12149
|
+
return [
|
|
12150
|
+
{
|
|
12151
|
+
message: `SVG text "${fact.content}" sits ${overflow} units below the viewBox \u2014 it is clipped away and never reaches the document's text layer.`,
|
|
12152
|
+
path: fact.path,
|
|
12153
|
+
suggestion: `Move the baseline above ${Math.round(canvasBottom)}, or grow the viewBox height.`,
|
|
12154
|
+
context: {
|
|
12155
|
+
content: fact.content,
|
|
12156
|
+
baselineY: fact.baselineY,
|
|
12157
|
+
canvasBottom,
|
|
12158
|
+
overflowUnits: overflow
|
|
12159
|
+
},
|
|
12160
|
+
evidence: {
|
|
12161
|
+
actual: fact.baselineY,
|
|
12162
|
+
expected: canvasBottom,
|
|
12163
|
+
unit: "viewBox units"
|
|
12164
|
+
}
|
|
12165
|
+
}
|
|
12166
|
+
];
|
|
12167
|
+
})
|
|
12168
|
+
};
|
|
12169
|
+
var DOCX_QUALITY_RULES = {
|
|
12170
|
+
id: "docx/default",
|
|
12171
|
+
rules: [
|
|
12172
|
+
docxTableWidthRule,
|
|
12173
|
+
docxHeadingHierarchyRule,
|
|
12174
|
+
docxTextFitRule,
|
|
12175
|
+
docxSvgTextBoundsRule
|
|
12176
|
+
]
|
|
12177
|
+
};
|
|
12178
|
+
var DOCX_QUALITY_PROFILES = {
|
|
12179
|
+
"executive-report": {
|
|
12180
|
+
id: "executive-report",
|
|
12181
|
+
formats: ["docx"],
|
|
12182
|
+
description: "Short decision document with strict outline continuity.",
|
|
12183
|
+
rules: {
|
|
12184
|
+
"docx/heading-hierarchy": { severity: "warning" }
|
|
12185
|
+
}
|
|
12186
|
+
},
|
|
12187
|
+
"technical-report": {
|
|
12188
|
+
id: "technical-report",
|
|
12189
|
+
formats: ["docx"],
|
|
12190
|
+
description: "Portable professional report defaults."
|
|
12191
|
+
},
|
|
12192
|
+
"legal-appendix": {
|
|
12193
|
+
id: "legal-appendix",
|
|
12194
|
+
formats: ["docx"],
|
|
12195
|
+
description: "Dense appendix: preserve integrity without editorial taste."
|
|
12196
|
+
}
|
|
12197
|
+
};
|
|
12198
|
+
var DOCX_DEFAULT_QUALITY_PROFILE = DOCX_QUALITY_PROFILES["technical-report"];
|
|
12199
|
+
var DOCX_PROFILES_BY_ID = DOCX_QUALITY_PROFILES;
|
|
12200
|
+
function resolveDocxQualityProfile(requested) {
|
|
12201
|
+
if (!requested) return void 0;
|
|
12202
|
+
const registered = DOCX_PROFILES_BY_ID[requested.id];
|
|
12203
|
+
if (!registered) return requested;
|
|
12204
|
+
return mergeQualityProfiles(registered, requested);
|
|
12205
|
+
}
|
|
12206
|
+
var docxQualityEngine = new QualityEngine(DOCX_QUALITY_RULES.rules);
|
|
12207
|
+
|
|
12208
|
+
// src/quality/preflight.ts
|
|
12209
|
+
var PREPARE_RULE_ID = "quality/prepare";
|
|
12210
|
+
function emptyAnalysis(ruleErrors = [], blocked = false) {
|
|
12211
|
+
return {
|
|
12212
|
+
diagnostics: [],
|
|
12213
|
+
counts: { error: 0, warning: 0, info: 0 },
|
|
12214
|
+
blocked,
|
|
12215
|
+
truncated: false,
|
|
12216
|
+
suppressedCount: 0,
|
|
12217
|
+
evaluatedRuleIds: [],
|
|
12218
|
+
ruleErrors
|
|
12219
|
+
};
|
|
12220
|
+
}
|
|
12221
|
+
function analyzeDocxQuality(doc, options = {}) {
|
|
12222
|
+
assertValidQualityPolicy(options.policy);
|
|
12223
|
+
assertValidQualityProfile(options.profile);
|
|
12224
|
+
if (typeof doc !== "object" || doc === null || Array.isArray(doc) || doc.name !== "docx") {
|
|
12225
|
+
return emptyAnalysis();
|
|
12226
|
+
}
|
|
12227
|
+
let prepared = options.prepared;
|
|
12228
|
+
try {
|
|
12229
|
+
prepared ??= prepareDocxQualityDocument(
|
|
12230
|
+
doc,
|
|
12231
|
+
options
|
|
12232
|
+
);
|
|
12233
|
+
} catch (error) {
|
|
12234
|
+
if (options.policy?.onRuleError === "throw") throw error;
|
|
12235
|
+
const gate = options.policy?.gate;
|
|
12236
|
+
return emptyAnalysis(
|
|
12237
|
+
[
|
|
12238
|
+
{
|
|
12239
|
+
ruleId: PREPARE_RULE_ID,
|
|
12240
|
+
message: error instanceof Error ? error.message : String(error)
|
|
12241
|
+
}
|
|
12242
|
+
],
|
|
12243
|
+
gate !== void 0 && gate !== "none"
|
|
12244
|
+
);
|
|
12245
|
+
}
|
|
12246
|
+
return docxQualityEngine.analyzeSync(prepared, {
|
|
12247
|
+
profile: resolveDocxQualityProfile(options.profile) ?? DOCX_DEFAULT_QUALITY_PROFILE,
|
|
12248
|
+
policy: options.policy
|
|
12249
|
+
});
|
|
12250
|
+
}
|
|
12251
|
+
|
|
11524
12252
|
// src/utils/warningsDocument.ts
|
|
11525
12253
|
function generateWarningsDocument(warnings) {
|
|
11526
12254
|
if (!warnings || warnings.length === 0) {
|
|
@@ -12447,18 +13175,24 @@ export {
|
|
|
12447
13175
|
ComponentValidationError2 as ComponentValidationError,
|
|
12448
13176
|
DocumentGenerator as CoreDocumentGenerator,
|
|
12449
13177
|
DEFAULT_DOCX_RENDERER_ID,
|
|
13178
|
+
DOCX_DEFAULT_QUALITY_PROFILE,
|
|
13179
|
+
DOCX_QUALITY_PROFILES,
|
|
13180
|
+
DOCX_QUALITY_RULES,
|
|
12450
13181
|
ThemeFileError,
|
|
12451
13182
|
ThemeParseError,
|
|
12452
13183
|
ThemeValidationError,
|
|
12453
13184
|
UncompiledComponentError,
|
|
12454
13185
|
UnknownPreservedComponentError,
|
|
13186
|
+
analyzeDocxQuality,
|
|
12455
13187
|
cleanComponentProps,
|
|
12456
13188
|
corporateTheme,
|
|
12457
13189
|
createComponent,
|
|
12458
13190
|
createDocumentGenerator,
|
|
12459
13191
|
createMinimalTheme,
|
|
12460
13192
|
createVersion,
|
|
13193
|
+
docxQualityEngine,
|
|
12461
13194
|
docxRendererIds,
|
|
13195
|
+
docxRendererStatuses,
|
|
12462
13196
|
examples,
|
|
12463
13197
|
exportPluginSchema,
|
|
12464
13198
|
exportThemeToJson,
|
|
@@ -12499,7 +13233,9 @@ export {
|
|
|
12499
13233
|
mergeSchemas,
|
|
12500
13234
|
minimalTheme,
|
|
12501
13235
|
modernTheme,
|
|
13236
|
+
prepareDocxQualityDocument,
|
|
12502
13237
|
resetVisualPrepassStats,
|
|
13238
|
+
resolveDocxQualityProfile,
|
|
12503
13239
|
runExample,
|
|
12504
13240
|
themes,
|
|
12505
13241
|
validateComponentProps,
|