@harbour-enterprises/superdoc 1.0.0-next.3 → 1.0.0-next.4

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.
Files changed (27) hide show
  1. package/dist/chunks/{PdfViewer-cVGt7Ne7.cjs → PdfViewer-CtSTAdvv.cjs} +1 -1
  2. package/dist/chunks/{PdfViewer-PUn0q_lG.es.js → PdfViewer-DtdN17V4.es.js} +1 -1
  3. package/dist/chunks/{index-Djmhr-R6.cjs → index-Bj1kFbYe.cjs} +3 -3
  4. package/dist/chunks/{index-mHanL4xN.es.js → index-Cxv7dMYN.es.js} +3 -3
  5. package/dist/chunks/{index-DLO-SgUP-c73otQJ5.es.js → index-D_KE9gpD-aIqhxcuF.es.js} +1 -1
  6. package/dist/chunks/{index-DLO-SgUP-BNtYQDPq.cjs → index-D_KE9gpD-radGpP4I.cjs} +1 -1
  7. package/dist/chunks/{super-editor.es-S9Kp_7dy.es.js → super-editor.es-B2fSLkzN.es.js} +417 -117
  8. package/dist/chunks/{super-editor.es-Cve8WKfM.cjs → super-editor.es-BhQu31e4.cjs} +417 -117
  9. package/dist/super-editor/ai-writer.es.js +2 -2
  10. package/dist/super-editor/chunks/{converter-ByybZRFp.js → converter-CVqUjX24.js} +1 -1
  11. package/dist/super-editor/chunks/{docx-zipper-CFQANTLI.js → docx-zipper-CsWjoVKD.js} +1 -1
  12. package/dist/super-editor/chunks/{editor-Cu4hD14N.js → editor-Cc8nus2C.js} +418 -118
  13. package/dist/super-editor/chunks/{index-DLO-SgUP.js → index-D_KE9gpD.js} +1 -1
  14. package/dist/super-editor/chunks/{toolbar-5MMla0sj.js → toolbar-CoOpR1xE.js} +2 -2
  15. package/dist/super-editor/converter.es.js +1 -1
  16. package/dist/super-editor/docx-zipper.es.js +2 -2
  17. package/dist/super-editor/editor.es.js +3 -3
  18. package/dist/super-editor/file-zipper.es.js +1 -1
  19. package/dist/super-editor/super-editor.es.js +6 -6
  20. package/dist/super-editor/toolbar.es.js +2 -2
  21. package/dist/super-editor.cjs +1 -1
  22. package/dist/super-editor.es.js +1 -1
  23. package/dist/superdoc.cjs +2 -2
  24. package/dist/superdoc.es.js +2 -2
  25. package/dist/superdoc.umd.js +419 -119
  26. package/dist/superdoc.umd.js.map +1 -1
  27. package/package.json +1 -1
