@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.
package/dist/index.js CHANGED
@@ -5119,6 +5119,12 @@ function buildDocument(ir, resources = /* @__PURE__ */ new Map(), pageNumbers, e
5119
5119
  updateFields: ir.settings.updateFields,
5120
5120
  ...ir.settings.trackRevisions ? { trackRevisions: true } : {}
5121
5121
  },
5122
+ // A text's `\n` is a manual line break inside its paragraph, and Word
5123
+ // stretches the line before one across the whole width of a justified
5124
+ // paragraph — a two-word line spread edge to edge. `w:doNotExpandShiftReturn`
5125
+ // sets that line flush like the last line of a paragraph, the way every
5126
+ // reader expects. docx keeps compatibility mode 15 alongside it.
5127
+ compatibility: { doNotExpandShiftReturn: true },
5122
5128
  ...ir.numbering.length > 0 ? { numbering: { config: ir.numbering.map(numberingConfig) } } : {},
5123
5129
  // word/footnotes.xml and word/endnotes.xml, keyed by the id their
5124
5130
  // references carry.
@@ -6840,7 +6846,19 @@ async function buildDocumentOptions(ir, charts2 = [], svgRasterFallback) {
6840
6846
  ...coreProperties2(ir),
6841
6847
  settings: {
6842
6848
  updateFields: ir.settings.updateFields,
6843
- ...ir.settings.trackRevisions ? { trackRevisions: true } : {}
6849
+ ...ir.settings.trackRevisions ? { trackRevisions: true } : {},
6850
+ // A text's `\n` is a manual line break inside its paragraph, and Word
6851
+ // stretches the line before one across the whole width of a justified
6852
+ // paragraph. `w:doNotExpandShiftReturn` sets that line flush instead.
6853
+ // Passing `compatibility` replaces @office-open/docx's fresh-document
6854
+ // defaults rather than extending them, so they are restated here.
6855
+ compatibility: {
6856
+ version: 15,
6857
+ overrideTableStyleFontSizeAndJustification: true,
6858
+ enableOpenTypeFeatures: true,
6859
+ doNotFlipMirrorIndents: true,
6860
+ doNotExpandShiftReturn: true
6861
+ }
6844
6862
  },
6845
6863
  ...ir.numbering.length > 0 ? {
6846
6864
  numbering: {
@@ -8105,292 +8123,504 @@ import {
8105
8123
 
8106
8124
  // src/ir/nativeVisual.ts
8107
8125
  init_units();
8108
- init_imageUtils();
8109
- var PIXELS_PER_INCH2 = 96;
8110
- var DEFAULT_WIDTH_FRACTION = 0.75;
8111
- var ASSUMED_FONT_SIZE_POINTS = 18;
8112
- var MIN_DERIVED_TEXT_HEIGHT_INCHES = 0.5;
8113
- var DERIVED_LINE_HEIGHT_RATIO = 1.6;
8114
- var GEOMETRY_ALIASES = {
8115
- arrow: "rightArrow",
8116
- lightning: "lightningBolt"
8117
- };
8118
- var DASH_VALUES = /* @__PURE__ */ new Set([
8119
- "solid",
8120
- "dash",
8121
- "dot",
8122
- "dashDot"
8123
- ]);
8124
- var ANCHOR = {
8125
- top: "top",
8126
- middle: "middle",
8127
- bottom: "bottom"
8128
- };
8129
- var ALIGNMENT = {
8130
- left: "left",
8131
- center: "center",
8132
- right: "right",
8133
- justify: "justified"
8134
- };
8135
- function compileNativeVisualGroup(props, outer) {
8136
- const canvas = {
8137
- widthEmu: inchesToEmu(props.canvas.width),
8138
- heightEmu: inchesToEmu(props.canvas.height)
8139
- };
8140
- const children = [];
8141
- let refused = false;
8142
- const deps = {
8143
- ...outer,
8144
- reject: (detail) => {
8145
- refused = true;
8146
- outer.reject(detail);
8126
+
8127
+ // src/utils/unicode.ts
8128
+ function normalizeUnicodeText(text) {
8129
+ return (text ?? "").normalize("NFC");
8130
+ }
8131
+
8132
+ // src/ir/inline.ts
8133
+ function escapeRegExp(value) {
8134
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8135
+ }
8136
+ function buildNoProofWordsRegex(noProofWords2) {
8137
+ const words = (noProofWords2 ?? []).filter((word) => word?.trim().length > 0);
8138
+ if (words.length === 0) return null;
8139
+ const escaped = words.slice().sort((a, b) => b.length - a.length).map(escapeRegExp);
8140
+ return new RegExp(
8141
+ `(?<![\\p{L}\\p{N}])(?:${escaped.join("|")})(?![\\p{L}\\p{N}])`,
8142
+ "giu"
8143
+ );
8144
+ }
8145
+ var DECORATOR = /(\*\*\*|___)([\s\S]*?)\1|(\*\*|__)([\s\S]*?)\3|(\*|_)([\s\S]*?)\5/g;
8146
+ var ESCAPABLE = "*_[]{}\\";
8147
+ var ESCAPE = /\\([*_[\]{}\\])/g;
8148
+ var SENTINEL = "\uE000";
8149
+ var SENTINEL_SELF = "x";
8150
+ var SENTINEL_DIGIT_BASE = 48;
8151
+ function encodeEscapes(text) {
8152
+ const authored = text.includes(SENTINEL);
8153
+ if (!authored && !text.includes("\\")) return text;
8154
+ const guarded = authored ? text.split(SENTINEL).join(SENTINEL + SENTINEL_SELF) : text;
8155
+ return guarded.replace(
8156
+ ESCAPE,
8157
+ (_whole, char) => SENTINEL + String.fromCharCode(SENTINEL_DIGIT_BASE + ESCAPABLE.indexOf(char))
8158
+ );
8159
+ }
8160
+ function decodeEscapes(text) {
8161
+ if (!text.includes(SENTINEL)) return text;
8162
+ let out = "";
8163
+ for (let i = 0; i < text.length; i += 1) {
8164
+ if (text[i] !== SENTINEL) {
8165
+ out += text[i];
8166
+ continue;
8147
8167
  }
8148
- };
8149
- const background = props.canvas.background;
8150
- if (background?.color) {
8151
- const color = deps.color(background.color);
8152
- if (!color) {
8153
- deps.reject(`canvas.background.color "${background.color}"`);
8154
- } else {
8155
- children.push({
8156
- kind: "shape",
8157
- frame: fullBleed(canvas),
8158
- geometry: "rect",
8159
- fill: { kind: "solid", color },
8160
- name: "Canvas background"
8161
- });
8168
+ const marker = text[i + 1];
8169
+ if (marker === SENTINEL_SELF) {
8170
+ out += SENTINEL;
8171
+ i += 1;
8172
+ continue;
8162
8173
  }
8163
- }
8164
- if (background?.image) {
8165
- const source = resolveImageSource(background.image);
8166
- const resource = source ? deps.picture(source) : void 0;
8167
- if (!resource) {
8168
- deps.reject("canvas.background.image could not be loaded");
8169
- } else {
8170
- children.push({
8171
- kind: "picture",
8172
- frame: fullBleed(canvas),
8173
- resourceId: resource.resourceId,
8174
- name: "Canvas background"
8175
- });
8174
+ const index = marker === void 0 ? -1 : marker.charCodeAt(0) - SENTINEL_DIGIT_BASE;
8175
+ if (index >= 0 && index < ESCAPABLE.length) {
8176
+ out += ESCAPABLE[index];
8177
+ i += 1;
8178
+ continue;
8176
8179
  }
8180
+ out += text[i];
8177
8181
  }
8178
- for (const [index, element] of (props.elements ?? []).entries()) {
8179
- if (element.enabled === false) continue;
8180
- const child = compileElement(element, index, canvas, deps);
8181
- if (child) children.push(child);
8182
- }
8183
- return refused ? void 0 : { canvas, children };
8182
+ return out;
8184
8183
  }
8185
- function compileElement(element, index, canvas, deps) {
8186
- const at = `elements[${index}]`;
8187
- switch (element.name) {
8188
- case "text":
8189
- return compileText(element.props, at, canvas, deps);
8190
- case "shape":
8191
- return compileShape(element.props, at, canvas, deps);
8192
- case "image":
8193
- return compileImage(element.props, at, canvas, deps);
8194
- default:
8195
- deps.reject(`${at} is a "${element.name}"`);
8196
- return void 0;
8197
- }
8184
+ var LINK = /\[([^\]]+)\]\(([^)]+)\)/g;
8185
+ var DECORATOR_OR_PLACEHOLDER = /(\*\*\*|___)([\s\S]*?)\1|(\*\*|__)([\s\S]*?)\3|(\*|_)([\s\S]*?)\5|\{([^}]+)\}/g;
8186
+ var PLACEHOLDER = /\{([^}]+)\}/;
8187
+ var UNSUPPORTED_SYNTAX = [];
8188
+ var CROSS_REFERENCE = /\[@([^\]\s:]+)(?::(relative|no_context|full_context|none))?\]/;
8189
+ var INLINE_TOKEN = new RegExp(
8190
+ `${LINK.source}|${CROSS_REFERENCE.source}`,
8191
+ "g"
8192
+ );
8193
+ var NOTE_MARKER = /\[\^([^\]\s]+)\]/g;
8194
+ function containsPlaceholder(text) {
8195
+ return PLACEHOLDER.test(text);
8198
8196
  }
8199
- function compileText(props, at, canvas, deps) {
8200
- const runs = textRuns(props);
8201
- if (!runs) {
8202
- deps.warn(
8203
- `[core-docx] native visual ${at} has neither "text" nor "runs"; nothing was drawn for it.`
8204
- );
8205
- return void 0;
8197
+ function containsCrossReference(text) {
8198
+ return new RegExp(CROSS_REFERENCE.source).test(text);
8199
+ }
8200
+ function containsLink(text) {
8201
+ return new RegExp(LINK.source).test(text);
8202
+ }
8203
+ function containsUnsupportedSyntax(text) {
8204
+ for (const { pattern, what } of UNSUPPORTED_SYNTAX) {
8205
+ if (pattern.test(text)) return what;
8206
8206
  }
8207
- const colors = resolveColors(
8208
- [
8209
- ["color", props.color],
8210
- ["fill.color", props.fill?.color],
8211
- ...runs.map((run, i) => [`runs[${i}].color`, run.color]),
8212
- ...runs.map(
8213
- (run, i) => [
8214
- `runs[${i}].underline.color`,
8215
- underlineColor(run.underline)
8216
- ]
8217
- ),
8218
- ["underline.color", underlineColor(props.underline)]
8219
- ],
8220
- at,
8221
- deps
8222
- );
8223
- if (!colors) return void 0;
8224
- const frame = resolveFrame(props, canvas, {
8225
- defaultHeightInches: derivedTextHeightInches(props, runs)
8226
- });
8227
- const paragraphs = textParagraphs(runs, props, colors, at);
8228
- return {
8229
- kind: "shape",
8230
- frame,
8231
- geometry: "rect",
8232
- isTextBox: true,
8233
- // A text box with no fill is transparent, which is what the rasterized
8234
- // path draws; saying so explicitly stops Word applying its own default.
8235
- fill: props.fill?.color ? solidFill(colors.get("fill.color"), props.fill.transparency) : { kind: "none" },
8236
- text: {
8237
- paragraphs,
8238
- // pptx anchors a text box at the top unless told otherwise, and a
8239
- // drawing that changes anchor between render modes moves its text.
8240
- anchor: ANCHOR[props.valign ?? "top"] ?? "top",
8241
- // A text box's insets default to nothing, again matching pptx. A shape
8242
- // deliberately leaves them unstated so OOXML's own defaults apply.
8243
- insetsEmu: marginInsets(props.margin) ?? ZERO_INSETS
8244
- },
8245
- name: `Text ${at}`
8246
- };
8207
+ return void 0;
8247
8208
  }
8248
- var ZERO_INSETS = { top: 0, bottom: 0, left: 0, right: 0 };
8249
- function textRuns(props) {
8250
- if (props.runs?.length) return props.runs;
8251
- if (props.text === void 0) return void 0;
8252
- return [{ text: props.text }];
8209
+ function parseInline(text, options) {
8210
+ const normalized = encodeEscapes(normalizeUnicodeText(text));
8211
+ if (!normalized) {
8212
+ return [
8213
+ { kind: "text", text: "", formatting: emptyToUndefined(options.base) }
8214
+ ];
8215
+ }
8216
+ if (containsPlaceholder(normalized)) {
8217
+ return parsePlaceholders(normalized, options);
8218
+ }
8219
+ if (options.hyperlinks) return parseLinks(normalized, options);
8220
+ return parseDecorated(normalized, options);
8253
8221
  }
8254
- function textParagraphs(runs, props, colors, at) {
8255
- const paragraphs = [];
8256
- let current = [];
8257
- const flush = () => {
8258
- paragraphs.push({
8259
- kind: "paragraph",
8260
- id: `${at}.p${paragraphs.length}`,
8261
- path: `${at}.paragraphs[${paragraphs.length}]`,
8262
- children: current,
8263
- formatting: {
8264
- ...props.align ? { alignment: ALIGNMENT[props.align] } : {},
8265
- // A drawing's text is set flush against the shape; the document's
8266
- // paragraph spacing would push it away from the top edge, which is not
8267
- // what an absolutely-placed box means.
8268
- spacing: { beforeTwips: 0, afterTwips: 0 }
8269
- }
8270
- });
8271
- current = [];
8222
+ function parsePlaceholders(normalized, parseOptions) {
8223
+ const options = {
8224
+ ...parseOptions,
8225
+ boldColor: void 0,
8226
+ resolveNote: void 0
8272
8227
  };
8273
- runs.forEach((run, index) => {
8274
- const formatting = runFormatting(run, props, colors, index);
8275
- const lines = run.text.split("\n");
8276
- lines.forEach((line, lineIndex) => {
8277
- if (lineIndex > 0) current.push({ kind: "lineBreak" });
8278
- if (line) {
8279
- current.push({ kind: "text", text: line, ...formatting });
8280
- }
8281
- });
8282
- if (run.breakLine && index < runs.length - 1) flush();
8283
- });
8284
- flush();
8285
- return paragraphs;
8228
+ const out = [];
8229
+ const token = new RegExp(DECORATOR_OR_PLACEHOLDER.source, "g");
8230
+ let lastIndex = 0;
8231
+ let match;
8232
+ const literal = (text, base = options.base) => {
8233
+ if (!text) return;
8234
+ out.push(...parseLiteralLines(text, { ...options, base }));
8235
+ };
8236
+ while ((match = token.exec(normalized)) !== null) {
8237
+ if (match.index > lastIndex) {
8238
+ literal(normalized.slice(lastIndex, match.index));
8239
+ }
8240
+ lastIndex = match.index + match[0].length;
8241
+ if (match[7]) {
8242
+ const resolved = options.resolvePlaceholder?.(match[7]);
8243
+ if (!resolved) {
8244
+ literal(match[0]);
8245
+ } else if (resolved.kind === "text") {
8246
+ literal(resolved.text);
8247
+ } else {
8248
+ const formatting = emptyToUndefined({
8249
+ fontFamily: options.base.fontFamily,
8250
+ sizeHalfPoints: options.base.sizeHalfPoints,
8251
+ color: options.base.color,
8252
+ bold: options.base.bold,
8253
+ italic: options.base.italic,
8254
+ underline: options.base.underline
8255
+ });
8256
+ out.push({
8257
+ kind: "field",
8258
+ instruction: resolved.instruction,
8259
+ ...resolved.cachedText !== void 0 && {
8260
+ cachedText: resolved.cachedText
8261
+ },
8262
+ ...formatting ? { formatting } : {}
8263
+ });
8264
+ }
8265
+ continue;
8266
+ }
8267
+ const style = decoratedStyle(match, options.base);
8268
+ out.push(
8269
+ ...parsePlaceholders(decoratedText(match), {
8270
+ ...options,
8271
+ base: { ...options.base, ...style },
8272
+ // No-proof words do not survive into a decorated span here: the
8273
+ // recursion has always been entered without them.
8274
+ noProofWords: void 0
8275
+ })
8276
+ );
8277
+ }
8278
+ if (lastIndex < normalized.length) literal(normalized.slice(lastIndex));
8279
+ if (out.length === 0 && normalized) literal(normalized);
8280
+ return out;
8286
8281
  }
8287
- function runFormatting(run, props, colors, index) {
8288
- const fontFamily = run.fontFace ?? props.fontFace;
8289
- const sizePoints = run.fontSize ?? props.fontSize;
8290
- const color = colors.get(`runs[${index}].color`) ?? (run.color === void 0 ? colors.get("color") : void 0);
8291
- const bold = run.bold ?? props.bold;
8292
- const italic = run.italic ?? props.italic;
8293
- const strike = run.strike ?? props.strike;
8294
- const underline = compileUnderline(
8295
- run.underline ?? props.underline,
8296
- colors.get(
8297
- run.underline !== void 0 ? `runs[${index}].underline.color` : "underline.color"
8298
- )
8282
+ function parseLiteralLines(text, options) {
8283
+ const out = [];
8284
+ pushSegment(out, text, options);
8285
+ return out;
8286
+ }
8287
+ function parseLiteral(text, options) {
8288
+ const out = [];
8289
+ pushWords(
8290
+ out,
8291
+ normalizeUnicodeText(text),
8292
+ emptyToUndefined(options.base),
8293
+ options
8299
8294
  );
8300
- const formatting = {
8301
- ...fontFamily ? { fontFamily } : {},
8302
- ...sizePoints !== void 0 ? { sizeHalfPoints: pointsToHalfPoints(sizePoints) } : {},
8303
- ...color ? { color } : {},
8304
- ...bold !== void 0 ? { bold } : {},
8305
- ...italic !== void 0 ? { italic } : {},
8306
- ...strike !== void 0 ? { strike } : {},
8307
- ...underline ? { underline } : {}
8308
- };
8309
- return Object.keys(formatting).length > 0 ? { formatting } : {};
8295
+ return out;
8310
8296
  }
8311
- var UNDERLINE_TYPES = {
8312
- sng: "single",
8313
- dbl: "double",
8314
- dash: "dash",
8315
- dotted: "dotted"
8316
- };
8317
- function compileUnderline(value, color) {
8318
- if (value === void 0 || value === false) return void 0;
8319
- const style = value === true ? "sng" : value.style ?? "sng";
8320
- return {
8321
- type: UNDERLINE_TYPES[style] ?? "single",
8322
- ...color ? { color } : {}
8323
- };
8297
+ function parseLinks(normalized, options) {
8298
+ const out = [];
8299
+ const token = new RegExp(INLINE_TOKEN.source, "g");
8300
+ let lastIndex = 0;
8301
+ let match;
8302
+ while ((match = token.exec(normalized)) !== null) {
8303
+ if (match.index > lastIndex) {
8304
+ out.push(
8305
+ ...parseDecorated(normalized.slice(lastIndex, match.index), options)
8306
+ );
8307
+ }
8308
+ lastIndex = match.index + match[0].length;
8309
+ if (match[1] === void 0) {
8310
+ const resolved = options.resolveCrossReference?.(
8311
+ decodeEscapes(match[3]),
8312
+ match[4] ?? "relative",
8313
+ match[0]
8314
+ );
8315
+ if (resolved) out.push(resolved);
8316
+ else out.push(...parseDecorated(match[0], options));
8317
+ continue;
8318
+ }
8319
+ const [, linkText] = match;
8320
+ const target2 = decodeEscapes(match[2]);
8321
+ out.push({
8322
+ kind: "hyperlink",
8323
+ // A `#anchor` target names a bookmark in this document and needs no
8324
+ // relationship; anything else leaves the document.
8325
+ target: target2.startsWith("#") ? { kind: "bookmark", anchor: target2.slice(1) } : { kind: "external", url: target2 },
8326
+ children: parseDecorated(linkText, options)
8327
+ });
8328
+ }
8329
+ if (lastIndex < normalized.length) {
8330
+ out.push(...parseDecorated(normalized.slice(lastIndex), options));
8331
+ }
8332
+ if (out.length === 0) return parseDecorated(normalized, options);
8333
+ return out;
8324
8334
  }
8325
- function underlineColor(value) {
8326
- return typeof value === "object" ? value.color : void 0;
8335
+ function parseDecorated(normalized, options) {
8336
+ const out = [];
8337
+ let lastIndex = 0;
8338
+ let match;
8339
+ const decorator = new RegExp(DECORATOR.source, "g");
8340
+ while ((match = decorator.exec(normalized)) !== null) {
8341
+ if (match.index > lastIndex) {
8342
+ pushSegment(out, normalized.slice(lastIndex, match.index), options);
8343
+ }
8344
+ pushSegment(
8345
+ out,
8346
+ decoratedText(match),
8347
+ options,
8348
+ decoratedStyle(match, options.base)
8349
+ );
8350
+ lastIndex = match.index + match[0].length;
8351
+ }
8352
+ if (lastIndex < normalized.length) {
8353
+ pushSegment(out, normalized.slice(lastIndex), options);
8354
+ }
8355
+ if (out.length === 0) pushSegment(out, normalized, options);
8356
+ return out;
8327
8357
  }
8328
- function derivedTextHeightInches(props, runs) {
8329
- const fontSize = props.fontSize ?? ASSUMED_FONT_SIZE_POINTS;
8330
- const lineCount = runs.reduce(
8331
- (total, run) => total + (run.breakLine ? 1 : 0) + (run.text.match(/\n/g)?.length ?? 0),
8332
- 1
8333
- );
8334
- return Math.max(
8335
- MIN_DERIVED_TEXT_HEIGHT_INCHES,
8336
- fontSize / 72 * DERIVED_LINE_HEIGHT_RATIO * lineCount
8337
- );
8358
+ function decoratedText(match) {
8359
+ if (match[1]) return match[2];
8360
+ if (match[3]) return match[4];
8361
+ if (match[5]) return match[6];
8362
+ return match[0];
8338
8363
  }
8339
- function compileShape(props, at, canvas, deps) {
8340
- const segments = shapeSegments(props);
8341
- const colors = resolveColors(
8342
- [
8343
- ["fill.color", props.fill?.color],
8344
- ["line.color", props.line?.color],
8345
- ["fontColor", props.fontColor],
8346
- ...segments.map(
8347
- (segment, i) => [`text[${i}].color`, segment.color]
8348
- )
8349
- ],
8350
- at,
8351
- deps
8352
- );
8353
- if (!colors) return void 0;
8354
- const frame = resolveFrame(props, canvas, { defaultHeightInches: 0 });
8355
- if (props.flipH) frame.flipHorizontal = true;
8356
- if (props.flipV) frame.flipVertical = true;
8357
- const dash = props.line?.dashType;
8358
- if (dash !== void 0 && !DASH_VALUES.has(dash)) {
8359
- deps.reject(`${at}.line.dashType "${dash}"`);
8360
- return void 0;
8364
+ function decoratedStyle(match, base) {
8365
+ const bold = base.bold ?? false;
8366
+ const italic = base.italic ?? false;
8367
+ if (match[1]) return { bold: true, italic: true };
8368
+ if (match[3]) return { bold: true, italic };
8369
+ if (match[5]) return { bold, italic: true };
8370
+ return { bold, italic };
8371
+ }
8372
+ function pushSegment(out, text, options, override = {}) {
8373
+ const bold = override.bold ?? options.base.bold;
8374
+ const italic = override.italic ?? options.base.italic;
8375
+ const formatting = runFormatting(options, { bold, italic });
8376
+ const lines = decodeEscapes(text).split("\n");
8377
+ for (const [lineIndex, line] of lines.entries()) {
8378
+ if (!line && lineIndex === 0) continue;
8379
+ if (lineIndex > 0) out.push({ kind: "lineBreak" });
8380
+ if (!line) {
8381
+ out.push({ kind: "text", text: "", formatting });
8382
+ continue;
8383
+ }
8384
+ const tabSegments = line.split(" ");
8385
+ for (const [tabIndex, segment] of tabSegments.entries()) {
8386
+ if (tabIndex > 0) out.push({ kind: "tab", formatting });
8387
+ if (!segment && tabSegments.length > 1) continue;
8388
+ pushWords(out, segment, formatting, options);
8389
+ }
8361
8390
  }
8362
- const outline = props.line ? {
8363
- ...colors.get("line.color") ? { color: colors.get("line.color") } : {},
8364
- ...props.line.width !== void 0 ? { widthEmu: pointsToEmu(props.line.width) } : {},
8365
- ...dash && dash !== "solid" ? { dash } : {}
8366
- } : void 0;
8367
- const text = segments.length ? {
8368
- paragraphs: shapeParagraphs(segments, props, colors, at),
8369
- anchor: ANCHOR[props.valign ?? "middle"] ?? "middle",
8370
- // Unstated insets are left unstated so OOXML's own shape defaults
8371
- // apply, which is what the raster path draws.
8372
- ...marginInsets(props.margin) ? { insetsEmu: marginInsets(props.margin) } : {}
8373
- } : void 0;
8374
- return {
8375
- kind: "shape",
8376
- frame,
8377
- geometry: GEOMETRY_ALIASES[props.type] ?? props.type,
8378
- ...props.fill ? {
8379
- fill: props.fill.color ? solidFill(colors.get("fill.color"), props.fill.transparency) : { kind: "none" }
8380
- } : {},
8381
- ...outline && Object.keys(outline).length > 0 ? { outline } : {},
8382
- ...text ? { text } : {},
8383
- name: `Shape ${at}`
8384
- };
8385
8391
  }
8386
- function shapeSegments(props) {
8387
- if (props.text === void 0) return [];
8388
- if (typeof props.text === "string") {
8389
- return props.text ? [{ text: props.text }] : [];
8392
+ function pushWords(out, text, formatting, options) {
8393
+ if (options.resolveNote) {
8394
+ splitNoteMarkers(out, text, formatting, options);
8395
+ return;
8390
8396
  }
8391
- return props.text;
8397
+ pushNoProofWords(out, text, formatting, options);
8392
8398
  }
8393
- function shapeParagraphs(segments, props, colors, at) {
8399
+ function splitNoteMarkers(out, text, formatting, options) {
8400
+ const regex = new RegExp(NOTE_MARKER.source, "g");
8401
+ let lastIndex = 0;
8402
+ let pending = "";
8403
+ let match;
8404
+ while ((match = regex.exec(text)) !== null) {
8405
+ const note = options.resolveNote(match[1]);
8406
+ if (note === void 0) {
8407
+ pending += text.slice(lastIndex, regex.lastIndex);
8408
+ lastIndex = regex.lastIndex;
8409
+ continue;
8410
+ }
8411
+ const before = pending + text.slice(lastIndex, match.index);
8412
+ pending = "";
8413
+ if (before) pushNoProofWords(out, before, formatting, options);
8414
+ out.push({
8415
+ kind: "noteReference",
8416
+ noteKind: note.noteKind,
8417
+ id: note.id
8418
+ });
8419
+ lastIndex = regex.lastIndex;
8420
+ }
8421
+ const rest = pending + text.slice(lastIndex);
8422
+ if (rest) pushNoProofWords(out, rest, formatting, options);
8423
+ }
8424
+ function noProofRuns(text, formatting, noProofWords2) {
8425
+ const out = [];
8426
+ pushNoProofWords(out, text, formatting, { base: {}, noProofWords: noProofWords2 });
8427
+ return out.map(
8428
+ (run) => run.formatting ? run : { kind: "text", text: run.text }
8429
+ );
8430
+ }
8431
+ function pushNoProofWords(out, text, formatting, options) {
8432
+ const wholeRunNoProof = options.base.noProof === true;
8433
+ const regex = wholeRunNoProof ? null : buildNoProofWordsRegex(options.noProofWords);
8434
+ if (!regex) {
8435
+ out.push({ kind: "text", text, formatting });
8436
+ return;
8437
+ }
8438
+ let lastIndex = 0;
8439
+ let match;
8440
+ while ((match = regex.exec(text)) !== null) {
8441
+ if (match.index > lastIndex) {
8442
+ out.push({
8443
+ kind: "text",
8444
+ text: text.slice(lastIndex, match.index),
8445
+ formatting
8446
+ });
8447
+ }
8448
+ out.push({
8449
+ kind: "text",
8450
+ text: match[0],
8451
+ formatting: { ...formatting, noProof: true }
8452
+ });
8453
+ lastIndex = match.index + match[0].length;
8454
+ if (regex.lastIndex === match.index) regex.lastIndex += 1;
8455
+ }
8456
+ if (lastIndex < text.length) {
8457
+ out.push({ kind: "text", text: text.slice(lastIndex), formatting });
8458
+ }
8459
+ }
8460
+ function runFormatting(options, state) {
8461
+ const formatting = { ...options.base };
8462
+ if (state.bold !== void 0) formatting.bold = state.bold;
8463
+ if (state.italic !== void 0) formatting.italic = state.italic;
8464
+ if (state.bold && options.boldColor && options.base.color) {
8465
+ formatting.color = options.boldColor;
8466
+ }
8467
+ return emptyToUndefined(formatting);
8468
+ }
8469
+ function emptyToUndefined(formatting) {
8470
+ const entries = Object.entries(formatting).filter(
8471
+ ([, value]) => value !== void 0
8472
+ );
8473
+ return entries.length === 0 ? void 0 : Object.fromEntries(entries);
8474
+ }
8475
+
8476
+ // src/ir/nativeVisual.ts
8477
+ init_imageUtils();
8478
+ var PIXELS_PER_INCH2 = 96;
8479
+ var DEFAULT_WIDTH_FRACTION = 0.75;
8480
+ var ASSUMED_FONT_SIZE_POINTS = 18;
8481
+ var MIN_DERIVED_TEXT_HEIGHT_INCHES = 0.5;
8482
+ var DERIVED_LINE_HEIGHT_RATIO = 1.6;
8483
+ var GEOMETRY_ALIASES = {
8484
+ arrow: "rightArrow",
8485
+ lightning: "lightningBolt"
8486
+ };
8487
+ var DASH_VALUES = /* @__PURE__ */ new Set([
8488
+ "solid",
8489
+ "dash",
8490
+ "dot",
8491
+ "dashDot"
8492
+ ]);
8493
+ var ANCHOR = {
8494
+ top: "top",
8495
+ middle: "middle",
8496
+ bottom: "bottom"
8497
+ };
8498
+ var ALIGNMENT = {
8499
+ left: "left",
8500
+ center: "center",
8501
+ right: "right",
8502
+ justify: "justified"
8503
+ };
8504
+ function compileNativeVisualGroup(props, outer) {
8505
+ const canvas = {
8506
+ widthEmu: inchesToEmu(props.canvas.width),
8507
+ heightEmu: inchesToEmu(props.canvas.height)
8508
+ };
8509
+ const children = [];
8510
+ let refused = false;
8511
+ const deps = {
8512
+ ...outer,
8513
+ reject: (detail) => {
8514
+ refused = true;
8515
+ outer.reject(detail);
8516
+ }
8517
+ };
8518
+ const background = props.canvas.background;
8519
+ if (background?.color) {
8520
+ const color = deps.color(background.color);
8521
+ if (!color) {
8522
+ deps.reject(`canvas.background.color "${background.color}"`);
8523
+ } else {
8524
+ children.push({
8525
+ kind: "shape",
8526
+ frame: fullBleed(canvas),
8527
+ geometry: "rect",
8528
+ fill: { kind: "solid", color },
8529
+ name: "Canvas background"
8530
+ });
8531
+ }
8532
+ }
8533
+ if (background?.image) {
8534
+ const source = resolveImageSource(background.image);
8535
+ const resource = source ? deps.picture(source) : void 0;
8536
+ if (!resource) {
8537
+ deps.reject("canvas.background.image could not be loaded");
8538
+ } else {
8539
+ children.push({
8540
+ kind: "picture",
8541
+ frame: fullBleed(canvas),
8542
+ resourceId: resource.resourceId,
8543
+ name: "Canvas background"
8544
+ });
8545
+ }
8546
+ }
8547
+ for (const [index, element] of (props.elements ?? []).entries()) {
8548
+ if (element.enabled === false) continue;
8549
+ const child = compileElement(element, index, canvas, deps);
8550
+ if (child) children.push(child);
8551
+ }
8552
+ return refused ? void 0 : { canvas, children };
8553
+ }
8554
+ function compileElement(element, index, canvas, deps) {
8555
+ const at = `elements[${index}]`;
8556
+ switch (element.name) {
8557
+ case "text":
8558
+ return compileText(element.props, at, canvas, deps);
8559
+ case "shape":
8560
+ return compileShape(element.props, at, canvas, deps);
8561
+ case "image":
8562
+ return compileImage(element.props, at, canvas, deps);
8563
+ default:
8564
+ deps.reject(`${at} is a "${element.name}"`);
8565
+ return void 0;
8566
+ }
8567
+ }
8568
+ function compileText(props, at, canvas, deps) {
8569
+ const runs = textRuns(props);
8570
+ if (!runs) {
8571
+ deps.warn(
8572
+ `[core-docx] native visual ${at} has neither "text" nor "runs"; nothing was drawn for it.`
8573
+ );
8574
+ return void 0;
8575
+ }
8576
+ const colors = resolveColors(
8577
+ [
8578
+ ["color", props.color],
8579
+ ["fill.color", props.fill?.color],
8580
+ ...runs.map((run, i) => [`runs[${i}].color`, run.color]),
8581
+ ...runs.map(
8582
+ (run, i) => [
8583
+ `runs[${i}].underline.color`,
8584
+ underlineColor(run.underline)
8585
+ ]
8586
+ ),
8587
+ ["underline.color", underlineColor(props.underline)]
8588
+ ],
8589
+ at,
8590
+ deps
8591
+ );
8592
+ if (!colors) return void 0;
8593
+ const frame = resolveFrame(props, canvas, {
8594
+ defaultHeightInches: derivedTextHeightInches(props, runs)
8595
+ });
8596
+ const paragraphs = textParagraphs(runs, props, colors, at, deps.noProofWords);
8597
+ return {
8598
+ kind: "shape",
8599
+ frame,
8600
+ geometry: "rect",
8601
+ isTextBox: true,
8602
+ // A text box with no fill is transparent, which is what the rasterized
8603
+ // path draws; saying so explicitly stops Word applying its own default.
8604
+ fill: props.fill?.color ? solidFill(colors.get("fill.color"), props.fill.transparency) : { kind: "none" },
8605
+ text: {
8606
+ paragraphs,
8607
+ // pptx anchors a text box at the top unless told otherwise, and a
8608
+ // drawing that changes anchor between render modes moves its text.
8609
+ anchor: ANCHOR[props.valign ?? "top"] ?? "top",
8610
+ // A text box's insets default to nothing, again matching pptx. A shape
8611
+ // deliberately leaves them unstated so OOXML's own defaults apply.
8612
+ insetsEmu: marginInsets(props.margin) ?? ZERO_INSETS
8613
+ },
8614
+ name: `Text ${at}`
8615
+ };
8616
+ }
8617
+ var ZERO_INSETS = { top: 0, bottom: 0, left: 0, right: 0 };
8618
+ function textRuns(props) {
8619
+ if (props.runs?.length) return props.runs;
8620
+ if (props.text === void 0) return void 0;
8621
+ return [{ text: props.text }];
8622
+ }
8623
+ function textParagraphs(runs, props, colors, at, words) {
8394
8624
  const paragraphs = [];
8395
8625
  let current = [];
8396
8626
  const flush = () => {
@@ -8400,49 +8630,195 @@ function shapeParagraphs(segments, props, colors, at) {
8400
8630
  path: `${at}.paragraphs[${paragraphs.length}]`,
8401
8631
  children: current,
8402
8632
  formatting: {
8403
- // A shape centres its text unless told otherwise, which is what the
8404
- // rasterized shape draws.
8405
- alignment: props.align ? ALIGNMENT[props.align] : "center",
8633
+ ...props.align ? { alignment: ALIGNMENT[props.align] } : {},
8634
+ // A drawing's text is set flush against the shape; the document's
8635
+ // paragraph spacing would push it away from the top edge, which is not
8636
+ // what an absolutely-placed box means.
8406
8637
  spacing: { beforeTwips: 0, afterTwips: 0 }
8407
8638
  }
8408
8639
  });
8409
8640
  current = [];
8410
8641
  };
8411
- segments.forEach((segment, index) => {
8412
- const fontFamily = segment.fontFace ?? props.fontFace;
8413
- const sizePoints = segment.fontSize ?? props.fontSize;
8414
- const color = colors.get(`text[${index}].color`) ?? (segment.color === void 0 ? colors.get("fontColor") : void 0);
8415
- const bold = segment.bold ?? props.bold;
8416
- const italic = segment.italic ?? props.italic;
8417
- const formatting = {
8418
- ...fontFamily ? { fontFamily } : {},
8419
- ...sizePoints !== void 0 ? { sizeHalfPoints: pointsToHalfPoints(sizePoints) } : {},
8420
- ...color ? { color } : {},
8421
- ...bold !== void 0 ? { bold } : {},
8422
- ...italic !== void 0 ? { italic } : {}
8423
- };
8424
- const wrapped = Object.keys(formatting).length > 0 ? { formatting } : void 0;
8425
- segment.text.split("\n").forEach((line, lineIndex) => {
8642
+ runs.forEach((run, index) => {
8643
+ const formatting = runFormatting2(run, props, colors, index);
8644
+ const lines = run.text.split("\n");
8645
+ lines.forEach((line, lineIndex) => {
8426
8646
  if (lineIndex > 0) current.push({ kind: "lineBreak" });
8427
- if (line) current.push({ kind: "text", text: line, ...wrapped });
8647
+ if (line) current.push(...textLine(line, formatting.formatting, words));
8428
8648
  });
8429
- if (segment.breakLine && index < segments.length - 1) flush();
8649
+ if (run.breakLine && index < runs.length - 1) flush();
8430
8650
  });
8431
8651
  flush();
8432
8652
  return paragraphs;
8433
8653
  }
8434
- function compileImage(props, at, canvas, deps) {
8435
- const source = resolveImageSource(props);
8436
- if (!source) {
8437
- deps.reject(`${at} has none of "path", "base64" or "svg"`);
8438
- return void 0;
8439
- }
8440
- const resource = deps.picture(source);
8441
- if (!resource) {
8442
- deps.reject(`${at} image could not be loaded`);
8443
- return void 0;
8444
- }
8445
- const aspect = resource.intrinsic ? resource.intrinsic.width / resource.intrinsic.height : void 0;
8654
+ function runFormatting2(run, props, colors, index) {
8655
+ const fontFamily = run.fontFace ?? props.fontFace;
8656
+ const sizePoints = run.fontSize ?? props.fontSize;
8657
+ const color = colors.get(`runs[${index}].color`) ?? (run.color === void 0 ? colors.get("color") : void 0);
8658
+ const bold = run.bold ?? props.bold;
8659
+ const italic = run.italic ?? props.italic;
8660
+ const strike = run.strike ?? props.strike;
8661
+ const underline = compileUnderline(
8662
+ run.underline ?? props.underline,
8663
+ colors.get(
8664
+ run.underline !== void 0 ? `runs[${index}].underline.color` : "underline.color"
8665
+ )
8666
+ );
8667
+ const formatting = {
8668
+ ...fontFamily ? { fontFamily } : {},
8669
+ ...sizePoints !== void 0 ? { sizeHalfPoints: pointsToHalfPoints(sizePoints) } : {},
8670
+ ...color ? { color } : {},
8671
+ ...bold !== void 0 ? { bold } : {},
8672
+ ...italic !== void 0 ? { italic } : {},
8673
+ ...strike !== void 0 ? { strike } : {},
8674
+ ...underline ? { underline } : {}
8675
+ };
8676
+ return Object.keys(formatting).length > 0 ? { formatting } : {};
8677
+ }
8678
+ var UNDERLINE_TYPES = {
8679
+ sng: "single",
8680
+ dbl: "double",
8681
+ dash: "dash",
8682
+ dotted: "dotted"
8683
+ };
8684
+ function compileUnderline(value, color) {
8685
+ if (value === void 0 || value === false) return void 0;
8686
+ const style = value === true ? "sng" : value.style ?? "sng";
8687
+ return {
8688
+ type: UNDERLINE_TYPES[style] ?? "single",
8689
+ ...color ? { color } : {}
8690
+ };
8691
+ }
8692
+ function underlineColor(value) {
8693
+ return typeof value === "object" ? value.color : void 0;
8694
+ }
8695
+ function derivedTextHeightInches(props, runs) {
8696
+ const fontSize = props.fontSize ?? ASSUMED_FONT_SIZE_POINTS;
8697
+ const lineCount = runs.reduce(
8698
+ (total, run) => total + (run.breakLine ? 1 : 0) + (run.text.match(/\n/g)?.length ?? 0),
8699
+ 1
8700
+ );
8701
+ return Math.max(
8702
+ MIN_DERIVED_TEXT_HEIGHT_INCHES,
8703
+ fontSize / 72 * DERIVED_LINE_HEIGHT_RATIO * lineCount
8704
+ );
8705
+ }
8706
+ function compileShape(props, at, canvas, deps) {
8707
+ const segments = shapeSegments(props);
8708
+ const colors = resolveColors(
8709
+ [
8710
+ ["fill.color", props.fill?.color],
8711
+ ["line.color", props.line?.color],
8712
+ ["fontColor", props.fontColor],
8713
+ ...segments.map(
8714
+ (segment, i) => [`text[${i}].color`, segment.color]
8715
+ )
8716
+ ],
8717
+ at,
8718
+ deps
8719
+ );
8720
+ if (!colors) return void 0;
8721
+ const frame = resolveFrame(props, canvas, { defaultHeightInches: 0 });
8722
+ if (props.flipH) frame.flipHorizontal = true;
8723
+ if (props.flipV) frame.flipVertical = true;
8724
+ const dash = props.line?.dashType;
8725
+ if (dash !== void 0 && !DASH_VALUES.has(dash)) {
8726
+ deps.reject(`${at}.line.dashType "${dash}"`);
8727
+ return void 0;
8728
+ }
8729
+ const outline = props.line ? {
8730
+ ...colors.get("line.color") ? { color: colors.get("line.color") } : {},
8731
+ ...props.line.width !== void 0 ? { widthEmu: pointsToEmu(props.line.width) } : {},
8732
+ ...dash && dash !== "solid" ? { dash } : {}
8733
+ } : void 0;
8734
+ const text = segments.length ? {
8735
+ paragraphs: shapeParagraphs(
8736
+ segments,
8737
+ props,
8738
+ colors,
8739
+ at,
8740
+ deps.noProofWords
8741
+ ),
8742
+ anchor: ANCHOR[props.valign ?? "middle"] ?? "middle",
8743
+ // Unstated insets are left unstated so OOXML's own shape defaults
8744
+ // apply, which is what the raster path draws.
8745
+ ...marginInsets(props.margin) ? { insetsEmu: marginInsets(props.margin) } : {}
8746
+ } : void 0;
8747
+ return {
8748
+ kind: "shape",
8749
+ frame,
8750
+ geometry: GEOMETRY_ALIASES[props.type] ?? props.type,
8751
+ ...props.fill ? {
8752
+ fill: props.fill.color ? solidFill(colors.get("fill.color"), props.fill.transparency) : { kind: "none" }
8753
+ } : {},
8754
+ ...outline && Object.keys(outline).length > 0 ? { outline } : {},
8755
+ ...text ? { text } : {},
8756
+ name: `Shape ${at}`
8757
+ };
8758
+ }
8759
+ function shapeSegments(props) {
8760
+ if (props.text === void 0) return [];
8761
+ if (typeof props.text === "string") {
8762
+ return props.text ? [{ text: props.text }] : [];
8763
+ }
8764
+ return props.text;
8765
+ }
8766
+ function shapeParagraphs(segments, props, colors, at, words) {
8767
+ const paragraphs = [];
8768
+ let current = [];
8769
+ const flush = () => {
8770
+ paragraphs.push({
8771
+ kind: "paragraph",
8772
+ id: `${at}.p${paragraphs.length}`,
8773
+ path: `${at}.paragraphs[${paragraphs.length}]`,
8774
+ children: current,
8775
+ formatting: {
8776
+ // A shape centres its text unless told otherwise, which is what the
8777
+ // rasterized shape draws.
8778
+ alignment: props.align ? ALIGNMENT[props.align] : "center",
8779
+ spacing: { beforeTwips: 0, afterTwips: 0 }
8780
+ }
8781
+ });
8782
+ current = [];
8783
+ };
8784
+ segments.forEach((segment, index) => {
8785
+ const fontFamily = segment.fontFace ?? props.fontFace;
8786
+ const sizePoints = segment.fontSize ?? props.fontSize;
8787
+ const color = colors.get(`text[${index}].color`) ?? (segment.color === void 0 ? colors.get("fontColor") : void 0);
8788
+ const bold = segment.bold ?? props.bold;
8789
+ const italic = segment.italic ?? props.italic;
8790
+ const formatting = {
8791
+ ...fontFamily ? { fontFamily } : {},
8792
+ ...sizePoints !== void 0 ? { sizeHalfPoints: pointsToHalfPoints(sizePoints) } : {},
8793
+ ...color ? { color } : {},
8794
+ ...bold !== void 0 ? { bold } : {},
8795
+ ...italic !== void 0 ? { italic } : {}
8796
+ };
8797
+ const wrapped = Object.keys(formatting).length > 0 ? formatting : void 0;
8798
+ segment.text.split("\n").forEach((line, lineIndex) => {
8799
+ if (lineIndex > 0) current.push({ kind: "lineBreak" });
8800
+ if (line) current.push(...textLine(line, wrapped, words));
8801
+ });
8802
+ if (segment.breakLine && index < segments.length - 1) flush();
8803
+ });
8804
+ flush();
8805
+ return paragraphs;
8806
+ }
8807
+ function textLine(line, formatting, words) {
8808
+ return words ? noProofRuns(line, formatting, words) : [{ kind: "text", text: line, ...formatting ? { formatting } : {} }];
8809
+ }
8810
+ function compileImage(props, at, canvas, deps) {
8811
+ const source = resolveImageSource(props);
8812
+ if (!source) {
8813
+ deps.reject(`${at} has none of "path", "base64" or "svg"`);
8814
+ return void 0;
8815
+ }
8816
+ const resource = deps.picture(source);
8817
+ if (!resource) {
8818
+ deps.reject(`${at} image could not be loaded`);
8819
+ return void 0;
8820
+ }
8821
+ const aspect = resource.intrinsic ? resource.intrinsic.width / resource.intrinsic.height : void 0;
8446
8822
  const intrinsicInches = resource.intrinsic ? {
8447
8823
  width: resource.intrinsic.width / PIXELS_PER_INCH2,
8448
8824
  height: resource.intrinsic.height / PIXELS_PER_INCH2
@@ -9973,802 +10349,460 @@ function resolveTableModel(source, theme, themeName, options = {}) {
9973
10349
  });
9974
10350
  adjudicateInteriorEdges([...hasHeader ? [header] : [], ...rows]);
9975
10351
  return {
9976
- ...resolveWidths(
9977
- source,
9978
- columns,
9979
- options.measureTwips ?? getAvailableWidthTwips(theme, themeName)
9980
- ),
9981
- ...hasHeader ? { header } : {},
9982
- rows,
9983
- repeatHeader: source.repeatHeaderOnPageBreak ?? true
9984
- };
9985
- }
9986
- function drawsNothing(border3) {
9987
- return border3.hidden || border3.size <= 0;
9988
- }
9989
- function channels(hex) {
9990
- const digits = hex.startsWith("#") ? hex.slice(1) : hex;
9991
- return [
9992
- parseInt(digits.slice(0, 2), 16) || 0,
9993
- parseInt(digits.slice(2, 4), 16) || 0,
9994
- parseInt(digits.slice(4, 6), 16) || 0
9995
- ];
9996
- }
9997
- function outweighs(a, b) {
9998
- if (drawsNothing(a)) return false;
9999
- if (drawsNothing(b)) return true;
10000
- if (a.size !== b.size) return a.size > b.size;
10001
- const [redA, greenA, blueA] = channels(a.color);
10002
- const [redB, greenB, blueB] = channels(b.color);
10003
- const brightness = [
10004
- [redA + blueA + 2 * greenA, redB + blueB + 2 * greenB],
10005
- [blueA + 2 * greenA, blueB + 2 * greenB],
10006
- [greenA, greenB]
10007
- ];
10008
- for (const [ours, theirs] of brightness) {
10009
- if (ours !== theirs) return ours < theirs;
10010
- }
10011
- return false;
10012
- }
10013
- function adjudicateInteriorEdges(rows) {
10014
- const settle = (earlier, later) => {
10015
- if (drawsNothing(earlier) && drawsNothing(later)) return;
10016
- if (earlier.hidden === later.hidden && earlier.size === later.size && earlier.color === later.color) {
10017
- return;
10018
- }
10019
- const winner = earlier.explicit !== later.explicit ? earlier.explicit ? earlier : later : outweighs(later, earlier) ? later : earlier;
10020
- const loser = winner === earlier ? later : earlier;
10021
- loser.size = winner.size;
10022
- loser.color = winner.color;
10023
- loser.hidden = winner.hidden;
10024
- };
10025
- rows.forEach((row, rowIndex) => {
10026
- row.cells.forEach((cell, colIndex) => {
10027
- const right = row.cells[colIndex + 1];
10028
- if (right) settle(cell.borders.right, right.borders.left);
10029
- const below = rows[rowIndex + 1]?.cells[colIndex];
10030
- if (below) settle(cell.borders.bottom, below.borders.top);
10031
- });
10032
- });
10033
- }
10034
- function resolveWidths(source, columns, measureTwips) {
10035
- const hasExplicitWidths = columns.some((col) => col.width !== void 0);
10036
- if (!hasExplicitWidths) {
10037
- const percent = source.width ?? 100;
10038
- return {
10039
- columnGrid: {
10040
- unit: "twips",
10041
- values: splitTwips(columns.length, measureTwips * percent / 100)
10042
- },
10043
- width: { size: percent, unit: "percent" }
10044
- };
10045
- }
10046
- const stated = columns.map(
10047
- (col) => col.width !== void 0 ? relativeLengthToTwips(col.width, measureTwips) : void 0
10048
- );
10049
- const totalStated = stated.reduce((sum, w) => sum + (w ?? 0), 0);
10050
- const unstatedCount = columns.filter((col) => col.width === void 0).length;
10051
- const remaining = Math.max(0, measureTwips - totalStated);
10052
- const shareOfRemainder = unstatedCount > 0 ? remaining / unstatedCount : pointsToTwips(72);
10053
- const values = stated.map((w) => w || shareOfRemainder);
10054
- return {
10055
- columnGrid: { unit: "twips", values },
10056
- width: {
10057
- size: values.reduce((sum, w) => sum + w, 0),
10058
- unit: "twips"
10059
- },
10060
- ...totalStated > measureTwips ? { overflow: { totalTwips: totalStated, availableTwips: measureTwips } } : {}
10061
- };
10062
- }
10063
-
10064
- // src/ir/compiler.ts
10065
- init_layoutUtils();
10066
-
10067
- // src/utils/revisionUtils.ts
10068
- var DEFAULT_REVISION_AUTHOR = "json-to-office";
10069
- var DEFAULT_REVISION_DATE = "1970-01-01T00:00:00Z";
10070
-
10071
- // src/utils/commentRegistry.ts
10072
- var DEFAULT_COMMENT_AUTHOR = "json-to-office";
10073
- var DEFAULT_COMMENT_DATE = "1970-01-01T00:00:00Z";
10074
-
10075
- // src/ir/compiler.ts
10076
- init_imageUtils();
10077
- init_widthUtils();
10078
-
10079
- // src/utils/numberingConfig.ts
10080
- var HEADING_NUMBERING_REFERENCE = "jto-heading-numbering";
10081
- function headingNumberLabel(number) {
10082
- return `${number}.`;
10083
- }
10084
-
10085
- // src/utils/unicode.ts
10086
- function normalizeUnicodeText(text) {
10087
- return (text ?? "").normalize("NFC");
10088
- }
10089
-
10090
- // src/core/collectTocHeadings.ts
10091
- import { getStandardComponent } from "@json-to-office/shared-docx";
10092
-
10093
- // src/core/sectionBookmarks.ts
10094
- function sectionBookmark(ordinal) {
10095
- return { id: `_Section_${ordinal}`, linkId: ordinal };
10096
- }
10097
-
10098
- // src/utils/numberFormatting.ts
10099
- var ROMAN_NUMERALS = [
10100
- [1e3, "m"],
10101
- [900, "cm"],
10102
- [500, "d"],
10103
- [400, "cd"],
10104
- [100, "c"],
10105
- [90, "xc"],
10106
- [50, "l"],
10107
- [40, "xl"],
10108
- [10, "x"],
10109
- [9, "ix"],
10110
- [5, "v"],
10111
- [4, "iv"],
10112
- [1, "i"]
10113
- ];
10114
- function toRoman(value) {
10115
- let remaining = value;
10116
- let out = "";
10117
- for (const [amount, glyph] of ROMAN_NUMERALS) {
10118
- while (remaining >= amount) {
10119
- out += glyph;
10120
- remaining -= amount;
10121
- }
10122
- }
10123
- return out;
10124
- }
10125
- function toLetters(value) {
10126
- const index = (value - 1) % 26;
10127
- const repeats = Math.floor((value - 1) / 26) + 1;
10128
- return String.fromCharCode(97 + index).repeat(repeats);
10129
- }
10130
- function formatNumberForLevel(value, format2) {
10131
- if (!Number.isFinite(value) || value < 1) return void 0;
10132
- const n = Math.floor(value);
10133
- switch (format2) {
10134
- case "decimal":
10135
- return String(n);
10136
- case "lowerLetter":
10137
- return toLetters(n);
10138
- case "upperLetter":
10139
- return toLetters(n).toUpperCase();
10140
- case "lowerRoman":
10141
- return toRoman(n);
10142
- case "upperRoman":
10143
- return toRoman(n).toUpperCase();
10144
- default:
10145
- return void 0;
10146
- }
10147
- }
10148
-
10149
- // src/core/collectTocHeadings.ts
10150
- var MAX_HEADING_LEVEL = 6;
10151
- var MAX_LIST_LEVEL = 9;
10152
- function normalizeEntryTitle(text) {
10153
- return normalizeUnicodeText(text).replace(/(\*\*\*|___)([\s\S]*?)\1/g, "$2").replace(/(\*\*|__)([\s\S]*?)\1/g, "$2").replace(/(\*|_)([\s\S]*?)\1/g, "$2").trim();
10154
- }
10155
- function isContainer(name) {
10156
- return getStandardComponent(name)?.hasChildren === true;
10157
- }
10158
- function isEnabled(component) {
10159
- return !("enabled" in component && component.enabled === false);
10160
- }
10161
- function styleEntryKey(props) {
10162
- const themeStyle = props.themeStyle;
10163
- if (typeof themeStyle !== "string" || !themeStyle) return void 0;
10164
- if (/^heading[1-6]$/i.test(themeStyle)) return void 0;
10165
- if (["normal", "title", "subtitle"].includes(themeStyle.toLowerCase())) {
10166
- return void 0;
10167
- }
10168
- return themeStyle;
10169
- }
10170
- function levelStart(levels, level) {
10171
- return levels[level]?.start ?? 1;
10172
- }
10173
- function collectDocumentOutline(sections) {
10174
- const entries = [];
10175
- const numberedItems = /* @__PURE__ */ new Map();
10176
- const ordinals = computeSectionOrdinals(sections);
10177
- const takenIds = /* @__PURE__ */ new Set();
10178
- const headingCounters = new Array(MAX_HEADING_LEVEL).fill(0);
10179
- const listCounters = /* @__PURE__ */ new Map();
10180
- const visitHeading = (component, props, sectionBookmarkId) => {
10181
- const text = typeof props.text === "string" ? props.text : "";
10182
- const title = normalizeEntryTitle(text);
10183
- const level = typeof props.level === "number" ? props.level : 1;
10184
- const styleLevel = level >= 1 && level <= MAX_HEADING_LEVEL ? level : 1;
10185
- let full;
10186
- let own;
10187
- if (props.numbering === true) {
10188
- headingCounters[styleLevel - 1] += 1;
10189
- for (let deeper = styleLevel; deeper < MAX_HEADING_LEVEL; deeper++) {
10190
- headingCounters[deeper] = 0;
10191
- }
10192
- full = headingCounters.slice(0, styleLevel).join(".");
10193
- own = String(headingCounters[styleLevel - 1]);
10194
- }
10195
- const explicitId = component.id;
10196
- const bookmarkId = typeof explicitId === "string" && explicitId ? explicitId : dedupeBookmarkId(slugifyBookmarkText(text), (id) => takenIds.has(id));
10197
- takenIds.add(bookmarkId);
10198
- numberedItems.set(bookmarkId, {
10199
- kind: "heading",
10200
- text: title,
10201
- ...full !== void 0 && { full, own }
10202
- });
10203
- if (title) {
10204
- entries.push({
10205
- title,
10206
- level,
10207
- sectionBookmarkId,
10208
- ...full !== void 0 && { number: full }
10209
- });
10210
- }
10211
- };
10212
- const visitList = (props) => {
10213
- const items = Array.isArray(props.items) ? props.items : [];
10214
- if (items.length === 0) return;
10215
- const reference = typeof props.reference === "string" && props.reference ? props.reference : void 0;
10216
- const freshState = () => {
10217
- const levels = resolveListLevels(
10218
- props
10219
- );
10220
- return {
10221
- levels,
10222
- counters: Array.from(
10223
- { length: MAX_LIST_LEVEL },
10224
- (_, level) => levelStart(levels, level) - 1
10225
- )
10226
- };
10227
- };
10228
- let state;
10229
- if (reference === void 0) {
10230
- state = freshState();
10231
- } else {
10232
- state = listCounters.get(reference) ?? freshState();
10233
- listCounters.set(reference, state);
10234
- }
10235
- for (const item of items) {
10236
- const isObject = typeof item === "object" && item !== null;
10237
- const raw = isObject ? item.text : item;
10238
- const text = typeof raw === "string" ? raw : "";
10239
- if (!text.trim() && !(isObject && item.revision)) continue;
10240
- const rawLevel = isObject ? item.level : void 0;
10241
- const level = typeof rawLevel === "number" && rawLevel >= 0 ? rawLevel : 0;
10242
- if (level >= MAX_LIST_LEVEL) continue;
10243
- state.counters[level] += 1;
10244
- for (let deeper = level + 1; deeper < MAX_LIST_LEVEL; deeper++) {
10245
- state.counters[deeper] = levelStart(state.levels, deeper) - 1;
10246
- }
10247
- const id = isObject ? item.id : void 0;
10248
- if (typeof id !== "string" || !id) continue;
10249
- takenIds.add(id);
10250
- const number = formatNumberForLevel(
10251
- state.counters[level],
10252
- state.levels[level]?.format
10253
- );
10254
- numberedItems.set(id, {
10255
- kind: "list-item",
10256
- text: normalizeUnicodeText(text).trim(),
10257
- ...number !== void 0 && { full: number, own: number }
10258
- });
10259
- }
10260
- };
10261
- sections.forEach((section2, index) => {
10262
- const ordinal = ordinals[index]?.ordinal;
10263
- const sectionBookmarkId = ordinal ? sectionBookmark(ordinal).id : void 0;
10264
- const visit = (component) => {
10265
- if (!isEnabled(component)) return;
10266
- const props = component.props ?? {};
10267
- if (component.name === "heading") {
10268
- visitHeading(component, props, sectionBookmarkId);
10269
- return;
10270
- }
10271
- if (component.name === "paragraph") {
10272
- if (typeof props.id === "string" && props.id) takenIds.add(props.id);
10273
- const styleId = styleEntryKey(props);
10274
- const text = typeof props.text === "string" ? props.text : "";
10275
- const title = normalizeEntryTitle(text);
10276
- if (styleId && title) {
10277
- entries.push({ title, level: 1, styleId, sectionBookmarkId });
10278
- }
10279
- return;
10280
- }
10281
- if (component.name === "list") {
10282
- visitList(props);
10283
- return;
10284
- }
10285
- if (!isContainer(component.name)) return;
10286
- const children = component.children;
10287
- if (Array.isArray(children)) children.forEach(visit);
10288
- };
10289
- section2.components.forEach(visit);
10290
- });
10291
- return { entries, numberedItems };
10292
- }
10293
-
10294
- // src/core/tocField.ts
10295
- function toStyleDisplayName(styleId) {
10296
- return styleId.replace(/([A-Z])/g, " $1").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim();
10297
- }
10298
- function parseDepthRange(rawDepth, fieldName, defaultFrom = 1, defaultTo = 3) {
10299
- if (typeof rawDepth !== "object" || rawDepth === null) {
10300
- throw new Error(
10301
- `${fieldName} must be a range object with optional "from" and/or "to" fields, received: ${JSON.stringify(rawDepth)}`
10302
- );
10303
- }
10304
- const { from = defaultFrom, to = defaultTo } = rawDepth;
10305
- if (from < 1 || from > 6 || to < 1 || to > 6) {
10306
- throw new Error(
10307
- `${fieldName} range values must be between 1 and 6, received: from=${from}, to=${to}`
10308
- );
10309
- }
10310
- if (from > to) {
10311
- throw new Error(
10312
- `${fieldName} "from" must be less than or equal to "to", received: from=${from}, to=${to}`
10313
- );
10314
- }
10315
- return { from, to };
10316
- }
10317
- function selectEntries(collected, options) {
10318
- if (!collected || collected.length === 0) return [];
10319
- const { depthStart, depthEnd, bookmarkScope, styleLevels } = options;
10320
- const entries = [];
10321
- for (const entry of collected) {
10322
- if (bookmarkScope && entry.sectionBookmarkId !== bookmarkScope) continue;
10323
- if (entry.styleId !== void 0) {
10324
- const level = styleLevels.get(entry.styleId) ?? styleLevels.get(toStyleDisplayName(entry.styleId));
10325
- if (level === void 0) continue;
10326
- entries.push({ text: entry.title, level });
10327
- continue;
10328
- }
10329
- if (entry.level < depthStart || entry.level > depthEnd) continue;
10330
- entries.push({
10331
- text: entry.number ? `${headingNumberLabel(entry.number)} ${entry.title}` : entry.title,
10332
- level: entry.level
10333
- });
10334
- }
10335
- return entries;
10336
- }
10337
- function resolveTocField(props, theme, context) {
10338
- const warnings = [];
10339
- const headingRange = parseDepthRange(
10340
- props.depth ?? { to: 3 },
10341
- "TOC depth",
10342
- 1,
10343
- 3
10344
- );
10345
- const pageNumbers = props.pageNumbersDepth !== void 0 ? parseDepthRange(props.pageNumbersDepth, "TOC pageNumbersDepth", 1, 3) : void 0;
10346
- if (props.numberingStyle !== void 0) {
10347
- warnings.push(
10348
- `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.`
10349
- );
10350
- }
10351
- const scope = props.scope ?? "auto";
10352
- const effectiveScope = scope === "auto" ? context.sectionBookmarkId ? "section" : "document" : scope;
10353
- const bookmarkScope = effectiveScope === "section" ? context.sectionBookmarkId : void 0;
10354
- if (effectiveScope === "section" && !bookmarkScope) {
10355
- warnings.push(
10356
- "TOC configured for section scope but no section bookmark found. Falling back to document scope."
10357
- );
10358
- }
10359
- const styleLevels = /* @__PURE__ */ new Map();
10360
- const styleSwitches = [];
10361
- for (const mapping of props.styles ?? []) {
10362
- const isCustomStyle = !!theme.styles && Object.prototype.hasOwnProperty.call(theme.styles, mapping.styleId);
10363
- const displayName = isCustomStyle ? toStyleDisplayName(mapping.styleId) : mapping.styleId;
10364
- styleLevels.set(mapping.styleId, mapping.level);
10365
- styleLevels.set(displayName, mapping.level);
10366
- styleSwitches.push({ styleName: displayName, level: mapping.level });
10367
- }
10368
- return {
10369
- headingRange,
10370
- styleLevels: styleSwitches,
10371
- ...bookmarkScope ? { bookmarkScope } : {},
10372
- omitPageNumbersForLevels: omittedLevels(
10373
- headingRange,
10374
- pageNumbers,
10375
- props.includePageNumbers !== false
10376
- ),
10377
- // A boolean chooses between a tab and a space; a tab is the default.
10378
- entrySeparator: props.numberSeparator === false ? " " : " ",
10379
- entries: selectEntries(context.collected, {
10380
- depthStart: headingRange.from,
10381
- depthEnd: headingRange.to,
10382
- bookmarkScope,
10383
- styleLevels
10384
- }),
10385
- warnings
10352
+ ...resolveWidths(
10353
+ source,
10354
+ columns,
10355
+ options.measureTwips ?? getAvailableWidthTwips(theme, themeName)
10356
+ ),
10357
+ ...hasHeader ? { header } : {},
10358
+ rows,
10359
+ repeatHeader: source.repeatHeaderOnPageBreak ?? true
10386
10360
  };
10387
10361
  }
10388
- function omittedLevels(headingRange, pageNumbers, includePageNumbers) {
10389
- if (pageNumbers) {
10390
- const omitted = [];
10391
- if (pageNumbers.from > headingRange.from) {
10392
- omitted.push({ from: headingRange.from, to: pageNumbers.from - 1 });
10393
- }
10394
- if (pageNumbers.to < headingRange.to) {
10395
- omitted.push({ from: pageNumbers.to + 1, to: headingRange.to });
10396
- }
10397
- return omitted;
10362
+ function drawsNothing(border3) {
10363
+ return border3.hidden || border3.size <= 0;
10364
+ }
10365
+ function channels(hex) {
10366
+ const digits = hex.startsWith("#") ? hex.slice(1) : hex;
10367
+ return [
10368
+ parseInt(digits.slice(0, 2), 16) || 0,
10369
+ parseInt(digits.slice(2, 4), 16) || 0,
10370
+ parseInt(digits.slice(4, 6), 16) || 0
10371
+ ];
10372
+ }
10373
+ function outweighs(a, b) {
10374
+ if (drawsNothing(a)) return false;
10375
+ if (drawsNothing(b)) return true;
10376
+ if (a.size !== b.size) return a.size > b.size;
10377
+ const [redA, greenA, blueA] = channels(a.color);
10378
+ const [redB, greenB, blueB] = channels(b.color);
10379
+ const brightness = [
10380
+ [redA + blueA + 2 * greenA, redB + blueB + 2 * greenB],
10381
+ [blueA + 2 * greenA, blueB + 2 * greenB],
10382
+ [greenA, greenB]
10383
+ ];
10384
+ for (const [ours, theirs] of brightness) {
10385
+ if (ours !== theirs) return ours < theirs;
10398
10386
  }
10399
- return includePageNumbers ? [] : [headingRange];
10387
+ return false;
10400
10388
  }
10401
-
10402
- // src/core/markdownList.ts
10403
- function parseMarkdownList(text) {
10404
- const lines = text.split("\n");
10405
- const items = [];
10406
- let isUnordered = null;
10407
- let isOrdered = null;
10408
- for (const line of lines) {
10409
- if (!line.trim()) continue;
10410
- const unorderedMatch = line.match(/^(\s*)([-*])\s+(.+)$/);
10411
- if (unorderedMatch) {
10412
- const indentLevel = Math.floor(unorderedMatch[1].length / 2);
10413
- const text2 = unorderedMatch[3];
10414
- items.push({ text: text2, level: indentLevel });
10415
- if (isUnordered === null) isUnordered = true;
10416
- continue;
10417
- }
10418
- const orderedMatch = line.match(/^(\s*)(\d+)\.\s+(.+)$/);
10419
- if (orderedMatch) {
10420
- const indentLevel = Math.floor(orderedMatch[1].length / 2);
10421
- const text2 = orderedMatch[3];
10422
- items.push({ text: text2, level: indentLevel });
10423
- if (isOrdered === null) isOrdered = true;
10424
- continue;
10389
+ function adjudicateInteriorEdges(rows) {
10390
+ const settle = (earlier, later) => {
10391
+ if (drawsNothing(earlier) && drawsNothing(later)) return;
10392
+ if (earlier.hidden === later.hidden && earlier.size === later.size && earlier.color === later.color) {
10393
+ return;
10425
10394
  }
10426
- return null;
10427
- }
10428
- if (items.length === 0 || isUnordered && isOrdered) {
10429
- return null;
10395
+ const winner = earlier.explicit !== later.explicit ? earlier.explicit ? earlier : later : outweighs(later, earlier) ? later : earlier;
10396
+ const loser = winner === earlier ? later : earlier;
10397
+ loser.size = winner.size;
10398
+ loser.color = winner.color;
10399
+ loser.hidden = winner.hidden;
10400
+ };
10401
+ rows.forEach((row, rowIndex) => {
10402
+ row.cells.forEach((cell, colIndex) => {
10403
+ const right = row.cells[colIndex + 1];
10404
+ if (right) settle(cell.borders.right, right.borders.left);
10405
+ const below = rows[rowIndex + 1]?.cells[colIndex];
10406
+ if (below) settle(cell.borders.bottom, below.borders.top);
10407
+ });
10408
+ });
10409
+ }
10410
+ function resolveWidths(source, columns, measureTwips) {
10411
+ const hasExplicitWidths = columns.some((col) => col.width !== void 0);
10412
+ if (!hasExplicitWidths) {
10413
+ const percent = source.width ?? 100;
10414
+ return {
10415
+ columnGrid: {
10416
+ unit: "twips",
10417
+ values: splitTwips(columns.length, measureTwips * percent / 100)
10418
+ },
10419
+ width: { size: percent, unit: "percent" }
10420
+ };
10430
10421
  }
10422
+ const stated = columns.map(
10423
+ (col) => col.width !== void 0 ? relativeLengthToTwips(col.width, measureTwips) : void 0
10424
+ );
10425
+ const totalStated = stated.reduce((sum, w) => sum + (w ?? 0), 0);
10426
+ const unstatedCount = columns.filter((col) => col.width === void 0).length;
10427
+ const remaining = Math.max(0, measureTwips - totalStated);
10428
+ const shareOfRemainder = unstatedCount > 0 ? remaining / unstatedCount : pointsToTwips(72);
10429
+ const values = stated.map((w) => w || shareOfRemainder);
10431
10430
  return {
10432
- type: isUnordered ? "unordered" : "ordered",
10433
- items
10431
+ columnGrid: { unit: "twips", values },
10432
+ width: {
10433
+ size: values.reduce((sum, w) => sum + w, 0),
10434
+ unit: "twips"
10435
+ },
10436
+ ...totalStated > measureTwips ? { overflow: { totalTwips: totalStated, availableTwips: measureTwips } } : {}
10434
10437
  };
10435
10438
  }
10436
10439
 
10437
- // src/ir/inline.ts
10438
- function escapeRegExp(value) {
10439
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10440
- }
10441
- function buildNoProofWordsRegex(noProofWords2) {
10442
- const words = (noProofWords2 ?? []).filter((word) => word?.trim().length > 0);
10443
- if (words.length === 0) return null;
10444
- const escaped = words.slice().sort((a, b) => b.length - a.length).map(escapeRegExp);
10445
- return new RegExp(
10446
- `(?<![\\p{L}\\p{N}])(?:${escaped.join("|")})(?![\\p{L}\\p{N}])`,
10447
- "giu"
10448
- );
10440
+ // src/ir/compiler.ts
10441
+ init_layoutUtils();
10442
+
10443
+ // src/utils/revisionUtils.ts
10444
+ var DEFAULT_REVISION_AUTHOR = "json-to-office";
10445
+ var DEFAULT_REVISION_DATE = "1970-01-01T00:00:00Z";
10446
+
10447
+ // src/utils/commentRegistry.ts
10448
+ var DEFAULT_COMMENT_AUTHOR = "json-to-office";
10449
+ var DEFAULT_COMMENT_DATE = "1970-01-01T00:00:00Z";
10450
+
10451
+ // src/ir/compiler.ts
10452
+ init_imageUtils();
10453
+ init_widthUtils();
10454
+
10455
+ // src/utils/numberingConfig.ts
10456
+ var HEADING_NUMBERING_REFERENCE = "jto-heading-numbering";
10457
+ function headingNumberLabel(number) {
10458
+ return `${number}.`;
10449
10459
  }
10450
- var DECORATOR = /(\*\*\*|___)([\s\S]*?)\1|(\*\*|__)([\s\S]*?)\3|(\*|_)([\s\S]*?)\5/g;
10451
- var ESCAPABLE = "*_[]{}\\";
10452
- var ESCAPE = /\\([*_[\]{}\\])/g;
10453
- var SENTINEL = "\uE000";
10454
- var SENTINEL_SELF = "x";
10455
- var SENTINEL_DIGIT_BASE = 48;
10456
- function encodeEscapes(text) {
10457
- const authored = text.includes(SENTINEL);
10458
- if (!authored && !text.includes("\\")) return text;
10459
- const guarded = authored ? text.split(SENTINEL).join(SENTINEL + SENTINEL_SELF) : text;
10460
- return guarded.replace(
10461
- ESCAPE,
10462
- (_whole, char) => SENTINEL + String.fromCharCode(SENTINEL_DIGIT_BASE + ESCAPABLE.indexOf(char))
10463
- );
10460
+
10461
+ // src/core/collectTocHeadings.ts
10462
+ import { getStandardComponent } from "@json-to-office/shared-docx";
10463
+
10464
+ // src/core/sectionBookmarks.ts
10465
+ function sectionBookmark(ordinal) {
10466
+ return { id: `_Section_${ordinal}`, linkId: ordinal };
10464
10467
  }
10465
- function decodeEscapes(text) {
10466
- if (!text.includes(SENTINEL)) return text;
10468
+
10469
+ // src/utils/numberFormatting.ts
10470
+ var ROMAN_NUMERALS = [
10471
+ [1e3, "m"],
10472
+ [900, "cm"],
10473
+ [500, "d"],
10474
+ [400, "cd"],
10475
+ [100, "c"],
10476
+ [90, "xc"],
10477
+ [50, "l"],
10478
+ [40, "xl"],
10479
+ [10, "x"],
10480
+ [9, "ix"],
10481
+ [5, "v"],
10482
+ [4, "iv"],
10483
+ [1, "i"]
10484
+ ];
10485
+ function toRoman(value) {
10486
+ let remaining = value;
10467
10487
  let out = "";
10468
- for (let i = 0; i < text.length; i += 1) {
10469
- if (text[i] !== SENTINEL) {
10470
- out += text[i];
10471
- continue;
10472
- }
10473
- const marker = text[i + 1];
10474
- if (marker === SENTINEL_SELF) {
10475
- out += SENTINEL;
10476
- i += 1;
10477
- continue;
10478
- }
10479
- const index = marker === void 0 ? -1 : marker.charCodeAt(0) - SENTINEL_DIGIT_BASE;
10480
- if (index >= 0 && index < ESCAPABLE.length) {
10481
- out += ESCAPABLE[index];
10482
- i += 1;
10483
- continue;
10488
+ for (const [amount, glyph] of ROMAN_NUMERALS) {
10489
+ while (remaining >= amount) {
10490
+ out += glyph;
10491
+ remaining -= amount;
10484
10492
  }
10485
- out += text[i];
10486
10493
  }
10487
10494
  return out;
10488
10495
  }
10489
- var LINK = /\[([^\]]+)\]\(([^)]+)\)/g;
10490
- var DECORATOR_OR_PLACEHOLDER = /(\*\*\*|___)([\s\S]*?)\1|(\*\*|__)([\s\S]*?)\3|(\*|_)([\s\S]*?)\5|\{([^}]+)\}/g;
10491
- var PLACEHOLDER = /\{([^}]+)\}/;
10492
- var UNSUPPORTED_SYNTAX = [];
10493
- var CROSS_REFERENCE = /\[@([^\]\s:]+)(?::(relative|no_context|full_context|none))?\]/;
10494
- var INLINE_TOKEN = new RegExp(
10495
- `${LINK.source}|${CROSS_REFERENCE.source}`,
10496
- "g"
10497
- );
10498
- var NOTE_MARKER = /\[\^([^\]\s]+)\]/g;
10499
- function containsPlaceholder(text) {
10500
- return PLACEHOLDER.test(text);
10496
+ function toLetters(value) {
10497
+ const index = (value - 1) % 26;
10498
+ const repeats = Math.floor((value - 1) / 26) + 1;
10499
+ return String.fromCharCode(97 + index).repeat(repeats);
10501
10500
  }
10502
- function containsCrossReference(text) {
10503
- return new RegExp(CROSS_REFERENCE.source).test(text);
10501
+ function formatNumberForLevel(value, format2) {
10502
+ if (!Number.isFinite(value) || value < 1) return void 0;
10503
+ const n = Math.floor(value);
10504
+ switch (format2) {
10505
+ case "decimal":
10506
+ return String(n);
10507
+ case "lowerLetter":
10508
+ return toLetters(n);
10509
+ case "upperLetter":
10510
+ return toLetters(n).toUpperCase();
10511
+ case "lowerRoman":
10512
+ return toRoman(n);
10513
+ case "upperRoman":
10514
+ return toRoman(n).toUpperCase();
10515
+ default:
10516
+ return void 0;
10517
+ }
10518
+ }
10519
+
10520
+ // src/core/collectTocHeadings.ts
10521
+ var MAX_HEADING_LEVEL = 6;
10522
+ var MAX_LIST_LEVEL = 9;
10523
+ function normalizeEntryTitle(text) {
10524
+ return normalizeUnicodeText(text).replace(/(\*\*\*|___)([\s\S]*?)\1/g, "$2").replace(/(\*\*|__)([\s\S]*?)\1/g, "$2").replace(/(\*|_)([\s\S]*?)\1/g, "$2").trim();
10525
+ }
10526
+ function isContainer(name) {
10527
+ return getStandardComponent(name)?.hasChildren === true;
10504
10528
  }
10505
- function containsLink(text) {
10506
- return new RegExp(LINK.source).test(text);
10529
+ function isEnabled(component) {
10530
+ return !("enabled" in component && component.enabled === false);
10507
10531
  }
10508
- function containsUnsupportedSyntax(text) {
10509
- for (const { pattern, what } of UNSUPPORTED_SYNTAX) {
10510
- if (pattern.test(text)) return what;
10532
+ function styleEntryKey(props) {
10533
+ const themeStyle = props.themeStyle;
10534
+ if (typeof themeStyle !== "string" || !themeStyle) return void 0;
10535
+ if (/^heading[1-6]$/i.test(themeStyle)) return void 0;
10536
+ if (["normal", "title", "subtitle"].includes(themeStyle.toLowerCase())) {
10537
+ return void 0;
10511
10538
  }
10512
- return void 0;
10539
+ return themeStyle;
10513
10540
  }
10514
- function parseInline(text, options) {
10515
- const normalized = encodeEscapes(normalizeUnicodeText(text));
10516
- if (!normalized) {
10517
- return [
10518
- { kind: "text", text: "", formatting: emptyToUndefined(options.base) }
10519
- ];
10520
- }
10521
- if (containsPlaceholder(normalized)) {
10522
- return parsePlaceholders(normalized, options);
10523
- }
10524
- if (options.hyperlinks) return parseLinks(normalized, options);
10525
- return parseDecorated(normalized, options);
10541
+ function levelStart(levels, level) {
10542
+ return levels[level]?.start ?? 1;
10526
10543
  }
10527
- function parsePlaceholders(normalized, parseOptions) {
10528
- const options = {
10529
- ...parseOptions,
10530
- boldColor: void 0,
10531
- resolveNote: void 0
10532
- };
10533
- const out = [];
10534
- const token = new RegExp(DECORATOR_OR_PLACEHOLDER.source, "g");
10535
- let lastIndex = 0;
10536
- let match;
10537
- const literal = (text, base = options.base) => {
10538
- if (!text) return;
10539
- out.push(...parseLiteralLines(text, { ...options, base }));
10544
+ function collectDocumentOutline(sections) {
10545
+ const entries = [];
10546
+ const numberedItems = /* @__PURE__ */ new Map();
10547
+ const ordinals = computeSectionOrdinals(sections);
10548
+ const takenIds = /* @__PURE__ */ new Set();
10549
+ const headingCounters = new Array(MAX_HEADING_LEVEL).fill(0);
10550
+ const listCounters = /* @__PURE__ */ new Map();
10551
+ const visitHeading = (component, props, sectionBookmarkId) => {
10552
+ const text = typeof props.text === "string" ? props.text : "";
10553
+ const title = normalizeEntryTitle(text);
10554
+ const level = typeof props.level === "number" ? props.level : 1;
10555
+ const styleLevel = level >= 1 && level <= MAX_HEADING_LEVEL ? level : 1;
10556
+ let full;
10557
+ let own;
10558
+ if (props.numbering === true) {
10559
+ headingCounters[styleLevel - 1] += 1;
10560
+ for (let deeper = styleLevel; deeper < MAX_HEADING_LEVEL; deeper++) {
10561
+ headingCounters[deeper] = 0;
10562
+ }
10563
+ full = headingCounters.slice(0, styleLevel).join(".");
10564
+ own = String(headingCounters[styleLevel - 1]);
10565
+ }
10566
+ const explicitId = component.id;
10567
+ const bookmarkId = typeof explicitId === "string" && explicitId ? explicitId : dedupeBookmarkId(slugifyBookmarkText(text), (id) => takenIds.has(id));
10568
+ takenIds.add(bookmarkId);
10569
+ numberedItems.set(bookmarkId, {
10570
+ kind: "heading",
10571
+ text: title,
10572
+ ...full !== void 0 && { full, own }
10573
+ });
10574
+ if (title) {
10575
+ entries.push({
10576
+ title,
10577
+ level,
10578
+ sectionBookmarkId,
10579
+ ...full !== void 0 && { number: full }
10580
+ });
10581
+ }
10540
10582
  };
10541
- while ((match = token.exec(normalized)) !== null) {
10542
- if (match.index > lastIndex) {
10543
- literal(normalized.slice(lastIndex, match.index));
10583
+ const visitList = (props) => {
10584
+ const items = Array.isArray(props.items) ? props.items : [];
10585
+ if (items.length === 0) return;
10586
+ const reference = typeof props.reference === "string" && props.reference ? props.reference : void 0;
10587
+ const freshState = () => {
10588
+ const levels = resolveListLevels(
10589
+ props
10590
+ );
10591
+ return {
10592
+ levels,
10593
+ counters: Array.from(
10594
+ { length: MAX_LIST_LEVEL },
10595
+ (_, level) => levelStart(levels, level) - 1
10596
+ )
10597
+ };
10598
+ };
10599
+ let state;
10600
+ if (reference === void 0) {
10601
+ state = freshState();
10602
+ } else {
10603
+ state = listCounters.get(reference) ?? freshState();
10604
+ listCounters.set(reference, state);
10544
10605
  }
10545
- lastIndex = match.index + match[0].length;
10546
- if (match[7]) {
10547
- const resolved = options.resolvePlaceholder?.(match[7]);
10548
- if (!resolved) {
10549
- literal(match[0]);
10550
- } else if (resolved.kind === "text") {
10551
- literal(resolved.text);
10552
- } else {
10553
- const formatting = emptyToUndefined({
10554
- fontFamily: options.base.fontFamily,
10555
- sizeHalfPoints: options.base.sizeHalfPoints,
10556
- color: options.base.color,
10557
- bold: options.base.bold,
10558
- italic: options.base.italic,
10559
- underline: options.base.underline
10560
- });
10561
- out.push({
10562
- kind: "field",
10563
- instruction: resolved.instruction,
10564
- ...resolved.cachedText !== void 0 && {
10565
- cachedText: resolved.cachedText
10566
- },
10567
- ...formatting ? { formatting } : {}
10568
- });
10606
+ for (const item of items) {
10607
+ const isObject = typeof item === "object" && item !== null;
10608
+ const raw = isObject ? item.text : item;
10609
+ const text = typeof raw === "string" ? raw : "";
10610
+ if (!text.trim() && !(isObject && item.revision)) continue;
10611
+ const rawLevel = isObject ? item.level : void 0;
10612
+ const level = typeof rawLevel === "number" && rawLevel >= 0 ? rawLevel : 0;
10613
+ if (level >= MAX_LIST_LEVEL) continue;
10614
+ state.counters[level] += 1;
10615
+ for (let deeper = level + 1; deeper < MAX_LIST_LEVEL; deeper++) {
10616
+ state.counters[deeper] = levelStart(state.levels, deeper) - 1;
10569
10617
  }
10570
- continue;
10618
+ const id = isObject ? item.id : void 0;
10619
+ if (typeof id !== "string" || !id) continue;
10620
+ takenIds.add(id);
10621
+ const number = formatNumberForLevel(
10622
+ state.counters[level],
10623
+ state.levels[level]?.format
10624
+ );
10625
+ numberedItems.set(id, {
10626
+ kind: "list-item",
10627
+ text: normalizeUnicodeText(text).trim(),
10628
+ ...number !== void 0 && { full: number, own: number }
10629
+ });
10571
10630
  }
10572
- const style = decoratedStyle(match, options.base);
10573
- out.push(
10574
- ...parsePlaceholders(decoratedText(match), {
10575
- ...options,
10576
- base: { ...options.base, ...style },
10577
- // No-proof words do not survive into a decorated span here: the
10578
- // recursion has always been entered without them.
10579
- noProofWords: void 0
10580
- })
10581
- );
10582
- }
10583
- if (lastIndex < normalized.length) literal(normalized.slice(lastIndex));
10584
- if (out.length === 0 && normalized) literal(normalized);
10585
- return out;
10631
+ };
10632
+ sections.forEach((section2, index) => {
10633
+ const ordinal = ordinals[index]?.ordinal;
10634
+ const sectionBookmarkId = ordinal ? sectionBookmark(ordinal).id : void 0;
10635
+ const visit = (component) => {
10636
+ if (!isEnabled(component)) return;
10637
+ const props = component.props ?? {};
10638
+ if (component.name === "heading") {
10639
+ visitHeading(component, props, sectionBookmarkId);
10640
+ return;
10641
+ }
10642
+ if (component.name === "paragraph") {
10643
+ if (typeof props.id === "string" && props.id) takenIds.add(props.id);
10644
+ const styleId = styleEntryKey(props);
10645
+ const text = typeof props.text === "string" ? props.text : "";
10646
+ const title = normalizeEntryTitle(text);
10647
+ if (styleId && title) {
10648
+ entries.push({ title, level: 1, styleId, sectionBookmarkId });
10649
+ }
10650
+ return;
10651
+ }
10652
+ if (component.name === "list") {
10653
+ visitList(props);
10654
+ return;
10655
+ }
10656
+ if (!isContainer(component.name)) return;
10657
+ const children = component.children;
10658
+ if (Array.isArray(children)) children.forEach(visit);
10659
+ };
10660
+ section2.components.forEach(visit);
10661
+ });
10662
+ return { entries, numberedItems };
10586
10663
  }
10587
- function parseLiteralLines(text, options) {
10588
- const out = [];
10589
- pushSegment(out, text, options);
10590
- return out;
10664
+
10665
+ // src/core/tocField.ts
10666
+ function toStyleDisplayName(styleId) {
10667
+ return styleId.replace(/([A-Z])/g, " $1").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim();
10591
10668
  }
10592
- function parseLiteral(text, options) {
10593
- const out = [];
10594
- pushWords(
10595
- out,
10596
- normalizeUnicodeText(text),
10597
- emptyToUndefined(options.base),
10598
- options
10599
- );
10600
- return out;
10669
+ function parseDepthRange(rawDepth, fieldName, defaultFrom = 1, defaultTo = 3) {
10670
+ if (typeof rawDepth !== "object" || rawDepth === null) {
10671
+ throw new Error(
10672
+ `${fieldName} must be a range object with optional "from" and/or "to" fields, received: ${JSON.stringify(rawDepth)}`
10673
+ );
10674
+ }
10675
+ const { from = defaultFrom, to = defaultTo } = rawDepth;
10676
+ if (from < 1 || from > 6 || to < 1 || to > 6) {
10677
+ throw new Error(
10678
+ `${fieldName} range values must be between 1 and 6, received: from=${from}, to=${to}`
10679
+ );
10680
+ }
10681
+ if (from > to) {
10682
+ throw new Error(
10683
+ `${fieldName} "from" must be less than or equal to "to", received: from=${from}, to=${to}`
10684
+ );
10685
+ }
10686
+ return { from, to };
10601
10687
  }
10602
- function parseLinks(normalized, options) {
10603
- const out = [];
10604
- const token = new RegExp(INLINE_TOKEN.source, "g");
10605
- let lastIndex = 0;
10606
- let match;
10607
- while ((match = token.exec(normalized)) !== null) {
10608
- if (match.index > lastIndex) {
10609
- out.push(
10610
- ...parseDecorated(normalized.slice(lastIndex, match.index), options)
10611
- );
10612
- }
10613
- lastIndex = match.index + match[0].length;
10614
- if (match[1] === void 0) {
10615
- const resolved = options.resolveCrossReference?.(
10616
- decodeEscapes(match[3]),
10617
- match[4] ?? "relative",
10618
- match[0]
10619
- );
10620
- if (resolved) out.push(resolved);
10621
- else out.push(...parseDecorated(match[0], options));
10688
+ function selectEntries(collected, options) {
10689
+ if (!collected || collected.length === 0) return [];
10690
+ const { depthStart, depthEnd, bookmarkScope, styleLevels } = options;
10691
+ const entries = [];
10692
+ for (const entry of collected) {
10693
+ if (bookmarkScope && entry.sectionBookmarkId !== bookmarkScope) continue;
10694
+ if (entry.styleId !== void 0) {
10695
+ const level = styleLevels.get(entry.styleId) ?? styleLevels.get(toStyleDisplayName(entry.styleId));
10696
+ if (level === void 0) continue;
10697
+ entries.push({ text: entry.title, level });
10622
10698
  continue;
10623
10699
  }
10624
- const [, linkText] = match;
10625
- const target2 = decodeEscapes(match[2]);
10626
- out.push({
10627
- kind: "hyperlink",
10628
- // A `#anchor` target names a bookmark in this document and needs no
10629
- // relationship; anything else leaves the document.
10630
- target: target2.startsWith("#") ? { kind: "bookmark", anchor: target2.slice(1) } : { kind: "external", url: target2 },
10631
- children: parseDecorated(linkText, options)
10700
+ if (entry.level < depthStart || entry.level > depthEnd) continue;
10701
+ entries.push({
10702
+ text: entry.number ? `${headingNumberLabel(entry.number)} ${entry.title}` : entry.title,
10703
+ level: entry.level
10632
10704
  });
10633
10705
  }
10634
- if (lastIndex < normalized.length) {
10635
- out.push(...parseDecorated(normalized.slice(lastIndex), options));
10636
- }
10637
- if (out.length === 0) return parseDecorated(normalized, options);
10638
- return out;
10706
+ return entries;
10639
10707
  }
10640
- function parseDecorated(normalized, options) {
10641
- const out = [];
10642
- let lastIndex = 0;
10643
- let match;
10644
- const decorator = new RegExp(DECORATOR.source, "g");
10645
- while ((match = decorator.exec(normalized)) !== null) {
10646
- if (match.index > lastIndex) {
10647
- pushSegment(out, normalized.slice(lastIndex, match.index), options);
10648
- }
10649
- pushSegment(
10650
- out,
10651
- decoratedText(match),
10652
- options,
10653
- decoratedStyle(match, options.base)
10708
+ function resolveTocField(props, theme, context) {
10709
+ const warnings = [];
10710
+ const headingRange = parseDepthRange(
10711
+ props.depth ?? { to: 3 },
10712
+ "TOC depth",
10713
+ 1,
10714
+ 3
10715
+ );
10716
+ const pageNumbers = props.pageNumbersDepth !== void 0 ? parseDepthRange(props.pageNumbersDepth, "TOC pageNumbersDepth", 1, 3) : void 0;
10717
+ if (props.numberingStyle !== void 0) {
10718
+ warnings.push(
10719
+ `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.`
10654
10720
  );
10655
- lastIndex = match.index + match[0].length;
10656
10721
  }
10657
- if (lastIndex < normalized.length) {
10658
- pushSegment(out, normalized.slice(lastIndex), options);
10722
+ const scope = props.scope ?? "auto";
10723
+ const effectiveScope = scope === "auto" ? context.sectionBookmarkId ? "section" : "document" : scope;
10724
+ const bookmarkScope = effectiveScope === "section" ? context.sectionBookmarkId : void 0;
10725
+ if (effectiveScope === "section" && !bookmarkScope) {
10726
+ warnings.push(
10727
+ "TOC configured for section scope but no section bookmark found. Falling back to document scope."
10728
+ );
10659
10729
  }
10660
- if (out.length === 0) pushSegment(out, normalized, options);
10661
- return out;
10662
- }
10663
- function decoratedText(match) {
10664
- if (match[1]) return match[2];
10665
- if (match[3]) return match[4];
10666
- if (match[5]) return match[6];
10667
- return match[0];
10668
- }
10669
- function decoratedStyle(match, base) {
10670
- const bold = base.bold ?? false;
10671
- const italic = base.italic ?? false;
10672
- if (match[1]) return { bold: true, italic: true };
10673
- if (match[3]) return { bold: true, italic };
10674
- if (match[5]) return { bold, italic: true };
10675
- return { bold, italic };
10730
+ const styleLevels = /* @__PURE__ */ new Map();
10731
+ const styleSwitches = [];
10732
+ for (const mapping of props.styles ?? []) {
10733
+ const isCustomStyle = !!theme.styles && Object.prototype.hasOwnProperty.call(theme.styles, mapping.styleId);
10734
+ const displayName = isCustomStyle ? toStyleDisplayName(mapping.styleId) : mapping.styleId;
10735
+ styleLevels.set(mapping.styleId, mapping.level);
10736
+ styleLevels.set(displayName, mapping.level);
10737
+ styleSwitches.push({ styleName: displayName, level: mapping.level });
10738
+ }
10739
+ return {
10740
+ headingRange,
10741
+ styleLevels: styleSwitches,
10742
+ ...bookmarkScope ? { bookmarkScope } : {},
10743
+ omitPageNumbersForLevels: omittedLevels(
10744
+ headingRange,
10745
+ pageNumbers,
10746
+ props.includePageNumbers !== false
10747
+ ),
10748
+ // A boolean chooses between a tab and a space; a tab is the default.
10749
+ entrySeparator: props.numberSeparator === false ? " " : " ",
10750
+ entries: selectEntries(context.collected, {
10751
+ depthStart: headingRange.from,
10752
+ depthEnd: headingRange.to,
10753
+ bookmarkScope,
10754
+ styleLevels
10755
+ }),
10756
+ warnings
10757
+ };
10676
10758
  }
10677
- function pushSegment(out, text, options, override = {}) {
10678
- const bold = override.bold ?? options.base.bold;
10679
- const italic = override.italic ?? options.base.italic;
10680
- const formatting = runFormatting2(options, { bold, italic });
10681
- const lines = decodeEscapes(text).split("\n");
10682
- for (const [lineIndex, line] of lines.entries()) {
10683
- if (!line && lineIndex === 0) continue;
10684
- if (lineIndex > 0) out.push({ kind: "lineBreak" });
10685
- if (!line) {
10686
- out.push({ kind: "text", text: "", formatting });
10687
- continue;
10759
+ function omittedLevels(headingRange, pageNumbers, includePageNumbers) {
10760
+ if (pageNumbers) {
10761
+ const omitted = [];
10762
+ if (pageNumbers.from > headingRange.from) {
10763
+ omitted.push({ from: headingRange.from, to: pageNumbers.from - 1 });
10688
10764
  }
10689
- const tabSegments = line.split(" ");
10690
- for (const [tabIndex, segment] of tabSegments.entries()) {
10691
- if (tabIndex > 0) out.push({ kind: "tab", formatting });
10692
- if (!segment && tabSegments.length > 1) continue;
10693
- pushWords(out, segment, formatting, options);
10765
+ if (pageNumbers.to < headingRange.to) {
10766
+ omitted.push({ from: pageNumbers.to + 1, to: headingRange.to });
10694
10767
  }
10768
+ return omitted;
10695
10769
  }
10770
+ return includePageNumbers ? [] : [headingRange];
10696
10771
  }
10697
- function pushWords(out, text, formatting, options) {
10698
- if (options.resolveNote) {
10699
- splitNoteMarkers(out, text, formatting, options);
10700
- return;
10701
- }
10702
- pushNoProofWords(out, text, formatting, options);
10703
- }
10704
- function splitNoteMarkers(out, text, formatting, options) {
10705
- const regex = new RegExp(NOTE_MARKER.source, "g");
10706
- let lastIndex = 0;
10707
- let pending = "";
10708
- let match;
10709
- while ((match = regex.exec(text)) !== null) {
10710
- const note = options.resolveNote(match[1]);
10711
- if (note === void 0) {
10712
- pending += text.slice(lastIndex, regex.lastIndex);
10713
- lastIndex = regex.lastIndex;
10772
+
10773
+ // src/core/markdownList.ts
10774
+ function parseMarkdownList(text) {
10775
+ const lines = text.split("\n");
10776
+ const items = [];
10777
+ let isUnordered = null;
10778
+ let isOrdered = null;
10779
+ for (const line of lines) {
10780
+ if (!line.trim()) continue;
10781
+ const unorderedMatch = line.match(/^(\s*)([-*])\s+(.+)$/);
10782
+ if (unorderedMatch) {
10783
+ const indentLevel = Math.floor(unorderedMatch[1].length / 2);
10784
+ const text2 = unorderedMatch[3];
10785
+ items.push({ text: text2, level: indentLevel });
10786
+ if (isUnordered === null) isUnordered = true;
10714
10787
  continue;
10715
10788
  }
10716
- const before = pending + text.slice(lastIndex, match.index);
10717
- pending = "";
10718
- if (before) pushNoProofWords(out, before, formatting, options);
10719
- out.push({
10720
- kind: "noteReference",
10721
- noteKind: note.noteKind,
10722
- id: note.id
10723
- });
10724
- lastIndex = regex.lastIndex;
10725
- }
10726
- const rest = pending + text.slice(lastIndex);
10727
- if (rest) pushNoProofWords(out, rest, formatting, options);
10728
- }
10729
- function pushNoProofWords(out, text, formatting, options) {
10730
- const wholeRunNoProof = options.base.noProof === true;
10731
- const regex = wholeRunNoProof ? null : buildNoProofWordsRegex(options.noProofWords);
10732
- if (!regex) {
10733
- out.push({ kind: "text", text, formatting });
10734
- return;
10735
- }
10736
- let lastIndex = 0;
10737
- let match;
10738
- while ((match = regex.exec(text)) !== null) {
10739
- if (match.index > lastIndex) {
10740
- out.push({
10741
- kind: "text",
10742
- text: text.slice(lastIndex, match.index),
10743
- formatting
10744
- });
10789
+ const orderedMatch = line.match(/^(\s*)(\d+)\.\s+(.+)$/);
10790
+ if (orderedMatch) {
10791
+ const indentLevel = Math.floor(orderedMatch[1].length / 2);
10792
+ const text2 = orderedMatch[3];
10793
+ items.push({ text: text2, level: indentLevel });
10794
+ if (isOrdered === null) isOrdered = true;
10795
+ continue;
10745
10796
  }
10746
- out.push({
10747
- kind: "text",
10748
- text: match[0],
10749
- formatting: { ...formatting, noProof: true }
10750
- });
10751
- lastIndex = match.index + match[0].length;
10752
- if (regex.lastIndex === match.index) regex.lastIndex += 1;
10753
- }
10754
- if (lastIndex < text.length) {
10755
- out.push({ kind: "text", text: text.slice(lastIndex), formatting });
10797
+ return null;
10756
10798
  }
10757
- }
10758
- function runFormatting2(options, state) {
10759
- const formatting = { ...options.base };
10760
- if (state.bold !== void 0) formatting.bold = state.bold;
10761
- if (state.italic !== void 0) formatting.italic = state.italic;
10762
- if (state.bold && options.boldColor && options.base.color) {
10763
- formatting.color = options.boldColor;
10799
+ if (items.length === 0 || isUnordered && isOrdered) {
10800
+ return null;
10764
10801
  }
10765
- return emptyToUndefined(formatting);
10766
- }
10767
- function emptyToUndefined(formatting) {
10768
- const entries = Object.entries(formatting).filter(
10769
- ([, value]) => value !== void 0
10770
- );
10771
- return entries.length === 0 ? void 0 : Object.fromEntries(entries);
10802
+ return {
10803
+ type: isUnordered ? "unordered" : "ordered",
10804
+ items
10805
+ };
10772
10806
  }
10773
10807
 
10774
10808
  // src/ir/compiler.ts
@@ -11012,6 +11046,10 @@ function noProofWords(theme) {
11012
11046
  const words = theme.noProofWords;
11013
11047
  return words && words.length > 0 ? words : void 0;
11014
11048
  }
11049
+ function plainRuns(text, ctx, formatting) {
11050
+ const words = noProofWords(ctx.theme);
11051
+ return words && text ? noProofRuns(text, formatting, words) : [{ kind: "text", text, ...formatting ? { formatting } : {} }];
11052
+ }
11015
11053
  function mergeNoProofWords(themeWords, componentWords) {
11016
11054
  const own = Array.isArray(componentWords) ? componentWords : [];
11017
11055
  const merged = [...themeWords ?? [], ...own];
@@ -11370,7 +11408,8 @@ function compileParagraph(component, scope) {
11370
11408
  ...props.alignment ? { alignment: props.alignment } : {},
11371
11409
  ...props.comment ? { comment: props.comment } : {},
11372
11410
  ...props.footnotes ? { footnotes: props.footnotes } : {},
11373
- ...props.endnotes ? { endnotes: props.endnotes } : {}
11411
+ ...props.endnotes ? { endnotes: props.endnotes } : {},
11412
+ ...props.noProofWords ? { noProofWords: props.noProofWords } : {}
11374
11413
  }
11375
11414
  },
11376
11415
  scope,
@@ -11884,13 +11923,10 @@ function compileToc(component, scope) {
11884
11923
  // No style, not even Normal: a title with an outline level would collect
11885
11924
  // itself into the very list it introduces. It carries its own weight and
11886
11925
  // size instead.
11887
- children: [
11888
- {
11889
- kind: "text",
11890
- text: String(props.title),
11891
- formatting: { bold: true, sizeHalfPoints: 28 }
11892
- }
11893
- ],
11926
+ children: plainRuns(String(props.title), ctx, {
11927
+ bold: true,
11928
+ sizeHalfPoints: 28
11929
+ }),
11894
11930
  formatting: {
11895
11931
  alignment: "left",
11896
11932
  spacing: {
@@ -12078,7 +12114,7 @@ function compileStatistic(component, scope) {
12078
12114
  ...spacing ? { spacing } : {}
12079
12115
  }),
12080
12116
  line(
12081
- descriptionText ? [{ kind: "text", text: descriptionText }] : [],
12117
+ descriptionText ? plainRuns(descriptionText, ctx) : [],
12082
12118
  STATISTIC_DESCRIPTION_STYLE_ID,
12083
12119
  "description",
12084
12120
  { alignment: alignment2 }
@@ -12096,6 +12132,7 @@ function compileList(component, scope, referencePrefix = "list", explicitLevels)
12096
12132
  ctx
12097
12133
  );
12098
12134
  const notes = createNoteBinding(props.footnotes, props.endnotes, ctx);
12135
+ const words = mergeNoProofWords(noProofWords(ctx.theme), props.noProofWords);
12099
12136
  const reference = declareNumbering(
12100
12137
  props,
12101
12138
  ctx,
@@ -12132,10 +12169,11 @@ function compileList(component, scope, referencePrefix = "list", explicitLevels)
12132
12169
  revision === void 0 ? parseInline(text, {
12133
12170
  base,
12134
12171
  hyperlinks: true,
12172
+ ...words ? { noProofWords: words } : {},
12135
12173
  ...notes ? { resolveNote: notes.resolve } : {},
12136
12174
  resolvePlaceholder: placeholderResolver(ctx),
12137
12175
  resolveCrossReference: crossReferenceResolver(ctx, itemPath)
12138
- }) : compileRevision(revision, base, ctx),
12176
+ }) : compileRevision(revision, base, ctx, words),
12139
12177
  {
12140
12178
  styleId: "Normal",
12141
12179
  formatting: {
@@ -12305,7 +12343,7 @@ function compileRuns(props, text, ctx, path4, mode = "inline") {
12305
12343
  ctx.features.require("revisions", path4);
12306
12344
  return [
12307
12345
  ...props.columnBreak ? [{ kind: "columnBreak" }] : [],
12308
- ...compileRevision(props.revision, base, ctx)
12346
+ ...compileRevision(props.revision, base, ctx, words)
12309
12347
  ];
12310
12348
  }
12311
12349
  const parseOptions = {
@@ -12428,7 +12466,7 @@ function createNoteBinding(footnotes, endnotes, ctx) {
12428
12466
  id: `${note.endnote ? "endnote" : "footnote"}${resolved.id}:p${index}`,
12429
12467
  path: `${note.endnote ? "endnotes" : "footnotes"}[${resolved.id}].children[${index}]`,
12430
12468
  styleId: note.endnote ? "EndnoteText" : "FootnoteText",
12431
- children: [{ kind: "text", text: line }]
12469
+ children: plainRuns(line, ctx)
12432
12470
  }))
12433
12471
  });
12434
12472
  registered.set(id, resolved);
@@ -12481,7 +12519,7 @@ function declareComment(comment, ctx, path4) {
12481
12519
  kind: "paragraph",
12482
12520
  id: `comment${id}:p${index}`,
12483
12521
  path: `comments[${id}].children[${index}]`,
12484
- children: [{ kind: "text", text: line }]
12522
+ children: plainRuns(line, ctx)
12485
12523
  })),
12486
12524
  ...parentId !== void 0 ? { parentId } : {},
12487
12525
  ...resolved !== void 0 ? { resolved } : {}
@@ -12507,7 +12545,7 @@ function closeComment(ids) {
12507
12545
  { kind: "commentReference", id }
12508
12546
  ]);
12509
12547
  }
12510
- function compileRevision(revision, base, ctx) {
12548
+ function compileRevision(revision, base, ctx, words = noProofWords(ctx.theme)) {
12511
12549
  const author = revision.author || DEFAULT_REVISION_AUTHOR;
12512
12550
  const date = revision.date || DEFAULT_REVISION_DATE;
12513
12551
  const out = [];
@@ -12515,15 +12553,17 @@ function compileRevision(revision, base, ctx) {
12515
12553
  if (!segment.text) continue;
12516
12554
  const text = normalizeUnicodeText(segment.text);
12517
12555
  if (segment.type === "insert" || segment.type === "delete") {
12518
- for (const line of literalLines(text, base)) {
12519
- out.push({
12520
- kind: "revision",
12521
- type: segment.type,
12522
- id: ctx.nextRevisionId++,
12523
- author,
12524
- date,
12525
- children: line
12526
- });
12556
+ for (const line of literalLines(text, base, words)) {
12557
+ for (const children of oneRunEach(line)) {
12558
+ out.push({
12559
+ kind: "revision",
12560
+ type: segment.type,
12561
+ id: ctx.nextRevisionId++,
12562
+ author,
12563
+ date,
12564
+ children
12565
+ });
12566
+ }
12527
12567
  }
12528
12568
  continue;
12529
12569
  }
@@ -12531,24 +12571,36 @@ function compileRevision(revision, base, ctx) {
12531
12571
  out.push(
12532
12572
  ...parseInline(text, {
12533
12573
  base,
12574
+ ...words ? { noProofWords: words } : {},
12534
12575
  resolvePlaceholder: placeholderResolver(ctx)
12535
12576
  })
12536
12577
  );
12537
12578
  continue;
12538
12579
  }
12539
- for (const line of literalLines(text, base)) out.push(...line);
12580
+ for (const line of literalLines(text, base, words)) out.push(...line);
12540
12581
  }
12541
12582
  return out;
12542
12583
  }
12543
- function literalLines(text, base) {
12584
+ function oneRunEach(line) {
12585
+ const ranges = [];
12586
+ for (const node of line) {
12587
+ const last = ranges[ranges.length - 1];
12588
+ if (last?.[last.length - 1]?.kind === "lineBreak") last.push(node);
12589
+ else ranges.push([node]);
12590
+ }
12591
+ return ranges;
12592
+ }
12593
+ function literalLines(text, base, words) {
12544
12594
  const formatting = Object.keys(base).length > 0 ? base : void 0;
12545
12595
  return text.split("\n").map((line, index) => [
12546
12596
  ...index > 0 ? [{ kind: "lineBreak" }] : [],
12547
- {
12548
- kind: "text",
12549
- text: line,
12550
- ...formatting ? { formatting } : {}
12551
- }
12597
+ ...words && line ? noProofRuns(line, formatting, words) : [
12598
+ {
12599
+ kind: "text",
12600
+ text: line,
12601
+ ...formatting ? { formatting } : {}
12602
+ }
12603
+ ]
12552
12604
  ]);
12553
12605
  }
12554
12606
  function resolvePlaceholder(name, generatedAt) {
@@ -12843,7 +12895,8 @@ function nativeVisualDeps(ctx, path4) {
12843
12895
  };
12844
12896
  },
12845
12897
  reject: (detail) => ctx.unsupported.push({ name: "visual", path: path4, detail }),
12846
- warn: (message) => warnOnce(ctx, `visual:${message}`, message)
12898
+ warn: (message) => warnOnce(ctx, `visual:${message}`, message),
12899
+ ...noProofWords(ctx.theme) ? { noProofWords: noProofWords(ctx.theme) } : {}
12847
12900
  };
12848
12901
  }
12849
12902
  function visualPlacementPixels(props, canvas, reference) {
@@ -13127,6 +13180,7 @@ function captionBlock(value, scope, componentName) {
13127
13180
  ctx.unsupported.push({ name: componentName, path: path4, detail: syntax });
13128
13181
  return void 0;
13129
13182
  }
13183
+ const words = noProofWords(ctx.theme);
13130
13184
  return {
13131
13185
  kind: "paragraph",
13132
13186
  id: `${scope.id}:caption`,
@@ -13139,9 +13193,13 @@ function captionBlock(value, scope, componentName) {
13139
13193
  children: DECORATED.test(caption) ? parseInline(caption, {
13140
13194
  base: {},
13141
13195
  hyperlinks: true,
13196
+ ...words ? { noProofWords: words } : {},
13142
13197
  resolvePlaceholder: placeholderResolver(ctx),
13143
13198
  resolveCrossReference: crossReferenceResolver(ctx, path4)
13144
- }) : parseLiteral(caption, { base: {} })
13199
+ }) : parseLiteral(caption, {
13200
+ base: {},
13201
+ ...words ? { noProofWords: words } : {}
13202
+ })
13145
13203
  };
13146
13204
  }
13147
13205
  function relativeFrom(value, axis) {
@@ -13626,10 +13684,14 @@ function cellChildren(cell, baseStyle, scope, rowRevision) {
13626
13684
  }
13627
13685
  const text = cellText(cell);
13628
13686
  if (text === void 0) return wrap([]);
13687
+ const words = mergeNoProofWords(
13688
+ noProofWords(ctx.theme),
13689
+ typeof content === "object" ? content.props?.noProofWords : void 0
13690
+ );
13629
13691
  const revision = cell.revision ?? cellComponentRevision(cell);
13630
13692
  if (revision) {
13631
13693
  ctx.features.require("revisions", "");
13632
- return wrap(compileRevision(revision, base, ctx));
13694
+ return wrap(compileRevision(revision, base, ctx, words));
13633
13695
  }
13634
13696
  if (rowRevision) {
13635
13697
  return wrap(
@@ -13640,7 +13702,8 @@ function cellChildren(cell, baseStyle, scope, rowRevision) {
13640
13702
  segments: [{ type: rowRevision.type, text }]
13641
13703
  },
13642
13704
  base,
13643
- ctx
13705
+ ctx,
13706
+ words
13644
13707
  )
13645
13708
  );
13646
13709
  }
@@ -13648,6 +13711,7 @@ function cellChildren(cell, baseStyle, scope, rowRevision) {
13648
13711
  parseInline(text, {
13649
13712
  base,
13650
13713
  hyperlinks: true,
13714
+ ...words ? { noProofWords: words } : {},
13651
13715
  resolvePlaceholder: placeholderResolver(ctx),
13652
13716
  resolveCrossReference: crossReferenceResolver(ctx, "table")
13653
13717
  })