wordinweb 0.1.9 → 0.1.11

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
@@ -2457,6 +2457,8 @@ function parseRunProps(rPr, ctx) {
2457
2457
  else if (vertAlign === "baseline") props.verticalAlign = "baseline";
2458
2458
  const spacing = intAttr(child(rPr, "spacing"), "val");
2459
2459
  if (spacing !== void 0) props.letterSpacing = twipsToPx(spacing);
2460
+ const kern = intAttr(child(rPr, "kern"), "val");
2461
+ if (kern !== void 0) props.kerningThreshold = kern > 0 ? halfPtToPx(kern) : null;
2460
2462
  const wScale = intAttr(child(rPr, "w"), "val");
2461
2463
  if (wScale !== void 0 && wScale > 0 && wScale !== 100) props.textScale = wScale / 100;
2462
2464
  const fitText = intAttr(child(rPr, "fitText"), "val");
@@ -3417,6 +3419,17 @@ function parseParagraph(p, ctx) {
3417
3419
  parseParaChildren(p, ctx, para.children, field, bookmarks);
3418
3420
  if (bookmarks.length > 0) para.bookmarks = bookmarks;
3419
3421
  flushField(field);
3422
+ const visibleContent = para.children.flatMap(
3423
+ (paraChild) => (paraChild.type === "run" ? [paraChild] : paraChild.runs).flatMap(
3424
+ (run) => run.content.filter((content) => content.kind !== "text" || content.text.length > 0)
3425
+ )
3426
+ );
3427
+ if (visibleContent.length === 1 && visibleContent[0].kind === "math") {
3428
+ const node = visibleContent[0].nodes.length === 1 ? visibleContent[0].nodes[0] : void 0;
3429
+ if (node?.t === "frac" && node.num.every((part) => part.t === "run") && node.den.every((part) => part.t === "run")) {
3430
+ visibleContent[0].display = true;
3431
+ }
3432
+ }
3420
3433
  suppressTocHyperlinkFormatting(para);
3421
3434
  if (ctx.revisionView !== "markup" && pPr) {
3422
3435
  const markRPr = child(pPr, "rPr");
@@ -3659,13 +3672,14 @@ function parseParaChildren(parent, ctx, out, field, bookmarks) {
3659
3672
  const nodes = parseOmml(el16);
3660
3673
  if (nodes.length > 0) {
3661
3674
  const display = ln === "oMathPara";
3675
+ const multiEquationDisplay = display && children(el16, "oMath").length > 3;
3662
3676
  const jcVal = display ? attr(child(child(el16, "oMathParaPr"), "jc"), "val") : void 0;
3663
3677
  const jc = jcVal === "left" || jcVal === "right" || jcVal === "center" || jcVal === "centerGroup" ? jcVal : void 0;
3664
3678
  const content = [];
3665
3679
  let segment = [];
3666
3680
  const flushMath = () => {
3667
3681
  if (segment.length === 0) return;
3668
- content.push({ kind: "math", nodes: segment, src: el16, display, jc });
3682
+ content.push({ kind: "math", nodes: segment, src: el16, display, multiEquationDisplay, jc });
3669
3683
  segment = [];
3670
3684
  };
3671
3685
  for (const node of nodes) {
@@ -3896,6 +3910,9 @@ function parseRun(r, ctx, field, suppressedObjects) {
3896
3910
  case "tab":
3897
3911
  run.content.push({ kind: "tab" });
3898
3912
  break;
3913
+ case "pgNum":
3914
+ run.content.push({ kind: "field", instruction: "PAGE", cachedResult: "" });
3915
+ break;
3899
3916
  case "drawing": {
3900
3917
  const img = parseDrawing(el16, ctx);
3901
3918
  if (img) {
@@ -3943,7 +3960,7 @@ function parseRun(r, ctx, field, suppressedObjects) {
3943
3960
  let code = parseInt(charHex, 16);
3944
3961
  if (code >= 61440) code -= 61440;
3945
3962
  const font = attr(el16, "font") ?? "";
3946
- const mapped = /wingdings|webdings|symbol/i.test(font) ? SYMBOL_CHAR_MAP[code] : void 0;
3963
+ const mapped = /^symbol$/i.test(font) ? SYMBOL_TEXT_CHAR_MAP[code] : /wingdings|webdings/i.test(font) ? SYMBOL_CHAR_MAP[code] : void 0;
3947
3964
  run.content.push({ kind: "text", text: mapped ?? String.fromCharCode(code) });
3948
3965
  }
3949
3966
  break;
@@ -3955,10 +3972,12 @@ function parseRun(r, ctx, field, suppressedObjects) {
3955
3972
  case "endnoteReference": {
3956
3973
  const id = intAttr(el16, "id");
3957
3974
  if (id !== void 0) {
3975
+ const customMark = attr(el16, "customMarkFollows");
3958
3976
  run.content.push({
3959
3977
  kind: "noteRef",
3960
3978
  noteType: ln === "footnoteReference" ? "footnote" : "endnote",
3961
- id
3979
+ id,
3980
+ customMarkFollows: customMark !== void 0 && customMark !== "0" && customMark !== "false" && customMark !== "off"
3962
3981
  });
3963
3982
  }
3964
3983
  break;
@@ -4352,7 +4371,8 @@ function parseDrawing(drawing, ctx) {
4352
4371
  const pctEl = el16 ? el16.children.find((c) => localName(c.name).startsWith("pct")) : void 0;
4353
4372
  if (!pctEl) return void 0;
4354
4373
  const rf = attr(el16, "relativeFrom");
4355
- return { pct: (parseInt(pctEl.text, 10) || 0) / 1e5, rel: rf === "page" ? "page" : "margin" };
4374
+ const pct = (parseInt(pctEl.text, 10) || 0) / 1e5;
4375
+ return pct > 0 ? { pct, rel: rf === "page" ? "page" : "margin" } : void 0;
4356
4376
  };
4357
4377
  const spPr = child(textboxEl, "spPr");
4358
4378
  const txbxOwn = child(textboxEl, "txbx");
@@ -4361,6 +4381,7 @@ function parseDrawing(drawing, ctx) {
4361
4381
  const rot = intAttr(xfrm, "rot");
4362
4382
  const lnEl = child(spPr, "ln");
4363
4383
  const strokeColor = lnEl && !child(lnEl, "noFill") ? fillColorOf(lnEl) : void 0;
4384
+ const fill = child(spPr, "noFill") ? void 0 : fillColorOf(spPr) ?? styleFillOf(textboxEl);
4364
4385
  const bodyPr = child(textboxEl, "bodyPr");
4365
4386
  const insetOf = (name, dflt) => {
4366
4387
  const v = bodyPr ? intAttr(bodyPr, name) : void 0;
@@ -4404,7 +4425,7 @@ function parseDrawing(drawing, ctx) {
4404
4425
  vAlign: vAlignOf(posV),
4405
4426
  blocks: txbxContent ? parseBlocks(txbxContent, ctx) : [],
4406
4427
  ...textboxChainId !== void 0 ? { chainId: textboxChainId, chainSeq: textboxChainSeq ?? 0 } : {},
4407
- ...fillColorOf(spPr) ? { fill: fillColorOf(spPr) } : {},
4428
+ ...fill ? { fill } : {},
4408
4429
  ...strokeColor ? { stroke: { color: strokeColor, weight: Math.max(emuToPx(intAttr(lnEl, "w") ?? 0), 0.75) } } : {},
4409
4430
  textAnchor: anchorAttr === "ctr" ? "middle" : anchorAttr === "b" ? "bottom" : anchorAttr === "t" ? "top" : void 0,
4410
4431
  insets: {
@@ -4818,7 +4839,8 @@ function collectDiagramShapes(dspRoot, ctx, out, dx, dy) {
4818
4839
  height: emuToPx(th),
4819
4840
  blocks,
4820
4841
  insets: { l: insetOf("lIns", 9.6), t: insetOf("tIns", 4.8), r: insetOf("rIns", 9.6), b: insetOf("bIns", 4.8) },
4821
- textAnchor: anchorAttr === "ctr" ? "middle" : anchorAttr === "b" ? "bottom" : "top"
4842
+ textAnchor: anchorAttr === "ctr" ? "middle" : anchorAttr === "b" ? "bottom" : "top",
4843
+ paintOffsetY: 0.75
4822
4844
  });
4823
4845
  }
4824
4846
  }
@@ -4904,6 +4926,8 @@ function drawingMLRun(r, ctx) {
4904
4926
  const props = {};
4905
4927
  const sz = rPr ? intAttr(rPr, "sz") : void 0;
4906
4928
  props.size = ptToPx((sz ?? 1800) / 100);
4929
+ const kern = rPr ? intAttr(rPr, "kern") : void 0;
4930
+ if (kern !== void 0) props.kerningThreshold = kern > 0 ? ptToPx(kern / 100) : null;
4907
4931
  if (rPr && attr(rPr, "b") === "1") props.bold = true;
4908
4932
  if (rPr && attr(rPr, "i") === "1") props.italic = true;
4909
4933
  const u = rPr ? attr(rPr, "u") : void 0;
@@ -5348,6 +5372,8 @@ function parseCell(tc, ctx) {
5348
5372
  if (tcW && attr(tcW, "type") === "dxa") {
5349
5373
  const w = intAttr(tcW, "w");
5350
5374
  if (w) props.width = twipsToPx(w);
5375
+ } else if (tcW && attr(tcW, "type") === "auto") {
5376
+ props.widthAuto = true;
5351
5377
  }
5352
5378
  const borders = child(tcPr, "tcBorders");
5353
5379
  if (borders) {
@@ -5723,6 +5749,9 @@ var DocxDocument = class _DocxDocument {
5723
5749
  /** Note content by note id (render-only; sources stripped). */
5724
5750
  this.footnotes = /* @__PURE__ */ new Map();
5725
5751
  this.endnotes = /* @__PURE__ */ new Map();
5752
+ /** The separator paragraph controls the gap between its rule and the first
5753
+ * footnote. */
5754
+ this.footnoteSeparator = [];
5726
5755
  /** `_Ref` cross-reference bookmark ranges (name → captured runs). REF
5727
5756
  * fields re-render the referenced text from these — Word recomputes REF on
5728
5757
  * open, so the cached field result in the file is stale. */
@@ -5839,6 +5868,8 @@ var DocxDocument = class _DocxDocument {
5839
5868
  if (!docRoot) throw new Error(`Missing ${docPart} in package`);
5840
5869
  this.docRoot = docRoot;
5841
5870
  this.rememberOriginalXml(docPart, docRoot);
5871
+ const coreProperties = this.readXmlOptional("docProps/core.xml");
5872
+ this.hydrateCorePropertyControls(docRoot, coreProperties);
5842
5873
  this.repairLegacyWordInWebObjects();
5843
5874
  this.documentRels = parseRelationships(this.relsRoot ?? void 0, docPart);
5844
5875
  if (settings) {
@@ -5881,6 +5912,7 @@ var DocxDocument = class _DocxDocument {
5881
5912
  if (!root) continue;
5882
5913
  const partRels = parseRelationships(this.readXmlOptional(relsPathFor(rel.target)), rel.target);
5883
5914
  this.rememberOriginalXml(rel.target, root);
5915
+ this.hydrateCorePropertyControls(root, coreProperties);
5884
5916
  this.hfParts.push({ relId: rel.id, target: rel.target, root, isHeader, rels: partRels });
5885
5917
  }
5886
5918
  for (const rel of this.documentRels.values()) {
@@ -5894,6 +5926,12 @@ var DocxDocument = class _DocxDocument {
5894
5926
  this.footnotesPart = rel.target;
5895
5927
  this.footnotesRoot = root;
5896
5928
  this.footnotesRels = partRels;
5929
+ const separator = root.children.find(
5930
+ (item) => localName(item.name) === "footnote" && attr(item, "type") === "separator"
5931
+ );
5932
+ if (separator) {
5933
+ this.footnoteSeparator.push(...parseBlocks(separator, { ...this.ctxBase, rels: partRels }));
5934
+ }
5897
5935
  }
5898
5936
  const notes = parseNotesPart(root, { ...this.ctxBase, rels: partRels }, isFn);
5899
5937
  for (const [id, blocks] of notes) (isFn ? this.footnotes : this.endnotes).set(id, blocks);
@@ -5908,6 +5946,37 @@ var DocxDocument = class _DocxDocument {
5908
5946
  this._packageResourceVersion++;
5909
5947
  this._layoutGlobalSig = null;
5910
5948
  }
5949
+ /** Resolve content controls mapped to standard package core properties.
5950
+ * Word refreshes these bindings on open, so the serialized sdtContent can
5951
+ * be stale even though the visible value comes from docProps/core.xml. */
5952
+ hydrateCorePropertyControls(root, coreProperties) {
5953
+ if (!coreProperties) return;
5954
+ const textNodes = (element) => {
5955
+ const out = localName(element.name) === "t" ? [element] : [];
5956
+ for (const item of element.children) out.push(...textNodes(item));
5957
+ return out;
5958
+ };
5959
+ const walk2 = (element) => {
5960
+ if (localName(element.name) === "sdt") {
5961
+ const binding = child(child(element, "sdtPr"), "dataBinding");
5962
+ const xpath = attr(binding, "xpath") ?? "";
5963
+ const propertyMatch = /\/(?:[^/:]+:)?([A-Za-z_][\w.-]*)(?:\[\d+\])?\s*$/.exec(xpath);
5964
+ if (xpath.includes("coreProperties") && propertyMatch) {
5965
+ const property = coreProperties.children.find(
5966
+ (item) => localName(item.name) === propertyMatch[1]
5967
+ );
5968
+ const content = child(element, "sdtContent");
5969
+ const targets = content ? textNodes(content) : [];
5970
+ if (property?.text && targets.length > 0) {
5971
+ targets[0].text = property.text;
5972
+ for (const target of targets.slice(1)) target.text = "";
5973
+ }
5974
+ }
5975
+ }
5976
+ for (const item of element.children) walk2(item);
5977
+ };
5978
+ walk2(root);
5979
+ }
5911
5980
  /** Repair only objects emitted by older WordInWeb builds that Word rejects. */
5912
5981
  repairLegacyWordInWebObjects() {
5913
5982
  const raw = this.pkg.raw();
@@ -7989,7 +8058,7 @@ var BAKED_FONT_METRICS = {
7989
8058
  // ../core/src/layout/measure.ts
7990
8059
  function fontKey(font) {
7991
8060
  const paint = font.paintFamily && font.paintFamily !== font.family ? `${font.paintFamily}>` : "";
7992
- return `${font.bold ? "bold " : ""}${font.italic ? "italic " : ""}${font.size}px ${paint}${font.family}`;
8061
+ return `${font.bold ? "bold " : ""}${font.italic ? "italic " : ""}${font.kerning ? "kern " : ""}${font.size}px ${paint}${font.family}`;
7993
8062
  }
7994
8063
  var METRIC_SUBSTITUTES = {
7995
8064
  calibri: "Carlito",
@@ -8092,7 +8161,10 @@ var WORD_FONT_METRICS = {
8092
8161
  // baselines 118.80 + n x 20.00; first attempt baked the multiple into the
8093
8162
  // profile and over-pitched every line by 1.08x).
8094
8163
  mangal: { asc: 1.245, desc: 0.4399, gap: 0 },
8095
- latha: { asc: 0.9266, desc: 0.3369, gap: 0 }
8164
+ latha: { asc: 0.9266, desc: 0.3369, gap: 0 },
8165
+ // Word substitutes Lao text to DokChampa. Its hhea box is 2850/-1117 at
8166
+ // 2048 UPEM; this is also the line profile Word uses in the reference PDF.
8167
+ "noto sans lao looped": { asc: 1.391602, desc: 0.54541, gap: 0 }
8096
8168
  };
8097
8169
  var QUARTER_PT_PX = 1 / 3;
8098
8170
  function quantizeQuarterPt(px) {
@@ -8131,6 +8203,23 @@ function hasCambriaMath() {
8131
8203
  }
8132
8204
  return _cambriaMath;
8133
8205
  }
8206
+ var _segoeUIEmoji;
8207
+ function hasSegoeUIEmoji() {
8208
+ if (_segoeUIEmoji !== void 0) return _segoeUIEmoji;
8209
+ try {
8210
+ const canvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(1, 1) : typeof document !== "undefined" ? document.createElement("canvas") : null;
8211
+ const ctx = canvas?.getContext("2d");
8212
+ if (!ctx) return _segoeUIEmoji = false;
8213
+ ctx.font = '48px "Segoe UI Emoji", sans-serif';
8214
+ const requested = ctx.measureText("\u263A");
8215
+ ctx.font = "48px sans-serif";
8216
+ const fallback = ctx.measureText("\u263A");
8217
+ _segoeUIEmoji = Math.abs(requested.width - fallback.width) > 0.01 || Math.abs((requested.actualBoundingBoxRight ?? 0) - (fallback.actualBoundingBoxRight ?? 0)) > 0.01 || Math.abs((requested.actualBoundingBoxAscent ?? 0) - (fallback.actualBoundingBoxAscent ?? 0)) > 0.01;
8218
+ } catch {
8219
+ _segoeUIEmoji = false;
8220
+ }
8221
+ return _segoeUIEmoji;
8222
+ }
8134
8223
  function cambriaMathDescentShare() {
8135
8224
  return 0.238;
8136
8225
  }
@@ -8151,11 +8240,12 @@ var CanvasMeasurer = class {
8151
8240
  }
8152
8241
  setFont(font) {
8153
8242
  const css = cssFont(font);
8154
- if (css !== this.currentFont) {
8243
+ const key = `${css}\0${font.kerning ? "normal" : "none"}`;
8244
+ if (key !== this.currentFont) {
8155
8245
  this.ctx.font = css;
8156
- this.ctx.fontKerning = "none";
8246
+ this.ctx.fontKerning = font.kerning ? "normal" : "none";
8157
8247
  this.ctx.textRendering = "optimizeSpeed";
8158
- this.currentFont = css;
8248
+ this.currentFont = key;
8159
8249
  }
8160
8250
  }
8161
8251
  width(text, font, letterSpacing = 0) {
@@ -8259,6 +8349,8 @@ function createMeasurer() {
8259
8349
  // ../core/src/layout/math.ts
8260
8350
  var SCRIPT_SCALE = 0.73;
8261
8351
  var SCRIPT_SCRIPT_SCALE = 0.6;
8352
+ var EMPTY_SCRIPT_ADVANCE = 0.89;
8353
+ var EMPTY_SUP_ADVANCE = 0.84;
8262
8354
  var floorHalfPt = (px) => Math.floor(px * 0.75 * 2 + 1e-6) / 2 / 0.75;
8263
8355
  var scriptSize = (size, floor) => Math.max(floorHalfPt(size * SCRIPT_SCALE), floor);
8264
8356
  var SUP_RAISE = 4 / 11;
@@ -8270,6 +8362,7 @@ var RULE_THICK = 0.75 / 11;
8270
8362
  var BIN_OP_SPACE = 0.25;
8271
8363
  var FUNC_NAME_SPACE = 0.18;
8272
8364
  var COMMA_SPACE = 0.17;
8365
+ var SEMICOLON_SPACE = 0.153;
8273
8366
  var FRAC_PAD = 0.06;
8274
8367
  var NARY_SUM_RAISE = 0.5 / 11;
8275
8368
  var NARY_INT_DROP = 0.5 / 11;
@@ -8288,10 +8381,11 @@ var MAT_COL_GAP_STIX = 12.2 / 11;
8288
8381
  var MAT_COL_GAP_REAL_INLINE = 11.4 / 11;
8289
8382
  var MAT_COL_GAP_REAL_DISPLAY = 11.13 / 11;
8290
8383
  var DLM_PAD_STIX = 1.2 / 11;
8291
- var DLM_PAD_REAL = 0.8 / 11;
8384
+ var DLM_PAD_REAL = 1 / 11;
8292
8385
  var matColGap = (display) => hasCambriaMath() ? display ? MAT_COL_GAP_REAL_DISPLAY : MAT_COL_GAP_REAL_INLINE : MAT_COL_GAP_STIX;
8293
8386
  var dlmPad = () => hasCambriaMath() ? DLM_PAD_REAL : DLM_PAD_STIX;
8294
8387
  var DLM_MAT_END_PAD = 0.3 / 11;
8388
+ var DLM_TRAILING_SPACE_PAD = 0.125;
8295
8389
  var FRAC_NUM_RAISE_D = 1550 / 2048;
8296
8390
  var FRAC_DEN_DROP_D = 1370 / 2048;
8297
8391
  var FRAC_DEN_DLM_EXTRA = 133 / 2048;
@@ -8305,6 +8399,8 @@ var INT_D_INK = 1.3584 + 0.7925;
8305
8399
  var INT_D_ASC = 1.3584;
8306
8400
  var INT_D_DESC = 0.7925;
8307
8401
  var INT_D_ADV = 0.8066;
8402
+ var INT_D_LIMIT_OVERLAP = 0.37;
8403
+ var INT_D_SUP_STAGGER = 0.375;
8308
8404
  var INT_D_ANCHOR = (1.3584 - 0.877 * (INT_D_INK / INT_TEXT_INK)) / (1 - INT_D_INK / INT_TEXT_INK);
8309
8405
  var MATH_FONT = "Cambria Math";
8310
8406
  function mathText(text, normal = false) {
@@ -8382,7 +8478,7 @@ var BRACKET_RE = /[()[\]{}]/;
8382
8478
  var SUP_DLM_DROP = 460 / 2048;
8383
8479
  var ITALIC_MATH_RE = /[\u{1D434}-\u{1D467}ℎ]/u;
8384
8480
  var SUP_KERN_IN = 0;
8385
- var SCRIPT_TRAIL = 0;
8481
+ var SCRIPT_TRAIL_GROUPED_DISPLAY = 0.12;
8386
8482
  function containsDlm(nodes) {
8387
8483
  for (const n of nodes) {
8388
8484
  switch (n.t) {
@@ -8467,9 +8563,21 @@ function containsBlocky(nodes) {
8467
8563
  }
8468
8564
  return false;
8469
8565
  }
8470
- function layoutMath(nodes, baseSize, measurer, display = false) {
8566
+ function layoutMath(nodes, baseSize, measurer, display = false, fractionPadEm = FRAC_PAD, multiEquationDisplay = false) {
8471
8567
  const box = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [], display, baseSize };
8472
- flow(nodes, baseSize, 0, box, measurer, false, display, 0, floorHalfPt(baseSize * SCRIPT_SCRIPT_SCALE));
8568
+ flow(
8569
+ nodes,
8570
+ baseSize,
8571
+ 0,
8572
+ box,
8573
+ measurer,
8574
+ false,
8575
+ display,
8576
+ 0,
8577
+ floorHalfPt(baseSize * SCRIPT_SCRIPT_SCALE),
8578
+ fractionPadEm,
8579
+ multiEquationDisplay ? SCRIPT_TRAIL_GROUPED_DISPLAY : 0
8580
+ );
8473
8581
  for (const p of box.pieces) {
8474
8582
  const m = measurer.metrics(p.font);
8475
8583
  p.effAscent = p.ownAscent ?? p.dy + m.ascent;
@@ -8483,12 +8591,17 @@ function layoutMath(nodes, baseSize, measurer, display = false) {
8483
8591
  }
8484
8592
  return box;
8485
8593
  }
8486
- function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth = 0, scriptFloor = 0) {
8594
+ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth = 0, scriptFloor = 0, fractionPadEm = FRAC_PAD, scriptTrailEm = 0) {
8487
8595
  let prevOperand = false;
8488
8596
  for (const node of nodes) {
8489
8597
  switch (node.t) {
8490
8598
  case "run": {
8491
8599
  const font = fontAt(size);
8600
+ if (node.normal && /^\s+$/.test(node.text)) {
8601
+ box.pieces.push({ text: node.text, x: box.width, dy, font });
8602
+ box.width += [...node.text].length * size;
8603
+ break;
8604
+ }
8492
8605
  if (node.fname) {
8493
8606
  const gap = size * FUNC_NAME_SPACE;
8494
8607
  if (prevOperand) box.width += gap;
@@ -8500,16 +8613,19 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8500
8613
  }
8501
8614
  const medGap = size * BIN_OP_SPACE;
8502
8615
  const relGap = display ? medGap : size * (5 / 18);
8503
- const toks = node.text.split(/([=+−×÷<>≤≥±≠,-])/).filter((s) => s.length > 0);
8616
+ const toks = node.text.split(/([=+−×÷<>≤≥±≠,;\-])/).filter((s) => s.length > 0);
8504
8617
  for (let ti = 0; ti < toks.length; ti++) {
8505
8618
  const tok = toks[ti];
8506
- if (tok === ",") {
8619
+ if (tok === "," || tok === ";") {
8507
8620
  const text2 = mathText(tok, node.normal);
8508
8621
  box.pieces.push({ text: text2, x: box.width, dy, font });
8509
8622
  box.width += measurer.width(text2, font);
8510
8623
  const nextIsSpace = ti + 1 < toks.length && /^\s/.test(toks[ti + 1]);
8511
- if (!tight && !nextIsSpace) box.width += size * COMMA_SPACE;
8512
- prevOperand = false;
8624
+ const spacedSemicolon = tok === ";" && (ti === toks.length - 1 || !BIN_OPS.has(toks[ti + 1]));
8625
+ if (!tight && !nextIsSpace && (tok === "," || spacedSemicolon)) {
8626
+ box.width += size * (tok === "," ? COMMA_SPACE : SEMICOLON_SPACE);
8627
+ }
8628
+ prevOperand = tok === ";";
8513
8629
  continue;
8514
8630
  }
8515
8631
  const isOp = BIN_OPS.has(tok);
@@ -8533,7 +8649,7 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8533
8649
  case "sup":
8534
8650
  case "sub": {
8535
8651
  const beforeBase = box.pieces.length;
8536
- flow(node.base, size, dy, box, measurer, tight, display, breakDepth + 2, scriptFloor);
8652
+ flow(node.base, size, dy, box, measurer, tight, display, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8537
8653
  let baseInkAsc = 0;
8538
8654
  if (node.t === "sup") {
8539
8655
  for (let i = beforeBase; i < box.pieces.length; i++) {
@@ -8548,9 +8664,16 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8548
8664
  box.width += size * SUP_KERN_IN;
8549
8665
  }
8550
8666
  const beforeScript = box.pieces.length;
8551
- flow(node.script, scriptSize(size, scriptFloor), scriptDy, box, measurer, true, false, breakDepth + 2, scriptFloor);
8667
+ const scriptFontSize = scriptSize(size, scriptFloor);
8668
+ if (node.t === "sub" && node.script.length === 0) {
8669
+ box.width += scriptFontSize * EMPTY_SCRIPT_ADVANCE;
8670
+ } else if (node.t === "sup" && node.script.length === 0) {
8671
+ box.width += scriptFontSize * EMPTY_SUP_ADVANCE;
8672
+ } else {
8673
+ flow(node.script, scriptFontSize, scriptDy, box, measurer, true, false, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8674
+ }
8552
8675
  for (let i = beforeScript; i < box.pieces.length; i++) box.pieces[i].script = true;
8553
- box.width += size * SCRIPT_TRAIL;
8676
+ if (display) box.width += size * scriptTrailEm;
8554
8677
  prevOperand = true;
8555
8678
  break;
8556
8679
  }
@@ -8559,17 +8682,17 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8559
8682
  const numRaise = display ? size * FRAC_NUM_RAISE_D : size * FRAC_NUM_RAISE;
8560
8683
  const denDrop = (display ? size * FRAC_DEN_DROP_D : size * FRAC_DEN_DROP) + (display && containsDlm(node.den) ? size * FRAC_DEN_DLM_EXTRA : 0);
8561
8684
  const fracTight = display ? tight : true;
8562
- const numW = widthOf(node.num, scale, measurer, display, fracTight, scriptFloor);
8563
- const denW = widthOf(node.den, scale, measurer, display, fracTight, scriptFloor);
8564
- const pad = size * FRAC_PAD;
8685
+ const numW = widthOf(node.num, scale, measurer, display, fracTight, scriptFloor, fractionPadEm, scriptTrailEm);
8686
+ const denW = widthOf(node.den, scale, measurer, display, fracTight, scriptFloor, fractionPadEm, scriptTrailEm);
8687
+ const pad = size * fractionPadEm;
8565
8688
  const barW = Math.max(numW, denW) + 2 * pad;
8566
8689
  const x0 = box.width;
8567
8690
  const numBox = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
8568
- flow(node.num, scale, 0, numBox, measurer, fracTight, display, breakDepth + 2, scriptFloor);
8691
+ flow(node.num, scale, 0, numBox, measurer, fracTight, display, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8569
8692
  for (const p of numBox.pieces) box.pieces.push(rebase(p, x0 + (barW - numW) / 2, dy + numRaise));
8570
8693
  for (const r of numBox.rules) box.rules.push({ ...r, x1: x0 + (barW - numW) / 2 + r.x1, x2: x0 + (barW - numW) / 2 + r.x2, dy: dy + numRaise + r.dy });
8571
8694
  const denBox = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
8572
- flow(node.den, scale, 0, denBox, measurer, fracTight, display, breakDepth + 2, scriptFloor);
8695
+ flow(node.den, scale, 0, denBox, measurer, fracTight, display, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8573
8696
  let denDropFinal = denDrop;
8574
8697
  if (containsFrac(node.den)) {
8575
8698
  let denAsc = 0;
@@ -8594,7 +8717,7 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8594
8717
  case "nary": {
8595
8718
  const isInt = "\u222B\u222C\u222D\u222E\u222F\u2230".includes(node.chr);
8596
8719
  if (display && !isInt) {
8597
- naryDisplay(node, size, dy, box, measurer, scriptFloor);
8720
+ naryDisplay(node, size, dy, box, measurer, scriptFloor, fractionPadEm, scriptTrailEm);
8598
8721
  prevOperand = true;
8599
8722
  break;
8600
8723
  }
@@ -8623,20 +8746,24 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8623
8746
  const intSubDrop = display && visibleSub ? INT_SUB_DROP_D : INT_SUB_DROP;
8624
8747
  const supDy = dy + size * (isInt ? intSupRaise : NARY_SUP_RAISE);
8625
8748
  const subDy = dy - size * (isInt ? intSubDrop : NARY_SUB_DROP);
8626
- const supStagger = isInt ? size * INT_SUP_STAGGER : 0;
8627
- const supW = widthOf(node.sup, scale, measurer, false, true, scriptFloor) + supStagger;
8628
- const subW = widthOf(node.sub, scale, measurer, false, true, scriptFloor);
8629
- const x0 = box.width;
8749
+ const supStagger = isInt ? size * (display ? INT_D_SUP_STAGGER : INT_SUP_STAGGER) : 0;
8750
+ const supW = visibleSup ? widthOf(node.sup, scale, measurer, false, true, scriptFloor, fractionPadEm, scriptTrailEm) + supStagger : 0;
8751
+ const subW = visibleSub ? widthOf(node.sub, scale, measurer, false, true, scriptFloor, fractionPadEm, scriptTrailEm) : 0;
8752
+ const x0 = box.width - (display && isInt && (visibleSup || visibleSub) ? size * INT_D_LIMIT_OVERLAP : 0);
8630
8753
  const supBox = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
8631
- flow(node.sup, scale, 0, supBox, measurer, true, false, breakDepth + 2, scriptFloor);
8632
- for (const pc of supBox.pieces) box.pieces.push(rebase(pc, x0 + supStagger, supDy, true));
8633
- for (const r of supBox.rules) box.rules.push({ ...r, x1: x0 + supStagger + r.x1, x2: x0 + supStagger + r.x2, dy: supDy + r.dy });
8754
+ if (visibleSup) {
8755
+ flow(node.sup, scale, 0, supBox, measurer, true, false, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8756
+ for (const pc of supBox.pieces) box.pieces.push(rebase(pc, x0 + supStagger, supDy, true));
8757
+ for (const r of supBox.rules) box.rules.push({ ...r, x1: x0 + supStagger + r.x1, x2: x0 + supStagger + r.x2, dy: supDy + r.dy });
8758
+ }
8634
8759
  const subBox = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
8635
- flow(node.sub, scale, 0, subBox, measurer, true, false, breakDepth + 2, scriptFloor);
8636
- for (const pc of subBox.pieces) box.pieces.push(rebase(pc, x0, subDy, true));
8637
- for (const r of subBox.rules) box.rules.push({ ...r, x1: x0 + r.x1, x2: x0 + r.x2, dy: subDy + r.dy });
8760
+ if (visibleSub) {
8761
+ flow(node.sub, scale, 0, subBox, measurer, true, false, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8762
+ for (const pc of subBox.pieces) box.pieces.push(rebase(pc, x0, subDy, true));
8763
+ for (const r of subBox.rules) box.rules.push({ ...r, x1: x0 + r.x1, x2: x0 + r.x2, dy: subDy + r.dy });
8764
+ }
8638
8765
  box.width = x0 + Math.max(supW, subW) + size * NARY_E_GAP;
8639
- flow(node.e, size, dy, box, measurer, tight, display, breakDepth + 2, scriptFloor);
8766
+ flow(node.e, size, dy, box, measurer, tight, display, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8640
8767
  prevOperand = true;
8641
8768
  break;
8642
8769
  }
@@ -8646,7 +8773,7 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8646
8773
  let coreDesc = 0;
8647
8774
  const parts = node.e.map((part) => {
8648
8775
  const b = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
8649
- flow(part, size, 0, b, measurer, tight, display, breakDepth + 1, scriptFloor);
8776
+ flow(part, size, 0, b, measurer, tight, display, breakDepth + 1, scriptFloor, fractionPadEm, scriptTrailEm);
8650
8777
  for (const pc of b.pieces) {
8651
8778
  const m = measurer.metrics(pc.font);
8652
8779
  const asc = pc.ownAscent ?? pc.dy + m.ascent;
@@ -8702,7 +8829,10 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8702
8829
  grow = 1;
8703
8830
  }
8704
8831
  }
8705
- const innerPad = size * (matLike ? DLM_MAT_END_PAD : dlmPad());
8832
+ const lastPart = node.e[node.e.length - 1];
8833
+ const tail = lastPart?.[lastPart.length - 1];
8834
+ const trailingSpaces = display && tail?.t === "run" && /^\s+$/.test(tail.text) ? [...tail.text].length : 0;
8835
+ const innerPad = size * (matLike ? DLM_MAT_END_PAD : dlmPad()) + size * DLM_TRAILING_SPACE_PAD * trailingSpaces;
8706
8836
  const drawDelim = (ch) => {
8707
8837
  if (!ch) return;
8708
8838
  const asm = asmH !== void 0 ? DLM_ASM_PIECES[ch] : void 0;
@@ -8772,7 +8902,7 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8772
8902
  const cells = node.rows.map(
8773
8903
  (row) => row.map((cell) => {
8774
8904
  const b = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
8775
- flow(cell, size, 0, b, measurer, tight, display, breakDepth + 2, scriptFloor);
8905
+ flow(cell, size, 0, b, measurer, tight, display, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8776
8906
  for (const pc of b.pieces) {
8777
8907
  const m = measurer.metrics(pc.font);
8778
8908
  b.ascent = Math.max(b.ascent, pc.dy + m.ascent);
@@ -8818,7 +8948,7 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8818
8948
  const m = measurer.metrics(font);
8819
8949
  const ruleThick = Math.max(size * RULE_THICK, 0.75);
8820
8950
  const radBox = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
8821
- flow(node.e, size, 0, radBox, measurer, tight, display, breakDepth + 2, scriptFloor);
8951
+ flow(node.e, size, 0, radBox, measurer, tight, display, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8822
8952
  let radAsc = m.ascent * 0.5;
8823
8953
  let radDesc = 0;
8824
8954
  for (const pc of radBox.pieces) {
@@ -8842,7 +8972,7 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8842
8972
  const dScale = scriptSize(size, scriptFloor);
8843
8973
  box.width += size * RAD_KERN_BEFORE_DEG;
8844
8974
  const degBox = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
8845
- flow(node.deg, dScale, 0, degBox, measurer, true, false, breakDepth + 2, scriptFloor);
8975
+ flow(node.deg, dScale, 0, degBox, measurer, true, false, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8846
8976
  const degDy = dy - radDesc + RAD_DEG_RAISE * signInkH + size * RAD_DEG_ADJ;
8847
8977
  for (const pc of degBox.pieces) box.pieces.push(rebase(pc, box.width, degDy, true));
8848
8978
  for (const r of degBox.rules) box.rules.push({ ...r, x1: box.width + r.x1, x2: box.width + r.x2, dy: degDy + r.dy });
@@ -8889,7 +9019,7 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8889
9019
  const axis = dy + size * RULE_CENTER;
8890
9020
  const rowBoxes = node.rows.map((row) => {
8891
9021
  const b = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
8892
- flow(row, size, 0, b, measurer, tight, display, breakDepth + 2, scriptFloor);
9022
+ flow(row, size, 0, b, measurer, tight, display, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8893
9023
  for (const pc of b.pieces) {
8894
9024
  const pm = measurer.metrics(pc.font);
8895
9025
  b.ascent = Math.max(b.ascent, pc.ownAscent ?? pc.dy + pm.ascent);
@@ -8919,7 +9049,7 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8919
9049
  }
8920
9050
  case "acc": {
8921
9051
  const before = box.pieces.length;
8922
- flow(node.e, size, dy, box, measurer, tight, display, breakDepth + 2, scriptFloor);
9052
+ flow(node.e, size, dy, box, measurer, tight, display, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8923
9053
  if (box.pieces.length > before) {
8924
9054
  const last = box.pieces[box.pieces.length - 1];
8925
9055
  last.text += node.chr;
@@ -8931,7 +9061,7 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8931
9061
  const isTop = node.pos === "top";
8932
9062
  const bScale = isTop ? size : scriptSize(size, scriptFloor);
8933
9063
  const baseBox = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
8934
- flow(node.e, bScale, 0, baseBox, measurer, tight, display, breakDepth + 2, scriptFloor);
9064
+ flow(node.e, bScale, 0, baseBox, measurer, tight, display, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8935
9065
  const baseW = baseBox.width;
8936
9066
  const maxVariant = GRP_VARIANTS_EM[GRP_VARIANTS_EM.length - 1] * size;
8937
9067
  let braceW = maxVariant;
@@ -8968,10 +9098,10 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8968
9098
  case "lim": {
8969
9099
  const isLow = node.pos === "low";
8970
9100
  const baseBox = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
8971
- flow(node.e, size, 0, baseBox, measurer, tight, display, breakDepth + 2, scriptFloor);
9101
+ flow(node.e, size, 0, baseBox, measurer, tight, display, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8972
9102
  const lScale = scriptSize(size, scriptFloor);
8973
9103
  const limBox = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
8974
- flow(node.lim, lScale, 0, limBox, measurer, true, false, breakDepth + 2, scriptFloor);
9104
+ flow(node.lim, lScale, 0, limBox, measurer, true, false, breakDepth + 2, scriptFloor, fractionPadEm, scriptTrailEm);
8975
9105
  const w = Math.max(baseBox.width, limBox.width);
8976
9106
  const x0 = box.width;
8977
9107
  const baseX = x0 + (w - baseBox.width) / 2;
@@ -8988,19 +9118,19 @@ function flow(nodes, size, dy, box, measurer, tight, display = false, breakDepth
8988
9118
  }
8989
9119
  }
8990
9120
  }
8991
- function naryDisplay(node, size, dy, box, measurer, scriptFloor) {
9121
+ function naryDisplay(node, size, dy, box, measurer, scriptFloor, fractionPadEm, scriptTrailEm) {
8992
9122
  const opFont = fontAt(size * NARY_OP_SCALE_D);
8993
9123
  const opW = measurer.width(node.chr, opFont);
8994
9124
  const scale = scriptSize(size, scriptFloor);
8995
- const supW = widthOf(node.sup, scale, measurer, false, true, scriptFloor);
8996
- const subW = widthOf(node.sub, scale, measurer, false, true, scriptFloor);
9125
+ const supW = widthOf(node.sup, scale, measurer, false, true, scriptFloor, fractionPadEm, scriptTrailEm);
9126
+ const subW = widthOf(node.sub, scale, measurer, false, true, scriptFloor, fractionPadEm, scriptTrailEm);
8997
9127
  const stackW = Math.max(opW, supW, subW);
8998
9128
  const x0 = box.width;
8999
9129
  const cx = x0 + stackW / 2;
9000
9130
  box.pieces.push({ text: node.chr, x: cx - opW / 2, dy, font: opFont });
9001
9131
  if (node.sup.length) {
9002
9132
  const supBox = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
9003
- flow(node.sup, scale, 0, supBox, measurer, true, false, 2, scriptFloor);
9133
+ flow(node.sup, scale, 0, supBox, measurer, true, false, 2, scriptFloor, fractionPadEm, scriptTrailEm);
9004
9134
  const ox = cx - supW / 2;
9005
9135
  const oy = dy + size * NARY_OVER_RAISE_D;
9006
9136
  for (const pc of supBox.pieces) box.pieces.push(rebase(pc, ox, oy, true));
@@ -9008,18 +9138,18 @@ function naryDisplay(node, size, dy, box, measurer, scriptFloor) {
9008
9138
  }
9009
9139
  if (node.sub.length) {
9010
9140
  const subBox = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
9011
- flow(node.sub, scale, 0, subBox, measurer, true, false, 2, scriptFloor);
9141
+ flow(node.sub, scale, 0, subBox, measurer, true, false, 2, scriptFloor, fractionPadEm, scriptTrailEm);
9012
9142
  const ux = cx - subW / 2;
9013
9143
  const uy = dy - size * NARY_UNDER_DROP_D;
9014
9144
  for (const pc of subBox.pieces) box.pieces.push(rebase(pc, ux, uy, true));
9015
9145
  for (const r of subBox.rules) box.rules.push({ ...r, x1: ux + r.x1, x2: ux + r.x2, dy: uy + r.dy });
9016
9146
  }
9017
9147
  box.width = x0 + stackW + size * NARY_E_GAP;
9018
- flow(node.e, size, dy, box, measurer, false, true, 2, scriptFloor);
9148
+ flow(node.e, size, dy, box, measurer, false, true, 2, scriptFloor, fractionPadEm, scriptTrailEm);
9019
9149
  }
9020
- function widthOf(nodes, size, measurer, display = false, tight = true, scriptFloor = 0) {
9150
+ function widthOf(nodes, size, measurer, display = false, tight = true, scriptFloor = 0, fractionPadEm = FRAC_PAD, scriptTrailEm = 0) {
9021
9151
  const tmp = { width: 0, ascent: 0, descent: 0, pieces: [], rules: [] };
9022
- flow(nodes, size, 0, tmp, measurer, tight, display, 2, scriptFloor);
9152
+ flow(nodes, size, 0, tmp, measurer, tight, display, 2, scriptFloor, fractionPadEm, scriptTrailEm);
9023
9153
  return tmp.width;
9024
9154
  }
9025
9155
 
@@ -9047,16 +9177,18 @@ function starNumberFormat(instr, n) {
9047
9177
  }
9048
9178
  }
9049
9179
  function fontOf(props, fallbackFamily) {
9180
+ const family = props.rtl && props.fontComplex || props.font || fallbackFamily;
9050
9181
  let size = props.size ?? 14.666;
9051
9182
  if (props.verticalAlign === "superscript" || props.verticalAlign === "subscript") {
9052
- size = Math.round(size * 1.5 * 0.65) / 1.5;
9183
+ const scale = EA_FAMILY_RE.test(family) ? 0.52 : 0.65;
9184
+ size = Math.round(size * 1.5 * scale) / 1.5;
9053
9185
  }
9054
- const family = props.rtl && props.fontComplex || props.font || fallbackFamily;
9055
9186
  return {
9056
9187
  family,
9057
9188
  size,
9058
9189
  bold: props.bold ?? false,
9059
- italic: props.italic ?? false
9190
+ italic: props.italic ?? false,
9191
+ kerning: props.kerningThreshold !== void 0 && props.kerningThreshold !== null && size >= props.kerningThreshold
9060
9192
  };
9061
9193
  }
9062
9194
  function displayText(text, props) {
@@ -9071,13 +9203,21 @@ function runPlainText(run) {
9071
9203
  function isWordAlnum(ch) {
9072
9204
  return ch !== void 0 && /[\p{L}\p{Nd}]/u.test(ch);
9073
9205
  }
9074
- function hyphenBreaks(word) {
9206
+ function hyphenBreaks(word, allowNumericEnDash) {
9075
9207
  const out = [];
9208
+ let leading = 0;
9209
+ while (word[leading] === "-" || word[leading] === "\u2010") leading++;
9210
+ if (leading >= 2 && isWordAlnum(word[leading])) out.push(leading);
9076
9211
  for (let i = 1; i < word.length - 1; i++) {
9077
9212
  const ch = word[i];
9078
- if ((ch === "-" || ch === "\u2010") && isWordAlnum(word[i - 1]) && isWordAlnum(word[i + 1])) {
9079
- out.push(i + 1);
9080
- }
9213
+ if (ch !== "-" && ch !== "\u2010" && ch !== "\u2013") continue;
9214
+ let end = i + 1;
9215
+ while (word[end] === "-" || word[end] === "\u2010" || word[end] === "\u2013") end++;
9216
+ const wordBefore = isWordAlnum(word[i - 1]) || /^[\)\]\}]$/.test(word[i - 1] ?? "") && isWordAlnum(word[i - 2]);
9217
+ const wordAfter = isWordAlnum(word[end]);
9218
+ const numericEnDash = allowNumericEnDash && ch === "\u2013" && /\p{Nd}/u.test(word[i - 1] ?? "") && /\p{Nd}/u.test(word[end] ?? "");
9219
+ if (ch === "\u2013" ? numericEnDash : wordBefore && wordAfter) out.push(end);
9220
+ i = end - 1;
9081
9221
  }
9082
9222
  return out;
9083
9223
  }
@@ -9092,6 +9232,7 @@ function isCJK(ch) {
9092
9232
  return CJK_RE.test(ch);
9093
9233
  }
9094
9234
  var BALLOT_RE = /[☐☑☒]/;
9235
+ var LEGACY_CHECKBOX_TRAILING_ADVANCE = 2.5 * 96 / 72;
9095
9236
  function isBallot(ch) {
9096
9237
  return BALLOT_RE.test(ch);
9097
9238
  }
@@ -9177,16 +9318,35 @@ function wrapDisplayMath(box, maxWidth) {
9177
9318
  });
9178
9319
  }
9179
9320
  var DEFAULT_TAB = 48;
9321
+ var TABBED_INLINE_MATH_FRAC_PAD = 0.15;
9180
9322
  var OVERFLOW_PUNCT = /[.,:;!?)\]}、。,.:;?!)]}」』】〉》]/;
9181
- var CJK_SPACE_COMPRESS = 0.47;
9323
+ var CJK_SPACE_COMPRESS = 0.5;
9182
9324
  var EA_FAMILY_RE = /simsun|nsimsun|宋体|新宋体|songti|simhei|黑体|heiti|kaiti|楷体|fangsong|仿宋|yahei|雅黑|mingliu|细明|新細明|pmingliu|jhenghei|正黑|pingfang|dengxian|等线|ms (?:p?)(?:mincho|gothic)|mincho|meiryo|yu gothic|yu mincho|batang|gulim|dotum|malgun/i;
9183
- var JUSTIFY_MAX_COMPRESS = 0.25;
9325
+ var JUSTIFY_MAX_COMPRESS = 0.29;
9326
+ var JUSTIFY_GRID_MAX_COMPRESS = 0.25;
9184
9327
  var JUSTIFY_STRETCH_FACTOR = 0.5;
9185
9328
  var TAMIL_GLYPH_SCALE = 0.735;
9186
9329
  var TAMIL_BASELINE_DY_EM = 0.136;
9330
+ var LAO_GLYPH_SCALE = 1.048;
9331
+ function emojiFallbackScale(text, family) {
9332
+ if (family.toLowerCase() !== "segoe ui emoji" || hasSegoeUIEmoji()) return 1;
9333
+ const codepoints = Array.from(text);
9334
+ if (codepoints.some((ch) => {
9335
+ const cp = ch.codePointAt(0) ?? 0;
9336
+ return cp >= 127462 && cp <= 127487;
9337
+ })) return 1;
9338
+ const people = codepoints.filter((ch) => {
9339
+ const cp = ch.codePointAt(0) ?? 0;
9340
+ return cp >= 128102 && cp <= 128105;
9341
+ }).length;
9342
+ if (text.includes("\u200D") && people >= 3) return 1.84;
9343
+ if (/🚀|❤|✅/.test(text)) return 1.39;
9344
+ if (text.includes("\u200D") || /[🏻-🏿]/u.test(text)) return 1.29;
9345
+ return 1;
9346
+ }
9187
9347
  var KASHIDA_LOW_EM = 0;
9188
9348
  var KASHIDA_MEDIUM_EM = 0.032;
9189
- var KASHIDA_HIGH_EM = 0.04;
9349
+ var KASHIDA_HIGH_EM = 0.075;
9190
9350
  var BP_CACHES = /* @__PURE__ */ new WeakMap();
9191
9351
  var BP_CACHE_MAX = 6e4;
9192
9352
  function bpCacheFor(measurer) {
@@ -9330,6 +9490,7 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9330
9490
  let curSegments = [{ x: 0, width: contentWidth }];
9331
9491
  let curSegIdx = 0;
9332
9492
  let curClearY;
9493
+ let curEdgeReserve = 0;
9333
9494
  const ls = props.lineSpacing;
9334
9495
  const firstFont = atoms.find((a) => a.kind === "frag" || a.kind === "space")?.font;
9335
9496
  const natural = firstFont ? measurer.metrics(firstFont).lineHeight * (ls?.rule === "auto" ? ls.value : 1) : 0;
@@ -9341,6 +9502,7 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9341
9502
  curSegments = [{ x: 0, width: contentWidth }];
9342
9503
  curSegIdx = 0;
9343
9504
  curClearY = void 0;
9505
+ curEdgeReserve = 0;
9344
9506
  if (boundsAt) {
9345
9507
  let guard = 0;
9346
9508
  let b = boundsAt(yOff, EST_LINE);
@@ -9354,6 +9516,7 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9354
9516
  curSegments = b.segments && b.segments.length > 0 ? b.segments : [{ x: b.x, width: b.width }];
9355
9517
  curSegIdx = 0;
9356
9518
  curClearY = b.clearY;
9519
+ curEdgeReserve = b.edgeReserve ?? (b.clearY !== void 0 ? 0.75 : 0);
9357
9520
  }
9358
9521
  void idx;
9359
9522
  };
@@ -9538,6 +9701,7 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9538
9701
  href: atom.href,
9539
9702
  src: atom.src ? { ...atom.src, offset: atom.src.offset + sliceOff } : void 0,
9540
9703
  metricsFont: atom.metricsFont,
9704
+ metricsStrut: atom.metricsStrut,
9541
9705
  rtl: atom.rtl,
9542
9706
  rtlLevel: levelOf(atom.rtl)
9543
9707
  });
@@ -9628,7 +9792,10 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9628
9792
  rawStop = nextTabStop(rawStop.pos, props.tabs, contentWidth - indentRight, doc.defaultTabStop);
9629
9793
  }
9630
9794
  }
9631
- if (hanging > 0 && x + offset < indentLeft - 0.5 && rawStop.pos > indentLeft) {
9795
+ const explicitBeforeIndent = (props.tabs ?? []).some(
9796
+ (tab) => !tab.clear && tab.align !== "bar" && tab.pos > x + offset + 0.5 && tab.pos < indentLeft - 0.5
9797
+ );
9798
+ if (hanging > 0 && x + offset < indentLeft - 0.5 && !explicitBeforeIndent) {
9632
9799
  rawStop = { pos: indentLeft, align: "left" };
9633
9800
  }
9634
9801
  const stop = { ...rawStop, pos: rawStop.pos - offset };
@@ -9661,6 +9828,45 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9661
9828
  if (found) target = stop.pos - pre;
9662
9829
  }
9663
9830
  }
9831
+ if (doc.compatibilityMode < 15 && stop.align === "left" && !bidiPara && curLineWidth > 0) {
9832
+ let nextAtom = ai + 1;
9833
+ let probeX = Math.max(target, x + 2);
9834
+ while (atoms[nextAtom]?.kind === "tab") {
9835
+ let probeStop = nextTabStop(
9836
+ probeX + offset,
9837
+ props.tabs,
9838
+ contentWidth - indentRight,
9839
+ doc.defaultTabStop
9840
+ );
9841
+ if (opts?.inTableCell) {
9842
+ let guard = 0;
9843
+ while (probeStop.align === "decimal" && guard++ < 8) {
9844
+ probeStop = nextTabStop(
9845
+ probeStop.pos,
9846
+ props.tabs,
9847
+ contentWidth - indentRight,
9848
+ doc.defaultTabStop
9849
+ );
9850
+ }
9851
+ }
9852
+ if (probeStop.align !== "left") break;
9853
+ probeX = Math.max(probeStop.pos - offset, probeX + 2);
9854
+ nextAtom++;
9855
+ }
9856
+ let firstWordWidth = 0;
9857
+ for (let j = nextAtom; j < atoms.length; j++) {
9858
+ const a = atoms[j];
9859
+ if (a.kind !== "frag") break;
9860
+ firstWordWidth += a.width;
9861
+ }
9862
+ const lineEnd2 = lineStartX(lineIndex) + availFor(lineIndex);
9863
+ if (firstWordWidth > 0 && probeX + firstWordWidth > lineEnd2 + 0.01) {
9864
+ const legacyContinuationIndent = !!atom.props.bold && nextAtom === ai + 3;
9865
+ flush(false, false);
9866
+ if (!legacyContinuationIndent) ai--;
9867
+ continue;
9868
+ }
9869
+ }
9664
9870
  const aligned = (stop.align === "right" || stop.align === "decimal") && !bidiPara;
9665
9871
  if (aligned && curLineWidth > 0 && x > target + 0.75) {
9666
9872
  flush(false, false);
@@ -9733,9 +9939,11 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9733
9939
  const rows = wrapDisplayMath(atom.box, availFor(lineIndex));
9734
9940
  for (let ri = 0; ri < rows.length; ri++) {
9735
9941
  const row = rows[ri];
9736
- cur.push({ x, width: row.width, math: row, mathSrc: atom.src, props: {}, font: fontOf({}, fallbackFamily) });
9737
- curLineWidth += row.width;
9738
- x += row.width;
9942
+ const tabReserve = !row.display && ri === rows.length - 1 && atoms[ai + 1]?.kind === "tab" ? row.width * 0.01 : 0;
9943
+ const advance = row.width + tabReserve;
9944
+ cur.push({ x, width: advance, math: row, mathSrc: atom.src, props: {}, font: fontOf({}, fallbackFamily) });
9945
+ curLineWidth += advance;
9946
+ x += advance;
9739
9947
  if (ri + 1 < rows.length) flush(false, false);
9740
9948
  }
9741
9949
  continue;
@@ -9744,7 +9952,7 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9744
9952
  const w = atom.kind === "image" ? atom.width : atom.drawing.width;
9745
9953
  const h = atom.kind === "image" ? atom.height : atom.drawing.height;
9746
9954
  if (curLineWidth > 0 && x + w > lineStartX(lineIndex) + availFor(lineIndex)) {
9747
- const glued = atom.kind === "image" && !atom.srcDrawing;
9955
+ const glued = atom.kind === "image" && (!atom.srcDrawing || localName(atom.srcDrawing.name) !== "drawing");
9748
9956
  let hi = cur.length;
9749
9957
  let headW = 0;
9750
9958
  while (glued && hi > minSpans) {
@@ -9793,11 +10001,12 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9793
10001
  if (opts?.verticalGridResync && fragIsCJK && prevContentCJK === false && curLineWidth > 0 && curClearY === void 0) {
9794
10002
  flush(false, false);
9795
10003
  }
9796
- const lineEnd = lineStartX(lineIndex) + availFor(lineIndex);
10004
+ const lineEnd = lineStartX(lineIndex) + availFor(lineIndex) - curEdgeReserve;
9797
10005
  const bdrPad = bdrPadOf(atom);
9798
10006
  const bdrPadL = bdrPad && (!sameBdrAtom(atoms[ai - 1], atom) || curLineWidth === 0) ? bdrPad : 0;
9799
10007
  const bdrPadR = bdrPad && !sameBdrAtom(atom, atoms[ai + 1]) ? bdrPad : 0;
9800
- let fits = x + bdrPadL + atom.width + bdrPadR <= lineEnd + 0.01;
10008
+ const fitTolerance = minLineHeight !== void 0 ? 0.125 : 0.01;
10009
+ let fits = x + bdrPadL + atom.width + bdrPadR <= lineEnd + fitTolerance;
9801
10010
  if (!fits && packUntilSpace) fits = true;
9802
10011
  if (!fits && cur.length > 0 && cur[cur.length - 1].text === " ") {
9803
10012
  if (bidiPara && cur[cur.length - 1].x <= lineStartX(lineIndex) + 0.5) {
@@ -9807,14 +10016,15 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9807
10016
  fits = true;
9808
10017
  packUntilSpace = true;
9809
10018
  }
9810
- if (!fits && minLineHeight) {
10019
+ let overflowPunctWidth = 0;
10020
+ if (minLineHeight) {
9811
10021
  const last = atom.text[atom.text.length - 1];
9812
10022
  if (last && OVERFLOW_PUNCT.test(last)) {
9813
10023
  const nxt = atoms[ai + 1];
9814
10024
  const wordEnd = !nxt || nxt.kind !== "frag" || /^\s/.test(nxt.text);
9815
10025
  if (wordEnd) {
9816
- const pw = measurer.width(last, atom.font, atom.props.letterSpacing);
9817
- if (x + atom.width - pw <= lineEnd + 0.01) fits = true;
10026
+ overflowPunctWidth = measurer.width(last, atom.font, atom.props.letterSpacing);
10027
+ if (!fits && x + atom.width - overflowPunctWidth <= lineEnd + 0.01) fits = true;
9818
10028
  }
9819
10029
  }
9820
10030
  }
@@ -9843,8 +10053,14 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9843
10053
  }
9844
10054
  }
9845
10055
  const wordW = headW + bdrPadL + atom.width + bdrPadR + tailW;
9846
- if (!fits && minLineHeight !== void 0 && curEaSpaceWidth > 0) {
9847
- const compress = (x - headW + wordW - lineEnd) / curEaSpaceWidth;
10056
+ const previousInk = [...cur].reverse().find((span) => span.text?.trim());
10057
+ const sentenceBoundary = /[.!?][\)\]}'"]?$/.test(previousInk?.text ?? "");
10058
+ const clauseBoundaryTail = /[,;:]\s+(?:and|or|but)\s+the\s*$/i.test(
10059
+ cur.map((span) => span.text ?? "").join("")
10060
+ );
10061
+ if (!fits && minLineHeight !== void 0 && curEaSpaceWidth > 0 && !curPacked && !sentenceBoundary && !clauseBoundaryTail) {
10062
+ const packedPunctWidth = props.lineSpacing?.rule === "auto" ? overflowPunctWidth : 0;
10063
+ const compress = (x - headW + wordW - packedPunctWidth - lineEnd) / curEaSpaceWidth;
9848
10064
  if (compress <= CJK_SPACE_COMPRESS + 1e-6) {
9849
10065
  fits = true;
9850
10066
  packUntilSpace = true;
@@ -9856,8 +10072,10 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9856
10072
  // (compat 12, docGrid lines) p2 draws the justified "...zebeqo:" line
9857
10073
  // with every inter-word space at 4.25pt against SimSun's natural
9858
10074
  // 5.25pt (19% compression) to avoid the wrap Word 15+ would also
9859
- // avoid. Scoped to grid sections rather than all legacy docs.
9860
- (doc.compatibilityMode >= 15 || minLineHeight !== void 0) && !bidiPara && props.alignment === "justify" && curSpaceWidth > 0
10075
+ // avoid. In legacy modes that extension is limited to lines whose
10076
+ // compressible spaces use the East Asian face; ordinary Times New
10077
+ // Roman bibliography lines retain Word 2007's earlier wrap.
10078
+ (doc.compatibilityMode >= 15 || minLineHeight !== void 0 && curEaSpaceWidth > 0) && !bidiPara && props.alignment === "justify" && curSpaceWidth > 0
9861
10079
  ) {
9862
10080
  let trail = 0;
9863
10081
  for (let j = hi - 1; j >= 0 && cur[j].isSpace; j--) trail += cur[j].width;
@@ -9866,7 +10084,8 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9866
10084
  const compress = (x - headW + wordW - lineEnd) / compressible;
9867
10085
  if (spacesAfterBreak > 1e-6 && compressible > 1e-6) {
9868
10086
  const stretch = (lineEnd - (x - headW - trail)) / spacesAfterBreak;
9869
- if (compress <= Math.min(JUSTIFY_MAX_COMPRESS, stretch * JUSTIFY_STRETCH_FACTOR)) {
10087
+ const maxCompress = minLineHeight === void 0 && props.numbering ? JUSTIFY_MAX_COMPRESS : JUSTIFY_GRID_MAX_COMPRESS;
10088
+ if (compress <= Math.min(maxCompress, stretch * JUSTIFY_STRETCH_FACTOR)) {
9870
10089
  fits = true;
9871
10090
  packUntilSpace = true;
9872
10091
  }
@@ -9877,7 +10096,8 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9877
10096
  hardWrapFrag(atom);
9878
10097
  continue;
9879
10098
  }
9880
- const head = hi > minSpans && hi < cur.length && cur[hi - 1].isSpace && !cur[hi - 1].noBreak ? cur.splice(hi) : [];
10099
+ const breakBeforeHead = hi > minSpans && hi < cur.length && (cur[hi - 1].isSpace && !cur[hi - 1].noBreak || cur[hi - 1].breakAfter);
10100
+ const head = breakBeforeHead ? cur.splice(hi) : [];
9881
10101
  for (const h of head) curLineWidth -= h.width;
9882
10102
  let moved = false;
9883
10103
  while (advanceSegment()) {
@@ -9926,9 +10146,12 @@ function breakParagraphImpl(doc, measurer, para, contentWidth, fields, numbering
9926
10146
  }
9927
10147
  const padL = bdrPad && (!sameBdrAtom(atoms[ai - 1], atom) || curLineWidth === 0) ? bdrPad : 0;
9928
10148
  x += padL;
9929
- cur.push({ x, width: atom.width, text: atom.text, props: atom.props, font: atom.font, href: atom.href, src: atom.src, noteId: atom.noteId, metricsFont: atom.metricsFont, breakAfter: atom.breakAfter, pageRef: atom.pageRef, rtl: atom.rtl, rtlLevel: levelOf(atom.rtl), ruby: atom.ruby });
10149
+ cur.push({ x, width: atom.width, text: atom.text, props: atom.props, font: atom.font, href: atom.href, src: atom.src, noteId: atom.noteId, metricsFont: atom.metricsFont, metricsStrut: atom.metricsStrut, breakAfter: atom.breakAfter, pageRef: atom.pageRef, rtl: atom.rtl, rtlLevel: levelOf(atom.rtl), ruby: atom.ruby });
9930
10150
  curLineWidth += padL + atom.width + bdrPadR;
9931
10151
  x += atom.width + bdrPadR;
10152
+ if (packUntilSpace && atom.breakAfter && /[-‐]$/.test(atom.text)) {
10153
+ packUntilSpace = false;
10154
+ }
9932
10155
  if (atom.text.trim().length > 0) prevContentCJK = fragIsCJK;
9933
10156
  }
9934
10157
  if (!flushedTrailingBreak) flush(true, false);
@@ -9987,7 +10210,7 @@ function nextTabStop(x, tabs, rightEdge, defaultTab = DEFAULT_TAB) {
9987
10210
  }
9988
10211
  }
9989
10212
  const next = nextDefaultTab(x, defaultTab);
9990
- return { pos: next < rightEdge ? next : x + 10.7, align: "left" };
10213
+ return { pos: next < rightEdge ? next : x + 20.4, align: "left" };
9991
10214
  }
9992
10215
  function reorderVisual(spans, startX) {
9993
10216
  if (spans.length === 0) return;
@@ -10083,6 +10306,7 @@ function finishLine(spans, width, props, measurer, fallbackFamily, para, doc, is
10083
10306
  let maxBorderedAscent = 0;
10084
10307
  let maxBorderedDescent = 0;
10085
10308
  let tallestTextIsEa = false;
10309
+ let metricsStrutHeight = 0;
10086
10310
  const consider = (font, imageHeight, objRaise = 0, descShift = 0) => {
10087
10311
  if (imageHeight !== void 0) {
10088
10312
  maxAscent = Math.max(maxAscent, imageHeight + objRaise);
@@ -10123,9 +10347,10 @@ function finishLine(spans, width, props, measurer, fallbackFamily, para, doc, is
10123
10347
  };
10124
10348
  let metricSpans = spans.filter((s) => !(s.text === " " && !s.leader));
10125
10349
  const solidSpans = metricSpans.filter(
10126
- (s) => s.image || s.drawing || s.math || s.leader || s.text === void 0 || !/^[ \t]*$/.test(s.text)
10350
+ (s) => s.metricsStrut || s.image || s.drawing || s.math || s.leader || s.text === void 0 || !/^[ \t]*$/.test(s.text)
10127
10351
  );
10128
10352
  if (solidSpans.length > 0) metricSpans = solidSpans;
10353
+ const styledLoweredIntegral = para.props.styleId !== void 0 && metricSpans.length > 1 && metricSpans.some((s) => s.text?.includes("\u222B") && (s.props.raise ?? 0) < 0);
10129
10354
  if (metricSpans.some((s) => s.numLabel) && metricSpans.some((s) => !s.numLabel)) {
10130
10355
  let textLine = 0;
10131
10356
  for (const s of metricSpans) {
@@ -10156,7 +10381,13 @@ function finishLine(spans, width, props, measurer, fallbackFamily, para, doc, is
10156
10381
  const lineHasObject = metricSpans.some((s) => s.image || s.drawing);
10157
10382
  for (const s of metricSpans) {
10158
10383
  let textMetrics;
10159
- if (s.image || s.drawing) {
10384
+ if (s.metricsStrut) {
10385
+ metricsStrutHeight = Math.max(
10386
+ metricsStrutHeight,
10387
+ measurer.metrics(s.metricsFont ?? s.font).lineHeight
10388
+ );
10389
+ consider(s.metricsFont ?? s.font);
10390
+ } else if (s.image || s.drawing) {
10160
10391
  const objectMetrics = measurer.metrics(s.metricsFont ?? s.font);
10161
10392
  const objectHeight = s.image?.height ?? s.drawing.height;
10162
10393
  const objRaise = s.props.raise ?? 0;
@@ -10211,6 +10442,14 @@ function finishLine(spans, width, props, measurer, fallbackFamily, para, doc, is
10211
10442
  const commonLower = metricSpans.length > 0 && metricSpans.every(
10212
10443
  (s) => !s.image && !s.drawing && !s.math && (s.props.raise ?? 0) < 0
10213
10444
  ) ? Math.min(...metricSpans.map((s) => -s.props.raise)) : 0;
10445
+ if (metricsStrutHeight > 0) {
10446
+ for (const s of spans) {
10447
+ if (s.metricsStrut || s.image || s.drawing || s.math) continue;
10448
+ const spanHeight = measurer.metrics(s.font).lineHeight;
10449
+ const paintDY = Math.max(0, (metricsStrutHeight - spanHeight) / 2);
10450
+ if (paintDY > 0) s.font = { ...s.font, paintDY: (s.font.paintDY ?? 0) + paintDY };
10451
+ }
10452
+ }
10214
10453
  const natural = Math.max(maxNatural, maxAscent + maxDescent, minLineHeight ?? 0);
10215
10454
  let height = natural;
10216
10455
  let baselineH;
@@ -10254,8 +10493,11 @@ function finishLine(spans, width, props, measurer, fallbackFamily, para, doc, is
10254
10493
  const lead = ls.value >= 1.15 ? (ls.value - 1) * maxNaturalText : 0;
10255
10494
  height = Math.max(maxNaturalText * ls.value, maxInlineMath + lead);
10256
10495
  }
10257
- } else if (ls.rule === "exact") height = ls.value;
10258
- else height = Math.max(natural, ls.value);
10496
+ } else if (ls.rule === "exact") {
10497
+ height = ls.value;
10498
+ const lowerBand = Math.max(maxDescent, height * 0.2);
10499
+ baselineH = height - lowerBand + maxDescent;
10500
+ } else height = Math.max(natural, ls.value);
10259
10501
  }
10260
10502
  if (!gridObjSnap && (minLineHeight ?? 0) > 0 && ls?.rule === "atLeast" && baselineH === void 0) {
10261
10503
  const glyph = maxAscent + maxDescent;
@@ -10271,6 +10513,11 @@ function finishLine(spans, width, props, measurer, fallbackFamily, para, doc, is
10271
10513
  maxDescent -= descentReuse;
10272
10514
  maxRawDescent -= descentReuse;
10273
10515
  }
10516
+ if (styledLoweredIntegral && ls?.rule !== "exact" && !gridObjSnap) {
10517
+ const overlap = Math.min(6, height);
10518
+ height -= overlap;
10519
+ baselineH = (baselineH ?? height + overlap) - overlap;
10520
+ }
10274
10521
  return {
10275
10522
  spans,
10276
10523
  width,
@@ -10371,14 +10618,31 @@ function resolveBidiRtl(text) {
10371
10618
  function buildAtoms(doc, para, measurer, fields, fallbackFamily, gridPitch) {
10372
10619
  const atoms = [];
10373
10620
  const anchors = [];
10621
+ const stylelessOleParagraph = doc.styles.defaultRPr.font === void 0 && doc.styles.defaultRPr.size === void 0 && para.children.some((child4) => {
10622
+ const runs = child4.type === "run" ? [child4] : child4.runs;
10623
+ return runs.some(
10624
+ (run) => run.content.some(
10625
+ (content) => content.kind === "image" && content.embeddedObject !== void 0
10626
+ )
10627
+ );
10628
+ });
10374
10629
  const bidiPara = doc.effectiveParaProps(para).bidi === true;
10375
10630
  const kashidaKind = doc.effectiveParaProps(para).justifyKind;
10376
10631
  const kashidaEm = kashidaKind === "highKashida" ? KASHIDA_HIGH_EM : kashidaKind === "mediumKashida" ? KASHIDA_MEDIUM_EM : kashidaKind === "lowKashida" ? KASHIDA_LOW_EM : 0;
10377
10632
  const pushRun = (run, href) => {
10378
- const baseProps = doc.effectiveRunProps(para, run.props);
10633
+ let baseProps = doc.effectiveRunProps(para, run.props);
10634
+ if (stylelessOleParagraph && baseProps.size === void 0) {
10635
+ baseProps = { ...baseProps, size: 16 };
10636
+ }
10379
10637
  if (baseProps.vanish) return;
10380
10638
  const font = fontOf(baseProps, fallbackFamily);
10381
- let props = kashidaEm > 0 && baseProps.rtl ? { ...baseProps, letterSpacing: (baseProps.letterSpacing ?? 0) + kashidaEm * font.size } : baseProps;
10639
+ let props = kashidaEm > 0 && baseProps.rtl ? {
10640
+ ...baseProps,
10641
+ letterSpacing: (baseProps.letterSpacing ?? 0) + kashidaEm * font.size,
10642
+ // Word adds kashidas at eligible Arabic joins. Uniform CSS tracking
10643
+ // needs less paint spacing while retaining the same line advances.
10644
+ paintLetterSpacing: (baseProps.letterSpacing ?? 0) + Math.min(kashidaEm, 0.04) * font.size
10645
+ } : baseProps;
10382
10646
  if (props.fitText !== void 0 && props.fitText > 0) {
10383
10647
  const natural = measurer.width(displayText(runPlainText(run), props), font, props.letterSpacing) * (props.textScale ?? 1);
10384
10648
  if (natural > 0.1) {
@@ -10445,7 +10709,30 @@ function buildAtoms(doc, para, measurer, fields, fallbackFamily, gridPitch) {
10445
10709
  });
10446
10710
  break;
10447
10711
  }
10448
- if (text) pushStyled(displayText(text, props), props, font, href, { run, t: null, offset: 0 }, vertMetricsFont);
10712
+ if (!text && /^\s*SEQ\b/i.test(content.instruction) && /\\h(\s|$)/i.test(content.instruction)) {
10713
+ atoms.push({
10714
+ kind: "frag",
10715
+ text: "",
10716
+ props,
10717
+ font,
10718
+ width: 0,
10719
+ href,
10720
+ src: { run, t: null, offset: 0 },
10721
+ metricsFont: vertMetricsFont,
10722
+ metricsStrut: true
10723
+ });
10724
+ break;
10725
+ }
10726
+ if (text) {
10727
+ const firstAtom = atoms.length;
10728
+ pushStyled(displayText(text, props), props, font, href, { run, t: null, offset: 0 }, vertMetricsFont);
10729
+ if (content.checkbox) {
10730
+ const last = atoms[atoms.length - 1];
10731
+ if (atoms.length > firstAtom && last.kind === "frag") {
10732
+ last.width += LEGACY_CHECKBOX_TRAILING_ADVANCE;
10733
+ }
10734
+ }
10735
+ }
10449
10736
  break;
10450
10737
  }
10451
10738
  case "ptab":
@@ -10483,13 +10770,21 @@ function buildAtoms(doc, para, measurer, fields, fallbackFamily, gridPitch) {
10483
10770
  break;
10484
10771
  case "math": {
10485
10772
  const size = props.size ?? 14.666;
10486
- const box = layoutMath(content.nodes, size, measurer, content.display);
10773
+ const tabbedInline = !content.display && atoms[atoms.length - 1]?.kind === "tab" && (para.props.indentFirstLine ?? 0) > 0;
10774
+ const box = layoutMath(
10775
+ content.nodes,
10776
+ size,
10777
+ measurer,
10778
+ content.display,
10779
+ tabbedInline ? TABBED_INLINE_MATH_FRAC_PAD : void 0,
10780
+ content.multiEquationDisplay
10781
+ );
10487
10782
  if (content.display) box.jc = content.jc;
10488
10783
  atoms.push({ kind: "math", box, src: content.src });
10489
10784
  break;
10490
10785
  }
10491
10786
  case "noteRef": {
10492
- const text = content.self ? fields.selfNoteMark?.() ?? "" : fields.noteMark?.(content.noteType, content.id) ?? "";
10787
+ const text = content.self ? fields.selfNoteMark?.() ?? "" : content.customMarkFollows ? "" : fields.noteMark?.(content.noteType, content.id) ?? "";
10493
10788
  if (!text) break;
10494
10789
  const markProps = props.verticalAlign ? props : { ...props, verticalAlign: "superscript" };
10495
10790
  const markFont = fontOf(markProps, fallbackFamily);
@@ -10540,11 +10835,16 @@ function buildAtoms(doc, para, measurer, fields, fallbackFamily, gridPitch) {
10540
10835
  }
10541
10836
  };
10542
10837
  const pushStyled = (text, props, font, href, srcBase, metricsFont) => {
10838
+ const emojiScale = emojiFallbackScale(text, font.family);
10839
+ if (emojiScale !== 1) {
10840
+ props = { ...props, textScale: (props.textScale ?? 1) * emojiScale };
10841
+ }
10543
10842
  const hasDeva = /[ऀ-ॿ]/.test(text);
10544
10843
  const hasTamil = /[஀-௿]/.test(text);
10545
10844
  const hasLao = /[຀-໿]/.test(text);
10546
10845
  const indicFace = hasDeva && !hasTamil && !hasLao ? "Mangal" : hasTamil && !hasDeva && !hasLao ? "Latha" : hasLao && !hasDeva && !hasTamil ? "Noto Sans Lao Looped" : null;
10547
10846
  if (indicFace && font.family.toLowerCase() !== indicFace.toLowerCase()) {
10847
+ const scriptProps = indicFace === "Noto Sans Lao Looped" ? { ...props, textScale: (props.textScale ?? 1) * LAO_GLYPH_SCALE } : props;
10548
10848
  const indicScale = indicFace === "Latha" ? TAMIL_GLYPH_SCALE : 1;
10549
10849
  const indicFont = indicScale === 1 ? { ...font, family: indicFace } : {
10550
10850
  ...font,
@@ -10556,15 +10856,16 @@ function buildAtoms(doc, para, measurer, fields, fallbackFamily, gridPitch) {
10556
10856
  paintDY: font.size * TAMIL_BASELINE_DY_EM
10557
10857
  };
10558
10858
  const indicMetrics = indicScale !== 1 ? { ...metricsFont ?? font, family: indicFace, size: font.size } : metricsFont ? { ...metricsFont, family: indicFace } : metricsFont;
10859
+ const isIndic = indicFace === "Mangal" ? (ch) => /[ऀ-ॿ]/.test(ch) : indicFace === "Latha" ? (ch) => /[஀-௿]/.test(ch) : (ch) => /[຀-໿]/.test(ch);
10559
10860
  let i2 = 0;
10560
10861
  while (i2 < text.length) {
10561
- const isSpace = text[i2] === " ";
10862
+ const indic = isIndic(text[i2]);
10562
10863
  let j = i2 + 1;
10563
- while (j < text.length && text[j] === " " === isSpace) j++;
10864
+ while (j < text.length && isIndic(text[j]) === indic) j++;
10564
10865
  const seg = text.slice(i2, j);
10565
10866
  const src = srcBase ? { ...srcBase, offset: srcBase.offset + i2 } : void 0;
10566
- if (isSpace) pushText(seg, props, font, href, src, metricsFont);
10567
- else pushText(seg, props, indicFont, href, src, indicMetrics);
10867
+ if (indic) pushText(seg, scriptProps, indicFont, href, src, indicMetrics);
10868
+ else pushText(seg, props, font, href, src, metricsFont);
10568
10869
  i2 = j;
10569
10870
  }
10570
10871
  return;
@@ -10635,6 +10936,9 @@ function buildAtoms(doc, para, measurer, fields, fallbackFamily, gridPitch) {
10635
10936
  if (part[0] === " ") {
10636
10937
  let w = partWidth / part.length;
10637
10938
  let spaceFont = font;
10939
+ if (para.props.styleId === "HTML0" && EA_FAMILY_RE.test(font.family)) {
10940
+ w = font.size / 2 * tScale;
10941
+ }
10638
10942
  if (gridPitch && part.length >= 2 && props.fontEastAsia && !EA_FAMILY_RE.test(font.family)) {
10639
10943
  spaceFont = { ...font, family: props.fontEastAsia };
10640
10944
  w = measurer.width(" ", spaceFont, props.letterSpacing) * tScale;
@@ -10651,7 +10955,16 @@ function buildAtoms(doc, para, measurer, fields, fallbackFamily, gridPitch) {
10651
10955
  });
10652
10956
  }
10653
10957
  } else {
10654
- const hyBreaks = hyphenBreaks(part);
10958
+ const hyBreaks = hyphenBreaks(part, doc.compatibilityMode >= 15);
10959
+ if ((part[0] === "-" || part[0] === "\u2010") && isWordAlnum(part[1])) {
10960
+ const prev = atoms[atoms.length - 1];
10961
+ const beforePrev = atoms[atoms.length - 2];
10962
+ const prevText = prev?.kind === "frag" ? prev.text : "";
10963
+ const beforeText = beforePrev?.kind === "frag" ? beforePrev.text : "";
10964
+ const prevChar = prevText[prevText.length - 1];
10965
+ const runBoundaryWordBefore = isWordAlnum(prevChar) || /^[\)\]\}]$/.test(prevChar ?? "") && isWordAlnum(beforeText[beforeText.length - 1]);
10966
+ if (runBoundaryWordBefore) hyBreaks.unshift(1);
10967
+ }
10655
10968
  let breaks = hyBreaks;
10656
10969
  if (charRtl) {
10657
10970
  const set = new Set(hyBreaks);
@@ -10756,6 +11069,15 @@ function buildAtoms(doc, para, measurer, fields, fallbackFamily, gridPitch) {
10756
11069
  if (childEl.type === "run") pushRun(childEl);
10757
11070
  else for (const r of childEl.runs) pushRun(r, childEl.href ?? (childEl.anchor ? "#" + childEl.anchor : void 0));
10758
11071
  }
11072
+ if (gridPitch) {
11073
+ for (let i = 0; i + 1 < atoms.length; i++) {
11074
+ const punct = atoms[i];
11075
+ const next = atoms[i + 1];
11076
+ if (punct.kind === "frag" && /^[,。]$/.test(punct.text) && EA_FAMILY_RE.test(punct.font.family) && next.kind === "frag" && /^[\x00-\x7f]/.test(next.text)) {
11077
+ punct.width = punct.font.size * 0.625 * (punct.props.textScale ?? 1);
11078
+ }
11079
+ }
11080
+ }
10759
11081
  for (let i = atoms.length - 1, nextNbsp = false; i >= 0; i--) {
10760
11082
  const a = atoms[i];
10761
11083
  if (a.kind === "frag") nextNbsp = a.text.charCodeAt(0) === 160;
@@ -10787,8 +11109,8 @@ function resolveField(instruction, cachedResult, ctx, fieldKey) {
10787
11109
  }
10788
11110
  case "SEQ": {
10789
11111
  const ident = instr.split(/\s+/)[1];
10790
- if (ident && ctx.seq && fieldKey) return ctx.seq(ident, fieldKey, instr);
10791
- return cachedResult || "";
11112
+ const value = ident && ctx.seq && fieldKey ? ctx.seq(ident, fieldKey, instr) : cachedResult || "";
11113
+ return /\\h(\s|$)/i.test(instr) ? "" : value;
10792
11114
  }
10793
11115
  case "REF": {
10794
11116
  const bookmark = instr.split(/\s+/)[1];
@@ -10830,7 +11152,7 @@ function resolveField(instruction, cachedResult, ctx, fieldKey) {
10830
11152
  var DP_MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
10831
11153
  var DP_DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
10832
11154
  function formatDatePicture(d, picture) {
10833
- const h24 = d.getHours();
11155
+ const h24 = d.getUTCHours();
10834
11156
  const h12 = h24 % 12 === 0 ? 12 : h24 % 12;
10835
11157
  const pad = (n) => String(n).padStart(2, "0");
10836
11158
  const hasAmPm = /am\/pm/i.test(picture);
@@ -10840,25 +11162,25 @@ function formatDatePicture(d, picture) {
10840
11162
  if (tok.startsWith("'")) return tok.slice(1, -1);
10841
11163
  switch (tok) {
10842
11164
  case "yyyy":
10843
- return String(d.getFullYear());
11165
+ return String(d.getUTCFullYear());
10844
11166
  case "yy":
10845
- return pad(d.getFullYear() % 100);
11167
+ return pad(d.getUTCFullYear() % 100);
10846
11168
  case "MMMM":
10847
- return DP_MONTHS[d.getMonth()];
11169
+ return DP_MONTHS[d.getUTCMonth()];
10848
11170
  case "MMM":
10849
- return DP_MONTHS[d.getMonth()].slice(0, 3);
11171
+ return DP_MONTHS[d.getUTCMonth()].slice(0, 3);
10850
11172
  case "MM":
10851
- return pad(d.getMonth() + 1);
11173
+ return pad(d.getUTCMonth() + 1);
10852
11174
  case "M":
10853
- return String(d.getMonth() + 1);
11175
+ return String(d.getUTCMonth() + 1);
10854
11176
  case "dddd":
10855
- return DP_DAYS[d.getDay()];
11177
+ return DP_DAYS[d.getUTCDay()];
10856
11178
  case "ddd":
10857
- return DP_DAYS[d.getDay()].slice(0, 3);
11179
+ return DP_DAYS[d.getUTCDay()].slice(0, 3);
10858
11180
  case "dd":
10859
- return pad(d.getDate());
11181
+ return pad(d.getUTCDate());
10860
11182
  case "d":
10861
- return String(d.getDate());
11183
+ return String(d.getUTCDate());
10862
11184
  case "HH":
10863
11185
  return pad(h24);
10864
11186
  case "H":
@@ -10868,13 +11190,13 @@ function formatDatePicture(d, picture) {
10868
11190
  case "h":
10869
11191
  return String(hasAmPm ? h12 : h24);
10870
11192
  case "mm":
10871
- return pad(d.getMinutes());
11193
+ return pad(d.getUTCMinutes());
10872
11194
  case "m":
10873
- return String(d.getMinutes());
11195
+ return String(d.getUTCMinutes());
10874
11196
  case "ss":
10875
- return pad(d.getSeconds());
11197
+ return pad(d.getUTCSeconds());
10876
11198
  case "s":
10877
- return String(d.getSeconds());
11199
+ return String(d.getUTCSeconds());
10878
11200
  default:
10879
11201
  return /^am\/pm$/i.test(tok) ? h24 < 12 ? "AM" : "PM" : tok;
10880
11202
  }
@@ -11011,6 +11333,7 @@ var MULTI_COL_NOTE_SEP_RESERVE = 26;
11011
11333
  var NOTE_SEP_LEN = 192;
11012
11334
  var NOTE_SEP_GAP = 4.4;
11013
11335
  var ROW_OVERHANG_TOL = 3;
11336
+ var CUSTOM_NOTE_BANNER_OVERHANG = 3;
11014
11337
  var CHICAGO = ["*", "\u2020", "\u2021", "\xA7"];
11015
11338
  var AUTO_PARA_SPACING_PX = 14 * (96 / 72);
11016
11339
  var FRAME_WRAP_HSPACE_PX = 6 * (96 / 72);
@@ -11118,6 +11441,7 @@ var Engine = class {
11118
11441
  /** Vertical flow already consumed in the current later-column pre-banner
11119
11442
  * slot. It reduces the below-banner capacity by the same amount. */
11120
11443
  this.bannerSlotUsed = 0;
11444
+ this.customNoteBannerFit = false;
11121
11445
  /** True while a section-level tbRl vertical flow is being laid: paragraphs
11122
11446
  * re-establish the vertical grid after embedded Western runs (see
11123
11447
  * breakParagraph's verticalGridResync). */
@@ -11235,7 +11559,7 @@ var Engine = class {
11235
11559
  const contentWidth = page.sp.pageWidth - page.sp.marginLeft - page.sp.marginRight - page.sp.gutter;
11236
11560
  const header = this.doc.headers.get(page.headerRel ?? "");
11237
11561
  const footer = this.doc.footers.get(page.footerRel ?? "");
11238
- const headerHeight = this.measureHeaderFooter(header, page, contentWidth, this.pageFieldFrameOverlay(header));
11562
+ const headerHeight = this.measureHeaderFooter(header, page, contentWidth, this.pageFieldFrameOverlay(header), true);
11239
11563
  const footerHeight = this.measureHeaderFooter(footer, page, contentWidth, this.pageFieldFrameOverlay(footer));
11240
11564
  if (headerHeight !== page.headerHeight || footerHeight !== page.footerHeight) return null;
11241
11565
  }
@@ -11335,6 +11659,7 @@ var Engine = class {
11335
11659
  if (this.pages.length === 0) {
11336
11660
  this.sp = sections[0]?.props ?? {};
11337
11661
  }
11662
+ this.applyOpeningFlowOverlap();
11338
11663
  this.placeEndnotes();
11339
11664
  this.emitFootnoteAreas();
11340
11665
  this.emitColumnSeparators();
@@ -11363,6 +11688,43 @@ var Engine = class {
11363
11688
  }
11364
11689
  return result;
11365
11690
  }
11691
+ /** Some document-opening empty paragraphs reserve a second mark line for
11692
+ * pagination while the following content paints over it on page one. Keep
11693
+ * the reservation in flow so later page breaks stay unchanged, then lift
11694
+ * only the painted first-page body. */
11695
+ applyOpeningFlowOverlap() {
11696
+ const opening = this.doc.sections[0]?.blocks[0];
11697
+ const next = this.doc.sections[0]?.blocks[1];
11698
+ const page = this.pages[0];
11699
+ if (!page || page.openingFlowOverlapApplied || opening?.type !== "paragraph" || opening.sectionBreak || paragraphHasContent(opening)) {
11700
+ return;
11701
+ }
11702
+ const beforeNegativeTable = next?.type === "table" && (next.props.indent ?? 0) < 0;
11703
+ const header = this.doc.headers.get(page.headerRel ?? "");
11704
+ const headerAnchors = header?.blocks.flatMap(
11705
+ (block) => block.type === "paragraph" ? this.collectAnchors(block) : []
11706
+ ) ?? [];
11707
+ const beforeParagraphUnderUnwrappedHeader = next?.type === "paragraph" && page.headerGrown === true && headerAnchors.length > 0 && headerAnchors.every(
11708
+ (shape) => !("wrap" in shape) || shape.wrap === void 0 || shape.wrap === "none"
11709
+ );
11710
+ if (!beforeNegativeTable && !beforeParagraphUnderUnwrappedHeader) return;
11711
+ const firstBodyItem = page.items.findIndex(
11712
+ (item) => item.kind !== "text" || item.text.length > 0
11713
+ );
11714
+ if (firstBodyItem < 0) return;
11715
+ const paraProps = this.doc.effectiveParaProps(opening);
11716
+ const markProps = this.doc.effectiveRunProps(opening, paraProps.markRunProps ?? {});
11717
+ let overlap = this.measurer.metrics(
11718
+ fontOf(markProps, this.doc.styles.defaultRPr.font ?? "Calibri")
11719
+ ).lineHeight;
11720
+ if (beforeParagraphUnderUnwrappedHeader) {
11721
+ overlap += paraProps.spacingAfter ?? 0;
11722
+ }
11723
+ for (let i = firstBodyItem; i < page.items.length; i++) {
11724
+ offsetItem(page.items[i], 0, -overlap);
11725
+ }
11726
+ page.openingFlowOverlapApplied = true;
11727
+ }
11366
11728
  /** Whether this document is in the envelope the incremental path handles:
11367
11729
  * one section, no notes, single column, upright flow, no line numbering /
11368
11730
  * mirror margins / vertical alignment, and no positioned/floating content or
@@ -11621,7 +11983,7 @@ var Engine = class {
11621
11983
  const contentWidth = shifted.sp.pageWidth - shifted.sp.marginLeft - shifted.sp.marginRight - shifted.sp.gutter;
11622
11984
  const header = this.doc.headers.get(headerRel ?? "");
11623
11985
  const footer = this.doc.footers.get(footerRel ?? "");
11624
- const headerHeight = this.measureHeaderFooter(header, shifted, contentWidth, this.pageFieldFrameOverlay(header));
11986
+ const headerHeight = this.measureHeaderFooter(header, shifted, contentWidth, this.pageFieldFrameOverlay(header), true);
11625
11987
  const footerHeight = this.measureHeaderFooter(footer, shifted, contentWidth, this.pageFieldFrameOverlay(footer));
11626
11988
  if (headerHeight !== page.headerHeight || footerHeight !== page.footerHeight) return null;
11627
11989
  shifted.headerHeight = headerHeight;
@@ -11999,8 +12361,12 @@ var Engine = class {
11999
12361
  displayNumber,
12000
12362
  headerHeight: 0,
12001
12363
  footerHeight: 0,
12002
- bodyTop: Math.abs(sp.marginTop),
12003
- bandTop: Math.abs(sp.marginTop),
12364
+ // Legacy Word keeps a 3pt printable-edge inset when a signed negative
12365
+ // top margin fixes body flow independently of the header. California
12366
+ // pleading paper exposes this exactly: the 24pt body grid begins 3pt
12367
+ // below the raw -66.25pt margin while the header stays at headerDist.
12368
+ bodyTop: sp.marginTop < 0 ? sp.marginTop + ptToPx(3) : sp.marginTop,
12369
+ bandTop: sp.marginTop < 0 ? sp.marginTop + ptToPx(3) : sp.marginTop,
12004
12370
  softTop: !sectionStart,
12005
12371
  bodyBottom: sp.pageHeight - Math.abs(sp.marginBottom),
12006
12372
  colXs,
@@ -12024,13 +12390,17 @@ var Engine = class {
12024
12390
  }
12025
12391
  const header = this.doc.headers.get(page.headerRel ?? "");
12026
12392
  const footer = this.doc.footers.get(page.footerRel ?? "");
12027
- const headerH = this.measureHeaderFooter(header, page, contentWidth, this.pageFieldFrameOverlay(header));
12393
+ const headerH = this.measureHeaderFooter(header, page, contentWidth, this.pageFieldFrameOverlay(header), true);
12028
12394
  const footerOverlay = this.pageFieldFrameOverlay(footer);
12029
12395
  const footerH = this.measureHeaderFooter(footer, page, contentWidth, footerOverlay);
12030
12396
  page.headerHeight = headerH;
12031
12397
  page.footerHeight = footerH;
12032
12398
  if (sp.marginTop >= 0) {
12033
- page.bodyTop = Math.max(sp.marginTop, headerH > 0 ? sp.headerDistance + headerH : 0);
12399
+ const headerBodyHeight = this.doc.compatibilityMode < 15 && header?.blocks.every((block) => block.type === "paragraph" && !paragraphHasContent(block)) ? ptToPx(7) : headerH;
12400
+ page.bodyTop = Math.max(
12401
+ sp.marginTop,
12402
+ headerBodyHeight > 0 ? sp.headerDistance + headerBodyHeight : 0
12403
+ );
12034
12404
  page.bandTop = page.bodyTop;
12035
12405
  page.headerGrown = page.bodyTop > sp.marginTop;
12036
12406
  }
@@ -12270,7 +12640,7 @@ var Engine = class {
12270
12640
  if (this.col + 1 < this.cur.colXs.length) {
12271
12641
  this.col++;
12272
12642
  this.y = this.columnStartY(this.col);
12273
- this.bannerSlotUsed = 0;
12643
+ this.bannerSlotUsed = this.cur.bannerTop !== void 0 ? this.cur.openingColumnReserve ?? 0 : 0;
12274
12644
  this.lastParaSpacingAfter = 0;
12275
12645
  this.lastParaAfterPad = 0;
12276
12646
  this.lastParaAfterPad = 0;
@@ -12607,7 +12977,7 @@ var Engine = class {
12607
12977
  const runs = c.type === "run" ? [c] : c.runs;
12608
12978
  for (const r of runs) {
12609
12979
  for (const rc of r.content) {
12610
- if (rc.kind !== "noteRef" || rc.self) continue;
12980
+ if (rc.kind !== "noteRef" || rc.self || rc.customMarkFollows) continue;
12611
12981
  if (rc.noteType === "footnote" && this.doc.footnotes.has(rc.id) && !this.footnoteNumbers.has(rc.id)) {
12612
12982
  this.footnoteNumbers.set(rc.id, fnStart + fn++);
12613
12983
  } else if (rc.noteType === "endnote" && this.doc.endnotes.has(rc.id) && !this.endnoteNumbers.has(rc.id)) {
@@ -12715,7 +13085,8 @@ var Engine = class {
12715
13085
  const notes = page.footnotes.filter((note) => note.column === column);
12716
13086
  if (notes.length === 0) continue;
12717
13087
  const x0 = page.colXs[column];
12718
- let y = page.bodyBottom - (page.footnoteH[column] ?? 0) - NOTE_SEP_H;
13088
+ const separatorHeight = this.doc.footnoteSeparator.length > 0 ? this.layoutFrame(this.doc.footnoteSeparator, page.colWidths[column], this.fieldCtx()).height : NOTE_SEP_H;
13089
+ let y = page.bodyBottom - (page.footnoteH[column] ?? 0) - separatorHeight;
12719
13090
  page.items.push({
12720
13091
  kind: "edge",
12721
13092
  x1: x0,
@@ -12724,7 +13095,7 @@ var Engine = class {
12724
13095
  y2: y + NOTE_SEP_H * 0.6,
12725
13096
  border: { style: "single", width: 0.75, color: "#000000", space: 0 }
12726
13097
  });
12727
- y += NOTE_SEP_H;
13098
+ y += separatorHeight;
12728
13099
  for (const note of notes) {
12729
13100
  for (const it of note.items) {
12730
13101
  offsetItem(it, x0, y);
@@ -12967,10 +13338,20 @@ var Engine = class {
12967
13338
  return { x: 0, width: colW };
12968
13339
  }
12969
13340
  let clearY;
13341
+ let exactTextEdge = false;
12970
13342
  for (const f of floats) {
12971
- if (f.mode === "square" && overlaps(f)) clearY = Math.max(clearY ?? 0, f.y1 - paraTop + 2);
13343
+ if (f.mode === "square" && overlaps(f)) {
13344
+ clearY = Math.max(clearY ?? 0, f.y1 - paraTop + 2);
13345
+ exactTextEdge || (exactTextEdge = f.exactTextEdge === true);
13346
+ }
12972
13347
  }
12973
- return { x: segs[0].x, width: segs[0].width, segments: segs, clearY };
13348
+ return {
13349
+ x: segs[0].x,
13350
+ width: segs[0].width,
13351
+ segments: segs,
13352
+ clearY,
13353
+ edgeReserve: exactTextEdge ? 0 : void 0
13354
+ };
12974
13355
  };
12975
13356
  }
12976
13357
  /**
@@ -13120,6 +13501,20 @@ var Engine = class {
13120
13501
  }
13121
13502
  placeParagraph(para, prev, next, siblings, index) {
13122
13503
  const props = this.doc.effectiveParaProps(para);
13504
+ const detachedFootnotes = customFootnoteAnchorIds(para);
13505
+ if (detachedFootnotes && next?.type === "paragraph") {
13506
+ const nextProps2 = this.doc.effectiveParaProps(next);
13507
+ const nextFrame = nextProps2.frame ? this.resolveFrame(nextProps2.frame) : void 0;
13508
+ if (nextFrame?.wrap === "notBeside" && nextFrame.w !== void 0 && this.cur.colXs.length > 1 && nextFrame.w > this.colWidth + 1) {
13509
+ for (const id of detachedFootnotes) this.registerFootnote(id, this.cur);
13510
+ const markProps = this.doc.effectiveRunProps(para, props.markRunProps ?? {});
13511
+ this.cur.openingColumnReserve = this.measurer.metrics(
13512
+ fontOf(markProps, this.doc.styles.defaultRPr.font ?? "Calibri")
13513
+ ).lineHeight;
13514
+ this.customNoteBannerFit = true;
13515
+ return;
13516
+ }
13517
+ }
13123
13518
  if (this.consumedFrames.has(para)) return;
13124
13519
  if (props.frame && !props.dropCap && this.cur.physIndex !== -1) {
13125
13520
  const fr = this.resolveFrame(props.frame);
@@ -13172,7 +13567,8 @@ var Engine = class {
13172
13567
  x1: this.colX + dropLine.width + props.dropCap.hSpace,
13173
13568
  y0: this.y,
13174
13569
  y1: this.y + dropLine.naturalHeight,
13175
- mode: "square"
13570
+ mode: "square",
13571
+ exactTextEdge: true
13176
13572
  });
13177
13573
  this.floats.set(this.cur, list);
13178
13574
  }
@@ -13252,16 +13648,22 @@ var Engine = class {
13252
13648
  if (breakBeforeForced && !(isLeadingPageBreak && keepSpBeforeAtPageTop)) dropSpaceBefore = true;
13253
13649
  if (this.docGridDropBefore) {
13254
13650
  if (props.snapToGrid === false && this.sp.docGridLinePitch) {
13255
- const reduction = 2 * this.sp.docGridLinePitch;
13651
+ const reduction = (props.spacingBefore ?? 0) > 0 ? 4 * this.sp.docGridLinePitch : 2 * this.sp.docGridLinePitch;
13256
13652
  this.cur.bodyTop -= reduction;
13257
13653
  this.y -= reduction;
13654
+ } else if (para.props.styleId === "Heading1" && this.sp.docGridLinePitch) {
13655
+ const reduction = 3 * this.sp.docGridLinePitch - ptToPx(1.5);
13656
+ this.cur.bodyTop -= reduction;
13657
+ this.y -= reduction;
13658
+ dropSpaceBefore = true;
13659
+ } else {
13660
+ dropSpaceBefore = true;
13258
13661
  }
13259
13662
  this.docGridDropBefore = false;
13260
- dropSpaceBefore = true;
13261
13663
  }
13262
13664
  const rawSpacingBefore = dropSpaceBefore ? 0 : props.spacingBefore ?? 0;
13263
13665
  let paraTopEstimate = this.y + rawSpacingBefore;
13264
- emitParaAnchors(paraTopEstimate);
13666
+ emitParaAnchors(this.y);
13265
13667
  let broken = breakNow(paraTopEstimate);
13266
13668
  const emitCharLineAnchors = (paraTop) => {
13267
13669
  if (charLineAnchors.length === 0) return;
@@ -13431,7 +13833,7 @@ var Engine = class {
13431
13833
  const nextProps = nextPara ? this.doc.effectiveParaProps(nextPara) : void 0;
13432
13834
  const nextLeadBreak = nextPara ? leadingBreakOf(nextPara) : void 0;
13433
13835
  let legacyBreakChain = false;
13434
- if (lines.length > 0 && this.pageIsEmptyAtCursor() && this.doc.compatibilityMode < 15 && this.sp.docGridType === "default" && props.keepNext && nextPara && nextProps?.keepNext && nextLeadBreak?.type === "page") {
13836
+ if (lines.length > 0 && this.pageIsEmptyAtCursor() && this.doc.compatibilityMode < 15 && this.sp.docGridType === "default" && !this.cur.footerRel && props.keepNext && nextPara && nextProps?.keepNext && nextLeadBreak?.type === "page") {
13435
13837
  const breakProps = this.doc.effectiveRunProps(nextPara, nextLeadBreak.run.props);
13436
13838
  const breakFont = fontOf(breakProps, this.doc.styles.defaultRPr.font ?? "Calibri");
13437
13839
  let breakHeight = this.measurer.metrics(breakFont).lineHeight;
@@ -13454,7 +13856,7 @@ var Engine = class {
13454
13856
  retractParaAnchors();
13455
13857
  this.nextColumn();
13456
13858
  paraTopEstimate = this.y + extraSpacing;
13457
- emitParaAnchors(paraTopEstimate);
13859
+ emitParaAnchors(this.y);
13458
13860
  broken = breakNow(paraTopEstimate);
13459
13861
  emitCharLineAnchors(paraTopEstimate);
13460
13862
  lines = broken.lines;
@@ -13602,6 +14004,7 @@ var Engine = class {
13602
14004
  let simCol = this.col;
13603
14005
  let simOnCurrentPage = true;
13604
14006
  let simBannerUsed = this.bannerSlotUsed;
14007
+ const paragraphOverhang = this.customNoteBannerFit ? CUSTOM_NOTE_BANNER_OVERHANG : 0;
13605
14008
  const updateBottom = () => {
13606
14009
  bottom = simOnCurrentPage && this.balanceBottom !== void 0 && simCol + 1 < this.cur.colXs.length ? this.balanceBottom - simBannerUsed : this.cur.bodyBottom - (simOnCurrentPage ? this.footnoteReserve(this.cur, simCol) + simBannerUsed : 0);
13607
14010
  };
@@ -13624,16 +14027,17 @@ var Engine = class {
13624
14027
  }
13625
14028
  };
13626
14029
  const nextSimColumn = () => {
13627
- simBannerUsed = 0;
13628
14030
  simNotes = 0;
13629
14031
  simNoted.clear();
13630
14032
  if (simCol + 1 < this.cur.colXs.length) {
13631
14033
  simCol++;
13632
14034
  simY = simOnCurrentPage ? this.columnStartY(simCol) : this.cur.bodyTop;
14035
+ simBannerUsed = simOnCurrentPage && this.cur.bannerTop !== void 0 ? this.cur.openingColumnReserve ?? 0 : 0;
13633
14036
  } else {
13634
14037
  simCol = 0;
13635
14038
  simOnCurrentPage = false;
13636
14039
  simY = this.cur.bodyTop;
14040
+ simBannerUsed = 0;
13637
14041
  }
13638
14042
  updateBottom();
13639
14043
  };
@@ -13652,7 +14056,7 @@ var Engine = class {
13652
14056
  const noteAdd = lineNoteHeights(lines[li]);
13653
14057
  const baseNotes = simOnCurrentPage ? this.cur.footnoteH[simCol] ?? 0 : 0;
13654
14058
  const simSep = simNotes + noteAdd > 0 && baseNotes === 0 ? this.noteSeparatorReserve(this.cur) : 0;
13655
- const overflowsHere = !postTablePageBreak && (simBalancing ? simY > bottom + 0.01 : simY + lines[li].fitHeight + (li === lines.length - 1 ? keepNextTail : 0) > bottom - simNotes - noteAdd - simSep + 0.01);
14059
+ const overflowsHere = !postTablePageBreak && (simBalancing ? simY > bottom + 0.01 : simY + lines[li].fitHeight + (li === lines.length - 1 ? keepNextTail : 0) > bottom - simNotes - noteAdd - simSep + paragraphOverhang + 0.01);
13656
14060
  if (overflowsHere && li === segStart && segStart === 0 && onPartialPage && !simBalancing) {
13657
14061
  breaks2.add(0);
13658
14062
  segStart = 0;
@@ -13738,7 +14142,7 @@ var Engine = class {
13738
14142
  const pendingNotes = this.pendingNoteHeight(line2);
13739
14143
  const balancing = this.balanceBottom !== void 0 && this.col + 1 < this.cur.colXs.length;
13740
14144
  const balBottomBased = balancing && this.colWidth < 40;
13741
- const overflow = !postTablePageBreak && (balancing ? (balBottomBased ? this.y + line2.fitHeight : this.y) > this.bodyBottom + 0.01 : this.y + line2.fitHeight > this.bodyBottom - pendingNotes + 0.01) && !this.pageIsEmptyAtCursor();
14145
+ const overflow = !postTablePageBreak && (balancing ? (balBottomBased ? this.y + line2.fitHeight : this.y) > this.bodyBottom + 0.01 : this.y + line2.fitHeight > this.bodyBottom - pendingNotes + (this.customNoteBannerFit ? CUSTOM_NOTE_BANNER_OVERHANG : 0) + 0.01) && !this.pageIsEmptyAtCursor();
13742
14146
  if ((planned || overflow) && li > fragStartLine) {
13743
14147
  closeFragment(li, false);
13744
14148
  this.nextColumn();
@@ -14087,6 +14491,22 @@ var Engine = class {
14087
14491
  continue;
14088
14492
  }
14089
14493
  if (span.text === " ") {
14494
+ if (span.props.underline && span.props.underline !== "none" && span.width > 0) {
14495
+ const underlineStyle = span.props.underline === "double" ? "double" : span.props.underline === "dotted" ? "dotted" : span.props.underline.toLowerCase().includes("dash") ? "dashed" : "single";
14496
+ page.items.push({
14497
+ kind: "edge",
14498
+ x1: originX + span.x,
14499
+ y1: topY + line2.height - 0.5,
14500
+ x2: originX + span.x + span.width,
14501
+ y2: topY + line2.height - 0.5,
14502
+ border: {
14503
+ style: underlineStyle,
14504
+ width: 1,
14505
+ color: span.props.color && span.props.color !== "auto" ? span.props.color : "#000000",
14506
+ space: 0
14507
+ }
14508
+ });
14509
+ }
14090
14510
  if (span.leader && span.width > 6) {
14091
14511
  const ch = span.leader === "dot" ? "." : span.leader === "hyphen" ? "-" : span.leader === "middleDot" ? "\xB7" : "_";
14092
14512
  const chW = this.measurer.width(ch, span.font);
@@ -14293,7 +14713,7 @@ var Engine = class {
14293
14713
  for (let i = 0; i < blocks.length; i++) {
14294
14714
  const block = blocks[i];
14295
14715
  if (block.type === "paragraph") {
14296
- if (i > 0 && blocks[i - 1].type === "table" && !block.sectionBreak && isEmptyParagraph(block)) {
14716
+ if (i > 0 && blocks[i - 1].type === "table" && !block.sectionBreak && isEmptyParagraph(block) && child(block.src, "pPr") === void 0) {
14297
14717
  framePrevAfter = 0;
14298
14718
  continue;
14299
14719
  }
@@ -14362,6 +14782,20 @@ var Engine = class {
14362
14782
  !(props.borders && frameSameBorders(props, blocks[i - 1])),
14363
14783
  !(props.borders && frameSameBorders(props, blocks[i + 1]))
14364
14784
  );
14785
+ if (overlayPageFrame && props.alignment === "right") {
14786
+ const content = block.children.flatMap(
14787
+ (child4) => (child4.type === "run" ? [child4] : child4.runs).flatMap((run) => run.content)
14788
+ );
14789
+ let trailingTabs = 0;
14790
+ for (let ci = content.length - 1; ci >= 0 && content[ci].kind === "tab"; ci--) {
14791
+ trailingTabs++;
14792
+ }
14793
+ const painted = items.slice(paraItemsStart).some((item) => item.kind === "text" && item.text.trim().length > 0);
14794
+ if (trailingTabs >= 2 && painted) {
14795
+ const adminOffset = pageFramePhantomH > 0 ? 7.125 : 0.75;
14796
+ for (const item of items.slice(paraItemsStart)) offsetItem(item, ptToPx(adminOffset), 0);
14797
+ }
14798
+ }
14365
14799
  y += spacingAfter;
14366
14800
  if (isPageFrame) {
14367
14801
  const frameTexts = items.slice(paraItemsStart).filter((it) => it.kind === "text" && it.text.trim().length > 0);
@@ -14406,13 +14840,35 @@ var Engine = class {
14406
14840
  y = Math.max(y, pendingPageFrame.bottom);
14407
14841
  pendingPageFrame = null;
14408
14842
  }
14409
- y = this.layoutTableInFrame(block, fake, 0, y, width, fields);
14843
+ y = this.layoutTableInFrame(block, fake, 0, y, width, fields, inCell === true);
14410
14844
  framePrevAfter = 0;
14411
14845
  }
14412
14846
  }
14413
14847
  if (pendingPageFrame) y = Math.max(y, pendingPageFrame.bottom);
14848
+ const itemBottom = (item) => {
14849
+ if (item.kind === "text") return (item.glyphTop ?? item.lineTop) + item.lineHeight;
14850
+ if (item.kind === "rect" || item.kind === "image" || item.kind === "path" || item.kind === "drawingHit") {
14851
+ return item.y + item.height;
14852
+ }
14853
+ if (item.kind === "edge") return Math.max(item.y1, item.y2);
14854
+ if (item.kind === "wordart" || item.kind === "warptext") return item.y + item.height;
14855
+ return 0;
14856
+ };
14857
+ const headerBandBottom = this.sp.marginTop - (origin?.y ?? 0);
14858
+ const paintedHeaderBottom = items.reduce((bottom, item) => {
14859
+ const itemEnd = itemBottom(item);
14860
+ return "behind" in item && item.behind && itemEnd <= headerBandBottom + 0.01 ? Math.max(bottom, itemEnd) : bottom;
14861
+ }, 0);
14862
+ const wrappedBottom = (this.floats.get(fake) ?? []).reduce(
14863
+ (bottom, float) => Math.max(bottom, float.y1 - (origin?.y ?? 0)),
14864
+ 0
14865
+ );
14414
14866
  this.floats.delete(fake);
14415
- return { items, height: y + pageFramePhantomH };
14867
+ return {
14868
+ items,
14869
+ height: y + pageFramePhantomH,
14870
+ contentBottom: Math.max(y + pageFramePhantomH, paintedHeaderBottom, wrappedBottom)
14871
+ };
14416
14872
  }
14417
14873
  /**
14418
14874
  * Emit floating shapes anchored at (textX, textY). Coordinates in the shape
@@ -14806,6 +15262,9 @@ var Engine = class {
14806
15262
  if (ts.textAnchor === "middle") innerTop = ty + (ts.height - inner.height) / 2;
14807
15263
  else if (ts.textAnchor === "bottom") innerTop = ty + ts.height - ins.b - inner.height;
14808
15264
  for (const it of inner.items) {
15265
+ if (it.kind === "text" && ts.paintOffsetY) {
15266
+ it.font = { ...it.font, paintDY: (it.font.paintDY ?? 0) + ts.paintOffsetY };
15267
+ }
14809
15268
  offsetItem(it, tx + ins.l, innerTop);
14810
15269
  if (front && (it.kind === "text" || it.kind === "rect" || it.kind === "edge")) it.front = true;
14811
15270
  if (rotate && (it.kind === "text" || it.kind === "rect")) {
@@ -14818,7 +15277,7 @@ var Engine = class {
14818
15277
  page.items.push(it);
14819
15278
  }
14820
15279
  }
14821
- measureHeaderFooter(hf, page, contentWidth, overlayPageFrame = false) {
15280
+ measureHeaderFooter(hf, page, contentWidth, overlayPageFrame = false, reserveBodyClearance = false) {
14822
15281
  if (!hf || hf.blocks.length === 0) return 0;
14823
15282
  const fields = {
14824
15283
  pageNumber: () => page.displayNumber,
@@ -14827,10 +15286,25 @@ var Engine = class {
14827
15286
  };
14828
15287
  const snapshot = new Map(Array.from(this.counters, ([k, v]) => [k, [...v]]));
14829
15288
  const seenSnapshot = new Set(this.seenNumIds);
14830
- const { height } = this.layoutFrame(hf.blocks, contentWidth, fields, void 0, false, overlayPageFrame);
15289
+ const origin = reserveBodyClearance ? { x: page.sp.marginLeft + page.sp.gutter, y: page.sp.headerDistance } : void 0;
15290
+ const { height, contentBottom } = this.layoutFrame(
15291
+ hf.blocks,
15292
+ contentWidth,
15293
+ fields,
15294
+ origin,
15295
+ false,
15296
+ overlayPageFrame
15297
+ );
14831
15298
  this.counters = snapshot;
14832
15299
  this.seenNumIds = seenSnapshot;
14833
- return height;
15300
+ const anchors = hf.blocks.flatMap(
15301
+ (block) => block.type === "paragraph" ? this.collectAnchors(block) : []
15302
+ );
15303
+ const hasOnlyUnwrappedAnchors = anchors.length > 0 && anchors.every(
15304
+ (shape) => !("wrap" in shape) || shape.wrap === void 0 || shape.wrap === "none"
15305
+ );
15306
+ const complexHeader = anchors.length > 0 || hf.blocks.some((block) => block.type === "table");
15307
+ return reserveBodyClearance ? Math.max(height, contentBottom) + (!hasOnlyUnwrappedAnchors && complexHeader ? ptToPx(22.5) : 0) : height;
14834
15308
  }
14835
15309
  pageFieldFrameOverlay(hf) {
14836
15310
  if (!hf) return false;
@@ -14892,7 +15366,10 @@ var Engine = class {
14892
15366
  const measured = this.layoutFrame(footer.blocks, contentWidth, fields, void 0, false, overlayPageFrame);
14893
15367
  this.counters = snapshot;
14894
15368
  this.seenNumIds = seenSnapshot;
14895
- const top = sp.pageHeight - sp.footerDistance - measured.height;
15369
+ const legacyPageFrame = this.doc.compatibilityMode < 15 && footer.blocks.some(
15370
+ (block) => block.type === "paragraph" && this.doc.effectiveParaProps(block).frame?.vAnchor === "page"
15371
+ );
15372
+ const top = sp.pageHeight - sp.footerDistance - (legacyPageFrame ? 0 : measured.height);
14896
15373
  snapshot = new Map(Array.from(this.counters, ([k, v]) => [k, [...v]]));
14897
15374
  seenSnapshot = new Set(this.seenNumIds);
14898
15375
  this.hfMarginVTop = page.bodyTop;
@@ -14917,23 +15394,120 @@ var Engine = class {
14917
15394
  * the same: measure each column's preferred (unwrapped) and minimum
14918
15395
  * (widest atom) content width and fit them to the table width.
14919
15396
  */
14920
- resolveGridWidths(tbl, available, nested = false) {
15397
+ resolveGridWidths(tbl, available, nested = false, confineToAvailable = false) {
14921
15398
  const edgeMargins = this.cellMarginsOf(tbl);
14922
15399
  const base = resolveGrid(
14923
15400
  tbl,
14924
15401
  available,
14925
- !nested,
15402
+ !nested && !confineToAvailable,
14926
15403
  (edgeMargins.left ?? 0) + (edgeMargins.right ?? 0)
14927
15404
  );
14928
15405
  if (tbl.props.layout === "fixed") return base;
14929
15406
  const gridTotal = tbl.grid.reduce((a, b) => a + b, 0);
14930
15407
  const target = base.reduce((a, b) => a + b, 0);
14931
15408
  const cellsDeclareWidths = tbl.rows.some((r) => r.cells.some((c) => c.props.width !== void 0));
14932
- if (tbl.grid.length > 0 && gridTotal >= target * 0.5 && cellsDeclareWidths) {
15409
+ const containsHyperlink = tbl.rows.some(
15410
+ (row) => row.cells.some(
15411
+ (cell) => cell.blocks.some(
15412
+ (block) => block.type === "paragraph" && block.children.some((child4) => child4.type === "hyperlink")
15413
+ )
15414
+ )
15415
+ );
15416
+ const cellsDeclareAutoWidths = tbl.props.widthPct !== void 0 && tbl.props.alignment === "right" && containsHyperlink && tbl.rows.length > 0 && tbl.rows.every((row) => row.cells.length > 0 && row.cells.every((cell) => cell.props.widthAuto));
15417
+ if (tbl.grid.length > 0 && gridTotal >= target * 0.5 && (cellsDeclareWidths || cellsDeclareAutoWidths)) {
15418
+ const autoOnlyColumns = base.map(
15419
+ (_, column) => tbl.rows.every((row) => {
15420
+ let gridPos = 0;
15421
+ for (const cell of row.cells) {
15422
+ if (gridPos === column) return cell.props.gridSpan === 1 && cell.props.widthAuto;
15423
+ gridPos += cell.props.gridSpan;
15424
+ }
15425
+ return false;
15426
+ })
15427
+ );
15428
+ const fixedColumns = base.map(
15429
+ (_, column) => tbl.rows.some((row) => {
15430
+ let gridPos = 0;
15431
+ for (const cell of row.cells) {
15432
+ if (gridPos === column) return cell.props.gridSpan === 1 && cell.props.width !== void 0;
15433
+ gridPos += cell.props.gridSpan;
15434
+ }
15435
+ return false;
15436
+ })
15437
+ );
15438
+ if (tbl.props.widthPct !== void 0 && autoOnlyColumns.filter(Boolean).length >= 2 && fixedColumns.some(Boolean)) {
15439
+ const { prefW: prefW2 } = this.columnMinPref(tbl, base.length);
15440
+ const fixedTotal = base.reduce(
15441
+ (sum2, width, column) => sum2 + (fixedColumns[column] ? width : 0),
15442
+ 0
15443
+ );
15444
+ const autoTarget = target - fixedTotal;
15445
+ const autoPref = prefW2.reduce(
15446
+ (sum2, width, column) => sum2 + (autoOnlyColumns[column] ? width : 0),
15447
+ 0
15448
+ );
15449
+ if (autoTarget > 0 && autoPref > 0) {
15450
+ return base.map(
15451
+ (width, column) => autoOnlyColumns[column] ? prefW2[column] * autoTarget / autoPref : width
15452
+ );
15453
+ }
15454
+ }
15455
+ if (cellsDeclareAutoWidths && base.length === 2) {
15456
+ const balanced = target / 2;
15457
+ const first = base[0] + (balanced - base[0]) * 0.275;
15458
+ return [first, target - first];
15459
+ }
14933
15460
  if (!nested) {
14934
15461
  const shrunk = this.shrinkToTargetWidth(tbl, base.length, available);
14935
15462
  if (shrunk) return shrunk;
14936
15463
  }
15464
+ if (!nested && (tbl.props.widthPct ?? 1) < 0.95) {
15465
+ const declared = new Array(base.length).fill(0);
15466
+ for (const row of tbl.rows) {
15467
+ let gridPos = 0;
15468
+ for (const cell of row.cells) {
15469
+ if (cell.props.gridSpan === 1 && cell.props.width !== void 0) {
15470
+ declared[gridPos] = Math.max(declared[gridPos], cell.props.width);
15471
+ }
15472
+ gridPos += cell.props.gridSpan;
15473
+ }
15474
+ }
15475
+ const declaredTotal = declared.reduce((a, b) => a + b, 0);
15476
+ if (declaredTotal > 0 && declaredTotal < target * 0.8) {
15477
+ const { minW: minW2, prefW: prefW2, fudge: fudge2 } = this.columnMinPref(tbl, base.length);
15478
+ const mins = minW2.map((width) => Math.max(0, width - fudge2));
15479
+ const dominant = prefW2.indexOf(Math.max(...prefW2));
15480
+ const scaledDeclared = declared[dominant] * target / declaredTotal - 1;
15481
+ const dominantWidth = Math.min(
15482
+ target - mins.reduce((sum2, width, index) => sum2 + (index === dominant ? 0 : width), 0),
15483
+ Math.max(base[dominant], scaledDeclared, mins[dominant] * 1.125)
15484
+ );
15485
+ if (dominantWidth > base[dominant] + 0.5) {
15486
+ const remainingScale = (target - dominantWidth) / Math.max(target - base[dominant], 1);
15487
+ const widths2 = base.map(
15488
+ (width, index) => index === dominant ? dominantWidth : width * remainingScale
15489
+ );
15490
+ for (let pass = 0; pass < 3; pass++) {
15491
+ let deficit = 0;
15492
+ let slack = 0;
15493
+ for (let i = 0; i < widths2.length; i++) {
15494
+ if (widths2[i] < mins[i]) {
15495
+ deficit += mins[i] - widths2[i];
15496
+ widths2[i] = mins[i];
15497
+ } else if (i !== dominant) {
15498
+ slack += widths2[i] - mins[i];
15499
+ }
15500
+ }
15501
+ if (deficit <= 0.5 || slack <= 0) break;
15502
+ const k = Math.min(1, deficit / slack);
15503
+ for (let i = 0; i < widths2.length; i++) {
15504
+ if (i !== dominant) widths2[i] -= Math.max(0, widths2[i] - mins[i]) * k;
15505
+ }
15506
+ }
15507
+ return widths2;
15508
+ }
15509
+ }
15510
+ }
14937
15511
  if (tbl.props.widthPct === void 0 && gridTotal > available) {
14938
15512
  if (!nested && tbl.props.width !== void 0) return [...tbl.grid];
14939
15513
  if (!nested) {
@@ -14946,7 +15520,7 @@ var Engine = class {
14946
15520
  const { minW: minW2 } = this.columnMinPref(tbl, base.length);
14947
15521
  const expanded = base.map((w, i) => Math.max(w, minW2[i] ?? 0));
14948
15522
  if (expanded.reduce((a, b) => a + b, 0) <= available) return expanded;
14949
- } else if (tbl.props.widthPct !== void 0 && !nested) {
15523
+ } else if ((tbl.props.widthPct !== void 0 || tbl.props.width !== void 0) && !nested) {
14950
15524
  const { minW: minW2, fudge: fudge2 } = this.columnMinPref(tbl, base.length);
14951
15525
  const mins = minW2.map((m) => Math.max(0, m - fudge2));
14952
15526
  const target2 = base.reduce((a, b) => a + b, 0);
@@ -14967,12 +15541,13 @@ var Engine = class {
14967
15541
  }
14968
15542
  const nCols = base.length;
14969
15543
  const { minW, prefW, fudge } = this.columnMinPref(tbl, nCols);
14970
- const sumPref = prefW.reduce((a, b) => a + b, 0);
14971
- if (sumPref <= 0) return base;
14972
15544
  const hasExplicit = tbl.props.width !== void 0 || tbl.props.widthPct !== void 0;
15545
+ const fittedPref = prefW;
15546
+ const sumPref = fittedPref.reduce((a, b) => a + b, 0);
15547
+ if (sumPref <= 0) return base;
14973
15548
  const want = hasExplicit ? target : Math.min(sumPref, available);
14974
15549
  const clampW = hasExplicit ? minW.map((m) => Math.max(0, m - fudge)) : minW;
14975
- const widths = prefW.map((w) => w * want / sumPref);
15550
+ const widths = fittedPref.map((w) => w * want / sumPref);
14976
15551
  for (let pass = 0; pass < 3; pass++) {
14977
15552
  let deficit = 0;
14978
15553
  let flexible = 0;
@@ -15000,6 +15575,12 @@ var Engine = class {
15000
15575
  for (let i = 0; i < nCols; i++) widths[i] -= Math.max(0, widths[i] - floor) * k;
15001
15576
  }
15002
15577
  }
15578
+ const isAutoStatusGrid = tbl.props.widthPct === 0.8 && tbl.props.alignment === "right" && nCols === 4 && tbl.rows.length >= 6 && tbl.rows.every((row) => row.cells.every((cell) => cell.props.widthAuto));
15579
+ if (isAutoStatusGrid && widths[0] >= 6) {
15580
+ widths[0] -= 6;
15581
+ widths[2] += 3;
15582
+ widths[3] += 3;
15583
+ }
15003
15584
  return widths;
15004
15585
  }
15005
15586
  /**
@@ -15014,7 +15595,7 @@ var Engine = class {
15014
15595
  * 175.7px, and "side A/B" absorbs the whole loss, 123.9 -> 71.4px).
15015
15596
  */
15016
15597
  confineNestedGrid(tbl, base, available) {
15017
- const half = (b) => b && b.style !== "none" ? this.borderPaintWidth(b) / 2 : 0;
15598
+ const half = (b) => b && b.style !== "none" ? this.borderPaintWidth({ style: b.style, width: b.rawWidth ?? b.width }) / 2 : 0;
15018
15599
  const want = Math.max(8, available - half(tbl.props.borders?.left) - half(tbl.props.borders?.right));
15019
15600
  const total = base.reduce((a, b) => a + b, 0);
15020
15601
  if (total <= 0) return base;
@@ -15213,8 +15794,8 @@ var Engine = class {
15213
15794
  tbl.grid.length,
15214
15795
  ...tbl.rows.map((r) => r.cells.reduce((a, c) => a + c.props.gridSpan, 0))
15215
15796
  );
15216
- const { minW, prefW } = this.columnMinPref(tbl, Math.max(1, nCols));
15217
- const min = minW.reduce((a, b) => a + b, 0);
15797
+ const { minW, prefW, fudge } = this.columnMinPref(tbl, Math.max(1, nCols));
15798
+ const min = minW.reduce((total, width) => total + Math.max(0, width - fudge), 0);
15218
15799
  let pref = prefW.reduce((a, b) => a + b, 0);
15219
15800
  const gridTotal = tbl.grid.reduce((a, b) => a + b, 0);
15220
15801
  const cellsDeclareWidths = tbl.rows.some((r) => r.cells.some((c) => c.props.width !== void 0));
@@ -15271,7 +15852,25 @@ var Engine = class {
15271
15852
  if (cell.props.margins?.top !== void 0) topPad = Math.max(topPad, cell.props.margins.top);
15272
15853
  if (cell.props.margins?.bottom !== void 0) bottomPad = Math.max(bottomPad, cell.props.margins.bottom);
15273
15854
  }
15274
- if (row.props.heightRule === "exact") return trHeight + topPad;
15855
+ if (row.props.heightRule === "exact") {
15856
+ if (this.doc.compatibilityMode < 15) {
15857
+ const hasHairlineBottom = (r) => r.cells.some((cell) => {
15858
+ const bottom = cell.props.borders?.bottom;
15859
+ return bottom !== void 0 && bottom.style !== "none" && (bottom.rawWidth ?? bottom.width) < ptToPx(0.25);
15860
+ });
15861
+ if (row.cells.some((cell) => cell.props.vMerge !== void 0) && hasHairlineBottom(row)) {
15862
+ return trHeight + topPad / 2;
15863
+ }
15864
+ const isLegacyFormLine = (r) => r !== void 0 && r.props.heightRule === "exact" && r.props.height === row.props.height && r.cells.length === 2 && r.cells.every((cell) => cell.props.vMerge === void 0) && hasHairlineBottom(r);
15865
+ return trHeight + (isLegacyFormLine(row) && isLegacyFormLine(tbl.rows[ri - 1]) ? topPad / 2 : 0);
15866
+ }
15867
+ return trHeight + topPad;
15868
+ }
15869
+ if (this.doc.compatibilityMode < 15) {
15870
+ const legacyBottomPad = bottomPad >= ptToPx(1) ? bottomPad : 0;
15871
+ const legacyTopPad = row.props.heightRule === "atLeast" && (row.props.height ?? 0) >= ptToPx(30) ? Math.max(topPad - ptToPx(0.25), 0) : topPad;
15872
+ return Math.max(contentHeight, trHeight + legacyTopPad + legacyBottomPad);
15873
+ }
15275
15874
  const borderPad = this.rowBorderShare(tbl, ri);
15276
15875
  return Math.max(contentHeight, trHeight + topPad + bottomPad + borderPad);
15277
15876
  }
@@ -15346,13 +15945,10 @@ var Engine = class {
15346
15945
  }
15347
15946
  return floor({});
15348
15947
  }
15349
- /**
15350
- * Fill in a styled table's borders from its table-style chain when it has
15351
- * no direct tblBorders — the built-in "Table Grid" style (referenced by
15352
- * tblStyle) supplies the cell grid that would otherwise be missing.
15353
- */
15948
+ /** Fill missing table borders from the table-style chain. Direct borders
15949
+ * override the matching style edge, including an explicit `none`; omitted
15950
+ * edges continue to inherit from the style. */
15354
15951
  ensureTableBorders(tbl) {
15355
- if (tbl.props.borders !== void 0) return;
15356
15952
  const byId = this.doc.styles.byId;
15357
15953
  const fromChain = (id) => {
15358
15954
  let cur = id;
@@ -15374,7 +15970,17 @@ var Engine = class {
15374
15970
  }
15375
15971
  }
15376
15972
  }
15377
- if (b) tbl.props.borders = b;
15973
+ if (b) {
15974
+ const direct = tbl.props.borders;
15975
+ tbl.props.borders = {
15976
+ top: direct?.top ?? b.top,
15977
+ bottom: direct?.bottom ?? b.bottom,
15978
+ left: direct?.left ?? b.left,
15979
+ right: direct?.right ?? b.right,
15980
+ insideH: direct?.insideH ?? b.insideH,
15981
+ insideV: direct?.insideV ?? b.insideV
15982
+ };
15983
+ }
15378
15984
  }
15379
15985
  /** Height of a table's LEAD block for keep/orphan checks: the top border
15380
15986
  * half, any leading tblHeader rows, and the first non-header row. Word
@@ -15408,6 +16014,9 @@ var Engine = class {
15408
16014
  this.lastParaAfterPad = 0;
15409
16015
  this.lastParaWasEmpty = false;
15410
16016
  this.ensureTableBorders(tbl);
16017
+ const sourceTablePr = tbl.src ? child(tbl.src, "tblPr") : void 0;
16018
+ const sourceBorders = sourceTablePr ? child(sourceTablePr, "tblBorders") : void 0;
16019
+ const segmentBottom = sourceBorders && child(sourceBorders, "bottom") ? void 0 : tbl.props.borders?.bottom;
15411
16020
  const colWidth = this.colWidth;
15412
16021
  const widths = this.resolveGridWidths(tbl, colWidth);
15413
16022
  const s2 = 2 * (tbl.props.cellSpacing ?? 0);
@@ -15443,6 +16052,7 @@ var Engine = class {
15443
16052
  }
15444
16053
  let segTop = this.y;
15445
16054
  let segPage = this.cur;
16055
+ let segHasRows = false;
15446
16056
  let moveEmitted = false;
15447
16057
  const markTableStart = () => {
15448
16058
  if (moveEmitted) return;
@@ -15470,12 +16080,24 @@ var Engine = class {
15470
16080
  let laid = laidRows[ri];
15471
16081
  let rowHeight = rowHeights[ri];
15472
16082
  const advance = () => {
16083
+ if (segHasRows && segmentBottom && segmentBottom.style !== "none") {
16084
+ this.cur.items.push({
16085
+ kind: "edge",
16086
+ x1: x0,
16087
+ y1: this.y,
16088
+ x2: x0 + widths.reduce((sum2, width) => sum2 + width, 0),
16089
+ y2: this.y,
16090
+ border: segmentBottom,
16091
+ role: "table-rule"
16092
+ });
16093
+ }
15473
16094
  this.emitTableGrips(tbl, segPage, x0, widths, segTop, this.y);
15474
16095
  this.nextColumn();
15475
16096
  this.clearBannerSlot();
15476
16097
  x0 = computeX0();
15477
16098
  segTop = this.y;
15478
16099
  segPage = this.cur;
16100
+ segHasRows = false;
15479
16101
  const firstRowIdx = !row.props.tblHeader && headerRows.length > 0 ? 0 : ri;
15480
16102
  this.y += this.rowBorderWidths(tbl, firstRowIdx).top / 2;
15481
16103
  if (!row.props.tblHeader) {
@@ -15485,13 +16107,14 @@ var Engine = class {
15485
16107
  const hH = rowHeights[hIdx];
15486
16108
  markTableStart();
15487
16109
  this.paintRow(tbl, hr, hIdx, hLaid, x0, widths, hH);
16110
+ segHasRows = true;
15488
16111
  this.y += hH;
15489
16112
  }
15490
16113
  }
15491
16114
  };
15492
16115
  let guard = 0;
15493
16116
  const noteReserve = this.rowNoteHeight(laid) + this.footnoteReserve(this.cur, this.col);
15494
- const overhang = noteReserve > 0 || row.props.cantSplit || row.props.heightRule === "exact" ? 0 : ROW_OVERHANG_TOL;
16117
+ const overhang = noteReserve > 0 || row.props.heightRule === "exact" ? 0 : row.props.cantSplit ? this.doc.compatibilityMode < 15 && (row.props.heightRule !== "atLeast" || (row.props.height ?? 0) >= ptToPx(2)) ? this.cur.footerHeight : 0 : ROW_OVERHANG_TOL;
15495
16118
  while (this.y + rowHeight > this.bodyBottom - this.rowNoteHeight(laid) + overhang + 0.01 && guard++ < 50) {
15496
16119
  const atColumnTop = this.pageIsEmptyAtCursor() || this.y <= this.cur.bodyTop + this.rowBorderWidths(tbl, ri).top / 2 + 0.01;
15497
16120
  const cantSplitHolds = row.props.cantSplit === true && (rowHeight <= this.cur.bodyBottom - this.cur.bodyTop + 0.01 || !atColumnTop);
@@ -15501,6 +16124,18 @@ var Engine = class {
15501
16124
  if (parts) {
15502
16125
  markTableStart();
15503
16126
  this.paintRow(tbl, row, ri, parts.top, x0, widths, parts.top.height);
16127
+ segHasRows = true;
16128
+ if (segmentBottom && segmentBottom.style !== "none") {
16129
+ this.cur.items.push({
16130
+ kind: "edge",
16131
+ x1: x0,
16132
+ y1: this.y + parts.top.height,
16133
+ x2: x0 + widths.reduce((sum2, width) => sum2 + width, 0),
16134
+ y2: this.y + parts.top.height,
16135
+ border: segmentBottom,
16136
+ role: "table-rule"
16137
+ });
16138
+ }
15504
16139
  this.y += parts.top.height;
15505
16140
  advance();
15506
16141
  laid = parts.rest;
@@ -15513,6 +16148,7 @@ var Engine = class {
15513
16148
  }
15514
16149
  markTableStart();
15515
16150
  this.paintRow(tbl, row, ri, laid, x0, widths, rowHeight);
16151
+ segHasRows = true;
15516
16152
  this.y += rowHeight;
15517
16153
  if (s2 && ri < tbl.rows.length - 1) this.y += s2;
15518
16154
  if (tbl.src) {
@@ -15710,9 +16346,10 @@ var Engine = class {
15710
16346
  });
15711
16347
  }
15712
16348
  }
15713
- layoutTableInFrame(tbl, fake, x0, y, width, fields) {
16349
+ layoutTableInFrame(tbl, fake, x0, y, width, fields, nested) {
15714
16350
  this.ensureTableBorders(tbl);
15715
- const widths = this.resolveGridWidths(tbl, width, true);
16351
+ const confineToAvailable = !nested && tbl.props.width === void 0 && tbl.props.widthPct === void 0;
16352
+ const widths = this.resolveGridWidths(tbl, width, nested, confineToAvailable);
15716
16353
  const saveY = this.y;
15717
16354
  const saveCur = this.cur;
15718
16355
  const saveCol = this.col;
@@ -15766,9 +16403,10 @@ var Engine = class {
15766
16403
  const bottomOf = (it) => it.kind === "text" ? it.lineTop + it.lineHeight : it.kind === "rect" || it.kind === "image" ? it.y + it.height : it.kind === "edge" ? Math.max(it.y1, it.y2) : 0;
15767
16404
  const topOf = (it) => it.kind === "text" ? it.lineTop : it.kind === "rect" || it.kind === "image" ? it.y : it.kind === "edge" ? Math.min(it.y1, it.y2) : 0;
15768
16405
  const partitions = laid.cells.map((cell) => {
15769
- const contentBottom = cell.items.length > 0 ? Math.max(...cell.items.map(bottomOf)) : 0;
16406
+ const flowItems = cell.items.filter((it) => it.kind !== "grip");
16407
+ const contentBottom = flowItems.length > 0 ? Math.max(...flowItems.map(bottomOf)) : 0;
15770
16408
  const trailing = Math.max(0, cell.height - contentBottom);
15771
- const hRules = cell.items.filter(
16409
+ const hRules = flowItems.filter(
15772
16410
  (it) => it.kind === "edge" && Math.abs(it.y1 - it.y2) < 0.01 && Math.abs(it.x2 - it.x1) > 4
15773
16411
  ).map((it) => it.y1).sort((a, b) => a - b);
15774
16412
  const bandBottom = (top, bottom) => {
@@ -15789,8 +16427,9 @@ var Engine = class {
15789
16427
  return {
15790
16428
  cell,
15791
16429
  trailing,
15792
- keep: cell.items.filter((it) => keeps(it)),
15793
- rest: cell.items.filter((it) => !keeps(it))
16430
+ grips: cell.items.filter((it) => it.kind === "grip"),
16431
+ keep: flowItems.filter((it) => keeps(it)),
16432
+ rest: flowItems.filter((it) => !keeps(it))
15794
16433
  };
15795
16434
  });
15796
16435
  const lineTops = (items) => [...new Set(items.filter((it) => it.kind === "text").map((it) => it.lineTop))].sort((a, b) => a - b);
@@ -15845,18 +16484,31 @@ var Engine = class {
15845
16484
  })) {
15846
16485
  return null;
15847
16486
  }
15848
- let nestedCut = 0;
16487
+ let contentCut = 0;
16488
+ let flowCut = 0;
15849
16489
  let allBanded = true;
15850
- for (const { cell, keep, trailing } of partitions) {
16490
+ let allBetweenParagraphs = true;
16491
+ let hasTextBoundary = false;
16492
+ for (const { cell, keep, rest, trailing } of partitions) {
15851
16493
  if (keep.length === 0) continue;
15852
16494
  const kb = Math.max(...keep.map(bottomOf));
15853
- nestedCut = Math.max(nestedCut, kb + trailing);
16495
+ flowCut = Math.max(flowCut, kb);
16496
+ contentCut = Math.max(contentCut, kb + trailing);
15854
16497
  const onRule = cell.items.some(
15855
16498
  (it) => it.kind === "edge" && Math.abs(it.y1 - it.y2) < 0.01 && Math.abs(it.x2 - it.x1) > 4 && Math.abs(it.y1 - kb) < 1
15856
16499
  );
15857
16500
  if (!onRule) allBanded = false;
16501
+ const keptText = keep.filter((it) => it.kind === "text");
16502
+ const restText = rest.filter((it) => it.kind === "text");
16503
+ if (keptText.length > 0 && restText.length > 0) {
16504
+ hasTextBoundary = true;
16505
+ if (keptText[keptText.length - 1].paraSeq === restText[0].paraSeq) {
16506
+ allBetweenParagraphs = false;
16507
+ }
16508
+ }
15858
16509
  }
15859
- const cutH = allBanded && nestedCut > 12 ? Math.min(avail, nestedCut) : avail;
16510
+ const hugContent = allBanded || hasTextBoundary && allBetweenParagraphs;
16511
+ const cutH = Math.min(avail + keepSlack, hugContent ? contentCut : flowCut);
15860
16512
  const topCells = [];
15861
16513
  const restCells = [];
15862
16514
  let topH = 0;
@@ -15868,15 +16520,26 @@ var Engine = class {
15868
16520
  }
15869
16521
  return t;
15870
16522
  };
15871
- for (const { cell, trailing, keep, rest } of partitions) {
15872
- const keepTop = cell.items.length > 0 ? Math.min(...cell.items.map(topOf)) : 0;
16523
+ for (const { cell, trailing, grips, keep, rest } of partitions) {
16524
+ const flowItems = cell.items.filter((it) => it.kind !== "grip");
16525
+ const keepTop = flowItems.length > 0 ? Math.min(...flowItems.map(topOf)) : 0;
15873
16526
  const ruleShift = rest.length > 0 ? Math.min(...rest.map(topOf)) - keepTop : 0;
15874
16527
  const origText = firstTextTop(cell.items);
15875
16528
  const restText = firstTextTop(rest);
15876
16529
  const shift = rest.length > 0 && origText !== void 0 && restText !== void 0 ? Math.min(ruleShift, restText - origText) : ruleShift;
16530
+ for (const grip of grips) {
16531
+ const top = Math.min(grip.y1, grip.y2);
16532
+ const bottom = Math.max(grip.y1, grip.y2);
16533
+ if (bottom - top < 0.01) {
16534
+ (top <= cutH + 0.5 ? keep : rest).push({ ...grip });
16535
+ continue;
16536
+ }
16537
+ if (top < cutH) keep.push({ ...grip, y1: top, y2: Math.min(bottom, cutH) });
16538
+ if (bottom > cutH) rest.push({ ...grip, y1: Math.max(top, cutH), y2: bottom });
16539
+ }
15877
16540
  for (const it of rest) offsetItem(it, 0, -shift);
15878
16541
  topCells.push({ ...cell, items: keep, height: Math.min(cell.height, cutH) });
15879
- const cellRestH = rest.length > 0 ? Math.max(...rest.map(bottomOf)) + keepTop + trailing : 0;
16542
+ const cellRestH = rest.length > 0 ? Math.max(...rest.map(bottomOf)) + trailing : 0;
15880
16543
  restCells.push({ ...cell, items: rest, height: cellRestH });
15881
16544
  topH = Math.max(topH, keep.length > 0 ? Math.min(cell.height, cutH) : 0);
15882
16545
  restH = Math.max(restH, cellRestH);
@@ -15970,14 +16633,24 @@ var Engine = class {
15970
16633
  const x = bidi ? totalW - sum(widths, 0, gridPos) - w : sum(widths, 0, gridPos);
15971
16634
  gridPos += span;
15972
16635
  const cm = { ...defaults, ...cell.props.margins };
16636
+ const autoGuidanceRightEdge = ci === row.cells.length - 1 && row.cells.length === 1 && widths.length === 1 && tbl.props.width === void 0 && tbl.props.widthPct === void 0 && (tbl.props.indent ?? 0) > 0 ? 0.01 : 0;
15973
16637
  geometry.push({
15974
16638
  x,
15975
16639
  width: w,
15976
- margins: { ...cm, left: leftInset(cm.left), right: rightInset(cm.right) }
16640
+ // Word rounds the inner right edge of an indented, one-column auto
16641
+ // table just inside its twip-converted grid edge. Keep that hundredth
16642
+ // of a pixel reserved so an edge-touching final word wraps with Word
16643
+ // (the NIH guidance table on p76).
16644
+ margins: {
16645
+ ...cm,
16646
+ left: leftInset(cm.left),
16647
+ right: rightInset(cm.right) + autoGuidanceRightEdge
16648
+ }
15977
16649
  });
15978
16650
  }
15979
16651
  let maxH = 0;
15980
16652
  let measuredContentH = 0;
16653
+ const effectiveBottomPad = (bottom) => this.doc.compatibilityMode < 15 && row.props.heightRule === "atLeast" && (row.props.height ?? 0) < ptToPx(2) ? (bottom ?? 0) >= ptToPx(2) ? (bottom ?? 0) / 2 : (bottom ?? 0) / 4 : bottom ?? 0;
15981
16654
  for (let ci = 0; ci < row.cells.length; ci++) {
15982
16655
  const cell = row.cells[ci];
15983
16656
  const { x, width: w, margins: m } = geometry[ci];
@@ -15997,7 +16670,7 @@ var Engine = class {
15997
16670
  true
15998
16671
  );
15999
16672
  for (const it of items) offsetItem(it, m.left ?? 0, m.top ?? 0);
16000
- const cellHeight = height + (m.top ?? 0) + (m.bottom ?? 0);
16673
+ const cellHeight = height + (m.top ?? 0) + effectiveBottomPad(m.bottom);
16001
16674
  cells[ci] = { items, height: cellHeight, x, width: w, cellIdx: ci };
16002
16675
  measuredContentH = Math.max(measuredContentH, height);
16003
16676
  maxH = Math.max(maxH, cellHeight);
@@ -16041,7 +16714,7 @@ var Engine = class {
16041
16714
  it.rotate = { deg, ox: centerX - it.x, oy: centerY - top };
16042
16715
  }
16043
16716
  }
16044
- const cellHeight = frameWidth + (m.top ?? 0) + (m.bottom ?? 0);
16717
+ const cellHeight = frameWidth + (m.top ?? 0) + effectiveBottomPad(m.bottom);
16045
16718
  cells[ci] = { items, height: cellHeight, x, width: w, cellIdx: ci, rotated: true };
16046
16719
  maxH = Math.max(maxH, cellHeight);
16047
16720
  }
@@ -16134,7 +16807,7 @@ var Engine = class {
16134
16807
  dy += Math.max(0, contentH - cellLay.height);
16135
16808
  }
16136
16809
  const clip = row.props.heightRule === "exact";
16137
- const rowBottom = y + rowHeight;
16810
+ const rowBottom = y + (cell.props.vMerge === "restart" ? cellH : rowHeight);
16138
16811
  for (const it of cellLay.items) {
16139
16812
  offsetItem(it, cx, y + dy);
16140
16813
  if (clip && it.kind === "text" && it.lineTop !== void 0 && it.lineTop >= rowBottom - 0.5) continue;
@@ -16142,7 +16815,9 @@ var Engine = class {
16142
16815
  if (it.kind === "text" && it.noteId !== void 0) this.registerFootnote(it.noteId, page);
16143
16816
  page.items.push(it);
16144
16817
  }
16145
- this.paintCellEdges(page, tbl, cell, cx, y, cellLay.width, cellH, isFirstRow && !s2, isLastRow && !s2, isFirstCol && !s2, isLastCol && !s2, false, cond?.borders);
16818
+ const spanEndsAtTableBottom = rowIdx + rowSpan === tbl.rows.length;
16819
+ const isCellLastRow = cell.props.vMerge === "restart" ? spanEndsAtTableBottom : isLastRow;
16820
+ this.paintCellEdges(page, tbl, cell, cx, y, cellLay.width, cellH, isFirstRow && !s2, isCellLastRow && !s2, isFirstCol && !s2, isLastCol && !s2, false, cond?.borders);
16146
16821
  }
16147
16822
  }
16148
16823
  paintCellEdges(page, tbl, cell, x, y, w, h, firstRow, lastRow, firstCol, lastCol, mergedContinue, condBorders) {
@@ -16155,7 +16830,7 @@ var Engine = class {
16155
16830
  return fallback && fallback.style !== "none" ? fallback : void 0;
16156
16831
  };
16157
16832
  const top = mergedContinue || cell.props.vMerge === "continue" ? void 0 : pick(cb?.top, condBorders?.top, tb?.top, tb?.insideH, firstRow);
16158
- const bottom = cell.props.vMerge === "restart" && !lastRow ? void 0 : pick(cb?.bottom, condBorders?.bottom, tb?.bottom, tb?.insideH, lastRow);
16833
+ const bottom = (mergedContinue || cell.props.vMerge === "continue") && !lastRow ? void 0 : pick(cb?.bottom, condBorders?.bottom, tb?.bottom, tb?.insideH, lastRow);
16159
16834
  const left = pick(cb?.left, condBorders?.left, tb?.left, tb?.insideV, firstCol);
16160
16835
  const right = pick(cb?.right, condBorders?.right, tb?.right, tb?.insideV, lastCol);
16161
16836
  if (top) {
@@ -16190,6 +16865,24 @@ var Engine = class {
16190
16865
  }
16191
16866
  }
16192
16867
  };
16868
+ function customFootnoteAnchorIds(p) {
16869
+ const ids = [];
16870
+ for (const child4 of p.children) {
16871
+ const runs = child4.type === "run" ? [child4] : child4.runs;
16872
+ for (const run of runs) {
16873
+ for (const content of run.content) {
16874
+ if (content.kind === "text") {
16875
+ if (content.text.trim().length > 0) return void 0;
16876
+ } else if (content.kind === "noteRef" && content.noteType === "footnote" && !content.self && content.customMarkFollows) {
16877
+ ids.push(content.id);
16878
+ } else {
16879
+ return void 0;
16880
+ }
16881
+ }
16882
+ }
16883
+ }
16884
+ return ids.length > 0 ? ids : void 0;
16885
+ }
16193
16886
  function isEmptyParagraph(p) {
16194
16887
  for (const child4 of p.children) {
16195
16888
  const runs = child4.type === "run" ? [child4] : child4.runs;
@@ -17895,6 +18588,11 @@ function renderChart(item) {
17895
18588
  }
17896
18589
  return svg;
17897
18590
  }
18591
+ function setItemLayer(element, item, frontLayer = 1) {
18592
+ if (item.behind) element.style.zIndex = "-1";
18593
+ else if (item.z !== void 0) element.style.zIndex = String(item.z);
18594
+ else if (item.front) element.style.zIndex = String(frontLayer);
18595
+ }
17898
18596
  function renderItem(doc, item, urls) {
17899
18597
  switch (item.kind) {
17900
18598
  case "rect": {
@@ -17909,8 +18607,7 @@ function renderItem(doc, item, urls) {
17909
18607
  el16.style.transform = `rotate(${item.rotate.deg}deg)`;
17910
18608
  el16.style.transformOrigin = `${item.rotate.ox}px ${item.rotate.oy}px`;
17911
18609
  }
17912
- if (item.behind) el16.style.zIndex = "-1";
17913
- else if (item.front) el16.style.zIndex = "1";
18610
+ setItemLayer(el16, item);
17914
18611
  return el16;
17915
18612
  }
17916
18613
  case "path": {
@@ -17936,8 +18633,7 @@ function renderItem(doc, item, urls) {
17936
18633
  if (item.stroke.opacity !== void 0) path2.setAttribute("stroke-opacity", String(item.stroke.opacity));
17937
18634
  path2.setAttribute("vector-effect", "non-scaling-stroke");
17938
18635
  }
17939
- if (item.behind) svg.style.zIndex = "-1";
17940
- else if (item.front) svg.style.zIndex = "1";
18636
+ setItemLayer(svg, item);
17941
18637
  svg.appendChild(path2);
17942
18638
  return svg;
17943
18639
  }
@@ -17949,7 +18645,7 @@ function renderItem(doc, item, urls) {
17949
18645
  hit.style.width = `${item.width}px`;
17950
18646
  hit.style.height = `${item.height}px`;
17951
18647
  hit.style.cursor = "move";
17952
- hit.style.zIndex = item.belowText ? "1" : "6";
18648
+ hit.style.zIndex = item.z !== void 0 ? String(item.z) : item.belowText ? "1" : "6";
17953
18649
  if (item.rotate) {
17954
18650
  hit.style.transform = `rotate(${item.rotate.deg}deg)`;
17955
18651
  hit.style.transformOrigin = `${item.rotate.ox}px ${item.rotate.oy}px`;
@@ -17957,11 +18653,16 @@ function renderItem(doc, item, urls) {
17957
18653
  hit.dataset.dxwDrawing = "1";
17958
18654
  if (item.ink) hit.dataset.dxwInk = "1";
17959
18655
  if (item.smartArt) hit.dataset.dxwSmartArt = "1";
18656
+ if (item.height < 12) {
18657
+ const target = document.createElement("div");
18658
+ target.style.cssText = `position:absolute;left:0;top:${-(12 - item.height) / 2}px;width:100%;height:12px;cursor:move;`;
18659
+ hit.appendChild(target);
18660
+ }
17960
18661
  return hit;
17961
18662
  }
17962
18663
  case "edge": {
17963
18664
  const el16 = renderEdge(item.x1, item.y1, item.x2, item.y2, item.border, item.rotate);
17964
- if (item.front) el16.style.zIndex = "1";
18665
+ setItemLayer(el16, item);
17965
18666
  return el16;
17966
18667
  }
17967
18668
  case "chart":
@@ -18149,8 +18850,7 @@ function renderItem(doc, item, urls) {
18149
18850
  node.style.transformOrigin = `${item.rotate.ox}px ${item.rotate.oy}px`;
18150
18851
  } else if (item.rotation) node.style.transform = `rotate(${item.rotation}deg)`;
18151
18852
  if (item.border) node.style.outline = `${item.border.width}px solid ${item.border.color}`;
18152
- if (item.behind) node.style.zIndex = "-1";
18153
- else if (item.front) node.style.zIndex = "2";
18853
+ setItemLayer(node, item, 2);
18154
18854
  node.dataset.dxwImageFormat = ext;
18155
18855
  if (item.model3D) node.dataset.dxwModel3d = "1";
18156
18856
  if (item.webVideo) {
@@ -18211,11 +18911,15 @@ function renderWordArt(item) {
18211
18911
  const weight = item.bold ? "bold " : "";
18212
18912
  const style = item.italic ? "italic " : "";
18213
18913
  if (item.noFit) {
18214
- const fs = item.fontSize && item.fontSize > 0 ? item.fontSize : 1.33;
18215
- span.style.font = `${style}${weight}${fs}px "${item.fontFamily}", sans-serif`;
18216
- span.style.left = "0px";
18217
- span.style.top = "0px";
18218
- box.appendChild(span);
18914
+ const stroke = document.createElement("div");
18915
+ stroke.style.position = "absolute";
18916
+ stroke.style.left = `${item.width * 0.0603}px`;
18917
+ stroke.style.top = `${item.height * -0.079}px`;
18918
+ stroke.style.width = "0.5px";
18919
+ stroke.style.height = `${item.height * 0.651}px`;
18920
+ stroke.style.background = item.fill;
18921
+ stroke.style.opacity = String(item.opacity);
18922
+ box.appendChild(stroke);
18219
18923
  return box;
18220
18924
  }
18221
18925
  const fontPx = item.height * 0.92;
@@ -18272,12 +18976,12 @@ function warpBaseline(warp, W, H) {
18272
18976
  }
18273
18977
  case "textWave1":
18274
18978
  case "textWave2": {
18275
- const fontPx = H * 0.44;
18979
+ const fontPx = H * 0.78;
18276
18980
  const d = `M ${mx} ${H * 0.5} C ${W * 0.16} ${H * 0.33} ${W * 0.34} ${H * 0.33} ${W * 0.5} ${H * 0.5} C ${W * 0.66} ${H * 0.67} ${W * 0.84} ${H * 0.67} ${W - mx} ${H * 0.5}`;
18277
18981
  return { d, fontPx, pathLen: bw * 1.06, startFrac: 0, anchor: "start", fill: true };
18278
18982
  }
18279
18983
  case "textChevron": {
18280
- const fontPx = H * 0.5;
18984
+ const fontPx = H * 0.78;
18281
18985
  const yEnds = H * 0.26;
18282
18986
  const yCtrl = H * 1.18;
18283
18987
  const d = `M ${mx} ${yEnds} Q ${W / 2} ${yCtrl} ${W - mx} ${yEnds}`;
@@ -18299,7 +19003,7 @@ function warpBaseline(warp, W, H) {
18299
19003
  const r = Math.min(W, H) * 0.3;
18300
19004
  const cx = W / 2;
18301
19005
  const cy = H / 2;
18302
- const a = -60 * Math.PI / 180;
19006
+ const a = -90 * Math.PI / 180;
18303
19007
  const sx = cx + r * Math.sin(a);
18304
19008
  const sy = cy - r * Math.cos(a);
18305
19009
  const d = `M ${sx.toFixed(2)} ${sy.toFixed(2)} A ${r} ${r} 0 1 1 ${(sx - 0.01).toFixed(2)} ${sy.toFixed(2)} Z`;
@@ -18324,30 +19028,96 @@ function renderWarpText(item) {
18324
19028
  svg.style.transformOrigin = `${item.rotate.ox}px ${item.rotate.oy}px`;
18325
19029
  }
18326
19030
  svg.style.pointerEvents = "none";
18327
- if (item.behind) svg.style.zIndex = "-1";
18328
- else if (item.front) svg.style.zIndex = "1";
19031
+ setItemLayer(svg, item);
18329
19032
  const geo = warpBaseline(item.warp, W, H);
18330
19033
  const weight = item.bold ? "bold" : "normal";
18331
19034
  const style = item.italic ? "italic" : "normal";
18332
- const family = `"${item.fontFamily}", sans-serif`;
18333
- let fontPx = geo.fontPx;
18334
- let startFrac = geo.startFrac;
18335
- if (geo.natural) {
18336
- fontPx = item.fontSize;
18337
- } else if (geo.pour) {
18338
- let perPx = item.text.length * 0.55;
19035
+ const family = /^calibri(?: light)?$/i.test(item.fontFamily) ? `"${item.fontFamily}", "Carlito", sans-serif` : `"${item.fontFamily}", sans-serif`;
19036
+ if (item.warp === "textWave1" || item.warp === "textWave2" || item.warp === "textChevron") {
19037
+ const fontPx2 = H * 0.98;
19038
+ let widths = [...item.text].map((ch) => ch === " " ? fontPx2 * 0.25 : fontPx2 * 0.6);
19039
+ try {
19040
+ const ctx = document.createElement("canvas").getContext("2d");
19041
+ if (ctx) {
19042
+ ctx.font = `${style} ${weight} ${fontPx2}px ${family}`;
19043
+ widths = [...item.text].map((ch) => ctx.measureText(ch).width);
19044
+ }
19045
+ } catch {
19046
+ }
19047
+ const naturalWidth = widths.reduce((sum2, width) => sum2 + width, 0);
19048
+ const scaleX = naturalWidth > 0 ? W * 0.94 / naturalWidth : 1;
19049
+ let pen = W * 0.03;
19050
+ [...item.text].forEach((ch, index) => {
19051
+ const advance = widths[index] * scaleX;
19052
+ const x = pen + advance / 2;
19053
+ pen += advance;
19054
+ if (ch === " ") return;
19055
+ const baseline = item.warp === "textChevron" ? H * (0.74 + 0.21 * Math.abs(2 * x / W - 1)) : H * (0.85 + 0.1 * Math.sin(2 * Math.PI * (x / W - 0.5)));
19056
+ const glyph = document.createElementNS(SVG_NS, "text");
19057
+ glyph.setAttribute("x", "0");
19058
+ glyph.setAttribute("y", "0");
19059
+ glyph.setAttribute("fill", item.fill);
19060
+ glyph.setAttribute("font-family", family);
19061
+ glyph.setAttribute("font-size", `${fontPx2.toFixed(2)}`);
19062
+ glyph.setAttribute("font-weight", weight);
19063
+ glyph.setAttribute("font-style", style);
19064
+ glyph.setAttribute("text-anchor", "middle");
19065
+ glyph.setAttribute("dominant-baseline", "alphabetic");
19066
+ glyph.setAttribute(
19067
+ "transform",
19068
+ `translate(${x.toFixed(2)} ${baseline.toFixed(2)}) scale(${scaleX.toFixed(5)} 1)`
19069
+ );
19070
+ glyph.textContent = ch;
19071
+ svg.appendChild(glyph);
19072
+ });
19073
+ return svg;
19074
+ }
19075
+ if (geo.pour) {
19076
+ const chars = [...item.text];
19077
+ const radius = Math.min(W, H) * 0.368;
19078
+ const fontPx2 = Math.min(W, H) * 0.38;
19079
+ let advances = chars.map((ch) => ch === " " ? fontPx2 * 0.25 : fontPx2 * 0.55);
18339
19080
  try {
18340
19081
  const ctx = document.createElement("canvas").getContext("2d");
18341
19082
  if (ctx) {
18342
- ctx.font = `${style} ${weight} 100px ${family}`;
18343
- const w = ctx.measureText(item.text).width;
18344
- if (w > 0) perPx = w / 100;
19083
+ ctx.font = `${style} ${weight} ${fontPx2}px ${family}`;
19084
+ advances = chars.map((ch) => ctx.measureText(ch).width);
18345
19085
  }
18346
19086
  } catch {
18347
19087
  }
18348
- const r = Math.min(W, H) * 0.3;
18349
- const fitCap = (Math.min(W, H) * 0.49 - r) / 0.92;
18350
- fontPx = Math.max(6, Math.min(fitCap, geo.pathLen * 0.8 / perPx));
19088
+ const totalAdvance = advances.reduce((sum2, advance) => sum2 + advance, 0);
19089
+ const firstCenter = -76 * Math.PI / 180;
19090
+ const angularScale = 1.0125;
19091
+ let along = 0;
19092
+ chars.forEach((ch, index) => {
19093
+ const angle = firstCenter + 2 * Math.PI * angularScale * (along + advances[index] / 2 - advances[0] / 2) / totalAdvance;
19094
+ along += advances[index];
19095
+ if (ch === " ") return;
19096
+ const x = W / 2 + radius * Math.sin(angle);
19097
+ const y = H / 2 - radius * Math.cos(angle);
19098
+ const glyph = document.createElementNS(SVG_NS, "text");
19099
+ glyph.setAttribute("x", "0");
19100
+ glyph.setAttribute("y", "0");
19101
+ glyph.setAttribute("fill", item.fill);
19102
+ glyph.setAttribute("font-family", family);
19103
+ glyph.setAttribute("font-size", `${fontPx2.toFixed(2)}`);
19104
+ glyph.setAttribute("font-weight", weight);
19105
+ glyph.setAttribute("font-style", style);
19106
+ glyph.setAttribute("text-anchor", "middle");
19107
+ glyph.setAttribute("dominant-baseline", "central");
19108
+ glyph.setAttribute(
19109
+ "transform",
19110
+ `translate(${x.toFixed(2)} ${y.toFixed(2)}) rotate(${(angle * 180 / Math.PI).toFixed(2)})`
19111
+ );
19112
+ glyph.textContent = ch;
19113
+ svg.appendChild(glyph);
19114
+ });
19115
+ return svg;
19116
+ }
19117
+ let fontPx = geo.fontPx;
19118
+ let startFrac = geo.startFrac;
19119
+ if (geo.natural) {
19120
+ fontPx = item.fontSize;
18351
19121
  }
18352
19122
  const id = `dxw-warp-${warpPathSeq++}`;
18353
19123
  const defs = document.createElementNS(SVG_NS, "defs");
@@ -18416,6 +19186,10 @@ function renderText(item) {
18416
19186
  el16.style.alignItems = "flex-end";
18417
19187
  el16.style.whiteSpace = "pre";
18418
19188
  el16.style.font = cssFont(item.font);
19189
+ if (item.font.paintDY) {
19190
+ const transform = el16.style.transform;
19191
+ el16.style.transform = `${transform ? `${transform} ` : ""}translateY(${item.font.paintDY}px)`;
19192
+ }
18419
19193
  }
18420
19194
  if (item.rtl) {
18421
19195
  el16.style.direction = "rtl";
@@ -18425,6 +19199,7 @@ function renderText(item) {
18425
19199
  el16.style.lineHeight = `${boxH}px`;
18426
19200
  el16.style.setProperty("-webkit-font-smoothing", "antialiased");
18427
19201
  const props = item.props;
19202
+ if (item.font.kerning) el16.style.fontKerning = "normal";
18428
19203
  let color = props.color && props.color !== "auto" ? props.color : "#000000";
18429
19204
  el16.style.color = color;
18430
19205
  if (props.underline && props.underline !== "none") {
@@ -18436,7 +19211,15 @@ function renderText(item) {
18436
19211
  } else if (props.underline === "none" && item.href) {
18437
19212
  el16.style.textDecoration = "none";
18438
19213
  }
18439
- if (props.letterSpacing) el16.style.letterSpacing = `${props.letterSpacing}px`;
19214
+ const paintLetterSpacing = props.paintLetterSpacing ?? props.letterSpacing;
19215
+ if (paintLetterSpacing) el16.style.letterSpacing = `${paintLetterSpacing}px`;
19216
+ if (props.paintLetterSpacing !== void 0 && props.letterSpacing !== void 0 && item.text.trim()) {
19217
+ const paintWidth = item.width - (props.letterSpacing - props.paintLetterSpacing) * item.text.length;
19218
+ if (paintWidth > 0.1) {
19219
+ el16.style.transform = `scaleX(${item.width / paintWidth})`;
19220
+ el16.style.transformOrigin = "0 50%";
19221
+ }
19222
+ }
18440
19223
  if (props.textScale && props.textScale !== 1 && !item.mathScaleY) {
18441
19224
  el16.style.transform = `scaleX(${props.textScale})`;
18442
19225
  el16.style.transformOrigin = "0 50%";
@@ -18465,8 +19248,7 @@ function renderText(item) {
18465
19248
  el16.style.transform = `rotate(${item.rotate.deg}deg)${prev ? " " + prev : ""}`;
18466
19249
  el16.style.transformOrigin = `${item.rotate.ox}px ${item.rotate.oy}px`;
18467
19250
  }
18468
- if (item.behind) el16.style.zIndex = "-1";
18469
- else if (item.front) el16.style.zIndex = "1";
19251
+ setItemLayer(el16, item);
18470
19252
  return el16;
18471
19253
  }
18472
19254
  function renderEdge(x1, y1, x2, y2, border, rotate) {
@@ -18479,13 +19261,47 @@ function renderEdge(x1, y1, x2, y2, border, rotate) {
18479
19261
  }
18480
19262
  const horizontal = Math.abs(y2 - y1) < 0.01;
18481
19263
  const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
18482
- const w = Math.max(1 / dpr, Math.round(border.width * dpr) / dpr);
19264
+ const ultraThin = rotate === void 0 && border.style === "single" && border.rawWidth !== void 0 && border.rawWidth < 0.25;
19265
+ const deviceHairline = rotate === void 0 && border.style === "single" && border.rawWidth !== void 0 && border.rawWidth < 0.75;
19266
+ const declaredWidth = border.rawWidth ?? border.width;
19267
+ const w = border.rawWidth !== void 0 && border.rawWidth < 0.25 ? border.rawWidth : Math.max(1 / dpr, Math.round(declaredWidth * dpr) / dpr);
18483
19268
  const snap = (v) => Math.round(v * dpr) / dpr;
18484
- const place = border.space === 0 ? snap : (v) => v;
19269
+ const place = border.space === 0 && !deviceHairline ? snap : (v) => v;
18485
19270
  const dashPattern = border.style === "dashed" || border.style === "dotDash" || border.style === "dotDotDash" ? [3, 1] : border.style === "dotted" ? [1, 1] : null;
19271
+ if (!horizontal && Math.abs(x2 - x1) >= 0.01) {
19272
+ const x = Math.min(x1, x2);
19273
+ const y = Math.min(y1, y2);
19274
+ const stroke = document.createElement("div");
19275
+ const strokeHeight = border.style === "double" ? w * 3 : w;
19276
+ stroke.style.position = "absolute";
19277
+ stroke.style.left = `${x1 - x}px`;
19278
+ stroke.style.top = `${y1 - y - strokeHeight / 2}px`;
19279
+ stroke.style.width = `${Math.hypot(x2 - x1, y2 - y1)}px`;
19280
+ stroke.style.height = `${strokeHeight}px`;
19281
+ stroke.style.transform = `rotate(${Math.atan2(y2 - y1, x2 - x1)}rad)`;
19282
+ stroke.style.transformOrigin = "0 50%";
19283
+ if (border.style === "double") {
19284
+ stroke.style.background = `linear-gradient(180deg, ${border.color} 0 ${w}px, transparent ${w}px ${w * 2}px, ${border.color} ${w * 2}px ${w * 3}px)`;
19285
+ } else if (dashPattern) {
19286
+ const on = Math.max(dashPattern[0] * border.width, 2);
19287
+ const period = on + Math.max(dashPattern[1] * border.width, 1);
19288
+ stroke.style.background = `repeating-linear-gradient(90deg, ${border.color} 0 ${on}px, transparent ${on}px ${period}px)`;
19289
+ } else if (border.style === "triple") {
19290
+ stroke.style.height = "0";
19291
+ stroke.style.borderTop = `${w}px double ${border.color}`;
19292
+ } else {
19293
+ stroke.style.background = border.color;
19294
+ }
19295
+ el16.style.left = `${x}px`;
19296
+ el16.style.top = `${y}px`;
19297
+ el16.style.width = `${Math.abs(x2 - x1)}px`;
19298
+ el16.style.height = `${Math.abs(y2 - y1)}px`;
19299
+ el16.appendChild(stroke);
19300
+ return el16;
19301
+ }
18486
19302
  if (horizontal) {
18487
19303
  el16.style.left = `${Math.min(x1, x2)}px`;
18488
- el16.style.top = `${place(y1 - w / 2)}px`;
19304
+ el16.style.top = `${place(y1 - w / 2 - (ultraThin ? 0.4 : 0))}px`;
18489
19305
  el16.style.width = `${Math.abs(x2 - x1)}px`;
18490
19306
  if (border.style === "double") {
18491
19307
  el16.style.height = `${w * 3}px`;
@@ -18493,11 +19309,20 @@ function renderEdge(x1, y1, x2, y2, border, rotate) {
18493
19309
  } else if (dashPattern) {
18494
19310
  const on = Math.max(dashPattern[0] * border.width, 2);
18495
19311
  const period = on + Math.max(dashPattern[1] * border.width, 1);
18496
- el16.style.height = `${w}px`;
19312
+ el16.style.height = `${w < 1 && rotate === void 0 ? 1 : w}px`;
18497
19313
  el16.style.background = `repeating-linear-gradient(90deg, ${border.color} 0 ${on}px, transparent ${on}px ${period}px)`;
19314
+ if (w < 1 && rotate === void 0) {
19315
+ el16.style.transform = `scaleY(${w})`;
19316
+ el16.style.transformOrigin = "0 0";
19317
+ }
18498
19318
  } else if (border.style === "triple") {
18499
19319
  el16.style.height = "0";
18500
19320
  el16.style.borderTop = `${w}px double ${border.color}`;
19321
+ } else if (deviceHairline) {
19322
+ el16.style.height = "1px";
19323
+ el16.style.background = border.color;
19324
+ el16.style.transform = "scaleY(.5)";
19325
+ el16.style.transformOrigin = "0 0";
18501
19326
  } else {
18502
19327
  el16.style.height = `${w}px`;
18503
19328
  el16.style.background = border.color;
@@ -18512,11 +19337,20 @@ function renderEdge(x1, y1, x2, y2, border, rotate) {
18512
19337
  } else if (dashPattern) {
18513
19338
  const on = Math.max(dashPattern[0] * border.width, 2);
18514
19339
  const period = on + Math.max(dashPattern[1] * border.width, 1);
18515
- el16.style.width = `${w}px`;
19340
+ el16.style.width = `${w < 1 && rotate === void 0 ? 1 : w}px`;
18516
19341
  el16.style.background = `repeating-linear-gradient(180deg, ${border.color} 0 ${on}px, transparent ${on}px ${period}px)`;
19342
+ if (w < 1 && rotate === void 0) {
19343
+ el16.style.transform = `scaleX(${w})`;
19344
+ el16.style.transformOrigin = "0 0";
19345
+ }
18517
19346
  } else if (border.style === "triple") {
18518
19347
  el16.style.width = "0";
18519
19348
  el16.style.borderLeft = `${w}px double ${border.color}`;
19349
+ } else if (deviceHairline) {
19350
+ el16.style.width = "1px";
19351
+ el16.style.background = border.color;
19352
+ el16.style.transform = "scaleX(.5)";
19353
+ el16.style.transformOrigin = "0 0";
18520
19354
  } else {
18521
19355
  el16.style.width = `${w}px`;
18522
19356
  el16.style.background = border.color;
@@ -20700,23 +21534,44 @@ function insertShapeAt(doc, caretT, preset, text = "") {
20700
21534
  if (!caretRun || !parent || localName(caretRun.name) !== "r") return null;
20701
21535
  const w = prefixOf8(caretRun);
20702
21536
  const id = String(doc.nextDrawingId());
20703
- const width = preset === "textBox" ? 240 : 192;
20704
- const height = preset === "textBox" ? 72 : 96;
21537
+ const isLine = preset === "line";
21538
+ const width = isLine ? 240 : preset === "textBox" ? 240 : 192;
21539
+ const height = isLine ? 2 : preset === "textBox" ? 72 : 96;
20705
21540
  const cx = String(Math.round(width * EMU_PER_PX4));
20706
21541
  const cy = String(Math.round(height * EMU_PER_PX4));
20707
21542
  const geometry = preset === "roundedRectangle" ? "roundRect" : preset === "rectangle" || preset === "textBox" ? "rect" : preset;
20708
21543
  const isTextBox = preset === "textBox";
20709
- const shapeName = isTextBox ? `Text Box ${id}` : `Shape ${id}`;
21544
+ const shapeName = isLine ? `Line ${id}` : isTextBox ? `Text Box ${id}` : `Shape ${id}`;
20710
21545
  const spPr = [
20711
- el6("a:xfrm", {}, [el6("a:off", { x: "0", y: "0" }), el6("a:ext", { cx, cy })]),
21546
+ el6("a:xfrm", {}, [
21547
+ el6("a:off", { x: "0", y: "0" }),
21548
+ el6("a:ext", { cx, cy })
21549
+ ]),
20712
21550
  el6("a:prstGeom", { prst: geometry }, [el6("a:avLst")]),
20713
- isTextBox ? el6("a:noFill") : el6("a:solidFill", {}, [el6("a:srgbClr", { val: "4472C4" })]),
20714
- el6("a:ln", { w: "12700" }, [el6("a:solidFill", {}, [el6("a:srgbClr", { val: isTextBox ? "404040" : "2F5597" })])])
21551
+ isTextBox || isLine ? el6("a:noFill") : el6("a:solidFill", {}, [el6("a:srgbClr", { val: "4472C4" })]),
21552
+ el6("a:ln", { w: "12700" }, [el6("a:solidFill", {}, [el6("a:srgbClr", { val: isTextBox || isLine ? "404040" : "2F5597" })])])
20715
21553
  ];
20716
21554
  const textRun = el6(`${w}r`, {}, [
20717
21555
  el6(`${w}rPr`, {}, [el6(`${w}color`, { [`${w}val`]: isTextBox ? "202124" : "FFFFFF" }), el6(`${w}sz`, { [`${w}val`]: "22" })]),
20718
21556
  el6(`${w}t`, { "xml:space": "preserve" }, [], text)
20719
21557
  ]);
21558
+ const shapeChildren = isLine ? [
21559
+ el6("wps:cNvCnPr", {}, [el6("a:cxnSpLocks", { noChangeShapeType: "1" })]),
21560
+ el6("wps:spPr", { bwMode: "auto" }, spPr),
21561
+ el6("wps:bodyPr")
21562
+ ] : [
21563
+ el6("wps:cNvSpPr", isTextBox ? { txBox: "1" } : {}),
21564
+ el6("wps:spPr", {}, spPr),
21565
+ el6("wps:txbx", {}, [
21566
+ el6(`${w}txbxContent`, {}, [
21567
+ el6(`${w}p`, {}, [
21568
+ el6(`${w}pPr`, {}, [el6(`${w}jc`, { [`${w}val`]: isTextBox ? "left" : "center" })]),
21569
+ textRun
21570
+ ])
21571
+ ])
21572
+ ]),
21573
+ el6("wps:bodyPr", { rot: "0", anchor: isTextBox ? "t" : "ctr" }, [el6("a:noAutofit")])
21574
+ ];
20720
21575
  const drawing = el6(`${w}drawing`, {}, [
20721
21576
  el6("wp:anchor", {
20722
21577
  "xmlns:wp": NS_WP3,
@@ -20736,24 +21591,12 @@ function insertShapeAt(doc, caretT, preset, text = "") {
20736
21591
  el6("wp:positionV", { relativeFrom: "paragraph" }, [el6("wp:posOffset", {}, [], "0")]),
20737
21592
  el6("wp:extent", { cx, cy }),
20738
21593
  el6("wp:effectExtent", { l: "0", t: "0", r: "0", b: "0" }),
20739
- el6("wp:wrapSquare", { wrapText: "bothSides" }),
21594
+ isLine ? el6("wp:wrapNone") : el6("wp:wrapSquare", { wrapText: "bothSides" }),
20740
21595
  el6("wp:docPr", { id, name: shapeName }),
20741
21596
  el6("wp:cNvGraphicFramePr"),
20742
21597
  el6("a:graphic", { "xmlns:a": NS_A3 }, [
20743
21598
  el6("a:graphicData", { uri: NS_WPS }, [
20744
- el6("wps:wsp", { "xmlns:wps": NS_WPS }, [
20745
- el6("wps:cNvSpPr", { txBox: "1" }),
20746
- el6("wps:spPr", {}, spPr),
20747
- el6("wps:txbx", {}, [
20748
- el6(`${w}txbxContent`, {}, [
20749
- el6(`${w}p`, {}, [
20750
- el6(`${w}pPr`, {}, [el6(`${w}jc`, { [`${w}val`]: isTextBox ? "left" : "center" })]),
20751
- textRun
20752
- ])
20753
- ])
20754
- ]),
20755
- el6("wps:bodyPr", { rot: "0", anchor: isTextBox ? "t" : "ctr" }, [el6("a:noAutofit")])
20756
- ])
21599
+ el6("wps:wsp", { "xmlns:wps": NS_WPS }, shapeChildren)
20757
21600
  ])
20758
21601
  ])
20759
21602
  ])
@@ -20815,6 +21658,25 @@ function setDrawingWordArtText(doc, drawing, text) {
20815
21658
  doc.refresh();
20816
21659
  return true;
20817
21660
  }
21661
+ function drawingLineStyle(drawing) {
21662
+ const line2 = descendant3(drawing, "ln");
21663
+ if (!line2) return null;
21664
+ const color = descendant3(line2, "srgbClr")?.attrs.val ?? "000000";
21665
+ const width = Math.max((parseInt(line2.attrs.w ?? "0", 10) || 0) / EMU_PER_PX4, 0.75);
21666
+ return { color: `#${color.toUpperCase()}`, width };
21667
+ }
21668
+ function setDrawingLineStyle(doc, drawing, color, widthPx) {
21669
+ const line2 = descendant3(drawing, "ln");
21670
+ if (!line2) return false;
21671
+ line2.attrs.w = String(Math.max(1, Math.round(widthPx * EMU_PER_PX4)));
21672
+ line2.children = line2.children.filter((child4) => {
21673
+ const name = localName(child4.name);
21674
+ return name !== "solidFill" && name !== "noFill";
21675
+ });
21676
+ line2.children.unshift(el6("a:solidFill", {}, [el6("a:srgbClr", { val: color.replace(/^#/, "").toUpperCase() })]));
21677
+ doc.refresh();
21678
+ return true;
21679
+ }
20818
21680
  function insertInkAt(doc, caretT, points, color = "#202124", widthPx = 2, opacity = 1, refreshModel = true) {
20819
21681
  if (points.length < 2) return null;
20820
21682
  const caretRun = doc.findParentOf(caretT);
@@ -21223,11 +22085,116 @@ function deleteWatermark(doc, shapeEl) {
21223
22085
  return true;
21224
22086
  }
21225
22087
 
21226
- // ../core/src/edit/grapheme.ts
21227
- var segmenter = null;
21228
- var segmenterTried = false;
21229
- function getSegmenter() {
21230
- if (segmenterTried) return segmenter;
22088
+ // ../core/src/edit/dialog.ts
22089
+ var dialogSequence = 0;
22090
+ function requestTextInputDialog(anchor, options) {
22091
+ const doc = anchor.ownerDocument;
22092
+ const win = doc.defaultView;
22093
+ return new Promise((resolve) => {
22094
+ const id = `dxw-input-dialog-${++dialogSequence}`;
22095
+ const previousFocus = doc.activeElement instanceof HTMLElement ? doc.activeElement : null;
22096
+ const backdrop = doc.createElement("div");
22097
+ backdrop.className = "dxw-input-dialog-backdrop";
22098
+ backdrop.dataset.dxwInputDialog = "";
22099
+ backdrop.style.cssText = "position:fixed;inset:0;z-index:var(--dxw-dialog-z-index,1000);display:grid;place-items:center;padding:16px;box-sizing:border-box;background:var(--dxw-dialog-backdrop,rgba(32,33,36,.38));";
22100
+ if (win) {
22101
+ const computed = win.getComputedStyle(anchor);
22102
+ for (const property of [
22103
+ "--dxw-toolbar-fg",
22104
+ "--dxw-toolbar-border",
22105
+ "--dxw-toolbar-muted",
22106
+ "--dxw-accent",
22107
+ "--dxw-accent-fg",
22108
+ "--dxw-popover-bg",
22109
+ "--dxw-popover-shadow",
22110
+ "--dxw-dialog-backdrop",
22111
+ "--dxw-dialog-z-index",
22112
+ "--dxw-dialog-width",
22113
+ "--dxw-dialog-radius",
22114
+ "--dxw-dialog-field-bg"
22115
+ ]) {
22116
+ const value = computed.getPropertyValue(property);
22117
+ if (value) backdrop.style.setProperty(property, value);
22118
+ }
22119
+ }
22120
+ const form = doc.createElement("form");
22121
+ form.className = "dxw-input-dialog";
22122
+ form.setAttribute("role", "dialog");
22123
+ form.setAttribute("aria-modal", "true");
22124
+ form.setAttribute("aria-labelledby", id);
22125
+ form.style.cssText = "width:min(var(--dxw-dialog-width,420px),calc(100vw - 32px));box-sizing:border-box;display:grid;gap:10px;padding:16px;border:1px solid var(--dxw-toolbar-border,#dadce0);border-radius:var(--dxw-dialog-radius,10px);color:var(--dxw-toolbar-fg,#3c4043);background:var(--dxw-popover-bg,#fff);box-shadow:var(--dxw-popover-shadow,0 8px 28px rgba(0,0,0,.24));font:13px system-ui,sans-serif;";
22126
+ const title = doc.createElement("strong");
22127
+ title.id = id;
22128
+ title.className = "dxw-input-dialog-title";
22129
+ title.textContent = options.title;
22130
+ title.style.cssText = "font-size:15px;line-height:1.35;";
22131
+ form.appendChild(title);
22132
+ const label = doc.createElement("label");
22133
+ label.className = "dxw-input-dialog-label";
22134
+ label.textContent = options.label ?? options.title;
22135
+ label.style.cssText = "display:grid;gap:5px;color:var(--dxw-toolbar-muted,#5f6368);font-size:12px;";
22136
+ const field = options.multiline ? doc.createElement("textarea") : doc.createElement("input");
22137
+ field.className = "dxw-input-dialog-field";
22138
+ field.value = options.value ?? "";
22139
+ field.placeholder = options.placeholder ?? "";
22140
+ field.setAttribute("aria-label", options.label ?? options.title);
22141
+ if (field instanceof HTMLTextAreaElement) field.rows = 4;
22142
+ field.style.cssText = "width:100%;min-height:34px;box-sizing:border-box;padding:7px 9px;border:1px solid var(--dxw-toolbar-border,#dadce0);border-radius:6px;outline:none;color:var(--dxw-toolbar-fg,#3c4043);background:var(--dxw-dialog-field-bg,var(--dxw-popover-bg,#fff));font:13px system-ui,sans-serif;resize:vertical;";
22143
+ label.appendChild(field);
22144
+ form.appendChild(label);
22145
+ const actions = doc.createElement("div");
22146
+ actions.className = "dxw-input-dialog-actions";
22147
+ actions.style.cssText = "display:flex;justify-content:flex-end;gap:7px;";
22148
+ const cancel = doc.createElement("button");
22149
+ cancel.type = "button";
22150
+ cancel.className = "dxw-input-dialog-cancel";
22151
+ cancel.textContent = "Cancel";
22152
+ cancel.style.cssText = "min-height:32px;padding:0 12px;border:1px solid var(--dxw-toolbar-border,#dadce0);border-radius:7px;color:var(--dxw-toolbar-fg,#3c4043);background:var(--dxw-popover-bg,#fff);cursor:pointer;font:600 12px system-ui,sans-serif;";
22153
+ const submit = doc.createElement("button");
22154
+ submit.type = "submit";
22155
+ submit.className = "dxw-input-dialog-submit";
22156
+ submit.textContent = options.submitLabel ?? "Apply";
22157
+ submit.style.cssText = "min-height:32px;padding:0 14px;border:1px solid transparent;border-radius:7px;color:var(--dxw-accent-fg,#fff);background:var(--dxw-accent,#1a73e8);cursor:pointer;font:600 12px system-ui,sans-serif;";
22158
+ actions.append(cancel, submit);
22159
+ form.appendChild(actions);
22160
+ backdrop.appendChild(form);
22161
+ doc.body.appendChild(backdrop);
22162
+ let finished = false;
22163
+ const finish = (value) => {
22164
+ if (finished) return;
22165
+ finished = true;
22166
+ doc.removeEventListener("keydown", onKeyDown, true);
22167
+ backdrop.remove();
22168
+ previousFocus?.focus({ preventScroll: true });
22169
+ resolve(value);
22170
+ };
22171
+ const onKeyDown = (event) => {
22172
+ if (event.key === "Escape") {
22173
+ event.preventDefault();
22174
+ finish(null);
22175
+ }
22176
+ };
22177
+ doc.addEventListener("keydown", onKeyDown, true);
22178
+ backdrop.addEventListener("mousedown", (event) => {
22179
+ if (event.target === backdrop) finish(null);
22180
+ });
22181
+ cancel.addEventListener("click", () => finish(null));
22182
+ form.addEventListener("submit", (event) => {
22183
+ event.preventDefault();
22184
+ finish(field.value);
22185
+ });
22186
+ requestAnimationFrame(() => {
22187
+ field.focus({ preventScroll: true });
22188
+ field.select();
22189
+ });
22190
+ });
22191
+ }
22192
+
22193
+ // ../core/src/edit/grapheme.ts
22194
+ var segmenter = null;
22195
+ var segmenterTried = false;
22196
+ function getSegmenter() {
22197
+ if (segmenterTried) return segmenter;
21231
22198
  segmenterTried = true;
21232
22199
  try {
21233
22200
  const ctor = Intl.Segmenter;
@@ -21936,6 +22903,7 @@ var _DocxEditor = class _DocxEditor {
21936
22903
  * snapshot that the following text insertion belongs to. */
21937
22904
  this.pendingClickTypeCheckpoint = false;
21938
22905
  this.clickTypeCheckpointUntil = 0;
22906
+ this.textContextMenu = null;
21939
22907
  /** True while a drag-selection is in progress (checked by onMouseUp, which
21940
22908
  * bubbles from the container BEFORE the document-level drag-end listener). */
21941
22909
  this.dragSelecting = false;
@@ -22325,6 +23293,102 @@ var _DocxEditor = class _DocxEditor {
22325
23293
  };
22326
23294
  /** Which part tree the element lives in: document body or header/footer. */
22327
23295
  this.mathEditorEl = null;
23296
+ this.dismissTextContextMenu = () => {
23297
+ this.textContextMenu?.remove();
23298
+ this.textContextMenu = null;
23299
+ document.removeEventListener("mousedown", this.onContextMenuDocumentMouseDown);
23300
+ };
23301
+ this.onContextMenuDocumentMouseDown = (event) => {
23302
+ if (!this.textContextMenu?.contains(event.target)) this.dismissTextContextMenu();
23303
+ };
23304
+ this.onContextMenu = (event) => {
23305
+ const target = event.target;
23306
+ if (!target?.closest('[data-dxw-item-kind="text"]')) return;
23307
+ const caret = this.caretFromPoint(event.clientX, event.clientY);
23308
+ if (!caret || this.regionOf(caret.t) === "hf" !== this.inHeaderFooter) return;
23309
+ event.preventDefault();
23310
+ event.stopPropagation();
23311
+ this.dismissTextContextMenu();
23312
+ if (!this.hasSelection()) {
23313
+ this.caret = caret;
23314
+ this.selectWordAt(caret);
23315
+ }
23316
+ const menu = document.createElement("div");
23317
+ menu.dataset.dxwTextContextMenu = "1";
23318
+ menu.setAttribute("role", "menu");
23319
+ menu.style.cssText = `position:fixed;left:${event.clientX}px;top:${event.clientY}px;z-index:2147483647;min-width:210px;padding:5px;background:#fff;border:1px solid #dadce0;border-radius:8px;box-shadow:0 4px 18px rgba(0,0,0,.2);font:13px system-ui,sans-serif;color:#202124;`;
23320
+ const isApplePlatform = /Mac|iPhone|iPad/.test(navigator.platform);
23321
+ const mod = isApplePlatform ? "\u2318" : "Ctrl+";
23322
+ const item = (label, shortcut, disabled, action) => {
23323
+ const button = document.createElement("button");
23324
+ button.type = "button";
23325
+ button.disabled = disabled;
23326
+ button.setAttribute("role", "menuitem");
23327
+ button.style.cssText = `display:flex;width:100%;align-items:center;justify-content:space-between;gap:24px;border:0;border-radius:5px;padding:7px 9px;background:transparent;color:${disabled ? "#9aa0a6" : "#202124"};cursor:${disabled ? "default" : "pointer"};`;
23328
+ const name = document.createElement("span");
23329
+ name.textContent = label;
23330
+ const keys = document.createElement("span");
23331
+ keys.textContent = shortcut;
23332
+ keys.style.color = "#5f6368";
23333
+ keys.style.fontSize = "11px";
23334
+ button.append(name, keys);
23335
+ button.addEventListener("mouseenter", () => {
23336
+ if (!button.disabled) button.style.background = "#f1f3f4";
23337
+ });
23338
+ button.addEventListener("mouseleave", () => {
23339
+ button.style.background = "transparent";
23340
+ });
23341
+ button.addEventListener("click", () => {
23342
+ if (button.disabled) return;
23343
+ this.dismissTextContextMenu();
23344
+ action();
23345
+ });
23346
+ menu.appendChild(button);
23347
+ };
23348
+ const separator = () => {
23349
+ const line2 = document.createElement("div");
23350
+ line2.style.cssText = "height:1px;background:#e8eaed;margin:4px 2px;";
23351
+ menu.appendChild(line2);
23352
+ };
23353
+ const selectedText = selectionTextLogical(this.host.doc, this.getSelectionSegments());
23354
+ const clipboard = navigator.clipboard;
23355
+ const copy = () => clipboard.writeText(selectedText);
23356
+ item("Copy", `${mod}C`, !selectedText || !clipboard?.writeText, () => void copy());
23357
+ item("Cut", `${mod}X`, !selectedText || !clipboard?.writeText, () => {
23358
+ void copy().then(() => {
23359
+ this.host.history?.checkpoint();
23360
+ this.removeSelectedText();
23361
+ this.commit();
23362
+ });
23363
+ });
23364
+ item("Paste", `${mod}V`, !clipboard?.readText, () => {
23365
+ void clipboard.readText().then((text) => {
23366
+ if (text) this.insertText(text);
23367
+ });
23368
+ });
23369
+ separator();
23370
+ item("Bold", `${mod}B`, !selectedText, () => this.host.onFormatShortcut?.("bold"));
23371
+ item("Italic", `${mod}I`, !selectedText, () => this.host.onFormatShortcut?.("italic"));
23372
+ item("Underline", `${mod}U`, !selectedText, () => this.host.onFormatShortcut?.("underline"));
23373
+ separator();
23374
+ item("Link\u2026", `${mod}K`, !selectedText, () => this.host.onTextCommand?.("link"));
23375
+ item("New comment\u2026", isApplePlatform ? "\u2318\u2325A" : "Ctrl+Alt+M", !selectedText, () => this.host.onTextCommand?.("comment"));
23376
+ item("Bulleted list", `${mod}Shift+L`, false, () => this.host.onTextCommand?.("bullet"));
23377
+ item("Numbered list", `${mod}Shift+7`, false, () => this.host.onTextCommand?.("number"));
23378
+ separator();
23379
+ item("Align left", `${mod}L`, false, () => this.host.onTextCommand?.("alignLeft"));
23380
+ item("Center", `${mod}E`, false, () => this.host.onTextCommand?.("alignCenter"));
23381
+ item("Align right", `${mod}R`, false, () => this.host.onTextCommand?.("alignRight"));
23382
+ item("Justify", `${mod}J`, false, () => this.host.onTextCommand?.("justify"));
23383
+ separator();
23384
+ item("Select all", `${mod}A`, false, () => this.selectAll());
23385
+ document.body.appendChild(menu);
23386
+ const rect = menu.getBoundingClientRect();
23387
+ menu.style.left = `${Math.max(6, Math.min(event.clientX, window.innerWidth - rect.width - 6))}px`;
23388
+ menu.style.top = `${Math.max(6, Math.min(event.clientY, window.innerHeight - rect.height - 6))}px`;
23389
+ this.textContextMenu = menu;
23390
+ setTimeout(() => document.addEventListener("mousedown", this.onContextMenuDocumentMouseDown), 0);
23391
+ };
22328
23392
  // ---------- keyboard ----------
22329
23393
  this.onKeyDown = (e) => {
22330
23394
  if (this.composing || e.isComposing || e.keyCode === 229) return;
@@ -22332,6 +23396,11 @@ var _DocxEditor = class _DocxEditor {
22332
23396
  this.pendingClickTypeCheckpoint = false;
22333
23397
  this.clickTypeCheckpointUntil = 0;
22334
23398
  }
23399
+ if (this.textContextMenu) {
23400
+ if (e.key === "Escape") e.preventDefault();
23401
+ this.dismissTextContextMenu();
23402
+ if (e.key === "Escape") return;
23403
+ }
22335
23404
  if (e.key === "Escape" && this.drawingTool) {
22336
23405
  e.preventDefault();
22337
23406
  this.setDrawingTool(null);
@@ -22446,6 +23515,12 @@ var _DocxEditor = class _DocxEditor {
22446
23515
  }
22447
23516
  }
22448
23517
  const meta = e.metaKey || e.ctrlKey;
23518
+ const isApplePlatform = /Mac|iPhone|iPad/.test(navigator.platform);
23519
+ if (isApplePlatform ? e.metaKey && e.altKey && e.key.toLowerCase() === "a" : e.ctrlKey && e.altKey && e.key.toLowerCase() === "m") {
23520
+ e.preventDefault();
23521
+ if (this.hasSelection()) this.host.onTextCommand?.("comment");
23522
+ return;
23523
+ }
22449
23524
  if (meta && e.altKey && /^[0-6]$/.test(e.key)) {
22450
23525
  e.preventDefault();
22451
23526
  this.host.onStyleShortcut?.(e.key === "0" ? null : `Heading${e.key}`);
@@ -22465,6 +23540,28 @@ var _DocxEditor = class _DocxEditor {
22465
23540
  }
22466
23541
  return;
22467
23542
  }
23543
+ if (!e.shiftKey && k === "k") {
23544
+ e.preventDefault();
23545
+ if (this.hasSelection()) this.host.onTextCommand?.("link");
23546
+ return;
23547
+ }
23548
+ if (e.shiftKey && (k === "l" || k === "8")) {
23549
+ e.preventDefault();
23550
+ this.host.onTextCommand?.("bullet");
23551
+ return;
23552
+ }
23553
+ if (e.shiftKey && k === "7") {
23554
+ e.preventDefault();
23555
+ this.host.onTextCommand?.("number");
23556
+ return;
23557
+ }
23558
+ if (!e.shiftKey && (k === "l" || k === "e" || k === "r" || k === "j")) {
23559
+ e.preventDefault();
23560
+ this.host.onTextCommand?.(
23561
+ k === "l" ? "alignLeft" : k === "e" ? "alignCenter" : k === "r" ? "alignRight" : "justify"
23562
+ );
23563
+ return;
23564
+ }
22468
23565
  if (e.key.startsWith("Arrow")) {
22469
23566
  e.preventDefault();
22470
23567
  const extend = e.shiftKey;
@@ -23100,6 +24197,7 @@ var _DocxEditor = class _DocxEditor {
23100
24197
  c.addEventListener("copy", this.onCopy);
23101
24198
  c.addEventListener("cut", this.onCut);
23102
24199
  c.addEventListener("paste", this.onPaste);
24200
+ c.addEventListener("contextmenu", this.onContextMenu);
23103
24201
  c.addEventListener("dragover", this.onDragOver);
23104
24202
  c.addEventListener("drop", this.onDrop);
23105
24203
  this.applyHfChrome();
@@ -23119,8 +24217,10 @@ var _DocxEditor = class _DocxEditor {
23119
24217
  c.removeEventListener("copy", this.onCopy);
23120
24218
  c.removeEventListener("cut", this.onCut);
23121
24219
  c.removeEventListener("paste", this.onPaste);
24220
+ c.removeEventListener("contextmenu", this.onContextMenu);
23122
24221
  c.removeEventListener("dragover", this.onDragOver);
23123
24222
  c.removeEventListener("drop", this.onDrop);
24223
+ this.dismissTextContextMenu();
23124
24224
  this.dismissSuggestionPopover();
23125
24225
  this.setDrawingTool(null);
23126
24226
  this.deselectInkGroup();
@@ -23990,23 +25090,73 @@ var _DocxEditor = class _DocxEditor {
23990
25090
  };
23991
25091
  extra("Alt", "Alternative text", () => {
23992
25092
  const cur = imageAltText(src);
23993
- const next = window.prompt("Alternative text", cur);
23994
- if (next === null) return;
23995
- this.host.history?.checkpoint();
23996
- if (setImageAltText(this.host.doc, src, next)) this.host.rerender();
23997
- this.deselectImage();
25093
+ void requestTextInputDialog(this.host.container, {
25094
+ title: "Alternative text",
25095
+ label: "Describe this object for screen readers",
25096
+ value: cur
25097
+ }).then((next) => {
25098
+ if (next === null) return;
25099
+ this.host.history?.checkpoint();
25100
+ if (setImageAltText(this.host.doc, src, next)) this.host.rerender();
25101
+ this.deselectImage();
25102
+ });
23998
25103
  });
23999
25104
  extra("Size", "Exact size (px)", () => {
24000
25105
  const curW = parseFloat(el16.style.width) || 0;
24001
25106
  const curH = parseFloat(el16.style.height) || 0;
24002
25107
  const cur = `${Math.round(curW)}x${Math.round(curH)}`;
24003
- const next = window.prompt("Size in px (width x height)", cur);
24004
- const m = next && /^\s*(\d+)\s*[x×]\s*(\d+)\s*$/i.exec(next);
24005
- if (!m) return;
24006
- this.host.history?.checkpoint();
24007
- if (resizeDrawing(this.host.doc, src, parseInt(m[1], 10), parseInt(m[2], 10))) this.host.rerender();
24008
- this.deselectImage();
25108
+ void requestTextInputDialog(this.host.container, {
25109
+ title: "Exact size",
25110
+ label: "Width \xD7 height in pixels",
25111
+ value: cur,
25112
+ placeholder: "640 \xD7 360"
25113
+ }).then((next) => {
25114
+ const match = next && /^\s*(\d+)\s*[x×]\s*(\d+)\s*$/i.exec(next);
25115
+ if (!match) return;
25116
+ this.host.history?.checkpoint();
25117
+ if (resizeDrawing(this.host.doc, src, parseInt(match[1], 10), parseInt(match[2], 10))) this.host.rerender();
25118
+ this.deselectImage();
25119
+ });
25120
+ });
25121
+ extra("Position", "Exact page position (px)", () => {
25122
+ const curX = parseFloat(el16.style.left) || 0;
25123
+ const curY = parseFloat(el16.style.top) || 0;
25124
+ void requestTextInputDialog(this.host.container, {
25125
+ title: "Page position",
25126
+ label: "X, Y in pixels",
25127
+ value: `${Math.round(curX)}, ${Math.round(curY)}`,
25128
+ placeholder: "120, 80"
25129
+ }).then((next) => {
25130
+ const match = next && /^\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*$/.exec(next);
25131
+ if (!match) return;
25132
+ this.host.history?.checkpoint();
25133
+ if (!isFloatingDrawing(src)) setImageWrap(this.host.doc, src, "square", { x: 0, y: 0 });
25134
+ if (setFloatingPagePosition(this.host.doc, src, parseFloat(match[1]), parseFloat(match[2]))) {
25135
+ this.host.rerender();
25136
+ this.reselectImage(src);
25137
+ }
25138
+ });
24009
25139
  });
25140
+ if (kind === "drawing") {
25141
+ extra("Outline", "Outline color and weight", () => {
25142
+ const style = drawingLineStyle(src);
25143
+ if (!style) return;
25144
+ void requestTextInputDialog(this.host.container, {
25145
+ title: "Outline",
25146
+ label: "Color and width in pixels",
25147
+ value: `${style.color}, ${Number(style.width.toFixed(2))}`,
25148
+ placeholder: "#1a73e8, 1.5"
25149
+ }).then((next) => {
25150
+ const match = next && /^\s*#?([0-9a-f]{6})\s*,\s*(\d+(?:\.\d+)?)\s*$/i.exec(next);
25151
+ if (!match) return;
25152
+ this.host.history?.checkpoint();
25153
+ if (setDrawingLineStyle(this.host.doc, src, `#${match[1]}`, parseFloat(match[2]))) {
25154
+ this.host.rerender();
25155
+ this.reselectImage(src);
25156
+ }
25157
+ });
25158
+ });
25159
+ }
24010
25160
  if (kind === "drawing" && isDrawingWordArt(src)) {
24011
25161
  extra("Edit text", "Edit WordArt text", () => this.editDrawingWordArt(src));
24012
25162
  }
@@ -24160,13 +25310,17 @@ var _DocxEditor = class _DocxEditor {
24160
25310
  };
24161
25311
  button("Edit text", "Edit watermark text", () => {
24162
25312
  const cur = wordArtText(src);
24163
- const next = window.prompt("Watermark text", cur);
24164
- if (next === null || next === cur) return;
24165
- this.host.history?.checkpoint();
24166
- if (setWordArtText(this.host.doc, src, next)) {
24167
- this.host.rerender();
24168
- this.reselectWordArt(src);
24169
- }
25313
+ void requestTextInputDialog(this.host.container, {
25314
+ title: "Watermark text",
25315
+ value: cur
25316
+ }).then((next) => {
25317
+ if (next === null || next === cur) return;
25318
+ this.host.history?.checkpoint();
25319
+ if (setWordArtText(this.host.doc, src, next)) {
25320
+ this.host.rerender();
25321
+ this.reselectWordArt(src);
25322
+ }
25323
+ });
24170
25324
  });
24171
25325
  const sep1 = document.createElement("span");
24172
25326
  sep1.style.cssText = "width:1px;background:#dadce0;margin:2px 2px;";
@@ -24178,15 +25332,20 @@ var _DocxEditor = class _DocxEditor {
24178
25332
  bar.appendChild(sep2);
24179
25333
  button("Rotate", "Set rotation (degrees)", () => {
24180
25334
  const cur = wordArtRotation(src);
24181
- const next = window.prompt("Rotation (degrees clockwise)", String(cur));
24182
- if (next === null) return;
24183
- const deg = parseFloat(next);
24184
- if (!Number.isFinite(deg)) return;
24185
- this.host.history?.checkpoint();
24186
- if (setWordArtRotation(this.host.doc, src, deg)) {
24187
- this.host.rerender();
24188
- this.reselectWordArt(src);
24189
- }
25335
+ void requestTextInputDialog(this.host.container, {
25336
+ title: "Watermark rotation",
25337
+ label: "Degrees clockwise",
25338
+ value: String(cur)
25339
+ }).then((next) => {
25340
+ if (next === null) return;
25341
+ const degrees = parseFloat(next);
25342
+ if (!Number.isFinite(degrees)) return;
25343
+ this.host.history?.checkpoint();
25344
+ if (setWordArtRotation(this.host.doc, src, degrees)) {
25345
+ this.host.rerender();
25346
+ this.reselectWordArt(src);
25347
+ }
25348
+ });
24190
25349
  });
24191
25350
  const sep3 = document.createElement("span");
24192
25351
  sep3.style.cssText = "width:1px;background:#dadce0;margin:2px 2px;";
@@ -25034,16 +26193,19 @@ var _DocxEditor = class _DocxEditor {
25034
26193
  };
25035
26194
  const split = this.splitParagraphCore();
25036
26195
  if (!split) return null;
25037
- const afterSources = [split.after];
26196
+ const hasPageBreak = (element) => localName(element.name) === "br" && attr(element, "type") === "page" || element.children.some(hasPageBreak);
26197
+ const insertBeforeTrailingBreak = hasPageBreak(split.after);
26198
+ const afterSources = insertBeforeTrailingBreak ? [] : [split.after];
25038
26199
  const w = split.after.name.includes(":") ? split.after.name.slice(0, split.after.name.indexOf(":") + 1) : "";
25039
26200
  const templatePPr = split.after.children.find((child4) => localName(child4.name) === "pPr");
25040
26201
  const templateRun = split.after.children.find(
25041
26202
  (child4) => localName(child4.name) === "r" && child4.children.includes(this.caret.t)
25042
26203
  );
25043
26204
  const templateRPr = templateRun?.children.find((child4) => localName(child4.name) === "rPr");
25044
- let previous = split.after;
26205
+ let previous = insertBeforeTrailingBreak ? split.before : split.after;
26206
+ let caretSource = split.after;
25045
26207
  for (let i = 1; i < lineCount; i++) {
25046
- if (this.suggesting) markParagraphGlyph(previous, "ins", this.revMeta());
26208
+ if (this.suggesting && previous !== split.before) markParagraphGlyph(previous, "ins", this.revMeta());
25047
26209
  const t = {
25048
26210
  name: this.caret.t.name,
25049
26211
  attrs: { ...this.caret.t.attrs, "xml:space": "preserve" },
@@ -25066,11 +26228,14 @@ var _DocxEditor = class _DocxEditor {
25066
26228
  parent.children.splice(previousIndex + 1, 0, paragraph2);
25067
26229
  afterSources.push(paragraph2);
25068
26230
  previous = paragraph2;
26231
+ caretSource = paragraph2;
25069
26232
  this.caret = { t, run: caret.run, offset: 0 };
25070
26233
  }
26234
+ if (insertBeforeTrailingBreak) afterSources.push(split.after);
25071
26235
  const reparsed = this.host.doc.reparseDirectBodyParagraphSplits(split.before, afterSources);
25072
26236
  if (reparsed) {
25073
- for (const child4 of reparsed[reparsed.length - 1].children) {
26237
+ const caretParagraph = reparsed[afterSources.indexOf(caretSource) + 1];
26238
+ for (const child4 of caretParagraph.children) {
25074
26239
  const runs = child4.type === "run" ? [child4] : child4.runs;
25075
26240
  const run = runs.find(
25076
26241
  (candidate) => candidate.content.some((content) => content.kind === "text" && content.srcT === this.caret.t)
@@ -27387,7 +28552,8 @@ function detectMissingFonts(layout) {
27387
28552
  for (const page of layout.pages) {
27388
28553
  for (const item of page.items) {
27389
28554
  if (item.kind !== "text" || !item.text.trim()) continue;
27390
- const primary = item.font.family.split(",")[0].trim().replace(/^"|"$/g, "");
28555
+ const requested = item.font.family.split(",")[0].trim().replace(/^"|"$/g, "");
28556
+ const primary = normalizeFamily(requested, item.font.bold, item.font.italic).family;
27391
28557
  if (!primary || GENERIC.has(primary.toLowerCase())) continue;
27392
28558
  const existing = samples.get(primary);
27393
28559
  if (!existing || !/[^-ɏ]/.test(existing) && /[^-ɏ]/.test(item.text)) {
@@ -27519,15 +28685,6 @@ var btnStyle = (active) => ({
27519
28685
  padding: "0 5px",
27520
28686
  color: T.fg
27521
28687
  });
27522
- var selectStyle = {
27523
- height: 26,
27524
- border: "1px solid transparent",
27525
- background: "transparent",
27526
- borderRadius: 4,
27527
- fontSize: 13,
27528
- color: T.fg,
27529
- cursor: "pointer"
27530
- };
27531
28688
  var PAGE_SIZES = [
27532
28689
  { value: "letter", label: "Letter", description: '8.5" \xD7 11"', width: 8.5, height: 11 },
27533
28690
  { value: "legal", label: "Legal", description: '8.5" \xD7 14"', width: 8.5, height: 14 },
@@ -27631,6 +28788,264 @@ function HighlightIcon({ color }) {
27631
28788
  /* @__PURE__ */ jsx("rect", { x: "2", y: "13.5", width: "12", height: "2.5", rx: "0.5", fill: color })
27632
28789
  ] });
27633
28790
  }
28791
+ function ToolbarMenuSelect({
28792
+ value,
28793
+ options,
28794
+ onChange,
28795
+ placeholder = "Choose\u2026",
28796
+ title,
28797
+ ariaLabel,
28798
+ triggerAriaLabel,
28799
+ width,
28800
+ menuWidth,
28801
+ className,
28802
+ style
28803
+ }) {
28804
+ const [open, setOpen] = useState(false);
28805
+ const [position, setPosition] = useState({ left: 8, top: 8, width: menuWidth ?? 180, maxHeight: 320 });
28806
+ const rootRef = useRef(null);
28807
+ const triggerRef = useRef(null);
28808
+ const menuRef = useRef(null);
28809
+ const keyboardOpen = useRef(null);
28810
+ const id = useId();
28811
+ const selected = options.find((option) => option.value === value);
28812
+ useLayoutEffect(() => {
28813
+ if (!open) return;
28814
+ const update = () => {
28815
+ const trigger = triggerRef.current;
28816
+ if (!trigger) return;
28817
+ const rect = trigger.getBoundingClientRect();
28818
+ const nextWidth = Math.min(
28819
+ window.innerWidth - 16,
28820
+ menuWidth ?? Math.max(rect.width, menuRef.current?.scrollWidth ?? 180)
28821
+ );
28822
+ const below = window.innerHeight - rect.bottom - 8;
28823
+ const above = rect.top - 8;
28824
+ const placeAbove = below < 140 && above > below;
28825
+ const maxHeight = Math.max(96, Math.min(320, placeAbove ? above : below));
28826
+ const shownHeight = Math.min(menuRef.current?.scrollHeight ?? maxHeight, maxHeight);
28827
+ setPosition({
28828
+ left: Math.max(8, Math.min(rect.left, window.innerWidth - nextWidth - 8)),
28829
+ top: placeAbove ? Math.max(8, rect.top - shownHeight - 4) : rect.bottom + 4,
28830
+ width: nextWidth,
28831
+ maxHeight
28832
+ });
28833
+ };
28834
+ update();
28835
+ const frame = requestAnimationFrame(() => {
28836
+ update();
28837
+ if (!keyboardOpen.current) return;
28838
+ const items = menuRef.current?.querySelectorAll('[role="option"]');
28839
+ const item = keyboardOpen.current === "last" ? items?.[items.length - 1] : items?.[0];
28840
+ item?.focus({ preventScroll: true });
28841
+ keyboardOpen.current = null;
28842
+ });
28843
+ const close = (event) => {
28844
+ if (!rootRef.current?.contains(event.target)) setOpen(false);
28845
+ };
28846
+ const keydown = (event) => {
28847
+ if (event.key === "Escape") {
28848
+ setOpen(false);
28849
+ requestAnimationFrame(() => triggerRef.current?.focus({ preventScroll: true }));
28850
+ }
28851
+ };
28852
+ document.addEventListener("mousedown", close);
28853
+ document.addEventListener("keydown", keydown);
28854
+ window.addEventListener("resize", update);
28855
+ window.addEventListener("scroll", update, true);
28856
+ return () => {
28857
+ cancelAnimationFrame(frame);
28858
+ document.removeEventListener("mousedown", close);
28859
+ document.removeEventListener("keydown", keydown);
28860
+ window.removeEventListener("resize", update);
28861
+ window.removeEventListener("scroll", update, true);
28862
+ };
28863
+ }, [open, menuWidth]);
28864
+ const pick = (next) => {
28865
+ const option = options.find((item) => item.value === next);
28866
+ if (!option || option.disabled) return;
28867
+ onChange(next);
28868
+ setOpen(false);
28869
+ };
28870
+ const onMenuKeyDown = (event) => {
28871
+ const items = menuRef.current ? Array.from(menuRef.current.querySelectorAll('[role="option"]:not(:disabled)')) : [];
28872
+ const current = items.indexOf(document.activeElement);
28873
+ let next = current;
28874
+ if (event.key === "ArrowDown") next = current < 0 ? 0 : (current + 1) % items.length;
28875
+ else if (event.key === "ArrowUp") next = current < 0 ? items.length - 1 : (current - 1 + items.length) % items.length;
28876
+ else if (event.key === "Home") next = 0;
28877
+ else if (event.key === "End") next = items.length - 1;
28878
+ else if ((event.key === "Enter" || event.key === " ") && current >= 0) {
28879
+ event.preventDefault();
28880
+ items[current].click();
28881
+ return;
28882
+ } else if (event.key === "Escape" || event.key === "Tab") {
28883
+ event.preventDefault();
28884
+ setOpen(false);
28885
+ requestAnimationFrame(() => triggerRef.current?.focus({ preventScroll: true }));
28886
+ return;
28887
+ } else return;
28888
+ event.preventDefault();
28889
+ items[next]?.focus({ preventScroll: true });
28890
+ };
28891
+ let previousGroup;
28892
+ return /* @__PURE__ */ jsxs(
28893
+ "span",
28894
+ {
28895
+ ref: rootRef,
28896
+ className: `dxw-menu-select${className ? ` ${className}` : ""}`,
28897
+ "data-dxw-menu-select": "",
28898
+ style: { position: "relative", display: "inline-flex", width },
28899
+ children: [
28900
+ /* @__PURE__ */ jsxs(
28901
+ "select",
28902
+ {
28903
+ tabIndex: -1,
28904
+ title,
28905
+ "aria-label": ariaLabel,
28906
+ "aria-hidden": "true",
28907
+ value,
28908
+ onChange: (event) => pick(event.target.value),
28909
+ "data-dxw-native-bridge": "",
28910
+ style: {
28911
+ position: "absolute",
28912
+ width: 1,
28913
+ height: 1,
28914
+ inset: 0,
28915
+ opacity: 0,
28916
+ pointerEvents: "none"
28917
+ },
28918
+ children: [
28919
+ !options.some((option) => option.value === value) && /* @__PURE__ */ jsx("option", { value: "", children: String(placeholder) }),
28920
+ options.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, disabled: option.disabled, children: String(option.label) }, option.value))
28921
+ ]
28922
+ }
28923
+ ),
28924
+ /* @__PURE__ */ jsxs(
28925
+ "button",
28926
+ {
28927
+ ref: triggerRef,
28928
+ type: "button",
28929
+ "aria-haspopup": "listbox",
28930
+ "aria-expanded": open,
28931
+ "aria-controls": open ? id : void 0,
28932
+ "aria-label": triggerAriaLabel,
28933
+ "data-tip": title,
28934
+ className: "dxw-menu-select-trigger",
28935
+ "data-dxw-menu-select-trigger": "",
28936
+ onMouseDown: (event) => event.preventDefault(),
28937
+ onClick: () => setOpen(!open),
28938
+ onKeyDown: (event) => {
28939
+ if (!open && (event.key === "Enter" || event.key === " " || event.key === "ArrowDown" || event.key === "ArrowUp")) {
28940
+ event.preventDefault();
28941
+ keyboardOpen.current = event.key === "ArrowUp" ? "last" : "first";
28942
+ setOpen(true);
28943
+ }
28944
+ },
28945
+ style: {
28946
+ width: "100%",
28947
+ minWidth: 0,
28948
+ height: "var(--dxw-select-height, 26px)",
28949
+ border: "1px solid var(--dxw-select-border, transparent)",
28950
+ borderRadius: "var(--dxw-select-radius, 4px)",
28951
+ background: "var(--dxw-select-bg, transparent)",
28952
+ color: "var(--dxw-select-fg, var(--dxw-toolbar-fg, #3c4043))",
28953
+ padding: "var(--dxw-select-padding, 0 6px)",
28954
+ display: "inline-flex",
28955
+ alignItems: "center",
28956
+ justifyContent: "space-between",
28957
+ gap: 6,
28958
+ cursor: "pointer",
28959
+ font: "var(--dxw-select-font, 13px system-ui, sans-serif)",
28960
+ ...style
28961
+ },
28962
+ children: [
28963
+ /* @__PURE__ */ jsx("span", { style: { minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontFamily: selected?.fontFamily }, children: selected?.label ?? placeholder }),
28964
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "dxw-menu-select-chevron", style: { flexShrink: 0, fontSize: 10 }, children: "\u2304" })
28965
+ ]
28966
+ }
28967
+ ),
28968
+ open && /* @__PURE__ */ jsx(
28969
+ "div",
28970
+ {
28971
+ ref: menuRef,
28972
+ id,
28973
+ role: "listbox",
28974
+ "aria-label": ariaLabel ?? title,
28975
+ className: "dxw-menu-select-menu",
28976
+ "data-dxw-menu-select-menu": "",
28977
+ onMouseDown: (event) => event.preventDefault(),
28978
+ onKeyDown: onMenuKeyDown,
28979
+ style: {
28980
+ position: "fixed",
28981
+ left: position.left,
28982
+ top: position.top,
28983
+ zIndex: "var(--dxw-toolbar-z-index, 100)",
28984
+ width: position.width,
28985
+ maxHeight: `min(var(--dxw-select-menu-max-height, 320px), ${position.maxHeight}px)`,
28986
+ overflowY: "auto",
28987
+ overscrollBehavior: "contain",
28988
+ boxSizing: "border-box",
28989
+ padding: "var(--dxw-select-menu-padding, 5px)",
28990
+ border: "1px solid var(--dxw-select-menu-border, var(--dxw-toolbar-border, #dadce0))",
28991
+ borderRadius: "var(--dxw-select-menu-radius, 8px)",
28992
+ background: "var(--dxw-select-menu-bg, var(--dxw-popover-bg, #fff))",
28993
+ boxShadow: "var(--dxw-select-menu-shadow, var(--dxw-popover-shadow, 0 4px 16px rgba(0,0,0,.15)))"
28994
+ },
28995
+ children: options.map((option) => {
28996
+ const groupChanged = option.group !== previousGroup;
28997
+ previousGroup = option.group;
28998
+ const active = option.value === value;
28999
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
29000
+ groupChanged && option.group && /* @__PURE__ */ jsx("div", { className: "dxw-menu-select-group", style: { padding: "6px 8px 3px", color: T.muted, font: "600 10.5px system-ui, sans-serif" }, children: option.group }),
29001
+ /* @__PURE__ */ jsxs(
29002
+ "button",
29003
+ {
29004
+ type: "button",
29005
+ role: "option",
29006
+ "aria-selected": active,
29007
+ disabled: option.disabled,
29008
+ tabIndex: -1,
29009
+ className: "dxw-menu-select-option",
29010
+ "data-dxw-menu-select-option": option.value,
29011
+ onMouseDown: (event) => event.preventDefault(),
29012
+ onClick: () => pick(option.value),
29013
+ style: {
29014
+ width: "100%",
29015
+ minHeight: 30,
29016
+ border: 0,
29017
+ borderRadius: 6,
29018
+ padding: "5px 8px",
29019
+ display: "flex",
29020
+ alignItems: "center",
29021
+ gap: 8,
29022
+ background: active ? T.activeBg : "transparent",
29023
+ color: option.disabled ? T.muted : T.fg,
29024
+ textAlign: "left",
29025
+ cursor: option.disabled ? "default" : "pointer",
29026
+ font: "13px system-ui, sans-serif",
29027
+ fontFamily: option.fontFamily
29028
+ },
29029
+ onMouseEnter: (event) => {
29030
+ if (!active && !option.disabled) event.currentTarget.style.background = T.hoverBg;
29031
+ },
29032
+ onMouseLeave: (event) => {
29033
+ if (!active) event.currentTarget.style.background = "transparent";
29034
+ },
29035
+ children: [
29036
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: option.label }),
29037
+ active && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { color: T.accent, fontWeight: 700 }, children: "\u2713" })
29038
+ ]
29039
+ }
29040
+ )
29041
+ ] }, option.value);
29042
+ })
29043
+ }
29044
+ )
29045
+ ]
29046
+ }
29047
+ );
29048
+ }
27634
29049
  function ActionMenu({
27635
29050
  label,
27636
29051
  title,
@@ -27638,22 +29053,21 @@ function ActionMenu({
27638
29053
  onPick,
27639
29054
  width
27640
29055
  }) {
27641
- return /* @__PURE__ */ jsxs(
27642
- "select",
29056
+ return /* @__PURE__ */ jsx(
29057
+ ToolbarMenuSelect,
27643
29058
  {
27644
29059
  title,
29060
+ triggerAriaLabel: title,
27645
29061
  value: "",
27646
- onMouseDown: (e) => e.stopPropagation(),
27647
- onChange: (e) => {
27648
- if (e.target.value) onPick(e.target.value);
27649
- },
27650
- style: { ...selectStyle, width },
27651
- children: [
27652
- /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: label }),
27653
- groups.map(
27654
- (g, i) => g.label ? /* @__PURE__ */ jsx("optgroup", { label: g.label, children: g.items.map(([v, t]) => /* @__PURE__ */ jsx("option", { value: v, children: t }, v)) }, i) : g.items.map(([v, t]) => /* @__PURE__ */ jsx("option", { value: v, children: t }, v))
27655
- )
27656
- ]
29062
+ placeholder: label,
29063
+ width,
29064
+ menuWidth: Math.max(width ?? 0, 190),
29065
+ options: groups.flatMap((group) => group.items.map(([value, text]) => ({
29066
+ value,
29067
+ label: text,
29068
+ group: group.label
29069
+ }))),
29070
+ onChange: onPick
27657
29071
  }
27658
29072
  );
27659
29073
  }
@@ -27733,6 +29147,183 @@ function HighlightMenu({ current, onPick }) {
27733
29147
  )
27734
29148
  ] });
27735
29149
  }
29150
+ var COLOR_SWATCHES = [
29151
+ "#000000",
29152
+ "#434343",
29153
+ "#666666",
29154
+ "#999999",
29155
+ "#b7b7b7",
29156
+ "#cccccc",
29157
+ "#d9d9d9",
29158
+ "#ffffff",
29159
+ "#980000",
29160
+ "#ff0000",
29161
+ "#ff9900",
29162
+ "#ffff00",
29163
+ "#00ff00",
29164
+ "#00ffff",
29165
+ "#4a86e8",
29166
+ "#0000ff",
29167
+ "#9900ff",
29168
+ "#ff00ff",
29169
+ "#e6b8af",
29170
+ "#f4cccc",
29171
+ "#fce5cd",
29172
+ "#fff2cc",
29173
+ "#d9ead3",
29174
+ "#d0e0e3",
29175
+ "#c9daf8",
29176
+ "#cfe2f3",
29177
+ "#d9d2e9",
29178
+ "#ead1dc",
29179
+ "#a61c00",
29180
+ "#cc0000",
29181
+ "#e69138",
29182
+ "#6aa84f"
29183
+ ];
29184
+ function normalizedColor(value) {
29185
+ const match = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(value.trim());
29186
+ if (!match) return null;
29187
+ const digits = match[1].length === 3 ? match[1].split("").map((digit) => digit + digit).join("") : match[1];
29188
+ return `#${digits.toLowerCase()}`;
29189
+ }
29190
+ function ColorMenu({
29191
+ current,
29192
+ title,
29193
+ trigger,
29194
+ onPick
29195
+ }) {
29196
+ const [open, setOpen] = useState(false);
29197
+ const [custom, setCustom] = useState(current);
29198
+ const [position, setPosition] = useState({ left: 8, top: 8 });
29199
+ const rootRef = useRef(null);
29200
+ const triggerRef = useRef(null);
29201
+ const menuRef = useRef(null);
29202
+ const valid = normalizedColor(custom);
29203
+ useLayoutEffect(() => {
29204
+ if (!open) return;
29205
+ setCustom(current);
29206
+ const update = () => {
29207
+ const rect = triggerRef.current?.getBoundingClientRect();
29208
+ if (!rect) return;
29209
+ const width = Math.min(236, window.innerWidth - 16);
29210
+ const menuHeight = menuRef.current?.offsetHeight ?? 188;
29211
+ const top = window.innerHeight - rect.bottom >= menuHeight + 8 ? rect.bottom + 4 : Math.max(8, rect.top - menuHeight - 4);
29212
+ setPosition({ left: Math.max(8, Math.min(rect.left, window.innerWidth - width - 8)), top });
29213
+ };
29214
+ update();
29215
+ const frame = requestAnimationFrame(update);
29216
+ const close = (event) => {
29217
+ if (!rootRef.current?.contains(event.target)) setOpen(false);
29218
+ };
29219
+ const keydown = (event) => {
29220
+ if (event.key === "Escape") {
29221
+ setOpen(false);
29222
+ requestAnimationFrame(() => triggerRef.current?.focus({ preventScroll: true }));
29223
+ }
29224
+ };
29225
+ document.addEventListener("mousedown", close);
29226
+ document.addEventListener("keydown", keydown);
29227
+ window.addEventListener("resize", update);
29228
+ window.addEventListener("scroll", update, true);
29229
+ return () => {
29230
+ cancelAnimationFrame(frame);
29231
+ document.removeEventListener("mousedown", close);
29232
+ document.removeEventListener("keydown", keydown);
29233
+ window.removeEventListener("resize", update);
29234
+ window.removeEventListener("scroll", update, true);
29235
+ };
29236
+ }, [open, current]);
29237
+ const pick = (value) => {
29238
+ onPick(value);
29239
+ setOpen(false);
29240
+ };
29241
+ return /* @__PURE__ */ jsxs("span", { ref: rootRef, className: "dxw-color-control", style: { position: "relative", display: "inline-flex" }, children: [
29242
+ /* @__PURE__ */ jsx(
29243
+ "button",
29244
+ {
29245
+ ref: triggerRef,
29246
+ type: "button",
29247
+ title,
29248
+ "aria-haspopup": "dialog",
29249
+ "aria-expanded": open,
29250
+ className: "dxw-color-trigger",
29251
+ "data-dxw-color-trigger": "",
29252
+ style: { ...btnStyle(open), display: "inline-flex", alignItems: "center", gap: 5 },
29253
+ onMouseDown: (event) => event.preventDefault(),
29254
+ onClick: () => setOpen(!open),
29255
+ children: trigger
29256
+ }
29257
+ ),
29258
+ open && /* @__PURE__ */ jsxs(
29259
+ "div",
29260
+ {
29261
+ ref: menuRef,
29262
+ role: "dialog",
29263
+ "aria-label": title,
29264
+ className: "dxw-color-menu",
29265
+ "data-dxw-color-menu": "",
29266
+ onMouseDown: (event) => event.stopPropagation(),
29267
+ style: {
29268
+ position: "fixed",
29269
+ left: position.left,
29270
+ top: position.top,
29271
+ zIndex: "var(--dxw-toolbar-z-index, 100)",
29272
+ width: "min(var(--dxw-color-menu-width, 236px), calc(100vw - 16px))",
29273
+ boxSizing: "border-box",
29274
+ padding: 8,
29275
+ border: `1px solid ${T.border}`,
29276
+ borderRadius: 8,
29277
+ background: T.popoverBg,
29278
+ boxShadow: T.popoverShadow
29279
+ },
29280
+ children: [
29281
+ /* @__PURE__ */ jsx("div", { className: "dxw-color-swatches", style: { display: "grid", gridTemplateColumns: "repeat(8, 1fr)", gap: 4 }, children: COLOR_SWATCHES.map((color) => /* @__PURE__ */ jsx(
29282
+ "button",
29283
+ {
29284
+ type: "button",
29285
+ title: color,
29286
+ "aria-label": `Choose ${color}`,
29287
+ className: "dxw-color-swatch",
29288
+ "data-dxw-color": color,
29289
+ onMouseDown: (event) => event.preventDefault(),
29290
+ onClick: () => pick(color),
29291
+ style: {
29292
+ width: 23,
29293
+ height: 23,
29294
+ padding: 0,
29295
+ borderRadius: 4,
29296
+ cursor: "pointer",
29297
+ border: color.toLowerCase() === current.toLowerCase() ? `2px solid ${T.accent}` : `1px solid ${T.border}`,
29298
+ background: color
29299
+ }
29300
+ },
29301
+ color
29302
+ )) }),
29303
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginTop: 8 }, children: [
29304
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { width: 24, height: 24, borderRadius: 4, border: `1px solid ${T.border}`, background: valid ?? current, flexShrink: 0 } }),
29305
+ /* @__PURE__ */ jsx(
29306
+ "input",
29307
+ {
29308
+ "aria-label": "Custom hex color",
29309
+ className: "dxw-color-value",
29310
+ value: custom,
29311
+ onChange: (event) => setCustom(event.target.value),
29312
+ onKeyDown: (event) => {
29313
+ if (event.key === "Enter" && valid) pick(valid);
29314
+ },
29315
+ spellCheck: false,
29316
+ placeholder: "#1a73e8",
29317
+ style: { minWidth: 0, flex: 1, height: 28, boxSizing: "border-box", border: `1px solid ${T.border}`, borderRadius: 5, padding: "3px 6px", color: T.fg, background: T.popoverBg }
29318
+ }
29319
+ ),
29320
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: !valid, onClick: () => valid && pick(valid), style: pillBtn, children: "Apply" })
29321
+ ] })
29322
+ ]
29323
+ }
29324
+ )
29325
+ ] });
29326
+ }
27736
29327
  function BulletListIcon() {
27737
29328
  return /* @__PURE__ */ jsxs("svg", { style: icon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.4", children: [
27738
29329
  /* @__PURE__ */ jsx("circle", { cx: "3", cy: "4", r: "1.1", fill: "currentColor", stroke: "none" }),
@@ -28095,7 +29686,18 @@ function CrossReferenceMenu({ api }) {
28095
29686
  return /* @__PURE__ */ jsxs("span", { ref: rootRef, style: { position: "relative", display: "inline-block" }, children: [
28096
29687
  /* @__PURE__ */ jsx("button", { title: "Insert cross-reference", style: btnStyle(open), onMouseDown: (event) => event.preventDefault(), onClick: () => setOpen(!open), children: "Cross-reference" }),
28097
29688
  open && /* @__PURE__ */ jsx("div", { style: { position: "absolute", top: 28, left: 0, zIndex: 100, width: 260, padding: 10, background: T.popoverBg, border: `1px solid ${T.border}`, borderRadius: 8, boxShadow: T.popoverShadow }, children: bookmarks.length === 0 ? /* @__PURE__ */ jsx("div", { style: { color: T.muted, fontSize: 12 }, children: "Add a bookmark first, then reference its text or page." }) : /* @__PURE__ */ jsxs(Fragment2, { children: [
28098
- /* @__PURE__ */ jsx("select", { value: selected, onChange: (event) => setBookmark(event.target.value), style: { ...selectStyle, width: "100%", borderColor: T.border }, children: bookmarks.map((name) => /* @__PURE__ */ jsx("option", { value: name, children: name }, name)) }),
29689
+ /* @__PURE__ */ jsx(
29690
+ ToolbarMenuSelect,
29691
+ {
29692
+ value: selected,
29693
+ ariaLabel: "Bookmark to reference",
29694
+ width: "100%",
29695
+ menuWidth: 240,
29696
+ options: bookmarks.map((name) => ({ value: name, label: name })),
29697
+ onChange: setBookmark,
29698
+ style: { borderColor: T.border }
29699
+ }
29700
+ ),
28099
29701
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "flex-end", gap: 6, marginTop: 8 }, children: [
28100
29702
  /* @__PURE__ */ jsx("button", { style: { ...pillBtn, background: T.popoverBg, color: T.fg }, onClick: () => insert("page"), children: "Page number" }),
28101
29703
  /* @__PURE__ */ jsx("button", { style: pillBtn, onClick: () => insert("text"), children: "Bookmark text" })
@@ -28202,6 +29804,7 @@ function SymbolMenu({ api }) {
28202
29804
  ] });
28203
29805
  }
28204
29806
  var SHAPES = [
29807
+ ["line", "Line", "\u2015"],
28205
29808
  ["rectangle", "Rectangle", "\u25AD"],
28206
29809
  ["roundedRectangle", "Rounded rectangle", "\u25A2"],
28207
29810
  ["ellipse", "Ellipse", "\u25EF"],
@@ -28373,12 +29976,23 @@ function ChartMenu({ api }) {
28373
29976
  return /* @__PURE__ */ jsxs("span", { ref: rootRef, style: { position: "relative", display: "inline-block" }, children: [
28374
29977
  /* @__PURE__ */ jsx("button", { title: "Insert or edit chart", style: btnStyle(open), onMouseDown: (event) => event.preventDefault(), onClick: () => setOpen(!open), children: "Chart" }),
28375
29978
  open && /* @__PURE__ */ jsxs("div", { style: { position: "absolute", top: 28, right: 0, zIndex: 100, width: 320, padding: 10, display: "grid", gap: 7, background: T.popoverBg, border: `1px solid ${T.border}`, borderRadius: 8, boxShadow: T.popoverShadow }, children: [
28376
- /* @__PURE__ */ jsxs("select", { "aria-label": "Chart type", value: type, onChange: (event) => setType(event.target.value), style: fieldStyle, children: [
28377
- /* @__PURE__ */ jsx("option", { value: "column", children: "Column" }),
28378
- /* @__PURE__ */ jsx("option", { value: "bar", children: "Bar" }),
28379
- /* @__PURE__ */ jsx("option", { value: "line", children: "Line" }),
28380
- /* @__PURE__ */ jsx("option", { value: "pie", children: "Pie" })
28381
- ] }),
29979
+ /* @__PURE__ */ jsx(
29980
+ ToolbarMenuSelect,
29981
+ {
29982
+ value: type,
29983
+ ariaLabel: "Chart type",
29984
+ width: "100%",
29985
+ menuWidth: 300,
29986
+ options: [
29987
+ { value: "column", label: "Column" },
29988
+ { value: "bar", label: "Bar" },
29989
+ { value: "line", label: "Line" },
29990
+ { value: "pie", label: "Pie" }
29991
+ ],
29992
+ onChange: (value) => setType(value),
29993
+ style: fieldStyle
29994
+ }
29995
+ ),
28382
29996
  /* @__PURE__ */ jsx("input", { "aria-label": "Chart title", value: title, onChange: (event) => setTitle(event.target.value), placeholder: "Chart title", style: fieldStyle }),
28383
29997
  /* @__PURE__ */ jsx("input", { "aria-label": "Chart categories", value: categories, onChange: (event) => setCategories(event.target.value), placeholder: "Q1, Q2, Q3, Q4", style: fieldStyle }),
28384
29998
  /* @__PURE__ */ jsx("textarea", { "aria-label": "Chart series", rows: 3, value: series, onChange: (event) => setSeries(event.target.value), style: { ...fieldStyle, resize: "vertical" } }),
@@ -28411,12 +30025,23 @@ function SmartArtMenu({ api }) {
28411
30025
  return /* @__PURE__ */ jsxs("span", { ref: rootRef, style: { position: "relative", display: "inline-block" }, children: [
28412
30026
  /* @__PURE__ */ jsx("button", { title: "Insert or edit SmartArt", style: btnStyle(open), onMouseDown: (event) => event.preventDefault(), onClick: () => setOpen(!open), children: "SmartArt" }),
28413
30027
  open && /* @__PURE__ */ jsxs("div", { style: { position: "absolute", top: 28, right: 0, zIndex: 100, width: 280, padding: 10, display: "grid", gap: 7, background: T.popoverBg, border: `1px solid ${T.border}`, borderRadius: 8, boxShadow: T.popoverShadow }, children: [
28414
- /* @__PURE__ */ jsxs("select", { "aria-label": "SmartArt layout", value: layout, onChange: (event) => setLayout(event.target.value), style: fieldStyle, children: [
28415
- /* @__PURE__ */ jsx("option", { value: "process", children: "Process" }),
28416
- /* @__PURE__ */ jsx("option", { value: "cycle", children: "Cycle" }),
28417
- /* @__PURE__ */ jsx("option", { value: "hierarchy", children: "Hierarchy" }),
28418
- /* @__PURE__ */ jsx("option", { value: "list", children: "List" })
28419
- ] }),
30028
+ /* @__PURE__ */ jsx(
30029
+ ToolbarMenuSelect,
30030
+ {
30031
+ value: layout,
30032
+ ariaLabel: "SmartArt layout",
30033
+ width: "100%",
30034
+ menuWidth: 260,
30035
+ options: [
30036
+ { value: "process", label: "Process" },
30037
+ { value: "cycle", label: "Cycle" },
30038
+ { value: "hierarchy", label: "Hierarchy" },
30039
+ { value: "list", label: "List" }
30040
+ ],
30041
+ onChange: (value) => setLayout(value),
30042
+ style: fieldStyle
30043
+ }
30044
+ ),
28420
30045
  /* @__PURE__ */ jsx("textarea", { "aria-label": "SmartArt items", rows: 5, value: items, onChange: (event) => setItems(event.target.value), style: { ...fieldStyle, resize: "vertical" } }),
28421
30046
  /* @__PURE__ */ jsx("div", { style: { color: T.muted, font: "11.5px system-ui, sans-serif" }, children: "One diagram item per line." }),
28422
30047
  /* @__PURE__ */ jsx("button", { disabled: !items.trim(), onClick: submit, style: { border: 0, borderRadius: 6, padding: "7px 10px", background: items.trim() ? T.accent : T.border, color: T.accentFg, cursor: items.trim() ? "pointer" : "default", font: "600 12px system-ui, sans-serif" }, children: "Insert or update SmartArt" })
@@ -28474,19 +30099,18 @@ function DrawTab({ api }) {
28474
30099
  /* @__PURE__ */ jsx(Btn, { label: "Eraser", title: "Stroke eraser", active: active === "eraser", onClick: () => api?.setDrawingTool({ kind: "eraser", size: 14 }) }),
28475
30100
  /* @__PURE__ */ jsx(Btn, { label: "Lasso", title: "Lasso ink", active: active === "lasso", onClick: () => api?.setDrawingTool({ kind: "lasso" }) }),
28476
30101
  /* @__PURE__ */ jsx(Sep, {}),
28477
- /* @__PURE__ */ jsxs("label", { title: "Pen color", style: { display: "inline-flex", alignItems: "center", gap: 5, color: T.fg, font: "12px system-ui, sans-serif" }, children: [
28478
- "Color",
28479
- /* @__PURE__ */ jsx(
28480
- "input",
28481
- {
28482
- "aria-label": "Pen color",
28483
- type: "color",
28484
- value: color,
28485
- onChange: (event) => activate(active === "highlighter" ? "highlighter" : "pen", event.target.value, width),
28486
- style: { width: 28, height: 24, padding: 1, border: `1px solid ${T.border}`, borderRadius: 4, background: "transparent" }
28487
- }
28488
- )
28489
- ] }),
30102
+ /* @__PURE__ */ jsx(
30103
+ ColorMenu,
30104
+ {
30105
+ current: color,
30106
+ title: "Pen color",
30107
+ trigger: /* @__PURE__ */ jsxs(Fragment2, { children: [
30108
+ /* @__PURE__ */ jsx("span", { style: { font: "12px system-ui, sans-serif" }, children: "Color" }),
30109
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { width: 18, height: 18, borderRadius: 4, border: `1px solid ${T.border}`, background: color } })
30110
+ ] }),
30111
+ onPick: (value) => activate(active === "highlighter" ? "highlighter" : "pen", value, width)
30112
+ }
30113
+ ),
28490
30114
  /* @__PURE__ */ jsx(
28491
30115
  ActionMenu,
28492
30116
  {
@@ -29064,9 +30688,8 @@ function MarginMenu({
29064
30688
  "input",
29065
30689
  {
29066
30690
  "aria-label": `${label} margin (inches)`,
29067
- type: "number",
29068
- min: "0",
29069
- step: "0.01",
30691
+ type: "text",
30692
+ inputMode: "decimal",
29070
30693
  required: true,
29071
30694
  autoFocus: side === "top",
29072
30695
  value: values[side],
@@ -29191,9 +30814,8 @@ function PageSizeMenu({
29191
30814
  "input",
29192
30815
  {
29193
30816
  "aria-label": `Page ${side} (inches)`,
29194
- type: "number",
29195
- min: "0.01",
29196
- step: "0.01",
30817
+ type: "text",
30818
+ inputMode: "decimal",
29197
30819
  required: true,
29198
30820
  autoFocus: side === "width",
29199
30821
  value: values[side],
@@ -29291,19 +30913,20 @@ function LayoutTab({ api, showArrange }) {
29291
30913
  "data-dxw-layout-ribbon": "",
29292
30914
  style: { display: "flex", flex: "1 1 640px", minWidth: 0, alignItems: "center", flexWrap: "wrap", gap: 2 },
29293
30915
  children: [
29294
- /* @__PURE__ */ jsxs(
29295
- "select",
30916
+ /* @__PURE__ */ jsx(
30917
+ ToolbarMenuSelect,
29296
30918
  {
29297
30919
  title: "Apply layout changes to",
29298
- "aria-label": "Apply layout changes to",
30920
+ ariaLabel: "Apply layout changes to",
29299
30921
  value: scope,
29300
- onMouseDown: (e) => e.stopPropagation(),
29301
- onChange: (e) => setScope(e.target.value),
29302
- style: { ...selectStyle, width: 118, maxWidth: "100%" },
29303
- children: [
29304
- /* @__PURE__ */ jsx("option", { value: "document", children: "Whole document" }),
29305
- /* @__PURE__ */ jsx("option", { value: "section", children: "This section" })
29306
- ]
30922
+ width: 118,
30923
+ menuWidth: 170,
30924
+ options: [
30925
+ { value: "document", label: "Whole document" },
30926
+ { value: "section", label: "This section" }
30927
+ ],
30928
+ onChange: (value) => setScope(value),
30929
+ style: { maxWidth: "100%" }
29307
30930
  }
29308
30931
  ),
29309
30932
  /* @__PURE__ */ jsx(Sep, {}),
@@ -29530,42 +31153,44 @@ function DocxToolbar({
29530
31153
  if (on("styles"))
29531
31154
  groups.push({
29532
31155
  key: "styles",
29533
- node: /* @__PURE__ */ jsxs(
29534
- "select",
31156
+ node: /* @__PURE__ */ jsx(
31157
+ ToolbarMenuSelect,
29535
31158
  {
29536
31159
  title: "Paragraph style",
29537
31160
  value: curStyle ?? "__normal",
29538
- onMouseDown: (e) => e.stopPropagation(),
29539
- onChange: (e) => {
29540
- if (e.target.value) {
29541
- api?.setParagraphStyle(e.target.value === "__normal" ? null : e.target.value);
31161
+ width: 92,
31162
+ menuWidth: 190,
31163
+ options: [
31164
+ { value: "__normal", label: "Normal" },
31165
+ ...(api?.listParagraphStyles() ?? []).filter((style2) => !/^normal$/i.test(style2.name)).map((style2) => ({ value: style2.id, label: style2.name })),
31166
+ ...curStyle !== null && !(api?.listParagraphStyles() ?? []).some((style2) => style2.id === curStyle) ? [{ value: curStyle, label: api?.document.styles.byId.get(curStyle)?.name ?? curStyle }] : []
31167
+ ],
31168
+ onChange: (value) => {
31169
+ if (value) {
31170
+ api?.setParagraphStyle(value === "__normal" ? null : value);
29542
31171
  setCurStyle(api?.getParagraphStyleId?.() ?? null);
29543
31172
  }
29544
- },
29545
- style: { ...selectStyle, width: 92 },
29546
- children: [
29547
- /* @__PURE__ */ jsx("option", { value: "__normal", children: "Normal" }),
29548
- (api?.listParagraphStyles() ?? []).filter((s) => !/^normal$/i.test(s.name)).map((s) => /* @__PURE__ */ jsx("option", { value: s.id, children: s.name }, s.id)),
29549
- curStyle !== null && !(api?.listParagraphStyles() ?? []).some((s) => s.id === curStyle) && /* @__PURE__ */ jsx("option", { value: curStyle, children: api?.document.styles.byId.get(curStyle)?.name ?? curStyle })
29550
- ]
31173
+ }
29551
31174
  }
29552
31175
  )
29553
31176
  });
29554
31177
  if (on("font"))
29555
31178
  groups.push({
29556
31179
  key: "font",
29557
- node: /* @__PURE__ */ jsxs(
29558
- "select",
31180
+ node: /* @__PURE__ */ jsx(
31181
+ ToolbarMenuSelect,
29559
31182
  {
29560
31183
  title: "Font",
29561
31184
  value: fmt?.fontFamily ?? "",
29562
- onMouseDown: (e) => e.stopPropagation(),
29563
- onChange: (e) => e.target.value && apply({ fontFamily: e.target.value }),
29564
- style: { ...selectStyle, width: 130 },
29565
- children: [
29566
- /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Font" }),
29567
- (fmt?.fontFamily && !detectFonts().includes(fmt.fontFamily) ? [fmt.fontFamily, ...detectFonts()] : detectFonts()).map((f) => /* @__PURE__ */ jsx("option", { value: f, style: { fontFamily: f }, children: f }, f))
29568
- ]
31185
+ placeholder: "Font",
31186
+ width: 130,
31187
+ menuWidth: 210,
31188
+ options: (fmt?.fontFamily && !detectFonts().includes(fmt.fontFamily) ? [fmt.fontFamily, ...detectFonts()] : detectFonts()).map((font) => ({
31189
+ value: font,
31190
+ label: font,
31191
+ fontFamily: font
31192
+ })),
31193
+ onChange: (value) => value && apply({ fontFamily: value })
29569
31194
  }
29570
31195
  )
29571
31196
  });
@@ -29573,18 +31198,16 @@ function DocxToolbar({
29573
31198
  groups.push({
29574
31199
  key: "size",
29575
31200
  node: /* @__PURE__ */ jsxs(Fragment2, { children: [
29576
- /* @__PURE__ */ jsxs(
29577
- "select",
31201
+ /* @__PURE__ */ jsx(
31202
+ ToolbarMenuSelect,
29578
31203
  {
29579
31204
  title: "Font size",
29580
- value: fmt?.fontSizePt ?? "",
29581
- onMouseDown: (e) => e.stopPropagation(),
29582
- onChange: (e) => e.target.value && apply({ fontSizePt: parseFloat(e.target.value) }),
29583
- style: { ...selectStyle, width: 58 },
29584
- children: [
29585
- /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Size" }),
29586
- SIZES.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: s }, s))
29587
- ]
31205
+ value: fmt?.fontSizePt === void 0 ? "" : String(fmt.fontSizePt),
31206
+ placeholder: "Size",
31207
+ width: 58,
31208
+ menuWidth: 92,
31209
+ options: SIZES.map((size) => ({ value: String(size), label: String(size) })),
31210
+ onChange: (value) => value && apply({ fontSizePt: parseFloat(value) })
29588
31211
  }
29589
31212
  ),
29590
31213
  /* @__PURE__ */ jsx(Sep, {})
@@ -29641,19 +31264,15 @@ function DocxToolbar({
29641
31264
  if (on("color"))
29642
31265
  groups.push({
29643
31266
  key: "color",
29644
- node: /* @__PURE__ */ jsxs("label", { title: "Text color", style: { display: "inline-flex", alignItems: "center", cursor: "pointer" }, children: [
29645
- /* @__PURE__ */ jsx("span", { style: { fontSize: 13, borderBottom: `3px solid ${fmt?.color && fmt.color !== "auto" ? fmt.color : "#000"}`, padding: "0 3px", color: T.fg }, children: "A" }),
29646
- /* @__PURE__ */ jsx(
29647
- "input",
29648
- {
29649
- type: "color",
29650
- value: fmt?.color && fmt.color !== "auto" ? fmt.color : "#000000",
29651
- onMouseDown: (e) => e.stopPropagation(),
29652
- onChange: (e) => apply({ color: e.target.value }),
29653
- style: { width: 0, height: 0, opacity: 0, border: "none", padding: 0 }
29654
- }
29655
- )
29656
- ] })
31267
+ node: /* @__PURE__ */ jsx(
31268
+ ColorMenu,
31269
+ {
31270
+ current: fmt?.color && fmt.color !== "auto" ? fmt.color : "#000000",
31271
+ title: "Text color",
31272
+ trigger: /* @__PURE__ */ jsx("span", { style: { fontSize: 13, borderBottom: `3px solid ${fmt?.color && fmt.color !== "auto" ? fmt.color : "#000"}`, padding: "0 3px", color: T.fg }, children: "A" }),
31273
+ onPick: (value) => apply({ color: value })
31274
+ }
31275
+ )
29657
31276
  });
29658
31277
  if (on("highlight"))
29659
31278
  groups.push({
@@ -30325,20 +31944,25 @@ function DocxView({
30325
31944
  "Calibri",
30326
31945
  "Calibri Light",
30327
31946
  "Tahoma",
30328
- "Franklin Gothic Medium",
30329
- // Complex-script faces gate layout too (advances must be real
30330
- // before line breaking): Mangal (Devanagari), Latha (Tamil),
30331
- // Noto Sans Lao Looped (Lao — bundled OFL webfont).
30332
- "Mangal",
30333
- "Latha",
30334
- "Noto Sans Lao Looped"
31947
+ "Franklin Gothic Medium"
30335
31948
  ];
30336
31949
  for (const fam of latin) {
30337
31950
  for (const variant of ["", "italic ", "bold ", "bold italic "]) {
30338
31951
  loads.push(document.fonts.load(`${variant}16px "${fam}"`).catch(() => []));
30339
31952
  }
30340
31953
  }
31954
+ const complex = [
31955
+ ["Mangal", "\u0905"],
31956
+ ["Latha", "\u0B85"],
31957
+ ["Noto Sans Lao Looped", "\u0E81"]
31958
+ ];
31959
+ for (const [fam, sample] of complex) {
31960
+ for (const variant of ["", "bold "]) {
31961
+ loads.push(document.fonts.load(`${variant}16px "${fam}"`, sample).catch(() => []));
31962
+ }
31963
+ }
30341
31964
  loads.push(document.fonts.load('16px "Cambria Math"').catch(() => []));
31965
+ loads.push(document.fonts.load('16px "Segoe UI Emoji"', "\u{1F680}").catch(() => []));
30342
31966
  const cjk = [
30343
31967
  "MS Mincho",
30344
31968
  "MS Gothic",
@@ -30353,7 +31977,7 @@ function DocxView({
30353
31977
  ];
30354
31978
  for (const fam of cjk) {
30355
31979
  for (const variant of ["", "bold "]) {
30356
- loads.push(document.fonts.load(`${variant}16px "${fam}"`).catch(() => []));
31980
+ loads.push(document.fonts.load(`${variant}16px "${fam}"`, "A\u6F22").catch(() => []));
30357
31981
  }
30358
31982
  }
30359
31983
  await Promise.all(loads);
@@ -30400,7 +32024,39 @@ function DocxView({
30400
32024
  else if (formatted.length > 0) editor?.selectRanges(formatted);
30401
32025
  document.dispatchEvent(new CustomEvent("dxw-selection"));
30402
32026
  },
30403
- onStyleShortcut: (styleId) => applyStyleShortcut?.(styleId)
32027
+ onStyleShortcut: (styleId) => applyStyleShortcut?.(styleId),
32028
+ onTextCommand: (command) => {
32029
+ const current = apiRef.current;
32030
+ if (!current) return;
32031
+ if (command === "link") {
32032
+ const anchor = containerRef.current;
32033
+ if (!anchor) return;
32034
+ void requestTextInputDialog(anchor, {
32035
+ title: "Link address",
32036
+ label: "URL",
32037
+ value: current.getLinkAt() ?? "https://"
32038
+ }).then((next) => {
32039
+ if (next !== null) current.setLink(next.trim() || null);
32040
+ });
32041
+ } else if (command === "comment") {
32042
+ const anchor = containerRef.current;
32043
+ if (!anchor) return;
32044
+ void requestTextInputDialog(anchor, {
32045
+ title: "New comment",
32046
+ label: "Comment",
32047
+ submitLabel: "Comment",
32048
+ multiline: true
32049
+ }).then((text) => {
32050
+ if (text?.trim()) current.addComment(text.trim());
32051
+ });
32052
+ } else if (command === "bullet" || command === "number") {
32053
+ current.toggleList(command);
32054
+ } else {
32055
+ current.setAlignment(
32056
+ command === "alignLeft" ? "left" : command === "alignCenter" ? "center" : command === "alignRight" ? "right" : "justify"
32057
+ );
32058
+ }
32059
+ }
30404
32060
  };
30405
32061
  editor = new DocxEditor(editorConfig);
30406
32062
  editor.attach();
@@ -31019,7 +32675,8 @@ function DocxView({
31019
32675
  alignItems: "center"
31020
32676
  },
31021
32677
  children: [
31022
- /* @__PURE__ */ jsx2("span", { style: { font: "600 11.5px system-ui, sans-serif", color: "#5f6368", padding: "0 4px" }, children: "Header & footer" }),
32678
+ /* @__PURE__ */ jsx2("span", { style: { font: "600 11.5px system-ui, sans-serif", color: "#5f6368", padding: "0 4px" }, children: "Header & footer \xB7 repeats on pages" }),
32679
+ hotBtn("Line", "Insert a reusable line in the repeating page layer", () => apiRef.current?.insertShape("line")),
31023
32680
  hotBtn("Page number", "Insert a dynamic page number at the caret", () => apiRef.current?.insertPageNumber("page")),
31024
32681
  hotBtn("Page X of Y", "Insert 'Page X of Y' at the caret", () => apiRef.current?.insertPageNumber("pageOfTotal")),
31025
32682
  hotBtn("Close", "Return to the document body", () => {
@@ -31035,6 +32692,7 @@ export {
31035
32692
  DocxDocument,
31036
32693
  DocxToolbar,
31037
32694
  DocxView,
32695
+ ToolbarMenuSelect,
31038
32696
  layoutDocument,
31039
32697
  printPages,
31040
32698
  renderToDom