@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.
package/dist/index.js CHANGED
@@ -8454,641 +8454,6 @@ function emptyToUndefined(formatting) {
8454
8454
  return entries.length === 0 ? void 0 : Object.fromEntries(entries);
8455
8455
  }
8456
8456
 
8457
- // src/blocks/index.ts
8458
- import {
8459
- COVER_BUDGET,
8460
- KEY_TAKEAWAYS_BUDGET,
8461
- RUNNING_HEAD_BUDGET,
8462
- SECTION_OPENER_BUDGET
8463
- } from "@json-to-office/shared-docx";
8464
-
8465
- // src/blocks/cover.ts
8466
- init_widthUtils();
8467
-
8468
- // src/blocks/recipe.ts
8469
- var MIN_RULE_PT = 0.25;
8470
- var MAX_RULE_PT = 12;
8471
- function clampRule(weightPt) {
8472
- return Math.min(MAX_RULE_PT, Math.max(MIN_RULE_PT, weightPt));
8473
- }
8474
- var FALLBACK_EYEBROW_FONT = {
8475
- size: 9,
8476
- bold: true,
8477
- color: "accent",
8478
- case: "upper"
8479
- };
8480
- function hasStyle(theme, role) {
8481
- const styles = theme.styles;
8482
- return styles?.[role] !== void 0;
8483
- }
8484
- function roleProps(theme, role, fallbackFont, color) {
8485
- if (hasStyle(theme, role)) {
8486
- return {
8487
- themeStyle: role,
8488
- ...color !== void 0 && { font: { color } }
8489
- };
8490
- }
8491
- return { font: { ...fallbackFont, ...color !== void 0 && { color } } };
8492
- }
8493
-
8494
- // src/blocks/cover.ts
8495
- var DROP_SHARE = 0.3;
8496
- var DROP_SHARE_WITH_LOGO = 0.22;
8497
- var DEFAULT_LOGO_WIDTH = "25%";
8498
- var FALLBACK = {
8499
- ruleWeightPt: 3,
8500
- ruleColor: "accent",
8501
- padPt: 12,
8502
- metaFont: { size: 9, color: "textSecondary" }
8503
- };
8504
- function compileCover(props, theme) {
8505
- const recipe = theme.chrome?.cover;
8506
- const ruleWeightPt = recipe?.rule?.weightPt ?? FALLBACK.ruleWeightPt;
8507
- const padPt = recipe?.padPt ?? FALLBACK.padPt;
8508
- const heightPt = getAvailableHeightTwips(theme) / 20;
8509
- const dropPt = Math.round(
8510
- heightPt * (props.logo ? DROP_SHARE_WITH_LOGO : DROP_SHARE)
8511
- );
8512
- const children = [];
8513
- const sourceMap = {};
8514
- const emit = (child, slots) => {
8515
- const index = children.push(child) - 1;
8516
- sourceMap[`/children/${index}`] = "";
8517
- for (const [emitted, authored] of Object.entries(slots)) {
8518
- sourceMap[`/children/${index}${emitted}`] = authored;
8519
- }
8520
- };
8521
- if (props.logo) {
8522
- const { width, height, ...source } = props.logo;
8523
- emit(
8524
- {
8525
- name: "image",
8526
- props: {
8527
- ...source,
8528
- width: width ?? DEFAULT_LOGO_WIDTH,
8529
- ...height !== void 0 && { height },
8530
- alignment: theme.chrome?.logoSlot?.alignment ?? "left",
8531
- spacing: { before: 0, after: 0 }
8532
- }
8533
- },
8534
- { "/props": "/props/logo" }
8535
- );
8536
- }
8537
- const drawsRule = ruleWeightPt > 0;
8538
- if (drawsRule) {
8539
- emit(
8540
- {
8541
- name: "divider",
8542
- props: {
8543
- thickness: clampRule(ruleWeightPt),
8544
- color: recipe?.rule?.color ?? FALLBACK.ruleColor,
8545
- spacing: { before: dropPt, after: padPt }
8546
- }
8547
- },
8548
- {}
8549
- );
8550
- }
8551
- let dropPending = !drawsRule;
8552
- const before = () => {
8553
- if (!dropPending) return {};
8554
- dropPending = false;
8555
- return { before: dropPt };
8556
- };
8557
- if (props.client) {
8558
- emit(
8559
- {
8560
- name: "paragraph",
8561
- props: {
8562
- text: props.client,
8563
- keepNext: true,
8564
- spacing: { ...before(), after: 4 },
8565
- ...roleProps(theme, "eyebrow", FALLBACK_EYEBROW_FONT)
8566
- }
8567
- },
8568
- { "/props/text": "/props/client" }
8569
- );
8570
- }
8571
- const titleRole = recipe?.type && hasStyle(theme, recipe.type) ? recipe.type : "title";
8572
- emit(
8573
- {
8574
- name: "paragraph",
8575
- props: {
8576
- text: props.title,
8577
- keepNext: true,
8578
- ...dropPending && { spacing: before() },
8579
- ...roleProps(theme, titleRole, { size: 26, bold: true }, recipe?.color)
8580
- }
8581
- },
8582
- { "/props/text": "/props/title" }
8583
- );
8584
- if (props.subtitle) {
8585
- emit(
8586
- {
8587
- name: "paragraph",
8588
- props: {
8589
- text: props.subtitle,
8590
- keepNext: true,
8591
- ...roleProps(theme, "subtitle", { size: 13, color: "textSecondary" })
8592
- }
8593
- },
8594
- { "/props/text": "/props/subtitle" }
8595
- );
8596
- }
8597
- const meta = [props.date, props.confidentiality].filter(
8598
- (part) => typeof part === "string" && part !== ""
8599
- );
8600
- if (meta.length > 0) {
8601
- emit(
8602
- {
8603
- name: "paragraph",
8604
- props: {
8605
- text: meta.join(" \xB7 "),
8606
- spacing: { before: padPt, after: 0 },
8607
- ...roleProps(theme, "label", FALLBACK.metaFont)
8608
- }
8609
- },
8610
- {
8611
- "/props/text": props.date ? "/props/date" : "/props/confidentiality"
8612
- }
8613
- );
8614
- }
8615
- return { children, sourceMap };
8616
- }
8617
-
8618
- // src/blocks/keyTakeaways.ts
8619
- var KEY_TAKEAWAYS_DEFAULT_LABEL = "Key takeaways";
8620
- var FALLBACK_RECIPE = {
8621
- ruleWeightPt: 1.5,
8622
- ruleColor: "accent",
8623
- padPt: 6,
8624
- type: "label"
8625
- };
8626
- function compileKeyTakeaways(props, theme) {
8627
- const recipe = theme.chrome?.keyTakeaways;
8628
- const labelRole = recipe?.type ?? FALLBACK_RECIPE.type;
8629
- const labelStyle = hasStyle(theme, labelRole);
8630
- const ruleWeightPt = clampRule(
8631
- recipe?.rule?.weightPt ?? FALLBACK_RECIPE.ruleWeightPt
8632
- );
8633
- const ruleColor = recipe?.rule?.color ?? recipe?.color ?? FALLBACK_RECIPE.ruleColor;
8634
- const padPt = recipe?.padPt ?? FALLBACK_RECIPE.padPt;
8635
- const children = [
8636
- {
8637
- name: "divider",
8638
- props: {
8639
- thickness: ruleWeightPt,
8640
- color: ruleColor,
8641
- spacing: { before: 12, after: padPt }
8642
- }
8643
- },
8644
- {
8645
- name: "paragraph",
8646
- props: {
8647
- text: props.label ?? KEY_TAKEAWAYS_DEFAULT_LABEL,
8648
- keepNext: true,
8649
- spacing: { after: 4 },
8650
- // The label role when the theme resolves one; a bold run in the
8651
- // recipe's colour otherwise, so the label still reads as a label.
8652
- ...labelStyle ? { themeStyle: labelRole } : {
8653
- font: {
8654
- bold: true,
8655
- ...recipe?.color !== void 0 && { color: recipe.color }
8656
- }
8657
- }
8658
- }
8659
- },
8660
- {
8661
- name: "list",
8662
- props: { items: [...props.items] }
8663
- },
8664
- {
8665
- name: "divider",
8666
- props: {
8667
- thickness: 0.5,
8668
- color: "borderPrimary",
8669
- spacing: { before: padPt, after: 12 }
8670
- }
8671
- }
8672
- ];
8673
- return {
8674
- children,
8675
- sourceMap: {
8676
- "/children/0": "",
8677
- "/children/1": "",
8678
- "/children/1/props/text": "/props/label",
8679
- "/children/2": "",
8680
- "/children/2/props/items": "/props/items",
8681
- "/children/3": ""
8682
- }
8683
- };
8684
- }
8685
-
8686
- // src/blocks/runningHead.ts
8687
- init_styles();
8688
- init_widthUtils();
8689
- var FALLBACK2 = {
8690
- headFont: { size: 8, color: "textMuted", case: "upper" },
8691
- footFont: { size: 8, color: "textMuted" },
8692
- ruleWeightPt: 0.5,
8693
- ruleColor: "border"
8694
- };
8695
- var PAGE_OF_TOTAL = "{PAGE} / {TOTAL_PAGES}";
8696
- function sectionMeasureTwips(theme, page) {
8697
- if (!page || page.size === void 0 && page.margins === void 0) {
8698
- return getAvailableWidthTwips(theme);
8699
- }
8700
- const width = page.size ? getPageDimensions(page.size).width : getPageDimensions(theme.page.size).width;
8701
- const margins = getDocumentMargins(theme);
8702
- const left = page.margins?.left ?? margins.left ?? 1440;
8703
- const right = page.margins?.right ?? margins.right ?? 1440;
8704
- return Math.max(0, width - left - right);
8705
- }
8706
- function compileRunningHead(props, theme, scope) {
8707
- const measure = sectionMeasureTwips(theme, scope.page);
8708
- const head = theme.chrome?.runningHead;
8709
- const foot = theme.chrome?.confidentialFooter;
8710
- const slot = (name) => props[name] !== void 0 ? `${scope.block}/props/${name}` : void 0;
8711
- const header = [];
8712
- const headerMap = {};
8713
- const title = props.title ?? scope.documentTitle ?? "";
8714
- const tracker = scope.opener?.text ?? props.tracker ?? "";
8715
- if (title !== "" || tracker !== "") {
8716
- const both = title !== "" && tracker !== "";
8717
- header.push({
8718
- name: "paragraph",
8719
- props: {
8720
- text: both ? `${title} ${tracker}` : title || tracker,
8721
- ...both ? { tabStops: [{ type: "right", position: measure }] } : { alignment: title !== "" ? "left" : head?.alignment ?? "right" },
8722
- spacing: { before: 0, after: 0 },
8723
- ...roleProps(
8724
- theme,
8725
- head?.type ?? "tracker",
8726
- FALLBACK2.headFont,
8727
- head?.color
8728
- )
8729
- }
8730
- });
8731
- headerMap["/0"] = scope.block;
8732
- headerMap["/0/props/text"] = (tracker !== "" ? scope.opener?.slot ?? slot("tracker") : slot("title")) ?? scope.block;
8733
- }
8734
- const headRule = head?.rule?.weightPt ?? FALLBACK2.ruleWeightPt;
8735
- if (headRule > 0) {
8736
- header.push({
8737
- name: "divider",
8738
- props: {
8739
- thickness: clampRule(headRule),
8740
- color: head?.rule?.color ?? FALLBACK2.ruleColor,
8741
- spacing: { before: 2, after: 0 }
8742
- }
8743
- });
8744
- headerMap[`/${header.length - 1}`] = scope.block;
8745
- }
8746
- const footer = [];
8747
- const footerMap = {};
8748
- const footRule = foot?.rule?.weightPt ?? FALLBACK2.ruleWeightPt;
8749
- if (footRule > 0) {
8750
- footer.push({
8751
- name: "divider",
8752
- props: {
8753
- thickness: clampRule(footRule),
8754
- color: foot?.rule?.color ?? FALLBACK2.ruleColor,
8755
- spacing: { before: 0, after: 4 }
8756
- }
8757
- });
8758
- footerMap["/0"] = scope.block;
8759
- }
8760
- const parts = [
8761
- props.confidentiality ?? "",
8762
- props.pageNumbers === false ? "" : PAGE_OF_TOTAL,
8763
- props.date ?? ""
8764
- ];
8765
- const present = parts.filter((part) => part !== "");
8766
- if (present.length > 0) {
8767
- const single = present.length === 1;
8768
- const alignment2 = present[0] === PAGE_OF_TOTAL ? "center" : foot?.alignment ?? "left";
8769
- footer.push({
8770
- name: "paragraph",
8771
- props: {
8772
- text: single ? present[0] : parts.join(" ").replace(/\t+$/, ""),
8773
- ...single ? { alignment: alignment2 } : {
8774
- tabStops: [
8775
- { type: "center", position: Math.round(measure / 2) },
8776
- { type: "right", position: measure }
8777
- ]
8778
- },
8779
- spacing: { before: 0, after: 0 },
8780
- ...roleProps(
8781
- theme,
8782
- foot?.type ?? "footer",
8783
- FALLBACK2.footFont,
8784
- foot?.color
8785
- )
8786
- }
8787
- });
8788
- const index = footer.length - 1;
8789
- footerMap[`/${index}`] = scope.block;
8790
- footerMap[`/${index}/props/text`] = slot("confidentiality") ?? slot("date") ?? scope.block;
8791
- }
8792
- return {
8793
- header: { children: header, sourceMap: headerMap },
8794
- footer: { children: footer, sourceMap: footerMap }
8795
- };
8796
- }
8797
-
8798
- // src/blocks/sectionOpener.ts
8799
- function compileSectionOpener(props, theme) {
8800
- const children = [];
8801
- const sourceMap = {};
8802
- const pageBreak = props.pageBreak === true;
8803
- if (props.number !== void 0) {
8804
- children.push({
8805
- name: "paragraph",
8806
- props: {
8807
- text: String(props.number),
8808
- keepNext: true,
8809
- spacing: { after: 2 },
8810
- ...pageBreak && { pageBreak: true },
8811
- ...roleProps(theme, "eyebrow", FALLBACK_EYEBROW_FONT)
8812
- }
8813
- });
8814
- sourceMap["/children/0"] = "";
8815
- sourceMap["/children/0/props/text"] = "/props/number";
8816
- }
8817
- const index = children.length;
8818
- children.push({
8819
- name: "heading",
8820
- props: {
8821
- text: props.title,
8822
- level: 1,
8823
- ...pageBreak && index === 0 && { pageBreak: true }
8824
- }
8825
- });
8826
- sourceMap[`/children/${index}`] = "";
8827
- sourceMap[`/children/${index}/props/text`] = "/props/title";
8828
- return { children, sourceMap };
8829
- }
8830
- function sectionTracker(section2) {
8831
- if (!Array.isArray(section2.children)) return void 0;
8832
- for (const [index, child] of section2.children.entries()) {
8833
- if (typeof child !== "object" || child === null || child.name !== "section-opener" || child.enabled === false) {
8834
- continue;
8835
- }
8836
- const props = child.props;
8837
- if (!props) continue;
8838
- if (typeof props.tracker === "string" && props.tracker !== "") {
8839
- return { text: props.tracker, slot: `/children/${index}/props/tracker` };
8840
- }
8841
- if (typeof props.title === "string" && props.title !== "") {
8842
- return { text: props.title, slot: `/children/${index}/props/title` };
8843
- }
8844
- }
8845
- return void 0;
8846
- }
8847
-
8848
- // src/blocks/index.ts
8849
- var BLOCK_NAMES = [
8850
- "key-takeaways",
8851
- "cover",
8852
- "section-opener",
8853
- "running-head"
8854
- ];
8855
- function slotBudget(block2, props, pointer, slot, maxWords) {
8856
- const value = props[slot];
8857
- return typeof value === "string" ? [
8858
- {
8859
- block: block2,
8860
- slot,
8861
- path: `${pointer}/props/${slot}`,
8862
- words: wordCount(value),
8863
- maxWords
8864
- }
8865
- ] : [];
8866
- }
8867
- function budgetsOf(block2, budget) {
8868
- return (props, pointer) => Object.entries(budget).flatMap(
8869
- ([slot, { maxWords }]) => slotBudget(block2, props, pointer, slot, maxWords)
8870
- );
8871
- }
8872
- var BLOCKS = {
8873
- "key-takeaways": {
8874
- kind: "flow",
8875
- compile: (props, theme) => compileKeyTakeaways(props, theme),
8876
- budgets: (props, pointer) => (Array.isArray(props.items) ? props.items : []).flatMap(
8877
- (item, index) => typeof item === "string" ? [
8878
- {
8879
- block: "key-takeaways",
8880
- slot: "items",
8881
- path: `${pointer}/props/items/${index}`,
8882
- words: wordCount(item),
8883
- maxWords: KEY_TAKEAWAYS_BUDGET.items.maxWords
8884
- }
8885
- ] : []
8886
- )
8887
- },
8888
- cover: {
8889
- kind: "flow",
8890
- compile: (props, theme) => compileCover(props, theme),
8891
- budgets: budgetsOf("cover", COVER_BUDGET)
8892
- },
8893
- "section-opener": {
8894
- kind: "flow",
8895
- compile: (props, theme) => compileSectionOpener(props, theme),
8896
- budgets: budgetsOf("section-opener", SECTION_OPENER_BUDGET)
8897
- },
8898
- "running-head": {
8899
- kind: "chrome",
8900
- budgets: budgetsOf("running-head", RUNNING_HEAD_BUDGET)
8901
- }
8902
- };
8903
- function isBlockName(name) {
8904
- return BLOCK_NAMES.includes(name);
8905
- }
8906
- function isChromeBlockName(name) {
8907
- return isBlockName(name) && BLOCKS[name].kind === "chrome";
8908
- }
8909
- function isRecord(value) {
8910
- return typeof value === "object" && value !== null && !Array.isArray(value);
8911
- }
8912
- var SECTION_CHILD = /^\/children\/\d+\/children\/\d+$/;
8913
- var SECTION = /^\/children\/(\d+)$/;
8914
- function planChrome(document, theme) {
8915
- const plans = /* @__PURE__ */ new Map();
8916
- if (!isRecord(document) || document.name !== "docx" || !Array.isArray(document.children)) {
8917
- return plans;
8918
- }
8919
- const metadata = isRecord(document.props) ? document.props.metadata : void 0;
8920
- const documentTitle = isRecord(metadata) && typeof metadata.title === "string" ? metadata.title : void 0;
8921
- let inForce;
8922
- document.children.forEach((section2, index) => {
8923
- if (!isRecord(section2) || section2.name !== "section" || section2.enabled === false) {
8924
- return;
8925
- }
8926
- const sectionPointer = `/children/${index}`;
8927
- const children = Array.isArray(section2.children) ? section2.children : [];
8928
- children.forEach((child, childIndex) => {
8929
- if (isRecord(child) && isChromeBlockName(child.name) && child.enabled !== false) {
8930
- inForce = {
8931
- block: `${sectionPointer}/children/${childIndex}`,
8932
- props: isRecord(child.props) ? child.props : {}
8933
- };
8934
- }
8935
- });
8936
- if (!inForce) return;
8937
- const opener = sectionTracker(section2);
8938
- const sectionProps = isRecord(section2.props) ? section2.props : {};
8939
- plans.set(
8940
- index,
8941
- compileRunningHead(inForce.props, theme, {
8942
- block: inForce.block,
8943
- documentTitle,
8944
- ...opener && {
8945
- opener: {
8946
- text: opener.text,
8947
- slot: `${sectionPointer}${opener.slot}`
8948
- }
8949
- },
8950
- ...isRecord(sectionProps.page) && {
8951
- page: sectionProps.page
8952
- }
8953
- })
8954
- );
8955
- });
8956
- return plans;
8957
- }
8958
- function expandBlocks(document, theme) {
8959
- const sourceMap = {};
8960
- const blocks = [];
8961
- const chrome = planChrome(document, theme);
8962
- const walk2 = (node, pointer) => {
8963
- if (Array.isArray(node)) {
8964
- return node.map((child, index) => walk2(child, `${pointer}/${index}`));
8965
- }
8966
- if (!isRecord(node)) return node;
8967
- if (typeof node.name === "string" && node.enabled !== false && isBlockName(node.name)) {
8968
- const definition = BLOCKS[node.name];
8969
- if (definition.kind === "chrome") {
8970
- if (!SECTION_CHILD.test(pointer)) return node;
8971
- blocks.push(pointer);
8972
- return { ...node, children: [] };
8973
- }
8974
- if (isRecord(node.props)) {
8975
- const compiled = definition.compile(node.props, theme);
8976
- blocks.push(pointer);
8977
- for (const [emitted, authored] of Object.entries(compiled.sourceMap)) {
8978
- sourceMap[`${pointer}${emitted}`] = `${pointer}${authored}`;
8979
- }
8980
- return { ...node, children: compiled.children };
8981
- }
8982
- }
8983
- const next = { ...node };
8984
- if (isRecord(node.props)) {
8985
- const props = { ...node.props };
8986
- let changed = false;
8987
- for (const key of ["header", "footer"]) {
8988
- if (Array.isArray(props[key])) {
8989
- props[key] = walk2(props[key], `${pointer}/props/${key}`);
8990
- changed = true;
8991
- }
8992
- }
8993
- if (Array.isArray(props.columns)) {
8994
- props.columns = props.columns.map((column, index) => {
8995
- if (!isRecord(column)) return column;
8996
- const base = `${pointer}/props/columns/${index}`;
8997
- const out = { ...column };
8998
- if (isRecord(column.header) && "content" in column.header) {
8999
- out.header = {
9000
- ...column.header,
9001
- content: walk2(column.header.content, `${base}/header/content`)
9002
- };
9003
- }
9004
- if (Array.isArray(column.cells)) {
9005
- out.cells = column.cells.map(
9006
- (cell, cellIndex) => isRecord(cell) && "content" in cell ? {
9007
- ...cell,
9008
- content: walk2(
9009
- cell.content,
9010
- `${base}/cells/${cellIndex}/content`
9011
- )
9012
- } : cell
9013
- );
9014
- }
9015
- return out;
9016
- });
9017
- changed = true;
9018
- }
9019
- if (changed) next.props = props;
9020
- }
9021
- if (Array.isArray(node.children)) {
9022
- next.children = walk2(node.children, `${pointer}/children`);
9023
- }
9024
- const section2 = SECTION.exec(pointer);
9025
- if (section2 && node.name === "section" && node.enabled !== false) {
9026
- const plan = chrome.get(Number(section2[1]));
9027
- if (plan) {
9028
- const props = isRecord(next.props) ? { ...next.props } : {};
9029
- let changed = false;
9030
- for (const part of ["header", "footer"]) {
9031
- const { children, sourceMap: partMap } = plan[part];
9032
- if (props[part] !== void 0 || children.length === 0) continue;
9033
- props[part] = children;
9034
- changed = true;
9035
- const base = `${pointer}/props/${part}`;
9036
- sourceMap[base] = partMap["/0"] ?? pointer;
9037
- for (const [emitted, authored] of Object.entries(partMap)) {
9038
- sourceMap[`${base}${emitted}`] = authored;
9039
- }
9040
- }
9041
- if (props.pageBreak === void 0) {
9042
- props.pageBreak = true;
9043
- changed = true;
9044
- }
9045
- if (changed) next.props = props;
9046
- }
9047
- }
9048
- return next;
9049
- };
9050
- const expanded = walk2(document, "");
9051
- return {
9052
- document: blocks.length > 0 ? expanded : document,
9053
- sourceMap,
9054
- blocks
9055
- };
9056
- }
9057
- function toAuthoredPointer(sourceMap, pointer) {
9058
- let best;
9059
- for (const emitted of Object.keys(sourceMap)) {
9060
- if ((pointer === emitted || pointer.startsWith(`${emitted}/`)) && (best === void 0 || emitted.length > best.length)) {
9061
- best = emitted;
9062
- }
9063
- }
9064
- if (best === void 0) return pointer;
9065
- return `${sourceMap[best]}${pointer.slice(best.length)}`;
9066
- }
9067
- function blockSlotBudgets(document, blocks) {
9068
- return blocks.flatMap((pointer) => {
9069
- const node = nodeAt(document, pointer);
9070
- if (!isRecord(node) || !isRecord(node.props) || !isBlockName(node.name)) {
9071
- return [];
9072
- }
9073
- return BLOCKS[node.name].budgets(node.props, pointer);
9074
- });
9075
- }
9076
- function wordCount(text) {
9077
- const trimmed = text.trim();
9078
- return trimmed === "" ? 0 : trimmed.split(/\s+/).length;
9079
- }
9080
- function nodeAt(root, pointer) {
9081
- if (pointer === "") return root;
9082
- let current = root;
9083
- for (const segment of pointer.slice(1).split("/")) {
9084
- const key = segment.replace(/~1/g, "/").replace(/~0/g, "~");
9085
- if (Array.isArray(current)) current = current[Number(key)];
9086
- else if (isRecord(current)) current = current[key];
9087
- else return void 0;
9088
- }
9089
- return current;
9090
- }
9091
-
9092
8457
  // src/ir/compiler.ts
