@stll/folio-core 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +2 -2
  2. package/dist/ai-edits/headless.d.ts +2 -1
  3. package/dist/ai-edits/headless.js +9 -7
  4. package/dist/compat/eigenpal.d.ts +29 -0
  5. package/dist/compat/eigenpal.js +24 -0
  6. package/dist/controller/layoutPipeline.d.ts +1 -1
  7. package/dist/controller/layoutPipeline.js +22 -0
  8. package/dist/controller/layoutSession.d.ts +1 -1
  9. package/dist/docx/encryption/agileDecryption.d.ts +6 -0
  10. package/dist/docx/encryption/agileDecryption.js +208 -0
  11. package/dist/docx/encryption/compoundFile.d.ts +20 -0
  12. package/dist/docx/encryption/compoundFile.js +249 -0
  13. package/dist/docx/encryption/containerFormat.d.ts +15 -0
  14. package/dist/docx/encryption/containerFormat.js +40 -0
  15. package/dist/docx/encryption/cryptoBytes.d.ts +11 -0
  16. package/dist/docx/encryption/cryptoBytes.js +55 -0
  17. package/dist/docx/encryption/encryptionInfo.d.ts +32 -0
  18. package/dist/docx/encryption/encryptionInfo.js +128 -0
  19. package/dist/docx/encryption/errors.d.ts +18 -0
  20. package/dist/docx/encryption/errors.js +13 -0
  21. package/dist/docx/encryption/index.d.ts +4 -0
  22. package/dist/docx/encryption/index.js +4 -0
  23. package/dist/docx/encryption/openEncryptedDocx.d.ts +28 -0
  24. package/dist/docx/encryption/openEncryptedDocx.js +65 -0
  25. package/dist/docx/index.d.ts +3 -1
  26. package/dist/docx/index.js +3 -1
  27. package/dist/docx/parser.d.ts +4 -5
  28. package/dist/docx/parser.js +8 -2
  29. package/dist/docx/unzip.d.ts +7 -5
  30. package/dist/docx/unzip.js +8 -2
  31. package/dist/index.d.ts +6 -5
  32. package/dist/index.js +2 -1
  33. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -1
  34. package/dist/layout-bridge/convert/headerFooterLayout.js +16 -6
  35. package/dist/layout-bridge/convert/templatePreviewFlow.d.ts +1 -1
  36. package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
  37. package/dist/layout-engine/index.js +27 -1
  38. package/dist/layout-engine/measure/measureBlocks.js +15 -1
  39. package/dist/layout-engine/measure/measureParagraph.js +37 -6
  40. package/dist/layout-engine/types.d.ts +3 -0
  41. package/dist/layout-painter/renderPage.js +2 -1
  42. package/dist/layout-painter/renderParagraph.js +30 -5
  43. package/dist/layout-painter/renderTable.js +17 -0
  44. package/dist/managers/DocumentLoaderManager.d.ts +3 -1
  45. package/dist/managers/DocumentLoaderManager.js +3 -2
  46. package/dist/paged-layout/headerFooterMargins.d.ts +3 -1
  47. package/dist/paged-layout/headerFooterMargins.js +5 -3
  48. package/dist/prosemirror/attrs/index.js +1 -0
  49. package/dist/prosemirror/conversion/fromProseDoc.js +7 -2
  50. package/dist/prosemirror/conversion/toProseDoc.js +14 -7
  51. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
  52. package/dist/prosemirror/extensions/nodes/TableExtension.js +2 -0
  53. package/dist/prosemirror/schema/nodes.d.ts +4 -2
  54. package/dist/server.d.ts +2 -2
  55. package/dist/utils/fontResolver.d.ts +3 -1
  56. package/dist/utils/fontResolver.js +28 -4
  57. package/package.json +9 -1
@@ -16,13 +16,13 @@ function hasAuthoredVisualContent(block) {
16
16
  if (attrs.spacingExplicit?.before || attrs.spacingExplicit?.after) return true;
17
17
  return false;
18
18
  }
