@json-to-office/core-docx 2.7.0 → 3.1.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.
@@ -8702,641 +8702,6 @@ function emptyToUndefined(formatting) {
8702
8702
  return entries.length === 0 ? void 0 : Object.fromEntries(entries);
8703
8703
  }
8704
8704
 
8705
- // src/blocks/index.ts
8706
- import {
8707
- COVER_BUDGET,
8708
- KEY_TAKEAWAYS_BUDGET,
8709
- RUNNING_HEAD_BUDGET,
8710
- SECTION_OPENER_BUDGET
8711
- } from "@json-to-office/shared-docx";
8712
-
8713
- // src/blocks/cover.ts
8714
- init_widthUtils();
8715
-
8716
- // src/blocks/recipe.ts
8717
- var MIN_RULE_PT = 0.25;
8718
- var MAX_RULE_PT = 12;
8719
- function clampRule(weightPt) {
8720
- return Math.min(MAX_RULE_PT, Math.max(MIN_RULE_PT, weightPt));
8721
- }
8722
- var FALLBACK_EYEBROW_FONT = {
8723
- size: 9,
8724
- bold: true,
8725
- color: "accent",
8726
- case: "upper"
8727
- };
8728
- function hasStyle(theme, role) {
8729
- const styles = theme.styles;
8730
- return styles?.[role] !== void 0;
8731
- }
8732
- function roleProps(theme, role, fallbackFont, color) {
8733
- if (hasStyle(theme, role)) {
8734
- return {
8735
- themeStyle: role,
8736
- ...color !== void 0 && { font: { color } }
8737
- };
8738
- }
8739
- return { font: { ...fallbackFont, ...color !== void 0 && { color } } };
8740
- }
8741
-
8742
- // src/blocks/cover.ts
8743
- var DROP_SHARE = 0.3;
8744
- var DROP_SHARE_WITH_LOGO = 0.22;
8745
- var DEFAULT_LOGO_WIDTH = "25%";
8746
- var FALLBACK = {
8747
- ruleWeightPt: 3,
8748
- ruleColor: "accent",
8749
- padPt: 12,
8750
- metaFont: { size: 9, color: "textSecondary" }
8751
- };
8752
- function compileCover(props, theme) {
8753
- const recipe = theme.chrome?.cover;
8754
- const ruleWeightPt = recipe?.rule?.weightPt ?? FALLBACK.ruleWeightPt;
8755
- const padPt = recipe?.padPt ?? FALLBACK.padPt;
8756
- const heightPt = getAvailableHeightTwips(theme) / 20;
8757
- const dropPt = Math.round(
8758
- heightPt * (props.logo ? DROP_SHARE_WITH_LOGO : DROP_SHARE)
8759
- );
8760
- const children = [];
8761
- const sourceMap = {};
8762
- const emit = (child, slots) => {
8763
- const index = children.push(child) - 1;
8764
- sourceMap[`/children/${index}`] = "";
8765
- for (const [emitted, authored] of Object.entries(slots)) {
8766
- sourceMap[`/children/${index}${emitted}`] = authored;
8767
- }
8768
- };
8769
- if (props.logo) {
8770
- const { width, height, ...source } = props.logo;
8771
- emit(
8772
- {
8773
- name: "image",
8774
- props: {
8775
- ...source,
8776
- width: width ?? DEFAULT_LOGO_WIDTH,
8777
- ...height !== void 0 && { height },
8778
- alignment: theme.chrome?.logoSlot?.alignment ?? "left",
8779
- spacing: { before: 0, after: 0 }
8780
- }
8781
- },
8782
- { "/props": "/props/logo" }
8783
- );
8784
- }
8785
- const drawsRule = ruleWeightPt > 0;
8786
- if (drawsRule) {
8787
- emit(
8788
- {
8789
- name: "divider",
8790
- props: {
8791
- thickness: clampRule(ruleWeightPt),
8792
- color: recipe?.rule?.color ?? FALLBACK.ruleColor,
8793
- spacing: { before: dropPt, after: padPt }
8794
- }
8795
- },
8796
- {}
8797
- );
8798
- }
8799
- let dropPending = !drawsRule;
8800
- const before = () => {
8801
- if (!dropPending) return {};
8802
- dropPending = false;
8803
- return { before: dropPt };
8804
- };
8805
- if (props.client) {
8806
- emit(
8807
- {
8808
- name: "paragraph",
8809
- props: {
8810
- text: props.client,
8811
- keepNext: true,
8812
- spacing: { ...before(), after: 4 },
8813
- ...roleProps(theme, "eyebrow", FALLBACK_EYEBROW_FONT)
8814
- }
8815
- },
8816
- { "/props/text": "/props/client" }
8817
- );
8818
- }
8819
- const titleRole = recipe?.type && hasStyle(theme, recipe.type) ? recipe.type : "title";
8820
- emit(
8821
- {
8822
- name: "paragraph",
8823
- props: {
8824
- text: props.title,
8825
- keepNext: true,
8826
- ...dropPending && { spacing: before() },
8827
- ...roleProps(theme, titleRole, { size: 26, bold: true }, recipe?.color)
8828
- }
8829
- },
8830
- { "/props/text": "/props/title" }
8831
- );
8832
- if (props.subtitle) {
8833
- emit(
8834
- {
8835
- name: "paragraph",
8836
- props: {
8837
- text: props.subtitle,
8838
- keepNext: true,
8839
- ...roleProps(theme, "subtitle", { size: 13, color: "textSecondary" })
8840
- }
8841
- },
8842
- { "/props/text": "/props/subtitle" }
8843
- );
8844
- }
8845
- const meta = [props.date, props.confidentiality].filter(
8846
- (part) => typeof part === "string" && part !== ""
8847
- );
8848
- if (meta.length > 0) {
8849
- emit(
8850
- {
8851
- name: "paragraph",
8852
- props: {
8853
- text: meta.join(" \xB7 "),
8854
- spacing: { before: padPt, after: 0 },
8855
- ...roleProps(theme, "label", FALLBACK.metaFont)
8856
- }
8857
- },
8858
- {
8859
- "/props/text": props.date ? "/props/date" : "/props/confidentiality"
8860
- }
8861
- );
8862
- }
8863
- return { children, sourceMap };
8864
- }
8865
-
8866
- // src/blocks/keyTakeaways.ts
8867
- var KEY_TAKEAWAYS_DEFAULT_LABEL = "Key takeaways";
8868
- var FALLBACK_RECIPE = {
8869
- ruleWeightPt: 1.5,
8870
- ruleColor: "accent",
8871
- padPt: 6,
8872
- type: "label"
8873
- };
8874
- function compileKeyTakeaways(props, theme) {
8875
- const recipe = theme.chrome?.keyTakeaways;
8876
- const labelRole = recipe?.type ?? FALLBACK_RECIPE.type;
8877
- const labelStyle = hasStyle(theme, labelRole);
8878
- const ruleWeightPt = clampRule(
8879
- recipe?.rule?.weightPt ?? FALLBACK_RECIPE.ruleWeightPt
8880
- );
8881
- const ruleColor = recipe?.rule?.color ?? recipe?.color ?? FALLBACK_RECIPE.ruleColor;
8882
- const padPt = recipe?.padPt ?? FALLBACK_RECIPE.padPt;
8883
- const children = [
8884
- {
8885
- name: "divider",
8886
- props: {
8887
- thickness: ruleWeightPt,
8888
- color: ruleColor,
8889
- spacing: { before: 12, after: padPt }
8890
- }
8891
- },
8892
- {
8893
- name: "paragraph",
8894
- props: {
8895
- text: props.label ?? KEY_TAKEAWAYS_DEFAULT_LABEL,
8896
- keepNext: true,
8897
- spacing: { after: 4 },
8898
- // The label role when the theme resolves one; a bold run in the
8899
- // recipe's colour otherwise, so the label still reads as a label.
8900
- ...labelStyle ? { themeStyle: labelRole } : {
8901
- font: {
8902
- bold: true,
8903
- ...recipe?.color !== void 0 && { color: recipe.color }
8904
- }
8905
- }
8906
- }
8907
- },
8908
- {
8909
- name: "list",
8910
- props: { items: [...props.items] }
8911
- },
8912
- {
8913
- name: "divider",
8914
- props: {
8915
- thickness: 0.5,
8916
- color: "borderPrimary",
8917
- spacing: { before: padPt, after: 12 }
8918
- }
8919
- }
8920
- ];
8921
- return {
8922
- children,
8923
- sourceMap: {
8924
- "/children/0": "",
8925
- "/children/1": "",
8926
- "/children/1/props/text": "/props/label",
8927
- "/children/2": "",
8928
- "/children/2/props/items": "/props/items",
8929
- "/children/3": ""
8930
- }
8931
- };
8932
- }
8933
-
8934
- // src/blocks/runningHead.ts
8935
- init_styles();
8936
- init_widthUtils();
8937
- var FALLBACK2 = {
8938
- headFont: { size: 8, color: "textMuted", case: "upper" },
8939
- footFont: { size: 8, color: "textMuted" },
8940
- ruleWeightPt: 0.5,
8941
- ruleColor: "border"
8942
- };
8943
- var PAGE_OF_TOTAL = "{PAGE} / {TOTAL_PAGES}";
8944
- function sectionMeasureTwips(theme, page) {
8945
- if (!page || page.size === void 0 && page.margins === void 0) {
8946
- return getAvailableWidthTwips(theme);
8947
- }
8948
- const width = page.size ? getPageDimensions(page.size).width : getPageDimensions(theme.page.size).width;
8949
- const margins = getDocumentMargins(theme);
8950
- const left = page.margins?.left ?? margins.left ?? 1440;
8951
- const right = page.margins?.right ?? margins.right ?? 1440;
8952
- return Math.max(0, width - left - right);
8953
- }
8954
- function compileRunningHead(props, theme, scope) {
8955
- const measure = sectionMeasureTwips(theme, scope.page);
8956
- const head = theme.chrome?.runningHead;
8957
- const foot = theme.chrome?.confidentialFooter;
8958
- const slot = (name) => props[name] !== void 0 ? `${scope.block}/props/${name}` : void 0;
8959
- const header = [];
8960
- const headerMap = {};
8961
- const title = props.title ?? scope.documentTitle ?? "";
8962
- const tracker = scope.opener?.text ?? props.tracker ?? "";
8963
- if (title !== "" || tracker !== "") {
8964
- const both = title !== "" && tracker !== "";
8965
- header.push({
8966
- name: "paragraph",
8967
- props: {
8968
- text: both ? `${title} ${tracker}` : title || tracker,
8969
- ...both ? { tabStops: [{ type: "right", position: measure }] } : { alignment: title !== "" ? "left" : head?.alignment ?? "right" },
8970
- spacing: { before: 0, after: 0 },
8971
- ...roleProps(
8972
- theme,
8973
- head?.type ?? "tracker",
8974
- FALLBACK2.headFont,
8975
- head?.color
8976
- )
8977
- }
8978
- });
8979
- headerMap["/0"] = scope.block;
8980
- headerMap["/0/props/text"] = (tracker !== "" ? scope.opener?.slot ?? slot("tracker") : slot("title")) ?? scope.block;
8981
- }
8982
- const headRule = head?.rule?.weightPt ?? FALLBACK2.ruleWeightPt;
8983
- if (headRule > 0) {
8984
- header.push({
8985
- name: "divider",
8986
- props: {
8987
- thickness: clampRule(headRule),
8988
- color: head?.rule?.color ?? FALLBACK2.ruleColor,
8989
- spacing: { before: 2, after: 0 }
8990
- }
8991
- });
8992
- headerMap[`/${header.length - 1}`] = scope.block;
8993
- }
8994
- const footer = [];
8995
- const footerMap = {};
8996
- const footRule = foot?.rule?.weightPt ?? FALLBACK2.ruleWeightPt;
8997
- if (footRule > 0) {
8998
- footer.push({
8999
- name: "divider",
9000
- props: {
9001
- thickness: clampRule(footRule),
9002
- color: foot?.rule?.color ?? FALLBACK2.ruleColor,
9003
- spacing: { before: 0, after: 4 }
9004
- }
9005
- });
9006
- footerMap["/0"] = scope.block;
9007
- }
9008
- const parts = [
9009
- props.confidentiality ?? "",
9010
- props.pageNumbers === false ? "" : PAGE_OF_TOTAL,
9011
- props.date ?? ""
9012
- ];
9013
- const present = parts.filter((part) => part !== "");
9014
- if (present.length > 0) {
9015
- const single = present.length === 1;
9016
- const alignment2 = present[0] === PAGE_OF_TOTAL ? "center" : foot?.alignment ?? "left";
9017
- footer.push({
9018
- name: "paragraph",
9019
- props: {
9020
- text: single ? present[0] : parts.join(" ").replace(/\t+$/, ""),
9021
- ...single ? { alignment: alignment2 } : {
9022
- tabStops: [
9023
- { type: "center", position: Math.round(measure / 2) },
9024
- { type: "right", position: measure }
9025
- ]
9026
- },
9027
- spacing: { before: 0, after: 0 },
9028
- ...roleProps(
9029
- theme,
9030
- foot?.type ?? "footer",
9031
- FALLBACK2.footFont,
9032
- foot?.color
9033
- )
9034
- }
9035
- });
9036
- const index = footer.length - 1;
9037
- footerMap[`/${index}`] = scope.block;
9038
- footerMap[`/${index}/props/text`] = slot("confidentiality") ?? slot("date") ?? scope.block;
9039
- }
9040
- return {
9041
- header: { children: header, sourceMap: headerMap },
9042
- footer: { children: footer, sourceMap: footerMap }
9043
- };
9044
- }
9045
-
9046
- // src/blocks/sectionOpener.ts
9047
- function compileSectionOpener(props, theme) {
9048
- const children = [];
9049
- const sourceMap = {};
9050
- const pageBreak = props.pageBreak === true;
9051
- if (props.number !== void 0) {
9052
- children.push({
9053
- name: "paragraph",
9054
- props: {
9055
- text: String(props.number),
9056
- keepNext: true,
9057
- spacing: { after: 2 },
9058
- ...pageBreak && { pageBreak: true },
9059
- ...roleProps(theme, "eyebrow", FALLBACK_EYEBROW_FONT)
9060
- }
9061
- });
9062
- sourceMap["/children/0"] = "";
9063
- sourceMap["/children/0/props/text"] = "/props/number";
9064
- }
9065
- const index = children.length;
9066
- children.push({
9067
- name: "heading",
9068
- props: {
9069
- text: props.title,
9070
- level: 1,
9071
- ...pageBreak && index === 0 && { pageBreak: true }
9072
- }
9073
- });
9074
- sourceMap[`/children/${index}`] = "";
9075
- sourceMap[`/children/${index}/props/text`] = "/props/title";
9076
- return { children, sourceMap };
9077
- }
9078
- function sectionTracker(section2) {
9079
- if (!Array.isArray(section2.children)) return void 0;
9080
- for (const [index, child] of section2.children.entries()) {
9081
- if (typeof child !== "object" || child === null || child.name !== "section-opener" || child.enabled === false) {
9082
- continue;
9083
- }
9084
- const props = child.props;
9085
- if (!props) continue;
9086
- if (typeof props.tracker === "string" && props.tracker !== "") {
9087
- return { text: props.tracker, slot: `/children/${index}/props/tracker` };
9088
- }
9089
- if (typeof props.title === "string" && props.title !== "") {
9090
- return { text: props.title, slot: `/children/${index}/props/title` };
9091
- }
9092
- }
9093
- return void 0;
9094
- }
9095
-
9096
- // src/blocks/index.ts
9097
- var BLOCK_NAMES = [
9098
- "key-takeaways",
9099
- "cover",
9100
- "section-opener",
9101
- "running-head"
9102
- ];
9103
- function slotBudget(block2, props, pointer, slot, maxWords) {
9104
- const value = props[slot];
9105
- return typeof value === "string" ? [
9106
- {
9107
- block: block2,
9108
- slot,
9109
- path: `${pointer}/props/${slot}`,
9110
- words: wordCount(value),
9111
- maxWords
9112
- }
9113
- ] : [];
9114
- }
9115
- function budgetsOf(block2, budget) {
9116
- return (props, pointer) => Object.entries(budget).flatMap(
9117
- ([slot, { maxWords }]) => slotBudget(block2, props, pointer, slot, maxWords)
9118
- );
9119
- }
9120
- var BLOCKS = {
9121
- "key-takeaways": {
9122
- kind: "flow",
9123
- compile: (props, theme) => compileKeyTakeaways(props, theme),
9124
- budgets: (props, pointer) => (Array.isArray(props.items) ? props.items : []).flatMap(
9125
- (item, index) => typeof item === "string" ? [
9126
- {
9127
- block: "key-takeaways",
9128
- slot: "items",
9129
- path: `${pointer}/props/items/${index}`,
9130
- words: wordCount(item),
9131
- maxWords: KEY_TAKEAWAYS_BUDGET.items.maxWords
9132
- }
9133
- ] : []
9134
- )
9135
- },
9136
- cover: {
9137
- kind: "flow",
9138
- compile: (props, theme) => compileCover(props, theme),
9139
- budgets: budgetsOf("cover", COVER_BUDGET)
9140
- },
9141
- "section-opener": {
9142
- kind: "flow",
9143
- compile: (props, theme) => compileSectionOpener(props, theme),
9144
- budgets: budgetsOf("section-opener", SECTION_OPENER_BUDGET)
9145
- },
9146
- "running-head": {
9147
- kind: "chrome",
9148
- budgets: budgetsOf("running-head", RUNNING_HEAD_BUDGET)
9149
- }
9150
- };
9151
- function isBlockName(name) {
9152
- return BLOCK_NAMES.includes(name);
9153
- }
9154
- function isChromeBlockName(name) {
9155
- return isBlockName(name) && BLOCKS[name].kind === "chrome";
9156
- }
9157
- function isRecord(value) {
9158
- return typeof value === "object" && value !== null && !Array.isArray(value);
9159
- }
9160
- var SECTION_CHILD = /^\/children\/\d+\/children\/\d+$/;
9161
- var SECTION = /^\/children\/(\d+)$/;
9162
- function planChrome(document, theme) {
9163
- const plans = /* @__PURE__ */ new Map();
9164
- if (!isRecord(document) || document.name !== "docx" || !Array.isArray(document.children)) {
9165
- return plans;
9166
- }
9167
- const metadata = isRecord(document.props) ? document.props.metadata : void 0;
9168
- const documentTitle = isRecord(metadata) && typeof metadata.title === "string" ? metadata.title : void 0;
9169
- let inForce;
9170
- document.children.forEach((section2, index) => {
9171
- if (!isRecord(section2) || section2.name !== "section" || section2.enabled === false) {
9172
- return;
9173
- }
9174
- const sectionPointer = `/children/${index}`;
9175
- const children = Array.isArray(section2.children) ? section2.children : [];
9176
- children.forEach((child, childIndex) => {
9177
- if (isRecord(child) && isChromeBlockName(child.name) && child.enabled !== false) {
9178
- inForce = {
9179
- block: `${sectionPointer}/children/${childIndex}`,
9180
- props: isRecord(child.props) ? child.props : {}
9181
- };
9182
- }
9183
- });
9184
- if (!inForce) return;
9185
- const opener = sectionTracker(section2);
9186
- const sectionProps = isRecord(section2.props) ? section2.props : {};
9187
- plans.set(
9188
- index,
9189
- compileRunningHead(inForce.props, theme, {
9190
- block: inForce.block,
9191
- documentTitle,
9192
- ...opener && {
9193
- opener: {
9194
- text: opener.text,
9195
- slot: `${sectionPointer}${opener.slot}`
9196
- }
9197
- },
9198
- ...isRecord(sectionProps.page) && {
9199
- page: sectionProps.page
9200
- }
9201
- })
9202
- );
9203
- });
9204
- return plans;
9205
- }
9206
- function expandBlocks(document, theme) {
9207
- const sourceMap = {};
9208
- const blocks = [];
9209
- const chrome = planChrome(document, theme);
9210
- const walk2 = (node, pointer) => {
9211
- if (Array.isArray(node)) {
9212
- return node.map((child, index) => walk2(child, `${pointer}/${index}`));
9213
- }
9214
- if (!isRecord(node)) return node;
9215
- if (typeof node.name === "string" && node.enabled !== false && isBlockName(node.name)) {
9216
- const definition = BLOCKS[node.name];
9217
- if (definition.kind === "chrome") {
9218
- if (!SECTION_CHILD.test(pointer)) return node;
9219
- blocks.push(pointer);
9220
- return { ...node, children: [] };
9221
- }
9222
- if (isRecord(node.props)) {
9223
- const compiled = definition.compile(node.props, theme);
9224
- blocks.push(pointer);
9225
- for (const [emitted, authored] of Object.entries(compiled.sourceMap)) {
9226
- sourceMap[`${pointer}${emitted}`] = `${pointer}${authored}`;
9227
- }
9228
- return { ...node, children: compiled.children };
9229
- }
9230
- }
9231
- const next = { ...node };
9232
- if (isRecord(node.props)) {
9233
- const props = { ...node.props };
9234
- let changed = false;
9235
- for (const key of ["header", "footer"]) {
9236
- if (Array.isArray(props[key])) {
9237
- props[key] = walk2(props[key], `${pointer}/props/${key}`);
9238
- changed = true;
9239
- }
9240
- }
9241
- if (Array.isArray(props.columns)) {
9242
- props.columns = props.columns.map((column, index) => {
9243
- if (!isRecord(column)) return column;
9244
- const base = `${pointer}/props/columns/${index}`;
9245
- const out = { ...column };
9246
- if (isRecord(column.header) && "content" in column.header) {
9247
- out.header = {
9248
- ...column.header,
9249
- content: walk2(column.header.content, `${base}/header/content`)
9250
- };
9251
- }
9252
- if (Array.isArray(column.cells)) {
9253
- out.cells = column.cells.map(
9254
- (cell, cellIndex) => isRecord(cell) && "content" in cell ? {
9255
- ...cell,
9256
- content: walk2(
9257
- cell.content,
9258
- `${base}/cells/${cellIndex}/content`
9259
- )
9260
- } : cell
9261
- );
9262
- }
9263
- return out;
9264
- });
9265
- changed = true;
9266
- }
9267
- if (changed) next.props = props;
9268
- }
9269
- if (Array.isArray(node.children)) {
9270
- next.children = walk2(node.children, `${pointer}/children`);
9271
- }
9272
- const section2 = SECTION.exec(pointer);
9273
- if (section2 && node.name === "section" && node.enabled !== false) {
9274
- const plan = chrome.get(Number(section2[1]));
9275
- if (plan) {
9276
- const props = isRecord(next.props) ? { ...next.props } : {};
9277
- let changed = false;
9278
- for (const part of ["header", "footer"]) {
9279
- const { children, sourceMap: partMap } = plan[part];
9280
- if (props[part] !== void 0 || children.length === 0) continue;
9281
- props[part] = children;
9282
- changed = true;
9283
- const base = `${pointer}/props/${part}`;
9284
- sourceMap[base] = partMap["/0"] ?? pointer;
9285
- for (const [emitted, authored] of Object.entries(partMap)) {
9286
- sourceMap[`${base}${emitted}`] = authored;
9287
- }
9288
- }
9289
- if (props.pageBreak === void 0) {
9290
- props.pageBreak = true;
9291
- changed = true;
9292
- }
9293
- if (changed) next.props = props;
9294
- }
9295
- }
9296
- return next;
9297
- };
9298
- const expanded = walk2(document, "");
9299
- return {
9300
- document: blocks.length > 0 ? expanded : document,
9301
- sourceMap,
9302
- blocks
9303
- };
9304
- }
9305
- function toAuthoredPointer(sourceMap, pointer) {
9306
- let best;
9307
- for (const emitted of Object.keys(sourceMap)) {
9308
- if ((pointer === emitted || pointer.startsWith(`${emitted}/`)) && (best === void 0 || emitted.length > best.length)) {
9309
- best = emitted;
9310
- }
9311
- }
9312
- if (best === void 0) return pointer;
9313
- return `${sourceMap[best]}${pointer.slice(best.length)}`;
9314
- }
9315
- function blockSlotBudgets(document, blocks) {
9316
- return blocks.flatMap((pointer) => {
9317
- const node = nodeAt(document, pointer);
9318
- if (!isRecord(node) || !isRecord(node.props) || !isBlockName(node.name)) {
9319
- return [];
9320
- }
9321
- return BLOCKS[node.name].budgets(node.props, pointer);
9322
- });
9323
- }
9324
- function wordCount(text) {
9325
- const trimmed = text.trim();
9326
- return trimmed === "" ? 0 : trimmed.split(/\s+/).length;
9327
- }
9328
- function nodeAt(root, pointer) {
9329
- if (pointer === "") return root;
9330
- let current = root;
9331
- for (const segment of pointer.slice(1).split("/")) {
9332
- const key = segment.replace(/~1/g, "/").replace(/~0/g, "~");
9333
- if (Array.isArray(current)) current = current[Number(key)];
9334
- else if (isRecord(current)) current = current[key];
9335
- else return void 0;
9336
- }
9337
- return current;
9338
- }
9339
-
9340
8705
  // src/ir/compiler.ts
