@stll/folio-core 0.28.1 → 0.30.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 (125) hide show
  1. package/dist/controller/folioEditor.d.ts +28 -1
  2. package/dist/controller/folioEditor.js +37 -26
  3. package/dist/controller/fontReadiness.js +28 -24
  4. package/dist/controller/hiddenEditorManager.d.ts +2 -7
  5. package/dist/controller/layoutPipeline.js +33 -3
  6. package/dist/controller/noteEditorManager.d.ts +3 -6
  7. package/dist/docx/metafileSvg.d.ts +12 -0
  8. package/dist/docx/metafileSvg.js +462 -0
  9. package/dist/docx/paragraphTextBoxEnrichment.js +55 -4
  10. package/dist/docx/parser.d.ts +2 -2
  11. package/dist/docx/parser.js +26 -0
  12. package/dist/docx/runParser.js +2 -1
  13. package/dist/docx/serializer/runSerializer.js +14 -2
  14. package/dist/docx/styleParser.js +2 -1
  15. package/dist/docx/textBoxParser.js +14 -0
  16. package/dist/docx/vmlImageParser.js +72 -152
  17. package/dist/docx/vmlPreview.d.ts +31 -0
  18. package/dist/docx/vmlPreview.js +535 -0
  19. package/dist/docx/wrapTypes.d.ts +2 -36
  20. package/dist/docx/wrapTypes.js +1 -39
  21. package/dist/fonts/fontAlternates.d.ts +8 -0
  22. package/dist/fonts/fontAlternates.js +16 -0
  23. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  24. package/dist/layout-bridge/convert/footnoteLayout.js +34 -3
  25. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  26. package/dist/layout-bridge/convert/headerFooterLayout.js +5 -0
  27. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -2
  28. package/dist/layout-bridge/convert/toFlowBlocks.js +136 -120
  29. package/dist/layout-bridge/dom/noteStoryDom.d.ts +1 -1
  30. package/dist/layout-bridge/engine/selectionRects.js +3 -1
  31. package/dist/layout-engine/index.js +65 -32
  32. package/dist/layout-engine/justifiedLineFit.d.ts +7 -0
  33. package/dist/layout-engine/justifiedLineFit.js +6 -0
  34. package/dist/layout-engine/keep-together.js +15 -10
  35. package/dist/layout-engine/measure/complexScriptFormatting.d.ts +6 -1
  36. package/dist/layout-engine/measure/complexScriptFormatting.js +11 -2
  37. package/dist/layout-engine/measure/floatingTablePosition.d.ts +16 -1
  38. package/dist/layout-engine/measure/floatingTablePosition.js +29 -6
  39. package/dist/layout-engine/measure/lineBreakProvider.js +67 -8
  40. package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
  41. package/dist/layout-engine/measure/listMarkerWidth.js +23 -10
  42. package/dist/layout-engine/measure/measureBlocks.d.ts +15 -0
  43. package/dist/layout-engine/measure/measureBlocks.js +197 -30
  44. package/dist/layout-engine/measure/measureContainer.js +20 -24
  45. package/dist/layout-engine/measure/measureHelpers.d.ts +1 -1
  46. package/dist/layout-engine/measure/measureHelpers.js +14 -8
  47. package/dist/layout-engine/measure/measureParagraph.js +111 -37
  48. package/dist/layout-engine/measure/measureTypes.d.ts +3 -0
  49. package/dist/layout-engine/measure/tabCalculator.d.ts +123 -0
  50. package/dist/layout-engine/measure/tabCalculator.js +144 -0
  51. package/dist/layout-engine/measure/tableCellFlow.d.ts +21 -1
  52. package/dist/layout-engine/measure/tableCellFlow.js +45 -8
  53. package/dist/layout-engine/measure/tableInlinePlacement.d.ts +10 -2
  54. package/dist/layout-engine/measure/tableInlinePlacement.js +17 -15
  55. package/dist/layout-engine/paginator.d.ts +26 -2
  56. package/dist/layout-engine/paginator.js +76 -14
  57. package/dist/layout-engine/paragraphSpacing.d.ts +3 -1
  58. package/dist/layout-engine/paragraphSpacing.js +4 -3
  59. package/dist/layout-engine/renderedBreakReconciliation.d.ts +14 -3
  60. package/dist/layout-engine/renderedBreakReconciliation.js +45 -14
  61. package/dist/layout-engine/section-breaks.d.ts +3 -1
  62. package/dist/layout-engine/section-breaks.js +6 -2
  63. package/dist/layout-engine/types.d.ts +18 -1
  64. package/dist/layout-engine/types.js +1 -1
  65. package/dist/layout-painter/imageLayout.d.ts +1 -1
  66. package/dist/layout-painter/renderPage.js +6 -1
  67. package/dist/layout-painter/renderParagraph.js +169 -82
  68. package/dist/layout-painter/renderTable.d.ts +1 -1
  69. package/dist/layout-painter/renderTable.js +18 -7
  70. package/dist/layout-painter/renderTextBox.d.ts +1 -1
  71. package/dist/layout-painter/renderTextBox.js +6 -1
  72. package/dist/markdown/renderParagraph.js +7 -2
  73. package/dist/model.d.ts +3 -3
  74. package/dist/model.js +2 -2
  75. package/dist/paged-layout/sectionBlockWidths.d.ts +14 -0
  76. package/dist/paged-layout/sectionBlockWidths.js +103 -14
  77. package/dist/prosemirror/attrs/index.js +14 -2
  78. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +8 -0
  79. package/dist/prosemirror/bookmarkBoundaryAttrs.js +66 -0
  80. package/dist/prosemirror/conversion/fromProseDoc.js +176 -54
  81. package/dist/prosemirror/conversion/toProseDoc.js +179 -64
  82. package/dist/prosemirror/extensions/StarterKit.js +11 -3
  83. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +8 -4
  84. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  85. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +9 -5
  86. package/dist/prosemirror/extensions/features/pasteCleanup.d.ts +10 -23
  87. package/dist/prosemirror/extensions/features/pasteCleanup.js +77 -22
  88. package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +9 -5
  89. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +9 -0
  90. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +67 -0
  91. package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +10 -4
  92. package/dist/prosemirror/extensions/nodes/FieldExtension.js +77 -59
  93. package/dist/prosemirror/extensions/nodes/TableExtension.js +2 -0
  94. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +4 -1
  95. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +4 -3
  96. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +6 -2
  97. package/dist/prosemirror/listMarker.d.ts +58 -0
  98. package/dist/prosemirror/listMarker.js +185 -0
  99. package/dist/prosemirror/numberedRefFields.d.ts +24 -0
  100. package/dist/prosemirror/numberedRefFields.js +276 -0
  101. package/dist/prosemirror/paraText.js +56 -17
  102. package/dist/prosemirror/plugins/anonymizationDecorations.js +1 -1
  103. package/dist/prosemirror/plugins/pmTextScan.d.ts +3 -1
  104. package/dist/prosemirror/plugins/pmTextScan.js +28 -7
  105. package/dist/prosemirror/plugins/templateDirectives.js +2 -2
  106. package/dist/prosemirror/schema/index.d.ts +2 -2
  107. package/dist/prosemirror/schema/nodes.d.ts +17 -1
  108. package/dist/prosemirror/utils/tabCalculator.d.ts +2 -123
  109. package/dist/prosemirror/utils/tabCalculator.js +1 -140
  110. package/dist/prosemirror/validation.js +93 -0
  111. package/dist/render-dom/RemoteSelectionOverlay.d.ts +4 -4
  112. package/dist/types/documentEnumValues.d.ts +4 -1
  113. package/dist/types/documentEnumValues.js +12 -1
  114. package/dist/types/editor-story.d.ts +8 -0
  115. package/dist/types/editor-story.js +0 -0
  116. package/dist/types/remote-selection.d.ts +11 -0
  117. package/dist/types/remote-selection.js +0 -0
  118. package/dist/types/wrap.d.ts +36 -0
  119. package/dist/types/wrap.js +40 -0
  120. package/dist/utils/fontResolver.d.ts +1 -1
  121. package/dist/utils/fontResolver.js +42 -2
  122. package/dist/utils/formatToStyle.js +4 -2
  123. package/dist/utils/horizontalScale.d.ts +28 -0
  124. package/dist/utils/horizontalScale.js +42 -0
  125. package/package.json +2 -2
