@json-to-office/core-docx 2.7.0 → 3.0.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();
@@ -13161,7 +12724,10 @@ function prepareDocxQualityDocument(document, options = {}) {
13161
12724
  const facts = [];
13162
12725
  const provenance = {};
13163
12726
  let previousHeadingLevel;
13164
- const authoredPath = (path4) => toAuthoredPointer(expanded.sourceMap, path4);
12727
+ const authoredPath = (path4) => toAuthoredPointer(
12728
+ themed.sourceMap ?? {},
12729
+ toAuthoredPointer(expanded.sourceMap, path4)
12730
+ );
13165
12731
  const addFact = (raw) => {
13166
12732
  const fact = {
13167
12733
  ...raw,
@@ -13176,7 +12742,7 @@ function prepareDocxQualityDocument(document, options = {}) {
13176
12742
  ...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
13177
12743
  };
13178
12744
  };
13179
- for (const budget of blockSlotBudgets(context.document, expanded.blocks)) {
12745
+ for (const budget of blockSlotBudgets(themed.document, expanded.blocks)) {
13180
12746
  addFact({
13181
12747
  id: `docx:block-slot:${budget.path}`,
13182
12748
  kind: "docx/block-slot",
@@ -13326,11 +12892,19 @@ function prepareDocxQualityDocument(document, options = {}) {
13326
12892
  facts,
13327
12893
  provenance,
13328
12894
  renderer: options.renderer ?? DEFAULT_DOCX_RENDERER_ID2,
13329
- ...expanded.blocks.length > 0 && {
12895
+ ...(expanded.blocks.length > 0 || (themed.blockPaths?.length ?? 0) > 0) && {
13330
12896
  metadata: {
13331
12897
  blocks: {
13332
- sourceMap: expanded.sourceMap,
13333
- blocks: expanded.blocks,
12898
+ sourceMap: {
12899
+ ...themed.sourceMap,
12900
+ ...Object.fromEntries(
12901
+ Object.entries(expanded.sourceMap).map(([key, value]) => [
12902
+ key,
12903
+ toAuthoredPointer(themed.sourceMap ?? {}, value)
12904
+ ])
12905
+ )
12906
+ },
12907
+ blocks: [...themed.blockPaths ?? [], ...expanded.blocks],
13334
12908
  document: context.document
13335
12909
  }
13336
12910
  }
@@ -14760,7 +14334,7 @@ function validateComponentProps(schema, props, componentName, opts) {
14760
14334
  componentName
14761
14335
  });
14762
14336
  }
14763
- function validateDocument(document, customComponents, options) {
14337
+ function validateDocument2(document, customComponents, options) {
14764
14338
  const knownCustomNames = new Set(customComponents.map((c) => c.name));
14765
14339
  const documentResult = validateDocumentUnified(document, {
14766
14340
  knownCustomNames,
@@ -15164,7 +14738,7 @@ function createBuilderImpl(state) {
15164
14738
  ...options?.validation
15165
14739
  };
15166
14740
  if (vOpts.enabled !== false) {
15167
- const result = validateDocument(
14741
+ const result = validateDocument2(
15168
14742
  validationDocument,
15169
14743
  state.components,
15170
14744
  { allowUnknownFields: vOpts.allowUnknownFields }
@@ -15179,12 +14753,12 @@ function createBuilderImpl(state) {
15179
14753
  );
15180
14754
  }
15181
14755
  }
15182
- const validateEmitted = vOpts.enabled === false ? void 0 : (emitted, componentLabel) => {
15183
- const result = validateDocument(
14756
+ const validateEmitted = vOpts.enabled === false ? void 0 : (emitted, componentLabel, sourcePath) => {
14757
+ const result = validateDocument2(
15184
14758
  {
15185
14759
  ...internalDocument,
15186
14760
  ...renderer !== void 0 ? { renderer } : {},
15187
- children: emitted
14761
+ children: sourcePath === void 0 ? emitted : [{ name: "section", props: {}, children: emitted }]
15188
14762
  },
15189
14763
  state.components,
15190
14764
  { allowUnknownFields: vOpts.allowUnknownFields }
@@ -15192,7 +14766,10 @@ function createBuilderImpl(state) {
15192
14766
  if (!result.valid) {
15193
14767
  throw new ComponentValidationError2(
15194
14768
  (result.errors ?? []).map((e) => ({
15195
- path: e.path ?? "",
14769
+ path: sourcePath === void 0 ? e.path ?? "" : (e.path ?? "").replace(
14770
+ /^\/children\/0\/children\/\d+/,
14771
+ sourcePath
14772
+ ),
15196
14773
  message: `custom component '${componentLabel}' emitted invalid output \u2014 ${e.message}`
15197
14774
  })),
15198
14775
  emitted
@@ -15210,6 +14787,62 @@ function createBuilderImpl(state) {
15210
14787
  warnings,
15211
14788
  resolveNamedTheme: resolveDocumentTheme
15212
14789
  });
14790
+ if (modedRoot.props.blocks !== void 0) {
14791
+ const expanded = await expandBlocksWithPlugins(
14792
+ modedRoot,
14793
+ modedTheme,
14794
+ new Set(componentMap.keys()),
14795
+ async (component, path4) => {
14796
+ const custom = componentMap.get(String(component.name));
14797
+ const version = resolveComponentVersion(
14798
+ custom.name,
14799
+ custom.versions,
14800
+ component.version
14801
+ );
14802
+ const checked = validateComponentProps(
14803
+ version,
14804
+ component.props ?? {},
14805
+ custom.name,
14806
+ { clean: vOpts.allowUnknownFields === true }
14807
+ );
14808
+ if (!checked.valid)
14809
+ throw new ComponentValidationError2(
14810
+ (checked.errors ?? []).map((e) => ({ path: path4, message: e.message })),
14811
+ component
14812
+ );
14813
+ const output = await version.render({
14814
+ props: cleanComponentProps(version, component.props ?? {}),
14815
+ theme: modedTheme,
14816
+ children: component.children,
14817
+ addWarning: (message, context) => warnings.push({
14818
+ component: custom.name,
14819
+ message,
14820
+ severity: "warning",
14821
+ context
14822
+ })
14823
+ });
14824
+ const emitted = Array.isArray(output) ? output : [output];
14825
+ validateEmitted?.(emitted, custom.name, path4);
14826
+ return emitted;
14827
+ },
14828
+ preserveSet
14829
+ );
14830
+ const [modedDoc2] = normalizeDocument(expanded.document);
14831
+ return {
14832
+ modedRoot,
14833
+ modedTheme,
14834
+ themeName,
14835
+ modedDoc: modedDoc2,
14836
+ sourceMap: expanded.sourceMap,
14837
+ blockPaths: expanded.blocks,
14838
+ warnings,
14839
+ preserveSet,
14840
+ processed: {
14841
+ standard: modedDoc2.children ?? [],
14842
+ preserved: expanded.preserved.children ?? []
14843
+ }
14844
+ };
14845
+ }
15213
14846
  const processed = await processDocumentComponents(
15214
14847
  modedRoot.children || [],
15215
14848
  preserveSet,
@@ -15234,9 +14867,13 @@ function createBuilderImpl(state) {
15234
14867
  }
15235
14868
  async function expandStandardDefinition(document, options) {
15236
14869
  try {
15237
- const { modedDoc, warnings } = await expandDocument(document, options);
14870
+ const { modedDoc, warnings, sourceMap } = await expandDocument(
14871
+ document,
14872
+ options
14873
+ );
15238
14874
  return {
15239
14875
  standardDefinition: modedDoc,
14876
+ ...sourceMap && { sourceMap },
15240
14877
  warnings: warnings.length > 0 ? warnings : null
15241
14878
  };
15242
14879
  } catch (error) {
@@ -15254,6 +14891,8 @@ function createBuilderImpl(state) {
15254
14891
  themeName,
15255
14892
  processed,
15256
14893
  modedDoc,
14894
+ sourceMap,
14895
+ blockPaths,
15257
14896
  warnings,
15258
14897
  preserveSet
15259
14898
  } = await expandDocument(document, options);
@@ -15264,7 +14903,13 @@ function createBuilderImpl(state) {
15264
14903
  const { buffer } = await generateBufferViaIr(modedDoc, {
15265
14904
  // The prologue already ran: the theme had to be resolved before custom
15266
14905
  // components expanded, because each one's `render` is handed it.
15267
- context: { document: modedDoc, theme: modedTheme, themeName },
14906
+ context: {
14907
+ document: modedDoc,
14908
+ theme: modedTheme,
14909
+ themeName,
14910
+ ...sourceMap !== void 0 && { sourceMap },
14911
+ ...blockPaths !== void 0 && { blockPaths }
14912
+ },
15268
14913
  ...state.services ? { services: state.services } : {},
15269
14914
  ...state.fonts ? { fonts: state.fonts } : {},
15270
14915
  ...baseDir !== void 0 ? { baseDir } : {},
@@ -15321,7 +14966,7 @@ function createBuilderImpl(state) {
15321
14966
  function validate(document) {
15322
14967
  try {
15323
14968
  const internalDocument = document;
15324
- const result = validateDocument(
14969
+ const result = validateDocument2(
15325
14970
  internalDocument,
15326
14971
  state.components
15327
14972
  );
@@ -15400,7 +15045,6 @@ function getCoreVersion() {
15400
15045
  return "Core v1.0.0";
15401
15046
  }
15402
15047
  export {
15403
- BLOCK_NAMES,
15404
15048
  ComponentValidationError2 as ComponentValidationError,
15405
15049
  DocumentGenerator as CoreDocumentGenerator,
15406
15050
  DEFAULT_DOCX_RENDERER_ID,
@@ -15415,10 +15059,6 @@ export {
15415
15059
  analyzeDocxQuality,
15416
15060
  blockSlotBudgets,
15417
15061
  cleanComponentProps,
15418
- compileCover,
15419
- compileKeyTakeaways,
15420
- compileRunningHead,
15421
- compileSectionOpener,
15422
15062
  consultingTheme,
15423
15063
  createComponent,
15424
15064
  createDocumentGenerator,
@@ -15449,7 +15089,6 @@ export {
15449
15089
  getExampleNames,
15450
15090
  getVisualPrepassStats,
15451
15091
  hasNodeBuiltins,
15452
- isChromeBlockName,
15453
15092
  isColumnsComponent,
15454
15093
  isDocxRendererId,
15455
15094
  isHeadingComponent,
@@ -15473,12 +15112,10 @@ export {
15473
15112
  resetVisualPrepassStats,
15474
15113
  resolveDocxQualityProfile,
15475
15114
  runExample,
15476
- sectionMeasureTwips,
15477
- sectionTracker,
15478
15115
  themes,
15479
15116
  toAuthoredPointer,
15480
15117
  validateComponentProps,
15481
- validateDocument,
15118
+ validateDocument2 as validateDocument,
15482
15119
  validateJsonSchema,
15483
15120
  validateThemeJsonString,
15484
15121
  vermilionTheme