@stll/folio-core 0.25.3 → 0.25.5

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 (28) hide show
  1. package/dist/docx/compatibility.js +2 -2
  2. package/dist/docx/runParser.js +3 -2
  3. package/dist/docx/serializer/runSerializer.js +5 -1
  4. package/dist/docx/shapeParser.js +26 -3
  5. package/dist/layout-bridge/convert/toFlowBlocks.js +2 -7
  6. package/dist/layout-engine/measure/cache.d.ts +1 -4
  7. package/dist/layout-engine/measure/cache.js +31 -34
  8. package/dist/layout-painter/renderParagraph.js +26 -18
  9. package/dist/prosemirror/attrs/index.js +9 -1
  10. package/dist/prosemirror/commands/image.js +2 -1
  11. package/dist/prosemirror/conversion/fromProseDoc.js +26 -6
  12. package/dist/prosemirror/conversion/toProseDoc.js +17 -6
  13. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +2 -0
  14. package/dist/prosemirror/extensions/core/ParagraphExtension.js +15 -3
  15. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +6 -1
  16. package/dist/prosemirror/extensions/nodes/ImageExtension.js +1 -0
  17. package/dist/prosemirror/extensions/nodes/ShapeExtension.d.ts +7 -1
  18. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +55 -3
  19. package/dist/prosemirror/schema/nodes.d.ts +6 -0
  20. package/dist/prosemirror/shapeGeometryAdjustments.d.ts +5 -0
  21. package/dist/prosemirror/shapeGeometryAdjustments.js +24 -0
  22. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +5 -1
  23. package/dist/prosemirror/styles/resolvedStyleAttrs.js +4 -2
  24. package/dist/types/content.d.ts +2 -2
  25. package/dist/types/index.d.ts +3 -1
  26. package/dist/utils/tableOfContentsStyle.d.ts +9 -0
  27. package/dist/utils/tableOfContentsStyle.js +14 -0
  28. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