19
- function normalizeHeaderFooterMeasureBlocks(blocks) {
20
- return normalizeFlowBlockArray(blocks);
19
+ function normalizeHeaderFooterMeasureBlocks(blocks, section = "header") {
20
+ return normalizeFlowBlockArray(blocks, { suppressTrailingEmptyAfterTable: section === "header" });
21
21
  }
22
- function normalizeFlowBlockArray(blocks) {
22
+ function normalizeFlowBlockArray(blocks, normalization) {
23
23
  const trailingEmptyAfterTable = /* @__PURE__ */ new Set();
24
24
  const lastIndex = blocks.length - 1;
25
- if (lastIndex > 0) {
25
+ if (normalization.suppressTrailingEmptyAfterTable && lastIndex > 0) {
26
26
  const cur = blocks[lastIndex];
27
27
  if (blocks[lastIndex - 1]?.kind === "table" && cur?.kind === "paragraph" && cur.runs.length === 0 && !hasAuthoredVisualContent(cur)) trailingEmptyAfterTable.add(lastIndex);
28
28
  }
@@ -70,12 +70,21 @@ function normalizeFlowBlockArray(blocks) {
70
70
  };
71
71
  });
72
72
  }
73
+ function isCanonicalTrailingEmptyParagraphAfterTable(blocks, index) {
74
+ const cur = blocks[index];
75
+ const prev = blocks[index - 1];
76
+ return index === blocks.length - 1 && prev?.kind === "table" && cur?.kind === "paragraph" && isVisuallyEmptyParagraph(cur) && !hasAuthoredVisualContent(cur);
77
+ }
78
+ function isVisuallyEmptyParagraph(block) {
79
+ if (block.kind !== "paragraph") return false;
80
+ return block.runs.every((run) => run.kind === "text" && run.text.trim().length === 0);
81
+ }
73
82
  function normalizeTableBlock(block) {
74
83
  const blockState = { changed: false };
75
84
  const rows = block.rows.map((row) => {
76
85
  const rowState = { changed: false };
77
86
  const cells = row.cells.map((cell) => {
78
- const normalizedBlocks = normalizeFlowBlockArray(cell.blocks);
87
+ const normalizedBlocks = normalizeFlowBlockArray(cell.blocks, { suppressTrailingEmptyAfterTable: true });
79
88
  if (!normalizedBlocks.some((normalizedBlock, idx) => normalizedBlock !== cell.blocks[idx])) return cell;
80
89
  rowState.changed = true;
81
90
  return {
@@ -232,6 +241,7 @@ function calculateHeaderFooterMarginPushBounds(blocks, measures, flowHeight, met
232
241
  const block = blocks[i];
233
242
  const measure = measures[i];
234
243
  if (!block || !measure) continue;
244
+ if (isCanonicalTrailingEmptyParagraphAfterTable(blocks, i)) continue;
235
245
  if (block.kind === "paragraph" && measure.kind === "paragraph") {
236
246
  const paragraphBottomY = cursorY + measure.totalHeight;
237
247
  top = Math.min(top, cursorY);
@@ -312,7 +322,7 @@ function convertHeaderFooterPmDocToContent(pmDoc, contentWidth, metrics, options
312
322
  }
313
323
  function finalizeHeaderFooterContent(blocks, contentWidth, metrics, options) {
314
324
  if (blocks.length === 0) return;
315
- const blocksForMeasure = normalizeHeaderFooterMeasureBlocks(blocks);
325
+ const blocksForMeasure = normalizeHeaderFooterMeasureBlocks(blocks, metrics.section);
316
326
  const measures = options.measureBlocks(blocksForMeasure, contentWidth);
317
327
  let flowHeight = 0;
318
328
  for (let i = 0; i < measures.length; i++) {
@@ -1,5 +1,5 @@
1
- import { FlowBlock } from "../../layout-engine/types.js";
2
1
  import { TemplatePreviewValue } from "../../prosemirror/plugins/templatePreviewValues.js";
2
+ import { FlowBlock } from "../../layout-engine/types.js";
3
3
 
4
4
  //#region src/layout-bridge/convert/templatePreviewFlow.d.ts
5
5
  /** One marker→value substitution, in PM doc positions. */
@@ -732,6 +732,7 @@ function convertParagraphAttrs(pmAttrs, theme, listCounters, listAbstractCounter
732
732
  if (pmAttrs.pageBreakBefore) attrs.pageBreakBefore = true;
733
733
  if (pmAttrs.keepNext) attrs.keepNext = true;
734
734
  if (pmAttrs.keepLines) attrs.keepLines = true;
735
+ if (pmAttrs.widowControl === false) attrs.widowControl = false;
735
736
  if (pmAttrs.contextualSpacing) attrs.contextualSpacing = true;
736
737
  if (pmAttrs.runInWithNext) attrs.runInWithNext = true;
737
738
  if (directionIsRtl(pmAttrs.direction)) attrs.bidi = true;
@@ -941,6 +942,7 @@ function convertTableRow(node, startPos, options, tableCellMargins) {
941
942
  if (attrs.height) row.height = twipsToPixels(attrs.height);
942
943
  if (attrs.heightRule) row.heightRule = attrs.heightRule;
943
944
  if (attrs.isHeader) row.isHeader = attrs.isHeader;
945
+ if (attrs.hidden) row.hidden = attrs.hidden;
944
946
  return row;
945
947
  }
946
948
  /**
@@ -72,6 +72,9 @@ function getSpacingAfter(block) {
72
72
  if (isEmptyParagraph(block) && !block.attrs?.spacingExplicit?.after) return 0;
73
73
  return value;
74
74
  }
75
+ function hasWidowControl(block) {
76
+ return block.attrs?.widowControl !== false;
77
+ }
75
78
  /**
76
79
  * Apply contextual spacing suppression (OOXML §17.3.1.9).
77
80
  *
@@ -309,6 +312,28 @@ function layoutParagraph(block, measure, paginator, contentWidth, footnoteHeight
309
312
  fittingLines++;
310
313
  } else break;
311
314
  }
315
+ let forceBreakAfterFragment = false;
316
+ if (hasWidowControl(block)) {
317
+ const remainingAfter = lines.length - (currentLineIndex + fittingLines);
318
+ if (fittingLines > 1 && remainingAfter === 1) {
319
+ if (currentLineIndex === 0 && fittingLines === 2 && state.cursorY !== state.topMargin) {
320
+ paginator.forceColumnBreak();
321
+ continue;
322
+ }
323
+ fittingLines -= 1;
324
+ forceBreakAfterFragment = true;
325
+ linesHeight = 0;
326
+ linesFnHeight = 0;
327
+ linesFnIds.length = 0;
328
+ for (let j = currentLineIndex; j < currentLineIndex + fittingLines; j++) {
329
+ const line = lines[j];
330
+ linesHeight += measuredLineAdvance(line);
331
+ const lineRefs = getLineFootnoteRefs(block, line.fromRun, line.toRun, footnoteHeightById);
332
+ linesFnHeight += lineRefs.height;
333
+ for (const id of lineRefs.ids) linesFnIds.push(id);
334
+ }
335
+ }
336
+ }
312
337
  const isFirstFragment = currentLineIndex === 0;
313
338
  const isLastFragment = currentLineIndex + fittingLines >= lines.length;
314
339
  const effectiveSpaceBefore = isFirstFragment ? spaceBefore : 0;
@@ -336,7 +361,8 @@ function layoutParagraph(block, measure, paginator, contentWidth, footnoteHeight
336
361
  fragment.y = paginator.addFragment(fragment, linesHeight, effectiveSpaceBefore, effectiveSpaceAfter).y;
337
362
  if (linesFnHeight > 0) paginator.addFootnoteHeight(linesFnHeight, linesFnIds);
338
363
  currentLineIndex += fittingLines;
339
- if (currentLineIndex < lines.length) paginator.ensureFits(measuredLineAdvance(lines[currentLineIndex]));
364
+ if (currentLineIndex < lines.length) if (forceBreakAfterFragment) paginator.forceColumnBreak();
365
+ else paginator.ensureFits(measuredLineAdvance(lines[currentLineIndex]));
340
366
  }
341
367
  }
342
368
  /**
@@ -71,6 +71,11 @@ function measureTableCellBlockVisualHeight(block, blockMeasure) {
71
71
  }
72
72
  let maxImageHeight = 0;
73
73
  for (const run of inlineImageRuns) maxImageHeight = Math.max(maxImageHeight, run.height);
74
+ if (inlineImageRuns.length === 1) {
75
+ const spacingBefore = paragraphBlock.attrs?.spacing?.before ?? 0;
76
+ const spacingAfter = paragraphBlock.attrs?.spacing?.after ?? 0;
77
+ return spacingBefore + maxImageHeight + spacingAfter;
78
+ }
74
79
  const spacingBefore = paragraphBlock.attrs?.spacing?.before ?? 0;
75
80
  const spacingAfter = paragraphBlock.attrs?.spacing?.after ?? 0;
76
81
  return spacingBefore + maxImageHeight + spacingAfter;
@@ -143,6 +148,16 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
143
148
  });
144
149
  for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
145
150
  const row = rows[rowIdx];
151
+ const sourceRow = tableBlock.rows[rowIdx];
152
+ if (sourceRow?.hidden) {
153
+ row.height = 0;
154
+ row.cells = sourceRow.cells.map(() => ({
155
+ blocks: [],
156
+ width: 0,
157
+ height: 0
158
+ }));
159
+ continue;
160
+ }
146
161
  const sourceRowCells = tableBlock.rows[rowIdx]?.cells;
147
162
  let maxCellHeightWithBorders = 0;
148
163
  for (let cellIdx = 0; cellIdx < row.cells.length; cellIdx++) {
@@ -160,7 +175,6 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
160
175
  cell.height += padTop + padBottom;
161
176
  maxCellHeightWithBorders = Math.max(maxCellHeightWithBorders, cell.height + getTableCellVerticalBorderHeight(sourceCell));
162
177
  }
163
- const sourceRow = tableBlock.rows[rowIdx];
164
178
  const explicitHeight = sourceRow?.height;
165
179
  const heightRule = sourceRow?.heightRule;
166
180
  if (explicitHeight && heightRule === "exact") row.height = explicitHeight;
@@ -21,6 +21,10 @@ const DEFAULT_FONT_SIZE = 11;
21
21
  const DEFAULT_FONT_FAMILY = "Calibri";
22
22
  const DEFAULT_LINE_HEIGHT_MULTIPLIER = 1;
23
23
  const WIDTH_TOLERANCE = .5;
24
+ const JUSTIFY_SHRINK_TOLERANCE_RATIO = .016;
25
+ const JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO = .025;
26
+ const JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO = .021;
27
+ const ALL_CAPS_RATIO_THRESHOLD = .8;
24
28
  /**
25
29
  * Find the longest prefix of `text` that fits within `maxWidth` pixels.
26
30
  * Returns the number of characters that fit (at least 1 if `forceMin` is true).
@@ -266,6 +270,10 @@ function hasFollowingTabOnLine(runs, tabIndex) {
266
270
  }
267
271
  return false;
268
272
  }
273
+ function canClampTabToRightEdge(alignment, currentLineWidth) {
274
+ if (alignment === "start" || alignment === "default") return currentLineWidth > WIDTH_TOLERANCE;
275
+ return true;
276
+ }
269
277
  /**
270
278
  * Width of the inline content preceding the first `.` in the runs that follow
271
279
  * a tab, used to anchor `decimal` tab stops. Mirrors `getTextAfterTab` +
@@ -299,6 +307,25 @@ function measureDecimalPrefixWidthAfterTab(runs, tabIndex, fieldValues) {
299
307
  function isSpaceOrTab(char) {
300
308
  return char === " " || char === " ";
301
309
  }
310
+ function uppercaseLetterRatio(text) {
311
+ let letters = 0;
312
+ let uppercase = 0;
313
+ for (const char of text) {
314
+ const lower = char.toLocaleLowerCase();
315
+ const upper = char.toLocaleUpperCase();
316
+ if (lower === upper) continue;
317
+ letters++;
318
+ if (char === upper) uppercase++;
319
+ }
320
+ return letters === 0 ? 0 : uppercase / letters;
321
+ }
322
+ function justifyShrinkToleranceRatio(block) {
323
+ const hasTabStops = (block.attrs?.tabs?.length ?? 0) > 0;
324
+ const hasTabRuns = block.runs.some(isTabRun);
325
+ if (hasTabStops || hasTabRuns) return (block.attrs?.indent?.firstLine ?? 0) === 0 ? JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO : JUSTIFY_SHRINK_TOLERANCE_RATIO;
326
+ if (uppercaseLetterRatio(block.runs.map((run) => isTextRun(run) ? run.text ?? "" : "").join("")) > ALL_CAPS_RATIO_THRESHOLD) return JUSTIFY_SHRINK_TOLERANCE_RATIO;
327
+ return JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO;
328
+ }
302
329
  function trimTrailingSpacesAndTabs(text) {
303
330
  let end = text.length;
304
331
  while (end > 0) {
@@ -384,6 +411,8 @@ function measureParagraph(block, maxWidth, options) {
384
411
  const runs = block.runs;
385
412
  const attrs = block.attrs;
386
413
  const spacing = attrs?.spacing;
414
+ const isJustifiedParagraph = attrs?.alignment === "justify";
415
+ const justifyToleranceRatio = justifyShrinkToleranceRatio(block);
387
416
  const floatingZones = options?.floatingZones;
388
417
  const paragraphYOffset = options?.paragraphYOffset ?? 0;
389
418
  const indent = attrs?.indent;
@@ -589,15 +618,16 @@ function measureParagraph(block, maxWidth, options) {
589
618
  const lineX = currentLine.width + currentLine.leftOffset;
590
619
  const isFirstLine = lines.length === 0;
591
620
  const contentX = indentLeft + (isFirstLine ? firstLineOffset + markerInlineWidth : 0) + lineX;
592
- let tabWidth = calculateTabWidth(contentX, {
621
+ const tabResult = calculateTabWidth(contentX, {
593
622
  ...attrs?.tabs !== void 0 ? { explicitStops: attrs.tabs } : {},
594
623
  leftIndent: pixelsToTwips(indentLeft)
595
624
  }, {
596
625
  followingWidth,
597
626
  decimalPrefixWidth
598
- }).width;
627
+ });
628
+ let tabWidth = tabResult.width;
599
629
  const lineRightEdgeX = indentLeft + (isFirstLine ? firstLineOffset + markerInlineWidth : 0) + currentLine.availableWidth + currentLine.leftOffset;
600
- if (!hasFollowingTabOnLine(runs, runIndex) && (tabWidth > 0 || followingWidth > 0) && contentX + tabWidth + followingWidth > lineRightEdgeX + WIDTH_TOLERANCE) tabWidth = Math.max(1, lineRightEdgeX - contentX - followingWidth);
630
+ if (!hasFollowingTabOnLine(runs, runIndex) && canClampTabToRightEdge(tabResult.alignment, currentLine.width) && (tabWidth > 0 || followingWidth > 0) && contentX + tabWidth + followingWidth > lineRightEdgeX + WIDTH_TOLERANCE) tabWidth = Math.max(1, lineRightEdgeX - contentX - followingWidth);
601
631
  if (currentLine.width + tabWidth > currentLine.availableWidth + WIDTH_TOLERANCE) {
602
632
  startNewLine(runIndex, 0);
603
633
  updateMaxFont(style);
@@ -691,7 +721,8 @@ function measureParagraph(block, maxWidth, options) {
691
721
  }
692
722
  const word = text.slice(charIndex, nextBreak);
693
723
  const wordWidth = measureTextWidth(word, style);
694
- if (wordWidth > currentLine.availableWidth + WIDTH_TOLERANCE) {
724
+ const widthTolerance = isJustifiedParagraph ? Math.max(WIDTH_TOLERANCE, currentLine.availableWidth * justifyToleranceRatio) : WIDTH_TOLERANCE;
725
+ if (wordWidth > currentLine.availableWidth + widthTolerance) {
695
726
  let chunkStart = 0;
696
727
  while (chunkStart < word.length) {
697
728
  const spaceLeft = currentLine.availableWidth - currentLine.width + WIDTH_TOLERANCE;
@@ -719,8 +750,8 @@ function measureParagraph(block, maxWidth, options) {
719
750
  continue;
720
751
  }
721
752
  const rawGlueWidth = nextBreak === text.length && word.length > 0 && !isBreakChar(word[word.length - 1]) ? trailingGlueWidths[runIndex] ?? 0 : 0;
722
- const glueWidth = rawGlueWidth > 0 && wordWidth + rawGlueWidth <= getPostWrapAvailableWidth() + WIDTH_TOLERANCE ? rawGlueWidth : 0;
723
- if (currentLine.width > 0 && currentLine.width + wordWidth + glueWidth > currentLine.availableWidth + WIDTH_TOLERANCE) {
753
+ const glueWidth = rawGlueWidth > 0 && wordWidth + rawGlueWidth <= getPostWrapAvailableWidth() + widthTolerance ? rawGlueWidth : 0;
754
+ if (currentLine.width > 0 && currentLine.width + wordWidth + glueWidth > currentLine.availableWidth + widthTolerance) {
724
755
  startNewLine(runIndex, charIndex);
725
756
  updateMaxFont(lineHeightStyle);
726
757
  }
@@ -303,6 +303,7 @@ type ParagraphAttrs = {
303
303
  indent?: ParagraphIndent;
304
304
  keepNext?: boolean;
305
305
  keepLines?: boolean;
306
+ widowControl?: boolean;
306
307
  pageBreakBefore?: boolean;
307
308
  styleId?: string;
308
309
  contextualSpacing?: boolean;
@@ -432,6 +433,7 @@ type TableRow = {
432
433
  height?: number;
433
434
  heightRule?: "auto" | "atLeast" | "exact";
434
435
  isHeader?: boolean;
436
+ hidden?: boolean;
435
437
  };
436
438
  /**
437
439
  * Floating table positioning info (pixel values).
@@ -518,6 +520,7 @@ type SectionBreakBlock = {
518
520
  };
519
521
  type PageHeaderFooterRefs = {
520
522
  titlePg?: boolean;
523
+ evenAndOddHeaders?: boolean;
521
524
  headerDefault?: string;
522
525
  headerFirst?: string;
523
526
  headerEven?: string;
@@ -1162,7 +1162,8 @@ function applySectionHeaderFooterOptions(page, pageOptions, options) {
1162
1162
  if (!refs) return false;
1163
1163
  const isFirstSectionPage = page.sectionPageNumber === 1;
1164
1164
  const useFirst = refs.titlePg === true && isFirstSectionPage;
1165
- const useEven = (page.sectionPageNumber ?? page.number) % 2 === 0;
1165
+ const sectionPageNumber = page.sectionPageNumber ?? page.number;
1166
+ const useEven = refs.evenAndOddHeaders === true && sectionPageNumber % 2 === 0;
1166
1167
  const headerRId = (() => {
1167
1168
  if (useFirst) return refs.headerFirst;
1168
1169
  if (useEven && refs.headerEven) return refs.headerEven;
@@ -330,6 +330,10 @@ function renderTabRun(run, doc, width, leader) {
330
330
  } else span.textContent = "\xA0";
331
331
  return span;
332
332
  }
333
+ function canClampTabToRightEdge(alignment, hasPriorRenderedContent) {
334
+ if (alignment === "start" || alignment === "default") return hasPriorRenderedContent;
335
+ return true;
336
+ }
333
337
  function applyTabUnderline(element, run) {
334
338
  if (!run.underline) return;
335
339
  removeUnderlineTextDecoration(element);
@@ -529,7 +533,9 @@ const EMPTY_SEQ_VALUES = /* @__PURE__ */ new Map();
529
533
  * Returns the cached fallback when no render context is available.
530
534
  */
531
535
  function resolveFieldText(run, context) {
532
- if (!context) return run.fallback ?? "";
536
+ const fallback = run.fallback ?? "";
537
+ if (run.fieldType === "OTHER" && run.pmStart === void 0) return fallback;
538
+ if (!context) return fallback;
533
539
  const fieldContext = {
534
540
  pageNumber: context.pageNumber,
535
541
  totalPages: context.totalPages,
@@ -540,7 +546,7 @@ function resolveFieldText(run, context) {
540
546
  ...context.sectionPages === void 0 ? {} : { sectionPages: context.sectionPages }
541
547
  };
542
548
  return evaluateFieldInstruction(run.instruction || run.fieldType, fieldContext, {
543
- fallback: run.fallback ?? "",
549
+ fallback,
544
550
  ...run.pmStart === void 0 ? {} : { instanceId: run.pmStart },
545
551
  ...run.fldLock ? { locked: true } : {}
546
552
  });
@@ -710,6 +716,17 @@ function splitTextRunsByEastAsia(runs) {
710
716
  * must trigger within this slack.
711
717
  */
712
718
  const RIGHT_EDGE_EPSILON_PX = .5;
719
+ function countShrinkableSpaces(runs) {
720
+ let count = 0;
721
+ for (const run of runs) if (isTextRun(run)) {
722
+ for (const char of run.text ?? "") if (char === " ") count++;
723
+ } else if (isFieldRun(run)) {
724
+ for (const char of run.fallback ?? "") if (char === " ") count++;
725
+ } else if (isMathRun(run)) {
726
+ for (const char of run.plainText ?? "") if (char === " ") count++;
727
+ }
728
+ return count;
729
+ }
713
730
  /**
714
731
  * Build a TextMeasureStyle from a TextRun or FieldRun's relevant fields.
715
732
  */
@@ -890,8 +907,16 @@ function renderLine(block, line, alignment, doc, options) {
890
907
  }
891
908
  if (alignment === "justify" && options) {
892
909
  if (!options.isLastLine || options.paragraphEndsWithLineBreak) {
893
- lineEl.style.textAlign = "justify";
894
- lineEl.style.textAlignLast = "justify";
910
+ const overfullPx = line.width - options.availableWidth;
911
+ const shrinkableSpaces = countShrinkableSpaces(runsForLine);
912
+ if (overfullPx > RIGHT_EDGE_EPSILON_PX && shrinkableSpaces > 0) {
913
+ lineEl.style.textAlign = "left";
914
+ lineEl.style.textAlignLast = "auto";
915
+ lineEl.style.wordSpacing = `${-overfullPx / shrinkableSpaces}px`;
916
+ } else {
917
+ lineEl.style.textAlign = "justify";
918
+ lineEl.style.textAlignLast = "justify";
919
+ }
895
920
  lineEl.style.width = `${options.availableWidth}px`;
896
921
  }
897
922
  }
@@ -967,7 +992,7 @@ function renderLine(block, line, alignment, doc, options) {
967
992
  break;
968
993
  }
969
994
  let tabWidth = tabResult.width;
970
- if (lineRightEdgeX !== void 0 && currentX + tabWidth + followingWidthForCheck > lineRightEdgeX) tabWidth = Math.max(1, lineRightEdgeX - currentX - followingWidthForCheck);
995
+ if (lineRightEdgeX !== void 0 && canClampTabToRightEdge(tabResult.alignment, i > 0) && currentX + tabWidth + followingWidthForCheck > lineRightEdgeX) tabWidth = Math.max(1, lineRightEdgeX - currentX - followingWidthForCheck);
971
996
  const tabEl = renderTabRun(run, doc, tabWidth, tabResult.leader);
972
997
  lineEl.append(tabEl);
973
998
  currentX += tabWidth;
@@ -97,6 +97,10 @@ function renderCellContent(cell, cellMeasure, context, doc) {
97
97
  };
98
98
  const fragEl = renderParagraphFragment(syntheticFragment, paragraphBlock, paragraphMeasure, cellContext, { document: doc });
99
99
  fragEl.style.position = "relative";
100
+ fragEl.style.boxSizing = "border-box";
101
+ fragEl.style.height = `${paragraphMeasure.totalHeight}px`;
102
+ const spaceBefore = paragraphBlock.attrs?.spacing?.before ?? 0;
103
+ if (spaceBefore > 0) fragEl.style.paddingTop = `${spaceBefore}px`;
100
104
  contentEl.append(fragEl);
101
105
  cumulativeY += paragraphMeasure.totalHeight;
102
106
  } else if (block?.kind === "table" && measure?.kind === "table") {
@@ -138,6 +142,10 @@ function renderNestedTable(block, measure, context, doc) {
138
142
  const row = block.rows[rowIndex];
139
143
  const rowMeasure = measure.rows[rowIndex];
140
144
  if (!row || !rowMeasure) continue;
145
+ if (row.hidden) {
146
+ y += rowMeasure.height;
147
+ continue;
148
+ }
141
149
  const rowEl = renderTableRow(row, rowMeasure, rowIndex, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, void 0, block.bidi);
142
150
  tableEl.append(rowEl);
143
151
  y += rowMeasure.height;
@@ -346,6 +354,10 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
346
354
  const hdrRow = block.rows[hdrIdx];
347
355
  const hdrRowMeasure = measure.rows[hdrIdx];
348
356
  if (!hdrRow || !hdrRowMeasure) continue;
357
+ if (hdrRow.hidden) {
358
+ y += hdrRowMeasure.height;
359
+ continue;
360
+ }
349
361
  const rowEl = renderTableRow(hdrRow, hdrRowMeasure, hdrIdx, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, hdrIdx === 0, block.bidi);
350
362
  rowEl.dataset["repeatedHeader"] = "true";
351
363
  tableEl.append(rowEl);
@@ -369,6 +381,10 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
369
381
  const row = block.rows[rowIndex];
370
382
  const rowMeasure = measure.rows[rowIndex];
371
383
  if (!row || !rowMeasure) continue;
384
+ if (row.hidden) {
385
+ y += rowMeasure.height;
386
+ continue;
387
+ }
372
388
  const isFirstRowInFragment = headerRowCount > 0 && fragment.continuesFromPrev ? false : fragment.continuesFromPrev && rowIndex === fragment.fromRow;
373
389
  const rowEl = renderTableRow(row, rowMeasure, rowIndex, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, isFirstRowInFragment, block.bidi);
374
390
  contentParent.append(rowEl);
@@ -376,6 +392,7 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
376
392
  }
377
393
  let handleY = 0;
378
394
  for (let rowIdx = fragment.fromRow; rowIdx < fragment.toRow; rowIdx++) {
395
+ if (block.rows[rowIdx]?.hidden) continue;
379
396
  handleY += measure.rows[rowIdx]?.height ?? 0;
380
397
  if (rowIdx < fragment.toRow - 1) {
381
398
  const rowHandle = doc.createElement("div");
@@ -41,7 +41,9 @@ declare class DocumentLoaderManager {
41
41
  * the result (and any error) when a newer load started while this one was in
42
42
  * flight.
43
43
  */
44
- loadBuffer(buffer: DocxInput): Promise<void>;
44
+ loadBuffer(buffer: DocxInput, options?: {
45
+ password?: string | undefined;
46
+ }): Promise<void>;
45
47
  }
46
48
  //#endregion
47
49
  export { DocumentLoadState, DocumentLoaderCallbacks, DocumentLoaderHistory, DocumentLoaderManager };
@@ -43,14 +43,15 @@ var DocumentLoaderManager = class {
43
43
  * the result (and any error) when a newer load started while this one was in
44
44
  * flight.
45
45
  */
46
- async loadBuffer(buffer) {
46
+ async loadBuffer(buffer, options = {}) {
47
47
  const { history, onError, setDocumentLoadState } = this.callbacks;
48
48
  const generation = ++this.loadGeneration;
49
49
  if (!(history.state !== null)) setDocumentLoadState({ status: "loading" });
50
50
  try {
51
51
  const doc = await parseDocx(buffer, {
52
52
  detectVariables: false,
53
- preloadFonts: false
53
+ preloadFonts: false,
54
+ password: options.password
54
55
  });
55
56
  if (this.loadGeneration !== generation) return;
56
57
  this.loadParsedDocument(doc);
@@ -43,7 +43,8 @@ declare function computeEffectiveHeaderFooterMargins({
43
43
  /** Rendered header/footer content shared by every extender on a page. */
44
44
  type HeaderFooterExtenderContent = Omit<EffectiveHeaderFooterMarginsInput, "margins" | "pageSize" | "warn">;
45
45
  type ExtendSectionBreakMarginsInput = {
46
- content: HeaderFooterExtenderContent; /** Body page size and effective margins — the inheritance seed. */
46
+ content: HeaderFooterExtenderContent;
47
+ sectionContent?: HeaderFooterExtenderContent[] | undefined; /** Body page size and effective margins — the inheritance seed. */
47
48
  bodyPageSize: {
48
49
  w: number;
49
50
  h: number;
@@ -65,6 +66,7 @@ type ExtendSectionBreakMarginsInput = {
65
66
  */
66
67
  declare function extendSectionBreakMargins(sectionBreaks: SectionBreakBlock[], {
67
68
  content,
69
+ sectionContent,
68
70
  bodyPageSize,
69
71
  bodyMargins,
70
72
  warn
@@ -102,14 +102,16 @@ function computeEffectiveHeaderFooterMargins({ margins, headerContent, footerCon
102
102
  * clamped reservation (and vice versa). Materializes `margins` on every
103
103
  * non-inheriting break in place.
104
104
  */
105
- function extendSectionBreakMargins(sectionBreaks, { content, bodyPageSize, bodyMargins, warn }) {
105
+ function extendSectionBreakMargins(sectionBreaks, { content, sectionContent, bodyPageSize, bodyMargins, warn }) {
106
106
  let pageSize = bodyPageSize;
107
107
  let margins = bodyMargins;
108
- for (const sb of sectionBreaks) {
108
+ for (let index = 0; index < sectionBreaks.length; index++) {
109
+ const sb = sectionBreaks[index];
110
+ if (!sb) continue;
109
111
  if (!sb.pageSize && !sb.margins) continue;
110
112
  pageSize = sb.pageSize ?? pageSize;
111
113
  margins = computeHeaderFooterMarginExtender({
112
- ...content,
114
+ ...sectionContent?.[index + 1] ?? content,
113
115
  pageSize,
114
116
  warn
115
117
  })(sb.margins ?? margins);
@@ -236,6 +236,7 @@ const readTableRowAttrs = (node) => {
236
236
  optionalNumber(attrs, "height", "tableRow.attrs.height", issues);
237
237
  optionalOneOf(attrs, "heightRule", "tableRow.attrs.heightRule", issues, TABLE_ROW_HEIGHT_RULE_VALUES);
238
238
  optionalBoolean(attrs, "isHeader", "tableRow.attrs.isHeader", issues);
239
+ optionalBoolean(attrs, "hidden", "tableRow.attrs.hidden", issues);
239
240
  optionalRecord(attrs, "_originalFormatting", "tableRow.attrs._originalFormatting", issues);
240
241
  return attrsResult(attrs, issues);
241
242
  };
@@ -341,6 +341,7 @@ function paragraphAttrsToFormatting(attrs) {
341
341
  if (attrs.styleId !== (orig.styleId ?? void 0)) if (attrs.styleId) result.styleId = attrs.styleId;
342
342
  else delete result.styleId;
343
343
  assignBooleanToggle(result, attrs, orig, "pageBreakBefore");
344
+ assignBooleanToggle(result, attrs, orig, "widowControl");
344
345
  if (attrs.spacingExplicit !== orig.spacingExplicit) if (attrs.spacingExplicit) result.spacingExplicit = attrs.spacingExplicit;
345
346
  else delete result.spacingExplicit;
346
347
  const bidi = directionToBidi(attrs.direction);
@@ -350,7 +351,7 @@ function paragraphAttrsToFormatting(attrs) {
350
351
  }
351
352
  const outlineLevel = Reflect.get(attrs, "outlineLevel");
352
353
  const bidi = directionToBidi(attrs.direction);
353
- if (!(attrs.alignment || shouldSerializeSpaceBefore || shouldSerializeSpaceAfter || beforeAutospacingEdited || afterAutospacingEdited || attrs.lineSpacing || attrs.indentLeft || attrs.indentRight || attrs.indentFirstLine || attrs.numPr || attrs.styleId || attrs.borders || attrs.shading || attrs.tabs || typeof outlineLevel === "number" || attrs.contextualSpacing || attrs.spacingExplicit || bidi != null || attrs.pageBreakBefore != null)) return;
354
+ if (!(attrs.alignment || shouldSerializeSpaceBefore || shouldSerializeSpaceAfter || beforeAutospacingEdited || afterAutospacingEdited || attrs.lineSpacing || attrs.indentLeft || attrs.indentRight || attrs.indentFirstLine || attrs.numPr || attrs.styleId || attrs.borders || attrs.shading || attrs.tabs || typeof outlineLevel === "number" || attrs.contextualSpacing || attrs.spacingExplicit || bidi != null || attrs.pageBreakBefore != null || attrs.widowControl != null)) return;
354
355
  const f = {};
355
356
  if (attrs.alignment) f.alignment = attrs.alignment;
356
357
  if (shouldSerializeSpaceBefore) f.spaceBefore = spaceBefore;
@@ -373,6 +374,7 @@ function paragraphAttrsToFormatting(attrs) {
373
374
  if (attrs.contextualSpacing) f.contextualSpacing = attrs.contextualSpacing;
374
375
  if (bidi != null) f.bidi = bidi;
375
376
  if (attrs.pageBreakBefore != null) f.pageBreakBefore = attrs.pageBreakBefore;
377
+ if (attrs.widowControl != null) f.widowControl = attrs.widowControl;
376
378
  return f;
377
379
  }
378
380
  /**
@@ -1397,9 +1399,11 @@ function tableRowAttrsToFormatting(attrs) {
1397
1399
  else delete result.heightRule;
1398
1400
  if (attrs.isHeader !== (orig.header ?? void 0)) if (attrs.isHeader) result.header = attrs.isHeader;
1399
1401
  else delete result.header;
1402
+ if (attrs.hidden !== (orig.hidden ?? void 0)) if (attrs.hidden) result.hidden = attrs.hidden;
1403
+ else delete result.hidden;
1400
1404
  return result;
1401
1405
  }
1402
- if (!(attrs.height || attrs.isHeader)) return;
1406
+ if (!(attrs.height || attrs.isHeader || attrs.hidden)) return;
1403
1407
  const f = {};
1404
1408
  if (attrs.height) f.height = {
1405
1409
  value: attrs.height,
@@ -1407,6 +1411,7 @@ function tableRowAttrsToFormatting(attrs) {
1407
1411
  };
1408
1412
  if (attrs.heightRule) f.heightRule = attrs.heightRule;
1409
1413
  if (attrs.isHeader) f.header = attrs.isHeader;
1414
+ if (attrs.hidden) f.hidden = attrs.hidden;
1410
1415
  return f;
1411
1416
  }
1412
1417
  /**
@@ -11,6 +11,7 @@ import { createStyleEngine } from "../../style-engine/styleEngine.js";
11
11
  import { buildRunFormattingOverrideAttrs } from "../extensions/marks/RunFormattingOverrideExtension.js";
12
12
  import { schema } from "../schema/index.js";
13
13
  //#region src/prosemirror/conversion/toProseDoc.ts
14
+ const TOC_STYLE_ID = /^TOC\d*$/iu;
14
15
  /**
15
16
  * Convert a Document to a ProseMirror document
16
17
  *
@@ -80,6 +81,7 @@ function convertBlockSdt(blockSdt, convertBlocks) {
80
81
  */
81
82
  function convertParagraph(paragraph, styleResolver, activeCommentIds, extraRunFormatting, tableParagraphOverlay) {
82
83
  const attrs = paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOverlay);
84
+ const isTocParagraph = TOC_STYLE_ID.test(paragraph.formatting?.styleId ?? "");
83
85
  const inlineNodes = [];
84
86
  let inlineOffset = 0;
85
87
  let bookmarksArr;
@@ -99,10 +101,12 @@ function convertParagraph(paragraph, styleResolver, activeCommentIds, extraRunFo
99
101
  let styleRunFormatting;
100
102
  if (styleResolver) styleRunFormatting = styleResolver.resolveParagraphStyle(paragraph.formatting?.styleId).runFormatting;
101
103
  const paragraphRunFormatting = paragraph.formatting?.runProperties ? resolveTextFormatting(paragraph.formatting.runProperties, styleResolver) : void 0;
102
- const inheritableParagraphRunFormatting = paragraphRunFormatting ? stripParagraphMarkOnlyFormatting(paragraphRunFormatting) : void 0;
104
+ let inheritableParagraphRunFormatting;
105
+ if (paragraphRunFormatting && !isTocParagraph) inheritableParagraphRunFormatting = stripParagraphMarkOnlyFormatting(paragraphRunFormatting);
103
106
  const baseRunFormatting = mergeTextFormatting(styleRunFormatting, extraRunFormatting);
104
107
  const defaultRunFormatting = mergeTextFormatting(baseRunFormatting, inheritableParagraphRunFormatting);
105
- const getInheritedRunFormatting = (formatting) => {
108
+ const getInheritedRunFormatting = (formatting, fieldType) => {
109
+ if (fieldType === "TOC") return hasDirectRunFormatting(formatting) ? suppressParagraphMarkFormatting(baseRunFormatting, void 0, formatting) : baseRunFormatting;
106
110
  if (!hasDirectRunFormatting(formatting)) return defaultRunFormatting;
107
111
  return suppressParagraphMarkFormatting(baseRunFormatting, inheritableParagraphRunFormatting, formatting);
108
112
  };
@@ -252,11 +256,12 @@ function paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOver
252
256
  set("pageBreakBefore", formatting?.pageBreakBefore ?? stylePpr?.pageBreakBefore);
253
257
  set("keepNext", formatting?.keepNext ?? stylePpr?.keepNext);
254
258
  set("keepLines", formatting?.keepLines ?? stylePpr?.keepLines);
259
+ set("widowControl", formatting?.widowControl ?? stylePpr?.widowControl);
255
260
  set("contextualSpacing", formatting?.contextualSpacing ?? stylePpr?.contextualSpacing);
256
261
  set("runInWithNext", formatting?.runInWithNext ?? stylePpr?.runInWithNext);
257
262
  set("outlineLevel", formatting?.outlineLevel ?? stylePpr?.outlineLevel);
258
263
  set("direction", directionFromBidi(formatting?.bidi ?? stylePpr?.bidi));
259
- set("defaultTextFormatting", resolveParagraphDefaultTextFormatting(styleId, formatting, styleResolver));
264
+ set("defaultTextFormatting", resolveParagraphDefaultTextFormatting(styleId, formatting, styleResolver, { includeParagraphMarkRunProperties: !TOC_STYLE_ID.test(styleId ?? "") }));
260
265
  if (!formatting?.numPr && stylePpr?.numPr && stylePpr.numPr.numId !== 0) {
261
266
  attrs.numPr = stylePpr.numPr;
262
267
  attrs.numPrFromStyle = stylePpr.numPr;
@@ -278,6 +283,7 @@ function paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOver
278
283
  set("pageBreakBefore", formatting?.pageBreakBefore);
279
284
  set("keepNext", formatting?.keepNext);
280
285
  set("keepLines", formatting?.keepLines);
286
+ set("widowControl", formatting?.widowControl);
281
287
  set("runInWithNext", formatting?.runInWithNext);
282
288
  set("outlineLevel", formatting?.outlineLevel);
283
289
  set("direction", directionFromBidi(formatting?.bidi));
@@ -381,7 +387,7 @@ function hasDirectRunFormatting(formatting) {
381
387
  return Object.entries(formatting).some(([key, value]) => key !== "styleId" && value !== void 0);
382
388
  }
383
389
  function stripParagraphMarkOnlyFormatting(formatting) {
384
- const { highlight: _h, shading: _s, ...rest } = formatting;
390
+ const { allCaps: _ac, highlight: _h, shading: _s, smallCaps: _sc, ...rest } = formatting;
385
391
  return Object.keys(rest).length > 0 ? rest : void 0;
386
392
  }
387
393
  function suppressParagraphMarkFormatting(base, paragraphMark, direct) {
@@ -411,10 +417,10 @@ function resolveTextFormatting(formatting, styleResolver) {
411
417
  if (!styleResolver) return formatting;
412
418
  return mergeTextFormatting(styleResolver.resolveRunStyle(formatting.styleId), formatting);
413
419
  }
414
- function resolveParagraphDefaultTextFormatting(styleId, formatting, styleResolver) {
420
+ function resolveParagraphDefaultTextFormatting(styleId, formatting, styleResolver, options = {}) {
415
421
  const style = styleId ? styleResolver.getStyle(styleId) ?? styleResolver.getDefaultParagraphStyle() : styleResolver.getDefaultParagraphStyle();
416
422
  const paragraphStyleRpr = style?.type === "paragraph" ? style.rPr : void 0;
417
- const rawParagraphMarkRpr = formatting?.runProperties;
423
+ const rawParagraphMarkRpr = options.includeParagraphMarkRunProperties === false ? void 0 : formatting?.runProperties;
418
424
  const characterStyleRpr = rawParagraphMarkRpr?.styleId !== void 0 ? styleResolver.getRunStyleOwnProperties(rawParagraphMarkRpr.styleId) : void 0;
419
425
  const paragraphRunProperties = rawParagraphMarkRpr ? stripParagraphMarkOnlyFormatting(mergeTextFormatting(characterStyleRpr, rawParagraphMarkRpr) ?? {}) : void 0;
420
426
  return mergeTextFormatting(mergeTextFormatting(mergeTextFormatting(styleResolver.getDocDefaults()?.rPr, styleResolver.getDefaultCharacterStyle()?.rPr), paragraphStyleRpr), paragraphRunProperties);
@@ -599,6 +605,7 @@ function convertTableRow(row, styleResolver, isHeaderRow, columnWidths, totalWid
599
605
  const attrs = { isHeader: !!row.formatting?.header };
600
606
  if (row.formatting?.height?.value !== void 0) attrs.height = row.formatting.height.value;
601
607
  if (row.formatting?.heightRule) attrs.heightRule = row.formatting.heightRule;
608
+ if (row.formatting?.hidden) attrs.hidden = true;
602
609
  if (row.formatting) attrs._originalFormatting = row.formatting;
603
610
  const numCells = row.cells.length;
604
611
  const isFirstRow = rowIndex === 0;
@@ -769,7 +776,7 @@ function convertField(field, getInheritedRunFormatting, styleResolver) {
769
776
  if (!fieldFormatting && r.formatting) fieldFormatting = r.formatting;
770
777
  }
771
778
  if (!fieldFormatting && field.type === "complexField" && field.fieldResult.length === 0) fieldFormatting = field.formatting;
772
- const inheritedFormatting = getInheritedRunFormatting(fieldFormatting);
779
+ const inheritedFormatting = getInheritedRunFormatting(fieldFormatting, field.fieldType);
773
780
  const { marks } = buildRunMarks(fieldFormatting, inheritedFormatting, styleResolver);
774
781
  return schema.node("field", {
775
782
  fieldType: field.fieldType,
@@ -213,6 +213,7 @@ const paragraphNodeSpec = {
213
213
  renderedPageBreakBefore: { default: null },
214
214
  keepNext: { default: null },
215
215
  keepLines: { default: null },
216
+ widowControl: { default: null },
216
217
  contextualSpacing: { default: null },
217
218
  runInWithNext: { default: null },
218
219
  defaultTextFormatting: { default: null },