@json-to-office/core-docx 1.7.0 → 1.9.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.
@@ -1 +1 @@
1
- {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../../src/ir/compiler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAKhE,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,gBAAgB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AA6C3D,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,wBAAwB,CAAC;AAuC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAEL,KAAK,MAAM,EAoCZ,MAAM,SAAS,CAAC;AAiBjB,2DAA2D;AAC3D,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,2EAA2E;IAC3E,QAAQ,EAAE,SAAS,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC;IACrD,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B;;;;OAIG;IACH,WAAW,EAAE,oBAAoB,EAAE,CAAC;CACrC;AA8FD,wBAAgB,eAAe,CAC7B,SAAS,EAAE,iBAAiB,EAC5B,MAAM,EAAE,UAAU,EAClB,QAAQ,GAAE,iBAAiB,EAAO,EAClC,MAAM,GAAE,cAA0B,EAClC,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GACvC,iBAAiB,CAgHnB"}
1
+ {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../../src/ir/compiler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAKhE,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,gBAAgB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AA6C3D,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,wBAAwB,CAAC;AAuC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAEL,KAAK,MAAM,EAqCZ,MAAM,SAAS,CAAC;AAiBjB,2DAA2D;AAC3D,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,2EAA2E;IAC3E,QAAQ,EAAE,SAAS,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC;IACrD,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B;;;;OAIG;IACH,WAAW,EAAE,oBAAoB,EAAE,CAAC;CACrC;AA8FD,wBAAgB,eAAe,CAC7B,SAAS,EAAE,iBAAiB,EAC5B,MAAM,EAAE,UAAU,EAClB,QAAQ,GAAE,iBAAiB,EAAO,EAClC,MAAM,GAAE,cAA0B,EAClC,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GACvC,iBAAiB,CAgHnB"}
@@ -1182,6 +1182,15 @@ function resolveFontFamily(theme, fontRef) {
1182
1182
  }
1183
1183
  return theme.fonts[fontRef]?.family || theme.fonts.body.family;
1184
1184
  }
1185
+ function resolveFontSize(theme, fontRef) {
1186
+ if (!theme?.fonts?.body?.size) {
1187
+ return void 0;
1188
+ }
1189
+ if (!fontRef) {
1190
+ return theme.fonts.body.size;
1191
+ }
1192
+ return theme.fonts[fontRef]?.size || theme.fonts.body.size;
1193
+ }
1185
1194
  function resolveFontProperties(theme, fontRef) {
1186
1195
  const targetFontKey = fontRef || "body";
1187
1196
  if (!theme?.fonts?.[targetFontKey]) {
@@ -2484,6 +2493,14 @@ function paragraphOptions(formatting) {
2484
2493
  if (formatting.outlineLevel !== void 0) {
2485
2494
  options.outlineLevel = formatting.outlineLevel;
2486
2495
  }
2496
+ if (formatting.borders) {
2497
+ const borders3 = {};
2498
+ for (const side of ["top", "bottom", "left", "right", "between"]) {
2499
+ const emitted = emitBorder(formatting.borders[side]);
2500
+ if (emitted) borders3[side] = emitted;
2501
+ }
2502
+ if (Object.keys(borders3).length > 0) options.border = borders3;
2503
+ }
2487
2504
  return options;
2488
2505
  }
2489
2506
  function emitParagraph(block2, resources = /* @__PURE__ */ new Map()) {
@@ -2667,7 +2684,11 @@ function emitBorder(border3) {
2667
2684
  return {
2668
2685
  style: BORDER_STYLE[border3.style] ?? BorderStyle.SINGLE,
2669
2686
  size: border3.sizeEighthPoints ?? 0,
2670
- color: border3.color?.hex ?? "000000"
2687
+ color: border3.color?.hex ?? "000000",
2688
+ // Stated only when the IR states it, so every border that predates the
2689
+ // rule component emits exactly the bytes it did before. office-open
2690
+ // already carries `w:space`; this is what keeps the two agreeing.
2691
+ ...border3.spacePoints !== void 0 ? { space: border3.spacePoints } : {}
2671
2692
  };
2672
2693
  }
2673
2694
  var ALIGNMENT2, PAGE_FIELD, WRAP_TYPE, VERTICAL_ALIGN, TABLE_ANCHOR, HORIZONTAL_POSITION, VERTICAL_POSITION, BORDER_STYLE;
@@ -3337,6 +3358,7 @@ var init_docxjs = __esm({
3337
3358
  "endnotes",
3338
3359
  "revisions",
3339
3360
  "breaks",
3361
+ "borders",
3340
3362
  "tab-stops",
3341
3363
  "proofing-language",
3342
3364
  "custom-properties"
@@ -4636,6 +4658,7 @@ var init_office_open = __esm({
4636
4658
  "endnotes",
4637
4659
  "revisions",
4638
4660
  "breaks",
4661
+ "borders",
4639
4662
  "tab-stops",
4640
4663
  "proofing-language",
4641
4664
  "custom-properties"
@@ -6690,17 +6713,17 @@ function createDocumentStyles(themeNameOrObject = "minimal", language) {
6690
6713
  "subtitle"
6691
6714
  ]);
6692
6715
  if (theme.styles) {
6693
- for (const [styleKey, styleValue] of Object.entries(theme.styles)) {
6694
- if (predefinedStyleKeys.has(styleKey)) {
6716
+ for (const [styleKey2, styleValue] of Object.entries(theme.styles)) {
6717
+ if (predefinedStyleKeys.has(styleKey2)) {
6695
6718
  continue;
6696
6719
  }
6697
6720
  if (!styleValue) {
6698
6721
  continue;
6699
6722
  }
6700
- const tocMatch = /^TOC([1-9])$/.exec(styleKey);
6701
- const customStyle = tocMatch ? styleValue : resolveStyleWithBaseStyle(theme, styleKey) || styleValue;
6702
- const styleId = tocMatch ? `TOC${tocMatch[1]}` : styleKey;
6703
- const styleName = tocMatch ? `TOC ${tocMatch[1]}` : styleKey.replace(/([A-Z])/g, " $1").trim();
6723
+ const tocMatch = /^TOC([1-9])$/.exec(styleKey2);
6724
+ const customStyle = tocMatch ? styleValue : resolveStyleWithBaseStyle(theme, styleKey2) || styleValue;
6725
+ const styleId = tocMatch ? `TOC${tocMatch[1]}` : styleKey2;
6726
+ const styleName = tocMatch ? `TOC ${tocMatch[1]}` : styleKey2.replace(/([A-Z])/g, " $1").trim();
6704
6727
  const mapBaseStyleId = (base) => {
6705
6728
  if (!base) return "Normal";
6706
6729
  const lower = base.toLowerCase();
@@ -8624,6 +8647,8 @@ function compileComponent(component, scope) {
8624
8647
  return compileStatistic(component, scope);
8625
8648
  case "toc":
8626
8649
  return compileToc(component, scope);
8650
+ case "rule":
8651
+ return compileRule(component, scope);
8627
8652
  case "text-box":
8628
8653
  return compileTextBox(component, scope);
8629
8654
  case "columns":
@@ -9200,6 +9225,100 @@ function compileToc(component, scope) {
9200
9225
  });
9201
9226
  return blocks;
9202
9227
  }
9228
+ var DEFAULT_RULE_SPACING_PT = 6;
9229
+ var DEFAULT_RULE_THICKNESS_PT = 1;
9230
+ var RULE_LINE_TWIPS = 20;
9231
+ var RULE_BORDER_STYLE = {
9232
+ solid: "single",
9233
+ dashed: "dashed",
9234
+ dotted: "dotted",
9235
+ double: "double"
9236
+ };
9237
+ function compileRule(component, scope) {
9238
+ const { ctx, path: path3 } = scope;
9239
+ const props = component.props ?? {};
9240
+ ctx.features.require("borders", path3);
9241
+ const thicknessPt = Math.min(
9242
+ 12,
9243
+ Math.max(
9244
+ 0.25,
9245
+ typeof props.thickness === "number" && Number.isFinite(props.thickness) ? props.thickness : DEFAULT_RULE_THICKNESS_PT
9246
+ )
9247
+ );
9248
+ const spacing = props.spacing ?? {};
9249
+ const beforePt = typeof spacing.before === "number" && Number.isFinite(spacing.before) ? spacing.before : DEFAULT_RULE_SPACING_PT;
9250
+ const afterPt = typeof spacing.after === "number" && Number.isFinite(spacing.after) ? spacing.after : DEFAULT_RULE_SPACING_PT;
9251
+ return [
9252
+ {
9253
+ kind: "paragraph",
9254
+ id: scope.id,
9255
+ path: path3,
9256
+ styleId: "Normal",
9257
+ children: [],
9258
+ formatting: {
9259
+ spacing: {
9260
+ beforeTwips: pointsToTwips2(beforePt),
9261
+ afterTwips: pointsToTwips2(afterPt),
9262
+ lineTwips: RULE_LINE_TWIPS,
9263
+ lineRule: "exact"
9264
+ },
9265
+ ...ruleIndent(props, ctx, path3),
9266
+ borders: {
9267
+ bottom: {
9268
+ style: RULE_BORDER_STYLE[String(props.style)] ?? "single",
9269
+ sizeEighthPoints: pointsToEighthPoints(thicknessPt),
9270
+ color: irColor(
9271
+ resolveColor(String(props.color ?? "border"), ctx.theme)
9272
+ ),
9273
+ // The border hangs off the paragraph edge; any gap the author
9274
+ // wants belongs to `spacing`, where they can see it.
9275
+ spacePoints: 0
9276
+ }
9277
+ }
9278
+ }
9279
+ }
9280
+ ];
9281
+ }
9282
+ function ruleIndent(props, ctx, path3) {
9283
+ const width = props.width;
9284
+ if (width === void 0) return {};
9285
+ const page = getPageSetup(ctx.theme);
9286
+ const measureTwips = Math.max(
9287
+ 0,
9288
+ page.size.width - page.margin.left - page.margin.right
9289
+ );
9290
+ let usedTwips;
9291
+ if (typeof width === "number" && Number.isFinite(width)) {
9292
+ usedTwips = pointsToTwips2(width);
9293
+ } else if (typeof width === "string" && width.trim().endsWith("%")) {
9294
+ const percent = Number(width.trim().slice(0, -1));
9295
+ if (Number.isFinite(percent)) {
9296
+ usedTwips = Math.round(measureTwips * percent / 100);
9297
+ }
9298
+ }
9299
+ if (usedTwips === void 0) return {};
9300
+ const remainder = measureTwips - usedTwips;
9301
+ if (remainder <= 0) {
9302
+ if (remainder < 0) {
9303
+ warnOnce(
9304
+ ctx,
9305
+ "rule",
9306
+ `Rule width ${JSON.stringify(width)} at ${path3} is wider than the ${Math.round(measureTwips / 20)}pt text measure; it runs the full measure instead.`
9307
+ );
9308
+ }
9309
+ return {};
9310
+ }
9311
+ switch (props.alignment) {
9312
+ case "right":
9313
+ return { indent: { leftTwips: remainder } };
9314
+ case "center": {
9315
+ const half = Math.round(remainder / 2);
9316
+ return { indent: { leftTwips: half, rightTwips: remainder - half } };
9317
+ }
9318
+ default:
9319
+ return { indent: { rightTwips: remainder } };
9320
+ }
9321
+ }
9203
9322
  var TREND_GLYPHS = {
9204
9323
  up: "\u25B2",
9205
9324
  down: "\u25BC",
@@ -11196,8 +11315,8 @@ function getHeadingDefaults(theme) {
11196
11315
  function getHeadingDefaultsForLevel(theme, level) {
11197
11316
  const defaults = {};
11198
11317
  if (theme.styles) {
11199
- const styleKey = `heading${level}`;
11200
- const headingStyle = theme.styles[styleKey];
11318
+ const styleKey2 = `heading${level}`;
11319
+ const headingStyle = theme.styles[styleKey2];
11201
11320
  if (headingStyle?.alignment) {
11202
11321
  defaults.alignment = headingStyle.alignment;
11203
11322
  }
@@ -11545,6 +11664,8 @@ function normalizeDocument(document) {
11545
11664
  }
11546
11665
 
11547
11666
  // src/quality/facts.ts
11667
+ init_styleHelpers();
11668
+ init_defaults();
11548
11669
  init_widthUtils();
11549
11670
  function asRecord(value) {
11550
11671
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
@@ -11634,6 +11755,70 @@ function characterSpacingPt(spacing) {
11634
11755
  if (rule === void 0 || value === void 0) return 0;
11635
11756
  return rule.type === "condensed" ? -value / TWIPS_PER_POINT3 : value / TWIPS_PER_POINT3;
11636
11757
  }
11758
+ function pointerExists(root, pointer) {
11759
+ let current = root;
11760
+ for (const token of pointer.split("/").slice(1)) {
11761
+ if (Array.isArray(current)) {
11762
+ const index = Number(token);
11763
+ if (!Number.isInteger(index)) return false;
11764
+ current = current[index];
11765
+ continue;
11766
+ }
11767
+ const record = asRecord(current);
11768
+ if (!record) return false;
11769
+ current = record[token];
11770
+ }
11771
+ return current !== void 0;
11772
+ }
11773
+ function styleKey(node, props) {
11774
+ if (node.name === "heading") {
11775
+ return `heading${finiteNumber(props.level) ?? 1}`;
11776
+ }
11777
+ return typeof props.themeStyle === "string" && props.themeStyle !== "" ? props.themeStyle : "normal";
11778
+ }
11779
+ function effectiveFontSize(node, props, typography) {
11780
+ const authored = finiteNumber(asRecord(props.font)?.size);
11781
+ if (authored !== void 0) return { fontSizePt: authored, authored: true };
11782
+ const { styles, theme } = typography;
11783
+ const style = asRecord(styles[styleKey(node, props)]) ?? asRecord(styles.normal);
11784
+ const stated = finiteNumber(style?.size);
11785
+ if (stated !== void 0) return { fontSizePt: stated, authored: false };
11786
+ const reference = style?.font === "heading" || style?.font === "body" || style?.font === "mono" || style?.font === "light" ? style.font : void 0;
11787
+ const inherited = resolveFontSize(theme, reference);
11788
+ return inherited === void 0 ? void 0 : { fontSizePt: inherited, authored: false };
11789
+ }
11790
+ function pinnedLineBox(props, path3) {
11791
+ const candidates = [
11792
+ [props.lineSpacing, `${path3}/props/lineSpacing`],
11793
+ [asRecord(props.font)?.lineSpacing, `${path3}/props/font/lineSpacing`]
11794
+ ];
11795
+ for (const [spacing, pointer] of candidates) {
11796
+ if (spacing === void 0) continue;
11797
+ const rule = asRecord(spacing);
11798
+ if (rule?.type !== "exactly") return void 0;
11799
+ const lineBoxPt = finiteNumber(rule.value);
11800
+ return lineBoxPt === void 0 ? void 0 : { lineBoxPt, pointer };
11801
+ }
11802
+ return void 0;
11803
+ }
11804
+ function lineBoxFact(node, props, path3, typography, authored) {
11805
+ if (typeof props.text !== "string" || props.text.trim() === "") {
11806
+ return void 0;
11807
+ }
11808
+ const pinned = pinnedLineBox(props, path3);
11809
+ if (!pinned) return void 0;
11810
+ const size = effectiveFontSize(node, props, typography);
11811
+ if (size === void 0) return void 0;
11812
+ return {
11813
+ id: `docx:line-box:${path3}`,
11814
+ kind: "docx/line-box",
11815
+ path: pinned.pointer,
11816
+ lineBoxPt: pinned.lineBoxPt,
11817
+ fontSizePt: size.fontSizePt,
11818
+ fontSizeAuthored: size.authored,
11819
+ patchable: pointerExists(authored, pinned.pointer)
11820
+ };
11821
+ }
11637
11822
  function frameSignature(floating) {
11638
11823
  const horizontal = asRecord(floating.horizontalPosition);
11639
11824
  const vertical = asRecord(floating.verticalPosition);
@@ -11750,6 +11935,10 @@ function prepareDocxQualityDocument(document, options = {}) {
11750
11935
  });
11751
11936
  const resolved = resolveDocumentTree(context.document, context.theme);
11752
11937
  const basePage = pageBox(resolved.theme, context.themeName);
11938
+ const typography = {
11939
+ styles: getThemeStyles(resolved.theme),
11940
+ theme: resolved.theme
11941
+ };
11753
11942
  const facts = [];
11754
11943
  const provenance = {};
11755
11944
  let previousHeadingLevel;
@@ -11780,6 +11969,10 @@ function prepareDocxQualityDocument(document, options = {}) {
11780
11969
  if (fact) addFact(fact);
11781
11970
  }
11782
11971
  }
11972
+ if (node.name === "paragraph" || node.name === "heading") {
11973
+ const fact = lineBoxFact(node, props, path3, typography, context.document);
11974
+ if (fact) addFact(fact);
11975
+ }
11783
11976
  if (node.name === "image" || node.name === "visual") {
11784
11977
  for (const fact of svgTextFacts(props, path3)) addFact(fact);
11785
11978
  }