@stll/folio-core 0.7.0 → 0.8.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 (92) hide show
  1. package/dist/ai-edits/blockRange.d.ts +13 -2
  2. package/dist/ai-edits/blockRange.js +23 -2
  3. package/dist/ai-edits/index.d.ts +4 -2
  4. package/dist/ai-edits/index.js +3 -1
  5. package/dist/ai-edits/scoped-reading.d.ts +9 -0
  6. package/dist/ai-edits/scoped-reading.js +60 -0
  7. package/dist/ai-edits/snapshot.js +13 -4
  8. package/dist/ai-edits/types.d.ts +44 -2
  9. package/dist/compat/eigenpal.d.ts +5 -2
  10. package/dist/compat/eigenpal.js +4 -1
  11. package/dist/controller/fontReadiness.d.ts +29 -0
  12. package/dist/controller/fontReadiness.js +139 -0
  13. package/dist/controller/layoutPipeline.js +76 -9
  14. package/dist/docx/blockContentParser.js +51 -8
  15. package/dist/docx/capabilities.d.ts +41 -0
  16. package/dist/docx/capabilities.js +322 -0
  17. package/dist/docx/compatibility.d.ts +34 -2
  18. package/dist/docx/compatibility.js +128 -38
  19. package/dist/docx/conformance.d.ts +6 -0
  20. package/dist/docx/conformance.js +18 -0
  21. package/dist/docx/ensureParaIds.d.ts +29 -0
  22. package/dist/docx/ensureParaIds.js +423 -0
  23. package/dist/docx/numberingParser.js +1 -0
  24. package/dist/docx/paragraphParser.js +9 -0
  25. package/dist/docx/paragraphTextBoxEnrichment.js +141 -2
  26. package/dist/docx/parser.js +2 -0
  27. package/dist/docx/selectiveXmlPatch.d.ts +10 -1
  28. package/dist/docx/selectiveXmlPatch.js +1 -1
  29. package/dist/docx/serializer/paragraphSerializer.js +4 -0
  30. package/dist/docx/serializer/runSerializer.js +7 -2
  31. package/dist/docx/serializer/tableSerializer.js +4 -0
  32. package/dist/docx/tableParser.js +8 -0
  33. package/dist/docx/textBoxParser.js +6 -1
  34. package/dist/docx/vmlImageParser.js +145 -1
  35. package/dist/index.d.ts +5 -2
  36. package/dist/index.js +4 -1
  37. package/dist/layout-bridge/convert/headerFooterLayout.js +16 -2
  38. package/dist/layout-bridge/convert/paragraphFrames.d.ts +7 -0
  39. package/dist/layout-bridge/convert/paragraphFrames.js +136 -0
  40. package/dist/layout-bridge/convert/toFlowBlocks.js +42 -3
  41. package/dist/layout-bridge/engine/hitTest.js +2 -1
  42. package/dist/layout-bridge/engine/selectionRects.js +2 -1
  43. package/dist/layout-engine/headerFooterRefs.d.ts +7 -0
  44. package/dist/layout-engine/headerFooterRefs.js +40 -0
  45. package/dist/layout-engine/index.d.ts +3 -2
  46. package/dist/layout-engine/index.js +36 -82
  47. package/dist/layout-engine/measure/listMarkerWidth.d.ts +3 -1
  48. package/dist/layout-engine/measure/listMarkerWidth.js +23 -4
  49. package/dist/layout-engine/measure/measureBlocks.d.ts +3 -0
  50. package/dist/layout-engine/measure/measureBlocks.js +133 -51
  51. package/dist/layout-engine/measure/measureParagraph.js +13 -4
  52. package/dist/layout-engine/measure/tableCellGrid.d.ts +15 -0
  53. package/dist/layout-engine/measure/tableCellGrid.js +62 -0
  54. package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +18 -0
  55. package/dist/layout-engine/measure/textBoxParagraphLayout.js +32 -0
  56. package/dist/layout-engine/paginator.d.ts +2 -1
  57. package/dist/layout-engine/paginator.js +7 -6
  58. package/dist/layout-engine/paragraphFrame.d.ts +22 -0
  59. package/dist/layout-engine/paragraphFrame.js +17 -0
  60. package/dist/layout-engine/paragraphSequence.d.ts +7 -0
  61. package/dist/layout-engine/paragraphSequence.js +25 -0
  62. package/dist/layout-engine/paragraphSpacing.d.ts +17 -0
  63. package/dist/layout-engine/paragraphSpacing.js +30 -0
  64. package/dist/layout-engine/renderedBreakReconciliation.d.ts +58 -0
  65. package/dist/layout-engine/renderedBreakReconciliation.js +63 -0
  66. package/dist/layout-engine/types.d.ts +17 -8
  67. package/dist/layout-engine/types.js +15 -2
  68. package/dist/layout-painter/renderPage.js +2 -2
  69. package/dist/layout-painter/renderParagraph.js +6 -5
  70. package/dist/layout-painter/renderTable.js +42 -12
  71. package/dist/layout-painter/renderTextBox.js +17 -7
  72. package/dist/managers/DocumentLoaderManager.js +1 -1
  73. package/dist/paged-layout/sectionBlockWidths.d.ts +3 -0
  74. package/dist/paged-layout/sectionBlockWidths.js +9 -0
  75. package/dist/paged-layout/sectionGeometry.js +1 -1
  76. package/dist/prosemirror/attrs/index.js +11 -0
  77. package/dist/prosemirror/conversion/fromProseDoc.js +2 -0
  78. package/dist/prosemirror/conversion/toProseDoc.js +27 -23
  79. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
  80. package/dist/prosemirror/extensions/features/ListExtension.js +1 -0
  81. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +68 -16
  82. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
  83. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
  84. package/dist/prosemirror/schema/nodes.d.ts +4 -2
  85. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  86. package/dist/server.d.ts +4 -2
  87. package/dist/server.js +3 -1
  88. package/dist/types/index.d.ts +2 -1
  89. package/dist/utils/createDocument.js +26 -18
  90. package/dist/utils/hexId.d.ts +8 -1
  91. package/dist/utils/hexId.js +11 -3
  92. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { emuToPixels } from "../utils/units.js";