@@ -42427,7 +42427,7 @@ const _SuperConverter = class _SuperConverter2 {
42427
42427
  static getStoredSuperdocVersion(docx) {
42428
42428
  return _SuperConverter2.getStoredCustomProperty(docx, "SuperdocVersion");
42429
42429
  }
42430
- static setStoredSuperdocVersion(docx = this.convertedXml, version2 = "1.0.0-next.3") {
42430
+ static setStoredSuperdocVersion(docx = this.convertedXml, version2 = "1.0.0-next.4") {
42431
42431
  return _SuperConverter2.setStoredCustomProperty(docx, "SuperdocVersion", version2, false);
42432
42432
  }
42433
42433
  /**
@@ -59655,7 +59655,7 @@ const isHeadless = (editor) => {
59655
59655
  const shouldSkipNodeView = (editor) => {
59656
59656
  return isHeadless(editor);
59657
59657
  };
59658
- const summaryVersion = "1.0.0-next.3";
59658
+ const summaryVersion = "1.0.0-next.4";
59659
59659
  const nodeKeys = ["group", "content", "marks", "inline", "atom", "defining", "code", "tableRole", "summary"];
59660
59660
  const markKeys = ["group", "inclusive", "excludes", "spanning", "code"];
59661
59661
  function mapAttributes(attrs) {
@@ -60444,7 +60444,7 @@ const _Editor = class _Editor2 extends EventEmitter$1 {
60444
60444
  { default: remarkStringify },
60445
60445
  { default: remarkGfm }
60446
60446
  ] = await Promise.all([
60447
- Promise.resolve().then(() => require("./index-DLO-SgUP-BNtYQDPq.cjs")),
60447
+ Promise.resolve().then(() => require("./index-D_KE9gpD-radGpP4I.cjs")),
60448
60448
  Promise.resolve().then(() => require("./index-DRCvimau-H4Ck3S9a.cjs")),
60449
60449
  Promise.resolve().then(() => require("./index-C_x_N6Uh-Db3CUJMX.cjs")),
60450
60450
  Promise.resolve().then(() => require("./index-D_sWOSiG-BtDZzJ6I.cjs")),
@@ -60649,7 +60649,7 @@ const _Editor = class _Editor2 extends EventEmitter$1 {
60649
60649
  * Process collaboration migrations
60650
60650
  */
60651
60651
  processCollaborationMigrations() {
60652
- console.debug("[checkVersionMigrations] Current editor version", "1.0.0-next.3");
60652
+ console.debug("[checkVersionMigrations] Current editor version", "1.0.0-next.4");
60653
60653
  if (!this.options.ydoc) return;
60654
60654
  const metaMap = this.options.ydoc.getMap("meta");
60655
60655
  let docVersion = metaMap.get("version");
@@ -66254,6 +66254,34 @@ function calculateTabWidth(params2) {
66254
66254
  tabStopPosUsed: nextStop.pos
66255
66255
  };
66256
66256
  }
66257
+ const SPACE_CHARS$1 = /* @__PURE__ */ new Set([" ", " "]);
66258
+ function shouldApplyJustify(params2) {
66259
+ const { alignment: alignment2, hasExplicitPositioning, isLastLineOfParagraph, paragraphEndsWithLineBreak, skipJustifyOverride } = params2;
66260
+ if (alignment2 !== "justify" && alignment2 !== "both") {
66261
+ return false;
66262
+ }
66263
+ if (skipJustifyOverride === true) {
66264
+ return false;
66265
+ }
66266
+ if (hasExplicitPositioning) {
66267
+ return false;
66268
+ }
66269
+ if (isLastLineOfParagraph && !paragraphEndsWithLineBreak) {
66270
+ return false;
66271
+ }
66272
+ return true;
66273
+ }
66274
+ function calculateJustifySpacing(params2) {
66275
+ const { lineWidth, availableWidth, spaceCount, shouldJustify } = params2;
66276
+ if (!shouldJustify) {
66277
+ return 0;
66278
+ }
66279
+ if (spaceCount <= 0) {
66280
+ return 0;
66281
+ }
66282
+ const slack = availableWidth - lineWidth;
66283
+ return slack / spaceCount;
66284
+ }
66257
66285
  function resolveSpacingIndent$1(style2, numbering) {
66258
66286
  const spacing = {
66259
66287
  before: style2.spacing?.before ?? 0,
@@ -66520,13 +66548,14 @@ const hydrateParagraphStyleAttrs = (para, context, preResolved) => {
66520
66548
  };
66521
66549
  }
66522
66550
  }
66551
+ const normalizedAlign = normalizeAlignment(resolvedExtended.justification);
66523
66552
  const hydrated = {
66524
66553
  resolved,
66525
66554
  spacing: resolvedSpacing,
66526
66555
  indent: resolvedIndent,
66527
66556
  borders: cloneIfObject(resolvedExtended.borders),
66528
66557
  shading: cloneIfObject(resolvedExtended.shading),
66529
- alignment: resolvedExtended.justification,
66558
+ alignment: normalizedAlign,
66530
66559
  tabStops: cloneIfObject(resolvedExtended.tabStops),
66531
66560
  keepLines: resolvedExtended.keepLines,
66532
66561
  keepNext: resolvedExtended.keepNext,
@@ -67227,7 +67256,7 @@ const computeParagraphAttrs = (para, styleContext, listCounterContext, converter
67227
67256
  } else if (styleAlignment) {
67228
67257
  paragraphAttrs.alignment = styleAlignment;
67229
67258
  } else if (computed2.paragraph.alignment) {
67230
- paragraphAttrs.alignment = computed2.paragraph.alignment;
67259
+ paragraphAttrs.alignment = normalizeAlignment(computed2.paragraph.alignment);
67231
67260
  }
67232
67261
  const spacingPx = spacingPtToPx(spacing, normalizedSpacing);
67233
67262
  if (spacingPx) paragraphAttrs.spacing = spacingPx;
@@ -70184,7 +70213,7 @@ function tableNodeToBlock(node, nextBlockId, positions, defaultFont, defaultSize
70184
70213
  let measurementCanvas = null;
70185
70214
  let measurementCtx = null;
70186
70215
  const TAB_CHAR_LENGTH = 1;
70187
- const SPACE_CHARS = /* @__PURE__ */ new Set([" ", " "]);
70216
+ const SPACE_CHARS = SPACE_CHARS$1;
70188
70217
  const isTabRun$1 = (run2) => run2?.kind === "tab";
70189
70218
  function getMeasurementContext() {
70190
70219
  if (measurementCtx) return measurementCtx;
@@ -70210,29 +70239,50 @@ const countSpaces = (text) => {
70210
70239
  }
70211
70240
  return spaces;
70212
70241
  };
70213
- const getJustifyAdjustment = (block, line, availableWidthOverride, alignmentOverride) => {
70242
+ const getJustifyAdjustment = (block, line, availableWidthOverride, alignmentOverride, isLastLineOfParagraph, paragraphEndsWithLineBreak, skipJustifyOverride) => {
70214
70243
  if (block.kind !== "paragraph") {
70215
70244
  return { extraPerSpace: 0, totalSpaces: 0 };
70216
70245
  }
70217
- const alignment2 = alignmentOverride ?? block.attrs?.alignment;
70218
- const hasExplicitPositioning = line.segments?.some((seg) => seg.x !== void 0);
70219
- const availableWidth = availableWidthOverride ?? line.maxWidth ?? line.width;
70220
- const slack = Math.max(0, availableWidth - line.width);
70221
- if (alignment2 !== "justify" || hasExplicitPositioning || slack <= 0) {
70246
+ if (block.runs.length === 0) {
70222
70247
  return { extraPerSpace: 0, totalSpaces: 0 };
70223
70248
  }
70224
- const runs = sliceRunsForLine$1(block, line);
70225
- const totalSpaces = runs.reduce((sum, run2) => {
70226
- if (isTabRun$1(run2) || "src" in run2 || run2.kind === "lineBreak" || run2.kind === "break" || run2.kind === "fieldAnnotation") {
70227
- return sum;
70228
- }
70229
- return sum + countSpaces(run2.text ?? "");
70230
- }, 0);
70231
- if (totalSpaces <= 0) {
70249
+ const alignment2 = alignmentOverride ?? block.attrs?.alignment;
70250
+ const hasExplicitPositioning = line.segments?.some((seg) => seg.x !== void 0) ?? false;
70251
+ const lastRunIndex = block.runs.length - 1;
70252
+ const lastRun = block.runs[lastRunIndex];
70253
+ const derivedIsLastLine = line.toRun >= lastRunIndex;
70254
+ const derivedEndsWithLineBreak = lastRun ? lastRun.kind === "lineBreak" : false;
70255
+ const shouldJustify = shouldApplyJustify({
70256
+ alignment: alignment2,
70257
+ hasExplicitPositioning,
70258
+ isLastLineOfParagraph: derivedIsLastLine,
70259
+ paragraphEndsWithLineBreak: derivedEndsWithLineBreak,
70260
+ skipJustifyOverride
70261
+ });
70262
+ if (!shouldJustify) {
70232
70263
  return { extraPerSpace: 0, totalSpaces: 0 };
70233
70264
  }
70265
+ let totalSpaces = line.spaceCount ?? 0;
70266
+ if (totalSpaces === 0) {
70267
+ const runs = sliceRunsForLine$1(block, line);
70268
+ totalSpaces = runs.reduce((sum, run2) => {
70269
+ if (isTabRun$1(run2) || "src" in run2 || run2.kind === "lineBreak" || run2.kind === "break" || run2.kind === "fieldAnnotation") {
70270
+ return sum;
70271
+ }
70272
+ return sum + countSpaces(run2.text ?? "");
70273
+ }, 0);
70274
+ }
70275
+ const availableWidth = availableWidthOverride ?? line.maxWidth ?? line.width;
70276
+ const lineWidth = line.naturalWidth ?? line.width;
70277
+ const extraPerSpace = calculateJustifySpacing({
70278
+ lineWidth,
70279
+ availableWidth,
70280
+ spaceCount: totalSpaces,
70281
+ shouldJustify: true
70282
+ // Already checked above
70283
+ });
70234
70284
  return {
70235
- extraPerSpace: slack / totalSpaces,
70285
+ extraPerSpace,
70236
70286
  totalSpaces
70237
70287
  };
70238
70288
  };
@@ -70299,7 +70349,7 @@ function measureCharacterX(block, line, charOffset, availableWidthOverride, alig
70299
70349
  line.width;
70300
70350
  const justify = getJustifyAdjustment(block, line, availableWidth, alignmentOverride);
70301
70351
  const alignment2 = alignmentOverride ?? (block.kind === "paragraph" ? block.attrs?.alignment : void 0);
70302
- const renderedLineWidth = alignment2 === "justify" ? line.width + Math.max(0, availableWidth - line.width) : line.width;
70352
+ const renderedLineWidth = alignment2 === "justify" && justify.extraPerSpace !== 0 ? availableWidth : line.width;
70303
70353
  const hasExplicitPositioning = line.segments?.some((seg) => seg.x !== void 0);
70304
70354
  const alignmentOffset = !hasExplicitPositioning && alignment2 === "center" ? Math.max(0, (availableWidth - renderedLineWidth) / 2) : !hasExplicitPositioning && alignment2 === "right" ? Math.max(0, availableWidth - renderedLineWidth) : 0;
70305
70355
  if (hasExplicitPositioning && line.segments && ctx2) {
@@ -70342,13 +70392,13 @@ function measureCharacterX(block, line, charOffset, availableWidthOverride, alig
70342
70392
  const textUpToTarget = text.slice(0, offsetInRun);
70343
70393
  const measured2 = ctx2.measureText(textUpToTarget);
70344
70394
  const spacingWidth = computeLetterSpacingWidth(run2, offsetInRun, runLength);
70345
- const spacesInPortion = justify.extraPerSpace > 0 ? countSpaces(textUpToTarget) : 0;
70395
+ const spacesInPortion = justify.extraPerSpace !== 0 ? countSpaces(textUpToTarget) : 0;
70346
70396
  return alignmentOffset + currentX + measured2.width + spacingWidth + justify.extraPerSpace * (spaceTally + spacesInPortion);
70347
70397
  }
70348
70398
  ctx2.font = getRunFontString(run2);
70349
70399
  const measured = ctx2.measureText(text);
70350
70400
  const runLetterSpacing = computeLetterSpacingWidth(run2, runLength, runLength);
70351
- const spacesInRun = justify.extraPerSpace > 0 ? countSpaces(text) : 0;
70401
+ const spacesInRun = justify.extraPerSpace !== 0 ? countSpaces(text) : 0;
70352
70402
  currentX += measured.width + runLetterSpacing + justify.extraPerSpace * spacesInRun;
70353
70403
  spaceTally += spacesInRun;
70354
70404
  currentCharOffset += runLength;
@@ -75500,9 +75550,66 @@ const _DomPainter = class _DomPainter2 {
75500
75550
  const firstLineOffset = suppressFirstLineIndent ? 0 : (paraIndent?.firstLine ?? 0) - (paraIndent?.hanging ?? 0);
75501
75551
  const lastRun = block.runs.length > 0 ? block.runs[block.runs.length - 1] : null;
75502
75552
  const paragraphEndsWithLineBreak = lastRun?.kind === "lineBreak";
75553
+ let listFirstLineMarkerTabWidth;
75554
+ if (!fragment.continuesFromPrev && fragment.markerWidth && wordLayout?.marker) {
75555
+ const markerBoxWidth = fragment.markerWidth;
75556
+ const markerTextWidth = fragment.markerTextWidth != null && isFinite(fragment.markerTextWidth) && fragment.markerTextWidth >= 0 ? fragment.markerTextWidth : markerBoxWidth;
75557
+ const suffix2 = wordLayout.marker.suffix ?? "tab";
75558
+ if (suffix2 === "tab") {
75559
+ const markerJustification = wordLayout.marker.justification ?? "left";
75560
+ const isFirstLineIndentMode = wordLayout.firstLineIndentMode === true;
75561
+ let markerStartPos;
75562
+ if (isFirstLineIndentMode && wordLayout.marker.markerX !== void 0 && Number.isFinite(wordLayout.marker.markerX)) {
75563
+ markerStartPos = wordLayout.marker.markerX;
75564
+ } else {
75565
+ const hanging = paraIndent?.hanging ?? 0;
75566
+ const firstLine = paraIndent?.firstLine ?? 0;
75567
+ markerStartPos = paraIndentLeft - hanging + firstLine;
75568
+ }
75569
+ const validMarkerStartPos = Number.isFinite(markerStartPos) ? markerStartPos : 0;
75570
+ let tabWidth;
75571
+ if (markerJustification === "left") {
75572
+ const currentPos = validMarkerStartPos + markerTextWidth;
75573
+ if (isFirstLineIndentMode) {
75574
+ const textStartTarget = wordLayout.marker.textStartX !== void 0 && Number.isFinite(wordLayout.marker.textStartX) ? wordLayout.marker.textStartX : wordLayout.textStartPx;
75575
+ if (textStartTarget !== void 0 && Number.isFinite(textStartTarget) && textStartTarget > currentPos) {
75576
+ tabWidth = textStartTarget - currentPos;
75577
+ } else {
75578
+ tabWidth = LIST_MARKER_GAP$1;
75579
+ }
75580
+ } else {
75581
+ const firstLine = paraIndent?.firstLine ?? 0;
75582
+ const textStart = paraIndentLeft + firstLine;
75583
+ tabWidth = textStart - currentPos;
75584
+ if (tabWidth <= 0) {
75585
+ tabWidth = DEFAULT_TAB_INTERVAL_PX$1 - currentPos % DEFAULT_TAB_INTERVAL_PX$1;
75586
+ } else if (tabWidth < LIST_MARKER_GAP$1) {
75587
+ tabWidth = LIST_MARKER_GAP$1;
75588
+ }
75589
+ }
75590
+ } else {
75591
+ const gutterWidth = fragment.markerGutter ?? wordLayout.marker.gutterWidthPx;
75592
+ tabWidth = gutterWidth !== void 0 && Number.isFinite(gutterWidth) && gutterWidth > 0 ? gutterWidth : LIST_MARKER_GAP$1;
75593
+ }
75594
+ if (tabWidth < LIST_MARKER_GAP$1) {
75595
+ tabWidth = LIST_MARKER_GAP$1;
75596
+ }
75597
+ listFirstLineMarkerTabWidth = validMarkerStartPos + markerTextWidth + tabWidth;
75598
+ } else if (suffix2 === "space") {
75599
+ const hanging = paraIndent?.hanging ?? 0;
75600
+ const firstLine = paraIndent?.firstLine ?? 0;
75601
+ const markerStartPos = paraIndentLeft - hanging + firstLine;
75602
+ const validMarkerStartPos = Number.isFinite(markerStartPos) ? markerStartPos : 0;
75603
+ listFirstLineMarkerTabWidth = validMarkerStartPos + markerTextWidth + 4;
75604
+ }
75605
+ }
75503
75606
  lines.forEach((line, index2) => {
75504
- const fallbackAvailableWidth = Math.max(0, fragment.width - (paraIndentLeft + paraIndentRight));
75505
- const availableWidthOverride = line.maxWidth ?? fallbackAvailableWidth;
75607
+ const positiveIndentReduction = Math.max(0, paraIndentLeft) + Math.max(0, paraIndentRight);
75608
+ const fallbackAvailableWidth = Math.max(0, fragment.width - positiveIndentReduction);
75609
+ let availableWidthOverride = line.maxWidth != null ? Math.min(line.maxWidth, fallbackAvailableWidth) : fallbackAvailableWidth;
75610
+ if (index2 === 0 && listFirstLineMarkerTabWidth != null) {
75611
+ availableWidthOverride = fragment.width - listFirstLineMarkerTabWidth - Math.max(0, paraIndentRight);
75612
+ }
75506
75613
  const isLastLineOfFragment = index2 === lines.length - 1;
75507
75614
  const isLastLineOfParagraph = isLastLineOfFragment && !fragment.continuesOnNext;
75508
75615
  const shouldSkipJustifyForLastLine = isLastLineOfParagraph && !paragraphEndsWithLineBreak;
@@ -75528,7 +75635,10 @@ const _DomPainter = class _DomPainter2 {
75528
75635
  }
75529
75636
  } else if (paraIndentLeft && paraIndentLeft > 0) {
75530
75637
  lineEl.style.paddingLeft = `${paraIndentLeft}px`;
75531
- } else if (!isFirstLine && paraIndent?.hanging && paraIndent.hanging > 0) {
75638
+ } else if (!isFirstLine && paraIndent?.hanging && paraIndent.hanging > 0 && // Only apply hanging padding when left indent is NOT negative.
75639
+ // When left indent is negative, the fragment position already accounts for it.
75640
+ // Adding padding here would shift body lines right, causing right-side overflow.
75641
+ !(paraIndentLeft != null && paraIndentLeft < 0)) {
75532
75642
  lineEl.style.paddingLeft = `${paraIndent.hanging}px`;
75533
75643
  }
75534
75644
  }
@@ -75557,6 +75667,7 @@ const _DomPainter = class _DomPainter2 {
75557
75667
  lineEl.style.paddingLeft = `${validMarkerStartPos}px`;
75558
75668
  const markerContainer = this.doc.createElement("span");
75559
75669
  markerContainer.style.display = "inline-block";
75670
+ markerContainer.style.wordSpacing = "0px";
75560
75671
  const markerEl = this.doc.createElement("span");
75561
75672
  markerEl.classList.add("superdoc-paragraph-marker");
75562
75673
  markerEl.textContent = wordLayout.marker.markerText ?? "";
@@ -75642,10 +75753,15 @@ const _DomPainter = class _DomPainter2 {
75642
75753
  }
75643
75754
  }
75644
75755
  tabEl.style.display = "inline-block";
75756
+ tabEl.style.wordSpacing = "0px";
75645
75757
  tabEl.style.width = `${tabWidth}px`;
75646
75758
  lineEl.prepend(tabEl);
75647
75759
  } else if (suffix2 === "space") {
75648
- lineEl.prepend(this.doc.createTextNode(" "));
75760
+ const spaceEl = this.doc.createElement("span");
75761
+ spaceEl.classList.add("superdoc-marker-suffix-space");
75762
+ spaceEl.style.wordSpacing = "0px";
75763
+ spaceEl.textContent = " ";
75764
+ lineEl.prepend(spaceEl);
75649
75765
  }
75650
75766
  lineEl.prepend(markerContainer);
75651
75767
  }
@@ -76937,11 +77053,8 @@ const _DomPainter = class _DomPainter2 {
76937
77053
  el.setAttribute("styleid", styleId);
76938
77054
  }
76939
77055
  const alignment2 = block.attrs?.alignment;
76940
- const effectiveAlignment = alignment2;
76941
- if (effectiveAlignment === "center" || effectiveAlignment === "right") {
76942
- el.style.textAlign = effectiveAlignment;
76943
- } else if (effectiveAlignment === "justify") {
76944
- el.style.textAlign = "left";
77056
+ if (alignment2 === "center" || alignment2 === "right") {
77057
+ el.style.textAlign = alignment2;
76945
77058
  } else {
76946
77059
  el.style.textAlign = "left";
76947
77060
  }
@@ -76952,9 +77065,8 @@ const _DomPainter = class _DomPainter2 {
76952
77065
  if (lineRange.pmEnd != null) {
76953
77066
  el.dataset.pmEnd = String(lineRange.pmEnd);
76954
77067
  }
76955
- const runsForLine = sliceRunsForLine(block, line);
77068
+ let runsForLine = sliceRunsForLine(block, line);
76956
77069
  const trackedConfig = this.resolveTrackedChangesConfig(block);
76957
- runsForLine.length > 0 ? runsForLine.filter((r2) => (r2.kind === "text" || r2.kind === void 0) && "text" in r2 && r2.text != null).map((r2) => r2.text) : gatherTextSlicesForLine(block, line);
76958
77070
  if (runsForLine.length === 0) {
76959
77071
  const span = this.doc.createElement("span");
76960
77072
  span.innerHTML = "&nbsp;";
@@ -77001,7 +77113,156 @@ const _DomPainter = class _DomPainter2 {
77001
77113
  });
77002
77114
  }
77003
77115
  const hasExplicitPositioning = line.segments?.some((seg) => seg.x !== void 0);
77004
- availableWidthOverride ?? line.maxWidth ?? line.width;
77116
+ const availableWidth = availableWidthOverride ?? line.maxWidth ?? line.width;
77117
+ const justifyShouldApply = shouldApplyJustify({
77118
+ alignment: block.attrs?.alignment,
77119
+ hasExplicitPositioning: hasExplicitPositioning ?? false,
77120
+ // Caller already folds last-line + trailing lineBreak behavior into skipJustify.
77121
+ isLastLineOfParagraph: false,
77122
+ paragraphEndsWithLineBreak: false,
77123
+ skipJustifyOverride: skipJustify
77124
+ });
77125
+ const countSpaces2 = (text) => {
77126
+ let count = 0;
77127
+ for (let i = 0; i < text.length; i += 1) {
77128
+ if (SPACE_CHARS$1.has(text[i])) count += 1;
77129
+ }
77130
+ return count;
77131
+ };
77132
+ if (justifyShouldApply) {
77133
+ const stableDataAttrs = (attrs) => {
77134
+ if (!attrs) return void 0;
77135
+ const keys2 = Object.keys(attrs).sort();
77136
+ const out = {};
77137
+ keys2.forEach((key2) => {
77138
+ out[key2] = attrs[key2];
77139
+ });
77140
+ return out;
77141
+ };
77142
+ const mergeSignature = (run2) => JSON.stringify({
77143
+ kind: run2.kind ?? "text",
77144
+ fontFamily: run2.fontFamily,
77145
+ fontSize: run2.fontSize,
77146
+ bold: run2.bold ?? false,
77147
+ italic: run2.italic ?? false,
77148
+ letterSpacing: run2.letterSpacing ?? null,
77149
+ color: run2.color ?? null,
77150
+ underline: run2.underline ?? null,
77151
+ strike: run2.strike ?? false,
77152
+ highlight: run2.highlight ?? null,
77153
+ textTransform: run2.textTransform ?? null,
77154
+ token: run2.token ?? null,
77155
+ pageRefMetadata: run2.pageRefMetadata ?? null,
77156
+ trackedChange: run2.trackedChange ?? null,
77157
+ sdt: run2.sdt ?? null,
77158
+ link: run2.link ?? null,
77159
+ comments: run2.comments ?? null,
77160
+ dataAttrs: stableDataAttrs(run2.dataAttrs) ?? null
77161
+ });
77162
+ const isWhitespaceOnly = (text) => {
77163
+ if (text.length === 0) return false;
77164
+ for (let i = 0; i < text.length; i += 1) {
77165
+ if (!SPACE_CHARS$1.has(text[i])) return false;
77166
+ }
77167
+ return true;
77168
+ };
77169
+ const cloneTextRun = (run2) => ({
77170
+ ...run2,
77171
+ comments: run2.comments ? [...run2.comments] : void 0,
77172
+ dataAttrs: run2.dataAttrs ? { ...run2.dataAttrs } : void 0,
77173
+ underline: run2.underline ? { ...run2.underline } : void 0,
77174
+ pageRefMetadata: run2.pageRefMetadata ? { ...run2.pageRefMetadata } : void 0
77175
+ });
77176
+ const normalized = runsForLine.map((run2) => {
77177
+ if (run2.kind !== "text" && run2.kind !== void 0 || !("text" in run2)) return run2;
77178
+ return cloneTextRun(run2);
77179
+ });
77180
+ const merged = [];
77181
+ for (let i = 0; i < normalized.length; i += 1) {
77182
+ const run2 = normalized[i];
77183
+ if (run2.kind !== "text" && run2.kind !== void 0 || !("text" in run2)) {
77184
+ merged.push(run2);
77185
+ continue;
77186
+ }
77187
+ const textRun = run2;
77188
+ if (!isWhitespaceOnly(textRun.text ?? "")) {
77189
+ merged.push(textRun);
77190
+ continue;
77191
+ }
77192
+ const prev = merged[merged.length - 1];
77193
+ if (prev && (prev.kind === "text" || prev.kind === void 0) && "text" in prev) {
77194
+ const prevTextRun = prev;
77195
+ if (mergeSignature(prevTextRun) === mergeSignature(textRun)) {
77196
+ const extra = textRun.text ?? "";
77197
+ prevTextRun.text = (prevTextRun.text ?? "") + extra;
77198
+ if (prevTextRun.pmStart != null) {
77199
+ prevTextRun.pmEnd = prevTextRun.pmStart + prevTextRun.text.length;
77200
+ } else if (prevTextRun.pmEnd != null) {
77201
+ prevTextRun.pmEnd = prevTextRun.pmEnd + extra.length;
77202
+ }
77203
+ continue;
77204
+ }
77205
+ }
77206
+ const next = normalized[i + 1];
77207
+ if (next && (next.kind === "text" || next.kind === void 0) && "text" in next) {
77208
+ const nextTextRun = next;
77209
+ if (mergeSignature(nextTextRun) === mergeSignature(textRun)) {
77210
+ const extra = textRun.text ?? "";
77211
+ nextTextRun.text = extra + (nextTextRun.text ?? "");
77212
+ if (textRun.pmStart != null) {
77213
+ nextTextRun.pmStart = textRun.pmStart;
77214
+ } else if (nextTextRun.pmStart != null) {
77215
+ nextTextRun.pmStart = nextTextRun.pmStart - extra.length;
77216
+ }
77217
+ if (nextTextRun.pmStart != null && nextTextRun.pmEnd == null) {
77218
+ nextTextRun.pmEnd = nextTextRun.pmStart + nextTextRun.text.length;
77219
+ }
77220
+ continue;
77221
+ }
77222
+ }
77223
+ merged.push(textRun);
77224
+ }
77225
+ runsForLine = merged;
77226
+ const hasNonSpaceText = runsForLine.some(
77227
+ (run2) => (run2.kind === "text" || run2.kind === void 0) && "text" in run2 && (run2.text ?? "").trim().length > 0
77228
+ );
77229
+ if (hasNonSpaceText) {
77230
+ for (let i = runsForLine.length - 1; i >= 0; i -= 1) {
77231
+ const run2 = runsForLine[i];
77232
+ if (run2.kind !== "text" && run2.kind !== void 0 || !("text" in run2)) continue;
77233
+ const text = run2.text ?? "";
77234
+ let trimCount = 0;
77235
+ for (let j2 = text.length - 1; j2 >= 0 && text[j2] === " "; j2 -= 1) {
77236
+ trimCount += 1;
77237
+ }
77238
+ if (trimCount === 0) break;
77239
+ const nextText = text.slice(0, Math.max(0, text.length - trimCount));
77240
+ if (nextText.length === 0) {
77241
+ runsForLine.splice(i, 1);
77242
+ continue;
77243
+ }
77244
+ run2.text = nextText;
77245
+ if (run2.pmEnd != null) {
77246
+ run2.pmEnd = run2.pmEnd - trimCount;
77247
+ }
77248
+ break;
77249
+ }
77250
+ }
77251
+ }
77252
+ const spaceCount = line.spaceCount ?? runsForLine.reduce((sum, run2) => {
77253
+ if (run2.kind !== "text" && run2.kind !== void 0 || !("text" in run2) || run2.text == null) return sum;
77254
+ return sum + countSpaces2(run2.text);
77255
+ }, 0);
77256
+ const lineWidth = line.naturalWidth ?? line.width;
77257
+ const spacingPerSpace = calculateJustifySpacing({
77258
+ lineWidth,
77259
+ availableWidth,
77260
+ spaceCount,
77261
+ shouldJustify: justifyShouldApply
77262
+ });
77263
+ if (spacingPerSpace !== 0) {
77264
+ el.style.wordSpacing = `${spacingPerSpace}px`;
77265
+ }
77005
77266
  if (hasExplicitPositioning && line.segments) {
77006
77267
  const paraIndent = block.attrs?.indent;
77007
77268
  const indentLeft = paraIndent?.left ?? 0;
@@ -77873,7 +78134,7 @@ const applyParagraphBlockStyles = (element, attrs) => {
77873
78134
  element.setAttribute("styleid", attrs.styleId);
77874
78135
  }
77875
78136
  if (attrs.alignment) {
77876
- element.style.textAlign = attrs.alignment;
78137
+ element.style.textAlign = attrs.alignment === "justify" || attrs.alignment === "both" ? "left" : attrs.alignment;
77877
78138
  }
77878
78139
  if (attrs.dropCap) {
77879
78140
  element.classList.add("sd-editor-dropcap");
@@ -77938,23 +78199,6 @@ const applyParagraphShadingStyles = (element, shading) => {
77938
78199
  if (!shading?.fill) return;
77939
78200
  element.style.backgroundColor = shading.fill;
77940
78201
  };
77941
- const gatherTextSlicesForLine = (block, line) => {
77942
- const slices = [];
77943
- const startRun = line.fromRun ?? 0;
77944
- const endRun = line.toRun ?? startRun;
77945
- for (let runIndex = startRun; runIndex <= endRun; runIndex += 1) {
77946
- const run2 = block.runs[runIndex];
77947
- if (!run2 || run2.kind !== "text" && run2.kind !== void 0 || !("text" in run2) || !run2.text) continue;
77948
- const isFirst = runIndex === startRun;
77949
- const isLast = runIndex === endRun;
77950
- const start2 = isFirst ? line.fromChar ?? 0 : 0;
77951
- const end2 = isLast ? line.toChar ?? run2.text.length : run2.text.length;
77952
- if (start2 >= end2) continue;
77953
- const slice2 = run2.text.slice(start2, end2);
77954
- if (slice2) slices.push(slice2);
77955
- }
77956
- return slices;
77957
- };
77958
78202
  const sliceRunsForLine = (block, line) => {
77959
78203
  const result = [];
77960
78204
  for (let runIndex = line.fromRun; runIndex <= line.toRun; runIndex += 1) {
@@ -78576,7 +78820,8 @@ function calculateTextStartIndent(params2) {
78576
78820
  let indentAdjust = paraIndentLeft;
78577
78821
  if (isListItem2 && isFirstLine && isFirstLineIndentMode) {
78578
78822
  const textStartFallback = paraIndentLeft + Math.max(firstLineIndent, 0) + markerWidth;
78579
- indentAdjust = typeof wordLayout?.textStartPx === "number" && Number.isFinite(wordLayout.textStartPx) ? wordLayout.textStartPx : textStartFallback;
78823
+ const markerTextStartX = wordLayout?.marker?.textStartX;
78824
+ indentAdjust = typeof markerTextStartX === "number" && Number.isFinite(markerTextStartX) ? markerTextStartX : typeof wordLayout?.textStartPx === "number" && Number.isFinite(wordLayout.textStartPx) ? wordLayout.textStartPx : textStartFallback;
78580
78825
  } else if (isFirstLine && !isListItem2) {
78581
78826
  indentAdjust += firstLineOffset;
78582
78827
  }
@@ -79266,6 +79511,20 @@ const asSafeNumber = (value) => {
79266
79511
  }
79267
79512
  return value;
79268
79513
  };
79514
+ function calculateFirstLineIndent(block, measure) {
79515
+ const wordLayout = block.attrs?.wordLayout;
79516
+ if (!wordLayout?.firstLineIndentMode) {
79517
+ return 0;
79518
+ }
79519
+ if (!wordLayout.marker || !measure.marker) {
79520
+ return 0;
79521
+ }
79522
+ const markerWidthRaw = measure.marker.markerWidth ?? wordLayout.marker.markerBoxWidthPx ?? 0;
79523
+ const markerWidth = Number.isFinite(markerWidthRaw) && markerWidthRaw >= 0 ? markerWidthRaw : 0;
79524
+ const gutterWidthRaw = measure.marker.gutterWidth ?? 0;
79525
+ const gutterWidth = Number.isFinite(gutterWidthRaw) && gutterWidthRaw >= 0 ? gutterWidthRaw : 0;
79526
+ return markerWidth + gutterWidth;
79527
+ }
79269
79528
  function layoutParagraphBlock(ctx2, anchors) {
79270
79529
  const { block, measure, columnWidth, ensurePage, advanceColumn, columnX, floatManager } = ctx2;
79271
79530
  const remeasureParagraph2 = ctx2.remeasureParagraph;
@@ -79395,17 +79654,8 @@ function layoutParagraphBlock(ctx2, anchors) {
79395
79654
  const remeasureWidth = Math.max(1, columnWidth - indentLeft - indentRight);
79396
79655
  let didRemeasureForColumnWidth = false;
79397
79656
  if (typeof remeasureParagraph2 === "function" && typeof measurementWidth === "number" && measurementWidth > remeasureWidth) {
79398
- let firstLineIndent = 0;
79399
- const wordLayout = block.attrs?.wordLayout;
79400
- if (wordLayout?.marker && measure.marker) {
79401
- const markerJustification = wordLayout.marker.justification ?? "left";
79402
- if (markerJustification === "left") {
79403
- const markerWidth = measure.marker.markerWidth ?? 0;
79404
- const gutterWidth = measure.marker.gutterWidth ?? wordLayout.marker.gutterWidthPx ?? 0;
79405
- firstLineIndent = markerWidth + gutterWidth;
79406
- }
79407
- }
79408
- const newMeasure = remeasureParagraph2(block, remeasureWidth, firstLineIndent);
79657
+ const firstLineIndent = calculateFirstLineIndent(block, measure);
79658
+ const newMeasure = remeasureParagraph2(block, columnWidth, firstLineIndent);
79409
79659
  lines = normalizeLines(newMeasure);
79410
79660
  didRemeasureForColumnWidth = true;
79411
79661
  }
@@ -79486,16 +79736,7 @@ function layoutParagraphBlock(ctx2, anchors) {
79486
79736
  }
79487
79737
  const narrowestRemeasureWidth = Math.max(1, narrowestWidth - indentLeft - indentRight);
79488
79738
  if (narrowestRemeasureWidth < remeasureWidth) {
79489
- let firstLineIndent = 0;
79490
- const wordLayout = block.attrs?.wordLayout;
79491
- if (wordLayout?.marker && measure.marker) {
79492
- const markerJustification = wordLayout.marker.justification ?? "left";
79493
- if (markerJustification === "left") {
79494
- const markerWidth = measure.marker.markerWidth ?? 0;
79495
- const gutterWidth = measure.marker.gutterWidth ?? wordLayout.marker.gutterWidthPx ?? 0;
79496
- firstLineIndent = markerWidth + gutterWidth;
79497
- }
79498
- }
79739
+ const firstLineIndent = calculateFirstLineIndent(block, measure);
79499
79740
  const newMeasure = remeasureParagraph2(block, narrowestRemeasureWidth, firstLineIndent);
79500
79741
  lines = normalizeLines(newMeasure);
79501
79742
  didRemeasureForFloats = true;
@@ -82642,7 +82883,8 @@ function remeasureParagraph(block, maxWidth, firstLineIndent = 0) {
82642
82883
  const indentHanging = Math.max(0, indent?.hanging ?? 0);
82643
82884
  const rawFirstLineOffset = Math.max(0, firstLineIndent || indentFirstLine - indentHanging);
82644
82885
  const contentWidth = Math.max(1, maxWidth - indentLeft - indentRight);
82645
- const textStartPx = wordLayout?.textStartPx;
82886
+ const markerTextStartX = wordLayout?.marker?.textStartX;
82887
+ const textStartPx = typeof markerTextStartX === "number" && Number.isFinite(markerTextStartX) ? markerTextStartX : typeof wordLayout?.textStartPx === "number" && Number.isFinite(wordLayout.textStartPx) ? wordLayout.textStartPx : void 0;
82646
82888
  const treatAsHanging = textStartPx && indentLeft === 0 && indentHanging === 0;
82647
82889
  const firstLineWidth = typeof textStartPx === "number" && textStartPx > indentLeft && !treatAsHanging ? Math.max(1, maxWidth - textStartPx - indentRight) : Math.max(1, contentWidth - rawFirstLineOffset);
82648
82890
  const tabStops = buildTabStopsPx$1(indent, attrs?.tabs, attrs?.tabIntervalTwips);
@@ -82654,6 +82896,7 @@ function remeasureParagraph(block, maxWidth, firstLineIndent = 0) {
82654
82896
  const startRun = currentRun;
82655
82897
  const startChar = currentChar;
82656
82898
  let width = 0;
82899
+ let widthAtLastBreak = -1;
82657
82900
  let lastBreakRun = -1;
82658
82901
  let lastBreakChar = -1;
82659
82902
  let endRun = currentRun;
@@ -82671,6 +82914,7 @@ function remeasureParagraph(block, maxWidth, firstLineIndent = 0) {
82671
82914
  endChar = 1;
82672
82915
  lastBreakRun = r2;
82673
82916
  lastBreakChar = 1;
82917
+ widthAtLastBreak = width;
82674
82918
  continue;
82675
82919
  }
82676
82920
  const text = runText(run2);
@@ -82681,6 +82925,7 @@ function remeasureParagraph(block, maxWidth, firstLineIndent = 0) {
82681
82925
  if (lastBreakRun >= 0) {
82682
82926
  endRun = lastBreakRun;
82683
82927
  endChar = lastBreakChar;
82928
+ width = widthAtLastBreak >= 0 ? widthAtLastBreak : width;
82684
82929
  } else {
82685
82930
  endRun = r2;
82686
82931
  endChar = c2;
@@ -82695,6 +82940,7 @@ function remeasureParagraph(block, maxWidth, firstLineIndent = 0) {
82695
82940
  if (ch === " " || ch === " " || ch === "-") {
82696
82941
  lastBreakRun = r2;
82697
82942
  lastBreakChar = c2 + 1;
82943
+ widthAtLastBreak = width;
82698
82944
  }
82699
82945
  }
82700
82946
  if (didBreakInThisLine) break;
@@ -84405,7 +84651,9 @@ function clickToPosition(layout, blocks, measures, containerPoint, domContainer,
84405
84651
  }
84406
84652
  const markerWidth = fragment.markerWidth ?? measure.marker?.markerWidth ?? 0;
84407
84653
  const isListItem3 = markerWidth > 0;
84408
- const alignmentOverride = isListItem3 ? "left" : void 0;
84654
+ const paraAlignment = block.attrs?.alignment;
84655
+ const isJustified = paraAlignment === "justify" || paraAlignment === "both";
84656
+ const alignmentOverride = isListItem3 && !isJustified ? "left" : void 0;
84409
84657
  const pos = mapPointToPm(block, line, pageRelativePoint.x - fragment.x, isRTL, availableWidth, alignmentOverride);
84410
84658
  if (pos == null) {
84411
84659
  logClickStage("warn", "no-position", {
@@ -84472,7 +84720,9 @@ function clickToPosition(layout, blocks, measures, containerPoint, domContainer,
84472
84720
  }
84473
84721
  const cellMarkerWidth = cellMeasure.marker?.markerWidth ?? 0;
84474
84722
  const isListItem3 = cellMarkerWidth > 0;
84475
- const alignmentOverride = isListItem3 ? "left" : void 0;
84723
+ const cellAlignment = cellBlock.attrs?.alignment;
84724
+ const isJustified = cellAlignment === "justify" || cellAlignment === "both";
84725
+ const alignmentOverride = isListItem3 && !isJustified ? "left" : void 0;
84476
84726
  const pos = mapPointToPm(cellBlock, line, localX, isRTL, availableWidth, alignmentOverride);
84477
84727
  if (pos != null) {
84478
84728
  logClickStage("log", "success", {
@@ -84641,7 +84891,9 @@ function selectionToRects(layout, blocks, measures, from2, to, geometryHelper) {
84641
84891
  const charOffsetTo = pmPosToCharOffset(block, line, sliceTo);
84642
84892
  const markerWidth = fragment.markerWidth ?? measure.marker?.markerWidth ?? 0;
84643
84893
  const isListItemFlag = isListItem(markerWidth, block);
84644
- const alignmentOverride = isListItemFlag ? "left" : void 0;
84894
+ const blockAlignment = block.attrs?.alignment;
84895
+ const isJustified = blockAlignment === "justify" || blockAlignment === "both";
84896
+ const alignmentOverride = isListItemFlag && !isJustified ? "left" : void 0;
84645
84897
  const startX = mapPmToX(block, line, charOffsetFrom, fragment.width, alignmentOverride);
84646
84898
  const endX = mapPmToX(block, line, charOffsetTo, fragment.width, alignmentOverride);
84647
84899
  const indent = extractParagraphIndent(block.attrs?.indent);
@@ -85383,7 +85635,9 @@ async function measureParagraphBlock(block, maxWidth) {
85383
85635
  const contentWidth = Math.max(1, maxWidth - indentLeft - indentRight);
85384
85636
  const bodyContentWidth = contentWidth;
85385
85637
  let initialAvailableWidth;
85386
- const textStartPx = wordLayout?.textStartPx;
85638
+ const rawTextStartPx = wordLayout?.textStartPx;
85639
+ const markerTextStartX = wordLayout?.marker?.textStartX;
85640
+ const textStartPx = typeof markerTextStartX === "number" && Number.isFinite(markerTextStartX) ? markerTextStartX : typeof rawTextStartPx === "number" && Number.isFinite(rawTextStartPx) ? rawTextStartPx : void 0;
85387
85641
  if (typeof textStartPx === "number" && textStartPx > indentLeft) {
85388
85642
  initialAvailableWidth = Math.max(1, maxWidth - textStartPx - indentRight);
85389
85643
  } else {
@@ -85518,12 +85772,40 @@ async function measureParagraphBlock(block, maxWidth) {
85518
85772
  runsToProcess.push(run2);
85519
85773
  }
85520
85774
  }
85775
+ const trimTrailingWrapSpaces = (lineToTrim) => {
85776
+ const lastRun = runsToProcess[lineToTrim.toRun];
85777
+ if (!lastRun || !("text" in lastRun) || typeof lastRun.text !== "string") return;
85778
+ const sliceStart = lineToTrim.toRun === lineToTrim.fromRun ? lineToTrim.fromChar : 0;
85779
+ const sliceEnd = lineToTrim.toChar;
85780
+ if (sliceEnd <= sliceStart) return;
85781
+ const sliceText = lastRun.text.slice(sliceStart, sliceEnd);
85782
+ let trimCount = 0;
85783
+ for (let i = sliceText.length - 1; i >= 0 && sliceText[i] === " "; i -= 1) {
85784
+ trimCount += 1;
85785
+ }
85786
+ if (trimCount === 0) return;
85787
+ if (lineToTrim.fromRun === lineToTrim.toRun && sliceText.trim().length === 0) {
85788
+ return;
85789
+ }
85790
+ const keptText = sliceText.slice(0, Math.max(0, sliceText.length - trimCount));
85791
+ const { font } = buildFontString(
85792
+ lastRun
85793
+ );
85794
+ const fullWidth = measureRunWidth(sliceText, font, ctx2, lastRun);
85795
+ const keptWidth = keptText.length > 0 ? measureRunWidth(keptText, font, ctx2, lastRun) : 0;
85796
+ const delta = Math.max(0, fullWidth - keptWidth);
85797
+ lineToTrim.width = roundValue(Math.max(0, lineToTrim.width - delta));
85798
+ lineToTrim.spaceCount = Math.max(0, lineToTrim.spaceCount - trimCount);
85799
+ if (lineToTrim.naturalWidth != null && typeof lineToTrim.naturalWidth === "number") {
85800
+ lineToTrim.naturalWidth = roundValue(Math.max(0, lineToTrim.naturalWidth - delta));
85801
+ }
85802
+ };
85521
85803
  for (let runIndex = 0; runIndex < runsToProcess.length; runIndex++) {
85522
85804
  const run2 = runsToProcess[runIndex];
85523
85805
  if (run2.kind === "break") {
85524
85806
  if (currentLine) {
85525
85807
  const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
85526
- const { spaceCount: _sc, ...lineBase } = currentLine;
85808
+ const lineBase = currentLine;
85527
85809
  const completedLine = { ...lineBase, ...metrics };
85528
85810
  addBarTabsToLine(completedLine);
85529
85811
  lines.push(completedLine);
@@ -85568,7 +85850,7 @@ async function measureParagraphBlock(block, maxWidth) {
85568
85850
  toRun: runIndex,
85569
85851
  toChar: 0,
85570
85852
  width: 0,
85571
- maxWidth: getEffectiveWidth(initialAvailableWidth),
85853
+ maxWidth: getEffectiveWidth(lines.length === 0 ? initialAvailableWidth : bodyContentWidth),
85572
85854
  segments: [],
85573
85855
  ...metrics
85574
85856
  };
@@ -85603,7 +85885,7 @@ async function measureParagraphBlock(block, maxWidth) {
85603
85885
  width: 0,
85604
85886
  maxFontSize: 12,
85605
85887
  // Default font size for tabs
85606
- maxWidth: getEffectiveWidth(initialAvailableWidth),
85888
+ maxWidth: getEffectiveWidth(lines.length === 0 ? initialAvailableWidth : bodyContentWidth),
85607
85889
  segments: [],
85608
85890
  spaceCount: 0
85609
85891
  };
@@ -85653,7 +85935,7 @@ async function measureParagraphBlock(block, maxWidth) {
85653
85935
  width: imageWidth,
85654
85936
  maxFontSize: imageHeight,
85655
85937
  // Use image height for line height calculation
85656
- maxWidth: getEffectiveWidth(initialAvailableWidth),
85938
+ maxWidth: getEffectiveWidth(lines.length === 0 ? initialAvailableWidth : bodyContentWidth),
85657
85939
  spaceCount: 0,
85658
85940
  segments: [
85659
85941
  {
@@ -85669,8 +85951,9 @@ async function measureParagraphBlock(block, maxWidth) {
85669
85951
  }
85670
85952
  const appliedTabAlign = lastAppliedTabAlign;
85671
85953
  if (currentLine.width + imageWidth > currentLine.maxWidth && currentLine.width > 0) {
85954
+ trimTrailingWrapSpaces(currentLine);
85672
85955
  const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
85673
- const { spaceCount: _sc, ...lineBase } = currentLine;
85956
+ const lineBase = currentLine;
85674
85957
  const completedLine = {
85675
85958
  ...lineBase,
85676
85959
  ...metrics
@@ -85743,7 +86026,7 @@ async function measureParagraphBlock(block, maxWidth) {
85743
86026
  // Field annotations are atomic units
85744
86027
  width: annotationWidth,
85745
86028
  maxFontSize: annotationHeight,
85746
- maxWidth: getEffectiveWidth(initialAvailableWidth),
86029
+ maxWidth: getEffectiveWidth(lines.length === 0 ? initialAvailableWidth : bodyContentWidth),
85747
86030
  spaceCount: 0,
85748
86031
  segments: [
85749
86032
  {
@@ -85758,8 +86041,9 @@ async function measureParagraphBlock(block, maxWidth) {
85758
86041
  continue;
85759
86042
  }
85760
86043
  if (currentLine.width + annotationWidth > currentLine.maxWidth && currentLine.width > 0) {
86044
+ trimTrailingWrapSpaces(currentLine);
85761
86045
  const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
85762
- const { spaceCount: _sc, ...lineBase } = currentLine;
86046
+ const lineBase = currentLine;
85763
86047
  const completedLine = {
85764
86048
  ...lineBase,
85765
86049
  ...metrics
@@ -85832,15 +86116,16 @@ async function measureParagraphBlock(block, maxWidth) {
85832
86116
  width: spacesWidth,
85833
86117
  maxFontSize: run2.fontSize,
85834
86118
  maxFontInfo: getFontInfoFromRun(run2),
85835
- maxWidth: getEffectiveWidth(initialAvailableWidth),
86119
+ maxWidth: getEffectiveWidth(lines.length === 0 ? initialAvailableWidth : bodyContentWidth),
85836
86120
  segments: [{ runIndex, fromChar: spacesStartChar, toChar: spacesEndChar, width: spacesWidth }],
85837
86121
  spaceCount: spacesLength
85838
86122
  };
85839
86123
  } else {
85840
86124
  const boundarySpacing = currentLine.width > 0 ? run2.letterSpacing ?? 0 : 0;
85841
86125
  if (currentLine.width + boundarySpacing + spacesWidth > currentLine.maxWidth - WIDTH_FUDGE_PX2 && currentLine.width > 0) {
86126
+ trimTrailingWrapSpaces(currentLine);
85842
86127
  const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
85843
- const { spaceCount: _sc, ...lineBase } = currentLine;
86128
+ const lineBase = currentLine;
85844
86129
  const completedLine = {
85845
86130
  ...lineBase,
85846
86131
  ...metrics
@@ -85876,6 +86161,13 @@ async function measureParagraphBlock(block, maxWidth) {
85876
86161
  continue;
85877
86162
  }
85878
86163
  const words = segment.split(" ");
86164
+ let lastNonEmptyWordIndex = -1;
86165
+ for (let i = words.length - 1; i >= 0; i -= 1) {
86166
+ if (words[i] !== "") {
86167
+ lastNonEmptyWordIndex = i;
86168
+ break;
86169
+ }
86170
+ }
85879
86171
  let segmentStartX;
85880
86172
  if (currentLine && pendingTabAlignment) {
85881
86173
  segmentStartX = alignSegmentAtTab(segment, font, run2);
@@ -85898,15 +86190,16 @@ async function measureParagraphBlock(block, maxWidth) {
85898
86190
  width: singleSpaceWidth,
85899
86191
  maxFontSize: run2.fontSize,
85900
86192
  maxFontInfo: getFontInfoFromRun(run2),
85901
- maxWidth: getEffectiveWidth(initialAvailableWidth),
86193
+ maxWidth: getEffectiveWidth(lines.length === 0 ? initialAvailableWidth : bodyContentWidth),
85902
86194
  segments: [{ runIndex, fromChar: spaceStartChar, toChar: spaceEndChar, width: singleSpaceWidth }],
85903
86195
  spaceCount: 1
85904
86196
  };
85905
86197
  } else {
85906
86198
  const boundarySpacing2 = currentLine.width > 0 ? run2.letterSpacing ?? 0 : 0;
85907
86199
  if (currentLine.width + boundarySpacing2 + singleSpaceWidth > currentLine.maxWidth - WIDTH_FUDGE_PX2 && currentLine.width > 0) {
86200
+ trimTrailingWrapSpaces(currentLine);
85908
86201
  const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
85909
- const { spaceCount: _sc, ...lineBase } = currentLine;
86202
+ const lineBase = currentLine;
85910
86203
  const completedLine = { ...lineBase, ...metrics };
85911
86204
  addBarTabsToLine(completedLine);
85912
86205
  lines.push(completedLine);
@@ -85938,19 +86231,19 @@ async function measureParagraphBlock(block, maxWidth) {
85938
86231
  charPosInRun = spaceEndChar;
85939
86232
  continue;
85940
86233
  }
85941
- const isLastWordInSegment = wordIndex === words.length - 1;
85942
- const isLastWord = isLastWordInSegment && isLastSegment;
85943
86234
  const wordOnlyWidth = measureRunWidth(word, font, ctx2, run2);
85944
- const spaceWidth = isLastWord ? 0 : measureRunWidth(" ", font, ctx2, run2);
85945
- const wordCommitWidth = isLastWord ? wordOnlyWidth : wordOnlyWidth + spaceWidth;
86235
+ const shouldIncludeDelimiterSpace = wordIndex < lastNonEmptyWordIndex;
86236
+ const spaceWidth = shouldIncludeDelimiterSpace ? measureRunWidth(" ", font, ctx2, run2) : 0;
86237
+ const wordCommitWidth = wordOnlyWidth + spaceWidth;
85946
86238
  const wordStartChar = charPosInRun;
85947
86239
  const wordEndNoSpace = charPosInRun + word.length;
85948
- const wordEndWithSpace = charPosInRun + (isLastWord ? word.length : word.length + 1);
86240
+ const wordEndWithSpace = wordEndNoSpace + (shouldIncludeDelimiterSpace ? 1 : 0);
85949
86241
  const effectiveMaxWidth = currentLine ? currentLine.maxWidth : getEffectiveWidth(lines.length === 0 ? initialAvailableWidth : contentWidth);
85950
86242
  if (wordOnlyWidth > effectiveMaxWidth && word.length > 1) {
85951
86243
  if (currentLine && currentLine.width > 0 && currentLine.segments && currentLine.segments.length > 0) {
86244
+ trimTrailingWrapSpaces(currentLine);
85952
86245
  const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
85953
- const { spaceCount: _sc, ...lineBase } = currentLine;
86246
+ const lineBase = currentLine;
85954
86247
  const completedLine = { ...lineBase, ...metrics };
85955
86248
  addBarTabsToLine(completedLine);
85956
86249
  lines.push(completedLine);
@@ -85984,7 +86277,7 @@ async function measureParagraphBlock(block, maxWidth) {
85984
86277
  });
85985
86278
  if (isLastChunk) {
85986
86279
  const ls = run2.letterSpacing ?? 0;
85987
- if (!isLastWord && currentLine.width + spaceWidth <= currentLine.maxWidth - WIDTH_FUDGE_PX2) {
86280
+ if (shouldIncludeDelimiterSpace && currentLine.width + spaceWidth <= currentLine.maxWidth - WIDTH_FUDGE_PX2) {
85988
86281
  currentLine.toChar = wordEndWithSpace;
85989
86282
  currentLine.width = roundValue(currentLine.width + spaceWidth + ls);
85990
86283
  charPosInRun = wordEndWithSpace;
@@ -85993,8 +86286,9 @@ async function measureParagraphBlock(block, maxWidth) {
85993
86286
  charPosInRun = wordEndWithSpace;
85994
86287
  }
85995
86288
  } else {
86289
+ trimTrailingWrapSpaces(currentLine);
85996
86290
  const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
85997
- const { spaceCount: _sc, ...lineBase } = currentLine;
86291
+ const lineBase = currentLine;
85998
86292
  const completedLine = { ...lineBase, ...metrics };
85999
86293
  addBarTabsToLine(completedLine);
86000
86294
  lines.push(completedLine);
@@ -86016,7 +86310,7 @@ async function measureParagraphBlock(block, maxWidth) {
86016
86310
  spaceCount: 0
86017
86311
  };
86018
86312
  const ls = run2.letterSpacing ?? 0;
86019
- if (!isLastWord && currentLine.width + spaceWidth <= currentLine.maxWidth - WIDTH_FUDGE_PX2) {
86313
+ if (shouldIncludeDelimiterSpace && currentLine.width + spaceWidth <= currentLine.maxWidth - WIDTH_FUDGE_PX2) {
86020
86314
  currentLine.toChar = wordEndWithSpace;
86021
86315
  currentLine.width = roundValue(currentLine.width + spaceWidth + ls);
86022
86316
  charPosInRun = wordEndWithSpace;
@@ -86053,12 +86347,12 @@ async function measureParagraphBlock(block, maxWidth) {
86053
86347
  width: wordOnlyWidth,
86054
86348
  maxFontSize: run2.fontSize,
86055
86349
  maxFontInfo: getFontInfoFromRun(run2),
86056
- maxWidth: getEffectiveWidth(initialAvailableWidth),
86350
+ maxWidth: getEffectiveWidth(lines.length === 0 ? initialAvailableWidth : bodyContentWidth),
86057
86351
  segments: [{ runIndex, fromChar: wordStartChar, toChar: wordEndNoSpace, width: wordOnlyWidth }],
86058
86352
  spaceCount: 0
86059
86353
  };
86060
86354
  const ls = run2.letterSpacing ?? 0;
86061
- if (!isLastWord && currentLine.width + spaceWidth <= currentLine.maxWidth - WIDTH_FUDGE_PX2) {
86355
+ if (shouldIncludeDelimiterSpace && currentLine.width + spaceWidth <= currentLine.maxWidth - WIDTH_FUDGE_PX2) {
86062
86356
  currentLine.toChar = wordEndWithSpace;
86063
86357
  currentLine.width = roundValue(currentLine.width + spaceWidth + ls);
86064
86358
  charPosInRun = wordEndWithSpace;
@@ -86072,15 +86366,16 @@ async function measureParagraphBlock(block, maxWidth) {
86072
86366
  const boundarySpacing = currentLine.width > 0 ? run2.letterSpacing ?? 0 : 0;
86073
86367
  const justifyAlignment = block.attrs?.alignment === "justify";
86074
86368
  const totalWidthWithWord = currentLine.width + boundarySpacing + wordCommitWidth + // Safe cast: only TextRuns produce word segments from split(), other run types are handled earlier
86075
- (isLastWord ? 0 : run2.letterSpacing ?? 0);
86369
+ (shouldIncludeDelimiterSpace ? run2.letterSpacing ?? 0 : 0);
86076
86370
  const availableWidth = currentLine.maxWidth - WIDTH_FUDGE_PX2;
86077
86371
  let shouldBreak = currentLine.width + boundarySpacing + wordOnlyWidth > availableWidth && currentLine.width > 0 && !isTocEntry;
86078
86372
  let compressedWidth = null;
86079
86373
  if (shouldBreak && justifyAlignment) {
86080
- const isParagraphLastWord = isLastWord && runIndex === runsToProcess.length - 1;
86374
+ const isLastNonEmptyWordInSegment = wordIndex === lastNonEmptyWordIndex;
86375
+ const isParagraphLastWord = isLastSegment && isLastNonEmptyWordInSegment && runIndex === runsToProcess.length - 1;
86081
86376
  if (!isParagraphLastWord) {
86082
86377
  const existingSpaces = currentLine.spaceCount ?? 0;
86083
- const candidateSpaces = existingSpaces + (isLastWord ? 0 : 1);
86378
+ const candidateSpaces = existingSpaces + (shouldIncludeDelimiterSpace ? 1 : 0);
86084
86379
  if (candidateSpaces > 0) {
86085
86380
  const overflow = totalWidthWithWord - availableWidth;
86086
86381
  if (overflow > 0) {
@@ -86096,8 +86391,9 @@ async function measureParagraphBlock(block, maxWidth) {
86096
86391
  }
86097
86392
  }
86098
86393
  if (shouldBreak) {
86394
+ trimTrailingWrapSpaces(currentLine);
86099
86395
  const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
86100
- const { spaceCount: _sc, ...lineBase } = currentLine;
86396
+ const lineBase = currentLine;
86101
86397
  const completedLine = {
86102
86398
  ...lineBase,
86103
86399
  ...metrics
@@ -86118,7 +86414,7 @@ async function measureParagraphBlock(block, maxWidth) {
86118
86414
  segments: [{ runIndex, fromChar: wordStartChar, toChar: wordEndNoSpace, width: wordOnlyWidth }],
86119
86415
  spaceCount: 0
86120
86416
  };
86121
- if (!isLastWord && currentLine.width + spaceWidth <= currentLine.maxWidth - WIDTH_FUDGE_PX2) {
86417
+ if (shouldIncludeDelimiterSpace && currentLine.width + spaceWidth <= currentLine.maxWidth - WIDTH_FUDGE_PX2) {
86122
86418
  currentLine.toChar = wordEndWithSpace;
86123
86419
  currentLine.width = roundValue(currentLine.width + spaceWidth + (run2.letterSpacing ?? 0));
86124
86420
  charPosInRun = wordEndWithSpace;
@@ -86128,7 +86424,7 @@ async function measureParagraphBlock(block, maxWidth) {
86128
86424
  }
86129
86425
  } else {
86130
86426
  currentLine.toRun = runIndex;
86131
- if (!isLastWord && currentLine.width + boundarySpacing + wordOnlyWidth + spaceWidth > currentLine.maxWidth - WIDTH_FUDGE_PX2) {
86427
+ if (shouldIncludeDelimiterSpace && currentLine.width + boundarySpacing + wordOnlyWidth + spaceWidth > currentLine.maxWidth - WIDTH_FUDGE_PX2) {
86132
86428
  currentLine.toChar = wordEndNoSpace;
86133
86429
  currentLine.width = roundValue(currentLine.width + boundarySpacing + wordOnlyWidth);
86134
86430
  currentLine.maxFontInfo = updateMaxFontInfo(currentLine.maxFontSize, currentLine.maxFontInfo, run2);
@@ -86142,8 +86438,9 @@ async function measureParagraphBlock(block, maxWidth) {
86142
86438
  wordOnlyWidth,
86143
86439
  useExplicitXHere ? segmentStartX : void 0
86144
86440
  );
86441
+ trimTrailingWrapSpaces(currentLine);
86145
86442
  const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
86146
- const { spaceCount: _sc, ...lineBase } = currentLine;
86443
+ const lineBase = currentLine;
86147
86444
  const completedLine = { ...lineBase, ...metrics };
86148
86445
  addBarTabsToLine(completedLine);
86149
86446
  lines.push(completedLine);
@@ -86153,20 +86450,23 @@ async function measureParagraphBlock(block, maxWidth) {
86153
86450
  charPosInRun = wordEndNoSpace + 1;
86154
86451
  continue;
86155
86452
  }
86156
- const newToChar = isLastWord ? wordEndNoSpace : wordEndWithSpace;
86453
+ const newToChar = shouldIncludeDelimiterSpace ? wordEndWithSpace : wordEndNoSpace;
86157
86454
  currentLine.toChar = newToChar;
86158
86455
  const useExplicitX = wordIndex === 0 && segmentStartX !== void 0;
86159
86456
  const explicitX = useExplicitX ? segmentStartX : void 0;
86160
- const targetWidth = compressedWidth != null ? compressedWidth : currentLine.width + boundarySpacing + wordCommitWidth + (isLastWord ? 0 : run2.letterSpacing ?? 0);
86457
+ const targetWidth = compressedWidth != null ? compressedWidth : currentLine.width + boundarySpacing + wordCommitWidth + (shouldIncludeDelimiterSpace ? run2.letterSpacing ?? 0 : 0);
86458
+ if (compressedWidth != null) {
86459
+ currentLine.naturalWidth = roundValue(totalWidthWithWord);
86460
+ }
86161
86461
  currentLine.width = roundValue(targetWidth);
86162
86462
  currentLine.maxFontInfo = updateMaxFontInfo(currentLine.maxFontSize, currentLine.maxFontInfo, run2);
86163
86463
  currentLine.maxFontSize = Math.max(currentLine.maxFontSize, run2.fontSize);
86164
86464
  appendSegment(currentLine.segments, runIndex, wordStartChar, newToChar, wordCommitWidth, explicitX);
86165
- if (!isLastWord) {
86465
+ if (shouldIncludeDelimiterSpace) {
86166
86466
  currentLine.spaceCount += 1;
86167
86467
  }
86168
86468
  }
86169
- charPosInRun = isLastWord ? wordEndNoSpace : wordEndWithSpace;
86469
+ charPosInRun = shouldIncludeDelimiterSpace ? wordEndWithSpace : wordEndNoSpace;
86170
86470
  }
86171
86471
  if (lastAppliedTabAlign && currentLine) {
86172
86472
  const appliedTab = lastAppliedTabAlign;
@@ -86186,7 +86486,7 @@ async function measureParagraphBlock(block, maxWidth) {
86186
86486
  width: 0,
86187
86487
  maxFontSize: run2.fontSize,
86188
86488
  maxFontInfo: getFontInfoFromRun(run2),
86189
- maxWidth: getEffectiveWidth(initialAvailableWidth),
86489
+ maxWidth: getEffectiveWidth(lines.length === 0 ? initialAvailableWidth : bodyContentWidth),
86190
86490
  segments: [],
86191
86491
  spaceCount: 0
86192
86492
  };
@@ -86234,7 +86534,7 @@ async function measureParagraphBlock(block, maxWidth) {
86234
86534
  }
86235
86535
  if (currentLine) {
86236
86536
  const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
86237
- const { spaceCount: _sc, ...lineBase } = currentLine;
86537
+ const lineBase = currentLine;
86238
86538
  const finalLine = {
86239
86539
  ...lineBase,
86240
86540
  ...metrics