9093
8458
  init_types();
9094
8459
  init_units();
@@ -9514,7 +8879,7 @@ function compileComponent(component, scope) {
9514
8879
  case "table":
9515
8880
  return compileTable(component, scope);
9516
8881
  default:
9517
- if (isBlockName(component.name)) {
8882
+ if (component.name === "group") {
9518
8883
  if (component.enabled === false) return [];
9519
8884
  return compileBlock(component, scope);
9520
8885
  }
@@ -12719,6 +12084,204 @@ function normalizeDocument(document) {
12719
12084
  return [normalized];
12720
12085
  }
12721
12086
 
12087
+ // src/blocks/document.ts
12088
+ init_styles();
12089
+ init_widthUtils();
12090
+ import {
12091
+ JsonBlockEvaluator,
12092
+ BlockEvaluationError,
12093
+ composeBlocksWithPlugins,
12094
+ readBlockDefinitions,
12095
+ blockValueAt,
12096
+ isBlockRecord,
12097
+ toAuthoredBlockPointer
12098
+ } from "@json-to-office/shared";
12099
+ import { blockSlotBudgets } from "@json-to-office/shared";
12100
+ import { validateDocument } from "@json-to-office/shared-docx";
12101
+ var toAuthoredPointer = toAuthoredBlockPointer;
12102
+ function docxBlockContext(document, theme, path4 = "") {
12103
+ const index = /^\/children\/(\d+)/.exec(path4)?.[1];
12104
+ const section2 = index === void 0 ? void 0 : blockValueAt(document, `/children/${index}`);
12105
+ const page = isBlockRecord(section2) && isBlockRecord(section2.props) && isBlockRecord(section2.props.page) ? section2.props.page : {};
12106
+ const margins = getDocumentMargins(theme);
12107
+ const dimensions = getPageDimensions(
12108
+ page.size ?? theme.page.size
12109
+ );
12110
+ const overrides = isBlockRecord(page.margins) ? page.margins : {};
12111
+ const width = Object.keys(page).length ? Math.max(
12112
+ 0,
12113
+ dimensions.width - Number(overrides.left ?? margins.left ?? 1440) - Number(overrides.right ?? margins.right ?? 1440)
12114
+ ) : getAvailableWidthTwips(theme);
12115
+ const height = Object.keys(page).length ? Math.max(
12116
+ 0,
12117
+ dimensions.height - Number(overrides.top ?? margins.top ?? 1440) - Number(overrides.bottom ?? margins.bottom ?? 1440)
12118
+ ) : getAvailableHeightTwips(theme);
12119
+ return {
12120
+ document: blockValueAt(document, "/props/metadata") ?? {},
12121
+ page: { width, height },
12122
+ section: {}
12123
+ };
12124
+ }
12125
+ function createDocxBlockEvaluator(document, theme, extra = {}) {
12126
+ return new JsonBlockEvaluator(readBlockDefinitions(document), {
12127
+ format: "docx",
12128
+ theme,
12129
+ contextAt: (path4) => docxBlockContext(document, theme, path4),
12130
+ contextSources: { "/document": "/props/metadata" },
12131
+ measure: (axis, unit, context) => Number(blockValueAt(context, `/page/${axis}`)) / (unit === "twip" ? 1 : unit === "in" ? 1440 : 20),
12132
+ ...extra
12133
+ });
12134
+ }
12135
+ function expandBlocks(document, theme) {
12136
+ const effects = [];
12137
+ const evaluator = createDocxBlockEvaluator(document, theme, {
12138
+ onSection: (effect) => effects.push(effect)
12139
+ });
12140
+ const expanded = evaluator.expand(document);
12141
+ return finishDocxBlocks(expanded, document, theme, evaluator, effects);
12142
+ }
12143
+ function validateEffects(document, effects) {
12144
+ for (const effect of effects) {
12145
+ const parts = effect.path.split("/").slice(1);
12146
+ const section2 = blockValueAt(document, "/" + parts.slice(0, 2).join("/"));
12147
+ let valid = parts.length >= 4 && parts[0] === "children" && parts[2] === "children" && isBlockRecord(section2) && section2.name === "section";
12148
+ for (let i = 4; i < parts.length; i += 2) {
12149
+ const parent = blockValueAt(document, "/" + parts.slice(0, i).join("/"));
12150
+ valid &&= parts[i] === "children" && isBlockRecord(parent) && parent.name === "group";
12151
+ }
12152
+ if (!valid)
12153
+ throw new BlockEvaluationError([
12154
+ {
12155
+ path: effect.environment.source,
12156
+ code: "invalid_placement",
12157
+ message: "Section effects require a block in a top-level section body, optionally nested in transparent groups."
12158
+ }
12159
+ ]);
12160
+ }
12161
+ }
12162
+ function finishDocxBlocks(expanded, document, theme, evaluator, effects, validate = true) {
12163
+ if (isBlockRecord(expanded) && Array.isArray(expanded.children)) {
12164
+ let inherited;
12165
+ expanded.children.forEach((section2, index) => {
12166
+ if (!isBlockRecord(section2) || section2.name !== "section" || section2.enabled === false)
12167
+ return;
12168
+ const path4 = `/children/${index}`;
12169
+ const local = effects.filter(
12170
+ (e) => e.path.startsWith(`${path4}/children/`)
12171
+ );
12172
+ let tracker;
12173
+ let trackerSource = path4;
12174
+ for (const effect of local) {
12175
+ if (effect.settings.tracker !== void 0) {
12176
+ tracker = evaluator.evaluate(
12177
+ effect.settings.tracker,
12178
+ effect.environment,
12179
+ `${path4}/props/tracker`,
12180
+ `${effect.environment.definition}/section/tracker`
12181
+ );
12182
+ trackerSource = toAuthoredPointer(
12183
+ evaluator.sourceMap,
12184
+ `${path4}/props/tracker`
12185
+ );
12186
+ }
12187
+ }
12188
+ let chrome = inherited;
12189
+ for (const effect of local) {
12190
+ if (effect.settings.header !== void 0 || effect.settings.footer !== void 0) {
12191
+ chrome = effect;
12192
+ if (effect.settings.scope === "following") inherited = effect;
12193
+ }
12194
+ }
12195
+ const props = isBlockRecord(section2.props) ? { ...section2.props } : {};
12196
+ if (chrome) {
12197
+ const environment = {
12198
+ ...chrome.environment,
12199
+ context: {
12200
+ ...docxBlockContext(document, theme, path4),
12201
+ section: { tracker }
12202
+ },
12203
+ contextSources: {
12204
+ "/document": "/props/metadata",
12205
+ "/section/tracker": trackerSource
12206
+ }
12207
+ };
12208
+ for (const part of ["header", "footer"]) {
12209
+ if (props[part] !== void 0 || chrome.settings[part] === void 0)
12210
+ continue;
12211
+ const compiled = evaluator.evaluate(
12212
+ chrome.settings[part],
12213
+ environment,
12214
+ `${path4}/props/${part}`,
12215
+ `${environment.definition}/section/${part}`
12216
+ );
12217
+ props[part] = evaluator.expand(compiled, `${path4}/props/${part}`);
12218
+ }
12219
+ if (props.pageBreak === void 0)
12220
+ props.pageBreak = chrome.settings.pageBreak ?? true;
12221
+ }
12222
+ for (const effect of local)
12223
+ if (effect.settings.pageBreak !== void 0 && props.pageBreak === void 0)
12224
+ props.pageBreak = effect.settings.pageBreak;
12225
+ section2.props = props;
12226
+ });
12227
+ }
12228
+ validateEffects(expanded, effects);
12229
+ if (evaluator.blocks.length && validate) {
12230
+ const result = validateDocument(expanded);
12231
+ if (!result.valid)
12232
+ throw new BlockEvaluationError(
12233
+ result.errors.map((issue) => ({
12234
+ path: toAuthoredPointer(evaluator.sourceMap, issue.path),
12235
+ code: issue.code ?? "block_invalid_output",
12236
+ message: issue.message
12237
+ }))
12238
+ );
12239
+ }
12240
+ return {
12241
+ document: expanded,
12242
+ sourceMap: evaluator.sourceMap,
12243
+ blocks: evaluator.blocks
12244
+ };
12245
+ }
12246
+ async function expandBlocksWithPlugins(document, theme, plugins, render, preserve = /* @__PURE__ */ new Set()) {
12247
+ const effects = [];
12248
+ const evaluator = createDocxBlockEvaluator(document, theme, {
12249
+ reservedNames: [...plugins],
12250
+ onSection: (effect) => effects.push(effect)
12251
+ });
12252
+ const walk2 = (value) => composeBlocksWithPlugins(evaluator, value, {
12253
+ plugins,
12254
+ render,
12255
+ preserve
12256
+ });
12257
+ const first = await walk2(document);
12258
+ const finished = finishDocxBlocks(
12259
+ first.standard,
12260
+ document,
12261
+ theme,
12262
+ evaluator,
12263
+ effects,
12264
+ false
12265
+ );
12266
+ const second = await walk2(finished.document);
12267
+ validateEffects(second.standard, effects);
12268
+ const result = validateDocument(second.standard);
12269
+ if (!result.valid)
12270
+ throw new BlockEvaluationError(
12271
+ result.errors.map((issue) => ({
12272
+ path: toAuthoredPointer(evaluator.sourceMap, issue.path),
12273
+ code: issue.code ?? "block_invalid_output",
12274
+ message: issue.message
12275
+ }))
12276
+ );
12277
+ return {
12278
+ document: second.standard,
12279
+ preserved: first.preserved,
12280
+ sourceMap: evaluator.sourceMap,
12281
+ blocks: evaluator.blocks
12282
+ };
12283
+ }
12284
+
12722
12285
  // src/quality/facts.ts
12723
12286
  init_styleHelpers();
12724
12287
  init_defaults();
@@ -12925,6 +12488,7 @@ function tableDesignFact(props, path4, theme, themeName, authored) {
12925
12488
  alignment: alignments.size === 1 ? [...alignments][0] : alignments.size === 0 ? "left" : "mixed",
12926
12489
  hasCellDefaults: asRecord(authoredColumn.cellDefaults) !== void 0,
12927
12490
  hasHeader: asRecord(authoredColumn.header) !== void 0,
12491
+ generated: false,
12928
12492
  cellsWithOwnAlignment: cells.flatMap((cell, cellIndex) => {
12929
12493
  const alignment2 = asRecord(cell)?.horizontalAlignment;
12930
12494
  return typeof alignment2 === "string" && DOCX_ALIGNMENTS.has(alignment2) && alignment2 !== "right" ? [cellIndex] : [];
@@ -13161,7 +12725,10 @@ function prepareDocxQualityDocument(document, options = {}) {
13161
12725
  const facts = [];
13162
12726
  const provenance = {};
13163
12727
  let previousHeadingLevel;
13164
- const authoredPath = (path4) => toAuthoredPointer(expanded.sourceMap, path4);
12728
+ const authoredPath = (path4) => toAuthoredPointer(
12729
+ themed.sourceMap ?? {},
12730
+ toAuthoredPointer(expanded.sourceMap, path4)
12731
+ );
13165
12732
  const addFact = (raw) => {
13166
12733
  const fact = {
13167
12734
  ...raw,
@@ -13176,7 +12743,7 @@ function prepareDocxQualityDocument(document, options = {}) {
13176
12743
  ...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
13177
12744
  };
13178
12745
  };
13179
- for (const budget of blockSlotBudgets(context.document, expanded.blocks)) {
12746
+ for (const budget of blockSlotBudgets(themed.document, expanded.blocks)) {
13180
12747
  addFact({
13181
12748
  id: `docx:block-slot:${budget.path}`,
13182
12749
  kind: "docx/block-slot",
@@ -13271,7 +12838,19 @@ function prepareDocxQualityDocument(document, options = {}) {
13271
12838
  context.themeName,
13272
12839
  authoredPropsAt(path4)
13273
12840
  );
13274
- if (design) addFact(design);
12841
+ if (design)
12842
+ addFact({
12843
+ ...design,
12844
+ columns: design.columns.map((column) => {
12845
+ const authored = authoredPath(column.path);
12846
+ const table2 = /^(.*)\/props\/columns\/\d+$/.exec(authored)?.[1];
12847
+ return {
12848
+ ...column,
12849
+ path: authored,
12850
+ generated: table2 === void 0 || asRecord(nodeAtPointer(themed.document, table2))?.name !== "table"
12851
+ };
12852
+ })
12853
+ });
13275
12854
  }
13276
12855
  if (node.name === "chart" || node.name === "highcharts") {
13277
12856
  const fact = chartFact(node, props, path4, paletteTokens);
@@ -13326,11 +12905,19 @@ function prepareDocxQualityDocument(document, options = {}) {
13326
12905
  facts,
13327
12906
  provenance,
13328
12907
  renderer: options.renderer ?? DEFAULT_DOCX_RENDERER_ID2,
13329
- ...expanded.blocks.length > 0 && {
12908
+ ...(expanded.blocks.length > 0 || (themed.blockPaths?.length ?? 0) > 0) && {
13330
12909
  metadata: {
13331
12910
  blocks: {
13332
- sourceMap: expanded.sourceMap,
13333
- blocks: expanded.blocks,
12911
+ sourceMap: {
12912
+ ...themed.sourceMap,
12913
+ ...Object.fromEntries(
12914
+ Object.entries(expanded.sourceMap).map(([key, value]) => [
12915
+ key,
12916
+ toAuthoredPointer(themed.sourceMap ?? {}, value)
12917
+ ])
12918
+ )
12919
+ },
12920
+ blocks: [...themed.blockPaths ?? [], ...expanded.blocks],
13334
12921
  document: context.document
13335
12922
  }
13336
12923
  }
@@ -14355,6 +13942,7 @@ var docxTableDesignRule = {
14355
13942
  }
14356
13943
  };
14357
13944
  function alignColumnRight(column) {
13945
+ if (column.generated) return [];
14358
13946
  const operations = [
14359
13947
  column.hasCellDefaults ? {
14360
13948
  op: "add",
@@ -14760,7 +14348,7 @@ function validateComponentProps(schema, props, componentName, opts) {
14760
14348
  componentName
14761
14349
  });
14762
14350
  }
14763
- function validateDocument(document, customComponents, options) {
14351
+ function validateDocument2(document, customComponents, options) {
14764
14352
  const knownCustomNames = new Set(customComponents.map((c) => c.name));
14765
14353
  const documentResult = validateDocumentUnified(document, {
14766
14354
  knownCustomNames,
@@ -15164,7 +14752,7 @@ function createBuilderImpl(state) {
15164
14752
  ...options?.validation
15165
14753
  };
15166
14754
  if (vOpts.enabled !== false) {
15167
- const result = validateDocument(
14755
+ const result = validateDocument2(
15168
14756
  validationDocument,
15169
14757
  state.components,
15170
14758
  { allowUnknownFields: vOpts.allowUnknownFields }
@@ -15179,12 +14767,12 @@ function createBuilderImpl(state) {
15179
14767
  );
15180
14768
  }
15181
14769
  }
15182
- const validateEmitted = vOpts.enabled === false ? void 0 : (emitted, componentLabel) => {
15183
- const result = validateDocument(
14770
+ const validateEmitted = vOpts.enabled === false ? void 0 : (emitted, componentLabel, sourcePath) => {
14771
+ const result = validateDocument2(
15184
14772
  {
15185
14773
  ...internalDocument,
15186
14774
  ...renderer !== void 0 ? { renderer } : {},
15187
- children: emitted
14775
+ children: sourcePath === void 0 ? emitted : [{ name: "section", props: {}, children: emitted }]
15188
14776
  },
15189
14777
  state.components,
15190
14778
  { allowUnknownFields: vOpts.allowUnknownFields }
@@ -15192,7 +14780,10 @@ function createBuilderImpl(state) {
15192
14780
  if (!result.valid) {
15193
14781
  throw new ComponentValidationError2(
15194
14782
  (result.errors ?? []).map((e) => ({
15195
- path: e.path ?? "",
14783
+ path: sourcePath === void 0 ? e.path ?? "" : (e.path ?? "").replace(
14784
+ /^\/children\/0\/children\/\d+/,
14785
+ sourcePath
14786
+ ),
15196
14787
  message: `custom component '${componentLabel}' emitted invalid output \u2014 ${e.message}`
15197
14788
  })),
15198
14789
  emitted
@@ -15210,6 +14801,62 @@ function createBuilderImpl(state) {
15210
14801
  warnings,
15211
14802
  resolveNamedTheme: resolveDocumentTheme
15212
14803
  });
14804
+ if (modedRoot.props.blocks !== void 0) {
14805
+ const expanded = await expandBlocksWithPlugins(
14806
+ modedRoot,
14807
+ modedTheme,
14808
+ new Set(componentMap.keys()),
14809
+ async (component, path4) => {
14810
+ const custom = componentMap.get(String(component.name));
14811
+ const version = resolveComponentVersion(
14812
+ custom.name,
14813
+ custom.versions,
14814
+ component.version
14815
+ );
14816
+ const checked = validateComponentProps(
14817
+ version,
14818
+ component.props ?? {},
14819
+ custom.name,
14820
+ { clean: vOpts.allowUnknownFields === true }
14821
+ );
14822
+ if (!checked.valid)
14823
+ throw new ComponentValidationError2(
14824
+ (checked.errors ?? []).map((e) => ({ path: path4, message: e.message })),
14825
+ component
14826
+ );
14827
+ const output = await version.render({
14828
+ props: cleanComponentProps(version, component.props ?? {}),
14829
+ theme: modedTheme,
14830
+ children: component.children,
14831
+ addWarning: (message, context) => warnings.push({
14832
+ component: custom.name,
14833
+ message,
14834
+ severity: "warning",
14835
+ context
14836
+ })
14837
+ });
14838
+ const emitted = Array.isArray(output) ? output : [output];
14839
+ validateEmitted?.(emitted, custom.name, path4);
14840
+ return emitted;
14841
+ },
14842
+ preserveSet
14843
+ );
14844
+ const [modedDoc2] = normalizeDocument(expanded.document);
14845
+ return {
14846
+ modedRoot,
14847
+ modedTheme,
14848
+ themeName,
14849
+ modedDoc: modedDoc2,
14850
+ sourceMap: expanded.sourceMap,
14851
+ blockPaths: expanded.blocks,
14852
+ warnings,
14853
+ preserveSet,
14854
+ processed: {
14855
+ standard: modedDoc2.children ?? [],
14856
+ preserved: expanded.preserved.children ?? []
14857
+ }
14858
+ };
14859
+ }
15213
14860
  const processed = await processDocumentComponents(
15214
14861
  modedRoot.children || [],
15215
14862
  preserveSet,
@@ -15234,9 +14881,13 @@ function createBuilderImpl(state) {
15234
14881
  }
15235
14882
  async function expandStandardDefinition(document, options) {
15236
14883
  try {
15237
- const { modedDoc, warnings } = await expandDocument(document, options);
14884
+ const { modedDoc, warnings, sourceMap } = await expandDocument(
14885
+ document,
14886
+ options
14887
+ );
15238
14888
  return {
15239
14889
  standardDefinition: modedDoc,
14890
+ ...sourceMap && { sourceMap },
15240
14891
  warnings: warnings.length > 0 ? warnings : null
15241
14892
  };
15242
14893
  } catch (error) {
@@ -15254,6 +14905,8 @@ function createBuilderImpl(state) {
15254
14905
  themeName,
15255
14906
  processed,
15256
14907
  modedDoc,
14908
+ sourceMap,
14909
+ blockPaths,
15257
14910
  warnings,
15258
14911
  preserveSet
15259
14912
  } = await expandDocument(document, options);
@@ -15264,7 +14917,13 @@ function createBuilderImpl(state) {
15264
14917
  const { buffer } = await generateBufferViaIr(modedDoc, {
15265
14918
  // The prologue already ran: the theme had to be resolved before custom
15266
14919
  // components expanded, because each one's `render` is handed it.
15267
- context: { document: modedDoc, theme: modedTheme, themeName },
14920
+ context: {
14921
+ document: modedDoc,
14922
+ theme: modedTheme,
14923
+ themeName,
14924
+ ...sourceMap !== void 0 && { sourceMap },
14925
+ ...blockPaths !== void 0 && { blockPaths }
14926
+ },
15268
14927
  ...state.services ? { services: state.services } : {},
15269
14928
  ...state.fonts ? { fonts: state.fonts } : {},
15270
14929
  ...baseDir !== void 0 ? { baseDir } : {},
@@ -15321,7 +14980,7 @@ function createBuilderImpl(state) {
15321
14980
  function validate(document) {
15322
14981
  try {
15323
14982
  const internalDocument = document;
15324
- const result = validateDocument(
14983
+ const result = validateDocument2(
15325
14984
  internalDocument,
15326
14985
  state.components
15327
14986
  );
@@ -15400,7 +15059,6 @@ function getCoreVersion() {
15400
15059
  return "Core v1.0.0";
15401
15060
  }
15402
15061
  export {
15403
- BLOCK_NAMES,
15404
15062
  ComponentValidationError2 as ComponentValidationError,
15405
15063
  DocumentGenerator as CoreDocumentGenerator,
15406
15064
  DEFAULT_DOCX_RENDERER_ID,
@@ -15415,10 +15073,6 @@ export {
15415
15073
  analyzeDocxQuality,
15416
15074
  blockSlotBudgets,
15417
15075
  cleanComponentProps,
15418
- compileCover,
15419
- compileKeyTakeaways,
15420
- compileRunningHead,
15421
- compileSectionOpener,
15422
15076
  consultingTheme,
15423
15077
  createComponent,
15424
15078
  createDocumentGenerator,
@@ -15449,7 +15103,6 @@ export {
15449
15103
  getExampleNames,
15450
15104
  getVisualPrepassStats,
15451
15105
  hasNodeBuiltins,
15452
- isChromeBlockName,
15453
15106
  isColumnsComponent,
15454
15107
  isDocxRendererId,
15455
15108
  isHeadingComponent,
@@ -15473,12 +15126,10 @@ export {
15473
15126
  resetVisualPrepassStats,
15474
15127
  resolveDocxQualityProfile,
15475
15128
  runExample,
15476
- sectionMeasureTwips,
15477
- sectionTracker,
15478
15129
  themes,
15479
15130
  toAuthoredPointer,
15480
15131
  validateComponentProps,
15481
- validateDocument,
15132
+ validateDocument2 as validateDocument,
15482
15133
  validateJsonSchema,
15483
15134
  validateThemeJsonString,
15484
15135
  vermilionTheme