@stll/folio-core 0.23.0 → 0.24.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 (50) hide show
  1. package/dist/controller/fontReadiness.js +12 -7
  2. package/dist/controller/layoutPipeline.js +7 -3
  3. package/dist/docx/blockContentParser.js +2 -47
  4. package/dist/docx/fieldParser.d.ts +1 -1
  5. package/dist/docx/fieldParser.js +12 -4
  6. package/dist/docx/numberingParser.d.ts +3 -11
  7. package/dist/docx/numberingParser.js +26 -158
  8. package/dist/docx/ooxmlCounterFormatter.d.ts +8 -0
  9. package/dist/docx/ooxmlCounterFormatter.js +134 -0
  10. package/dist/docx/paragraphParser.js +3 -4
  11. package/dist/docx/serializer/tableSerializer.js +1 -1
  12. package/dist/docx/tableParser.js +2 -1
  13. package/dist/fields/bookmarkPages.js +1 -1
  14. package/dist/fields/evaluateField.js +1 -1
  15. package/dist/fields/fieldContext.d.ts +2 -0
  16. package/dist/fields/resolveFieldValues.js +5 -2
  17. package/dist/layout-bridge/convert/headerFooterLayout.js +7 -1
  18. package/dist/layout-bridge/convert/toFlowBlocks.js +27 -64
  19. package/dist/layout-engine/index.d.ts +3 -2
  20. package/dist/layout-engine/index.js +40 -16
  21. package/dist/layout-engine/measure/cache.js +5 -1
  22. package/dist/layout-engine/measure/complexScriptFormatting.d.ts +18 -0
  23. package/dist/layout-engine/measure/complexScriptFormatting.js +11 -0
  24. package/dist/layout-engine/measure/listMarkerWidth.d.ts +3 -1
  25. package/dist/layout-engine/measure/listMarkerWidth.js +25 -21
  26. package/dist/layout-engine/measure/measureContainer.js +31 -16
  27. package/dist/layout-engine/measure/measureHelpers.js +4 -0
  28. package/dist/layout-engine/measure/measureParagraph.js +13 -3
  29. package/dist/layout-engine/measure/measureTypes.d.ts +4 -0
  30. package/dist/layout-engine/paginator.d.ts +5 -3
  31. package/dist/layout-engine/paginator.js +34 -13
  32. package/dist/layout-engine/types.d.ts +34 -7
  33. package/dist/layout-painter/index.js +2 -1
  34. package/dist/layout-painter/renderPage.js +9 -3
  35. package/dist/layout-painter/renderParagraph.js +50 -42
  36. package/dist/layout-painter/renderUtils.d.ts +2 -0
  37. package/dist/paged-layout/sectionGeometry.d.ts +3 -2
  38. package/dist/paged-layout/sectionGeometry.js +17 -1
  39. package/dist/prosemirror/attrs/index.js +7 -6
  40. package/dist/prosemirror/conversion/fromProseDoc.js +5 -19
  41. package/dist/prosemirror/conversion/toProseDoc.js +5 -4
  42. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -3
  43. package/dist/prosemirror/extensions/features/ListExtension.js +1 -3
  44. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.d.ts +3 -2
  45. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +31 -17
  46. package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -0
  47. package/dist/prosemirror/schema/marks.d.ts +8 -0
  48. package/dist/prosemirror/schema/nodes.d.ts +5 -7
  49. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -3
  50. package/package.json +2 -2
@@ -1,7 +1,8 @@
1
1
  import { ommlToMathml } from "../docx/mathToMathml.js";
2
2
  import { parseXmlDocument } from "../docx/xmlParser.js";
3
3
  import { evaluateFieldInstruction } from "../fields/evaluateField.js";
4
- import { getListMarkerInlineWidth, getListMarkerVisualOffset } from "../layout-engine/measure/listMarkerWidth.js";
4
+ import { hasComplexScriptFormatting, resolveComplexScriptFormatting } from "../layout-engine/measure/complexScriptFormatting.js";
5
+ import { getListMarkerInlineWidth, getListMarkerVisualOffset, resolveListMarkerFont } from "../layout-engine/measure/listMarkerWidth.js";
5
6
  import { DOCX_SCRIPT_FONT_SCALE } from "../layout-engine/measure/measureHelpers.js";
6
7
  import { FONT_KERNING_MODE, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode, toPaintedText } from "../layout-engine/measure/textMeasurementPolicy.js";
7
8
  import { isFloatingImageRun } from "../layout-engine/types.js";