@@ -813,7 +813,7 @@ let embeddedFontFamilyMap = null;
813
813
  const setEmbeddedFontFamilyMap = (map) => {
814
814
  embeddedFontFamilyMap = map;
815
815
  };
816
- function resolveFontFamily(docxFontName) {
816
+ function resolveFontFamily(docxFontName, alternateFontName) {
817
817
  const normalizedName = docxFontName.trim().toLowerCase();
818
818
  const scopedFamily = embeddedFontFamilyMap?.get(docxFontName) ?? null;
819
819
  const aliasTarget = CJK_FONT_ALIASES[normalizedName];
@@ -821,13 +821,41 @@ function resolveFontFamily(docxFontName) {
821
821
  if (mapping) {
822
822
  const fallbackStack = aliasTarget && !mapping.fallbackStack.some((f) => f.toLowerCase() === normalizedName) ? [docxFontName, ...mapping.fallbackStack] : mapping.fallbackStack;
823
823
  const primaryStack = scopedFamily ? [scopedFamily, ...fallbackStack.filter((f) => f.toLowerCase() !== normalizedName)] : fallbackStack;
824
- return {
824
+ if (alternateFontName === void 0 || alternateFontName.trim().length === 0 || alternateFontName.trim().toLowerCase() === normalizedName) return {
825
825
  googleFont: googleFontsEnabled ? mapping.googleFont : null,
826
826
  cssFallback: withArabicFallback(primaryStack.map(quoteFontName).join(", ")),
827
827
  originalFont: docxFontName,
828
828
  hasGoogleEquivalent: googleFontsEnabled,
829
829
  singleLineRatio: mapping.singleLineRatio
830
830
  };
831
+ const alternate = resolveFontFamily(alternateFontName);
832
+ const category = mapping.category;
833
+ const concreteStack = dedupeFontFamilies([
834
+ scopedFamily ?? docxFontName,
835
+ ...parseFontFamilyList(alternate.cssFallback),
836
+ ...primaryStack
837
+ ]).filter((family) => !isCssGenericFontFamily(family));
838
+ const cssFallback = withArabicFallback([...concreteStack, category].map(quoteFontName).join(", "));
839
+ return {
840
+ googleFont: googleFontsEnabled ? mapping.googleFont : null,
841
+ cssFallback,
842
+ originalFont: docxFontName,
843
+ hasGoogleEquivalent: googleFontsEnabled,
844
+ singleLineRatio: mapping.singleLineRatio
845
+ };
846
+ }
847
+ const alternateName = alternateFontName?.trim();
848
+ if (alternateName && alternateName.toLowerCase() !== normalizedName) {
849
+ const alternate = resolveFontFamily(alternateName);
850
+ const category = detectFontCategory(docxFontName);
851
+ const concreteStack = dedupeFontFamilies([scopedFamily ?? docxFontName, ...parseFontFamilyList(alternate.cssFallback)]).filter((family) => !isCssGenericFontFamily(family));
852
+ return {
853
+ googleFont: googleFontsEnabled ? alternate.googleFont : null,
854
+ cssFallback: withArabicFallback([...concreteStack, category].map(quoteFontName).join(", ")),
855
+ originalFont: docxFontName,
856
+ hasGoogleEquivalent: googleFontsEnabled && alternate.hasGoogleEquivalent,
857
+ singleLineRatio: alternate.singleLineRatio
858
+ };
831
859
  }
832
860
  const category = detectFontCategory(docxFontName);
833
861
  const defaultFallback = DEFAULT_FALLBACKS[category];
@@ -839,6 +867,18 @@ function resolveFontFamily(docxFontName) {
839
867
  singleLineRatio: DEFAULT_SINGLE_LINE_RATIO
840
868
  };
841
869
  }
870
+ const isCssGenericFontFamily = (family) => /^(?:sans-serif|serif|monospace|cursive|fantasy|system-ui)$/u.test(family.trim().toLowerCase());
871
+ const dedupeFontFamilies = (families) => {
872
+ const seen = /* @__PURE__ */ new Set();
873
+ const result = [];
874
+ for (const family of families) {
875
+ const normalized = family.trim().toLowerCase();
876
+ if (!normalized || seen.has(normalized)) continue;
877
+ seen.add(normalized);
878
+ result.push(family.trim());
879
+ }
880
+ return result;
881
+ };
842
882
  const CSS_NEWLINE_ESCAPES = {
843
883
  "\n": "\\a ",
844
884
  "\r": "\\d ",
@@ -1,5 +1,6 @@
1
1
  import { resolveColor, resolveHighlightToCss, resolveShadingColor } from "./colorResolver.js";
2
2
  import { resolveFontFamily, resolveThemeFont } from "./fontResolver.js";
3
+ import { getHorizontalScaleFactor, normalizeHorizontalScalePercent } from "./horizontalScale.js";
3
4
  import { AUTO_PARAGRAPH_SPACING_PX, eighthsToPixels, formatPx, halfPointsToPixels, halfPointsToPoints, twipsToPixels } from "./units.js";
4
5
  //#region src/utils/formatToStyle.ts
5
6
  /**
@@ -63,8 +64,9 @@ function textToStyle(formatting, theme) {
63
64
  if (formatting.allCaps) style.textTransform = "uppercase";
64
65
  else if (formatting.smallCaps) style.fontVariant = "small-caps";
65
66
  if (formatting.spacing !== void 0 && formatting.spacing !== 0) style.letterSpacing = formatPx(twipsToPixels(formatting.spacing));
66
- if (formatting.scale !== void 0 && formatting.scale !== 100) {
67
- style.transform = `scaleX(${formatting.scale / 100})`;
67
+ const horizontalScale = normalizeHorizontalScalePercent(formatting.scale);
68
+ if (horizontalScale !== void 0 && horizontalScale !== 100) {
69
+ style.transform = `scaleX(${getHorizontalScaleFactor(horizontalScale)})`;
68
70
  style.display = "inline-block";
69
71
  }
70
72
  if (formatting.hidden) style.display = "none";
@@ -0,0 +1,28 @@
1
+ //#region src/utils/horizontalScale.d.ts
2
+ /**
3
+ * Normalize OOXML `w:w` horizontal text scale percentages.
4
+ *
5
+ * The OOXML value is bounded from 0% through 600%. Missing or malformed
6
+ * values use the document default (100%) and stay absent in the model.
7
+ */
8
+ declare function normalizeHorizontalScalePercent(horizontalScale: number | null | undefined): number | undefined;
9
+ /**
10
+ * Parse the lexical forms accepted by OOXML `ST_TextScale`.
11
+ *
12
+ * Decimal values use the XML Schema integer grammar; percent values use the
13
+ * unsigned decimal grammar defined by `ST_TextScalePercent`. Surrounding
14
+ * whitespace is ignored, but an empty value or partial numeric prefix is not.
15
+ */
16
+ declare function parseHorizontalScalePercent(horizontalScale: string | null | undefined): number | undefined;
17
+ declare function getHorizontalScaleFactor(horizontalScale: number | null | undefined): number;
18
+ /**
19
+ * Quantize an in-range model value for OOXML's integer-only representation.
20
+ *
21
+ * Imported documents use strict integer normalization. Serialization also
22
+ * tolerates finite floating-point residue produced by editing calculations,
23
+ * preserving the existing integer-attribute contract without emitting an
24
+ * invalid fractional value.
25
+ */
26
+ declare function roundHorizontalScalePercentForSerialization(horizontalScale: number | null | undefined): number | undefined;
27
+ //#endregion
28
+ export { getHorizontalScaleFactor, normalizeHorizontalScalePercent, parseHorizontalScalePercent, roundHorizontalScalePercentForSerialization };
@@ -0,0 +1,42 @@
1
+ //#region src/utils/horizontalScale.ts
2
+ /**
3
+ * Normalize OOXML `w:w` horizontal text scale percentages.
4
+ *
5
+ * The OOXML value is bounded from 0% through 600%. Missing or malformed
6
+ * values use the document default (100%) and stay absent in the model.
7
+ */
8
+ function normalizeHorizontalScalePercent(horizontalScale) {
9
+ if (horizontalScale === null || horizontalScale === void 0 || !Number.isFinite(horizontalScale) || !Number.isInteger(horizontalScale) || horizontalScale < 0 || horizontalScale > 600) return;
10
+ return horizontalScale;
11
+ }
12
+ /**
13
+ * Parse the lexical forms accepted by OOXML `ST_TextScale`.
14
+ *
15
+ * Decimal values use the XML Schema integer grammar; percent values use the
16
+ * unsigned decimal grammar defined by `ST_TextScalePercent`. Surrounding
17
+ * whitespace is ignored, but an empty value or partial numeric prefix is not.
18
+ */
19
+ function parseHorizontalScalePercent(horizontalScale) {
20
+ if (horizontalScale === null || horizontalScale === void 0) return;
21
+ const lexicalValue = horizontalScale.trim();
22
+ if (!/^(?:[+-]?\d+|\d+%)$/u.test(lexicalValue)) return;
23
+ const numericValue = lexicalValue.endsWith("%") ? lexicalValue.slice(0, -1) : lexicalValue;
24
+ return normalizeHorizontalScalePercent(Number(numericValue));
25
+ }
26
+ function getHorizontalScaleFactor(horizontalScale) {
27
+ return (normalizeHorizontalScalePercent(horizontalScale) ?? 100) / 100;
28
+ }
29
+ /**
30
+ * Quantize an in-range model value for OOXML's integer-only representation.
31
+ *
32
+ * Imported documents use strict integer normalization. Serialization also
33
+ * tolerates finite floating-point residue produced by editing calculations,
34
+ * preserving the existing integer-attribute contract without emitting an
35
+ * invalid fractional value.
36
+ */
37
+ function roundHorizontalScalePercentForSerialization(horizontalScale) {
38
+ if (horizontalScale === null || horizontalScale === void 0 || !Number.isFinite(horizontalScale) || horizontalScale < 0 || horizontalScale > 600) return;
39
+ return Math.round(horizontalScale);
40
+ }
41
+ //#endregion
42
+ export { getHorizontalScaleFactor, normalizeHorizontalScalePercent, parseHorizontalScalePercent, roundHorizontalScalePercentForSerialization };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/folio-core",
3
- "version": "0.28.1",
3
+ "version": "0.30.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.17.2",
116
+ "@stll/docx-core": "^0.17.3",
117
117
  "@stll/docx-utils": "^0.1.0",
118
118
  "@stll/template-conditions": "^0.1.0",
119
119
  "better-result": "3.0.1",