2
- import { isFloatingImageRun, isFloatingTextBoxBlock, tableColumnsArePinned } from "../layout-engine/types.js";
2
+ import { getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, tableColumnsArePinned } from "../layout-engine/types.js";
3
3
  import { measureParagraph } from "../layout-engine/measure/measureParagraph.js";
4
4
  import { getAutomaticTextColorForBackground } from "./documentColors.js";
5
5
  import { renderParagraphFragment } from "./renderParagraph.js";
@@ -207,6 +207,7 @@ function renderNestedTable(block, measure, context, doc) {
207
207
  rowYPositions.push(yPos);
208
208
  const spanningCells = /* @__PURE__ */ new Map();
209
209
  const columnsPinned = tableColumnsArePinned(block);
210
+ const cellGrid = buildTableCellGrid(block, measure.columnWidths.length);
210
211
  let y = 0;
211
212
  for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex++) {
212
213
  const row = block.rows[rowIndex];
@@ -216,7 +217,7 @@ function renderNestedTable(block, measure, context, doc) {
216
217
  y += rowMeasure.height;
217
218
  continue;
218
219
  }
219
- const rowEl = renderTableRow(row, rowMeasure, rowIndex, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, void 0, block.bidi, columnsPinned);
220
+ const rowEl = renderTableRow(row, rowMeasure, rowIndex, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, void 0, block.bidi, columnsPinned, cellGrid);
220
221
  tableEl.append(rowEl);
221
222
  y += rowMeasure.height;
222
223
  }
@@ -255,10 +256,10 @@ function renderTableCell(cell, cellMeasure, x, rowHeight, borderFlags, columnsPi
255
256
  const padLeft = cell.padding?.left ?? 7;
256
257
  cellEl.style.padding = `${padTop}px ${padRight}px ${padBottom}px ${padLeft}px`;
257
258
  if (cell.borders) {
258
- if (borderFlags.isFirstRow) applyBorder(cellEl, "top", cell.borders.top);
259
+ if (borderFlags.drawTop) applyBorder(cellEl, "top", cell.borders.top);
259
260
  applyBorder(cellEl, "right", cell.borders.right);
260
261
  applyBorder(cellEl, "bottom", cell.borders.bottom);
261
- if (borderFlags.isFirstCol) applyBorder(cellEl, "left", cell.borders.left);
262
+ if (borderFlags.drawLeft) applyBorder(cellEl, "left", cell.borders.left);
262
263
  }
263
264
  if (cell.background) {
264
265
  cellEl.style.backgroundColor = cell.background;
@@ -303,10 +304,30 @@ function renderTableCell(cell, cellMeasure, x, rowHeight, borderFlags, columnsPi
303
304
  }
304
305
  return cellEl;
305
306
  }
307
+ const tableCellGridKey = (rowIndex, columnIndex) => `${rowIndex}:${columnIndex}`;
308
+ function buildTableCellGrid(block, columnCount) {
309
+ const grid = /* @__PURE__ */ new Map();
310
+ for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex++) {
311
+ const row = block.rows[rowIndex];
312
+ if (!row) continue;
313
+ let columnIndex = 0;
314
+ for (const cell of row.cells) {
315
+ while (grid.has(tableCellGridKey(rowIndex, columnIndex))) columnIndex += 1;
316
+ const colSpan = cell.colSpan ?? 1;
317
+ const rowSpan = cell.rowSpan ?? 1;
318
+ const rowEnd = Math.min(block.rows.length, rowIndex + rowSpan);
319
+ const columnEnd = Math.min(columnCount, columnIndex + colSpan);
320
+ for (let gridRow = rowIndex; gridRow < rowEnd; gridRow++) for (let gridColumn = columnIndex; gridColumn < columnEnd; gridColumn++) grid.set(tableCellGridKey(gridRow, gridColumn), cell);
321
+ columnIndex += colSpan;
322
+ }
323
+ }
324
+ return grid;
325
+ }
326
+ const hasVisibleBorder = (border) => border !== void 0 && border.width !== 0 && border.style !== "none" && border.style !== "nil";
306
327
  /**
307
328
  * Render a table row with rowSpan support
308
329
  */
