@json-to-office/core-docx 7.7.1 → 7.7.3

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.
@@ -5091,6 +5091,12 @@ function buildDocument(ir, resources = /* @__PURE__ */ new Map(), pageNumbers, e
5091
5091
  updateFields: ir.settings.updateFields,
5092
5092
  ...ir.settings.trackRevisions ? { trackRevisions: true } : {}
5093
5093
  },
5094
+ // A text's `\n` is a manual line break inside its paragraph, and Word
5095
+ // stretches the line before one across the whole width of a justified
5096
+ // paragraph — a two-word line spread edge to edge. `w:doNotExpandShiftReturn`
5097
+ // sets that line flush like the last line of a paragraph, the way every
5098
+ // reader expects. docx keeps compatibility mode 15 alongside it.
5099
+ compatibility: { doNotExpandShiftReturn: true },
5094
5100
  ...ir.numbering.length > 0 ? { numbering: { config: ir.numbering.map(numberingConfig) } } : {},
5095
5101
  // word/footnotes.xml and word/endnotes.xml, keyed by the id their
5096
5102
  // references carry.
@@ -6812,7 +6818,19 @@ async function buildDocumentOptions(ir, charts2 = [], svgRasterFallback) {
6812
6818
  ...coreProperties2(ir),
6813
6819
  settings: {
6814
6820
  updateFields: ir.settings.updateFields,
6815
- ...ir.settings.trackRevisions ? { trackRevisions: true } : {}
6821
+ ...ir.settings.trackRevisions ? { trackRevisions: true } : {},
6822
+ // A text's `\n` is a manual line break inside its paragraph, and Word
6823
+ // stretches the line before one across the whole width of a justified
6824
+ // paragraph. `w:doNotExpandShiftReturn` sets that line flush instead.
6825
+ // Passing `compatibility` replaces @office-open/docx's fresh-document
6826
+ // defaults rather than extending them, so they are restated here.
6827
+ compatibility: {
6828
+ version: 15,
6829
+ overrideTableStyleFontSizeAndJustification: true,
6830
+ enableOpenTypeFeatures: true,
6831
+ doNotFlipMirrorIndents: true,
6832
+ doNotExpandShiftReturn: true
6833
+ }
6816
6834
  },
6817
6835
  ...ir.numbering.length > 0 ? {
6818
6836
  numbering: {
@@ -8359,292 +8377,504 @@ import {
8359
8377
 
8360
8378
  // src/ir/nativeVisual.ts
8361
8379
  init_units();
8362
- init_imageUtils();
8363
- var PIXELS_PER_INCH2 = 96;
8364
- var DEFAULT_WIDTH_FRACTION = 0.75;
8365
- var ASSUMED_FONT_SIZE_POINTS = 18;
8366
- var MIN_DERIVED_TEXT_HEIGHT_INCHES = 0.5;
8367
- var DERIVED_LINE_HEIGHT_RATIO = 1.6;
8368
- var GEOMETRY_ALIASES = {
8369
- arrow: "rightArrow",
8370
- lightning: "lightningBolt"
8371
- };
8372
- var DASH_VALUES = /* @__PURE__ */ new Set([
8373
- "solid",
8374
- "dash",
8375
- "dot",
8376
- "dashDot"
8377
- ]);
8378
- var ANCHOR = {
8379
- top: "top",
8380
- middle: "middle",
8381
- bottom: "bottom"
8382
- };
8383
- var ALIGNMENT = {
8384
- left: "left",
8385
- center: "center",
8386
- right: "right",
8387
- justify: "justified"
8388
- };
8389
- function compileNativeVisualGroup(props, outer) {
8390
- const canvas = {
8391
- widthEmu: inchesToEmu(props.canvas.width),
8392
- heightEmu: inchesToEmu(props.canvas.height)
8393
- };
8394
- const children = [];
8395
- let refused = false;
8396
- const deps = {
8397
- ...outer,
8398
- reject: (detail) => {
8399
- refused = true;
8400
- outer.reject(detail);
8380
+
8381
+ // src/utils/unicode.ts
8382
+ function normalizeUnicodeText(text) {
8383
+ return (text ?? "").normalize("NFC");
8384
+ }
8385
+
8386
+ // src/ir/inline.ts
8387
+ function escapeRegExp(value) {
8388
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8389
+ }
8390
+ function buildNoProofWordsRegex(noProofWords2) {
8391
+ const words = (noProofWords2 ?? []).filter((word) => word?.trim().length > 0);
8392
+ if (words.length === 0) return null;
8393
+ const escaped = words.slice().sort((a, b) => b.length - a.length).map(escapeRegExp);
8394
+ return new RegExp(
8395
+ `(?<![\\p{L}\\p{N}])(?:${escaped.join("|")})(?![\\p{L}\\p{N}])`,
8396
+ "giu"
8397
+ );
8398
+ }
8399
+ var DECORATOR = /(\*\*\*|___)([\s\S]*?)\1|(\*\*|__)([\s\S]*?)\3|(\*|_)([\s\S]*?)\5/g;
8400
+ var ESCAPABLE = "*_[]{}\\";
8401
+ var ESCAPE = /\\([*_[\]{}\\])/g;
8402
+ var SENTINEL = "\uE000";
8403
+ var SENTINEL_SELF = "x";
8404
+ var SENTINEL_DIGIT_BASE = 48;
8405
+ function encodeEscapes(text) {
8406
+ const authored = text.includes(SENTINEL);
8407
+ if (!authored && !text.includes("\\")) return text;
8408
+ const guarded = authored ? text.split(SENTINEL).join(SENTINEL + SENTINEL_SELF) : text;
8409
+ return guarded.replace(
8410
+ ESCAPE,
8411
+ (_whole, char) => SENTINEL + String.fromCharCode(SENTINEL_DIGIT_BASE + ESCAPABLE.indexOf(char))
8412
+ );
8413
+ }
8414
+ function decodeEscapes(text) {
8415
+ if (!text.includes(SENTINEL)) return text;
8416
+ let out = "";
8417
+ for (let i = 0; i < text.length; i += 1) {
8418
+ if (text[i] !== SENTINEL) {
8419
+ out += text[i];
8420
+ continue;
8401
8421
  }
8402
- };
8403
- const background = props.canvas.background;
8404
- if (background?.color) {
8405
- const color = deps.color(background.color);
8406
- if (!color) {
8407
- deps.reject(`canvas.background.color "${background.color}"`);
8408
- } else {
8409
- children.push({
8410
- kind: "shape",
8411
- frame: fullBleed(canvas),
8412
- geometry: "rect",
8413
- fill: { kind: "solid", color },
8414
- name: "Canvas background"
8415
- });
8422
+ const marker = text[i + 1];
8423
+ if (marker === SENTINEL_SELF) {
8424
+ out += SENTINEL;
8425
+ i += 1;
8426
+ continue;
8416
8427
  }
8417
- }
8418
- if (background?.image) {
8419
- const source = resolveImageSource(background.image);
8420
- const resource = source ? deps.picture(source) : void 0;
8421
- if (!resource) {
8422
- deps.reject("canvas.background.image could not be loaded");
8423
- } else {
8424
- children.push({
8425
- kind: "picture",
8426
- frame: fullBleed(canvas),
8427
- resourceId: resource.resourceId,
8428
- name: "Canvas background"
8429
- });
8428
+ const index = marker === void 0 ? -1 : marker.charCodeAt(0) - SENTINEL_DIGIT_BASE;
8429
+ if (index >= 0 && index < ESCAPABLE.length) {
8430
+ out += ESCAPABLE[index];
8431
+ i += 1;
8432
+ continue;
8430
8433
  }
8434
+ out += text[i];
8431
8435
  }
8432
- for (const [index, element] of (props.elements ?? []).entries()) {
8433
- if (element.enabled === false) continue;
8434
- const child = compileElement(element, index, canvas, deps);
8435
- if (child) children.push(child);
8436
- }
8437
- return refused ? void 0 : { canvas, children };
8436
+ return out;
8438
8437
  }
8439
- function compileElement(element, index, canvas, deps) {
8440
- const at = `elements[${index}]`;
8441
- switch (element.name) {
8442
- case "text":
8443
- return compileText(element.props, at, canvas, deps);
8444
- case "shape":
8445
- return compileShape(element.props, at, canvas, deps);
8446
- case "image":
8447
- return compileImage(element.props, at, canvas, deps);
8448
- default:
8449
- deps.reject(`${at} is a "${element.name}"`);
8450
- return void 0;
8451
- }
8438
+ var LINK = /\[([^\]]+)\]\(([^)]+)\)/g;
8439
+ var DECORATOR_OR_PLACEHOLDER = /(\*\*\*|___)([\s\S]*?)\1|(\*\*|__)([\s\S]*?)\3|(\*|_)([\s\S]*?)\5|\{([^}]+)\}/g;
8440
+ var PLACEHOLDER = /\{([^}]+)\}/;
8441
+ var UNSUPPORTED_SYNTAX = [];
8442
+ var CROSS_REFERENCE = /\[@([^\]\s:]+)(?::(relative|no_context|full_context|none))?\]/;
8443
+ var INLINE_TOKEN = new RegExp(
8444
+ `${LINK.source}|${CROSS_REFERENCE.source}`,
8445
+ "g"
8446
+ );
8447
+ var NOTE_MARKER = /\[\^([^\]\s]+)\]/g;
8448
+ function containsPlaceholder(text) {
8449
+ return PLACEHOLDER.test(text);
8452
8450
  }
8453
- function compileText(props, at, canvas, deps) {
8454
- const runs = textRuns(props);
8455
- if (!runs) {
8456
- deps.warn(
8457
- `[core-docx] native visual ${at} has neither "text" nor "runs"; nothing was drawn for it.`
8458
- );
8459
- return void 0;
8451
+ function containsCrossReference(text) {
8452
+ return new RegExp(CROSS_REFERENCE.source).test(text);
8453
+ }
8454
+ function containsLink(text) {
8455
+ return new RegExp(LINK.source).test(text);
8456
+ }
8457
+ function containsUnsupportedSyntax(text) {
8458
+ for (const { pattern, what } of UNSUPPORTED_SYNTAX) {
8459
+ if (pattern.test(text)) return what;
8460
8460
  }
8461
- const colors = resolveColors(
8462
- [
8463
- ["color", props.color],
8464
- ["fill.color", props.fill?.color],
8465
- ...runs.map((run, i) => [`runs[${i}].color`, run.color]),
8466
- ...runs.map(
8467
- (run, i) => [
8468
- `runs[${i}].underline.color`,
8469
- underlineColor(run.underline)
8470
- ]
8471
- ),
8472
- ["underline.color", underlineColor(props.underline)]
8473
- ],
8474
- at,
8475
- deps
8476
- );
8477
- if (!colors) return void 0;
8478
- const frame = resolveFrame(props, canvas, {
8479
- defaultHeightInches: derivedTextHeightInches(props, runs)
8480
- });
8481
- const paragraphs = textParagraphs(runs, props, colors, at);
8482
- return {
8483
- kind: "shape",
8484
- frame,
8485
- geometry: "rect",
8486
- isTextBox: true,
8487
- // A text box with no fill is transparent, which is what the rasterized
8488
- // path draws; saying so explicitly stops Word applying its own default.
8489
- fill: props.fill?.color ? solidFill(colors.get("fill.color"), props.fill.transparency) : { kind: "none" },
8490
- text: {
8491
- paragraphs,
8492
- // pptx anchors a text box at the top unless told otherwise, and a
8493
- // drawing that changes anchor between render modes moves its text.
8494
- anchor: ANCHOR[props.valign ?? "top"] ?? "top",
8495
- // A text box's insets default to nothing, again matching pptx. A shape
8496
- // deliberately leaves them unstated so OOXML's own defaults apply.
8497
- insetsEmu: marginInsets(props.margin) ?? ZERO_INSETS
8498
- },
8499
- name: `Text ${at}`
8500
- };
8461
+ return void 0;
8501
8462
  }
8502
- var ZERO_INSETS = { top: 0, bottom: 0, left: 0, right: 0 };
8503
- function textRuns(props) {
8504
- if (props.runs?.length) return props.runs;
8505
- if (props.text === void 0) return void 0;
8506
- return [{ text: props.text }];
8463
+ function parseInline(text, options) {
8464
+ const normalized = encodeEscapes(normalizeUnicodeText(text));
8465
+ if (!normalized) {
8466
+ return [
8467
+ { kind: "text", text: "", formatting: emptyToUndefined(options.base) }
8468
+ ];
8469
+ }
8470
+ if (containsPlaceholder(normalized)) {
8471
+ return parsePlaceholders(normalized, options);
8472
+ }
8473
+ if (options.hyperlinks) return parseLinks(normalized, options);
8474
+ return parseDecorated(normalized, options);
8507
8475
  }
8508
- function textParagraphs(runs, props, colors, at) {
8509
- const paragraphs = [];
8510
- let current = [];
8511
- const flush = () => {
8512
- paragraphs.push({
8513
- kind: "paragraph",
8514
- id: `${at}.p${paragraphs.length}`,
8515
- path: `${at}.paragraphs[${paragraphs.length}]`,
8516
- children: current,
8517
- formatting: {
8518
- ...props.align ? { alignment: ALIGNMENT[props.align] } : {},
8519
- // A drawing's text is set flush against the shape; the document's
8520
- // paragraph spacing would push it away from the top edge, which is not
8521
- // what an absolutely-placed box means.
8522
- spacing: { beforeTwips: 0, afterTwips: 0 }
8523
- }
8524
- });
8525
- current = [];
8476
+ function parsePlaceholders(normalized, parseOptions) {
8477
+ const options = {
8478
+ ...parseOptions,
8479
+ boldColor: void 0,
8480
+ resolveNote: void 0
8526
8481
  };
8527
- runs.forEach((run, index) => {
8528
- const formatting = runFormatting(run, props, colors, index);
8529
- const lines = run.text.split("\n");
8530
- lines.forEach((line, lineIndex) => {
8531
- if (lineIndex > 0) current.push({ kind: "lineBreak" });
8532
- if (line) {
8533
- current.push({ kind: "text", text: line, ...formatting });
8534
- }
8535
- });
8536
- if (run.breakLine && index < runs.length - 1) flush();
8537
- });
8538
- flush();
8539
- return paragraphs;
8482
+ const out = [];
8483
+ const token = new RegExp(DECORATOR_OR_PLACEHOLDER.source, "g");
8484
+ let lastIndex = 0;
8485
+ let match;
8486
+ const literal = (text, base = options.base) => {
8487
+ if (!text) return;
8488
+ out.push(...parseLiteralLines(text, { ...options, base }));
8489
+ };
8490
+ while ((match = token.exec(normalized)) !== null) {
8491
+ if (match.index > lastIndex) {
8492
+ literal(normalized.slice(lastIndex, match.index));
8493
+ }
8494
+ lastIndex = match.index + match[0].length;
8495
+ if (match[7]) {
8496
+ const resolved = options.resolvePlaceholder?.(match[7]);
8497
+ if (!resolved) {
8498
+ literal(match[0]);
8499
+ } else if (resolved.kind === "text") {
8500
+ literal(resolved.text);
8501
+ } else {
8502
+ const formatting = emptyToUndefined({
8503
+ fontFamily: options.base.fontFamily,
8504
+ sizeHalfPoints: options.base.sizeHalfPoints,
8505
+ color: options.base.color,
8506
+ bold: options.base.bold,
8507
+ italic: options.base.italic,
8508
+ underline: options.base.underline
8509
+ });
8510
+ out.push({
8511
+ kind: "field",
8512
+ instruction: resolved.instruction,
8513
+ ...resolved.cachedText !== void 0 && {
8514
+ cachedText: resolved.cachedText
8515
+ },
8516
+ ...formatting ? { formatting } : {}
8517
+ });
8518
+ }
8519
+ continue;
8520
+ }
8521
+ const style = decoratedStyle(match, options.base);
8522
+ out.push(
8523
+ ...parsePlaceholders(decoratedText(match), {
8524
+ ...options,
8525
+ base: { ...options.base, ...style },
8526
+ // No-proof words do not survive into a decorated span here: the
8527
+ // recursion has always been entered without them.
8528
+ noProofWords: void 0
8529
+ })
8530
+ );
8531
+ }
8532
+ if (lastIndex < normalized.length) literal(normalized.slice(lastIndex));
8533
+ if (out.length === 0 && normalized) literal(normalized);
8534
+ return out;
8540
8535
  }
8541
- function runFormatting(run, props, colors, index) {
8542
- const fontFamily = run.fontFace ?? props.fontFace;
8543
- const sizePoints = run.fontSize ?? props.fontSize;
8544
- const color = colors.get(`runs[${index}].color`) ?? (run.color === void 0 ? colors.get("color") : void 0);
8545
- const bold = run.bold ?? props.bold;
8546
- const italic = run.italic ?? props.italic;
8547
- const strike = run.strike ?? props.strike;
8548
- const underline = compileUnderline(
8549
- run.underline ?? props.underline,
8550
- colors.get(
8551
- run.underline !== void 0 ? `runs[${index}].underline.color` : "underline.color"
8552
- )
8536
+ function parseLiteralLines(text, options) {
8537
+ const out = [];
8538
+ pushSegment(out, text, options);
8539
+ return out;
8540
+ }
8541
+ function parseLiteral(text, options) {
8542
+ const out = [];
8543
+ pushWords(
8544
+ out,
8545
+ normalizeUnicodeText(text),
8546
+ emptyToUndefined(options.base),
8547
+ options
8553
8548
  );
8554
- const formatting = {
8555
- ...fontFamily ? { fontFamily } : {},
8556
- ...sizePoints !== void 0 ? { sizeHalfPoints: pointsToHalfPoints(sizePoints) } : {},
8557
- ...color ? { color } : {},
8558
- ...bold !== void 0 ? { bold } : {},
8559
- ...italic !== void 0 ? { italic } : {},
8560
- ...strike !== void 0 ? { strike } : {},
8561
- ...underline ? { underline } : {}
8562
- };
8563
- return Object.keys(formatting).length > 0 ? { formatting } : {};
8549
+ return out;
8564
8550
  }
8565
- var UNDERLINE_TYPES = {
8566
- sng: "single",
8567
- dbl: "double",
8568
- dash: "dash",
8569
- dotted: "dotted"
8570
- };
8571
- function compileUnderline(value, color) {
8572
- if (value === void 0 || value === false) return void 0;
8573
- const style = value === true ? "sng" : value.style ?? "sng";
8574
- return {
8575
- type: UNDERLINE_TYPES[style] ?? "single",
8576
- ...color ? { color } : {}
8577
- };
8551
+ function parseLinks(normalized, options) {
8552
+ const out = [];
8553
+ const token = new RegExp(INLINE_TOKEN.source, "g");
8554
+ let lastIndex = 0;
8555
+ let match;
8556
+ while ((match = token.exec(normalized)) !== null) {
8557
+ if (match.index > lastIndex) {
8558
+ out.push(
8559
+ ...parseDecorated(normalized.slice(lastIndex, match.index), options)
8560
+ );
8561
+ }
8562
+ lastIndex = match.index + match[0].length;
8563
+ if (match[1] === void 0) {
8564
+ const resolved = options.resolveCrossReference?.(
8565
+ decodeEscapes(match[3]),
8566
+ match[4] ?? "relative",
8567
+ match[0]
8568
+ );
8569
+ if (resolved) out.push(resolved);
8570
+ else out.push(...parseDecorated(match[0], options));
8571
+ continue;
8572
+ }
8573
+ const [, linkText] = match;
8574
+ const target2 = decodeEscapes(match[2]);
8575
+ out.push({
8576
+ kind: "hyperlink",
8577
+ // A `#anchor` target names a bookmark in this document and needs no
8578
+ // relationship; anything else leaves the document.
8579
+ target: target2.startsWith("#") ? { kind: "bookmark", anchor: target2.slice(1) } : { kind: "external", url: target2 },
8580
+ children: parseDecorated(linkText, options)
8581
+ });
8582
+ }
8583
+ if (lastIndex < normalized.length) {
8584
+ out.push(...parseDecorated(normalized.slice(lastIndex), options));
8585
+ }
8586
+ if (out.length === 0) return parseDecorated(normalized, options);
8587
+ return out;
8578
8588
  }
8579
- function underlineColor(value) {
8580
- return typeof value === "object" ? value.color : void 0;
8589
+ function parseDecorated(normalized, options) {
8590
+ const out = [];
8591
+ let lastIndex = 0;
8592
+ let match;
8593
+ const decorator = new RegExp(DECORATOR.source, "g");
8594
+ while ((match = decorator.exec(normalized)) !== null) {
8595
+ if (match.index > lastIndex) {
8596
+ pushSegment(out, normalized.slice(lastIndex, match.index), options);
8597
+ }
8598
+ pushSegment(
8599
+ out,
8600
+ decoratedText(match),
8601
+ options,
8602
+ decoratedStyle(match, options.base)
8603
+ );
8604
+ lastIndex = match.index + match[0].length;
8605
+ }
8606
+ if (lastIndex < normalized.length) {
8607
+ pushSegment(out, normalized.slice(lastIndex), options);
8608
+ }
8609
+ if (out.length === 0) pushSegment(out, normalized, options);
8610
+ return out;
8581
8611
  }
8582
- function derivedTextHeightInches(props, runs) {
8583
- const fontSize = props.fontSize ?? ASSUMED_FONT_SIZE_POINTS;
8584
- const lineCount = runs.reduce(
8585
- (total, run) => total + (run.breakLine ? 1 : 0) + (run.text.match(/\n/g)?.length ?? 0),
8586
- 1
8587
- );
8588
- return Math.max(
8589
- MIN_DERIVED_TEXT_HEIGHT_INCHES,
8590
- fontSize / 72 * DERIVED_LINE_HEIGHT_RATIO * lineCount
8591
- );
8612
+ function decoratedText(match) {
8613
+ if (match[1]) return match[2];
8614
+ if (match[3]) return match[4];
8615
+ if (match[5]) return match[6];
8616
+ return match[0];
8592
8617
  }
8593
- function compileShape(props, at, canvas, deps) {
8594
- const segments = shapeSegments(props);
8595
- const colors = resolveColors(
8596
- [
8597
- ["fill.color", props.fill?.color],
8598
- ["line.color", props.line?.color],
8599
- ["fontColor", props.fontColor],
8600
- ...segments.map(
8601
- (segment, i) => [`text[${i}].color`, segment.color]
8602
- )
8603
- ],
8604
- at,
8605
- deps
8606
- );
8607
- if (!colors) return void 0;
8608
- const frame = resolveFrame(props, canvas, { defaultHeightInches: 0 });
8609
- if (props.flipH) frame.flipHorizontal = true;
8610
- if (props.flipV) frame.flipVertical = true;
8611
- const dash = props.line?.dashType;
8612
- if (dash !== void 0 && !DASH_VALUES.has(dash)) {
8613
- deps.reject(`${at}.line.dashType "${dash}"`);
8614
- return void 0;
8618
+ function decoratedStyle(match, base) {
8619
+ const bold = base.bold ?? false;
8620
+ const italic = base.italic ?? false;
8621
+ if (match[1]) return { bold: true, italic: true };
8622
+ if (match[3]) return { bold: true, italic };
8623
+ if (match[5]) return { bold, italic: true };
8624
+ return { bold, italic };
8625
+ }
8626
+ function pushSegment(out, text, options, override = {}) {
8627
+ const bold = override.bold ?? options.base.bold;
8628
+ const italic = override.italic ?? options.base.italic;
8629
+ const formatting = runFormatting(options, { bold, italic });
8630
+ const lines = decodeEscapes(text).split("\n");
8631
+ for (const [lineIndex, line] of lines.entries()) {
8632
+ if (!line && lineIndex === 0) continue;
8633
+ if (lineIndex > 0) out.push({ kind: "lineBreak" });
8634
+ if (!line) {
8635
+ out.push({ kind: "text", text: "", formatting });
8636
+ continue;
8637
+ }
8638
+ const tabSegments = line.split(" ");
8639
+ for (const [tabIndex, segment] of tabSegments.entries()) {
8640
+ if (tabIndex > 0) out.push({ kind: "tab", formatting });
8641
+ if (!segment && tabSegments.length > 1) continue;
8642
+ pushWords(out, segment, formatting, options);
8643
+ }
8615
8644
  }
8616
- const outline = props.line ? {
8617
- ...colors.get("line.color") ? { color: colors.get("line.color") } : {},
8618
- ...props.line.width !== void 0 ? { widthEmu: pointsToEmu(props.line.width) } : {},
8619
- ...dash && dash !== "solid" ? { dash } : {}
8620
- } : void 0;
8621
- const text = segments.length ? {
8622
- paragraphs: shapeParagraphs(segments, props, colors, at),
8623
- anchor: ANCHOR[props.valign ?? "middle"] ?? "middle",
8624
- // Unstated insets are left unstated so OOXML's own shape defaults
8625
- // apply, which is what the raster path draws.
8626
- ...marginInsets(props.margin) ? { insetsEmu: marginInsets(props.margin) } : {}
8627
- } : void 0;
8628
- return {
8629
- kind: "shape",
8630
- frame,
8631
- geometry: GEOMETRY_ALIASES[props.type] ?? props.type,
8632
- ...props.fill ? {
8633
- fill: props.fill.color ? solidFill(colors.get("fill.color"), props.fill.transparency) : { kind: "none" }
8634
- } : {},
8635
- ...outline && Object.keys(outline).length > 0 ? { outline } : {},
8636
- ...text ? { text } : {},
8637
- name: `Shape ${at}`
8638
- };
8639
8645
  }
8640
- function shapeSegments(props) {
8641
- if (props.text === void 0) return [];
8642
- if (typeof props.text === "string") {
8643
- return props.text ? [{ text: props.text }] : [];
8646
+ function pushWords(out, text, formatting, options) {
8647
+ if (options.resolveNote) {
8648
+ splitNoteMarkers(out, text, formatting, options);
8649
+ return;
8644
8650
  }
8645
- return props.text;
8651
+ pushNoProofWords(out, text, formatting, options);
8646
8652
  }
8647
- function shapeParagraphs(segments, props, colors, at) {
8653
+ function splitNoteMarkers(out, text, formatting, options) {
8654
+ const regex = new RegExp(NOTE_MARKER.source, "g");
8655
+ let lastIndex = 0;
8656
+ let pending = "";
8657
+ let match;
8658
+ while ((match = regex.exec(text)) !== null) {
8659
+ const note = options.resolveNote(match[1]);
8660
+ if (note === void 0) {
8661
+ pending += text.slice(lastIndex, regex.lastIndex);
8662
+ lastIndex = regex.lastIndex;
8663
+ continue;
8664
+ }
8665
+ const before = pending + text.slice(lastIndex, match.index);
8666
+ pending = "";
8667
+ if (before) pushNoProofWords(out, before, formatting, options);
8668
+ out.push({
8669
+ kind: "noteReference",
8670
+ noteKind: note.noteKind,
8671
+ id: note.id
8672
+ });
8673
+ lastIndex = regex.lastIndex;
8674
+ }
8675
+ const rest = pending + text.slice(lastIndex);
8676
+ if (rest) pushNoProofWords(out, rest, formatting, options);
8677
+ }
8678
+ function noProofRuns(text, formatting, noProofWords2) {
8679
+ const out = [];
8680
+ pushNoProofWords(out, text, formatting, { base: {}, noProofWords: noProofWords2 });
8681
+ return out.map(
8682
+ (run) => run.formatting ? run : { kind: "text", text: run.text }
8683
+ );
8684
+ }
8685
+ function pushNoProofWords(out, text, formatting, options) {
8686
+ const wholeRunNoProof = options.base.noProof === true;
8687
+ const regex = wholeRunNoProof ? null : buildNoProofWordsRegex(options.noProofWords);
8688
+ if (!regex) {
8689
+ out.push({ kind: "text", text, formatting });
8690
+ return;
8691
+ }
8692
+ let lastIndex = 0;
8693
+ let match;
8694
+ while ((match = regex.exec(text)) !== null) {
8695
+ if (match.index > lastIndex) {
8696
+ out.push({
8697
+ kind: "text",
8698
+ text: text.slice(lastIndex, match.index),
8699
+ formatting
8700
+ });
8701
+ }
8702
+ out.push({
8703
+ kind: "text",
8704
+ text: match[0],
8705
+ formatting: { ...formatting, noProof: true }
8706
+ });
8707
+ lastIndex = match.index + match[0].length;
8708
+ if (regex.lastIndex === match.index) regex.lastIndex += 1;
8709
+ }
8710
+ if (lastIndex < text.length) {
8711
+ out.push({ kind: "text", text: text.slice(lastIndex), formatting });
8712
+ }
8713
+ }
8714
+ function runFormatting(options, state) {
8715
+ const formatting = { ...options.base };
8716
+ if (state.bold !== void 0) formatting.bold = state.bold;
8717
+ if (state.italic !== void 0) formatting.italic = state.italic;
8718
+ if (state.bold && options.boldColor && options.base.color) {
8719
+ formatting.color = options.boldColor;
8720
+ }
8721
+ return emptyToUndefined(formatting);
8722
+ }
8723
+ function emptyToUndefined(formatting) {
8724
+ const entries = Object.entries(formatting).filter(
8725
+ ([, value]) => value !== void 0
8726
+ );
8727
+ return entries.length === 0 ? void 0 : Object.fromEntries(entries);
8728
+ }
8729
+
8730
+ // src/ir/nativeVisual.ts
8731
+ init_imageUtils();
8732
+ var PIXELS_PER_INCH2 = 96;
8733
+ var DEFAULT_WIDTH_FRACTION = 0.75;
8734
+ var ASSUMED_FONT_SIZE_POINTS = 18;
8735
+ var MIN_DERIVED_TEXT_HEIGHT_INCHES = 0.5;
8736
+ var DERIVED_LINE_HEIGHT_RATIO = 1.6;
8737
+ var GEOMETRY_ALIASES = {
8738
+ arrow: "rightArrow",
8739
+ lightning: "lightningBolt"
8740
+ };
8741
+ var DASH_VALUES = /* @__PURE__ */ new Set([
8742
+ "solid",
8743
+ "dash",
8744
+ "dot",
8745
+ "dashDot"
8746
+ ]);
8747
+ var ANCHOR = {
8748
+ top: "top",
8749
+ middle: "middle",
8750
+ bottom: "bottom"
8751
+ };
8752
+ var ALIGNMENT = {
8753
+ left: "left",
8754
+ center: "center",
8755
+ right: "right",
8756
+ justify: "justified"
8757
+ };
8758
+ function compileNativeVisualGroup(props, outer) {
8759
+ const canvas = {
8760
+ widthEmu: inchesToEmu(props.canvas.width),
8761
+ heightEmu: inchesToEmu(props.canvas.height)
8762
+ };
8763
+ const children = [];
8764
+ let refused = false;
8765
+ const deps = {
8766
+ ...outer,
8767
+ reject: (detail) => {
8768
+ refused = true;
8769
+ outer.reject(detail);
8770
+ }
8771
+ };
8772
+ const background = props.canvas.background;
8773
+ if (background?.color) {
8774
+ const color = deps.color(background.color);
8775
+ if (!color) {
8776
+ deps.reject(`canvas.background.color "${background.color}"`);
8777
+ } else {
8778
+ children.push({
8779
+ kind: "shape",
8780
+ frame: fullBleed(canvas),
8781
+ geometry: "rect",
8782
+ fill: { kind: "solid", color },
8783
+ name: "Canvas background"
8784
+ });
8785
+ }
8786
+ }
8787
+ if (background?.image) {
8788
+ const source = resolveImageSource(background.image);
8789
+ const resource = source ? deps.picture(source) : void 0;
8790
+ if (!resource) {
8791
+ deps.reject("canvas.background.image could not be loaded");
8792
+ } else {
8793
+ children.push({
8794
+ kind: "picture",
8795
+ frame: fullBleed(canvas),
8796
+ resourceId: resource.resourceId,
8797
+ name: "Canvas background"
8798
+ });
8799
+ }
8800
+ }
8801
+ for (const [index, element] of (props.elements ?? []).entries()) {
8802
+ if (element.enabled === false) continue;
8803
+ const child = compileElement(element, index, canvas, deps);
8804
+ if (child) children.push(child);
8805
+ }
8806
+ return refused ? void 0 : { canvas, children };
8807
+ }
8808
+ function compileElement(element, index, canvas, deps) {
8809
+ const at = `elements[${index}]`;
8810
+ switch (element.name) {
8811
+ case "text":
8812
+ return compileText(element.props, at, canvas, deps);
8813
+ case "shape":
8814
+ return compileShape(element.props, at, canvas, deps);
8815
+ case "image":
8816
+ return compileImage(element.props, at, canvas, deps);
8817
+ default:
8818
+ deps.reject(`${at} is a "${element.name}"`);
8819
+ return void 0;
8820
+ }
8821
+ }
8822
+ function compileText(props, at, canvas, deps) {
8823
+ const runs = textRuns(props);
8824
+ if (!runs) {
8825
+ deps.warn(
8826
+ `[core-docx] native visual ${at} has neither "text" nor "runs"; nothing was drawn for it.`
8827
+ );
8828
+ return void 0;
8829
+ }
8830
+ const colors = resolveColors(
8831
+ [
8832
+ ["color", props.color],
8833
+ ["fill.color", props.fill?.color],
8834
+ ...runs.map((run, i) => [`runs[${i}].color`, run.color]),
8835
+ ...runs.map(
8836
+ (run, i) => [
8837
+ `runs[${i}].underline.color`,
8838
+ underlineColor(run.underline)
8839
+ ]
8840
+ ),
8841
+ ["underline.color", underlineColor(props.underline)]
8842
+ ],
8843
+ at,
8844
+ deps
8845
+ );
8846
+ if (!colors) return void 0;
8847
+ const frame = resolveFrame(props, canvas, {
8848
+ defaultHeightInches: derivedTextHeightInches(props, runs)
8849
+ });
8850
+ const paragraphs = textParagraphs(runs, props, colors, at, deps.noProofWords);
8851
+ return {
8852
+ kind: "shape",
8853
+ frame,
8854
+ geometry: "rect",
8855
+ isTextBox: true,
8856
+ // A text box with no fill is transparent, which is what the rasterized
8857
+ // path draws; saying so explicitly stops Word applying its own default.
8858
+ fill: props.fill?.color ? solidFill(colors.get("fill.color"), props.fill.transparency) : { kind: "none" },
8859
+ text: {
8860
+ paragraphs,
8861
+ // pptx anchors a text box at the top unless told otherwise, and a
8862
+ // drawing that changes anchor between render modes moves its text.
8863
+ anchor: ANCHOR[props.valign ?? "top"] ?? "top",
8864
+ // A text box's insets default to nothing, again matching pptx. A shape
8865
+ // deliberately leaves them unstated so OOXML's own defaults apply.
8866
+ insetsEmu: marginInsets(props.margin) ?? ZERO_INSETS
8867
+ },
8868
+ name: `Text ${at}`
8869
+ };
8870
+ }
8871
+ var ZERO_INSETS = { top: 0, bottom: 0, left: 0, right: 0 };
8872
+ function textRuns(props) {
8873
+ if (props.runs?.length) return props.runs;
8874
+ if (props.text === void 0) return void 0;
8875
+ return [{ text: props.text }];
8876
+ }
8877
+ function textParagraphs(runs, props, colors, at, words) {
8648
8878
  const paragraphs = [];
8649
8879
  let current = [];
8650
8880
  const flush = () => {
@@ -8654,49 +8884,195 @@ function shapeParagraphs(segments, props, colors, at) {
8654
8884
  path: `${at}.paragraphs[${paragraphs.length}]`,
8655
8885
  children: current,
8656
8886
  formatting: {
8657
- // A shape centres its text unless told otherwise, which is what the
8658
- // rasterized shape draws.
8659
- alignment: props.align ? ALIGNMENT[props.align] : "center",
8887
+ ...props.align ? { alignment: ALIGNMENT[props.align] } : {},
8888
+ // A drawing's text is set flush against the shape; the document's
8889
+ // paragraph spacing would push it away from the top edge, which is not
8890
+ // what an absolutely-placed box means.
8660
8891
  spacing: { beforeTwips: 0, afterTwips: 0 }
8661
8892
  }
8662
8893
  });
8663
8894
  current = [];
8664
8895
  };
8665
- segments.forEach((segment, index) => {
8666
- const fontFamily = segment.fontFace ?? props.fontFace;
8667
- const sizePoints = segment.fontSize ?? props.fontSize;
8668
- const color = colors.get(`text[${index}].color`) ?? (segment.color === void 0 ? colors.get("fontColor") : void 0);
8669
- const bold = segment.bold ?? props.bold;
8670
- const italic = segment.italic ?? props.italic;
8671
- const formatting = {
8672
- ...fontFamily ? { fontFamily } : {},
8673
- ...sizePoints !== void 0 ? { sizeHalfPoints: pointsToHalfPoints(sizePoints) } : {},
8674
- ...color ? { color } : {},
8675
- ...bold !== void 0 ? { bold } : {},
8676
- ...italic !== void 0 ? { italic } : {}
8677
- };
8678
- const wrapped = Object.keys(formatting).length > 0 ? { formatting } : void 0;
8679
- segment.text.split("\n").forEach((line, lineIndex) => {
8896
+ runs.forEach((run, index) => {
8897
+ const formatting = runFormatting2(run, props, colors, index);
8898
+ const lines = run.text.split("\n");
8899
+ lines.forEach((line, lineIndex) => {
8680
8900
  if (lineIndex > 0) current.push({ kind: "lineBreak" });
8681
- if (line) current.push({ kind: "text", text: line, ...wrapped });
8901
+ if (line) current.push(...textLine(line, formatting.formatting, words));
8682
8902
  });
8683
- if (segment.breakLine && index < segments.length - 1) flush();
8903
+ if (run.breakLine && index < runs.length - 1) flush();
8684
8904
  });
8685
8905
  flush();
8686
8906
  return paragraphs;
8687
8907
  }
8688
- function compileImage(props, at, canvas, deps) {
8689
- const source = resolveImageSource(props);
8690
- if (!source) {
8691
- deps.reject(`${at} has none of "path", "base64" or "svg"`);
8692
- return void 0;
8693
- }
8694
- const resource = deps.picture(source);
8695
- if (!resource) {
8696
- deps.reject(`${at} image could not be loaded`);
8697
- return void 0;
8698
- }
8699
- const aspect = resource.intrinsic ? resource.intrinsic.width / resource.intrinsic.height : void 0;
8908
+ function runFormatting2(run, props, colors, index) {
8909
+ const fontFamily = run.fontFace ?? props.fontFace;
8910
+ const sizePoints = run.fontSize ?? props.fontSize;
8911
+ const color = colors.get(`runs[${index}].color`) ?? (run.color === void 0 ? colors.get("color") : void 0);
8912
+ const bold = run.bold ?? props.bold;
8913
+ const italic = run.italic ?? props.italic;
8914
+ const strike = run.strike ?? props.strike;
8915
+ const underline = compileUnderline(
8916
+ run.underline ?? props.underline,
8917
+ colors.get(
8918
+ run.underline !== void 0 ? `runs[${index}].underline.color` : "underline.color"
8919
+ )
8920
+ );
8921
+ const formatting = {
8922
+ ...fontFamily ? { fontFamily } : {},
8923
+ ...sizePoints !== void 0 ? { sizeHalfPoints: pointsToHalfPoints(sizePoints) } : {},
8924
+ ...color ? { color } : {},
8925
+ ...bold !== void 0 ? { bold } : {},
8926
+ ...italic !== void 0 ? { italic } : {},
8927
+ ...strike !== void 0 ? { strike } : {},
8928
+ ...underline ? { underline } : {}
8929
+ };
8930
+ return Object.keys(formatting).length > 0 ? { formatting } : {};
8931
+ }
8932
+ var UNDERLINE_TYPES = {
8933
+ sng: "single",
8934
+ dbl: "double",
8935
+ dash: "dash",
8936
+ dotted: "dotted"
8937
+ };
8938
+ function compileUnderline(value, color) {
8939
+ if (value === void 0 || value === false) return void 0;
8940
+ const style = value === true ? "sng" : value.style ?? "sng";
8941
+ return {
8942
+ type: UNDERLINE_TYPES[style] ?? "single",
8943
+ ...color ? { color } : {}
8944
+ };
8945
+ }
8946
+ function underlineColor(value) {
8947
+ return typeof value === "object" ? value.color : void 0;
8948
+ }
8949
+ function derivedTextHeightInches(props, runs) {
8950
+ const fontSize = props.fontSize ?? ASSUMED_FONT_SIZE_POINTS;
8951
+ const lineCount = runs.reduce(
8952
+ (total, run) => total + (run.breakLine ? 1 : 0) + (run.text.match(/\n/g)?.length ?? 0),
8953
+ 1
8954
+ );
8955
+ return Math.max(
8956
+ MIN_DERIVED_TEXT_HEIGHT_INCHES,
8957
+ fontSize / 72 * DERIVED_LINE_HEIGHT_RATIO * lineCount
8958
+ );
8959
+ }
8960
+ function compileShape(props, at, canvas, deps) {
8961
+ const segments = shapeSegments(props);
8962
+ const colors = resolveColors(
8963
+ [
8964
+ ["fill.color", props.fill?.color],
8965
+ ["line.color", props.line?.color],
8966
+ ["fontColor", props.fontColor],
8967
+ ...segments.map(
8968
+ (segment, i) => [`text[${i}].color`, segment.color]
8969
+ )
8970
+ ],
8971
+ at,
8972
+ deps
8973
+ );
8974
+ if (!colors) return void 0;
8975
+ const frame = resolveFrame(props, canvas, { defaultHeightInches: 0 });
8976
+ if (props.flipH) frame.flipHorizontal = true;
8977
+ if (props.flipV) frame.flipVertical = true;
8978
+ const dash = props.line?.dashType;
8979
+ if (dash !== void 0 && !DASH_VALUES.has(dash)) {
8980
+ deps.reject(`${at}.line.dashType "${dash}"`);
8981
+ return void 0;
8982
+ }
8983
+ const outline = props.line ? {
8984
+ ...colors.get("line.color") ? { color: colors.get("line.color") } : {},
8985
+ ...props.line.width !== void 0 ? { widthEmu: pointsToEmu(props.line.width) } : {},
8986
+ ...dash && dash !== "solid" ? { dash } : {}
8987
+ } : void 0;
8988
+ const text = segments.length ? {
8989
+ paragraphs: shapeParagraphs(
8990
+ segments,
8991
+ props,
8992
+ colors,
8993
+ at,
8994
+ deps.noProofWords
8995
+ ),
8996
+ anchor: ANCHOR[props.valign ?? "middle"] ?? "middle",
8997
+ // Unstated insets are left unstated so OOXML's own shape defaults
8998
+ // apply, which is what the raster path draws.
8999
+ ...marginInsets(props.margin) ? { insetsEmu: marginInsets(props.margin) } : {}
9000
+ } : void 0;
9001
+ return {
9002
+ kind: "shape",
9003
+ frame,
9004
+ geometry: GEOMETRY_ALIASES[props.type] ?? props.type,
9005
+ ...props.fill ? {
9006
+ fill: props.fill.color ? solidFill(colors.get("fill.color"), props.fill.transparency) : { kind: "none" }
9007
+ } : {},
9008
+ ...outline && Object.keys(outline).length > 0 ? { outline } : {},
9009
+ ...text ? { text } : {},
9010
+ name: `Shape ${at}`
9011
+ };
9012
+ }
9013
+ function shapeSegments(props) {
9014
+ if (props.text === void 0) return [];
9015
+ if (typeof props.text === "string") {
9016
+ return props.text ? [{ text: props.text }] : [];
9017
+ }
9018
+ return props.text;
9019
+ }
9020
+ function shapeParagraphs(segments, props, colors, at, words) {
9021
+ const paragraphs = [];
9022
+ let current = [];
9023
+ const flush = () => {
9024
+ paragraphs.push({
9025
+ kind: "paragraph",
9026
+ id: `${at}.p${paragraphs.length}`,
9027
+ path: `${at}.paragraphs[${paragraphs.length}]`,
9028
+ children: current,
9029
+ formatting: {
9030
+ // A shape centres its text unless told otherwise, which is what the
9031
+ // rasterized shape draws.
9032
+ alignment: props.align ? ALIGNMENT[props.align] : "center",
9033
+ spacing: { beforeTwips: 0, afterTwips: 0 }
9034
+ }
9035
+ });
9036
+ current = [];
9037
+ };
9038
+ segments.forEach((segment, index) => {
9039
+ const fontFamily = segment.fontFace ?? props.fontFace;
9040
+ const sizePoints = segment.fontSize ?? props.fontSize;
9041
+ const color = colors.get(`text[${index}].color`) ?? (segment.color === void 0 ? colors.get("fontColor") : void 0);
9042
+ const bold = segment.bold ?? props.bold;
9043
+ const italic = segment.italic ?? props.italic;
9044
+ const formatting = {
9045
+ ...fontFamily ? { fontFamily } : {},
9046
+ ...sizePoints !== void 0 ? { sizeHalfPoints: pointsToHalfPoints(sizePoints) } : {},
9047
+ ...color ? { color } : {},
9048
+ ...bold !== void 0 ? { bold } : {},
9049
+ ...italic !== void 0 ? { italic } : {}
9050
+ };
9051
+ const wrapped = Object.keys(formatting).length > 0 ? formatting : void 0;
9052
+ segment.text.split("\n").forEach((line, lineIndex) => {
9053
+ if (lineIndex > 0) current.push({ kind: "lineBreak" });
9054
+ if (line) current.push(...textLine(line, wrapped, words));
9055
+ });
9056
+ if (segment.breakLine && index < segments.length - 1) flush();
9057
+ });
9058
+ flush();
9059
+ return paragraphs;
9060
+ }
9061
+ function textLine(line, formatting, words) {
9062
+ return words ? noProofRuns(line, formatting, words) : [{ kind: "text", text: line, ...formatting ? { formatting } : {} }];
9063
+ }
9064
+ function compileImage(props, at, canvas, deps) {
9065
+ const source = resolveImageSource(props);
9066
+ if (!source) {
9067
+ deps.reject(`${at} has none of "path", "base64" or "svg"`);
9068
+ return void 0;
9069
+ }
9070
+ const resource = deps.picture(source);
9071
+ if (!resource) {
9072
+ deps.reject(`${at} image could not be loaded`);
9073
+ return void 0;
9074
+ }
9075
+ const aspect = resource.intrinsic ? resource.intrinsic.width / resource.intrinsic.height : void 0;
8700
9076
  const intrinsicInches = resource.intrinsic ? {
8701
9077
  width: resource.intrinsic.width / PIXELS_PER_INCH2,
8702
9078
  height: resource.intrinsic.height / PIXELS_PER_INCH2
@@ -10228,801 +10604,459 @@ function resolveTableModel(source, theme, themeName, options = {}) {
10228
10604
  adjudicateInteriorEdges([...hasHeader ? [header] : [], ...rows]);
10229
10605
  return {
10230
10606
  ...resolveWidths(
10231
- source,
10232
- columns,
10233
- options.measureTwips ?? getAvailableWidthTwips(theme, themeName)
10234
- ),
10235
- ...hasHeader ? { header } : {},
10236
- rows,
10237
- repeatHeader: source.repeatHeaderOnPageBreak ?? true
10238
- };
10239
- }
10240
- function drawsNothing(border3) {
10241
- return border3.hidden || border3.size <= 0;
10242
- }
10243
- function channels(hex) {
10244
- const digits = hex.startsWith("#") ? hex.slice(1) : hex;
10245
- return [
10246
- parseInt(digits.slice(0, 2), 16) || 0,
10247
- parseInt(digits.slice(2, 4), 16) || 0,
10248
- parseInt(digits.slice(4, 6), 16) || 0
10249
- ];
10250
- }
10251
- function outweighs(a, b) {
10252
- if (drawsNothing(a)) return false;
10253
- if (drawsNothing(b)) return true;
10254
- if (a.size !== b.size) return a.size > b.size;
10255
- const [redA, greenA, blueA] = channels(a.color);
10256
- const [redB, greenB, blueB] = channels(b.color);
10257
- const brightness = [
10258
- [redA + blueA + 2 * greenA, redB + blueB + 2 * greenB],
10259
- [blueA + 2 * greenA, blueB + 2 * greenB],
10260
- [greenA, greenB]
10261
- ];
10262
- for (const [ours, theirs] of brightness) {
10263
- if (ours !== theirs) return ours < theirs;
10264
- }
10265
- return false;
10266
- }
10267
- function adjudicateInteriorEdges(rows) {
10268
- const settle = (earlier, later) => {
10269
- if (drawsNothing(earlier) && drawsNothing(later)) return;
10270
- if (earlier.hidden === later.hidden && earlier.size === later.size && earlier.color === later.color) {
10271
- return;
10272
- }
10273
- const winner = earlier.explicit !== later.explicit ? earlier.explicit ? earlier : later : outweighs(later, earlier) ? later : earlier;
10274
- const loser = winner === earlier ? later : earlier;
10275
- loser.size = winner.size;
10276
- loser.color = winner.color;
10277
- loser.hidden = winner.hidden;
10278
- };
10279
- rows.forEach((row, rowIndex) => {
10280
- row.cells.forEach((cell, colIndex) => {
10281
- const right = row.cells[colIndex + 1];
10282
- if (right) settle(cell.borders.right, right.borders.left);
10283
- const below = rows[rowIndex + 1]?.cells[colIndex];
10284
- if (below) settle(cell.borders.bottom, below.borders.top);
10285
- });
10286
- });
10287
- }
10288
- function resolveWidths(source, columns, measureTwips) {
10289
- const hasExplicitWidths = columns.some((col) => col.width !== void 0);
10290
- if (!hasExplicitWidths) {
10291
- const percent = source.width ?? 100;
10292
- return {
10293
- columnGrid: {
10294
- unit: "twips",
10295
- values: splitTwips(columns.length, measureTwips * percent / 100)
10296
- },
10297
- width: { size: percent, unit: "percent" }
10298
- };
10299
- }
10300
- const stated = columns.map(
10301
- (col) => col.width !== void 0 ? relativeLengthToTwips(col.width, measureTwips) : void 0
10302
- );
10303
- const totalStated = stated.reduce((sum, w) => sum + (w ?? 0), 0);
10304
- const unstatedCount = columns.filter((col) => col.width === void 0).length;
10305
- const remaining = Math.max(0, measureTwips - totalStated);
10306
- const shareOfRemainder = unstatedCount > 0 ? remaining / unstatedCount : pointsToTwips(72);
10307
- const values = stated.map((w) => w || shareOfRemainder);
10308
- return {
10309
- columnGrid: { unit: "twips", values },
10310
- width: {
10311
- size: values.reduce((sum, w) => sum + w, 0),
10312
- unit: "twips"
10313
- },
10314
- ...totalStated > measureTwips ? { overflow: { totalTwips: totalStated, availableTwips: measureTwips } } : {}
10315
- };
10316
- }
10317
-
10318
- // src/ir/compiler.ts
10319
- init_layoutUtils();
10320
-
10321
- // src/utils/revisionUtils.ts
10322
- var DEFAULT_REVISION_AUTHOR = "json-to-office";
10323
- var DEFAULT_REVISION_DATE = "1970-01-01T00:00:00Z";
10324
-
10325
- // src/utils/commentRegistry.ts
10326
- var DEFAULT_COMMENT_AUTHOR = "json-to-office";
10327
- var DEFAULT_COMMENT_DATE = "1970-01-01T00:00:00Z";
10328
-
10329
- // src/ir/compiler.ts
10330
- init_imageUtils();
10331
- init_widthUtils();
10332
-
10333
- // src/utils/numberingConfig.ts
10334
- var HEADING_NUMBERING_REFERENCE = "jto-heading-numbering";
10335
- function headingNumberLabel(number) {
10336
- return `${number}.`;
10337
- }
10338
-
10339
- // src/utils/unicode.ts
10340
- function normalizeUnicodeText(text) {
10341
- return (text ?? "").normalize("NFC");
10342
- }
10343
-
10344
- // src/core/collectTocHeadings.ts
10345
- import { getStandardComponent } from "@json-to-office/shared-docx";
10346
-
10347
- // src/core/sectionBookmarks.ts
10348
- function sectionBookmark(ordinal) {
10349
- return { id: `_Section_${ordinal}`, linkId: ordinal };
10350
- }
10351
-
10352
- // src/utils/numberFormatting.ts
10353
- var ROMAN_NUMERALS = [
10354
- [1e3, "m"],
10355
- [900, "cm"],
10356
- [500, "d"],
10357
- [400, "cd"],
10358
- [100, "c"],
10359
- [90, "xc"],
10360
- [50, "l"],
10361
- [40, "xl"],
10362
- [10, "x"],
10363
- [9, "ix"],
10364
- [5, "v"],
10365
- [4, "iv"],
10366
- [1, "i"]
10367
- ];
10368
- function toRoman(value) {
10369
- let remaining = value;
10370
- let out = "";
10371
- for (const [amount, glyph] of ROMAN_NUMERALS) {
10372
- while (remaining >= amount) {
10373
- out += glyph;
10374
- remaining -= amount;
10375
- }
10376
- }
10377
- return out;
10378
- }
10379
- function toLetters(value) {
10380
- const index = (value - 1) % 26;
10381
- const repeats = Math.floor((value - 1) / 26) + 1;
10382
- return String.fromCharCode(97 + index).repeat(repeats);
10383
- }
10384
- function formatNumberForLevel(value, format2) {
10385
- if (!Number.isFinite(value) || value < 1) return void 0;
10386
- const n = Math.floor(value);
10387
- switch (format2) {
10388
- case "decimal":
10389
- return String(n);
10390
- case "lowerLetter":
10391
- return toLetters(n);
10392
- case "upperLetter":
10393
- return toLetters(n).toUpperCase();
10394
- case "lowerRoman":
10395
- return toRoman(n);
10396
- case "upperRoman":
10397
- return toRoman(n).toUpperCase();
10398
- default:
10399
- return void 0;
10400
- }
10401
- }
10402
-
10403
- // src/core/collectTocHeadings.ts
10404
- var MAX_HEADING_LEVEL = 6;
10405
- var MAX_LIST_LEVEL = 9;
10406
- function normalizeEntryTitle(text) {
10407
- return normalizeUnicodeText(text).replace(/(\*\*\*|___)([\s\S]*?)\1/g, "$2").replace(/(\*\*|__)([\s\S]*?)\1/g, "$2").replace(/(\*|_)([\s\S]*?)\1/g, "$2").trim();
10408
- }
10409
- function isContainer(name) {
10410
- return getStandardComponent(name)?.hasChildren === true;
10411
- }
10412
- function isEnabled(component) {
10413
- return !("enabled" in component && component.enabled === false);
10414
- }
10415
- function styleEntryKey(props) {
10416
- const themeStyle = props.themeStyle;
10417
- if (typeof themeStyle !== "string" || !themeStyle) return void 0;
10418
- if (/^heading[1-6]$/i.test(themeStyle)) return void 0;
10419
- if (["normal", "title", "subtitle"].includes(themeStyle.toLowerCase())) {
10420
- return void 0;
10421
- }
10422
- return themeStyle;
10423
- }
10424
- function levelStart(levels, level) {
10425
- return levels[level]?.start ?? 1;
10426
- }
10427
- function collectDocumentOutline(sections) {
10428
- const entries = [];
10429
- const numberedItems = /* @__PURE__ */ new Map();
10430
- const ordinals = computeSectionOrdinals(sections);
10431
- const takenIds = /* @__PURE__ */ new Set();
10432
- const headingCounters = new Array(MAX_HEADING_LEVEL).fill(0);
10433
- const listCounters = /* @__PURE__ */ new Map();
10434
- const visitHeading = (component, props, sectionBookmarkId) => {
10435
- const text = typeof props.text === "string" ? props.text : "";
10436
- const title = normalizeEntryTitle(text);
10437
- const level = typeof props.level === "number" ? props.level : 1;
10438
- const styleLevel = level >= 1 && level <= MAX_HEADING_LEVEL ? level : 1;
10439
- let full;
10440
- let own;
10441
- if (props.numbering === true) {
10442
- headingCounters[styleLevel - 1] += 1;
10443
- for (let deeper = styleLevel; deeper < MAX_HEADING_LEVEL; deeper++) {
10444
- headingCounters[deeper] = 0;
10445
- }
10446
- full = headingCounters.slice(0, styleLevel).join(".");
10447
- own = String(headingCounters[styleLevel - 1]);
10448
- }
10449
- const explicitId = component.id;
10450
- const bookmarkId = typeof explicitId === "string" && explicitId ? explicitId : dedupeBookmarkId(slugifyBookmarkText(text), (id) => takenIds.has(id));
10451
- takenIds.add(bookmarkId);
10452
- numberedItems.set(bookmarkId, {
10453
- kind: "heading",
10454
- text: title,
10455
- ...full !== void 0 && { full, own }
10456
- });
10457
- if (title) {
10458
- entries.push({
10459
- title,
10460
- level,
10461
- sectionBookmarkId,
10462
- ...full !== void 0 && { number: full }
10463
- });
10464
- }
10465
- };
10466
- const visitList = (props) => {
10467
- const items = Array.isArray(props.items) ? props.items : [];
10468
- if (items.length === 0) return;
10469
- const reference = typeof props.reference === "string" && props.reference ? props.reference : void 0;
10470
- const freshState = () => {
10471
- const levels = resolveListLevels(
10472
- props
10473
- );
10474
- return {
10475
- levels,
10476
- counters: Array.from(
10477
- { length: MAX_LIST_LEVEL },
10478
- (_, level) => levelStart(levels, level) - 1
10479
- )
10480
- };
10481
- };
10482
- let state;
10483
- if (reference === void 0) {
10484
- state = freshState();
10485
- } else {
10486
- state = listCounters.get(reference) ?? freshState();
10487
- listCounters.set(reference, state);
10488
- }
10489
- for (const item of items) {
10490
- const isObject = typeof item === "object" && item !== null;
10491
- const raw = isObject ? item.text : item;
10492
- const text = typeof raw === "string" ? raw : "";
10493
- if (!text.trim() && !(isObject && item.revision)) continue;
10494
- const rawLevel = isObject ? item.level : void 0;
10495
- const level = typeof rawLevel === "number" && rawLevel >= 0 ? rawLevel : 0;
10496
- if (level >= MAX_LIST_LEVEL) continue;
10497
- state.counters[level] += 1;
10498
- for (let deeper = level + 1; deeper < MAX_LIST_LEVEL; deeper++) {
10499
- state.counters[deeper] = levelStart(state.levels, deeper) - 1;
10500
- }
10501
- const id = isObject ? item.id : void 0;
10502
- if (typeof id !== "string" || !id) continue;
10503
- takenIds.add(id);
10504
- const number = formatNumberForLevel(
10505
- state.counters[level],
10506
- state.levels[level]?.format
10507
- );
10508
- numberedItems.set(id, {
10509
- kind: "list-item",
10510
- text: normalizeUnicodeText(text).trim(),
10511
- ...number !== void 0 && { full: number, own: number }
10512
- });
10513
- }
10514
- };
10515
- sections.forEach((section2, index) => {
10516
- const ordinal = ordinals[index]?.ordinal;
10517
- const sectionBookmarkId = ordinal ? sectionBookmark(ordinal).id : void 0;
10518
- const visit = (component) => {
10519
- if (!isEnabled(component)) return;
10520
- const props = component.props ?? {};
10521
- if (component.name === "heading") {
10522
- visitHeading(component, props, sectionBookmarkId);
10523
- return;
10524
- }
10525
- if (component.name === "paragraph") {
10526
- if (typeof props.id === "string" && props.id) takenIds.add(props.id);
10527
- const styleId = styleEntryKey(props);
10528
- const text = typeof props.text === "string" ? props.text : "";
10529
- const title = normalizeEntryTitle(text);
10530
- if (styleId && title) {
10531
- entries.push({ title, level: 1, styleId, sectionBookmarkId });
10532
- }
10533
- return;
10534
- }
10535
- if (component.name === "list") {
10536
- visitList(props);
10537
- return;
10538
- }
10539
- if (!isContainer(component.name)) return;
10540
- const children = component.children;
10541
- if (Array.isArray(children)) children.forEach(visit);
10542
- };
10543
- section2.components.forEach(visit);
10544
- });
10545
- return { entries, numberedItems };
10546
- }
10547
-
10548
- // src/core/tocField.ts
10549
- function toStyleDisplayName(styleId) {
10550
- return styleId.replace(/([A-Z])/g, " $1").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim();
10551
- }
10552
- function parseDepthRange(rawDepth, fieldName, defaultFrom = 1, defaultTo = 3) {
10553
- if (typeof rawDepth !== "object" || rawDepth === null) {
10554
- throw new Error(
10555
- `${fieldName} must be a range object with optional "from" and/or "to" fields, received: ${JSON.stringify(rawDepth)}`
10556
- );
10557
- }
10558
- const { from = defaultFrom, to = defaultTo } = rawDepth;
10559
- if (from < 1 || from > 6 || to < 1 || to > 6) {
10560
- throw new Error(
10561
- `${fieldName} range values must be between 1 and 6, received: from=${from}, to=${to}`
10562
- );
10563
- }
10564
- if (from > to) {
10565
- throw new Error(
10566
- `${fieldName} "from" must be less than or equal to "to", received: from=${from}, to=${to}`
10567
- );
10568
- }
10569
- return { from, to };
10570
- }
10571
- function selectEntries(collected, options) {
10572
- if (!collected || collected.length === 0) return [];
10573
- const { depthStart, depthEnd, bookmarkScope, styleLevels } = options;
10574
- const entries = [];
10575
- for (const entry of collected) {
10576
- if (bookmarkScope && entry.sectionBookmarkId !== bookmarkScope) continue;
10577
- if (entry.styleId !== void 0) {
10578
- const level = styleLevels.get(entry.styleId) ?? styleLevels.get(toStyleDisplayName(entry.styleId));
10579
- if (level === void 0) continue;
10580
- entries.push({ text: entry.title, level });
10581
- continue;
10582
- }
10583
- if (entry.level < depthStart || entry.level > depthEnd) continue;
10584
- entries.push({
10585
- text: entry.number ? `${headingNumberLabel(entry.number)} ${entry.title}` : entry.title,
10586
- level: entry.level
10587
- });
10588
- }
10589
- return entries;
10590
- }
10591
- function resolveTocField(props, theme, context) {
10592
- const warnings = [];
10593
- const headingRange = parseDepthRange(
10594
- props.depth ?? { to: 3 },
10595
- "TOC depth",
10596
- 1,
10597
- 3
10598
- );
10599
- const pageNumbers = props.pageNumbersDepth !== void 0 ? parseDepthRange(props.pageNumbersDepth, "TOC pageNumbersDepth", 1, 3) : void 0;
10600
- if (props.numberingStyle !== void 0) {
10601
- warnings.push(
10602
- `TOC numberingStyle "${props.numberingStyle}" is ignored: Word's table-of-contents field has no numbering switch. TOC entries inherit numbering from the heading styles they reference.`
10603
- );
10604
- }
10605
- const scope = props.scope ?? "auto";
10606
- const effectiveScope = scope === "auto" ? context.sectionBookmarkId ? "section" : "document" : scope;
10607
- const bookmarkScope = effectiveScope === "section" ? context.sectionBookmarkId : void 0;
10608
- if (effectiveScope === "section" && !bookmarkScope) {
10609
- warnings.push(
10610
- "TOC configured for section scope but no section bookmark found. Falling back to document scope."
10611
- );
10612
- }
10613
- const styleLevels = /* @__PURE__ */ new Map();
10614
- const styleSwitches = [];
10615
- for (const mapping of props.styles ?? []) {
10616
- const isCustomStyle = !!theme.styles && Object.prototype.hasOwnProperty.call(theme.styles, mapping.styleId);
10617
- const displayName = isCustomStyle ? toStyleDisplayName(mapping.styleId) : mapping.styleId;
10618
- styleLevels.set(mapping.styleId, mapping.level);
10619
- styleLevels.set(displayName, mapping.level);
10620
- styleSwitches.push({ styleName: displayName, level: mapping.level });
10621
- }
10622
- return {
10623
- headingRange,
10624
- styleLevels: styleSwitches,
10625
- ...bookmarkScope ? { bookmarkScope } : {},
10626
- omitPageNumbersForLevels: omittedLevels(
10627
- headingRange,
10628
- pageNumbers,
10629
- props.includePageNumbers !== false
10630
- ),
10631
- // A boolean chooses between a tab and a space; a tab is the default.
10632
- entrySeparator: props.numberSeparator === false ? " " : " ",
10633
- entries: selectEntries(context.collected, {
10634
- depthStart: headingRange.from,
10635
- depthEnd: headingRange.to,
10636
- bookmarkScope,
10637
- styleLevels
10638
- }),
10639
- warnings
10607
+ source,
10608
+ columns,
10609
+ options.measureTwips ?? getAvailableWidthTwips(theme, themeName)
10610
+ ),
10611
+ ...hasHeader ? { header } : {},
10612
+ rows,
10613
+ repeatHeader: source.repeatHeaderOnPageBreak ?? true
10640
10614
  };
10641
10615
  }
10642
- function omittedLevels(headingRange, pageNumbers, includePageNumbers) {
10643
- if (pageNumbers) {
10644
- const omitted = [];
10645
- if (pageNumbers.from > headingRange.from) {
10646
- omitted.push({ from: headingRange.from, to: pageNumbers.from - 1 });
10647
- }
10648
- if (pageNumbers.to < headingRange.to) {
10649
- omitted.push({ from: pageNumbers.to + 1, to: headingRange.to });
10650
- }
10651
- return omitted;
10616
+ function drawsNothing(border3) {
10617
+ return border3.hidden || border3.size <= 0;
10618
+ }
10619
+ function channels(hex) {
10620
+ const digits = hex.startsWith("#") ? hex.slice(1) : hex;
10621
+ return [
10622
+ parseInt(digits.slice(0, 2), 16) || 0,
10623
+ parseInt(digits.slice(2, 4), 16) || 0,
10624
+ parseInt(digits.slice(4, 6), 16) || 0
10625
+ ];
10626
+ }
10627
+ function outweighs(a, b) {
10628
+ if (drawsNothing(a)) return false;
10629
+ if (drawsNothing(b)) return true;
10630
+ if (a.size !== b.size) return a.size > b.size;
10631
+ const [redA, greenA, blueA] = channels(a.color);
10632
+ const [redB, greenB, blueB] = channels(b.color);
10633
+ const brightness = [
10634
+ [redA + blueA + 2 * greenA, redB + blueB + 2 * greenB],
10635
+ [blueA + 2 * greenA, blueB + 2 * greenB],
10636
+ [greenA, greenB]
10637
+ ];
10638
+ for (const [ours, theirs] of brightness) {
10639
+ if (ours !== theirs) return ours < theirs;
10652
10640
  }
10653
- return includePageNumbers ? [] : [headingRange];
10641
+ return false;
10654
10642
  }
10655
-
10656
- // src/core/markdownList.ts
10657
- function parseMarkdownList(text) {
10658
- const lines = text.split("\n");
10659
- const items = [];
10660
- let isUnordered = null;
10661
- let isOrdered = null;
10662
- for (const line of lines) {
10663
- if (!line.trim()) continue;
10664
- const unorderedMatch = line.match(/^(\s*)([-*])\s+(.+)$/);
10665
- if (unorderedMatch) {
10666
- const indentLevel = Math.floor(unorderedMatch[1].length / 2);
10667
- const text2 = unorderedMatch[3];
10668
- items.push({ text: text2, level: indentLevel });
10669
- if (isUnordered === null) isUnordered = true;
10670
- continue;
10671
- }
10672
- const orderedMatch = line.match(/^(\s*)(\d+)\.\s+(.+)$/);
10673
- if (orderedMatch) {
10674
- const indentLevel = Math.floor(orderedMatch[1].length / 2);
10675
- const text2 = orderedMatch[3];
10676
- items.push({ text: text2, level: indentLevel });
10677
- if (isOrdered === null) isOrdered = true;
10678
- continue;
10643
+ function adjudicateInteriorEdges(rows) {
10644
+ const settle = (earlier, later) => {
10645
+ if (drawsNothing(earlier) && drawsNothing(later)) return;
10646
+ if (earlier.hidden === later.hidden && earlier.size === later.size && earlier.color === later.color) {
10647
+ return;
10679
10648
  }
10680
- return null;
10681
- }
10682
- if (items.length === 0 || isUnordered && isOrdered) {
10683
- return null;
10649
+ const winner = earlier.explicit !== later.explicit ? earlier.explicit ? earlier : later : outweighs(later, earlier) ? later : earlier;
10650
+ const loser = winner === earlier ? later : earlier;
10651
+ loser.size = winner.size;
10652
+ loser.color = winner.color;
10653
+ loser.hidden = winner.hidden;
10654
+ };
10655
+ rows.forEach((row, rowIndex) => {
10656
+ row.cells.forEach((cell, colIndex) => {
10657
+ const right = row.cells[colIndex + 1];
10658
+ if (right) settle(cell.borders.right, right.borders.left);
10659
+ const below = rows[rowIndex + 1]?.cells[colIndex];
10660
+ if (below) settle(cell.borders.bottom, below.borders.top);
10661
+ });
10662
+ });
10663
+ }
10664
+ function resolveWidths(source, columns, measureTwips) {
10665
+ const hasExplicitWidths = columns.some((col) => col.width !== void 0);
10666
+ if (!hasExplicitWidths) {
10667
+ const percent = source.width ?? 100;
10668
+ return {
10669
+ columnGrid: {
10670
+ unit: "twips",
10671
+ values: splitTwips(columns.length, measureTwips * percent / 100)
10672
+ },
10673
+ width: { size: percent, unit: "percent" }
10674
+ };
10684
10675
  }
10676
+ const stated = columns.map(
10677
+ (col) => col.width !== void 0 ? relativeLengthToTwips(col.width, measureTwips) : void 0
10678
+ );
10679
+ const totalStated = stated.reduce((sum, w) => sum + (w ?? 0), 0);
10680
+ const unstatedCount = columns.filter((col) => col.width === void 0).length;
10681
+ const remaining = Math.max(0, measureTwips - totalStated);
10682
+ const shareOfRemainder = unstatedCount > 0 ? remaining / unstatedCount : pointsToTwips(72);
10683
+ const values = stated.map((w) => w || shareOfRemainder);
10685
10684
  return {
10686
- type: isUnordered ? "unordered" : "ordered",
10687
- items
10685
+ columnGrid: { unit: "twips", values },
10686
+ width: {
10687
+ size: values.reduce((sum, w) => sum + w, 0),
10688
+ unit: "twips"
10689
+ },
10690
+ ...totalStated > measureTwips ? { overflow: { totalTwips: totalStated, availableTwips: measureTwips } } : {}
10688
10691
  };
10689
10692
  }
10690
10693
 
10691
- // src/ir/inline.ts
10692
- function escapeRegExp(value) {
10693
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10694
- }
10695
- function buildNoProofWordsRegex(noProofWords2) {
10696
- const words = (noProofWords2 ?? []).filter((word) => word?.trim().length > 0);
10697
- if (words.length === 0) return null;
10698
- const escaped = words.slice().sort((a, b) => b.length - a.length).map(escapeRegExp);
10699
- return new RegExp(
10700
- `(?<![\\p{L}\\p{N}])(?:${escaped.join("|")})(?![\\p{L}\\p{N}])`,
10701
- "giu"
10702
- );
10694
+ // src/ir/compiler.ts
10695
+ init_layoutUtils();
10696
+
10697
+ // src/utils/revisionUtils.ts
10698
+ var DEFAULT_REVISION_AUTHOR = "json-to-office";
10699
+ var DEFAULT_REVISION_DATE = "1970-01-01T00:00:00Z";
10700
+
10701
+ // src/utils/commentRegistry.ts
10702
+ var DEFAULT_COMMENT_AUTHOR = "json-to-office";
10703
+ var DEFAULT_COMMENT_DATE = "1970-01-01T00:00:00Z";
10704
+
10705
+ // src/ir/compiler.ts
10706
+ init_imageUtils();
10707
+ init_widthUtils();
10708
+
10709
+ // src/utils/numberingConfig.ts
10710
+ var HEADING_NUMBERING_REFERENCE = "jto-heading-numbering";
10711
+ function headingNumberLabel(number) {
10712
+ return `${number}.`;
10703
10713
  }
10704
- var DECORATOR = /(\*\*\*|___)([\s\S]*?)\1|(\*\*|__)([\s\S]*?)\3|(\*|_)([\s\S]*?)\5/g;
10705
- var ESCAPABLE = "*_[]{}\\";
10706
- var ESCAPE = /\\([*_[\]{}\\])/g;
10707
- var SENTINEL = "\uE000";
10708
- var SENTINEL_SELF = "x";
10709
- var SENTINEL_DIGIT_BASE = 48;
10710
- function encodeEscapes(text) {
10711
- const authored = text.includes(SENTINEL);
10712
- if (!authored && !text.includes("\\")) return text;
10713
- const guarded = authored ? text.split(SENTINEL).join(SENTINEL + SENTINEL_SELF) : text;
10714
- return guarded.replace(
10715
- ESCAPE,
10716
- (_whole, char) => SENTINEL + String.fromCharCode(SENTINEL_DIGIT_BASE + ESCAPABLE.indexOf(char))
10717
- );
10714
+
10715
+ // src/core/collectTocHeadings.ts
10716
+ import { getStandardComponent } from "@json-to-office/shared-docx";
10717
+
10718
+ // src/core/sectionBookmarks.ts
10719
+ function sectionBookmark(ordinal) {
10720
+ return { id: `_Section_${ordinal}`, linkId: ordinal };
10718
10721
  }
10719
- function decodeEscapes(text) {
10720
- if (!text.includes(SENTINEL)) return text;
10722
+
10723
+ // src/utils/numberFormatting.ts
10724
+ var ROMAN_NUMERALS = [
10725
+ [1e3, "m"],
10726
+ [900, "cm"],
10727
+ [500, "d"],
10728
+ [400, "cd"],
10729
+ [100, "c"],
10730
+ [90, "xc"],
10731
+ [50, "l"],
10732
+ [40, "xl"],
10733
+ [10, "x"],
10734
+ [9, "ix"],
10735
+ [5, "v"],
10736
+ [4, "iv"],
10737
+ [1, "i"]
10738
+ ];
10739
+ function toRoman(value) {
10740
+ let remaining = value;
10721
10741
  let out = "";
10722
- for (let i = 0; i < text.length; i += 1) {
10723
- if (text[i] !== SENTINEL) {
10724
- out += text[i];
10725
- continue;
10726
- }
10727
- const marker = text[i + 1];
10728
- if (marker === SENTINEL_SELF) {
10729
- out += SENTINEL;
10730
- i += 1;
10731
- continue;
10732
- }
10733
- const index = marker === void 0 ? -1 : marker.charCodeAt(0) - SENTINEL_DIGIT_BASE;
10734
- if (index >= 0 && index < ESCAPABLE.length) {
10735
- out += ESCAPABLE[index];
10736
- i += 1;
10737
- continue;
10742
+ for (const [amount, glyph] of ROMAN_NUMERALS) {
10743
+ while (remaining >= amount) {
10744
+ out += glyph;
10745
+ remaining -= amount;
10738
10746
  }
10739
- out += text[i];
10740
10747
  }
10741
10748
  return out;
10742
10749
  }
10743
- var LINK = /\[([^\]]+)\]\(([^)]+)\)/g;
10744
- var DECORATOR_OR_PLACEHOLDER = /(\*\*\*|___)([\s\S]*?)\1|(\*\*|__)([\s\S]*?)\3|(\*|_)([\s\S]*?)\5|\{([^}]+)\}/g;
10745
- var PLACEHOLDER = /\{([^}]+)\}/;
10746
- var UNSUPPORTED_SYNTAX = [];
10747
- var CROSS_REFERENCE = /\[@([^\]\s:]+)(?::(relative|no_context|full_context|none))?\]/;
10748
- var INLINE_TOKEN = new RegExp(
10749
- `${LINK.source}|${CROSS_REFERENCE.source}`,
10750
- "g"
10751
- );
10752
- var NOTE_MARKER = /\[\^([^\]\s]+)\]/g;
10753
- function containsPlaceholder(text) {
10754
- return PLACEHOLDER.test(text);
10750
+ function toLetters(value) {
10751
+ const index = (value - 1) % 26;
10752
+ const repeats = Math.floor((value - 1) / 26) + 1;
10753
+ return String.fromCharCode(97 + index).repeat(repeats);
10755
10754
  }
10756
- function containsCrossReference(text) {
10757
- return new RegExp(CROSS_REFERENCE.source).test(text);
10755
+ function formatNumberForLevel(value, format2) {
10756
+ if (!Number.isFinite(value) || value < 1) return void 0;
10757
+ const n = Math.floor(value);
10758
+ switch (format2) {
10759
+ case "decimal":
10760
+ return String(n);
10761
+ case "lowerLetter":
10762
+ return toLetters(n);
10763
+ case "upperLetter":
10764
+ return toLetters(n).toUpperCase();
10765
+ case "lowerRoman":
10766
+ return toRoman(n);
10767
+ case "upperRoman":
10768
+ return toRoman(n).toUpperCase();
10769
+ default:
10770
+ return void 0;
10771
+ }
10772
+ }
10773
+
10774
+ // src/core/collectTocHeadings.ts
10775
+ var MAX_HEADING_LEVEL = 6;
10776
+ var MAX_LIST_LEVEL = 9;
10777
+ function normalizeEntryTitle(text) {
10778
+ return normalizeUnicodeText(text).replace(/(\*\*\*|___)([\s\S]*?)\1/g, "$2").replace(/(\*\*|__)([\s\S]*?)\1/g, "$2").replace(/(\*|_)([\s\S]*?)\1/g, "$2").trim();
10779
+ }
10780
+ function isContainer(name) {
10781
+ return getStandardComponent(name)?.hasChildren === true;
10758
10782
  }
10759
- function containsLink(text) {
10760
- return new RegExp(LINK.source).test(text);
10783
+ function isEnabled(component) {
10784
+ return !("enabled" in component && component.enabled === false);
10761
10785
  }
10762
- function containsUnsupportedSyntax(text) {
10763
- for (const { pattern, what } of UNSUPPORTED_SYNTAX) {
10764
- if (pattern.test(text)) return what;
10786
+ function styleEntryKey(props) {
10787
+ const themeStyle = props.themeStyle;
10788
+ if (typeof themeStyle !== "string" || !themeStyle) return void 0;
10789
+ if (/^heading[1-6]$/i.test(themeStyle)) return void 0;
10790
+ if (["normal", "title", "subtitle"].includes(themeStyle.toLowerCase())) {
10791
+ return void 0;
10765
10792
  }
10766
- return void 0;
10793
+ return themeStyle;
10767
10794
  }
10768
- function parseInline(text, options) {
10769
- const normalized = encodeEscapes(normalizeUnicodeText(text));
10770
- if (!normalized) {
10771
- return [
10772
- { kind: "text", text: "", formatting: emptyToUndefined(options.base) }
10773
- ];
10774
- }
10775
- if (containsPlaceholder(normalized)) {
10776
- return parsePlaceholders(normalized, options);
10777
- }
10778
- if (options.hyperlinks) return parseLinks(normalized, options);
10779
- return parseDecorated(normalized, options);
10795
+ function levelStart(levels, level) {
10796
+ return levels[level]?.start ?? 1;
10780
10797
  }
10781
- function parsePlaceholders(normalized, parseOptions) {
10782
- const options = {
10783
- ...parseOptions,
10784
- boldColor: void 0,
10785
- resolveNote: void 0
10786
- };
10787
- const out = [];
10788
- const token = new RegExp(DECORATOR_OR_PLACEHOLDER.source, "g");
10789
- let lastIndex = 0;
10790
- let match;
10791
- const literal = (text, base = options.base) => {
10792
- if (!text) return;
10793
- out.push(...parseLiteralLines(text, { ...options, base }));
10798
+ function collectDocumentOutline(sections) {
10799
+ const entries = [];
10800
+ const numberedItems = /* @__PURE__ */ new Map();
10801
+ const ordinals = computeSectionOrdinals(sections);
10802
+ const takenIds = /* @__PURE__ */ new Set();
10803
+ const headingCounters = new Array(MAX_HEADING_LEVEL).fill(0);
10804
+ const listCounters = /* @__PURE__ */ new Map();
10805
+ const visitHeading = (component, props, sectionBookmarkId) => {
10806
+ const text = typeof props.text === "string" ? props.text : "";
10807
+ const title = normalizeEntryTitle(text);
10808
+ const level = typeof props.level === "number" ? props.level : 1;
10809
+ const styleLevel = level >= 1 && level <= MAX_HEADING_LEVEL ? level : 1;
10810
+ let full;
10811
+ let own;
10812
+ if (props.numbering === true) {
10813
+ headingCounters[styleLevel - 1] += 1;
10814
+ for (let deeper = styleLevel; deeper < MAX_HEADING_LEVEL; deeper++) {
10815
+ headingCounters[deeper] = 0;
10816
+ }
10817
+ full = headingCounters.slice(0, styleLevel).join(".");
10818
+ own = String(headingCounters[styleLevel - 1]);
10819
+ }
10820
+ const explicitId = component.id;
10821
+ const bookmarkId = typeof explicitId === "string" && explicitId ? explicitId : dedupeBookmarkId(slugifyBookmarkText(text), (id) => takenIds.has(id));
10822
+ takenIds.add(bookmarkId);
10823
+ numberedItems.set(bookmarkId, {
10824
+ kind: "heading",
10825
+ text: title,
10826
+ ...full !== void 0 && { full, own }
10827
+ });
10828
+ if (title) {
10829
+ entries.push({
10830
+ title,
10831
+ level,
10832
+ sectionBookmarkId,
10833
+ ...full !== void 0 && { number: full }
10834
+ });
10835
+ }
10794
10836
  };
10795
- while ((match = token.exec(normalized)) !== null) {
10796
- if (match.index > lastIndex) {
10797
- literal(normalized.slice(lastIndex, match.index));
10837
+ const visitList = (props) => {
10838
+ const items = Array.isArray(props.items) ? props.items : [];
10839
+ if (items.length === 0) return;
10840
+ const reference = typeof props.reference === "string" && props.reference ? props.reference : void 0;
10841
+ const freshState = () => {
10842
+ const levels = resolveListLevels(
10843
+ props
10844
+ );
10845
+ return {
10846
+ levels,
10847
+ counters: Array.from(
10848
+ { length: MAX_LIST_LEVEL },
10849
+ (_, level) => levelStart(levels, level) - 1
10850
+ )
10851
+ };
10852
+ };
10853
+ let state;
10854
+ if (reference === void 0) {
10855
+ state = freshState();
10856
+ } else {
10857
+ state = listCounters.get(reference) ?? freshState();
10858
+ listCounters.set(reference, state);
10798
10859
  }
10799
- lastIndex = match.index + match[0].length;
10800
- if (match[7]) {
10801
- const resolved = options.resolvePlaceholder?.(match[7]);
10802
- if (!resolved) {
10803
- literal(match[0]);
10804
- } else if (resolved.kind === "text") {
10805
- literal(resolved.text);
10806
- } else {
10807
- const formatting = emptyToUndefined({
10808
- fontFamily: options.base.fontFamily,
10809
- sizeHalfPoints: options.base.sizeHalfPoints,
10810
- color: options.base.color,
10811
- bold: options.base.bold,
10812
- italic: options.base.italic,
10813
- underline: options.base.underline
10814
- });
10815
- out.push({
10816
- kind: "field",
10817
- instruction: resolved.instruction,
10818
- ...resolved.cachedText !== void 0 && {
10819
- cachedText: resolved.cachedText
10820
- },
10821
- ...formatting ? { formatting } : {}
10822
- });
10860
+ for (const item of items) {
10861
+ const isObject = typeof item === "object" && item !== null;
10862
+ const raw = isObject ? item.text : item;
10863
+ const text = typeof raw === "string" ? raw : "";
10864
+ if (!text.trim() && !(isObject && item.revision)) continue;
10865
+ const rawLevel = isObject ? item.level : void 0;
10866
+ const level = typeof rawLevel === "number" && rawLevel >= 0 ? rawLevel : 0;
10867
+ if (level >= MAX_LIST_LEVEL) continue;
10868
+ state.counters[level] += 1;
10869
+ for (let deeper = level + 1; deeper < MAX_LIST_LEVEL; deeper++) {
10870
+ state.counters[deeper] = levelStart(state.levels, deeper) - 1;
10823
10871
  }
10824
- continue;
10872
+ const id = isObject ? item.id : void 0;
10873
+ if (typeof id !== "string" || !id) continue;
10874
+ takenIds.add(id);
10875
+ const number = formatNumberForLevel(
10876
+ state.counters[level],
10877
+ state.levels[level]?.format
10878
+ );
10879
+ numberedItems.set(id, {
10880
+ kind: "list-item",
10881
+ text: normalizeUnicodeText(text).trim(),
10882
+ ...number !== void 0 && { full: number, own: number }
10883
+ });
10825
10884
  }
10826
- const style = decoratedStyle(match, options.base);
10827
- out.push(
10828
- ...parsePlaceholders(decoratedText(match), {
10829
- ...options,
10830
- base: { ...options.base, ...style },
10831
- // No-proof words do not survive into a decorated span here: the
10832
- // recursion has always been entered without them.
10833
- noProofWords: void 0
10834
- })
10835
- );
10836
- }
10837
- if (lastIndex < normalized.length) literal(normalized.slice(lastIndex));
10838
- if (out.length === 0 && normalized) literal(normalized);
10839
- return out;
10840
- }
10841
- function parseLiteralLines(text, options) {
10842
- const out = [];
10843
- pushSegment(out, text, options);
10844
- return out;
10885
+ };
10886
+ sections.forEach((section2, index) => {
10887
+ const ordinal = ordinals[index]?.ordinal;
10888
+ const sectionBookmarkId = ordinal ? sectionBookmark(ordinal).id : void 0;
10889
+ const visit = (component) => {
10890
+ if (!isEnabled(component)) return;
10891
+ const props = component.props ?? {};
10892
+ if (component.name === "heading") {
10893
+ visitHeading(component, props, sectionBookmarkId);
10894
+ return;
10895
+ }
10896
+ if (component.name === "paragraph") {
10897
+ if (typeof props.id === "string" && props.id) takenIds.add(props.id);
10898
+ const styleId = styleEntryKey(props);
10899
+ const text = typeof props.text === "string" ? props.text : "";
10900
+ const title = normalizeEntryTitle(text);
10901
+ if (styleId && title) {
10902
+ entries.push({ title, level: 1, styleId, sectionBookmarkId });
10903
+ }
10904
+ return;
10905
+ }
10906
+ if (component.name === "list") {
10907
+ visitList(props);
10908
+ return;
10909
+ }
10910
+ if (!isContainer(component.name)) return;
10911
+ const children = component.children;
10912
+ if (Array.isArray(children)) children.forEach(visit);
10913
+ };
10914
+ section2.components.forEach(visit);
10915
+ });
10916
+ return { entries, numberedItems };
10845
10917
  }
10846
- function parseLiteral(text, options) {
10847
- const out = [];
10848
- pushWords(
10849
- out,
10850
- normalizeUnicodeText(text),
10851
- emptyToUndefined(options.base),
10852
- options
10853
- );
10854
- return out;
10918
+
10919
+ // src/core/tocField.ts
10920
+ function toStyleDisplayName(styleId) {
10921
+ return styleId.replace(/([A-Z])/g, " $1").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim();
10855
10922
  }
10856
- function parseLinks(normalized, options) {
10857
- const out = [];
10858
- const token = new RegExp(INLINE_TOKEN.source, "g");
10859
- let lastIndex = 0;
10860
- let match;
10861
- while ((match = token.exec(normalized)) !== null) {
10862
- if (match.index > lastIndex) {
10863
- out.push(
10864
- ...parseDecorated(normalized.slice(lastIndex, match.index), options)
10865
- );
10866
- }
10867
- lastIndex = match.index + match[0].length;
10868
- if (match[1] === void 0) {
10869
- const resolved = options.resolveCrossReference?.(
10870
- decodeEscapes(match[3]),
10871
- match[4] ?? "relative",
10872
- match[0]
10873
- );
10874
- if (resolved) out.push(resolved);
10875
- else out.push(...parseDecorated(match[0], options));
10923
+ function parseDepthRange(rawDepth, fieldName, defaultFrom = 1, defaultTo = 3) {
10924
+ if (typeof rawDepth !== "object" || rawDepth === null) {
10925
+ throw new Error(
10926
+ `${fieldName} must be a range object with optional "from" and/or "to" fields, received: ${JSON.stringify(rawDepth)}`
10927
+ );
10928
+ }
10929
+ const { from = defaultFrom, to = defaultTo } = rawDepth;
10930
+ if (from < 1 || from > 6 || to < 1 || to > 6) {
10931
+ throw new Error(
10932
+ `${fieldName} range values must be between 1 and 6, received: from=${from}, to=${to}`
10933
+ );
10934
+ }
10935
+ if (from > to) {
10936
+ throw new Error(
10937
+ `${fieldName} "from" must be less than or equal to "to", received: from=${from}, to=${to}`
10938
+ );
10939
+ }
10940
+ return { from, to };
10941
+ }
10942
+ function selectEntries(collected, options) {
10943
+ if (!collected || collected.length === 0) return [];
10944
+ const { depthStart, depthEnd, bookmarkScope, styleLevels } = options;
10945
+ const entries = [];
10946
+ for (const entry of collected) {
10947
+ if (bookmarkScope && entry.sectionBookmarkId !== bookmarkScope) continue;
10948
+ if (entry.styleId !== void 0) {
10949
+ const level = styleLevels.get(entry.styleId) ?? styleLevels.get(toStyleDisplayName(entry.styleId));
10950
+ if (level === void 0) continue;
10951
+ entries.push({ text: entry.title, level });
10876
10952
  continue;
10877
10953
  }
10878
- const [, linkText] = match;
10879
- const target2 = decodeEscapes(match[2]);
10880
- out.push({
10881
- kind: "hyperlink",
10882
- // A `#anchor` target names a bookmark in this document and needs no
10883
- // relationship; anything else leaves the document.
10884
- target: target2.startsWith("#") ? { kind: "bookmark", anchor: target2.slice(1) } : { kind: "external", url: target2 },
10885
- children: parseDecorated(linkText, options)
10954
+ if (entry.level < depthStart || entry.level > depthEnd) continue;
10955
+ entries.push({
10956
+ text: entry.number ? `${headingNumberLabel(entry.number)} ${entry.title}` : entry.title,
10957
+ level: entry.level
10886
10958
  });
10887
10959
  }
10888
- if (lastIndex < normalized.length) {
10889
- out.push(...parseDecorated(normalized.slice(lastIndex), options));
10890
- }
10891
- if (out.length === 0) return parseDecorated(normalized, options);
10892
- return out;
10960
+ return entries;
10893
10961
  }
10894
- function parseDecorated(normalized, options) {
10895
- const out = [];
10896
- let lastIndex = 0;
10897
- let match;
10898
- const decorator = new RegExp(DECORATOR.source, "g");
10899
- while ((match = decorator.exec(normalized)) !== null) {
10900
- if (match.index > lastIndex) {
10901
- pushSegment(out, normalized.slice(lastIndex, match.index), options);
10902
- }
10903
- pushSegment(
10904
- out,
10905
- decoratedText(match),
10906
- options,
10907
- decoratedStyle(match, options.base)
10962
+ function resolveTocField(props, theme, context) {
10963
+ const warnings = [];
10964
+ const headingRange = parseDepthRange(
10965
+ props.depth ?? { to: 3 },
10966
+ "TOC depth",
10967
+ 1,
10968
+ 3
10969
+ );
10970
+ const pageNumbers = props.pageNumbersDepth !== void 0 ? parseDepthRange(props.pageNumbersDepth, "TOC pageNumbersDepth", 1, 3) : void 0;
10971
+ if (props.numberingStyle !== void 0) {
10972
+ warnings.push(
10973
+ `TOC numberingStyle "${props.numberingStyle}" is ignored: Word's table-of-contents field has no numbering switch. TOC entries inherit numbering from the heading styles they reference.`
10908
10974
  );
10909
- lastIndex = match.index + match[0].length;
10910
10975
  }
10911
- if (lastIndex < normalized.length) {
10912
- pushSegment(out, normalized.slice(lastIndex), options);
10976
+ const scope = props.scope ?? "auto";
10977
+ const effectiveScope = scope === "auto" ? context.sectionBookmarkId ? "section" : "document" : scope;
10978
+ const bookmarkScope = effectiveScope === "section" ? context.sectionBookmarkId : void 0;
10979
+ if (effectiveScope === "section" && !bookmarkScope) {
10980
+ warnings.push(
10981
+ "TOC configured for section scope but no section bookmark found. Falling back to document scope."
10982
+ );
10913
10983
  }
10914
- if (out.length === 0) pushSegment(out, normalized, options);
10915
- return out;
10916
- }
10917
- function decoratedText(match) {
10918
- if (match[1]) return match[2];
10919
- if (match[3]) return match[4];
10920
- if (match[5]) return match[6];
10921
- return match[0];
10922
- }
10923
- function decoratedStyle(match, base) {
10924
- const bold = base.bold ?? false;
10925
- const italic = base.italic ?? false;
10926
- if (match[1]) return { bold: true, italic: true };
10927
- if (match[3]) return { bold: true, italic };
10928
- if (match[5]) return { bold, italic: true };
10929
- return { bold, italic };
10984
+ const styleLevels = /* @__PURE__ */ new Map();
10985
+ const styleSwitches = [];
10986
+ for (const mapping of props.styles ?? []) {
10987
+ const isCustomStyle = !!theme.styles && Object.prototype.hasOwnProperty.call(theme.styles, mapping.styleId);
10988
+ const displayName = isCustomStyle ? toStyleDisplayName(mapping.styleId) : mapping.styleId;
10989
+ styleLevels.set(mapping.styleId, mapping.level);
10990
+ styleLevels.set(displayName, mapping.level);
10991
+ styleSwitches.push({ styleName: displayName, level: mapping.level });
10992
+ }
10993
+ return {
10994
+ headingRange,
10995
+ styleLevels: styleSwitches,
10996
+ ...bookmarkScope ? { bookmarkScope } : {},
10997
+ omitPageNumbersForLevels: omittedLevels(
10998
+ headingRange,
10999
+ pageNumbers,
11000
+ props.includePageNumbers !== false
11001
+ ),
11002
+ // A boolean chooses between a tab and a space; a tab is the default.
11003
+ entrySeparator: props.numberSeparator === false ? " " : " ",
11004
+ entries: selectEntries(context.collected, {
11005
+ depthStart: headingRange.from,
11006
+ depthEnd: headingRange.to,
11007
+ bookmarkScope,
11008
+ styleLevels
11009
+ }),
11010
+ warnings
11011
+ };
10930
11012
  }
10931
- function pushSegment(out, text, options, override = {}) {
10932
- const bold = override.bold ?? options.base.bold;
10933
- const italic = override.italic ?? options.base.italic;
10934
- const formatting = runFormatting2(options, { bold, italic });
10935
- const lines = decodeEscapes(text).split("\n");
10936
- for (const [lineIndex, line] of lines.entries()) {
10937
- if (!line && lineIndex === 0) continue;
10938
- if (lineIndex > 0) out.push({ kind: "lineBreak" });
10939
- if (!line) {
10940
- out.push({ kind: "text", text: "", formatting });
10941
- continue;
11013
+ function omittedLevels(headingRange, pageNumbers, includePageNumbers) {
11014
+ if (pageNumbers) {
11015
+ const omitted = [];
11016
+ if (pageNumbers.from > headingRange.from) {
11017
+ omitted.push({ from: headingRange.from, to: pageNumbers.from - 1 });
10942
11018
  }
10943
- const tabSegments = line.split(" ");
10944
- for (const [tabIndex, segment] of tabSegments.entries()) {
10945
- if (tabIndex > 0) out.push({ kind: "tab", formatting });
10946
- if (!segment && tabSegments.length > 1) continue;
10947
- pushWords(out, segment, formatting, options);
11019
+ if (pageNumbers.to < headingRange.to) {
11020
+ omitted.push({ from: pageNumbers.to + 1, to: headingRange.to });
10948
11021
  }
11022
+ return omitted;
10949
11023
  }
11024
+ return includePageNumbers ? [] : [headingRange];
10950
11025
  }
10951
- function pushWords(out, text, formatting, options) {
10952
- if (options.resolveNote) {
10953
- splitNoteMarkers(out, text, formatting, options);
10954
- return;
10955
- }
10956
- pushNoProofWords(out, text, formatting, options);
10957
- }
10958
- function splitNoteMarkers(out, text, formatting, options) {
10959
- const regex = new RegExp(NOTE_MARKER.source, "g");
10960
- let lastIndex = 0;
10961
- let pending = "";
10962
- let match;
10963
- while ((match = regex.exec(text)) !== null) {
10964
- const note = options.resolveNote(match[1]);
10965
- if (note === void 0) {
10966
- pending += text.slice(lastIndex, regex.lastIndex);
10967
- lastIndex = regex.lastIndex;
11026
+
11027
+ // src/core/markdownList.ts
11028
+ function parseMarkdownList(text) {
11029
+ const lines = text.split("\n");
11030
+ const items = [];
11031
+ let isUnordered = null;
11032
+ let isOrdered = null;
11033
+ for (const line of lines) {
11034
+ if (!line.trim()) continue;
11035
+ const unorderedMatch = line.match(/^(\s*)([-*])\s+(.+)$/);
11036
+ if (unorderedMatch) {
11037
+ const indentLevel = Math.floor(unorderedMatch[1].length / 2);
11038
+ const text2 = unorderedMatch[3];
11039
+ items.push({ text: text2, level: indentLevel });
11040
+ if (isUnordered === null) isUnordered = true;
10968
11041
  continue;
10969
11042
  }
10970
- const before = pending + text.slice(lastIndex, match.index);
10971
- pending = "";
10972
- if (before) pushNoProofWords(out, before, formatting, options);
10973
- out.push({
10974
- kind: "noteReference",
10975
- noteKind: note.noteKind,
10976
- id: note.id
10977
- });
10978
- lastIndex = regex.lastIndex;
10979
- }
10980
- const rest = pending + text.slice(lastIndex);
10981
- if (rest) pushNoProofWords(out, rest, formatting, options);
10982
- }
10983
- function pushNoProofWords(out, text, formatting, options) {
10984
- const wholeRunNoProof = options.base.noProof === true;
10985
- const regex = wholeRunNoProof ? null : buildNoProofWordsRegex(options.noProofWords);
10986
- if (!regex) {
10987
- out.push({ kind: "text", text, formatting });
10988
- return;
10989
- }
10990
- let lastIndex = 0;
10991
- let match;
10992
- while ((match = regex.exec(text)) !== null) {
10993
- if (match.index > lastIndex) {
10994
- out.push({
10995
- kind: "text",
10996
- text: text.slice(lastIndex, match.index),
10997
- formatting
10998
- });
11043
+ const orderedMatch = line.match(/^(\s*)(\d+)\.\s+(.+)$/);
11044
+ if (orderedMatch) {
11045
+ const indentLevel = Math.floor(orderedMatch[1].length / 2);
11046
+ const text2 = orderedMatch[3];
11047
+ items.push({ text: text2, level: indentLevel });
11048
+ if (isOrdered === null) isOrdered = true;
11049
+ continue;
10999
11050
  }
11000
- out.push({
11001
- kind: "text",
11002
- text: match[0],
11003
- formatting: { ...formatting, noProof: true }
11004
- });
11005
- lastIndex = match.index + match[0].length;
11006
- if (regex.lastIndex === match.index) regex.lastIndex += 1;
11007
- }
11008
- if (lastIndex < text.length) {
11009
- out.push({ kind: "text", text: text.slice(lastIndex), formatting });
11051
+ return null;
11010
11052
  }
11011
- }
11012
- function runFormatting2(options, state) {
11013
- const formatting = { ...options.base };
11014
- if (state.bold !== void 0) formatting.bold = state.bold;
11015
- if (state.italic !== void 0) formatting.italic = state.italic;
11016
- if (state.bold && options.boldColor && options.base.color) {
11017
- formatting.color = options.boldColor;
11053
+ if (items.length === 0 || isUnordered && isOrdered) {
11054
+ return null;
11018
11055
  }
11019
- return emptyToUndefined(formatting);
11020
- }
11021
- function emptyToUndefined(formatting) {
11022
- const entries = Object.entries(formatting).filter(
11023
- ([, value]) => value !== void 0
11024
- );
11025
- return entries.length === 0 ? void 0 : Object.fromEntries(entries);
11056
+ return {
11057
+ type: isUnordered ? "unordered" : "ordered",
11058
+ items
11059
+ };
11026
11060
  }
11027
11061
 
11028
11062
  // src/ir/compiler.ts
@@ -11266,6 +11300,10 @@ function noProofWords(theme) {
11266
11300
  const words = theme.noProofWords;
11267
11301
  return words && words.length > 0 ? words : void 0;
11268
11302
  }
11303
+ function plainRuns(text, ctx, formatting) {
11304
+ const words = noProofWords(ctx.theme);
11305
+ return words && text ? noProofRuns(text, formatting, words) : [{ kind: "text", text, ...formatting ? { formatting } : {} }];
11306
+ }
11269
11307
  function mergeNoProofWords(themeWords, componentWords) {
11270
11308
  const own = Array.isArray(componentWords) ? componentWords : [];
11271
11309
  const merged = [...themeWords ?? [], ...own];
@@ -11624,7 +11662,8 @@ function compileParagraph(component, scope) {
11624
11662
  ...props.alignment ? { alignment: props.alignment } : {},
11625
11663
  ...props.comment ? { comment: props.comment } : {},
11626
11664
  ...props.footnotes ? { footnotes: props.footnotes } : {},
11627
- ...props.endnotes ? { endnotes: props.endnotes } : {}
11665
+ ...props.endnotes ? { endnotes: props.endnotes } : {},
11666
+ ...props.noProofWords ? { noProofWords: props.noProofWords } : {}
11628
11667
  }
11629
11668
  },
11630
11669
  scope,
@@ -12138,13 +12177,10 @@ function compileToc(component, scope) {
12138
12177
  // No style, not even Normal: a title with an outline level would collect
12139
12178
  // itself into the very list it introduces. It carries its own weight and
12140
12179
  // size instead.
12141
- children: [
12142
- {
12143
- kind: "text",
12144
- text: String(props.title),
12145
- formatting: { bold: true, sizeHalfPoints: 28 }
12146
- }
12147
- ],
12180
+ children: plainRuns(String(props.title), ctx, {
12181
+ bold: true,
12182
+ sizeHalfPoints: 28
12183
+ }),
12148
12184
  formatting: {
12149
12185
  alignment: "left",
12150
12186
  spacing: {
@@ -12332,7 +12368,7 @@ function compileStatistic(component, scope) {
12332
12368
  ...spacing ? { spacing } : {}
12333
12369
  }),
12334
12370
  line(
12335
- descriptionText ? [{ kind: "text", text: descriptionText }] : [],
12371
+ descriptionText ? plainRuns(descriptionText, ctx) : [],
12336
12372
  STATISTIC_DESCRIPTION_STYLE_ID,
12337
12373
  "description",
12338
12374
  { alignment: alignment2 }
@@ -12350,6 +12386,7 @@ function compileList(component, scope, referencePrefix = "list", explicitLevels)
12350
12386
  ctx
12351
12387
  );
12352
12388
  const notes = createNoteBinding(props.footnotes, props.endnotes, ctx);
12389
+ const words = mergeNoProofWords(noProofWords(ctx.theme), props.noProofWords);
12353
12390
  const reference = declareNumbering(
12354
12391
  props,
12355
12392
  ctx,
@@ -12386,10 +12423,11 @@ function compileList(component, scope, referencePrefix = "list", explicitLevels)
12386
12423
  revision === void 0 ? parseInline(text, {
12387
12424
  base,
12388
12425
  hyperlinks: true,
12426
+ ...words ? { noProofWords: words } : {},
12389
12427
  ...notes ? { resolveNote: notes.resolve } : {},
12390
12428
  resolvePlaceholder: placeholderResolver(ctx),
12391
12429
  resolveCrossReference: crossReferenceResolver(ctx, itemPath)
12392
- }) : compileRevision(revision, base, ctx),
12430
+ }) : compileRevision(revision, base, ctx, words),
12393
12431
  {
12394
12432
  styleId: "Normal",
12395
12433
  formatting: {
@@ -12559,7 +12597,7 @@ function compileRuns(props, text, ctx, path3, mode = "inline") {
12559
12597
  ctx.features.require("revisions", path3);
12560
12598
  return [
12561
12599
  ...props.columnBreak ? [{ kind: "columnBreak" }] : [],
12562
- ...compileRevision(props.revision, base, ctx)
12600
+ ...compileRevision(props.revision, base, ctx, words)
12563
12601
  ];
12564
12602
  }
12565
12603
  const parseOptions = {
@@ -12682,7 +12720,7 @@ function createNoteBinding(footnotes, endnotes, ctx) {
12682
12720
  id: `${note.endnote ? "endnote" : "footnote"}${resolved.id}:p${index}`,
12683
12721
  path: `${note.endnote ? "endnotes" : "footnotes"}[${resolved.id}].children[${index}]`,
12684
12722
  styleId: note.endnote ? "EndnoteText" : "FootnoteText",
12685
- children: [{ kind: "text", text: line }]
12723
+ children: plainRuns(line, ctx)
12686
12724
  }))
12687
12725
  });
12688
12726
  registered.set(id, resolved);
@@ -12735,7 +12773,7 @@ function declareComment(comment, ctx, path3) {
12735
12773
  kind: "paragraph",
12736
12774
  id: `comment${id}:p${index}`,
12737
12775
  path: `comments[${id}].children[${index}]`,
12738
- children: [{ kind: "text", text: line }]
12776
+ children: plainRuns(line, ctx)
12739
12777
  })),
12740
12778
  ...parentId !== void 0 ? { parentId } : {},
12741
12779
  ...resolved !== void 0 ? { resolved } : {}
@@ -12761,7 +12799,7 @@ function closeComment(ids) {
12761
12799
  { kind: "commentReference", id }
12762
12800
  ]);
12763
12801
  }
12764
- function compileRevision(revision, base, ctx) {
12802
+ function compileRevision(revision, base, ctx, words = noProofWords(ctx.theme)) {
12765
12803
  const author = revision.author || DEFAULT_REVISION_AUTHOR;
12766
12804
  const date = revision.date || DEFAULT_REVISION_DATE;
12767
12805
  const out = [];
@@ -12769,15 +12807,17 @@ function compileRevision(revision, base, ctx) {
12769
12807
  if (!segment.text) continue;
12770
12808
  const text = normalizeUnicodeText(segment.text);
12771
12809
  if (segment.type === "insert" || segment.type === "delete") {
12772
- for (const line of literalLines(text, base)) {
12773
- out.push({
12774
- kind: "revision",
12775
- type: segment.type,
12776
- id: ctx.nextRevisionId++,
12777
- author,
12778
- date,
12779
- children: line
12780
- });
12810
+ for (const line of literalLines(text, base, words)) {
12811
+ for (const children of oneRunEach(line)) {
12812
+ out.push({
12813
+ kind: "revision",
12814
+ type: segment.type,
12815
+ id: ctx.nextRevisionId++,
12816
+ author,
12817
+ date,
12818
+ children
12819
+ });
12820
+ }
12781
12821
  }
12782
12822
  continue;
12783
12823
  }
@@ -12785,24 +12825,36 @@ function compileRevision(revision, base, ctx) {
12785
12825
  out.push(
12786
12826
  ...parseInline(text, {
12787
12827
  base,
12828
+ ...words ? { noProofWords: words } : {},
12788
12829
  resolvePlaceholder: placeholderResolver(ctx)
12789
12830
  })
12790
12831
  );
12791
12832
  continue;
12792
12833
  }
12793
- for (const line of literalLines(text, base)) out.push(...line);
12834
+ for (const line of literalLines(text, base, words)) out.push(...line);
12794
12835
  }
12795
12836
  return out;
12796
12837
  }
12797
- function literalLines(text, base) {
12838
+ function oneRunEach(line) {
12839
+ const ranges = [];
12840
+ for (const node of line) {
12841
+ const last = ranges[ranges.length - 1];
12842
+ if (last?.[last.length - 1]?.kind === "lineBreak") last.push(node);
12843
+ else ranges.push([node]);
12844
+ }
12845
+ return ranges;
12846
+ }
12847
+ function literalLines(text, base, words) {
12798
12848
  const formatting = Object.keys(base).length > 0 ? base : void 0;
12799
12849
  return text.split("\n").map((line, index) => [
12800
12850
  ...index > 0 ? [{ kind: "lineBreak" }] : [],
12801
- {
12802
- kind: "text",
12803
- text: line,
12804
- ...formatting ? { formatting } : {}
12805
- }
12851
+ ...words && line ? noProofRuns(line, formatting, words) : [
12852
+ {
12853
+ kind: "text",
12854
+ text: line,
12855
+ ...formatting ? { formatting } : {}
12856
+ }
12857
+ ]
12806
12858
  ]);
12807
12859
  }
12808
12860
  function resolvePlaceholder(name, generatedAt) {
@@ -13097,7 +13149,8 @@ function nativeVisualDeps(ctx, path3) {
13097
13149
  };
13098
13150
  },
13099
13151
  reject: (detail) => ctx.unsupported.push({ name: "visual", path: path3, detail }),
13100
- warn: (message) => warnOnce(ctx, `visual:${message}`, message)
13152
+ warn: (message) => warnOnce(ctx, `visual:${message}`, message),
13153
+ ...noProofWords(ctx.theme) ? { noProofWords: noProofWords(ctx.theme) } : {}
13101
13154
  };
13102
13155
  }
13103
13156
  function visualPlacementPixels(props, canvas, reference) {
@@ -13381,6 +13434,7 @@ function captionBlock(value, scope, componentName) {
13381
13434
  ctx.unsupported.push({ name: componentName, path: path3, detail: syntax });
13382
13435
  return void 0;
13383
13436
  }
13437
+ const words = noProofWords(ctx.theme);
13384
13438
  return {
13385
13439
  kind: "paragraph",
13386
13440
  id: `${scope.id}:caption`,
@@ -13393,9 +13447,13 @@ function captionBlock(value, scope, componentName) {
13393
13447
  children: DECORATED.test(caption) ? parseInline(caption, {
13394
13448
  base: {},
13395
13449
  hyperlinks: true,
13450
+ ...words ? { noProofWords: words } : {},
13396
13451
  resolvePlaceholder: placeholderResolver(ctx),
13397
13452
  resolveCrossReference: crossReferenceResolver(ctx, path3)
13398
- }) : parseLiteral(caption, { base: {} })
13453
+ }) : parseLiteral(caption, {
13454
+ base: {},
13455
+ ...words ? { noProofWords: words } : {}
13456
+ })
13399
13457
  };
13400
13458
  }
13401
13459
  function relativeFrom(value, axis) {
@@ -13880,10 +13938,14 @@ function cellChildren(cell, baseStyle, scope, rowRevision) {
13880
13938
  }
13881
13939
  const text = cellText(cell);
13882
13940
  if (text === void 0) return wrap([]);
13941
+ const words = mergeNoProofWords(
13942
+ noProofWords(ctx.theme),
13943
+ typeof content === "object" ? content.props?.noProofWords : void 0
13944
+ );
13883
13945
  const revision = cell.revision ?? cellComponentRevision(cell);
13884
13946
  if (revision) {
13885
13947
  ctx.features.require("revisions", "");
13886
- return wrap(compileRevision(revision, base, ctx));
13948
+ return wrap(compileRevision(revision, base, ctx, words));
13887
13949
  }
13888
13950
  if (rowRevision) {
13889
13951
  return wrap(
@@ -13894,7 +13956,8 @@ function cellChildren(cell, baseStyle, scope, rowRevision) {
13894
13956
  segments: [{ type: rowRevision.type, text }]
13895
13957
  },
13896
13958
  base,
13897
- ctx
13959
+ ctx,
13960
+ words
13898
13961
  )
13899
13962
  );
13900
13963
  }
@@ -13902,6 +13965,7 @@ function cellChildren(cell, baseStyle, scope, rowRevision) {
13902
13965
  parseInline(text, {
13903
13966
  base,
13904
13967
  hyperlinks: true,
13968
+ ...words ? { noProofWords: words } : {},
13905
13969
  resolvePlaceholder: placeholderResolver(ctx),
13906
13970
  resolveCrossReference: crossReferenceResolver(ctx, "table")
13907
13971
  })