9341
8706
  init_types();
9342
8707
  init_units();
@@ -9762,7 +9127,7 @@ function compileComponent(component, scope) {
9762
9127
  case "table":
9763
9128
  return compileTable(component, scope);
9764
9129
  default:
9765
- if (isBlockName(component.name)) {
9130
+ if (component.name === "group") {
9766
9131
  if (component.enabled === false) return [];
9767
9132
  return compileBlock(component, scope);
9768
9133
  }
@@ -12805,6 +12170,204 @@ function normalizeDocument(document) {
12805
12170
  return [normalized];
12806
12171
  }
12807
12172
 
12173
+ // src/blocks/document.ts
12174
+ init_styles();
12175
+ init_widthUtils();
12176
+ import {
12177
+ JsonBlockEvaluator,
12178
+ BlockEvaluationError,
12179
+ composeBlocksWithPlugins,
12180
+ readBlockDefinitions,
12181
+ blockValueAt,
12182
+ isBlockRecord,
12183
+ toAuthoredBlockPointer
12184
+ } from "@json-to-office/shared";
12185
+ import { blockSlotBudgets } from "@json-to-office/shared";
12186
+ import { validateDocument as validateDocument2 } from "@json-to-office/shared-docx";
12187
+ var toAuthoredPointer = toAuthoredBlockPointer;
12188
+ function docxBlockContext(document, theme, path3 = "") {
12189
+ const index = /^\/children\/(\d+)/.exec(path3)?.[1];
12190
+ const section2 = index === void 0 ? void 0 : blockValueAt(document, `/children/${index}`);
12191
+ const page = isBlockRecord(section2) && isBlockRecord(section2.props) && isBlockRecord(section2.props.page) ? section2.props.page : {};
12192
+ const margins = getDocumentMargins(theme);
12193
+ const dimensions = getPageDimensions(
12194
+ page.size ?? theme.page.size
12195
+ );
12196
+ const overrides = isBlockRecord(page.margins) ? page.margins : {};
12197
+ const width = Object.keys(page).length ? Math.max(
12198
+ 0,
12199
+ dimensions.width - Number(overrides.left ?? margins.left ?? 1440) - Number(overrides.right ?? margins.right ?? 1440)
12200
+ ) : getAvailableWidthTwips(theme);
12201
+ const height = Object.keys(page).length ? Math.max(
12202
+ 0,
12203
+ dimensions.height - Number(overrides.top ?? margins.top ?? 1440) - Number(overrides.bottom ?? margins.bottom ?? 1440)
12204
+ ) : getAvailableHeightTwips(theme);
12205
+ return {
12206
+ document: blockValueAt(document, "/props/metadata") ?? {},
12207
+ page: { width, height },
12208
+ section: {}
12209
+ };
12210
+ }
12211
+ function createDocxBlockEvaluator(document, theme, extra = {}) {
12212
+ return new JsonBlockEvaluator(readBlockDefinitions(document), {
12213
+ format: "docx",
12214
+ theme,
12215
+ contextAt: (path3) => docxBlockContext(document, theme, path3),
12216
+ contextSources: { "/document": "/props/metadata" },
12217
+ measure: (axis, unit, context) => Number(blockValueAt(context, `/page/${axis}`)) / (unit === "twip" ? 1 : unit === "in" ? 1440 : 20),
12218
+ ...extra
12219
+ });
12220
+ }
12221
+ function expandBlocks(document, theme) {
12222
+ const effects = [];
12223
+ const evaluator = createDocxBlockEvaluator(document, theme, {
12224
+ onSection: (effect) => effects.push(effect)
12225
+ });
12226
+ const expanded = evaluator.expand(document);
12227
+ return finishDocxBlocks(expanded, document, theme, evaluator, effects);
12228
+ }
12229
+ function validateEffects(document, effects) {
12230
+ for (const effect of effects) {
12231
+ const parts = effect.path.split("/").slice(1);
12232
+ const section2 = blockValueAt(document, "/" + parts.slice(0, 2).join("/"));
12233
+ let valid = parts.length >= 4 && parts[0] === "children" && parts[2] === "children" && isBlockRecord(section2) && section2.name === "section";
12234
+ for (let i = 4; i < parts.length; i += 2) {
12235
+ const parent = blockValueAt(document, "/" + parts.slice(0, i).join("/"));
12236
+ valid &&= parts[i] === "children" && isBlockRecord(parent) && parent.name === "group";
12237
+ }
12238
+ if (!valid)
12239
+ throw new BlockEvaluationError([
12240
+ {
12241
+ path: effect.environment.source,
12242
+ code: "invalid_placement",
12243
+ message: "Section effects require a block in a top-level section body, optionally nested in transparent groups."
12244
+ }
12245
+ ]);
12246
+ }
12247
+ }
12248
+ function finishDocxBlocks(expanded, document, theme, evaluator, effects, validate = true) {
12249
+ if (isBlockRecord(expanded) && Array.isArray(expanded.children)) {
12250
+ let inherited;
12251
+ expanded.children.forEach((section2, index) => {
12252
+ if (!isBlockRecord(section2) || section2.name !== "section" || section2.enabled === false)
12253
+ return;
12254
+ const path3 = `/children/${index}`;
12255
+ const local = effects.filter(
12256
+ (e) => e.path.startsWith(`${path3}/children/`)
12257
+ );
12258
+ let tracker;
12259
+ let trackerSource = path3;
12260
+ for (const effect of local) {
12261
+ if (effect.settings.tracker !== void 0) {
12262
+ tracker = evaluator.evaluate(
12263
+ effect.settings.tracker,
12264
+ effect.environment,
12265
+ `${path3}/props/tracker`,
12266
+ `${effect.environment.definition}/section/tracker`
12267
+ );
12268
+ trackerSource = toAuthoredPointer(
12269
+ evaluator.sourceMap,
12270
+ `${path3}/props/tracker`
12271
+ );
12272
+ }
12273
+ }
12274
+ let chrome = inherited;
12275
+ for (const effect of local) {
12276
+ if (effect.settings.header !== void 0 || effect.settings.footer !== void 0) {
12277
+ chrome = effect;
12278
+ if (effect.settings.scope === "following") inherited = effect;
12279
+ }
12280
+ }
12281
+ const props = isBlockRecord(section2.props) ? { ...section2.props } : {};
12282
+ if (chrome) {
12283
+ const environment = {
12284
+ ...chrome.environment,
12285
+ context: {
12286
+ ...docxBlockContext(document, theme, path3),
12287
+ section: { tracker }
12288
+ },
12289
+ contextSources: {
12290
+ "/document": "/props/metadata",
12291
+ "/section/tracker": trackerSource
12292
+ }
12293
+ };
12294
+ for (const part of ["header", "footer"]) {
12295
+ if (props[part] !== void 0 || chrome.settings[part] === void 0)
12296
+ continue;
12297
+ const compiled = evaluator.evaluate(
12298
+ chrome.settings[part],
12299
+ environment,
12300
+ `${path3}/props/${part}`,
12301
+ `${environment.definition}/section/${part}`
12302
+ );
12303
+ props[part] = evaluator.expand(compiled, `${path3}/props/${part}`);
12304
+ }
12305
+ if (props.pageBreak === void 0)
12306
+ props.pageBreak = chrome.settings.pageBreak ?? true;
12307
+ }
12308
+ for (const effect of local)
12309
+ if (effect.settings.pageBreak !== void 0 && props.pageBreak === void 0)
12310
+ props.pageBreak = effect.settings.pageBreak;
12311
+ section2.props = props;
12312
+ });
12313
+ }
12314
+ validateEffects(expanded, effects);
12315
+ if (evaluator.blocks.length && validate) {
12316
+ const result = validateDocument2(expanded);
12317
+ if (!result.valid)
12318
+ throw new BlockEvaluationError(
12319
+ result.errors.map((issue) => ({
12320
+ path: toAuthoredPointer(evaluator.sourceMap, issue.path),
12321
+ code: issue.code ?? "block_invalid_output",
12322
+ message: issue.message
12323
+ }))
12324
+ );
12325
+ }
12326
+ return {
12327
+ document: expanded,
12328
+ sourceMap: evaluator.sourceMap,
12329
+ blocks: evaluator.blocks
12330
+ };
12331
+ }
12332
+ async function expandBlocksWithPlugins(document, theme, plugins, render, preserve = /* @__PURE__ */ new Set()) {
12333
+ const effects = [];
12334
+ const evaluator = createDocxBlockEvaluator(document, theme, {
12335
+ reservedNames: [...plugins],
12336
+ onSection: (effect) => effects.push(effect)
12337
+ });
12338
+ const walk2 = (value) => composeBlocksWithPlugins(evaluator, value, {
12339
+ plugins,
12340
+ render,
12341
+ preserve
12342
+ });
12343
+ const first = await walk2(document);
12344
+ const finished = finishDocxBlocks(
12345
+ first.standard,
12346
+ document,
12347
+ theme,
12348
+ evaluator,
12349
+ effects,
12350
+ false
12351
+ );
12352
+ const second = await walk2(finished.document);
12353
+ validateEffects(second.standard, effects);
12354
+ const result = validateDocument2(second.standard);
12355
+ if (!result.valid)
12356
+ throw new BlockEvaluationError(
12357
+ result.errors.map((issue) => ({
12358
+ path: toAuthoredPointer(evaluator.sourceMap, issue.path),
12359
+ code: issue.code ?? "block_invalid_output",
12360
+ message: issue.message
12361
+ }))
12362
+ );
12363
+ return {
12364
+ document: second.standard,
12365
+ preserved: first.preserved,
12366
+ sourceMap: evaluator.sourceMap,
12367
+ blocks: evaluator.blocks
12368
+ };
12369
+ }
12370
+
12808
12371
  // src/quality/facts.ts
12809
12372
  init_styleHelpers();
12810
12373
  init_defaults();
@@ -13011,6 +12574,7 @@ function tableDesignFact(props, path3, theme, themeName, authored) {
13011
12574
  alignment: alignments.size === 1 ? [...alignments][0] : alignments.size === 0 ? "left" : "mixed",
13012
12575
  hasCellDefaults: asRecord(authoredColumn.cellDefaults) !== void 0,
13013
12576
  hasHeader: asRecord(authoredColumn.header) !== void 0,
12577
+ generated: false,
13014
12578
  cellsWithOwnAlignment: cells.flatMap((cell, cellIndex) => {
13015
12579
  const alignment2 = asRecord(cell)?.horizontalAlignment;
13016
12580
  return typeof alignment2 === "string" && DOCX_ALIGNMENTS.has(alignment2) && alignment2 !== "right" ? [cellIndex] : [];
@@ -13247,7 +12811,10 @@ function prepareDocxQualityDocument(document, options = {}) {
13247
12811
  const facts = [];
13248
12812
  const provenance = {};
13249
12813
  let previousHeadingLevel;
13250
- const authoredPath = (path3) => toAuthoredPointer(expanded.sourceMap, path3);
12814
+ const authoredPath = (path3) => toAuthoredPointer(
12815
+ themed.sourceMap ?? {},
12816
+ toAuthoredPointer(expanded.sourceMap, path3)
12817
+ );
13251
12818
  const addFact = (raw) => {
13252
12819
  const fact = {
13253
12820
  ...raw,
@@ -13262,7 +12829,7 @@ function prepareDocxQualityDocument(document, options = {}) {
13262
12829
  ...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
13263
12830
  };
13264
12831
  };
13265
- for (const budget of blockSlotBudgets(context.document, expanded.blocks)) {
12832
+ for (const budget of blockSlotBudgets(themed.document, expanded.blocks)) {
13266
12833
  addFact({
13267
12834
  id: `docx:block-slot:${budget.path}`,
13268
12835
  kind: "docx/block-slot",
@@ -13357,7 +12924,19 @@ function prepareDocxQualityDocument(document, options = {}) {
13357
12924
  context.themeName,
13358
12925
  authoredPropsAt(path3)
13359
12926
  );
13360
- if (design) addFact(design);
12927
+ if (design)
12928
+ addFact({
12929
+ ...design,
12930
+ columns: design.columns.map((column) => {
12931
+ const authored = authoredPath(column.path);
12932
+ const table2 = /^(.*)\/props\/columns\/\d+$/.exec(authored)?.[1];
12933
+ return {
12934
+ ...column,
12935
+ path: authored,
12936
+ generated: table2 === void 0 || asRecord(nodeAtPointer(themed.document, table2))?.name !== "table"
12937
+ };
12938
+ })
12939
+ });
13361
12940
  }
13362
12941
  if (node.name === "chart" || node.name === "highcharts") {
13363
12942
  const fact = chartFact(node, props, path3, paletteTokens);
@@ -13412,11 +12991,19 @@ function prepareDocxQualityDocument(document, options = {}) {
13412
12991
  facts,
13413
12992
  provenance,
13414
12993
  renderer: options.renderer ?? DEFAULT_DOCX_RENDERER_ID2,
13415
- ...expanded.blocks.length > 0 && {
12994
+ ...(expanded.blocks.length > 0 || (themed.blockPaths?.length ?? 0) > 0) && {
13416
12995
  metadata: {
13417
12996
  blocks: {
13418
- sourceMap: expanded.sourceMap,
13419
- blocks: expanded.blocks,
12997
+ sourceMap: {
12998
+ ...themed.sourceMap,
12999
+ ...Object.fromEntries(
13000
+ Object.entries(expanded.sourceMap).map(([key, value]) => [
13001
+ key,
13002
+ toAuthoredPointer(themed.sourceMap ?? {}, value)
13003
+ ])
13004
+ )
13005
+ },
13006
+ blocks: [...themed.blockPaths ?? [], ...expanded.blocks],
13420
13007
  document: context.document
13421
13008
  }
13422
13009
  }
@@ -13754,12 +13341,12 @@ function createBuilderImpl(state) {
13754
13341
  );
13755
13342
  }
13756
13343
  }
13757
- const validateEmitted = vOpts.enabled === false ? void 0 : (emitted, componentLabel) => {
13344
+ const validateEmitted = vOpts.enabled === false ? void 0 : (emitted, componentLabel, sourcePath) => {
13758
13345
  const result = validateDocument(
13759
13346
  {
13760
13347
  ...internalDocument,
13761
13348
  ...renderer !== void 0 ? { renderer } : {},
13762
- children: emitted
13349
+ children: sourcePath === void 0 ? emitted : [{ name: "section", props: {}, children: emitted }]
13763
13350
  },
13764
13351
  state.components,
13765
13352
  { allowUnknownFields: vOpts.allowUnknownFields }
@@ -13767,7 +13354,10 @@ function createBuilderImpl(state) {
13767
13354
  if (!result.valid) {
13768
13355
  throw new ComponentValidationError2(
13769
13356
  (result.errors ?? []).map((e) => ({
13770
- path: e.path ?? "",
13357
+ path: sourcePath === void 0 ? e.path ?? "" : (e.path ?? "").replace(
13358
+ /^\/children\/0\/children\/\d+/,
13359
+ sourcePath
13360
+ ),
13771
13361
  message: `custom component '${componentLabel}' emitted invalid output \u2014 ${e.message}`
13772
13362
  })),
13773
13363
  emitted
@@ -13785,6 +13375,62 @@ function createBuilderImpl(state) {
13785
13375
  warnings,
13786
13376
  resolveNamedTheme: resolveDocumentTheme
13787
13377
  });
13378
+ if (modedRoot.props.blocks !== void 0) {
13379
+ const expanded = await expandBlocksWithPlugins(
13380
+ modedRoot,
13381
+ modedTheme,
13382
+ new Set(componentMap.keys()),
13383
+ async (component, path3) => {
13384
+ const custom = componentMap.get(String(component.name));
13385
+ const version = resolveComponentVersion(
13386
+ custom.name,
13387
+ custom.versions,
13388
+ component.version
13389
+ );
13390
+ const checked = validateComponentProps(
13391
+ version,
13392
+ component.props ?? {},
13393
+ custom.name,
13394
+ { clean: vOpts.allowUnknownFields === true }
13395
+ );
13396
+ if (!checked.valid)
13397
+ throw new ComponentValidationError2(
13398
+ (checked.errors ?? []).map((e) => ({ path: path3, message: e.message })),
13399
+ component
13400
+ );
13401
+ const output = await version.render({
13402
+ props: cleanComponentProps(version, component.props ?? {}),
13403
+ theme: modedTheme,
13404
+ children: component.children,
13405
+ addWarning: (message, context) => warnings.push({
13406
+ component: custom.name,
13407
+ message,
13408
+ severity: "warning",
13409
+ context
13410
+ })
13411
+ });
13412
+ const emitted = Array.isArray(output) ? output : [output];
13413
+ validateEmitted?.(emitted, custom.name, path3);
13414
+ return emitted;
13415
+ },
13416
+ preserveSet
13417
+ );
13418
+ const [modedDoc2] = normalizeDocument(expanded.document);
13419
+ return {
13420
+ modedRoot,
13421
+ modedTheme,
13422
+ themeName,
13423
+ modedDoc: modedDoc2,
13424
+ sourceMap: expanded.sourceMap,
13425
+ blockPaths: expanded.blocks,
13426
+ warnings,
13427
+ preserveSet,
13428
+ processed: {
13429
+ standard: modedDoc2.children ?? [],
13430
+ preserved: expanded.preserved.children ?? []
13431
+ }
13432
+ };
13433
+ }
13788
13434
  const processed = await processDocumentComponents(
13789
13435
  modedRoot.children || [],
13790
13436
  preserveSet,
@@ -13809,9 +13455,13 @@ function createBuilderImpl(state) {
13809
13455
  }
13810
13456
  async function expandStandardDefinition(document, options) {
13811
13457
  try {
13812
- const { modedDoc, warnings } = await expandDocument(document, options);
13458
+ const { modedDoc, warnings, sourceMap } = await expandDocument(
13459
+ document,
13460
+ options
13461
+ );
13813
13462
  return {
13814
13463
  standardDefinition: modedDoc,
13464
+ ...sourceMap && { sourceMap },
13815
13465
  warnings: warnings.length > 0 ? warnings : null
13816
13466
  };
13817
13467
  } catch (error) {
@@ -13829,6 +13479,8 @@ function createBuilderImpl(state) {
13829
13479
  themeName,
13830
13480
  processed,
13831
13481
  modedDoc,
13482
+ sourceMap,
13483
+ blockPaths,
13832
13484
  warnings,
13833
13485
  preserveSet
13834
13486
  } = await expandDocument(document, options);
@@ -13839,7 +13491,13 @@ function createBuilderImpl(state) {
13839
13491
  const { buffer } = await generateBufferViaIr(modedDoc, {
13840
13492
  // The prologue already ran: the theme had to be resolved before custom
13841
13493
  // components expanded, because each one's `render` is handed it.
13842
- context: { document: modedDoc, theme: modedTheme, themeName },
13494
+ context: {
13495
+ document: modedDoc,
13496
+ theme: modedTheme,
13497
+ themeName,
13498
+ ...sourceMap !== void 0 && { sourceMap },
13499
+ ...blockPaths !== void 0 && { blockPaths }
13500
+ },
13843
13501
  ...state.services ? { services: state.services } : {},
13844
13502
  ...state.fonts ? { fonts: state.fonts } : {},
13845
13503
  ...baseDir !== void 0 ? { baseDir } : {},