309
- function renderTableRow(row, rowMeasure, rowIndex, y, columnWidths, totalRows, context, doc, spanningCells, rowYPositions, isFirstRowInFragment, bidi = false, columnsPinned = false) {
330
+ function renderTableRow(row, rowMeasure, rowIndex, y, columnWidths, totalRows, context, doc, spanningCells, rowYPositions, isFirstRowInFragment, bidi = false, columnsPinned = false, cellGrid) {
310
331
  const rowEl = doc.createElement("div");
311
332
  rowEl.className = TABLE_CLASS_NAMES.row;
312
333
  const tableWidth = bidi ? columnWidths.reduce((sum, columnWidth) => sum + columnWidth, 0) : 0;
@@ -320,8 +341,9 @@ function renderTableRow(row, rowMeasure, rowIndex, y, columnWidths, totalRows, c
320
341
  if (spanningCells) {
321
342
  for (const [, spanCell] of spanningCells) if (spanCell.startRow < rowIndex && spanCell.startRow + spanCell.rowSpan > rowIndex) for (let c = 0; c < spanCell.colSpan; c++) occupiedColumns.add(spanCell.columnIndex + c);
322
343
  }
323
- let x = 0;
324
- let columnIndex = 0;
344
+ const gridBefore = row.gridBefore ?? 0;
345
+ let x = getTableRowLeadingWidth(row, columnWidths);
346
+ let columnIndex = gridBefore;
325
347
  while (occupiedColumns.has(columnIndex)) {
326
348
  x += columnWidths[columnIndex] ?? 0;
327
349
  columnIndex++;
@@ -345,11 +367,18 @@ function renderTableRow(row, rowMeasure, rowIndex, y, columnWidths, totalRows, c
345
367
  const isLastRow = rowIndex + rowSpan >= totalRows;
346
368
  const atLogicalStart = columnIndex === 0;
347
369
  const atLogicalEnd = columnIndex + colSpan >= columnWidths.length;
370
+ const isFirstCol = bidi ? atLogicalEnd : atLogicalStart;
371
+ const isLastCol = bidi ? atLogicalStart : atLogicalEnd;
372
+ const aboveCell = cellGrid?.get(tableCellGridKey(rowIndex - 1, columnIndex));
373
+ const leftNeighborColumn = bidi ? columnIndex + colSpan : columnIndex - 1;
374
+ const leftCell = cellGrid?.get(tableCellGridKey(rowIndex, leftNeighborColumn));
375
+ const drawTop = isFirstRow || !hasVisibleBorder(aboveCell?.borders?.bottom);
376
+ const drawLeft = isFirstCol || !hasVisibleBorder(leftCell?.borders?.right);
348
377
  const cellEl = renderTableCell(cell, cellMeasure, cellLeft, cellHeight, {
349
- isFirstRow,
378
+ drawTop,
350
379
  isLastRow,
351
- isFirstCol: bidi ? atLogicalEnd : atLogicalStart,
352
- isLastCol: bidi ? atLogicalStart : atLogicalEnd
380
+ drawLeft,
381
+ isLastCol
353
382
  }, columnsPinned, context, doc);
354
383
  cellEl.dataset["cellIndex"] = String(cellIndex);
355
384
  cellEl.dataset["columnIndex"] = String(columnIndex);
@@ -429,6 +458,7 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
429
458
  rowYPositions.push(yPos);
430
459
  const spanningCells = /* @__PURE__ */ new Map();
431
460
  const columnsPinned = tableColumnsArePinned(block);
461
+ const cellGrid = buildTableCellGrid(block, measure.columnWidths.length);
432
462
  const headerRowCount = fragment.headerRowCount ?? 0;
433
463
  let y = 0;
434
464
  if (headerRowCount > 0 && fragment.continuesFromPrev) for (let hdrIdx = 0; hdrIdx < headerRowCount; hdrIdx++) {
@@ -439,7 +469,7 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
439
469
  y += hdrRowMeasure.height;
440
470
  continue;
441
471
  }
442
- const rowEl = renderTableRow(hdrRow, hdrRowMeasure, hdrIdx, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, hdrIdx === 0, block.bidi, columnsPinned);
472
+ const rowEl = renderTableRow(hdrRow, hdrRowMeasure, hdrIdx, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, hdrIdx === 0, block.bidi, columnsPinned, cellGrid);
443
473
  rowEl.dataset["repeatedHeader"] = "true";
444
474
  tableEl.append(rowEl);
445
475
  y += hdrRowMeasure.height;
@@ -467,7 +497,7 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
467
497
  continue;
468
498
  }
469
499
  const isFirstRowInFragment = headerRowCount > 0 && fragment.continuesFromPrev ? false : fragment.continuesFromPrev && rowIndex === fragment.fromRow;
470
- const rowEl = renderTableRow(row, rowMeasure, rowIndex, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, isFirstRowInFragment, block.bidi, columnsPinned);
500
+ const rowEl = renderTableRow(row, rowMeasure, rowIndex, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, isFirstRowInFragment, block.bidi, columnsPinned, cellGrid);
471
501
  contentParent.append(rowEl);
472
502
  y += rowMeasure.height;
473
503
  }
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_TEXTBOX_MARGINS } from "../layout-engine/types.js";
2
2
  import { renderParagraphFragment } from "./renderParagraph.js";
3
+ import { layoutTextBoxParagraphs } from "../layout-engine/measure/textBoxParagraphLayout.js";
3
4
  //#region src/layout-painter/renderTextBox.ts
4
5
  /**
5
6
  * Text Box Renderer
@@ -38,28 +39,37 @@ function renderTextBoxFragment(fragment, block, measure, context, options = {})
38
39
  if (fragment.pmStart !== void 0) containerEl.dataset["pmStart"] = String(fragment.pmStart);
39
40
  if (fragment.pmEnd !== void 0) containerEl.dataset["pmEnd"] = String(fragment.pmEnd);
40
41
  const innerWidth = fragment.width - margins.left - margins.right;
41
- let yOffset = 0;
42
+ const paragraphLayout = layoutTextBoxParagraphs(block.content, measure.innerMeasures);
42
43
  for (let i = 0; i < block.content.length; i++) {
43
44
  const paraBlock = block.content[i];
44
45
  const paraMeasure = measure.innerMeasures[i];
45
- if (!paraBlock || !paraMeasure) continue;
46
- const paraEl = renderParagraphFragment({
46
+ const placement = paragraphLayout.placements[i];
47
+ if (!paraBlock || !paraMeasure || !placement) continue;
48
+ const paraFragment = {
47
49
  kind: "paragraph",
48
50
  blockId: paraBlock.id,
49
51
  x: 0,
50
- y: yOffset,
52
+ y: 0,
51
53
  width: innerWidth,
52
- height: paraMeasure.totalHeight,
54
+ height: placement.contentHeight,
53
55
  ...paraBlock.pmStart !== void 0 ? { pmStart: paraBlock.pmStart } : {},
54
56
  ...paraBlock.pmEnd !== void 0 ? { pmEnd: paraBlock.pmEnd } : {},
55
57
  fromLine: 0,
56
58
  toLine: paraMeasure.lines.length
57
- }, paraBlock, paraMeasure, context, { document: doc });
59
+ };
60
+ const prevBorders = block.content[i - 1]?.attrs?.borders;
61
+ const nextBorders = block.content[i + 1]?.attrs?.borders;
62
+ const paraEl = renderParagraphFragment(paraFragment, paraBlock, paraMeasure, context, {
63
+ document: doc,
64
+ ...prevBorders !== void 0 ? { prevBorders } : {},
65
+ ...nextBorders !== void 0 ? { nextBorders } : {}
66
+ });
58
67
  paraEl.style.position = "relative";
59
68
  paraEl.style.left = "0";
60
69
  paraEl.style.top = "0";
70
+ paraEl.style.height = `${placement.contentHeight}px`;
71
+ paraEl.style.marginTop = `${placement.leadingSpacing}px`;
61
72
  containerEl.append(paraEl);
62
- yOffset += paraMeasure.totalHeight;
63
73
  }
64
74
  return containerEl;
65
75
  }
@@ -1,8 +1,8 @@
1
+ import { inspectDocxCompatibility } from "../docx/compatibility.js";
1
2
  import { resetAuthorColors } from "../utils/authorColors.js";
2
3
  import { loadFontsWithMapping } from "../utils/fontLoader.js";
3
4
  import { parseDocx } from "../docx/parser.js";
4
5
  import { recordDocumentLoadPhase } from "../layout-engine/layoutInstrumentation.js";
5
- import { inspectDocxCompatibility } from "../docx/compatibility.js";
6
6
  //#region src/managers/DocumentLoaderManager.ts
7
7
  /**
8
8
  * DocumentLoaderManager
@@ -11,6 +11,9 @@ type PerBlockMeasureInputs = {
11
11
  widths: number[];
12
12
  marginTops: number[];
13
13
  pageHeights: number[];
14
+ pageWidths: number[];
15
+ marginLefts: number[];
16
+ marginRights: number[];
14
17
  marginBottoms: number[];
15
18
  };
16
19
  /**
@@ -35,6 +35,9 @@ function computePerBlockMeasureInputs({ blocks, bodyConfig, finalConfig }) {
35
35
  const widths = [];
36
36
  const marginTops = [];
37
37
  const pageHeights = [];
38
+ const pageWidths = [];
39
+ const marginLefts = [];
40
+ const marginRights = [];
38
41
  const marginBottoms = [];
39
42
  for (let i = 0; i < blocks.length; i++) {
40
43
  const config = sectionConfigs[sectionIdx] ?? finalConfig;
@@ -42,6 +45,9 @@ function computePerBlockMeasureInputs({ blocks, bodyConfig, finalConfig }) {
42
45
  widths.push(colWidth(contentWidth(config), columns, columnIndex));
43
46
  marginTops.push(config.margins.top);
44
47
  pageHeights.push(config.pageSize.h);
48
+ pageWidths.push(config.pageSize.w);
49
+ marginLefts.push(config.margins.left);
50
+ marginRights.push(config.margins.right);
45
51
  marginBottoms.push(config.margins.bottom);
46
52
  if (sectionIdx < breakIndices.length && i === breakIndices[sectionIdx]) {
47
53
  sectionIdx++;
@@ -53,6 +59,9 @@ function computePerBlockMeasureInputs({ blocks, bodyConfig, finalConfig }) {
53
59
  widths,
54
60
  marginTops,
55
61
  pageHeights,
62
+ pageWidths,
63
+ marginLefts,
64
+ marginRights,
56
65
  marginBottoms
57
66
  };
58
67
  }
@@ -9,7 +9,7 @@ const DEFAULT_MARGINS = {
9
9
  bottom: 96,
10
10
  left: 96
11
11
  };
12
- const twipsToPixels = (twips) => Math.round(twips / 1440 * 96);
12
+ const twipsToPixels = (twips) => twips / 1440 * 96;
13
13
  /**
14
14
  * Convert an offset-like twip dimension to px. Explicit 0 is meaningful for
15
15
  * page margins and header/footer distances; only absence should use fallback.
@@ -18,6 +18,11 @@ const IMAGE_CSS_FLOATS = [
18
18
  "right",
19
19
  "none"
20
20
  ];
21
+ const TEXT_BOX_AUTO_FIT_VALUES = [
22
+ "none",
23
+ "normal",
24
+ "shape"
25
+ ];
21
26
  const FIELD_KINDS = ["simple", "complex"];
22
27
  const MATH_DISPLAYS = ["inline", "block"];
23
28
  const SHAPE_FILL_TYPES = [
@@ -156,6 +161,11 @@ const readParagraphAttrs = (node) => {
156
161
  optionalBoolean(attrs, "listMarkerHidden", "paragraph.attrs.listMarkerHidden", issues);
157
162
  optionalString(attrs, "listMarkerFontFamily", "paragraph.attrs.listMarkerFontFamily", issues);
158
163
  optionalNumber(attrs, "listMarkerFontSize", "paragraph.attrs.listMarkerFontSize", issues);
164
+ optionalOneOf(attrs, "listMarkerAlignment", "paragraph.attrs.listMarkerAlignment", issues, [
165
+ "left",
166
+ "center",
167
+ "right"
168
+ ]);
159
169
  optionalString(attrs, "listMarkerSuffix", "paragraph.attrs.listMarkerSuffix", issues);
160
170
  optionalBoolean(attrs, "listMarkerAllCaps", "paragraph.attrs.listMarkerAllCaps", issues);
161
171
  optionalNumber(attrs, "listImplicitChildLevelAdvances", "paragraph.attrs.listImplicitChildLevelAdvances", issues);
@@ -410,6 +420,7 @@ const readTextBoxAttrs = (node) => {
410
420
  expectNodeType(node, "textBox", issues);
411
421
  optionalNumber(attrs, "width", "textBox.attrs.width", issues);
412
422
  optionalNumber(attrs, "height", "textBox.attrs.height", issues);
423
+ optionalOneOf(attrs, "autoFit", "textBox.attrs.autoFit", issues, TEXT_BOX_AUTO_FIT_VALUES);
413
424
  optionalString(attrs, "textBoxId", "textBox.attrs.textBoxId", issues);
414
425
  optionalString(attrs, "fillColor", "textBox.attrs.fillColor", issues);
415
426
  optionalNumber(attrs, "outlineWidth", "textBox.attrs.outlineWidth", issues);
@@ -210,6 +210,7 @@ function listRenderingFromAttrs(attrs) {
210
210
  ...attrs.listMarkerHidden != null && { markerHidden: attrs.listMarkerHidden },
211
211
  ...attrs.listMarkerFontFamily != null && { markerFontFamily: attrs.listMarkerFontFamily },
212
212
  ...attrs.listMarkerFontSize != null && { markerFontSize: attrs.listMarkerFontSize },
213
+ ...attrs.listMarkerAlignment != null && { markerAlignment: attrs.listMarkerAlignment },
213
214
  ...attrs.listMarkerSuffix != null && { markerSuffix: attrs.listMarkerSuffix },
214
215
  ...attrs.listMarkerAllCaps != null && { markerAllCaps: attrs.listMarkerAllCaps },
215
216
  ...attrs.listImplicitChildLevelAdvances != null && { implicitChildLevelAdvances: attrs.listImplicitChildLevelAdvances },
@@ -1516,6 +1517,7 @@ function convertPMTextBox(node) {
1516
1517
  type: "paragraph",
1517
1518
  content: []
1518
1519
  }],
1520
+ ...attrs.autoFit !== void 0 ? { autoFit: attrs.autoFit } : {},
1519
1521
  margins: (() => {
1520
1522
  const m = {};
1521
1523
  if (typeof attrs.marginTop === "number") m.top = pixelsToEmu(attrs.marginTop);
@@ -227,6 +227,7 @@ function paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOver
227
227
  if (paragraph.listRendering?.markerHidden) attrs.listMarkerHidden = paragraph.listRendering.markerHidden;
228
228
  if (paragraph.listRendering?.markerFontFamily) attrs.listMarkerFontFamily = paragraph.listRendering.markerFontFamily;
229
229
  if (paragraph.listRendering?.markerFontSize) attrs.listMarkerFontSize = paragraph.listRendering.markerFontSize;
230
+ if (paragraph.listRendering?.markerAlignment) attrs.listMarkerAlignment = paragraph.listRendering.markerAlignment;
230
231
  if (paragraph.listRendering?.markerSuffix) attrs.listMarkerSuffix = paragraph.listRendering.markerSuffix;
231
232
  if (paragraph.listRendering?.markerAllCaps) attrs.listMarkerAllCaps = paragraph.listRendering.markerAllCaps;
232
233
  if (paragraph.listRendering?.implicitChildLevelAdvances !== void 0) attrs.listImplicitChildLevelAdvances = paragraph.listRendering.implicitChildLevelAdvances;
@@ -259,11 +260,11 @@ function paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOver
259
260
  if (formatting?.spaceBefore === void 0 && tableParagraphOverlay?.spaceBefore === void 0 && paragraphStyle?.pPr?.spaceBefore === void 0 && docDefaultSpacing?.spaceBefore !== void 0) spacingFromDocDefaults.before = true;
260
261
  if (formatting?.spaceAfter === void 0 && tableParagraphOverlay?.spaceAfter === void 0 && paragraphStyle?.pPr?.spaceAfter === void 0 && docDefaultSpacing?.spaceAfter !== void 0) spacingFromDocDefaults.after = true;
261
262
  if (spacingFromDocDefaults.before || spacingFromDocDefaults.after) attrs.spacingFromDocDefaults = spacingFromDocDefaults;
262
- set("indentLeft", formatting?.indentLeft ?? stylePpr?.indentLeft);
263
+ const numberingStyleIndent = formatting?.numPr?.numId === 0 && stylePpr?.numPr !== void 0 && stylePpr.numPr.numId !== 0 ? void 0 : stylePpr;
264
+ set("indentLeft", formatting?.indentLeft ?? numberingStyleIndent?.indentLeft);
263
265
  set("indentRight", formatting?.indentRight ?? stylePpr?.indentRight);
264
- const styleFirstLine = formatting?.numPr?.numId === 0 && stylePpr?.numPr !== void 0 && stylePpr.numPr.numId !== 0 ? void 0 : stylePpr;
265
- set("indentFirstLine", formatting?.indentFirstLine ?? styleFirstLine?.indentFirstLine);
266
- set("hangingIndent", formatting?.hangingIndent ?? styleFirstLine?.hangingIndent);
266
+ set("indentFirstLine", formatting?.indentFirstLine ?? numberingStyleIndent?.indentFirstLine);
267
+ set("hangingIndent", formatting?.hangingIndent ?? numberingStyleIndent?.hangingIndent);
267
268
  set("borders", formatting?.borders ?? stylePpr?.borders);
268
269
  set("shading", formatting?.shading ?? stylePpr?.shading);
269
270
  set("tabs", formatting?.tabs ?? stylePpr?.tabs);
@@ -301,7 +302,7 @@ function paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOver
301
302
  set("runInWithNext", formatting?.runInWithNext);
302
303
  set("outlineLevel", formatting?.outlineLevel);
303
304
  set("direction", directionFromBidi(formatting?.bidi));
304
- set("defaultTextFormatting", resolveTextFormatting(formatting?.runProperties, styleResolver));
305
+ set("defaultTextFormatting", stripParagraphMarkOnlyFormatting(resolveTextFormatting(formatting?.runProperties, styleResolver) ?? {}));
305
306
  }
306
307
  if (paragraph.sectionProperties) {
307
308
  attrs._sectionProperties = paragraph.sectionProperties;
@@ -401,33 +402,33 @@ function hasDirectRunFormatting(formatting) {
401
402
  return Object.entries(formatting).some(([key, value]) => key !== "styleId" && value !== void 0);
402
403
  }
403
404
  function stripParagraphMarkOnlyFormatting(formatting) {
404
- const { allCaps: _ac, highlight: _h, shading: _s, smallCaps: _sc, ...rest } = formatting;
405
+ const { allCaps: _ac, highlight: _h, shading: _s, smallCaps: _sc, vertAlign: _va, ...rest } = formatting;
405
406
  return Object.keys(rest).length > 0 ? rest : void 0;
406
407
  }
407
408
  function suppressParagraphMarkFormatting(base, paragraphMark, direct, paragraphMarkPrecedesStyle = false) {
408
409
  if (!paragraphMark) return base;
409
410
  const result = (paragraphMarkPrecedesStyle ? mergeTextFormatting(paragraphMark, base) : mergeTextFormatting(base, paragraphMark)) ?? {};
410
- suppressBooleanParagraphMark(result, paragraphMark, direct, "bold");
411
- suppressBooleanParagraphMark(result, paragraphMark, direct, "italic");
412
- suppressBooleanParagraphMark(result, paragraphMark, direct, "strike");
413
- suppressBooleanParagraphMark(result, paragraphMark, direct, "doubleStrike");
414
- suppressBooleanParagraphMark(result, paragraphMark, direct, "allCaps");
415
- suppressBooleanParagraphMark(result, paragraphMark, direct, "smallCaps");
416
- suppressBooleanParagraphMark(result, paragraphMark, direct, "hidden");
417
- suppressBooleanParagraphMark(result, paragraphMark, direct, "emboss");
418
- suppressBooleanParagraphMark(result, paragraphMark, direct, "imprint");
419
- suppressBooleanParagraphMark(result, paragraphMark, direct, "shadow");
420
- suppressBooleanParagraphMark(result, paragraphMark, direct, "outline");
421
- suppressBooleanParagraphMark(result, paragraphMark, direct, "rtl");
411
+ suppressBooleanParagraphMark(result, base, paragraphMark, direct, "bold");
412
+ suppressBooleanParagraphMark(result, base, paragraphMark, direct, "italic");
413
+ suppressBooleanParagraphMark(result, base, paragraphMark, direct, "strike");
414
+ suppressBooleanParagraphMark(result, base, paragraphMark, direct, "doubleStrike");
415
+ suppressBooleanParagraphMark(result, base, paragraphMark, direct, "allCaps");
416
+ suppressBooleanParagraphMark(result, base, paragraphMark, direct, "smallCaps");
417
+ suppressBooleanParagraphMark(result, base, paragraphMark, direct, "hidden");
418
+ suppressBooleanParagraphMark(result, base, paragraphMark, direct, "emboss");
419
+ suppressBooleanParagraphMark(result, base, paragraphMark, direct, "imprint");
420
+ suppressBooleanParagraphMark(result, base, paragraphMark, direct, "shadow");
421
+ suppressBooleanParagraphMark(result, base, paragraphMark, direct, "outline");
422
+ suppressBooleanParagraphMark(result, base, paragraphMark, direct, "rtl");
422
423
  if (paragraphMark.fontSize !== void 0 && direct?.fontSize === void 0 && base?.fontSize !== void 0) result.fontSize = base.fontSize;
423
424
  if (paragraphMark.fontSizeCs !== void 0 && direct?.fontSizeCs === void 0 && base?.fontSizeCs !== void 0) result.fontSizeCs = base.fontSizeCs;
424
425
  if (paragraphMark.underline !== void 0 && direct?.underline === void 0) result.underline = { style: "none" };
425
426
  if (paragraphMark.spacing !== void 0 && direct?.spacing === void 0) result.spacing = 0;
426
427
  return Object.keys(result).length > 0 ? result : void 0;
427
428
  }
428
- function suppressBooleanParagraphMark(result, paragraphMark, direct, key) {
429
+ function suppressBooleanParagraphMark(result, base, paragraphMark, direct, key) {
429
430
  if (paragraphMark[key] === void 0 || direct?.[key] !== void 0) return;
430
- result[key] = false;
431
+ result[key] = base?.[key] ?? false;
431
432
  }
432
433
  function resolveTextFormatting(formatting, styleResolver) {
433
434
  if (!formatting) return styleResolver?.resolveRunStyle(null);
@@ -456,7 +457,7 @@ function calculateRowSpans(table) {
456
457
  clearActiveVerticalMerges(activeMerges, result);
457
458
  continue;
458
459
  }
459
- let colIndex = 0;
460
+ let colIndex = row.formatting?.gridBefore ?? 0;
460
461
  const rowCells = row.cells.map((cell) => {
461
462
  const colspan = cell.formatting?.gridSpan ?? 1;
462
463
  const vMerge = cell.formatting?.vMerge;
@@ -616,8 +617,9 @@ function convertTable(table, styleResolver, context) {
616
617
  function countTableColumns(rows) {
617
618
  let maxColumns = 0;
618
619
  for (const row of rows) {
619
- let rowColumns = 0;
620
+ let rowColumns = row.formatting?.gridBefore ?? 0;
620
621
  for (const cell of row.cells) rowColumns += cell.formatting?.gridSpan ?? 1;
622
+ rowColumns += row.formatting?.gridAfter ?? 0;
621
623
  maxColumns = Math.max(maxColumns, rowColumns);
622
624
  }
623
625
  return maxColumns;
@@ -651,7 +653,7 @@ function convertTableRow(row, styleResolver, context, isHeaderRow, columnWidths,
651
653
  if (totalCols > 1) fallback.formatting = { gridSpan: totalCols };
652
654
  effectiveCells = [fallback];
653
655
  }
654
- let colIndex = 0;
656
+ let colIndex = row.formatting?.gridBefore ?? 0;
655
657
  const cells = [];
656
658
  for (const cellIndex_item of effectiveCells) {
657
659
  const cell = cellIndex_item;
@@ -1264,6 +1266,7 @@ function extractTextBoxesFromParagraph(paragraph) {
1264
1266
  if (shape.fill) textBox.fill = shape.fill;
1265
1267
  if (shape.outline) textBox.outline = shape.outline;
1266
1268
  if (shape.textBody.margins) textBox.margins = shape.textBody.margins;
1269
+ if (shape.textBody.autoFit) textBox.autoFit = shape.textBody.autoFit;
1267
1270
  textBoxes.push(textBox);
1268
1271
  }
1269
1272
  }
@@ -1332,6 +1335,7 @@ function convertTextBox(textBox, styleResolver, options = {}) {
1332
1335
  return schema.node("textBox", {
1333
1336
  width: widthPx,
1334
1337
  height: heightPx,
1338
+ autoFit: textBox.autoFit,
1335
1339
  textBoxId: textBox.id,
1336
1340
  fillColor,
1337
1341
  outlineWidth,
@@ -199,6 +199,7 @@ const paragraphNodeSpec = {
199
199
  listMarkerHidden: { default: null },
200
200
  listMarkerFontFamily: { default: null },
201
201
  listMarkerFontSize: { default: null },
202
+ listMarkerAlignment: { default: null },
202
203
  listMarkerSuffix: { default: null },
203
204
  listMarkerAllCaps: { default: null },
204
205
  listImplicitChildLevelAdvances: { default: null },
@@ -34,6 +34,7 @@ const LIST_FORMATTING_ATTRS = [
34
34
  "listMarkerHidden",
35
35
  "listMarkerFontFamily",
36
36
  "listMarkerFontSize",
37
+ "listMarkerAlignment",
37
38
  "listMarkerSuffix",
38
39
  "listLevelNumFmts",
39
40
  "listLevelStarts",
@@ -4,26 +4,76 @@ import { ignoreTrackedChanges } from "./ParagraphChangeTrackerExtension.js";
4
4
  import { Plugin, PluginKey } from "prosemirror-state";
5
5
  //#region src/prosemirror/extensions/features/ParaIdAllocatorExtension.ts
6
6
  const paraIdAllocatorKey = new PluginKey("paraIdAllocator");
7
- const collectParaIdUpdates = (doc) => {
8
- const seen = /* @__PURE__ */ new Set();
9
- const updates = [];
7
+ const isUsableParaId = (value) => typeof value === "string" && value.length > 0 && value !== "00000000";
8
+ /**
9
+ * For every valid paraId in the pre-transaction doc, the position its
10
+ * paragraph ends up at after `transactions` — the occurrence that
11
+ * rightfully keeps the id when the new doc holds duplicates. Ids whose
12
+ * paragraph start was deleted by the steps are left out.
13
+ */
14
+ const mapKeeperPositions = (oldDoc, transactions) => {
15
+ const keepers = /* @__PURE__ */ new Map();
16
+ oldDoc.descendants((node, pos) => {
17
+ if (node.type.name !== "paragraph") return true;
18
+ const id = node.attrs["paraId"];
19
+ if (!isUsableParaId(id) || keepers.has(id)) return false;
20
+ let mapped = pos;
21
+ let deleted = false;
22
+ for (const tr of transactions) {
23
+ const result = tr.mapping.mapResult(mapped);
24
+ if (result.deleted) {
25
+ deleted = true;
26
+ break;
27
+ }
28
+ mapped = result.pos;
29
+ }
30
+ if (!deleted) keepers.set(id, mapped);
31
+ return false;
32
+ });
33
+ return keepers;
34
+ };
35
+ const collectParaIdUpdates = (doc, keeperPositions) => {
36
+ const missing = [];
37
+ const occurrencesById = /* @__PURE__ */ new Map();
10
38
  doc.descendants((node, pos) => {
11
- if (node.type.name !== "paragraph") return;
39
+ if (node.type.name !== "paragraph") return true;
12
40
  const id = node.attrs["paraId"];
13
- if (typeof id !== "string" || id.length === 0 || seen.has(id)) {
14
- let newId = generateHexId();
15
- while (seen.has(newId)) newId = generateHexId();
16
- seen.add(newId);
17
- updates.push({
41
+ if (!isUsableParaId(id)) missing.push({
42
+ pos,
43
+ attrs: node.attrs
44
+ });
45
+ else {
46
+ const occurrences = occurrencesById.get(id) ?? [];
47
+ occurrences.push({
18
48
  pos,
19
- attrs: {
20
- ...node.attrs,
21
- paraId: newId
22
- }
49
+ attrs: node.attrs
23
50
  });
24
- } else seen.add(id);
51
+ occurrencesById.set(id, occurrences);
52
+ }
25
53
  return false;
26
54
  });
55
+ const needFreshId = [...missing];
56
+ for (const [id, occurrences] of occurrencesById) {
57
+ if (occurrences.length === 1) continue;
58
+ const keeperPos = keeperPositions?.get(id);
59
+ const keeper = occurrences.find((occurrence) => occurrence.pos === keeperPos) ?? occurrences[0];
60
+ for (const occurrence of occurrences) if (occurrence !== keeper) needFreshId.push(occurrence);
61
+ }
62
+ const taken = new Set(occurrencesById.keys());
63
+ const updates = [];
64
+ for (const { pos, attrs } of needFreshId) {
65
+ let newId = generateHexId();
66
+ while (taken.has(newId)) newId = generateHexId();
67
+ taken.add(newId);
68
+ updates.push({
69
+ pos,
70
+ attrs: {
71
+ ...attrs,
72
+ paraId: newId
73
+ }
74
+ });
75
+ }
76
+ updates.sort((a, b) => a.pos - b.pos);
27
77
  return updates;
28
78
  };
29
79
  const ensureParaIdsInState = (state) => {
@@ -38,12 +88,14 @@ const ensureParaIdsInState = (state) => {
38
88
  };
39
89
  const createParaIdAllocatorPlugin = () => new Plugin({
40
90
  key: paraIdAllocatorKey,
41
- appendTransaction(transactions, _oldState, newState) {
91
+ appendTransaction(transactions, oldState, newState) {
42
92
  if (!transactions.some((t) => t.docChanged)) return null;
43
- const updates = collectParaIdUpdates(newState.doc);
93
+ const keeperPositions = mapKeeperPositions(oldState.doc, transactions);
94
+ const updates = collectParaIdUpdates(newState.doc, keeperPositions);
44
95
  if (updates.length === 0) return null;
45
96
  const tr = newState.tr;
46
97
  for (const u of updates) tr.setNodeMarkup(u.pos, void 0, u.attrs);
98
+ ignoreTrackedChanges(tr);
47
99
  tr.setMeta(paraIdAllocatorKey, "allocated");
48
100
  tr.setMeta("addToHistory", false);
49
101
  return tr;
@@ -6,7 +6,8 @@ import { ImagePositionAttrs } from "../../schema/nodes.js";
6
6
  //#region src/prosemirror/extensions/nodes/TextBoxExtension.d.ts
7
7
  type TextBoxAttrs = {
8
8
  /** Width in pixels */width?: number; /** Height in pixels */
9
- height?: number; /** Unique identifier */
9
+ height?: number; /** Text fitting behavior */
10
+ autoFit?: document_d_exports.ShapeTextBody["autoFit"]; /** Unique identifier */
10
11
  textBoxId?: string; /** Fill color as CSS color */
11
12
  fillColor?: string; /** Outline width in pixels */
12
13
  outlineWidth?: number; /** Outline color as CSS color */
@@ -15,6 +15,9 @@ function parseTextBoxPosition(raw) {
15
15
  function parseTextBoxWrapType(raw) {
16
16
  for (const value of IMAGE_WRAP_TYPE_VALUES) if (value === raw) return value;
17
17
  }
18
+ function parseTextBoxAutoFit(raw) {
19
+ if (raw === "none" || raw === "normal" || raw === "shape") return raw;
20
+ }
18
21
  const TextBoxExtension = createNodeExtension({
19
22
  name: "textBox",
20
23
  schemaNodeName: "textBox",
@@ -26,6 +29,7 @@ const TextBoxExtension = createNodeExtension({
26
29
  attrs: {
27
30
  width: { default: 200 },
28
31
  height: { default: null },
32
+ autoFit: { default: null },
29
33
  textBoxId: { default: null },
30
34
  fillColor: { default: null },
31
35
  outlineWidth: { default: null },
@@ -55,9 +59,11 @@ const TextBoxExtension = createNodeExtension({
55
59
  const d = dom.dataset;
56
60
  const position = parseTextBoxPosition(d["position"]);
57
61
  const wrapType = parseTextBoxWrapType(d["wrapType"]);
62
+ const autoFit = parseTextBoxAutoFit(d["autoFit"]);
58
63
  return {
59
64
  ...d["width"] ? { width: Number(d["width"]) } : {},
60
65
  ...d["height"] ? { height: Number(d["height"]) } : {},
66
+ ...autoFit ? { autoFit } : {},
61
67
  ...d["textboxId"] ? { textBoxId: d["textboxId"] } : {},
62
68
  ...d["fillColor"] ? { fillColor: d["fillColor"] } : {},
63
69
  ...d["outlineWidth"] ? { outlineWidth: Number(d["outlineWidth"]) } : {},
@@ -85,6 +91,7 @@ const TextBoxExtension = createNodeExtension({
85
91
  const domAttrs = { class: "docx-textbox" };
86
92
  if (attrs.width) domAttrs["data-width"] = String(attrs.width);
87
93
  if (attrs.height) domAttrs["data-height"] = String(attrs.height);
94
+ if (attrs.autoFit) domAttrs["data-auto-fit"] = attrs.autoFit;
88
95
  if (attrs.textBoxId) domAttrs["data-textbox-id"] = attrs.textBoxId;
89
96
  if (attrs.fillColor) domAttrs["data-fill-color"] = attrs.fillColor;
90
97
  if (attrs.outlineWidth) domAttrs["data-outline-width"] = String(attrs.outlineWidth);
@@ -46,7 +46,8 @@ type ParagraphAttrs = {
46
46
  listMarker?: string; /** Whether the list marker is hidden (w:vanish on numbering level rPr) */
47
47
  listMarkerHidden?: boolean; /** Marker font family from numbering level rPr */
48
48
  listMarkerFontFamily?: string; /** Marker font size from numbering level rPr, in points */
49
- listMarkerFontSize?: number;
49
+ listMarkerFontSize?: number; /** Horizontal alignment of the marker around the paragraph's list anchor. */
50
+ listMarkerAlignment?: "left" | "center" | "right";
50
51
  /**
51
52
  * `w:suff` (§17.9.25) — what follows the marker before body text.
52
53
  * `tab` (default) grows the marker to the next tab stop; `space` adds one
@@ -307,7 +308,8 @@ type ShapeAttrs = {
307
308
  */
308
309
  type TextBoxAttrs = {
309
310
  /** Width in pixels */width?: number; /** Height in pixels */
310
- height?: number; /** Unique identifier */
311
+ height?: number; /** Text fitting behavior */
312
+ autoFit?: document_d_exports.ShapeTextBody["autoFit"]; /** Unique identifier */
311
313
  textBoxId?: string; /** Fill color as CSS color */
312
314
  fillColor?: string; /** Outline width in pixels */
313
315
  outlineWidth?: number; /** Outline color as CSS color */
@@ -86,6 +86,7 @@ function listAttrsFromResolvedStyle(resolved, numbering) {
86
86
  listMarkerHidden: rendering?.markerHidden ?? null,
87
87
  listMarkerFontFamily: rendering?.markerFontFamily ?? null,
88
88
  listMarkerFontSize: rendering?.markerFontSize ?? null,
89
+ listMarkerAlignment: rendering?.markerAlignment ?? null,
89
90
  listMarkerSuffix: rendering?.markerSuffix ?? null,
90
91
  listMarkerAllCaps: rendering?.markerAllCaps ?? null,
91
92
  listImplicitChildLevelAdvances: null,