1
+ import { DOCX_CONFORMANCE_CLASSES, DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
2
2
  //#region src/docx/compatibility.ts
3
3
  const resolveCompatibilityContext = (doc, options) => ({
4
4
  host: options.host ?? "unknown",
@@ -146,7 +146,7 @@ function inspectHyperlink(hyperlink, context) {
146
146
  });
147
147
  }
148
148
  function inspectRun(run, context) {
149
- for (const [contentIndex, content] of run.content.entries()) if (content.type === "drawing" && content.rawXml) context.record({
149
+ for (const [contentIndex, content] of run.content.entries()) if (content.type === "drawing" && content.rawXml && content.rawXmlMode !== DRAWING_RAW_XML_MODES.PRESERVE_ONLY) context.record({
150
150
  ...context.blockId === void 0 ? {} : { blockId: context.blockId },
151
151
  part: context.part,
152
152
  path: `${context.path}.content[${contentIndex}]`
@@ -7,7 +7,7 @@ import { requiresXmlSpacePreserve } from "./textWhitespace.js";
7
7
  import { resolveThemeFontRef } from "./themeParser.js";
8
8
  import { parseVmlImageContent } from "./vmlImageParser.js";
9
9
  import { cloneWithXmlnsDeclarations, elementToXml, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
10
- import { normalizeRevisionId } from "@stll/docx-core/model";
10
+ import { DRAWING_RAW_XML_MODES, normalizeRevisionId } from "@stll/docx-core/model";
11
11
  //#region src/docx/runParser.ts
12
12
  /**
13
13
  * Sanity cap on `w:lang` `@w:val`/`@w:eastAsia`/`@w:bidi` tag length. BCP-47
@@ -500,7 +500,8 @@ function parseDrawingContent(element, rels, media) {
500
500
  },
501
501
  wrap: { type: "inline" }
502
502
  },
503
- rawXml: elementToXml(element)
503
+ rawXml: elementToXml(element),
504
+ rawXmlMode: DRAWING_RAW_XML_MODES.PRESERVE_ONLY
504
505
  };
505
506
  const shape = parseShapeFromDrawing(element);
506
507
  if (shape) return {
@@ -448,6 +448,10 @@ function serializeDrawingContent(content) {
448
448
  function serializeShapeTextBody(blocks) {
449
449
  return blocks.map((block) => block.type === "paragraph" ? serializeParagraph(block) : serializeTable(block, serializeParagraph)).join("");
450
450
  }
451
+ function serializeGeometryAdjustments(shape) {
452
+ if (!shape.geometryAdjustments || shape.geometryAdjustments.length === 0) return "<a:avLst/>";
453
+ return `<a:avLst>${shape.geometryAdjustments.map(({ name, formula }) => `<a:gd name="${escapeXml(name)}" fmla="${escapeXml(formula)}"/>`).join("")}</a:avLst>`;
454
+ }
451
455
  /**
452
456
  * Serialize shape content to full DrawingML XML (wps:wsp inside w:drawing)
453
457
  */
@@ -470,7 +474,7 @@ function serializeShapeContent(content) {
470
474
  "<a:off x=\"0\" y=\"0\"/>",
471
475
  `<a:ext cx="${intAttr(cx)}" cy="${intAttr(cy)}"/>`,
472
476
  "</a:xfrm>",
473
- `<a:prstGeom prst="${shape.shapeType === "textBox" ? "rect" : shape.shapeType}"><a:avLst/></a:prstGeom>`,
477
+ `<a:prstGeom prst="${shape.shapeType === "textBox" ? "rect" : shape.shapeType}">${serializeGeometryAdjustments(shape)}</a:prstGeom>`,
474
478
  serializeFill(shape.fill),
475
479
  serializeOutline(shape.outline),
476
480
  "</wps:spPr>"
@@ -1,6 +1,6 @@
1
1
  import { parseAnchorPosition, parseAnchorWrap, parseFill, parseOutline } from "./drawingUtils.js";
2
2
  import { ShapeTypeSchema, narrowEnum } from "./parserEnums.js";
3
- import { findAllDeep, findChildByLocalName, getAttribute, parseNumericAttribute } from "./xmlParser.js";
3
+ import { findAllDeep, findChildByLocalName, findChildren, getAttribute, getChildElements, parseNumericAttribute } from "./xmlParser.js";
4
4
  //#region src/docx/shapeParser.ts
5
5
  /** Convert OOXML rotation (1/60000ths of a degree) to degrees. */
6
6
  function rotToDegrees(rot) {
@@ -56,8 +56,29 @@ function hasUnsupportedGeometry(spPr) {
56
56
  if (!spPr) return false;
57
57
  const prstGeom = findChildByLocalName(spPr, "prstGeom");
58
58
  if (!prstGeom) return findChildByLocalName(spPr, "custGeom") !== null;
59
- if (findChildByLocalName(prstGeom, "avLst")?.elements?.some((child) => child.type === "element")) return true;
60
- return narrowEnum(getAttribute(prstGeom, null, "prst"), ShapeTypeSchema) === void 0;
59
+ const avLst = findChildByLocalName(prstGeom, "avLst");
60
+ const prst = getAttribute(prstGeom, null, "prst");
61
+ if (narrowEnum(prst, ShapeTypeSchema) === void 0) return true;
62
+ const adjustmentChildren = avLst ? getChildElements(avLst) : [];
63
+ if (adjustmentChildren.length === 0) return false;
64
+ const adjustments = findChildren(avLst, "a", "gd");
65
+ const adjustmentNames = new Set(adjustments.map((adjustment) => getAttribute(adjustment, null, "name")));
66
+ return !(prst === "rightBrace" && adjustments.length === 2 && adjustmentChildren.length === adjustments.length && adjustmentNames.size === 2 && adjustmentNames.has("adj1") && adjustmentNames.has("adj2") && adjustments.every((adjustment) => /^val\s+-?\d+$/u.test(getAttribute(adjustment, null, "fmla") ?? "")));
67
+ }
68
+ function parseGeometryAdjustments(spPr) {
69
+ const prstGeom = spPr ? findChildByLocalName(spPr, "prstGeom") : null;
70
+ const avLst = prstGeom ? findChildByLocalName(prstGeom, "avLst") : null;
71
+ if (!avLst) return;
72
+ const adjustments = [];
73
+ for (const adjustment of findChildren(avLst, "a", "gd")) {
74
+ const name = getAttribute(adjustment, null, "name");
75
+ const formula = getAttribute(adjustment, null, "fmla");
76
+ if (name !== null && formula !== null) adjustments.push({
77
+ name,
78
+ formula
79
+ });
80
+ }
81
+ return adjustments.length === 0 ? void 0 : adjustments;
61
82
  }
62
83
  function hasUnsupportedRgbColorModifiers(spPr) {
63
84
  for (const color of findAllDeep(spPr, "a", "srgbClr")) if (color.elements?.some((child) => child.type === "element")) return true;
@@ -94,6 +115,8 @@ function parseShape(node) {
94
115
  shapeType,
95
116
  size
96
117
  };
118
+ const geometryAdjustments = parseGeometryAdjustments(spPr);
119
+ if (geometryAdjustments !== void 0) shape.geometryAdjustments = geometryAdjustments;
97
120
  if (id !== void 0) shape.id = id;
98
121
  if (name !== void 0) shape.name = name;
99
122
  if (fill !== void 0) shape.fill = fill;
@@ -15,6 +15,7 @@ import { resolveColor, resolveHighlightToCss } from "../../utils/colorResolver.j
15
15
  import { resolveThemeFont } from "../../utils/fontResolver.js";
16
16
  import { resolveShadingFill } from "../../utils/formatToStyle.js";
17
17
  import { decodeOoxmlSymbolCharacter } from "../../utils/ooxmlSymbol.js";
18
+ import { tableOfContentsStyleLevel } from "../../utils/tableOfContentsStyle.js";
18
19
  import { AUTO_PARAGRAPH_SPACING_PX, halfPointsToPixels, halfPointsToPoints, pointsToPixels } from "../../utils/units.js";
19
20
  import { getColumns } from "../sectionColumns.js";
20
21
  import { groupParagraphFrames } from "./paragraphFrames.js";
@@ -454,12 +455,6 @@ function buildImageRun(attrs, constrained, pmStart, pmEnd, trackedChange) {
454
455
  return run;
455
456
  }
456
457
  /**
457
- * Paragraph styleId pattern used by Word for TOC entries (TOC, TOC1, TOC2, …).
458
- * Hyperlinks inside these paragraphs must render in the paragraph's own colour,
459
- * not the Hyperlink character style — see {@link stripTocHyperlinkStyle}.
460
- */
461
- const TOC_STYLE_ID = /^TOC\d*$/iu;
462
- /**
463
458
  * In TOC paragraphs, strip the resolved Hyperlink character-style colour and
464
459
  * underline so the painter's link fallback doesn't fire. The PM doc keeps the
465
460
  * original marks so copy/paste out of a TOC still carries the Hyperlink
@@ -484,7 +479,7 @@ function paragraphToRuns(node, startPos, _options) {
484
479
  const pmAttrs = expectParagraphAttrs(node);
485
480
  const paraDefaults = paragraphRunDefaults(pmAttrs, theme);
486
481
  const paragraphStyleId = pmAttrs.styleId;
487
- const inTocParagraph = typeof paragraphStyleId === "string" && TOC_STYLE_ID.test(paragraphStyleId);
482
+ const inTocParagraph = pmAttrs._tableOfContentsLevel !== void 0 || tableOfContentsStyleLevel({ styleId: paragraphStyleId }) !== void 0;
488
483
  let leadingRenderedPageBreakPending = pmAttrs.renderedPageBreakBefore === true;
489
484
  function pushRunsForChild(child, childPos) {
490
485
  if (child.type.name === "renderedPageBreak") {
@@ -54,10 +54,7 @@ declare function setFontCacheSize(size: number): void;
54
54
  * Get current font metrics cache size
55
55
  */
56
56
  declare function getFontCacheSize(): number;
57
- /**
58
- * Generate a simple hash for a paragraph block
59
- * Used as cache key to identify identical content
60
- */
57
+ /** Serialize the complete paragraph measurement contract into a cache key. */
61
58
  declare function hashParagraphBlock(block: ParagraphBlock): string;
62
59
  /**
63
60
  * Get cached paragraph measurement or return undefined
@@ -1,4 +1,3 @@
1
- import { lineBreakPolicyCacheParts } from "./effectiveLineBreakPolicy.js";
2
1
  import { getLineBreakProviderGeneration } from "./lineBreakProvider.js";
3
2
  import { clearFontResolvedCache } from "./measureHelpers.js";
4
3
  //#region src/layout-engine/measure/cache.ts
@@ -164,40 +163,38 @@ let paragraphCacheMaxSize = 5e3;
164
163
  * Key format: block content hash
165
164
  */
166
165
  const paragraphMeasureCache = /* @__PURE__ */ new Map();
167
- /**
168
- * Generate a simple hash for a paragraph block
169
- * Used as cache key to identify identical content
170
- */
171
- function hashParagraphBlock(block) {
172
- const parts = [`lbp:${getLineBreakProviderGeneration()}`];
173
- for (const run of block.runs) if (run.kind === "text") parts.push(`t:${run.text}|${run.fontFamily}|${run.eastAsiaFontFamily}|${run.complexScriptFontFamily}|${run.fontSize}|${run.complexScriptFontSize}|${run.bold}|${run.complexScriptBold}|${run.italic}|${run.complexScriptItalic}|${run.forceComplexScript}|${run.allCaps}|${run.smallCaps}|${run.horizontalScale}|${run.letterSpacing}|${run.language?.val}|${run.language?.eastAsia}|${run.language?.bidi}`);
174
- else if (run.kind === "tab") parts.push(`tab:${run.width}`);
175
- else if (run.kind === "image") parts.push(`img:${run.width}x${run.height}:${run.exactLineHeight === true ? "exact" : "text"}`);
176
- else if (run.kind === "lineBreak") parts.push("br");
177
- const attrs = block.attrs;
178
- if (attrs) {
179
- if (attrs.alignment) parts.push(`align:${attrs.alignment}`);
180
- if (attrs.outlineLevel !== void 0) parts.push(`outline:${attrs.outlineLevel}`);
181
- if (attrs.indent) parts.push(`indent:${attrs.indent.left}|${attrs.indent.right}|${attrs.indent.firstLine}|${attrs.indent.hanging}`);
182
- if (attrs.spacing) parts.push(`spacing:${attrs.spacing.before}|${attrs.spacing.after}|${attrs.spacing.line}|${attrs.spacing.lineRule}`);
183
- if (attrs.defaultFontSize != null) parts.push(`dfs:${attrs.defaultFontSize}`);
184
- if (attrs.defaultFontFamily != null) parts.push(`dff:${attrs.defaultFontFamily}`);
185
- if (attrs.suppressEmptyParagraphHeight) parts.push("sup");
186
- if (attrs.reserveEmptyOutlineHeight) parts.push("outline-empty-reserve");
187
- if (attrs.documentGridLinePitch !== void 0) parts.push(`documentGrid:${attrs.documentGridLinePitch}|${attrs.snapToGrid !== false}`);
188
- if (attrs.justificationCompatibility) parts.push(`justify-compat:${attrs.justificationCompatibility.type}`);
189
- if (attrs.listMarker !== void 0) {
190
- const marker = attrs.listMarkerFormatting;
191
- parts.push(`marker:${attrs.listMarker}|${attrs.listMarkerHidden}|${attrs.listMarkerAlignment}|${attrs.listMarkerSuffix}|${marker?.fontFamily}|${marker?.eastAsiaFontFamily}|${marker?.complexScriptFontFamily}|${marker?.fontSize}|${marker?.complexScriptFontSize}|${marker?.bold}|${marker?.complexScriptBold}|${marker?.italic}|${marker?.complexScriptItalic}|${marker?.rtl}|${marker?.forceComplexScript}`);
192
- }
193
- parts.push(...lineBreakPolicyCacheParts(attrs));
194
- const borders = attrs.borders;
195
- if (borders) {
196
- const signature = (border) => border ? `${border.width ?? ""},${border.style ?? ""},${border.color ?? ""}` : "";
197
- parts.push(`bdr:${signature(borders.top)}|${signature(borders.bottom)}|${signature(borders.left)}|${signature(borders.right)}`);
198
- }
166
+ /** Keep image payloads bounded while classifying every field as measured or intentionally ignored. */
167
+ const imageMeasureCacheInput = (run) => ({
168
+ kind: run.kind,
169
+ width: run.width,
170
+ height: run.height,
171
+ transform: run.transform,
172
+ wrapType: run.wrapType,
173
+ displayMode: run.displayMode,
174
+ distTop: run.distTop,
175
+ distBottom: run.distBottom,
176
+ exactLineHeight: run.exactLineHeight,
177
+ positioned: run.position !== void 0
178
+ });
179
+ const runMeasureCacheInput = (run) => {
180
+ switch (run.kind) {
181
+ case "text":
182
+ case "tab":
183
+ case "lineBreak":
184
+ case "renderedPageBreak":
185
+ case "field":
186
+ case "math": return run;
187
+ case "image": return imageMeasureCacheInput(run);
188
+ default: return run;
199
189
  }
200
- return parts.join("||");
190
+ };
191
+ /** Serialize the complete paragraph measurement contract into a cache key. */
192
+ function hashParagraphBlock(block) {
193
+ return JSON.stringify({
194
+ lineBreakProviderGeneration: getLineBreakProviderGeneration(),
195
+ attrs: block.attrs,
196
+ runs: block.runs.map(runMeasureCacheInput)
197
+ });
201
198
  }
202
199
  /**
203
200
  * Evict oldest entries if paragraph cache exceeds max size
@@ -1454,9 +1454,10 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
1454
1454
  });
1455
1455
  const isFlexLine = lineEl.dataset["flexLine"] === "true";
1456
1456
  const lineMarginLeft = Math.min(indentLeft, 0) + lineLeftOffset;
1457
+ const lineMarginRight = Math.min(indentRight, 0) + lineRightOffset;
1457
1458
  if (lineMarginLeft !== 0 || lineRightOffset > 0 || indentLeft < 0 || indentRight < 0) {
1458
1459
  lineEl.style.marginLeft = `${lineMarginLeft}px`;
1459
- if (lineRightOffset > 0) lineEl.style.marginRight = `${lineRightOffset}px`;
1460
+ if (lineMarginRight !== 0) lineEl.style.marginRight = `${lineMarginRight}px`;
1460
1461
  const constrainedWidth = lineAvailableWidth - lineLeftOffset - lineRightOffset;
1461
1462
  if (constrainedWidth > 0) lineEl.style.width = `${constrainedWidth}px`;
1462
1463
  }
@@ -1476,27 +1477,33 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
1476
1477
  if (isFirstLine && block.attrs?.listMarker && !block.attrs.listMarkerHidden) {
1477
1478
  const hanging = indent?.hanging ?? 0;
1478
1479
  const firstLine = indent?.firstLine ?? 0;
1479
- const markerStart = hanging > 0 ? indentLeft - hanging : indentLeft + firstLine;
1480
- lineEl.style.paddingLeft = `${Math.max(0, markerStart)}px`;
1480
+ const markerPhysicalStart = isRtl ? "right" : "left";
1481
+ const markerIndent = markerPhysicalStart === "right" ? indentRight : indentLeft;
1482
+ const markerStart = hanging > 0 ? markerIndent - hanging : markerIndent + firstLine;
1483
+ const logicalMarkerVisualOffset = getListMarkerVisualOffset(block);
1484
+ if (markerPhysicalStart === "right") lineEl.style.paddingRight = `${Math.max(0, markerStart)}px`;
1485
+ else lineEl.style.paddingLeft = `${Math.max(0, markerStart)}px`;
1481
1486
  lineEl.style.textIndent = "0";
1482
- const marker = renderListMarker(block.attrs.listMarker, getListMarkerInlineWidth(block), getListMarkerVisualOffset(block), doc, resolveListMarkerFont(block), block.attrs.listMarkerRevision, block.attrs.listMarkerSecondSlotOffsetTwips);
1483
- const markerMarginLeft = markerStart - Math.min(indentLeft, 0);
1484
- if (markerMarginLeft < 0) marker.style.marginLeft = `${markerMarginLeft}px`;
1487
+ const marker = renderListMarker({
1488
+ marker: block.attrs.listMarker,
1489
+ inlineWidth: getListMarkerInlineWidth(block),
1490
+ visualOffset: markerPhysicalStart === "right" ? -logicalMarkerVisualOffset : logicalMarkerVisualOffset,
1491
+ doc,
1492
+ formatting: resolveListMarkerFont(block),
1493
+ ...block.attrs.listMarkerRevision === void 0 ? {} : { revision: block.attrs.listMarkerRevision },
1494
+ ...block.attrs.listMarkerSecondSlotOffsetTwips === void 0 ? {} : { secondSlotOffsetTwips: block.attrs.listMarkerSecondSlotOffsetTwips },
1495
+ physicalStart: markerPhysicalStart
1496
+ });
1497
+ const markerMarginLeft = markerStart - (markerPhysicalStart === "right" ? Math.min(indentRight, 0) : Math.min(indentLeft, 0));
1498
+ if (markerMarginLeft < 0) if (markerPhysicalStart === "right") marker.style.marginRight = `${markerMarginLeft}px`;
1499
+ else marker.style.marginLeft = `${markerMarginLeft}px`;
1485
1500
  lineEl.prepend(marker);
1486
1501
  }
1487
1502
  fragmentEl.append(lineEl);
1488
1503
  }
1489
1504
  return fragmentEl;
1490
1505
  }
1491
- /**
1492
- * Render a list marker element as an inline-block at the start of the first
1493
- * body line. `inlineWidth` (from `getListMarkerInlineWidth`) sizes the marker
1494
- * so the body text aligns at the next tab stop per ECMA-376 §17.9.25 —
1495
- * this honours `w:suff` (`tab` / `space` / `nothing`) and the document's
1496
- * tab grid. Long markers like "1.1.1." therefore grow to the next stop
1497
- * instead of butting against the body text.
1498
- */
1499
- function renderListMarker(marker, inlineWidth, visualOffset, doc, formatting, revision, secondSlotOffsetTwips) {
1506
+ function renderListMarker({ marker, inlineWidth, visualOffset, doc, formatting, revision, secondSlotOffsetTwips, physicalStart }) {
1500
1507
  const span = doc.createElement("span");
1501
1508
  span.className = "layout-list-marker";
1502
1509
  span.style.display = "inline-block";
@@ -1505,8 +1512,8 @@ function renderListMarker(marker, inlineWidth, visualOffset, doc, formatting, re
1505
1512
  if (formatting.bold !== void 0) span.style.fontWeight = formatting.bold ? "700" : "normal";
1506
1513
  if (formatting.italic !== void 0) span.style.fontStyle = formatting.italic ? "italic" : "normal";
1507
1514
  if (formatting.rtl !== void 0) span.dir = formatting.rtl ? "rtl" : "ltr";
1508
- span.style.textAlign = "left";
1509
- span.style.textAlignLast = "left";
1515
+ span.style.textAlign = physicalStart;
1516
+ span.style.textAlignLast = physicalStart;
1510
1517
  span.style.boxSizing = "border-box";
1511
1518
  span.style.width = `${inlineWidth}px`;
1512
1519
  if (visualOffset !== 0) span.style.transform = `translateX(${visualOffset}px)`;
@@ -1540,7 +1547,8 @@ function renderListMarker(marker, inlineWidth, visualOffset, doc, formatting, re
1540
1547
  secondSlot.style.display = "inline-block";
1541
1548
  span.style.position = "relative";
1542
1549
  secondSlot.style.position = "absolute";
1543
- secondSlot.style.left = `${secondSlotOffsetTwips / 15}px`;
1550
+ if (physicalStart === "right") secondSlot.style.right = `${secondSlotOffsetTwips / 15}px`;
1551
+ else secondSlot.style.left = `${secondSlotOffsetTwips / 15}px`;
1544
1552
  secondSlot.style.top = "0";
1545
1553
  span.append(firstSlot, secondSlot);
1546
1554
  return span;
@@ -2,7 +2,7 @@ import { EMPHASIS_MARK_VALUES, FIELD_TYPE_VALUES, FONT_HINT_VALUES, FONT_THEME_V
2
2
  import { isParagraphDirection } from "../paragraphDirection.js";
3
3
  import { TRACKED_CHANGE_PROVENANCE_VALUES } from "../schema/marks.js";
4
4
  import { panic } from "better-result";
5
- import { isOoxmlSymbolCharacter } from "@stll/docx-core/model";
5
+ import { DRAWING_RAW_XML_MODES, isOoxmlSymbolCharacter } from "@stll/docx-core/model";
6
6
  //#region src/prosemirror/attrs/index.ts
7
7
  const SECTION_BREAK_TYPES = [
8
8
  "nextPage",
@@ -143,6 +143,7 @@ const readParagraphAttrs = (node) => {
143
143
  optionalString(attrs, "textId", "paragraph.attrs.textId", issues);
144
144
  optionalOneOf(attrs, "alignment", "paragraph.attrs.alignment", issues, PARAGRAPH_ALIGNMENT_VALUES);
145
145
  optionalString(attrs, "styleId", "paragraph.attrs.styleId", issues);
146
+ optionalNumber(attrs, "_tableOfContentsLevel", "paragraph.attrs._tableOfContentsLevel", issues);
146
147
  optionalBoolean(attrs, "kinsoku", "paragraph.attrs.kinsoku", issues);
147
148
  optionalBoolean(attrs, "overflowPunctuation", "paragraph.attrs.overflowPunctuation", issues);
148
149
  optionalBoolean(attrs, "suppressAutoHyphens", "paragraph.attrs.suppressAutoHyphens", issues);
@@ -357,6 +358,12 @@ const readImageAttrs = (node) => {
357
358
  optionalString(attrs, "hlinkHref", "image.attrs.hlinkHref", issues);
358
359
  optionalString(attrs, "hlinkRId", "image.attrs.hlinkRId", issues);
359
360
  optionalString(attrs, "_docxRawXml", "image.attrs._docxRawXml", issues);
361
+ optionalOneOf(attrs, "_docxRawXmlMode", "image.attrs._docxRawXmlMode", issues, Object.values(DRAWING_RAW_XML_MODES));
362
+ const rawXml = attrs["_docxRawXml"];
363
+ if (attrs["_docxRawXmlMode"] === DRAWING_RAW_XML_MODES.PRESERVE_ONLY && (typeof rawXml !== "string" || rawXml.trim().length === 0)) issues.push({
364
+ path: "image.attrs._docxRawXml",
365
+ message: "Preservation-only drawings require raw XML."
366
+ });
360
367
  optionalBoolean(attrs, "_docxObjectPreview", "image.attrs._docxObjectPreview", issues);
361
368
  return attrsResult(attrs, issues);
362
369
  };
@@ -422,6 +429,7 @@ const readShapeAttrs = (node) => {
422
429
  const issues = [];
423
430
  expectNodeType(node, "shape", issues);
424
431
  optionalString(attrs, "shapeType", "shape.attrs.shapeType", issues);
432
+ optionalString(attrs, "geometryAdjustments", "shape.attrs.geometryAdjustments", issues);
425
433
  optionalString(attrs, "shapeId", "shape.attrs.shapeId", issues);
426
434
  optionalNumber(attrs, "width", "shape.attrs.width", issues);
427
435
  optionalNumber(attrs, "height", "shape.attrs.height", issues);
@@ -105,7 +105,8 @@ const constrainImageSize = (width, height) => {
105
105
  };
106
106
  const imageNodeAt = (view, pos) => {
107
107
  const node = view.state.doc.nodeAt(pos);
108
- return node && node.type.name === "image" ? node : null;
108
+ if (!node || node.type.name !== "image") return null;
109
+ return expectImageAttrs(node)._docxRawXmlMode ? null : node;
109
110
  };
110
111
  /** Change the wrap mode of the image at `pos`. Returns whether it applied. */
111
112
  const applyImageWrapType = (view, pos, wrapType) => {
@@ -7,13 +7,27 @@ import { autospacingMatchesBase, hasAutospacingBaseSide } from "../autospacingBa
7
7
  import { applyRunFormattingOverrideAttrs } from "../extensions/marks/RunFormattingOverrideExtension.js";
8
8
  import { directionToBidi } from "../paragraphDirection.js";
9
9
  import { RUN_FORMATTING_MARK_NAMES } from "../runFormattingMarkNames.js";
10
+ import { parseShapeGeometryAdjustments } from "../shapeGeometryAdjustments.js";
10
11
  import { expectTextBoxAnchorAttrs } from "../textBoxAnchorAttrs.js";
11
12
  import { assertValidProseMirrorDocument } from "../validation.js";
12
13
  import { runShadingAttrsToShading } from "./runShadingMark.js";
13
14
  import { decodeSdtListItems, sdtPropertiesFromAttrs, sdtPropertiesMatchAttrs } from "./sdtAttrs.js";
14
15
  import { textFormattingToMarks } from "./toProseDoc.js";
16
+ import { panic } from "better-result";
17
+ import { DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
15
18
  import { Fragment } from "prosemirror-model";
16
19
  //#region src/prosemirror/conversion/fromProseDoc.ts
20
+ /**
21
+ * ProseMirror to Document Conversion
22
+ *
23
+ * Converts a ProseMirror document back to our Document type.
24
+ * This enables round-trip editing: DOCX -> Document -> PM -> Document -> DOCX
25
+ *
26
+ * Key responsibilities:
27
+ * - Coalesce consecutive text with same marks into single Runs
28
+ * - Preserve paragraph attributes (paraId, textId, formatting)
29
+ * - Handle marks -> TextFormatting conversion
30
+ */
17
31
  function normalizeShapeOutlineStyle(style) {
18
32
  if (!style) return;
19
33
  if (style in OUTLINE_STYLE_CSS_ALIASES) return OUTLINE_STYLE_CSS_ALIASES[style];
@@ -1256,14 +1270,18 @@ function createImageRun(node) {
1256
1270
  if (cropLeft !== void 0) crop.left = cropLeft;
1257
1271
  image.crop = crop;
1258
1272
  }
1259
- const drawingContent = {
1260
- type: "drawing",
1261
- image
1262
- };
1263
- if (attrs._docxRawXml) drawingContent.rawXml = attrs._docxRawXml;
1264
1273
  return {
1265
1274
  type: "run",
1266
- content: [drawingContent]
1275
+ content: [attrs._docxRawXmlMode === DRAWING_RAW_XML_MODES.PRESERVE_ONLY ? {
1276
+ type: "drawing",
1277
+ image,
1278
+ rawXml: attrs._docxRawXml ?? panic("Preservation-only ProseMirror image attrs must include raw XML."),
1279
+ rawXmlMode: DRAWING_RAW_XML_MODES.PRESERVE_ONLY
1280
+ } : {
1281
+ type: "drawing",
1282
+ image,
1283
+ ...attrs._docxRawXml ? { rawXml: attrs._docxRawXml } : {}
1284
+ }]
1267
1285
  };
1268
1286
  }
1269
1287
  /**
@@ -1280,6 +1298,8 @@ function createShapeRun(node) {
1280
1298
  }
1281
1299
  };
1282
1300
  if (attrs.shapeId) shape.id = attrs.shapeId;
1301
+ const geometryAdjustments = parseShapeGeometryAdjustments(attrs.geometryAdjustments);
1302
+ if (geometryAdjustments !== void 0) shape.geometryAdjustments = geometryAdjustments;
1283
1303
  const shapeTransform = parseTransformAttr(attrs.transform);
1284
1304
  if (shapeTransform) shape.transform = shapeTransform;
1285
1305
  const wrap = { type: attrs.wrapType || "inline" };
@@ -1,6 +1,7 @@
1
1
  import { resolveColorValueToHex } from "../../docx/drawingUtils.js";
2
2
  import { createStyleEngine } from "../../style-engine/styleEngine.js";
3
3
  import { mergeParagraphFormatting, mergeParagraphTabStops } from "../../utils/paragraphFormattingMerge.js";
4
+ import { tableOfContentsStyleLevel } from "../../utils/tableOfContentsStyle.js";
4
5
  import { mergeTextFormatting } from "../../utils/textFormattingMerge.js";
5
6
  import { emuToPixels } from "../../utils/units.js";
6
7
  import { setAutospacingBaseValue } from "../autospacingBase.js";
@@ -13,8 +14,6 @@ import { marksToTextFormatting } from "./fromProseDoc.js";
13
14
  import { shadingToRunShadingAttrs } from "./runShadingMark.js";
14
15
  import { sdtAttrsFromProperties } from "./sdtAttrs.js";
15
16
  //#region src/prosemirror/conversion/toProseDoc.ts
16
- const TOC_STYLE_ID = /^TOC\d*$/iu;
17
- const isTocStyleId = (styleId) => styleId !== void 0 && TOC_STYLE_ID.test(styleId);
18
17
  /**
19
18
  * Build a `nextTextBoxGroupId()` generator salted with a random per-load
20
19
  * nonce, so minted text-box anchor ids (`<salt>:<group>:<index>`) are unique
@@ -128,7 +127,7 @@ function convertBlockSdt(blockSdt, convertBlocks) {
128
127
  */
129
128
  function convertParagraph(paragraph, styleResolver, nextHyperlinkInstanceIndex, activeCommentIds, extraRunFormatting, tableParagraphOverlay, textBoxAnchors) {
130
129
  const attrs = paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOverlay);
131
- const isTocParagraph = isTocStyleId(paragraph.formatting?.styleId);
130
+ const isTocParagraph = attrs._tableOfContentsLevel !== void 0;
132
131
  const inlineNodes = [];
133
132
  let inlineOffset = 0;
134
133
  let bookmarksArr;
@@ -280,10 +279,16 @@ function canCarryTrackedRunMark(node, markType) {
280
279
  function paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOverlay) {
281
280
  const formatting = paragraph.formatting;
282
281
  const styleId = formatting?.styleId;
282
+ const styleName = styleId ? styleResolver?.getStyle(styleId)?.name : void 0;
283
+ const tableOfContentsLevel = tableOfContentsStyleLevel({
284
+ styleId,
285
+ ...styleName ? { styleName } : {}
286
+ });
283
287
  const attrs = {};
284
288
  if (paragraph.paraId) attrs.paraId = paragraph.paraId;
285
289
  if (paragraph.textId) attrs.textId = paragraph.textId;
286
290
  if (styleId) attrs.styleId = styleId;
291
+ if (tableOfContentsLevel !== void 0) attrs._tableOfContentsLevel = tableOfContentsLevel;
287
292
  if (formatting?.numPr) attrs.numPr = formatting.numPr;
288
293
  if (formatting?.numPrFromStyle) attrs.numPrFromStyle = formatting.numPrFromStyle;
289
294
  if (paragraph.listRendering?.numFmt) attrs.listNumFmt = paragraph.listRendering.numFmt;
@@ -350,7 +355,7 @@ function paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOver
350
355
  set("runInWithNext", formatting?.runInWithNext ?? stylePpr?.runInWithNext);
351
356
  set("outlineLevel", formatting?.outlineLevel ?? stylePpr?.outlineLevel);
352
357
  set("direction", directionFromBidi(formatting?.bidi ?? stylePpr?.bidi));
353
- set("defaultTextFormatting", resolveParagraphDefaultTextFormatting(styleId, formatting, styleResolver, { includeParagraphMarkRunProperties: !isTocStyleId(styleId) }));
358
+ set("defaultTextFormatting", resolveParagraphDefaultTextFormatting(styleId, formatting, styleResolver, { includeParagraphMarkRunProperties: tableOfContentsLevel === void 0 }));
354
359
  if (!formatting?.numPr && stylePpr?.numPr && stylePpr.numPr.numId !== 0) {
355
360
  attrs.numPr = stylePpr.numPr;
356
361
  attrs.numPrFromStyle = stylePpr.numPr;
@@ -1050,7 +1055,11 @@ function convertRunContent(content, marks, formatting, textBoxAnchors) {
1050
1055
  return [];
1051
1056
  case "renderedPageBreak": return [schema.node("renderedPageBreak").mark(marks)];
1052
1057
  case "tab": return [schema.node("tab", content.positional ? { positional: content.positional } : void 0).mark(marks)];
1053
- case "drawing": return [withHyperlinkBoundaryMarks(convertImage(content.image, content.rawXml), marks)];
1058
+ case "drawing": return [withHyperlinkBoundaryMarks(convertImage({
1059
+ image: content.image,
1060
+ rawXml: content.rawXml,
1061
+ rawXmlMode: content.rawXmlMode
1062
+ }), marks)];
1054
1063
  case "shape": {
1055
1064
  const shp = content.shape;
1056
1065
  if (shp.textBody) {
@@ -1089,7 +1098,7 @@ function withHyperlinkBoundaryMarks(node, marks) {
1089
1098
  if (!marks.some((mark) => mark.type.name === "hyperlink")) return node;
1090
1099
  return node.mark(marks);
1091
1100
  }
1092
- function convertImage(image, rawXml) {
1101
+ function convertImage({ image, rawXml, rawXmlMode }) {
1093
1102
  const imageSize = image.size;
1094
1103
  const widthPx = imageSize?.width ? emuToPixels(imageSize.width) : void 0;
1095
1104
  const heightPx = imageSize?.height ? emuToPixels(imageSize.height) : void 0;
@@ -1190,6 +1199,7 @@ function convertImage(image, rawXml) {
1190
1199
  hlinkHref: image.hlinkHref,
1191
1200
  hlinkRId: image.hlinkRId,
1192
1201
  _docxRawXml: rawXml,
1202
+ _docxRawXmlMode: rawXmlMode,
1193
1203
  _docxObjectPreview: rawXml !== void 0 && /<(?:[A-Za-z_][\w.-]*:)?object(?:\s|>)/u.test(rawXml)
1194
1204
  });
1195
1205
  }
@@ -1347,6 +1357,7 @@ function convertShape(shape) {
1347
1357
  };
1348
1358
  return schema.node("shape", {
1349
1359
  shapeType: shapeAttrs.shapeType ?? "rect",
1360
+ geometryAdjustments: shape.geometryAdjustments === void 0 ? void 0 : JSON.stringify(shape.geometryAdjustments),
1350
1361
  shapeId: shape.id,
1351
1362
  width: widthPx,
1352
1363
  height: heightPx,
@@ -6,6 +6,8 @@ import { EditorState } from "prosemirror-state";
6
6
  type ResolvedStyleAttrs = {
7
7
  paragraphFormatting?: document_d_exports.ParagraphFormatting;
8
8
  runFormatting?: document_d_exports.TextFormatting;
9
+ /** Canonical OOXML style name, used when the document-local style id is localized. */
10
+ styleName?: string;
9
11
  /**
10
12
  * Numbering definitions from the document package. When the applied style
11
13
  * carries a `w:numPr`, these resolve the numbering level into the list
@@ -1,5 +1,6 @@
1
1
  import { paragraphToStyle } from "../../../utils/formatToStyle.js";
2
2
  import { collectHeadings } from "../../../utils/headingCollector.js";
3
+ import { tableOfContentsStyleLevel } from "../../../utils/tableOfContentsStyle.js";
3
4
  import { expectParagraphAttrs } from "../../attrs/index.js";
4
5
  import { autospacingMatchesBase } from "../../autospacingBase.js";
5
6
  import { directionIsRtl } from "../../paragraphDirection.js";
@@ -215,6 +216,7 @@ const paragraphNodeSpec = {
215
216
  listAbstractNumId: { default: null },
216
217
  listStartOverride: { default: null },
217
218
  styleId: { default: null },
219
+ _tableOfContentsLevel: { default: null },
218
220
  borders: { default: null },
219
221
  shading: { default: null },
220
222
  tabs: { default: null },
@@ -248,11 +250,13 @@ const paragraphNodeSpec = {
248
250
  const paraId = element.dataset["paraId"];
249
251
  const alignment = element.dataset["alignment"];
250
252
  const styleId = element.dataset["styleId"];
253
+ const tableOfContentsLevel = Number(element.dataset["tableOfContentsLevel"]);
251
254
  const sectionBreakType = element.dataset["sectionBreak"];
252
255
  const attrs = {
253
256
  ...paraId ? { paraId } : {},
254
257
  ...alignment ? { alignment } : {},
255
258
  ...styleId ? { styleId } : {},
259
+ ...Number.isSafeInteger(tableOfContentsLevel) && tableOfContentsLevel > 0 ? { _tableOfContentsLevel: tableOfContentsLevel } : {},
256
260
  ...sectionBreakType ? { sectionBreakType } : {}
257
261
  };
258
262
  const styleAttrs = extractParagraphAttrsFromStyle(element);
@@ -291,6 +295,7 @@ const paragraphNodeSpec = {
291
295
  if (attrs.paraId) domAttrs["data-para-id"] = attrs.paraId;
292
296
  if (attrs.alignment) domAttrs["data-alignment"] = attrs.alignment;
293
297
  if (attrs.styleId) domAttrs["data-style-id"] = attrs.styleId;
298
+ if (typeof attrs._tableOfContentsLevel === "number") domAttrs["data-table-of-contents-level"] = String(attrs._tableOfContentsLevel);
294
299
  if (attrs.listMarker) domAttrs["data-list-marker"] = attrs.listMarker;
295
300
  if (directionIsRtl(attrs.direction)) domAttrs["dir"] = "rtl";
296
301
  if (attrs.sectionBreakType) {
@@ -460,10 +465,14 @@ function makeApplyStyle(schema) {
460
465
  seen.add(pos);
461
466
  const newAttrs = {
462
467
  ...node.attrs,
463
- styleId
468
+ styleId,
469
+ _tableOfContentsLevel: tableOfContentsStyleLevel({ styleId }) ?? null
464
470
  };
465
471
  if (resolvedAttrs) {
466
- Object.assign(newAttrs, paragraphAttrsFromResolvedStyle(resolvedAttrs));
472
+ Object.assign(newAttrs, paragraphAttrsFromResolvedStyle(resolvedAttrs, {
473
+ styleId,
474
+ ...resolvedAttrs.styleName ? { styleName: resolvedAttrs.styleName } : {}
475
+ }));
467
476
  const listAttrs = listAttrsFromResolvedStyle(resolvedAttrs, resolvedAttrs.numbering);
468
477
  if (listAttrs) Object.assign(newAttrs, listAttrs);
469
478
  }
@@ -534,7 +543,10 @@ const ParagraphExtension = createNodeExtension({
534
543
  hangingIndent: hanging ?? false
535
544
  }),
536
545
  applyStyle: (styleId, resolvedAttrs) => applyStyleFn(styleId, resolvedAttrs),
537
- clearStyle: () => setParagraphAttr("styleId", null),
546
+ clearStyle: () => setParagraphAttrsCmd({
547
+ styleId: null,
548
+ _tableOfContentsLevel: null
549
+ }),
538
550
  insertSectionBreak: (breakType) => setParagraphAttr("sectionBreakType", breakType),
539
551
  removeSectionBreak: () => setParagraphAttr("sectionBreakType", null),
540
552
  generateTOC: () => (state, dispatch) => {
@@ -53,6 +53,7 @@ const clearIndentOnBackspace = (state, dispatch) => {
53
53
  const INHERITED_PARA_ATTRS = [
54
54
  "defaultTextFormatting",
55
55
  "styleId",
56
+ "_tableOfContentsLevel",
56
57
  "lineSpacing",
57
58
  "lineSpacingRule",
58
59
  "snapToGrid",
@@ -77,11 +78,15 @@ function applyNextParagraphStyle(tr, sourcePara, newPara, resolver) {
77
78
  const nextStyleId = resolver.getNextStyleId(sourcePara.attrs["styleId"]);
78
79
  if (!nextStyleId) return false;
79
80
  const resolved = resolver.resolveParagraphStyle(nextStyleId);
81
+ const styleName = resolver.getStyle(nextStyleId)?.name;
80
82
  const { $from } = tr.selection;
81
83
  tr.setNodeMarkup($from.before(), void 0, {
82
84
  ...newPara.attrs,
83
85
  styleId: nextStyleId,
84
- ...paragraphAttrsFromResolvedStyle(resolved)
86
+ ...paragraphAttrsFromResolvedStyle(resolved, {
87
+ styleId: nextStyleId,
88
+ ...styleName ? { styleName } : {}
89
+ })
85
90
  });
86
91
  tr.setStoredMarks(resolved.runFormatting ? textFormattingToMarks(resolved.runFormatting, tr.doc.type.schema) : []);
87
92
  return true;
@@ -42,6 +42,7 @@ const ImageExtension = createNodeExtension({
42
42
  hlinkHref: { default: null },
43
43
  hlinkRId: { default: null },
44
44
  _docxRawXml: { default: null },
45
+ _docxRawXmlMode: { default: null },
45
46
  _docxObjectPreview: { default: null }
46
47
  },
47
48
  parseDOM: [{
@@ -1,3 +1,4 @@
1
+ import { document_d_exports } from "../../../types/document.js";
1
2
  import { ShapeAttrs as ShapeAttrs$1 } from "../../schema/nodes.js";
2
3
  import { NodeExtension } from "../types.js";
3
4
  //#region src/prosemirror/extensions/nodes/ShapeExtension.d.ts
@@ -7,6 +8,11 @@ declare function sanitizeTransform(value: string | null | undefined): string | n
7
8
  declare function sanitizeSvgId(value: string | null | undefined): string | null;
8
9
  declare function sanitizeShapeDimension(value: number | null | undefined, fallback: number): number;
9
10
  declare function strokeDashArrayForOutlineStyle(outlineStyle: string | undefined): string | undefined;
11
+ type RightBracePaths = {
12
+ fill: string;
13
+ outline: string;
14
+ };
15
+ declare function buildRightBracePaths(w: number, h: number, adjustments: readonly document_d_exports.ShapeGeometryAdjustment[]): RightBracePaths;
10
16
  /**
11
17
  * Build the `points` string for a polygon-based shape preset.
12
18
  *
@@ -26,4 +32,4 @@ type GradientStop = {
26
32
  declare function parseGradientStops(raw: string | undefined): GradientStop[];
27
33
  declare const ShapeExtension: (options?: Partial<Record<string, unknown>> | undefined) => NodeExtension;
28
34
  //#endregion
29
- export { ShapeAttrs, ShapeExtension, buildShapePolygonPoints, parseGradientStops, sanitizeColor, sanitizeShapeDimension, sanitizeSvgId, sanitizeTransform, strokeDashArrayForOutlineStyle };
35
+ export { ShapeAttrs, ShapeExtension, buildRightBracePaths, buildShapePolygonPoints, parseGradientStops, sanitizeColor, sanitizeShapeDimension, sanitizeSvgId, sanitizeTransform, strokeDashArrayForOutlineStyle };
@@ -1,4 +1,5 @@
1
1
  import { expectShapeAttrs } from "../../attrs/index.js";
2
+ import { parseShapeGeometryAdjustments } from "../../shapeGeometryAdjustments.js";
2
3
  import { createNodeExtension } from "../create.js";
3
4
  //#region src/prosemirror/extensions/nodes/ShapeExtension.ts
4
5
  /**
@@ -123,6 +124,40 @@ function setNum(el, name, value) {
123
124
  function fmt(n) {
124
125
  return Number.isInteger(n) ? String(n) : Number(n.toFixed(3)).toString();
125
126
  }
127
+ const RIGHT_BRACE_DEFAULT_THICKNESS = 8333;
128
+ const RIGHT_BRACE_DEFAULT_CUSP = 5e4;
129
+ const GEOMETRY_PERCENT_SCALE = 1e5;
130
+ const geometryAdjustment = (adjustments, name, fallback) => {
131
+ const authored = adjustments.find((adjustment) => adjustment.name === name);
132
+ if (!authored) return fallback;
133
+ const match = /^val\s+(-?\d+)$/u.exec(authored.formula);
134
+ if (!match) return fallback;
135
+ return Number(match.at(1));
136
+ };
137
+ function buildRightBracePaths(w, h, adjustments) {
138
+ const shortSide = Math.min(w, h);
139
+ const cuspAdjustment = Math.min(GEOMETRY_PERCENT_SCALE, Math.max(0, geometryAdjustment(adjustments, "adj2", RIGHT_BRACE_DEFAULT_CUSP)));
140
+ const maxThickness = Math.min(cuspAdjustment, GEOMETRY_PERCENT_SCALE - cuspAdjustment) / 2 * h / shortSide;
141
+ const radius = shortSide * Math.min(maxThickness, Math.max(0, geometryAdjustment(adjustments, "adj1", RIGHT_BRACE_DEFAULT_THICKNESS))) / GEOMETRY_PERCENT_SCALE;
142
+ const halfWidth = w / 2;
143
+ const cusp = h * cuspAdjustment / GEOMETRY_PERCENT_SCALE;
144
+ const upperStemEnd = cusp - radius;
145
+ const lowerStemStart = cusp + radius;
146
+ const bottomCurveStart = h - radius;
147
+ const commands = [
148
+ "M 0 0",
149
+ `A ${fmt(halfWidth)} ${fmt(radius)} 0 0 1 ${fmt(halfWidth)} ${fmt(radius)}`,
150
+ `L ${fmt(halfWidth)} ${fmt(upperStemEnd)}`,
151
+ `A ${fmt(halfWidth)} ${fmt(radius)} 0 0 0 ${fmt(w)} ${fmt(cusp)}`,
152
+ `A ${fmt(halfWidth)} ${fmt(radius)} 0 0 0 ${fmt(halfWidth)} ${fmt(lowerStemStart)}`,
153
+ `L ${fmt(halfWidth)} ${fmt(bottomCurveStart)}`,
154
+ `A ${fmt(halfWidth)} ${fmt(radius)} 0 0 1 0 ${fmt(h)}`
155
+ ].join(" ");
156
+ return {
157
+ fill: `${commands} Z`,
158
+ outline: commands
159
+ };
160
+ }
126
161
  /**
127
162
  * Build the `points` string for a polygon-based shape preset.
128
163
  *
@@ -209,7 +244,7 @@ function buildShapePolygonPoints(type, w, h) {
209
244
  * displays. The original `<a:prstGeom prst>` value round-trips through the
210
245
  * model regardless of what the renderer chooses to draw.
211
246
  */
212
- function createShapeElement(type, w, h) {
247
+ function createShapeElement(type, w, h, adjustments) {
213
248
  switch (type) {
214
249
  case "ellipse":
215
250
  case "oval": {
@@ -238,6 +273,19 @@ function createShapeElement(type, w, h) {
238
273
  setNum(el, "y2", h / 2);
239
274
  return el;
240
275
  }
276
+ case "rightBrace": {
277
+ const paths = buildRightBracePaths(w, h, adjustments);
278
+ const group = document.createElementNS(SVG_NS, "g");
279
+ const fill = document.createElementNS(SVG_NS, "path");
280
+ fill.setAttribute("d", paths.fill);
281
+ fill.setAttribute("stroke", "none");
282
+ group.append(fill);
283
+ const outline = document.createElementNS(SVG_NS, "path");
284
+ outline.setAttribute("d", paths.outline);
285
+ outline.setAttribute("fill", "none");
286
+ group.append(outline);
287
+ return group;
288
+ }
241
289
  default: {
242
290
  const points = buildShapePolygonPoints(type, w, h);
243
291
  if (points !== null) {
@@ -320,6 +368,7 @@ const ShapeExtension = createNodeExtension({
320
368
  atom: true,
321
369
  attrs: {
322
370
  shapeType: { default: "rect" },
371
+ geometryAdjustments: { default: null },
323
372
  shapeId: { default: null },
324
373
  width: { default: 100 },
325
374
  height: { default: 80 },
@@ -362,6 +411,7 @@ const ShapeExtension = createNodeExtension({
362
411
  const outlineTailEnd = parseShapeLineEnd(d["outlineTailEnd"]);
363
412
  return {
364
413
  shapeType: d["shapeType"] || "rect",
414
+ ...d["geometryAdjustments"] ? { geometryAdjustments: d["geometryAdjustments"] } : {},
365
415
  ...d["shapeId"] ? { shapeId: d["shapeId"] } : {},
366
416
  ...d["width"] ? { width: Number(d["width"]) } : {},
367
417
  ...d["height"] ? { height: Number(d["height"]) } : {},
@@ -403,6 +453,7 @@ const ShapeExtension = createNodeExtension({
403
453
  class: "docx-shape",
404
454
  "data-shape-type": attrs.shapeType || "rect"
405
455
  };
456
+ if (attrs.geometryAdjustments) domAttrs["data-geometry-adjustments"] = attrs.geometryAdjustments;
406
457
  if (attrs.shapeId) domAttrs["data-shape-id"] = attrs.shapeId;
407
458
  domAttrs["data-width"] = String(w);
408
459
  domAttrs["data-height"] = String(h);
@@ -489,7 +540,8 @@ const ShapeExtension = createNodeExtension({
489
540
  defs.append(gradient);
490
541
  svg.append(defs);
491
542
  }
492
- const shapeEl = createShapeElement(attrs.shapeType || "rect", w, h);
543
+ const geometryAdjustments = parseShapeGeometryAdjustments(attrs.geometryAdjustments) ?? [];
544
+ const shapeEl = createShapeElement(attrs.shapeType || "rect", w, h, geometryAdjustments);
493
545
  const strokeDasharray = strokeDashArrayForOutlineStyle(attrs.outlineStyle);
494
546
  if (strokeDasharray) shapeEl.setAttribute("stroke-dasharray", strokeDasharray);
495
547
  svg.append(shapeEl);
@@ -501,4 +553,4 @@ const ShapeExtension = createNodeExtension({
501
553
  }
502
554
  });
503
555
  //#endregion
504
- export { ShapeExtension, buildShapePolygonPoints, parseGradientStops, sanitizeColor, sanitizeShapeDimension, sanitizeSvgId, sanitizeTransform, strokeDashArrayForOutlineStyle };
556
+ export { ShapeExtension, buildRightBracePaths, buildShapePolygonPoints, parseGradientStops, sanitizeColor, sanitizeShapeDimension, sanitizeSvgId, sanitizeTransform, strokeDashArrayForOutlineStyle };
@@ -102,6 +102,8 @@ type ParagraphAttrs = {
102
102
  /** Numbering start override for this numId/level. */
103
103
  listStartOverride?: number;
104
104
  styleId?: string;
105
+ /** Imported built-in TOC entry level, resolved from the canonical style name. */
106
+ _tableOfContentsLevel?: number;
105
107
  borders?: {
106
108
  top?: document_d_exports.BorderSpec;
107
109
  bottom?: document_d_exports.BorderSpec;
@@ -275,6 +277,8 @@ type ImageAttrs = {
275
277
  hlinkRId?: string;
276
278
  /** Original OOXML for opaque/unsupported DOCX drawings. */
277
279
  _docxRawXml?: string;
280
+ /** Raw XML preserved without an editable image projection. */
281
+ _docxRawXmlMode?: document_d_exports.DrawingRawXmlMode;
278
282
  /** Embedded-object previews use their authored box as the exact line height. */
279
283
  _docxObjectPreview?: boolean;
280
284
  };
@@ -382,6 +386,8 @@ type BlockSdtAttrs = {
382
386
  type ShapeAttrs = {
383
387
  /** Shape type preset */
384
388
  shapeType?: string;
389
+ /** Preset geometry adjustments serialized as JSON. */
390
+ geometryAdjustments?: string;
385
391
  /** Unique identifier */
386
392
  shapeId?: string;
387
393
  /** Width in pixels */
@@ -0,0 +1,5 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ //#region src/prosemirror/shapeGeometryAdjustments.d.ts
3
+ declare const parseShapeGeometryAdjustments: (raw: string | undefined) => document_d_exports.ShapeGeometryAdjustment[] | undefined;
4
+ //#endregion
5
+ export { parseShapeGeometryAdjustments };
@@ -0,0 +1,24 @@
1
+ //#region src/prosemirror/shapeGeometryAdjustments.ts
2
+ const MAX_ADJUSTMENT_COUNT = 32;
3
+ const MAX_ADJUSTMENT_NAME_LENGTH = 64;
4
+ const MAX_ADJUSTMENT_FORMULA_LENGTH = 128;
5
+ const parseShapeGeometryAdjustments = (raw) => {
6
+ if (!raw) return;
7
+ try {
8
+ const parsed = JSON.parse(raw);
9
+ if (!Array.isArray(parsed) || parsed.length > MAX_ADJUSTMENT_COUNT) return;
10
+ const adjustments = [];
11
+ for (const item of parsed) {
12
+ if (typeof item !== "object" || item === null || !("name" in item) || typeof item.name !== "string" || item.name.length === 0 || item.name.length > MAX_ADJUSTMENT_NAME_LENGTH || !("formula" in item) || typeof item.formula !== "string" || item.formula.length === 0 || item.formula.length > MAX_ADJUSTMENT_FORMULA_LENGTH) return;
13
+ adjustments.push({
14
+ name: item.name,
15
+ formula: item.formula
16
+ });
17
+ }
18
+ return adjustments.length === 0 ? void 0 : adjustments;
19
+ } catch {
20
+ return;
21
+ }
22
+ };
23
+ //#endregion
24
+ export { parseShapeGeometryAdjustments };
@@ -1,13 +1,17 @@
1
1
  import { NumberingMap } from "../../docx/numberingParser.js";
2
2
  import { ResolvedParagraphStyle } from "./styleResolver.js";
3
3
  //#region src/prosemirror/styles/resolvedStyleAttrs.d.ts
4
+ type ResolvedStyleIdentity = {
5
+ styleId: string;
6
+ styleName?: string;
7
+ };
4
8
  /**
5
9
  * The paragraph attrs a style definition controls. Applying a style resets
6
10
  * every one of these to the style's value (or `null` to clear), so a prior
7
11
  * style's properties (e.g. a heading's spacing) never leak through. Returns
8
12
  * a partial attrs object to merge over the paragraph's existing attrs.
9
13
  */
10
- declare function paragraphAttrsFromResolvedStyle(resolved: ResolvedParagraphStyle): Record<string, unknown>;
14
+ declare function paragraphAttrsFromResolvedStyle(resolved: ResolvedParagraphStyle, identity: ResolvedStyleIdentity): Record<string, unknown>;
11
15
  /**
12
16
  * The list attrs a style's `w:pPr/w:numPr` controls (numbering reference plus
13
17
  * the baked marker-rendering attrs that `toProseDoc` normally derives from
@@ -1,4 +1,5 @@
1
1
  import { computeListRendering } from "../../docx/numberingParser.js";
2
+ import { tableOfContentsStyleLevel } from "../../utils/tableOfContentsStyle.js";
2
3
  import { setAutospacingBaseValue } from "../autospacingBase.js";
3
4
  //#region src/prosemirror/styles/resolvedStyleAttrs.ts
4
5
  /**
@@ -17,7 +18,7 @@ import { setAutospacingBaseValue } from "../autospacingBase.js";
17
18
  * style's properties (e.g. a heading's spacing) never leak through. Returns
18
19
  * a partial attrs object to merge over the paragraph's existing attrs.
19
20
  */
20
- function paragraphAttrsFromResolvedStyle(resolved) {
21
+ function paragraphAttrsFromResolvedStyle(resolved, identity) {
21
22
  const ppr = resolved.paragraphFormatting;
22
23
  const runFormatting = resolved.runFormatting;
23
24
  const hasRunFormatting = !!runFormatting && Object.keys(runFormatting).length > 0;
@@ -40,7 +41,8 @@ function paragraphAttrsFromResolvedStyle(resolved) {
40
41
  outlineLevel: ppr?.outlineLevel ?? null,
41
42
  borders: ppr?.borders ?? null,
42
43
  defaultTextFormatting: hasRunFormatting ? runFormatting : null,
43
- _autospacingBase: autospacingBaseFromResolvedParagraphFormatting(ppr)
44
+ _autospacingBase: autospacingBaseFromResolvedParagraphFormatting(ppr),
45
+ _tableOfContentsLevel: tableOfContentsStyleLevel(identity) ?? null
44
46
  };
45
47
  }
46
48
  function autospacingBaseFromResolvedParagraphFormatting(ppr) {
@@ -1,2 +1,2 @@
1
- import { BlockContent, BlockSdt, BookmarkEnd, BookmarkStart, BreakContent, Column, Comment, CommentRangeEnd, CommentRangeStart, CommentReference, ComplexField, Deletion, DocumentBody, DrawingContent, Endnote, EndnotePosition, EndnoteProperties, Field, FieldCharContent, FieldType, FooterReference, Footnote, FootnotePosition, FootnoteProperties, HeaderFooter, HeaderFooterType, HeaderReference, Hyperlink, Image, ImageCrop, ImagePadding, ImagePosition, ImageSize, ImageTransform, ImageWrap, InlineSdt, Insertion, InstrTextContent, LineNumberRestart, MathEquation, MoveFrom, MoveFromRangeEnd, MoveFromRangeStart, MoveTo, MoveToRangeEnd, MoveToRangeStart, NoBreakHyphenContent, NoteNumberRestart, NoteReferenceContent, PageOrientation, Paragraph, ParagraphContent, ParagraphPropertyChange, PositionalTab, PropertyChangeInfo, Run, RunContent, RunPropertyChange, SdtProperties, SdtType, Section, SectionProperties, SectionStart, Shape, ShapeContent, ShapeFill, ShapeOutline, ShapeTextBody, ShapeType, SimpleField, SoftHyphenContent, SymbolContent, TabContent, Table, TableCell, TableCellPropertyChange, TableRow, TableRowPropertyChange, TableStructuralChangeInfo, TextBox, TextContent, TrackedChangeInfo, TrackedRunChange, VerticalAlign } from "@stll/docx-core/model";
2
- export type { BlockContent, BlockSdt, BookmarkEnd, BookmarkStart, BreakContent, Column, Comment, CommentRangeEnd, CommentRangeStart, CommentReference, ComplexField, Deletion, DocumentBody, DrawingContent, Endnote, EndnotePosition, EndnoteProperties, Field, FieldCharContent, FieldType, FooterReference, Footnote, FootnotePosition, FootnoteProperties, HeaderFooter, HeaderFooterType, HeaderReference, Hyperlink, Image, ImageCrop, ImagePadding, ImagePosition, ImageSize, ImageTransform, ImageWrap, InlineSdt, Insertion, InstrTextContent, LineNumberRestart, MathEquation, MoveFrom, MoveFromRangeEnd, MoveFromRangeStart, MoveTo, MoveToRangeEnd, MoveToRangeStart, NoBreakHyphenContent, NoteNumberRestart, NoteReferenceContent, PageOrientation, Paragraph, ParagraphContent, ParagraphPropertyChange, PositionalTab, PropertyChangeInfo, Run, RunContent, RunPropertyChange, SdtProperties, SdtType, Section, SectionProperties, SectionStart, Shape, ShapeContent, ShapeFill, ShapeOutline, ShapeTextBody, ShapeType, SimpleField, SoftHyphenContent, SymbolContent, TabContent, Table, TableCell, TableCellPropertyChange, TableRow, TableRowPropertyChange, TableStructuralChangeInfo, TextBox, TextContent, TrackedChangeInfo, TrackedRunChange, VerticalAlign };
1
+ import { BlockContent, BlockSdt, BookmarkEnd, BookmarkStart, BreakContent, Column, Comment, CommentRangeEnd, CommentRangeStart, CommentReference, ComplexField, Deletion, DocumentBody, DrawingContent, DrawingRawXmlMode, Endnote, EndnotePosition, EndnoteProperties, Field, FieldCharContent, FieldType, FooterReference, Footnote, FootnotePosition, FootnoteProperties, HeaderFooter, HeaderFooterType, HeaderReference, Hyperlink, Image, ImageCrop, ImagePadding, ImagePosition, ImageSize, ImageTransform, ImageWrap, InlineSdt, Insertion, InstrTextContent, LineNumberRestart, MathEquation, MoveFrom, MoveFromRangeEnd, MoveFromRangeStart, MoveTo, MoveToRangeEnd, MoveToRangeStart, NoBreakHyphenContent, NoteNumberRestart, NoteReferenceContent, PageOrientation, Paragraph, ParagraphContent, ParagraphPropertyChange, PositionalTab, PropertyChangeInfo, Run, RunContent, RunPropertyChange, SdtProperties, SdtType, Section, SectionProperties, SectionStart, Shape, ShapeContent, ShapeFill, ShapeGeometryAdjustment, ShapeOutline, ShapeTextBody, ShapeType, SimpleField, SoftHyphenContent, SymbolContent, TabContent, Table, TableCell, TableCellPropertyChange, TableRow, TableRowPropertyChange, TableStructuralChangeInfo, TextBox, TextContent, TrackedChangeInfo, TrackedRunChange, VerticalAlign } from "@stll/docx-core/model";
2
+ export type { BlockContent, BlockSdt, BookmarkEnd, BookmarkStart, BreakContent, Column, Comment, CommentRangeEnd, CommentRangeStart, CommentReference, ComplexField, Deletion, DocumentBody, DrawingContent, DrawingRawXmlMode, Endnote, EndnotePosition, EndnoteProperties, Field, FieldCharContent, FieldType, FooterReference, Footnote, FootnotePosition, FootnoteProperties, HeaderFooter, HeaderFooterType, HeaderReference, Hyperlink, Image, ImageCrop, ImagePadding, ImagePosition, ImageSize, ImageTransform, ImageWrap, InlineSdt, Insertion, InstrTextContent, LineNumberRestart, MathEquation, MoveFrom, MoveFromRangeEnd, MoveFromRangeStart, MoveTo, MoveToRangeEnd, MoveToRangeStart, NoBreakHyphenContent, NoteNumberRestart, NoteReferenceContent, PageOrientation, Paragraph, ParagraphContent, ParagraphPropertyChange, PositionalTab, PropertyChangeInfo, Run, RunContent, RunPropertyChange, SdtProperties, SdtType, Section, SectionProperties, SectionStart, Shape, ShapeContent, ShapeFill, ShapeGeometryAdjustment, ShapeOutline, ShapeTextBody, ShapeType, SimpleField, SoftHyphenContent, SymbolContent, TabContent, Table, TableCell, TableCellPropertyChange, TableRow, TableRowPropertyChange, TableStructuralChangeInfo, TextBox, TextContent, TrackedChangeInfo, TrackedRunChange, VerticalAlign };
@@ -16,6 +16,7 @@ type DocumentBody = document_d_exports.DocumentBody;
16
16
  type DocxConformanceClass = document_d_exports.DocxConformanceClass;
17
17
  type DocxPackage = document_d_exports.DocxPackage;
18
18
  type DrawingContent = document_d_exports.DrawingContent;
19
+ type DrawingRawXmlMode = document_d_exports.DrawingRawXmlMode;
19
20
  type EmphasisMark = document_d_exports.EmphasisMark;
20
21
  type Endnote = document_d_exports.Endnote;
21
22
  type EndnotePosition = document_d_exports.EndnotePosition;
@@ -72,6 +73,7 @@ type ShadingProperties = document_d_exports.ShadingProperties;
72
73
  type Shape = document_d_exports.Shape;
73
74
  type ShapeContent = document_d_exports.ShapeContent;
74
75
  type ShapeFill = document_d_exports.ShapeFill;
76
+ type ShapeGeometryAdjustment = document_d_exports.ShapeGeometryAdjustment;
75
77
  type ShapeOutline = document_d_exports.ShapeOutline;
76
78
  type ShapeTextBody = document_d_exports.ShapeTextBody;
77
79
  type ShapeType = document_d_exports.ShapeType;
@@ -106,4 +108,4 @@ type ThemeFont = document_d_exports.ThemeFont;
106
108
  type ThemeFontScheme = document_d_exports.ThemeFontScheme;
107
109
  type UnderlineStyle = document_d_exports.UnderlineStyle;
108
110
  type VerticalAlign = document_d_exports.VerticalAlign;
109
- export type { AbstractNumbering, BlockContent, BookmarkEnd, BookmarkStart, BorderSpec, BreakContent, CellMargins, ColorValue, Column, ComplexField, ConditionalFormatStyle, DocDefaults, Document, DocumentBody, DocxConformanceClass, DocxPackage, DrawingContent, EmphasisMark, Endnote, EndnotePosition, EndnoteProperties, Field, FieldCharContent, FieldType, FloatingTableProperties, FontInfo, FontTable, FooterReference, Footnote, FootnotePosition, FootnoteProperties, HeaderFooter, HeaderFooterType, HeaderReference, Hyperlink, Image, ImageCrop, ImagePadding, ImagePosition, ImageSize, ImageTransform, ImageWrap, InstrTextContent, LevelSuffix, LineNumberRestart, LineSpacingRule, ListLevel, ListRendering, MediaFile, NoBreakHyphenContent, NoteNumberRestart, NoteReferenceContent, NumberFormat, NumberingDefinitions, NumberingInstance, PageOrientation, Paragraph, ParagraphAlignment, ParagraphContent, ParagraphFormatting, PositionalTab, Relationship, RelationshipMap, RelationshipType, Run, RunContent, Section, SectionProperties, SectionStart, ShadingProperties, Shape, ShapeContent, ShapeFill, ShapeOutline, ShapeTextBody, ShapeType, SimpleField, SoftHyphenContent, Style, StyleDefinitions, StyleType, SymbolContent, TabContent, TabLeader, TabStop, TabStopAlignment, Table, TableBorders, TableCell, TableCellFormatting, TableFormatting, TableLook, TableMeasurement, TableRow, TableRowFormatting, TableWidthType, TextBox, TextContent, TextEffect, TextFormatting, Theme, ThemeColorScheme, ThemeColorSlot, ThemeFont, ThemeFontScheme, UnderlineStyle, VerticalAlign };
111
+ export type { AbstractNumbering, BlockContent, BookmarkEnd, BookmarkStart, BorderSpec, BreakContent, CellMargins, ColorValue, Column, ComplexField, ConditionalFormatStyle, DocDefaults, Document, DocumentBody, DocxConformanceClass, DocxPackage, DrawingContent, DrawingRawXmlMode, EmphasisMark, Endnote, EndnotePosition, EndnoteProperties, Field, FieldCharContent, FieldType, FloatingTableProperties, FontInfo, FontTable, FooterReference, Footnote, FootnotePosition, FootnoteProperties, HeaderFooter, HeaderFooterType, HeaderReference, Hyperlink, Image, ImageCrop, ImagePadding, ImagePosition, ImageSize, ImageTransform, ImageWrap, InstrTextContent, LevelSuffix, LineNumberRestart, LineSpacingRule, ListLevel, ListRendering, MediaFile, NoBreakHyphenContent, NoteNumberRestart, NoteReferenceContent, NumberFormat, NumberingDefinitions, NumberingInstance, PageOrientation, Paragraph, ParagraphAlignment, ParagraphContent, ParagraphFormatting, PositionalTab, Relationship, RelationshipMap, RelationshipType, Run, RunContent, Section, SectionProperties, SectionStart, ShadingProperties, Shape, ShapeContent, ShapeFill, ShapeGeometryAdjustment, ShapeOutline, ShapeTextBody, ShapeType, SimpleField, SoftHyphenContent, Style, StyleDefinitions, StyleType, SymbolContent, TabContent, TabLeader, TabStop, TabStopAlignment, Table, TableBorders, TableCell, TableCellFormatting, TableFormatting, TableLook, TableMeasurement, TableRow, TableRowFormatting, TableWidthType, TextBox, TextContent, TextEffect, TextFormatting, Theme, ThemeColorScheme, ThemeColorSlot, ThemeFont, ThemeFontScheme, UnderlineStyle, VerticalAlign };
@@ -0,0 +1,9 @@
1
+ //#region src/utils/tableOfContentsStyle.d.ts
2
+ type TableOfContentsStyleIdentity = {
3
+ styleId: string | undefined;
4
+ styleName?: string;
5
+ };
6
+ /** Resolve a built-in TOC entry style without relying on its localized style id. */
7
+ declare const tableOfContentsStyleLevel: ({ styleId, styleName }: TableOfContentsStyleIdentity) => number | undefined;
8
+ //#endregion
9
+ export { tableOfContentsStyleLevel };
@@ -0,0 +1,14 @@
1
+ //#region src/utils/tableOfContentsStyle.ts
2
+ const TABLE_OF_CONTENTS_STYLE_ID = /^TOC(?<level>\d*)$/iu;
3
+ const TABLE_OF_CONTENTS_STYLE_NAME = /^toc\s+(?<level>\d+)$/iu;
4
+ const parsedLevel = (match) => {
5
+ if (!match) return;
6
+ const rawLevel = match.groups?.["level"];
7
+ if (rawLevel === "") return 1;
8
+ const level = Number(rawLevel);
9
+ return Number.isSafeInteger(level) && level > 0 ? level : void 0;
10
+ };
11
+ /** Resolve a built-in TOC entry style without relying on its localized style id. */
12
+ const tableOfContentsStyleLevel = ({ styleId, styleName }) => parsedLevel(TABLE_OF_CONTENTS_STYLE_ID.exec(styleId ?? "")) ?? parsedLevel(TABLE_OF_CONTENTS_STYLE_NAME.exec(styleName ?? ""));
13
+ //#endregion
14
+ export { tableOfContentsStyleLevel };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/folio-core",
3
- "version": "0.25.3",
3
+ "version": "0.25.5",
4
4
  "description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
5
5
  "keywords": [
6
6
  "document-model",
@@ -113,7 +113,7 @@
113
113
  "perf": "bun scripts/profile-editor.ts"
114
114
  },
115
115
  "dependencies": {
116
- "@stll/docx-core": "^0.17.0",
116
+ "@stll/docx-core": "^0.17.1",
117
117
  "@stll/docx-utils": "^0.1.0",
118
118
  "@stll/template-conditions": "^0.1.0",
119
119
  "better-result": "3.0.1",