@@ -571,6 +572,7 @@ function resolveFieldText(run, context) {
571
572
  if (!context) return fallback;
572
573
  const fieldContext = {
573
574
  pageNumber: context.pageNumber,
575
+ ...context.pageNumberFormat === void 0 ? {} : { pageNumberFormat: context.pageNumberFormat },
574
576
  totalPages: context.totalPages,
575
577
  bookmarkPages: context.bookmarkPages ?? EMPTY_BOOKMARK_PAGES,
576
578
  bookmarkText: context.bookmarkText ?? EMPTY_BOOKMARK_TEXT,
@@ -591,6 +593,10 @@ function renderFieldRun(run, doc, context) {
591
593
  kind: "text",
592
594
  text
593
595
  };
596
+ if (resolvedRun.forceComplexScript && hasComplexScriptFormatting(resolvedRun)) return renderTextRun({
597
+ ...resolvedRun,
598
+ ...resolveComplexScriptFormatting(resolvedRun)
599
+ }, doc);
594
600
  if (needsPerScriptSpans({
595
601
  ...resolvedRun,
596
602
  text
@@ -607,7 +613,7 @@ function renderFieldRun(run, doc, context) {
607
613
  const segmentRun = {
608
614
  ...segmentBase,
609
615
  text: segment.text,
610
- ...scriptFontOverride(resolvedRun, segment.script)
616
+ ...scriptFormattingOverride(resolvedRun, segment.script)
611
617
  };
612
618
  wrapper.append(renderTextRun(segmentRun, doc));
613
619
  }
@@ -725,9 +731,9 @@ function sliceRunsForLine(block, line) {
725
731
  * The font a script segment paints with, mirroring the measurer's
726
732
  * `scriptFontFamily`. Returning undefined leaves the run's own `fontFamily`.
727
733
  */
728
- const scriptFontOverride = (run, script) => {
734
+ const scriptFormattingOverride = (run, script) => {
735
+ if (run.forceComplexScript || script === SCRIPT_CLASS.complex) return resolveComplexScriptFormatting(run);
729
736
  if (script === SCRIPT_CLASS.eastAsia && run.eastAsiaFontFamily !== void 0) return { fontFamily: run.eastAsiaFontFamily };
730
- if (script === SCRIPT_CLASS.complex && run.complexScriptFontFamily !== void 0) return { fontFamily: run.complexScriptFontFamily };
731
737
  return {};
732
738
  };
733
739
  /**
@@ -739,7 +745,7 @@ const scriptFontOverride = (run, script) => {
739
745
  const needsPerScriptSpans = (run) => {
740
746
  if (run.letterSpacing) return false;
741
747
  if (run.eastAsiaFontFamily !== void 0 && hasCjk(run.text)) return true;
742
- return run.complexScriptFontFamily !== void 0 && hasComplexScript(run.text);
748
+ return hasComplexScriptFormatting(run) && hasComplexScript(run.text);
743
749
  };
744
750
  /**
745
751
  * Split each text run carrying an East-Asian font into per-script sub-runs, so
@@ -753,6 +759,13 @@ const needsPerScriptSpans = (run) => {
753
759
  function splitTextRunsByEastAsia(runs) {
754
760
  const result = [];
755
761
  for (const run of runs) {
762
+ if (isTextRun(run) && run.forceComplexScript && hasComplexScriptFormatting(run)) {
763
+ result.push({
764
+ ...run,
765
+ ...resolveComplexScriptFormatting(run)
766
+ });
767
+ continue;
768
+ }
756
769
  if (!isTextRun(run) || !needsPerScriptSpans(run)) {
757
770
  result.push(run);
758
771
  continue;
@@ -762,7 +775,7 @@ function splitTextRunsByEastAsia(runs) {
762
775
  result.push({
763
776
  ...run,
764
777
  text: segment.text,
765
- ...scriptFontOverride(run, segment.script),
778
+ ...scriptFormattingOverride(run, segment.script),
766
779
  ...run.pmStart !== void 0 ? {
767
780
  pmStart: run.pmStart + offset,
768
781
  pmEnd: run.pmStart + offset + segment.text.length
@@ -866,6 +879,10 @@ function runMeasureStyle(run) {
866
879
  ...run.smallCaps !== void 0 ? { smallCaps: run.smallCaps } : {},
867
880
  ...run.eastAsiaFontFamily !== void 0 ? { eastAsiaFontFamily: run.eastAsiaFontFamily } : {},
868
881
  ...run.complexScriptFontFamily !== void 0 ? { complexScriptFontFamily: run.complexScriptFontFamily } : {},
882
+ ...run.complexScriptFontSize !== void 0 ? { complexScriptFontSize: run.complexScriptFontSize } : {},
883
+ ...run.complexScriptBold !== void 0 ? { complexScriptBold: run.complexScriptBold } : {},
884
+ ...run.complexScriptItalic !== void 0 ? { complexScriptItalic: run.complexScriptItalic } : {},
885
+ ...run.forceComplexScript !== void 0 ? { forceComplexScript: run.forceComplexScript } : {},
869
886
  kerning: getRunFontKerningMode(run, 11) === FONT_KERNING_MODE.enabled
870
887
  };
871
888
  }
@@ -972,27 +989,32 @@ function createTextMeasurer(doc) {
972
989
  return (text, fontSize = 11, fontFamily = "Calibri", style = {}) => {
973
990
  if (!ctx) return applyLetterSpacingToMeasuredWidth(text.length * 7, text, style.letterSpacing);
974
991
  ctx.fontKerning = getFontKerningMode(style);
975
- const cssFallback = resolveFontFamily(fontFamily).cssFallback;
976
- const fontSizePx = fontSize * 96 / 72;
977
- const fontPrefixParts = [];
978
- if (style.italic) fontPrefixParts.push("italic");
979
- if (style.smallCaps) fontPrefixParts.push("small-caps");
980
- if (style.bold) fontPrefixParts.push("700");
981
- fontPrefixParts.push(`${fontSizePx}px`);
982
- const fontPrefix = fontPrefixParts.join(" ");
983
- const perScriptFallback = {
984
- ...style.eastAsiaFontFamily ? { [SCRIPT_CLASS.eastAsia]: resolveFontFamily(style.eastAsiaFontFamily).cssFallback } : {},
985
- ...style.complexScriptFontFamily ? { [SCRIPT_CLASS.complex]: resolveFontFamily(style.complexScriptFontFamily).cssFallback } : {}
992
+ const fontString = (family, size, bold, italic) => {
993
+ const fontPrefixParts = [];
994
+ if (italic) fontPrefixParts.push("italic");
995
+ if (style.smallCaps) fontPrefixParts.push("small-caps");
996
+ if (bold) fontPrefixParts.push("700");
997
+ fontPrefixParts.push(`${size * 96 / 72}px`);
998
+ return `${fontPrefixParts.join(" ")} ${resolveFontFamily(family).cssFallback}`;
986
999
  };
987
- if (!style.letterSpacing && (style.eastAsiaFontFamily && hasCjk(text) || style.complexScriptFontFamily && hasComplexScript(text))) {
1000
+ const baseFont = fontString(fontFamily, fontSize, style.bold, style.italic);
1001
+ const complex = resolveComplexScriptFormatting(style);
1002
+ const complexFont = fontString(complex.fontFamily ?? fontFamily, complex.fontSize ?? fontSize, complex.bold ?? style.bold, complex.italic ?? style.italic);
1003
+ if (style.forceComplexScript) {
1004
+ ctx.font = complexFont;
1005
+ return applyLetterSpacingToMeasuredWidth(ctx.measureText(text).width, text, style.letterSpacing);
1006
+ }
1007
+ if (!style.letterSpacing && (style.eastAsiaFontFamily && hasCjk(text) || hasComplexScriptFormatting(style) && hasComplexScript(text))) {
988
1008
  let segmentedWidth = 0;
989
1009
  for (const segment of segmentByScript(text)) {
990
- ctx.font = `${fontPrefix} ${perScriptFallback[segment.script] ?? cssFallback}`;
1010
+ if (segment.script === SCRIPT_CLASS.eastAsia && style.eastAsiaFontFamily) ctx.font = fontString(style.eastAsiaFontFamily, fontSize, style.bold, style.italic);
1011
+ else if (segment.script === SCRIPT_CLASS.complex) ctx.font = complexFont;
1012
+ else ctx.font = baseFont;
991
1013
  segmentedWidth += ctx.measureText(segment.text).width;
992
1014
  }
993
1015
  return segmentedWidth;
994
1016
  }
995
- ctx.font = `${fontPrefix} ${cssFallback}`;
1017
+ ctx.font = baseFont;
996
1018
  return applyLetterSpacingToMeasuredWidth(ctx.measureText(text).width, text, style.letterSpacing);
997
1019
  };
998
1020
  }
@@ -1180,12 +1202,7 @@ function renderLine(block, line, alignment, doc, options) {
1180
1202
  if (!measureText) continue;
1181
1203
  const fontSize = run.fontSize || 11;
1182
1204
  const fontFamily = run.fontFamily || "Calibri";
1183
- const measuredWidth = measureText(run.allCaps ? run.text.toLocaleUpperCase() : run.text, fontSize, fontFamily, {
1184
- ...run.bold !== void 0 ? { bold: run.bold } : {},
1185
- ...run.italic !== void 0 ? { italic: run.italic } : {},
1186
- ...run.letterSpacing !== void 0 ? { letterSpacing: run.letterSpacing } : {},
1187
- ...run.smallCaps !== void 0 ? { smallCaps: run.smallCaps } : {}
1188
- });
1205
+ const measuredWidth = measureText(run.allCaps ? run.text.toLocaleUpperCase() : run.text, fontSize, fontFamily, runMeasureStyle(run));
1189
1206
  reserveScaledAdvance(runEl, measuredWidth, run.horizontalScale);
1190
1207
  currentX += measuredWidth * ((run.horizontalScale ?? 100) / 100);
1191
1208
  } else if (isImageRun(run)) {
@@ -1446,18 +1463,7 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
1446
1463
  const markerStart = hanging > 0 ? indentLeft - hanging : indentLeft + firstLine;
1447
1464
  lineEl.style.paddingLeft = `${Math.max(0, markerStart)}px`;
1448
1465
  lineEl.style.textIndent = "0";
1449
- let firstTextRun;
1450
- if (!block.attrs.listMarkerFontFamily || !block.attrs.listMarkerFontSize || block.attrs.listMarkerBold === void 0) for (let ri = line.fromRun; ri <= line.toRun; ri++) {
1451
- const r = block.runs[ri];
1452
- if (r && r.kind === "text") {
1453
- firstTextRun = r;
1454
- break;
1455
- }
1456
- }
1457
- const markerFontFamily = block.attrs.listMarkerFontFamily ?? firstTextRun?.fontFamily ?? block.attrs.defaultFontFamily;
1458
- const markerFontSize = block.attrs.listMarkerFontSize ?? firstTextRun?.fontSize ?? block.attrs.defaultFontSize;
1459
- const markerBold = block.attrs.listMarkerBold ?? firstTextRun?.bold;
1460
- const marker = renderListMarker(block.attrs.listMarker, getListMarkerInlineWidth(block), getListMarkerVisualOffset(block), doc, markerFontFamily, markerFontSize, markerBold, block.attrs.listMarkerRevision, block.attrs.listMarkerSecondSlotOffsetTwips);
1466
+ const marker = renderListMarker(block.attrs.listMarker, getListMarkerInlineWidth(block), getListMarkerVisualOffset(block), doc, resolveListMarkerFont(block), block.attrs.listMarkerRevision, block.attrs.listMarkerSecondSlotOffsetTwips);
1461
1467
  const markerMarginLeft = markerStart - Math.min(indentLeft, 0);
1462
1468
  if (markerMarginLeft < 0) marker.style.marginLeft = `${markerMarginLeft}px`;
1463
1469
  lineEl.prepend(marker);
@@ -1474,13 +1480,15 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
1474
1480
  * tab grid. Long markers like "1.1.1." therefore grow to the next stop
1475
1481
  * instead of butting against the body text.
1476
1482
  */
1477
- function renderListMarker(marker, inlineWidth, visualOffset, doc, fontFamily, fontSize, bold, revision, secondSlotOffsetTwips) {
1483
+ function renderListMarker(marker, inlineWidth, visualOffset, doc, formatting, revision, secondSlotOffsetTwips) {
1478
1484
  const span = doc.createElement("span");
1479
1485
  span.className = "layout-list-marker";
1480
1486
  span.style.display = "inline-block";
1481
- if (fontFamily) span.style.fontFamily = resolveFontFamily(fontFamily).cssFallback;
1482
- if (fontSize) span.style.fontSize = `${fontSize * 96 / 72}px`;
1483
- if (bold !== void 0) span.style.fontWeight = bold ? "700" : "normal";
1487
+ span.style.fontFamily = resolveFontFamily(formatting.fontFamily).cssFallback;
1488
+ if (formatting.fontSize) span.style.fontSize = `${formatting.fontSize * 96 / 72}px`;
1489
+ if (formatting.bold !== void 0) span.style.fontWeight = formatting.bold ? "700" : "normal";
1490
+ if (formatting.italic !== void 0) span.style.fontStyle = formatting.italic ? "italic" : "normal";
1491
+ if (formatting.rtl !== void 0) span.dir = formatting.rtl ? "rtl" : "ltr";
1484
1492
  span.style.textAlign = "left";
1485
1493
  span.style.textAlignLast = "left";
1486
1494
  span.style.boxSizing = "border-box";
@@ -16,6 +16,8 @@ declare function resolveImageLineAlign(imageRun: ImageRun, paragraphAlignment: "
16
16
  type RenderContext = {
17
17
  /** Current page number (1-indexed) */
18
18
  pageNumber: number;
19
+ /** OOXML section-level format for the current logical page number. */
20
+ pageNumberFormat?: string;
19
21
  /** Total number of pages */
20
22
  totalPages: number;
21
23
  /** Which section is being rendered */
@@ -1,5 +1,5 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
- import { PageMargins } from "../layout-engine/types.js";
2
+ import { PageMargins, SectionPageNumbering } from "../layout-engine/types.js";
3
3
  //#region src/paged-layout/sectionGeometry.d.ts
4
4
  declare const DEFAULT_PAGE_WIDTH_PX = 816;
5
5
  declare const DEFAULT_PAGE_HEIGHT_PX = 1056;
@@ -16,5 +16,6 @@ declare const getPageSize: (sectionProps: document_d_exports.SectionProperties |
16
16
  h: number;
17
17
  };
18
18
  declare const getMargins: (sectionProps: document_d_exports.SectionProperties | null | undefined) => PageMargins;
19
+ declare const getPageNumbering: (sectionProps: document_d_exports.SectionProperties | null | undefined) => SectionPageNumbering;
19
20
  //#endregion
20
- export { DEFAULT_BODY_MARGIN_PX, DEFAULT_HEADER_FOOTER_DISTANCE_PX, DEFAULT_PAGE_HEIGHT_PX, DEFAULT_PAGE_WIDTH_PX, getMargins, getPageSize, twipsToPixels, twipsToPxOr };
21
+ export { DEFAULT_BODY_MARGIN_PX, DEFAULT_HEADER_FOOTER_DISTANCE_PX, DEFAULT_PAGE_HEIGHT_PX, DEFAULT_PAGE_WIDTH_PX, getMargins, getPageNumbering, getPageSize, twipsToPixels, twipsToPxOr };
@@ -32,5 +32,21 @@ const getMargins = (sectionProps) => ({
32
32
  header: twipsToPxOr(sectionProps?.headerDistance, 48),
33
33
  footer: twipsToPxOr(sectionProps?.footerDistance, 48)
34
34
  });
35
+ const getPageNumbering = (sectionProps) => {
36
+ const pageNumbering = sectionProps?.pageNumbering;
37
+ const format = pageNumbering?.format;
38
+ if (pageNumbering?.start === void 0) return format === void 0 ? { type: "continue" } : {
39
+ type: "continue",
40
+ format
41
+ };
42
+ return format === void 0 ? {
43
+ type: "restart",
44
+ start: pageNumbering.start
45
+ } : {
46
+ type: "restart",
47
+ start: pageNumbering.start,
48
+ format
49
+ };
50
+ };
35
51
  //#endregion
36
- export { DEFAULT_BODY_MARGIN_PX, DEFAULT_HEADER_FOOTER_DISTANCE_PX, DEFAULT_PAGE_HEIGHT_PX, DEFAULT_PAGE_WIDTH_PX, getMargins, getPageSize, twipsToPixels, twipsToPxOr };
52
+ export { DEFAULT_BODY_MARGIN_PX, DEFAULT_HEADER_FOOTER_DISTANCE_PX, DEFAULT_PAGE_HEIGHT_PX, DEFAULT_PAGE_WIDTH_PX, getMargins, getPageNumbering, getPageSize, twipsToPixels, twipsToPxOr };
@@ -162,9 +162,7 @@ const readParagraphAttrs = (node) => {
162
162
  optionalBoolean(attrs, "listIsLegal", "paragraph.attrs.listIsLegal", issues);
163
163
  optionalString(attrs, "listMarker", "paragraph.attrs.listMarker", issues);
164
164
  optionalBoolean(attrs, "listMarkerHidden", "paragraph.attrs.listMarkerHidden", issues);
165
- optionalString(attrs, "listMarkerFontFamily", "paragraph.attrs.listMarkerFontFamily", issues);
166
- optionalNumber(attrs, "listMarkerFontSize", "paragraph.attrs.listMarkerFontSize", issues);
167
- optionalBoolean(attrs, "listMarkerBold", "paragraph.attrs.listMarkerBold", issues);
165
+ optionalTextFormatting(attrs, "listMarkerFormatting", "paragraph.attrs.listMarkerFormatting", issues);
168
166
  optionalOneOf(attrs, "listMarkerAlignment", "paragraph.attrs.listMarkerAlignment", issues, [
169
167
  "left",
170
168
  "center",
@@ -276,6 +274,7 @@ const readTableAttrs = (node) => {
276
274
  "insideH",
277
275
  "insideV"
278
276
  ]);
277
+ optionalBoolean(attrs, "_resolvedBidi", "table.attrs._resolvedBidi", issues);
279
278
  optionalRecord(attrs, "_originalFormatting", "table.attrs._originalFormatting", issues);
280
279
  return attrsResult(attrs, issues);
281
280
  };
@@ -651,6 +650,10 @@ const readRunFormattingOverrideMarkAttrs = (mark) => {
651
650
  const issues = [];
652
651
  expectMarkType(mark, "runFormattingOverride", issues);
653
652
  for (const key of RUN_FORMATTING_OVERRIDE_FALSE_KEYS) optionalFalse(attrs, key, `runFormattingOverride.attrs.${key}`, issues);
653
+ optionalBoolean(attrs, "boldCs", "runFormattingOverride.attrs.boldCs", issues);
654
+ optionalBoolean(attrs, "italicCs", "runFormattingOverride.attrs.italicCs", issues);
655
+ optionalNumber(attrs, "fontSizeCs", "runFormattingOverride.attrs.fontSizeCs", issues);
656
+ optionalBoolean(attrs, "cs", "runFormattingOverride.attrs.cs", issues);
654
657
  optionalOneOf(attrs, "underline", "runFormattingOverride.attrs.underline", issues, ["none"]);
655
658
  return attrsResult(attrs, issues);
656
659
  };
@@ -1392,12 +1395,10 @@ const validateParagraphFormatting = (value, path, issues) => {
1392
1395
  "listIsBullet",
1393
1396
  "listIsLegal",
1394
1397
  "listMarkerHidden",
1395
- "listMarkerBold",
1396
1398
  "listMarkerAllCaps"
1397
1399
  ]) optionalBoolean(value, key, `${path}.${key}`, issues);
1398
1400
  for (const key of PARAGRAPH_FORMATTING_NUMBER_KEYS) optionalNumber(value, key, `${path}.${key}`, issues);
1399
1401
  for (const key of [
1400
- "listMarkerFontSize",
1401
1402
  "listImplicitChildLevelAdvances",
1402
1403
  "listMarkerSecondSlotOffsetTwips",
1403
1404
  "listAbstractNumId",
@@ -1407,7 +1408,7 @@ const validateParagraphFormatting = (value, path, issues) => {
1407
1408
  optionalString(value, "styleId", `${path}.styleId`, issues);
1408
1409
  optionalOneOf(value, "listNumFmt", `${path}.listNumFmt`, issues, NUMBER_FORMAT_VALUES);
1409
1410
  optionalString(value, "listMarker", `${path}.listMarker`, issues);
1410
- optionalString(value, "listMarkerFontFamily", `${path}.listMarkerFontFamily`, issues);
1411
+ optionalTextFormatting(value, "listMarkerFormatting", `${path}.listMarkerFormatting`, issues);
1411
1412
  optionalOneOf(value, "listMarkerAlignment", `${path}.listMarkerAlignment`, issues, [
1412
1413
  "left",
1413
1414
  "center",
@@ -4,6 +4,7 @@ import { OUTLINE_STYLE_CSS_ALIASES } from "../../types/documentEnumValues.js";
4
4
  import { pixelsToEmu } from "../../utils/units.js";
5
5
  import { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCharacterStyleMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHardBreakAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectLanguageMarkAttrs, expectMathAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs, expectRunPropertyChangeMarkAttrs, expectRunShadingMarkAttrs, expectSdtAttrs, expectShapeAttrs, expectStrikeMarkAttrs, expectSymbolAttrs, expectTabAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs } from "../attrs/index.js";
6
6
  import { autospacingMatchesBase, hasAutospacingBaseSide } from "../autospacingBase.js";
7
+ import { applyRunFormattingOverrideAttrs } from "../extensions/marks/RunFormattingOverrideExtension.js";
7
8
  import { directionToBidi } from "../paragraphDirection.js";
8
9
  import { RUN_FORMATTING_MARK_NAMES } from "../runFormattingMarkNames.js";
9
10
  import { expectTextBoxAnchorAttrs } from "../textBoxAnchorAttrs.js";
@@ -420,9 +421,7 @@ function listRenderingFromAttrs(attrs) {
420
421
  ...attrs.listIsLegal != null && { isLegal: attrs.listIsLegal },
421
422
  ...attrs.listNumFmt != null && { numFmt: attrs.listNumFmt },
422
423
  ...attrs.listMarkerHidden != null && { markerHidden: attrs.listMarkerHidden },
423
- ...attrs.listMarkerFontFamily != null && { markerFontFamily: attrs.listMarkerFontFamily },
424
- ...attrs.listMarkerFontSize != null && { markerFontSize: attrs.listMarkerFontSize },
425
- ...attrs.listMarkerBold != null && { markerBold: attrs.listMarkerBold },
424
+ ...attrs.listMarkerFormatting != null && { markerFormatting: attrs.listMarkerFormatting },
426
425
  ...attrs.listMarkerAlignment != null && { markerAlignment: attrs.listMarkerAlignment },
427
426
  ...attrs.listMarkerSuffix != null && { markerSuffix: attrs.listMarkerSuffix },
428
427
  ...attrs.listMarkerAllCaps != null && { markerAllCaps: attrs.listMarkerAllCaps },
@@ -1341,6 +1340,7 @@ function createShapeRun(node) {
1341
1340
  function marksToTextFormatting(marks) {
1342
1341
  const formatting = {};
1343
1342
  let characterStyleRPr;
1343
+ let runFormattingOverrideMark;
1344
1344
  for (const mark of marks) switch (mark.type.name) {
1345
1345
  case "bold":
1346
1346
  formatting.bold = true;
@@ -1453,7 +1453,7 @@ function marksToTextFormatting(marks) {
1453
1453
  formatting.effect = expectTextEffectMarkAttrs(mark).effect;
1454
1454
  break;
1455
1455
  case "runFormattingOverride":
1456
- applyRunFormattingOverrideAttrs(formatting, expectRunFormattingOverrideMarkAttrs(mark));
1456
+ runFormattingOverrideMark = mark;
1457
1457
  break;
1458
1458
  case "characterStyle": {
1459
1459
  const attrs = expectCharacterStyleMarkAttrs(mark);
@@ -1463,6 +1463,7 @@ function marksToTextFormatting(marks) {
1463
1463
  }
1464
1464
  default: break;
1465
1465
  }
1466
+ if (runFormattingOverrideMark) applyRunFormattingOverrideAttrs(formatting, expectRunFormattingOverrideMarkAttrs(runFormattingOverrideMark));
1466
1467
  if (characterStyleRPr) return subtractCharacterStyleFormatting(formatting, characterStyleRPr);
1467
1468
  return formatting;
1468
1469
  }
@@ -1522,21 +1523,6 @@ function subtractCharacterStyleFormatting(formatting, styleRPr) {
1522
1523
  }
1523
1524
  return result;
1524
1525
  }
1525
- function applyRunFormattingOverrideAttrs(formatting, attrs) {
1526
- if (attrs.bold === false) formatting.bold = false;
1527
- if (attrs.italic === false) formatting.italic = false;
1528
- if (attrs.underline === "none") formatting.underline = { style: "none" };
1529
- if (attrs.strike === false) formatting.strike = false;
1530
- if (attrs.doubleStrike === false) formatting.doubleStrike = false;
1531
- if (attrs.allCaps === false) formatting.allCaps = false;
1532
- if (attrs.smallCaps === false) formatting.smallCaps = false;
1533
- if (attrs.hidden === false) formatting.hidden = false;
1534
- if (attrs.emboss === false) formatting.emboss = false;
1535
- if (attrs.imprint === false) formatting.imprint = false;
1536
- if (attrs.shadow === false) formatting.shadow = false;
1537
- if (attrs.outline === false) formatting.outline = false;
1538
- if (attrs.rtl === false) formatting.rtl = false;
1539
- }
1540
1526
  /**
1541
1527
  * Convert a ProseMirror table node to our Table type
1542
1528
  */
@@ -290,9 +290,7 @@ function paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOver
290
290
  if (paragraph.listRendering?.isLegal) attrs.listIsLegal = paragraph.listRendering.isLegal;
291
291
  if (paragraph.listRendering?.marker) attrs.listMarker = paragraph.listRendering.marker;
292
292
  if (paragraph.listRendering?.markerHidden) attrs.listMarkerHidden = paragraph.listRendering.markerHidden;
293
- if (paragraph.listRendering?.markerFontFamily) attrs.listMarkerFontFamily = paragraph.listRendering.markerFontFamily;
294
- if (paragraph.listRendering?.markerFontSize) attrs.listMarkerFontSize = paragraph.listRendering.markerFontSize;
295
- if (paragraph.listRendering?.markerBold !== void 0) attrs.listMarkerBold = paragraph.listRendering.markerBold;
293
+ if (paragraph.listRendering?.markerFormatting) attrs.listMarkerFormatting = paragraph.listRendering.markerFormatting;
296
294
  if (paragraph.listRendering?.markerAlignment) attrs.listMarkerAlignment = paragraph.listRendering.markerAlignment;
297
295
  if (paragraph.listRendering?.markerSuffix) attrs.listMarkerSuffix = paragraph.listRendering.markerSuffix;
298
296
  if (paragraph.listRendering?.markerAllCaps) attrs.listMarkerAllCaps = paragraph.listRendering.markerAllCaps;
@@ -645,7 +643,9 @@ function convertTable(table, styleResolver, context) {
645
643
  const fallbackTableStyle = tableStyleId ? void 0 : defaultTableStyle;
646
644
  const conditionalTableStyleId = tableStyle?.styleId ?? fallbackTableStyle?.styleId;
647
645
  const resolvedTableBorders = table.formatting?.borders ?? tableStyle?.tblPr?.borders ?? fallbackTableStyle?.tblPr?.borders;
648
- const resolvedTableIndent = table.formatting?.indent ?? tableStyle?.tblPr?.indent ?? fallbackTableStyle?.tblPr?.indent;
646
+ const fallbackTableIndent = fallbackTableStyle?.tblPr?.indent;
647
+ const resolvedTableIndent = table.formatting?.indent ?? tableStyle?.tblPr?.indent ?? (fallbackTableIndent?.value === 0 ? void 0 : fallbackTableIndent);
648
+ const resolvedTableBidi = table.formatting?.bidi ?? tableStyle?.tblPr?.bidi ?? fallbackTableStyle?.tblPr?.bidi;
649
649
  const tableCellMargins = table.formatting?.cellMargins ?? tableStyle?.tblPr?.cellMargins ?? fallbackTableStyle?.tblPr?.cellMargins;
650
650
  let cellMarginsAttr;
651
651
  if (tableCellMargins) {
@@ -667,6 +667,7 @@ function convertTable(table, styleResolver, context) {
667
667
  if (table.formatting?.look) attrs.look = table.formatting.look;
668
668
  if (table.formatting?.borders) attrs.borders = table.formatting.borders;
669
669
  if (resolvedTableIndent) attrs._resolvedIndent = resolvedTableIndent;
670
+ if (resolvedTableBidi !== void 0) attrs._resolvedBidi = resolvedTableBidi;
670
671
  if (table.formatting) attrs._originalFormatting = table.formatting;
671
672
  if (table.propertyChanges && table.propertyChanges.length > 0) attrs.tblPrChange = [...table.propertyChanges];
672
673
  const conditionalStyles = {};
@@ -204,9 +204,7 @@ const paragraphNodeSpec = {
204
204
  listIsLegal: { default: null },
205
205
  listMarker: { default: null },
206
206
  listMarkerHidden: { default: null },
207
- listMarkerFontFamily: { default: null },
208
- listMarkerFontSize: { default: null },
209
- listMarkerBold: { default: null },
207
+ listMarkerFormatting: { default: null },
210
208
  listMarkerAlignment: { default: null },
211
209
  listMarkerSuffix: { default: null },
212
210
  listMarkerAllCaps: { default: null },
@@ -32,9 +32,7 @@ const LIST_FORMATTING_ATTRS = [
32
32
  "listNumFmt",
33
33
  "listMarker",
34
34
  "listMarkerHidden",
35
- "listMarkerFontFamily",
36
- "listMarkerFontSize",
37
- "listMarkerBold",
35
+ "listMarkerFormatting",
38
36
  "listMarkerAlignment",
39
37
  "listMarkerSuffix",
40
38
  "listLevelNumFmts",
@@ -1,10 +1,11 @@
1
1
  import { document_d_exports } from "../../../types/document.js";
2
+ import { RunFormattingOverrideAttrs } from "../../schema/marks.js";
2
3
  import { MarkExtension } from "../types.js";
3
4
  import { Mark } from "prosemirror-model";
4
5
  //#region src/prosemirror/extensions/marks/RunFormattingOverrideExtension.d.ts
5
- type RunFormattingOverrideAttrs = Record<string, false | "none">;
6
6
  declare function buildRunFormattingOverrideAttrs(formatting: document_d_exports.TextFormatting | undefined): RunFormattingOverrideAttrs | undefined;
7
+ declare function applyRunFormattingOverrideAttrs(formatting: document_d_exports.TextFormatting, attrs: RunFormattingOverrideAttrs): void;
7
8
  declare function applyRunFormattingOverrideMark(formatting: document_d_exports.TextFormatting, mark: Mark): void;
8
9
  declare const RunFormattingOverrideExtension: (options?: Partial<Record<string, unknown>> | undefined) => MarkExtension;
9
10
  //#endregion
10
- export { RunFormattingOverrideExtension, applyRunFormattingOverrideMark, buildRunFormattingOverrideAttrs };
11
+ export { RunFormattingOverrideExtension, applyRunFormattingOverrideAttrs, applyRunFormattingOverrideMark, buildRunFormattingOverrideAttrs };
@@ -4,23 +4,26 @@ import { createMarkExtension } from "../create.js";
4
4
  function buildRunFormattingOverrideAttrs(formatting) {
5
5
  if (!formatting) return;
6
6
  const attrs = {};
7
- if (formatting.bold === false) attrs["bold"] = false;
8
- if (formatting.italic === false) attrs["italic"] = false;
9
- if (formatting.underline?.style === "none") attrs["underline"] = "none";
10
- if (formatting.strike === false) attrs["strike"] = false;
11
- if (formatting.doubleStrike === false) attrs["doubleStrike"] = false;
12
- if (formatting.allCaps === false) attrs["allCaps"] = false;
13
- if (formatting.smallCaps === false) attrs["smallCaps"] = false;
14
- if (formatting.hidden === false) attrs["hidden"] = false;
15
- if (formatting.emboss === false) attrs["emboss"] = false;
16
- if (formatting.imprint === false) attrs["imprint"] = false;
17
- if (formatting.shadow === false) attrs["shadow"] = false;
18
- if (formatting.outline === false) attrs["outline"] = false;
19
- if (formatting.rtl === false) attrs["rtl"] = false;
7
+ if (formatting.bold === false) attrs.bold = false;
8
+ if (formatting.italic === false) attrs.italic = false;
9
+ if (formatting.underline?.style === "none") attrs.underline = "none";
10
+ if (formatting.strike === false) attrs.strike = false;
11
+ if (formatting.doubleStrike === false) attrs.doubleStrike = false;
12
+ if (formatting.allCaps === false) attrs.allCaps = false;
13
+ if (formatting.smallCaps === false) attrs.smallCaps = false;
14
+ if (formatting.hidden === false) attrs.hidden = false;
15
+ if (formatting.emboss === false) attrs.emboss = false;
16
+ if (formatting.imprint === false) attrs.imprint = false;
17
+ if (formatting.shadow === false) attrs.shadow = false;
18
+ if (formatting.outline === false) attrs.outline = false;
19
+ if (formatting.rtl === false) attrs.rtl = false;
20
+ if (formatting.boldCs !== void 0 && formatting.boldCs !== formatting.bold) attrs.boldCs = formatting.boldCs;
21
+ if (formatting.italicCs !== void 0 && formatting.italicCs !== formatting.italic) attrs.italicCs = formatting.italicCs;
22
+ if (formatting.fontSizeCs !== void 0 && formatting.fontSizeCs !== formatting.fontSize) attrs.fontSizeCs = formatting.fontSizeCs;
23
+ if (formatting.cs !== void 0) attrs.cs = formatting.cs;
20
24
  return Object.keys(attrs).length > 0 ? attrs : void 0;
21
25
  }
22
- function applyRunFormattingOverrideMark(formatting, mark) {
23
- const attrs = expectRunFormattingOverrideMarkAttrs(mark);
26
+ function applyRunFormattingOverrideAttrs(formatting, attrs) {
24
27
  if (attrs.bold === false) formatting.bold = false;
25
28
  if (attrs.italic === false) formatting.italic = false;
26
29
  if (attrs.underline === "none") formatting.underline = { style: "none" };
@@ -34,6 +37,13 @@ function applyRunFormattingOverrideMark(formatting, mark) {
34
37
  if (attrs.shadow === false) formatting.shadow = false;
35
38
  if (attrs.outline === false) formatting.outline = false;
36
39
  if (attrs.rtl === false) formatting.rtl = false;
40
+ if (attrs.boldCs !== void 0) formatting.boldCs = attrs.boldCs;
41
+ if (attrs.italicCs !== void 0) formatting.italicCs = attrs.italicCs;
42
+ if (attrs.fontSizeCs !== void 0) formatting.fontSizeCs = attrs.fontSizeCs;
43
+ if (attrs.cs !== void 0) formatting.cs = attrs.cs;
44
+ }
45
+ function applyRunFormattingOverrideMark(formatting, mark) {
46
+ applyRunFormattingOverrideAttrs(formatting, expectRunFormattingOverrideMarkAttrs(mark));
37
47
  }
38
48
  const RunFormattingOverrideExtension = createMarkExtension({
39
49
  name: "runFormattingOverride",
@@ -52,7 +62,11 @@ const RunFormattingOverrideExtension = createMarkExtension({
52
62
  imprint: { default: null },
53
63
  shadow: { default: null },
54
64
  outline: { default: null },
55
- rtl: { default: null }
65
+ rtl: { default: null },
66
+ boldCs: { default: null },
67
+ italicCs: { default: null },
68
+ fontSizeCs: { default: null },
69
+ cs: { default: null }
56
70
  },
57
71
  toDOM(mark) {
58
72
  const attrs = expectRunFormattingOverrideMarkAttrs(mark);
@@ -72,4 +86,4 @@ const RunFormattingOverrideExtension = createMarkExtension({
72
86
  }
73
87
  });
74
88
  //#endregion
75
- export { RunFormattingOverrideExtension, applyRunFormattingOverrideMark, buildRunFormattingOverrideAttrs };
89
+ export { RunFormattingOverrideExtension, applyRunFormattingOverrideAttrs, applyRunFormattingOverrideMark, buildRunFormattingOverrideAttrs };
@@ -154,6 +154,7 @@ const tableSpec = {
154
154
  look: { default: null },
155
155
  borders: { default: null },
156
156
  _resolvedIndent: { default: null },
157
+ _resolvedBidi: { default: null },
157
158
  _originalFormatting: { default: null },
158
159
  tblPrChange: { default: null },
159
160
  _suggestedInsert: { default: null }
@@ -122,6 +122,14 @@ type RunPropertyChangeMarkAttrs = {
122
122
  suggestionId?: string;
123
123
  };
124
124
  type RunFormattingOverrideAttrs = { [K in keyof Pick<document_d_exports.TextFormatting, "bold" | "italic" | "strike" | "doubleStrike" | "allCaps" | "smallCaps" | "hidden" | "emboss" | "imprint" | "shadow" | "outline" | "rtl">]?: false; } & {
125
+ /** Independent complex-script weight (`w:bCs`). */
126
+ boldCs?: boolean;
127
+ /** Force complex-script formatting for the full run (`w:cs`). */
128
+ cs?: boolean;
129
+ /** Independent complex-script size in half-points (`w:szCs`). */
130
+ fontSizeCs?: number;
131
+ /** Independent complex-script slant (`w:iCs`). */
132
+ italicCs?: boolean;
125
133
  underline?: "none";
126
134
  };
127
135
  /**
@@ -69,12 +69,8 @@ type ParagraphAttrs = {
69
69
  listMarker?: string;
70
70
  /** Whether the list marker is hidden (w:vanish on numbering level rPr) */
71
71
  listMarkerHidden?: boolean;
72
- /** Marker font family from numbering level rPr */
73
- listMarkerFontFamily?: string;
74
- /** Marker font size from numbering level rPr, in points */
75
- listMarkerFontSize?: number;
76
- /** Marker bold state from numbering level rPr */
77
- listMarkerBold?: boolean;
72
+ /** Canonical numbering-level marker typography, in OOXML units. */
73
+ listMarkerFormatting?: document_d_exports.ListMarkerFormatting;
78
74
  /** Horizontal alignment of the marker around the paragraph's list anchor. */
79
75
  listMarkerAlignment?: "left" | "center" | "right";
80
76
  /**
@@ -200,7 +196,7 @@ type ParagraphAttrs = {
200
196
  type ParagraphPropertyChangeAttrs = Omit<document_d_exports.ParagraphPropertyChange, "previousFormatting" | "currentFormatting"> & {
201
197
  previousFormatting?: Omit<document_d_exports.ParagraphFormatting, "numPr"> & {
202
198
  numPr?: document_d_exports.ParagraphFormatting["numPr"] | null;
203
- } & Partial<Pick<ParagraphAttrs, "listIsBullet" | "listIsLegal" | "listNumFmt" | "listMarker" | "listMarkerHidden" | "listMarkerFontFamily" | "listMarkerFontSize" | "listMarkerBold" | "listMarkerAlignment" | "listMarkerSuffix" | "listLevelNumFmts" | "listLevelStarts" | "listAbstractNumId" | "listStartOverride" | "lineSpacingExplicit" | "direction" | "_autospacingBase">>;
199
+ } & Partial<Pick<ParagraphAttrs, "listIsBullet" | "listIsLegal" | "listNumFmt" | "listMarker" | "listMarkerHidden" | "listMarkerFormatting" | "listMarkerAlignment" | "listMarkerSuffix" | "listLevelNumFmts" | "listLevelStarts" | "listAbstractNumId" | "listStartOverride" | "lineSpacingExplicit" | "direction" | "_autospacingBase">>;
204
200
  currentFormatting?: document_d_exports.ParagraphFormatting;
205
201
  };
206
202
  /**
@@ -555,6 +551,8 @@ type TableAttrs = {
555
551
  borders?: document_d_exports.TableBorders;
556
552
  /** Effective table indent after style resolution. PM-only; never serialized. */
557
553
  _resolvedIndent?: NonNullable<document_d_exports.TableFormatting["indent"]>;
554
+ /** Effective table direction after style resolution. PM-only; never serialized. */
555
+ _resolvedBidi?: boolean;
558
556
  /** Original table formatting from DOCX for lossless round-trip serialization */
559
557
  _originalFormatting?: document_d_exports.TableFormatting;
560
558
  /** Tracked table property changes (w:tblPrChange) for round-trip + accept/reject */
@@ -86,9 +86,7 @@ function listAttrsFromResolvedStyle(resolved, numbering) {
86
86
  listIsLegal: rendering?.isLegal ?? null,
87
87
  listMarker: rendering?.marker ?? null,
88
88
  listMarkerHidden: rendering?.markerHidden ?? null,
89
- listMarkerFontFamily: rendering?.markerFontFamily ?? null,
90
- listMarkerFontSize: rendering?.markerFontSize ?? null,
91
- listMarkerBold: rendering?.markerBold ?? null,
89
+ listMarkerFormatting: rendering?.markerFormatting ?? null,
92
90
  listMarkerAlignment: rendering?.markerAlignment ?? null,
93
91
  listMarkerSuffix: rendering?.markerSuffix ?? null,
94
92
  listMarkerAllCaps: rendering?.markerAllCaps ?? null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/folio-core",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
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.15.2",
116
+ "@stll/docx-core": "^0.16.0",
117
117
  "@stll/docx-utils": "^0.1.0",
118
118
  "@stll/template-conditions": "^0.1.0",
119
119
  "better-result": "3.0.1",