@stll/folio-core 0.1.3 → 0.3.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 (85) hide show
  1. package/dist/ai-edits/clean-text.d.ts +21 -1
  2. package/dist/ai-edits/clean-text.js +69 -1
  3. package/dist/ai-edits/headless.d.ts +49 -22
  4. package/dist/ai-edits/headless.js +101 -77
  5. package/dist/ai-edits/index.d.ts +3 -1
  6. package/dist/ai-edits/index.js +3 -1
  7. package/dist/ai-edits/read.d.ts +43 -0
  8. package/dist/ai-edits/read.js +91 -0
  9. package/dist/docx/index.d.ts +2 -0
  10. package/dist/docx/index.js +2 -0
  11. package/dist/docx/parser.js +1 -1
  12. package/dist/docx/wrapTypes.d.ts +8 -1
  13. package/dist/docx/wrapTypes.js +0 -8
  14. package/dist/i18n/messages/catalogs.gen.d.ts +9899 -0
  15. package/dist/i18n/messages/catalogs.gen.js +9831 -0
  16. package/dist/i18n/messages/messages.gen.d.ts +585 -0
  17. package/dist/i18n/messages/messages.gen.js +0 -0
  18. package/dist/i18n/messages.d.ts +13 -0
  19. package/dist/i18n/messages.js +39 -0
  20. package/dist/layout-bridge/cellSelectionHighlight.d.ts +18 -0
  21. package/dist/layout-bridge/cellSelectionHighlight.js +36 -0
  22. package/dist/layout-bridge/headerFooterLayout.d.ts +50 -0
  23. package/dist/layout-bridge/headerFooterLayout.js +216 -0
  24. package/dist/layout-bridge/measuring/measureBlocksPipeline.d.ts +33 -0
  25. package/dist/layout-bridge/measuring/measureBlocksPipeline.js +0 -0
  26. package/dist/layout-bridge/sectionColumns.d.ts +11 -0
  27. package/dist/layout-bridge/sectionColumns.js +21 -0
  28. package/dist/layout-bridge/tableInsertHover.d.ts +42 -0
  29. package/dist/layout-bridge/tableInsertHover.js +102 -0
  30. package/dist/layout-engine/index.d.ts +2 -1
  31. package/dist/layout-engine/index.js +2 -1
  32. package/dist/layout-engine/measure/measureParagraph.js +40 -5
  33. package/dist/layout-engine/pmPageIndex.d.ts +24 -0
  34. package/dist/layout-engine/pmPageIndex.js +37 -0
  35. package/dist/layout-painter/imageLayout.d.ts +101 -0
  36. package/dist/layout-painter/imageLayout.js +161 -0
  37. package/dist/layout-painter/renderPage.d.ts +16 -1
  38. package/dist/layout-painter/renderPage.js +28 -1
  39. package/dist/layout-painter/renderParagraph.js +1 -1
  40. package/dist/layout-painter/sdtBoundary.d.ts +24 -1
  41. package/dist/layout-painter/sdtBoundary.js +40 -1
  42. package/dist/managers/AutoSaveManager.d.ts +89 -0
  43. package/dist/managers/AutoSaveManager.js +279 -0
  44. package/dist/managers/ClipboardManager.d.ts +31 -0
  45. package/dist/managers/ClipboardManager.js +147 -0
  46. package/dist/paged-layout/rangeProjection.d.ts +23 -1
  47. package/dist/paged-layout/rangeProjection.js +34 -1
  48. package/dist/prosemirror/cellDragSelection.d.ts +34 -0
  49. package/dist/prosemirror/cellDragSelection.js +69 -0
  50. package/dist/prosemirror/commands/sectionBreak.d.ts +15 -0
  51. package/dist/prosemirror/commands/sectionBreak.js +54 -0
  52. package/dist/prosemirror/commentIdAllocator.d.ts +32 -0
  53. package/dist/prosemirror/commentIdAllocator.js +35 -0
  54. package/dist/prosemirror/commentOps.d.ts +35 -0
  55. package/dist/prosemirror/commentOps.js +104 -0
  56. package/dist/prosemirror/conversion/toProseDoc.js +29 -6
  57. package/dist/prosemirror/extensions/index.d.ts +3 -0
  58. package/dist/prosemirror/extensions/index.js +3 -0
  59. package/dist/prosemirror/imageCommit.d.ts +42 -0
  60. package/dist/prosemirror/imageCommit.js +118 -0
  61. package/dist/prosemirror/paraText.d.ts +26 -0
  62. package/dist/prosemirror/paraText.js +73 -0
  63. package/dist/prosemirror/plugins/templateDirectives.d.ts +21 -1
  64. package/dist/prosemirror/plugins/templateDirectives.js +40 -1
  65. package/dist/prosemirror/queries.d.ts +46 -0
  66. package/dist/prosemirror/queries.js +74 -0
  67. package/dist/prosemirror/styles/styleResolver.d.ts +43 -1
  68. package/dist/prosemirror/styles/styleResolver.js +34 -0
  69. package/dist/prosemirror/tableResize.d.ts +49 -0
  70. package/dist/prosemirror/tableResize.js +162 -0
  71. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +106 -0
  72. package/dist/prosemirror/utils/extractTrackedChanges.js +379 -0
  73. package/dist/prosemirror/utils/visualLineNavigation.d.ts +27 -0
  74. package/dist/prosemirror/utils/visualLineNavigation.js +217 -0
  75. package/dist/server.d.ts +2 -1
  76. package/dist/style-engine/index.d.ts +2 -2
  77. package/dist/style-engine/styleEngine.d.ts +14 -1
  78. package/dist/style-engine/styleEngine.js +15 -0
  79. package/dist/utils/colorResolver.d.ts +8 -1
  80. package/dist/utils/colorResolver.js +12 -1
  81. package/dist/utils/findVerticalScrollParent.d.ts +20 -0
  82. package/dist/utils/findVerticalScrollParent.js +30 -0
  83. package/dist/utils/fontResolver.d.ts +22 -3
  84. package/dist/utils/fontResolver.js +216 -31
  85. package/package.json +10 -1
@@ -23,6 +23,7 @@ function createStyleEngine(styleDefinitions, options) {
23
23
  const paragraphCache = /* @__PURE__ */ new Map();
24
24
  const runCache = /* @__PURE__ */ new Map();
25
25
  const ownPropsCache = /* @__PURE__ */ new Map();
26
+ let tableOverlayCache = /* @__PURE__ */ new WeakMap();
26
27
  let hits = 0;
27
28
  let misses = 0;
28
29
  const memoize = (cache, key, compute) => {
@@ -40,6 +41,18 @@ function createStyleEngine(styleDefinitions, options) {
40
41
  cache.set(key, { value });
41
42
  return value;
42
43
  };
44
+ const memoizeInTable = (styleId, tableParagraphOverlay) => {
45
+ if (!cacheEnabled) {
46
+ misses += 1;
47
+ return resolver.resolveParagraphStyleInTable(styleId, tableParagraphOverlay);
48
+ }
49
+ let overlayCache = tableOverlayCache.get(tableParagraphOverlay);
50
+ if (!overlayCache) {
51
+ overlayCache = /* @__PURE__ */ new Map();
52
+ tableOverlayCache.set(tableParagraphOverlay, overlayCache);
53
+ }
54
+ return memoize(overlayCache, cacheKey(styleId), () => resolver.resolveParagraphStyleInTable(styleId, tableParagraphOverlay));
55
+ };
43
56
  return {
44
57
  getStyle: (styleId) => resolver.getStyle(styleId),
45
58
  hasStyle: (styleId) => resolver.hasStyle(styleId),
@@ -50,12 +63,14 @@ function createStyleEngine(styleDefinitions, options) {
50
63
  getParagraphStyles: () => resolver.getParagraphStyles(),
51
64
  getTableStyles: () => resolver.getTableStyles(),
52
65
  resolveParagraphStyle: (styleId) => memoize(paragraphCache, cacheKey(styleId), () => resolver.resolveParagraphStyle(styleId)),
66
+ resolveParagraphStyleInTable: (styleId, tableParagraphOverlay) => tableParagraphOverlay === void 0 ? memoize(paragraphCache, cacheKey(styleId), () => resolver.resolveParagraphStyleInTable(styleId, void 0)) : memoizeInTable(styleId, tableParagraphOverlay),
53
67
  resolveRunStyle: (styleId) => memoize(runCache, cacheKey(styleId), () => resolver.resolveRunStyle(styleId)),
54
68
  getRunStyleOwnProperties: (styleId) => memoize(ownPropsCache, cacheKey(styleId), () => resolver.getRunStyleOwnProperties(styleId)),
55
69
  invalidate: () => {
56
70
  paragraphCache.clear();
57
71
  runCache.clear();
58
72
  ownPropsCache.clear();
73
+ tableOverlayCache = /* @__PURE__ */ new WeakMap();
59
74
  hits = 0;
60
75
  misses = 0;
61
76
  },
@@ -16,6 +16,13 @@ declare function resolveColor(color: document_d_exports.ColorValue | undefined |
16
16
  * @param highlight - Highlight color name (e.g., "yellow", "cyan")
17
17
  * @returns CSS color string or empty string for "none"
18
18
  */
19
+ /**
20
+ * Resolve a `ColorValue` to a bare 6-digit hex string (no leading `#`), or
21
+ * `undefined` for an `auto`/absent color. Theme colors resolve through the
22
+ * document theme; explicit `rgb` values are upper-cased. Used by adapters that
23
+ * need a normalized hex (e.g. comparing a cell fill against a swatch).
24
+ */
25
+ declare function resolveColorToHex(color: document_d_exports.ColorValue | undefined | null, theme: document_d_exports.Theme | null | undefined): string | undefined;
19
26
  declare function resolveHighlightColor(highlight: string | undefined): string;
20
27
  /**
21
28
  * Resolve a shading fill or pattern color to CSS
@@ -155,4 +162,4 @@ declare function generateThemeTintShadeMatrix(colorScheme?: document_d_exports.T
155
162
  */
156
163
  declare function colorsEqual(color1: document_d_exports.ColorValue | undefined | null, color2: document_d_exports.ColorValue | undefined | null, theme: document_d_exports.Theme | null | undefined): boolean;
157
164
  //#endregion
158
- export { STANDARD_TEXT_COLORS, ThemeMatrixCell, blendColors, colorsEqual, createRgbColor, createThemeColor, darkenColor, ensureHexPrefix, generateThemeTintShadeMatrix, getContrastingColor, getThemeTintShadeHex, isBlack, isWhite, lightenColor, parseColorString, resolveColor, resolveHighlightColor, resolveHighlightToCss, resolveShadingColor };
165
+ export { STANDARD_TEXT_COLORS, ThemeMatrixCell, blendColors, colorsEqual, createRgbColor, createThemeColor, darkenColor, ensureHexPrefix, generateThemeTintShadeMatrix, getContrastingColor, getThemeTintShadeHex, isBlack, isWhite, lightenColor, parseColorString, resolveColor, resolveColorToHex, resolveHighlightColor, resolveHighlightToCss, resolveShadingColor };
@@ -218,6 +218,17 @@ function resolveColor(color, theme, defaultColor = "000000") {
218
218
  * @param highlight - Highlight color name (e.g., "yellow", "cyan")
219
219
  * @returns CSS color string or empty string for "none"
220
220
  */
221
+ /**
222
+ * Resolve a `ColorValue` to a bare 6-digit hex string (no leading `#`), or
223
+ * `undefined` for an `auto`/absent color. Theme colors resolve through the
224
+ * document theme; explicit `rgb` values are upper-cased. Used by adapters that
225
+ * need a normalized hex (e.g. comparing a cell fill against a swatch).
226
+ */
227
+ function resolveColorToHex(color, theme) {
228
+ if (!color || color.auto) return void 0;
229
+ if (color.themeColor && theme) return resolveColor(color, theme).slice(1);
230
+ if (color.rgb && color.rgb !== "auto") return color.rgb.toUpperCase().replace(/^#/, "");
231
+ }
221
232
  function resolveHighlightColor(highlight) {
222
233
  if (!highlight || highlight === "none") return "";
223
234
  const hex = HIGHLIGHT_COLORS[highlight];
@@ -554,4 +565,4 @@ function colorsEqual(color1, color2, theme) {
554
565
  return resolveColor(color1, theme).toUpperCase() === resolveColor(color2, theme).toUpperCase();
555
566
  }
556
567
  //#endregion
557
- export { STANDARD_TEXT_COLORS, blendColors, colorsEqual, createRgbColor, createThemeColor, darkenColor, ensureHexPrefix, generateThemeTintShadeMatrix, getContrastingColor, getThemeTintShadeHex, isBlack, isWhite, lightenColor, parseColorString, resolveColor, resolveHighlightColor, resolveHighlightToCss, resolveShadingColor };
568
+ export { STANDARD_TEXT_COLORS, blendColors, colorsEqual, createRgbColor, createThemeColor, darkenColor, ensureHexPrefix, generateThemeTintShadeMatrix, getContrastingColor, getThemeTintShadeHex, isBlack, isWhite, lightenColor, parseColorString, resolveColor, resolveColorToHex, resolveHighlightColor, resolveHighlightToCss, resolveShadingColor };
@@ -0,0 +1,20 @@
1
+ //#region src/utils/findVerticalScrollParent.d.ts
2
+ /**
3
+ * Pure DOM helpers — locate the element that vertically scrolls the paginated
4
+ * editor. Shared by scroll-to-position, arrow-key line navigation, and drag
5
+ * auto-scroll so every framework adapter uses the same logic.
6
+ */
7
+ /**
8
+ * First ancestor of `el` with `overflow-y: auto|scroll` and a scrollable
9
+ * overflow height. The walk starts at `el.parentElement` (it does not treat
10
+ * `el` itself as the scroller). Returns `null` when no such ancestor exists
11
+ * before `document.documentElement`.
12
+ */
13
+ declare function findVerticalScrollParent(el: HTMLElement): HTMLElement | null;
14
+ /**
15
+ * Same as {@link findVerticalScrollParent} but falls back to
16
+ * `document.documentElement` so callers always get a valid scroll target.
17
+ */
18
+ declare function findVerticalScrollParentOrRoot(el: HTMLElement): HTMLElement;
19
+ //#endregion
20
+ export { findVerticalScrollParent, findVerticalScrollParentOrRoot };
@@ -0,0 +1,30 @@
1
+ //#region src/utils/findVerticalScrollParent.ts
2
+ /**
3
+ * Pure DOM helpers — locate the element that vertically scrolls the paginated
4
+ * editor. Shared by scroll-to-position, arrow-key line navigation, and drag
5
+ * auto-scroll so every framework adapter uses the same logic.
6
+ */
7
+ /**
8
+ * First ancestor of `el` with `overflow-y: auto|scroll` and a scrollable
9
+ * overflow height. The walk starts at `el.parentElement` (it does not treat
10
+ * `el` itself as the scroller). Returns `null` when no such ancestor exists
11
+ * before `document.documentElement`.
12
+ */
13
+ function findVerticalScrollParent(el) {
14
+ let parent = el.parentElement;
15
+ while (parent && parent !== document.documentElement) {
16
+ const { overflowY } = getComputedStyle(parent);
17
+ if ((overflowY === "auto" || overflowY === "scroll") && parent.scrollHeight > parent.clientHeight + 1) return parent;
18
+ parent = parent.parentElement;
19
+ }
20
+ return null;
21
+ }
22
+ /**
23
+ * Same as {@link findVerticalScrollParent} but falls back to
24
+ * `document.documentElement` so callers always get a valid scroll target.
25
+ */
26
+ function findVerticalScrollParentOrRoot(el) {
27
+ return findVerticalScrollParent(el) ?? document.documentElement;
28
+ }
29
+ //#endregion
30
+ export { findVerticalScrollParent, findVerticalScrollParentOrRoot };
@@ -7,7 +7,7 @@ type ResolvedFont = {
7
7
  /** Google Font name to load (null if no mapping available) */googleFont: string | null; /** CSS font-family value with proper fallback stack */
8
8
  cssFallback: string; /** Original font name from the DOCX */
9
9
  originalFont: string; /** Whether this font has a Google Fonts equivalent */
10
- hasGoogleEquivalent: boolean; /** OS/2 single-line ratio: (usWinAscent + usWinDescent) / unitsPerEm (no external leading) */
10
+ hasGoogleEquivalent: boolean; /** Single-line height ratio (single-line height ÷ font size). See `FontLineHeight`. */
11
11
  singleLineRatio: number;
12
12
  };
13
13
  /**
@@ -15,10 +15,29 @@ type ResolvedFont = {
15
15
  */
16
16
  type FontCategory = "sans-serif" | "serif" | "monospace" | "cursive" | "fantasy" | "system-ui";
17
17
  /**
18
- * Default OS/2 single-line ratio for unmapped fonts.
18
+ * Default single-line ratio for unmapped fonts.
19
19
  * Middle of the common range (1.07–1.27) for standard DOCX fonts.
20
20
  */
21
21
  declare const DEFAULT_SINGLE_LINE_RATIO = 1.15;
22
+ /**
23
+ * True when `family` is a known East-Asian (CJK) typeface — a direct
24
+ * `FONT_MAPPINGS` CJK entry or a romanized alias of one. Used by the measurer
25
+ * to decide whether a run's `w:eastAsia` font can supply the line height for
26
+ * CJK text, or whether Word would font-link to a default East-Asian face
27
+ * instead (see `CJK_FALLBACK_FONT_FAMILY`). Unmapped families return false:
28
+ * we cannot know their metrics, so the caller falls back.
29
+ */
30
+ declare function isCjkFont(family: string): boolean;
31
+ /**
32
+ * Font family whose `singleLineRatio` stands in for Word's default East-Asian
33
+ * face when a CJK-text run declares no usable CJK font (its `w:eastAsia` slot
34
+ * is absent or names a Latin face like "Century"). Word font-links those
35
+ * glyphs to the OS default East-Asian font and takes THAT face's taller line
36
+ * height; MS Mincho carries the measured ratio for it
37
+ * (`JP_MEASURED_LINE_HEIGHT`, ≈1.303). Line-height resolution only — never
38
+ * used for width measurement or painting.
39
+ */
40
+ declare const CJK_FALLBACK_FONT_FAMILY = "MS Mincho";
22
41
  declare function resolveFontFamily(docxFontName: string): ResolvedFont;
23
42
  /**
24
43
  * Resolve a theme font reference to actual font names
@@ -58,4 +77,4 @@ declare function getGoogleFontEquivalent(docxFontName: string): string | null;
58
77
  */
59
78
  declare function hasGoogleFontEquivalent(docxFontName: string): boolean;
60
79
  //#endregion
61
- export { DEFAULT_SINGLE_LINE_RATIO, ResolvedFont, buildFontFamilyString, getGoogleFontEquivalent, getGoogleFontsToLoad, hasGoogleFontEquivalent, resolveFontFamily, resolveThemeFont };
80
+ export { CJK_FALLBACK_FONT_FAMILY, DEFAULT_SINGLE_LINE_RATIO, ResolvedFont, buildFontFamilyString, getGoogleFontEquivalent, getGoogleFontsToLoad, hasGoogleFontEquivalent, isCjkFont, resolveFontFamily, resolveThemeFont };
@@ -1,20 +1,62 @@
1
1
  //#region src/utils/fontResolver.ts
2
2
  /**
3
- * Default OS/2 single-line ratio for unmapped fonts.
3
+ * Default single-line ratio for unmapped fonts.
4
4
  * Middle of the common range (1.07–1.27) for standard DOCX fonts.
5
5
  */
6
6
  const DEFAULT_SINGLE_LINE_RATIO = 1.15;
7
7
  /**
8
+ * Single-line height ratio (single-line height ÷ font size) for a
9
+ * `FontLineHeight`. This is the ONLY place the derivation formula lives.
10
+ *
11
+ * For `"hhea"` sources: `(hheaAscent + |hheaDescent| + hheaLineGap) / unitsPerEm`.
12
+ * This matches Word's rendered single-line pitch (11pt, single spacing) for
13
+ * every font measured against real Word output so far (16 fonts, no
14
+ * exceptions) — Word does NOT drop the font's line gap. Earlier revisions of
15
+ * this table hand-transcribed ratios from the OS/2 table and omitted the line
16
+ * gap for most fonts, which undershot Word's rendered line height for several
17
+ * of them (confirmed against real Word for cambria, trebuchet ms, palatino
18
+ * linotype, book antiqua, century gothic, consolas, and lucida console; arial
19
+ * and times new roman were fixed in a prior revision). Future readers: to
20
+ * correct or add a font, edit its `hhea*`/`unitsPerEm` metric fields, never the
21
+ * resulting ratio.
22
+ *
23
+ * Most CJK fonts are intentionally left on `DEFAULT_SINGLE_LINE_RATIO`: Word's
24
+ * East-Asian line height is not the run font's hhea ratio (it derives from the
25
+ * paragraph's `w:eastAsia` slot and East-Asian grid layout, not the ascii
26
+ * font), so a single per-font constant cannot capture it correctly. The
27
+ * Japanese Mincho/Gothic entries carry a `"measured"` ratio instead — see
28
+ * `JP_MEASURED_LINE_HEIGHT`.
29
+ */
30
+ const singleLineRatioOf = (lineHeight) => lineHeight.source === "hhea" ? (lineHeight.hheaAscent - lineHeight.hheaDescent + lineHeight.hheaLineGap) / lineHeight.unitsPerEm : lineHeight.ratio;
31
+ /**
32
+ * Word's East-Asian single-line height for the Japanese Mincho/Gothic faces,
33
+ * measured against real Word output on a NON-grid Japanese document
34
+ * (10.5pt body renders at 13.68pt line pitch → 13.68 / 10.5 ≈ 1.303). Word
35
+ * font-links CJK glyphs to the OS default East-Asian face and takes THAT
36
+ * face's height, so the declared font's own `hhea` table is not the source of
37
+ * truth here; a section with a `w:docGrid` line grid would override this value
38
+ * entirely (out of scope). The ratio is approximate for the CJK long tail
39
+ * (e.g. Yu Mincho renders taller, ≈1.60, when actually installed); it matches
40
+ * the common MS Mincho/Gothic default.
41
+ */
42
+ const JP_MEASURED_LINE_HEIGHT = {
43
+ source: "measured",
44
+ ratio: 1.303,
45
+ note: "Measured against real Word: 10.5pt Japanese body, non-grid section, renders at 13.68pt line pitch."
46
+ };
47
+ /**
8
48
  * Mapping of common DOCX fonts to Google Fonts equivalents
9
49
  *
10
50
  * These are metrically compatible fonts that preserve document layout.
11
51
  * See: https://wiki.archlinux.org/title/Metric-compatible_fonts
12
52
  *
13
- * singleLineRatio values are derived from each font's OS/2 table:
14
- * (usWinAscent + usWinDescent) / unitsPerEm
15
- * These define the Windows GDI "single line" height that OOXML lineRule="auto" uses.
16
- * sTypoLineGap (external leading) is NOT included Word excludes it from the
17
- * lineRule="auto" calculation (ECMA-376 §17.3.1.33).
53
+ * `singleLineRatio` values are computed by `singleLineRatioOf` from each
54
+ * font's `FontLineHeight`, never hand-written. For "hhea" entries the raw
55
+ * `hheaAscent`/`hheaDescent`/`hheaLineGap`/`unitsPerEm` fields below are read
56
+ * directly from the real font files' `hhea` table; the ratio is whatever
57
+ * falls out of the formula in `singleLineRatioOf`. "legacy" entries (e.g.
58
+ * garamond, lucida sans, lucida console) are unverified hand-transcribed
59
+ * ratios carried over unchanged, pending measurement.
18
60
  */
19
61
  const FONT_MAPPINGS = {
20
62
  calibri: {
@@ -27,7 +69,13 @@ const FONT_MAPPINGS = {
27
69
  "Helvetica",
28
70
  "sans-serif"
29
71
  ],
30
- singleLineRatio: 1.2207
72
+ singleLineRatio: singleLineRatioOf({
73
+ source: "hhea",
74
+ hheaAscent: 1950,
75
+ hheaDescent: -550,
76
+ hheaLineGap: 0,
77
+ unitsPerEm: 2048
78
+ })
31
79
  },
32
80
  cambria: {
33
81
  googleFont: "Caladea",
@@ -38,7 +86,13 @@ const FONT_MAPPINGS = {
38
86
  "Georgia",
39
87
  "serif"
40
88
  ],
41
- singleLineRatio: 1.2676
89
+ singleLineRatio: singleLineRatioOf({
90
+ source: "hhea",
91
+ hheaAscent: 1946,
92
+ hheaDescent: -455,
93
+ hheaLineGap: 0,
94
+ unitsPerEm: 2048
95
+ })
42
96
  },
43
97
  arial: {
44
98
  googleFont: "Arimo",
@@ -49,7 +103,13 @@ const FONT_MAPPINGS = {
49
103
  "Helvetica",
50
104
  "sans-serif"
51
105
  ],
52
- singleLineRatio: 1.1172
106
+ singleLineRatio: singleLineRatioOf({
107
+ source: "hhea",
108
+ hheaAscent: 1854,
109
+ hheaDescent: -434,
110
+ hheaLineGap: 67,
111
+ unitsPerEm: 2048
112
+ })
53
113
  },
54
114
  "times new roman": {
55
115
  googleFont: "Tinos",
@@ -60,7 +120,13 @@ const FONT_MAPPINGS = {
60
120
  "Times",
61
121
  "serif"
62
122
  ],
63
- singleLineRatio: 1.1074
123
+ singleLineRatio: singleLineRatioOf({
124
+ source: "hhea",
125
+ hheaAscent: 1825,
126
+ hheaDescent: -443,
127
+ hheaLineGap: 87,
128
+ unitsPerEm: 2048
129
+ })
64
130
  },
65
131
  "courier new": {
66
132
  googleFont: "Cousine",
@@ -71,7 +137,13 @@ const FONT_MAPPINGS = {
71
137
  "Courier",
72
138
  "monospace"
73
139
  ],
74
- singleLineRatio: 1.1328
140
+ singleLineRatio: singleLineRatioOf({
141
+ source: "hhea",
142
+ hheaAscent: 1705,
143
+ hheaDescent: -615,
144
+ hheaLineGap: 0,
145
+ unitsPerEm: 2048
146
+ })
75
147
  },
76
148
  georgia: {
77
149
  googleFont: "Tinos",
@@ -82,7 +154,13 @@ const FONT_MAPPINGS = {
82
154
  "Times New Roman",
83
155
  "serif"
84
156
  ],
85
- singleLineRatio: 1.1362
157
+ singleLineRatio: singleLineRatioOf({
158
+ source: "hhea",
159
+ hheaAscent: 1878,
160
+ hheaDescent: -449,
161
+ hheaLineGap: 0,
162
+ unitsPerEm: 2048
163
+ })
86
164
  },
87
165
  verdana: {
88
166
  googleFont: "Open Sans",
@@ -93,7 +171,13 @@ const FONT_MAPPINGS = {
93
171
  "Arial",
94
172
  "sans-serif"
95
173
  ],
96
- singleLineRatio: 1.2153
174
+ singleLineRatio: singleLineRatioOf({
175
+ source: "hhea",
176
+ hheaAscent: 2059,
177
+ hheaDescent: -430,
178
+ hheaLineGap: 0,
179
+ unitsPerEm: 2048
180
+ })
97
181
  },
98
182
  tahoma: {
99
183
  googleFont: "Open Sans",
@@ -104,7 +188,13 @@ const FONT_MAPPINGS = {
104
188
  "Arial",
105
189
  "sans-serif"
106
190
  ],
107
- singleLineRatio: 1.2075
191
+ singleLineRatio: singleLineRatioOf({
192
+ source: "hhea",
193
+ hheaAscent: 2049,
194
+ hheaDescent: -423,
195
+ hheaLineGap: 0,
196
+ unitsPerEm: 2048
197
+ })
108
198
  },
109
199
  "trebuchet ms": {
110
200
  googleFont: "Fira Sans",
@@ -115,7 +205,13 @@ const FONT_MAPPINGS = {
115
205
  "Arial",
116
206
  "sans-serif"
117
207
  ],
118
- singleLineRatio: 1.1431
208
+ singleLineRatio: singleLineRatioOf({
209
+ source: "hhea",
210
+ hheaAscent: 1923,
211
+ hheaDescent: -455,
212
+ hheaLineGap: 0,
213
+ unitsPerEm: 2048
214
+ })
119
215
  },
120
216
  "comic sans ms": {
121
217
  googleFont: "Comic Neue",
@@ -125,7 +221,13 @@ const FONT_MAPPINGS = {
125
221
  "Comic Neue",
126
222
  "cursive"
127
223
  ],
128
- singleLineRatio: 1.3936
224
+ singleLineRatio: singleLineRatioOf({
225
+ source: "hhea",
226
+ hheaAscent: 2257,
227
+ hheaDescent: -597,
228
+ hheaLineGap: 0,
229
+ unitsPerEm: 2048
230
+ })
129
231
  },
130
232
  impact: {
131
233
  googleFont: "Anton",
@@ -136,7 +238,13 @@ const FONT_MAPPINGS = {
136
238
  "Arial Black",
137
239
  "sans-serif"
138
240
  ],
139
- singleLineRatio: 1.2197
241
+ singleLineRatio: singleLineRatioOf({
242
+ source: "hhea",
243
+ hheaAscent: 2066,
244
+ hheaDescent: -432,
245
+ hheaLineGap: 0,
246
+ unitsPerEm: 2048
247
+ })
140
248
  },
141
249
  "palatino linotype": {
142
250
  googleFont: "EB Garamond",
@@ -148,7 +256,13 @@ const FONT_MAPPINGS = {
148
256
  "Georgia",
149
257
  "serif"
150
258
  ],
151
- singleLineRatio: 1.0259
259
+ singleLineRatio: singleLineRatioOf({
260
+ source: "hhea",
261
+ hheaAscent: 2150,
262
+ hheaDescent: -613,
263
+ hheaLineGap: 0,
264
+ unitsPerEm: 2048
265
+ })
152
266
  },
153
267
  "book antiqua": {
154
268
  googleFont: "EB Garamond",
@@ -160,7 +274,13 @@ const FONT_MAPPINGS = {
160
274
  "Georgia",
161
275
  "serif"
162
276
  ],
163
- singleLineRatio: 1.0259
277
+ singleLineRatio: singleLineRatioOf({
278
+ source: "hhea",
279
+ hheaAscent: 1891,
280
+ hheaDescent: -578,
281
+ hheaLineGap: 0,
282
+ unitsPerEm: 2048
283
+ })
164
284
  },
165
285
  garamond: {
166
286
  googleFont: "EB Garamond",
@@ -171,7 +291,11 @@ const FONT_MAPPINGS = {
171
291
  "Georgia",
172
292
  "serif"
173
293
  ],
174
- singleLineRatio: 1.068
294
+ singleLineRatio: singleLineRatioOf({
295
+ source: "legacy",
296
+ ratio: 1.068,
297
+ note: "Unverified hand-transcribed ratio; not yet measured against real Word output."
298
+ })
175
299
  },
176
300
  "century gothic": {
177
301
  googleFont: "Questrial",
@@ -182,7 +306,13 @@ const FONT_MAPPINGS = {
182
306
  "Arial",
183
307
  "sans-serif"
184
308
  ],
185
- singleLineRatio: 1.1611
309
+ singleLineRatio: singleLineRatioOf({
310
+ source: "hhea",
311
+ hheaAscent: 2060,
312
+ hheaDescent: -451,
313
+ hheaLineGap: 0,
314
+ unitsPerEm: 2048
315
+ })
186
316
  },
187
317
  "lucida sans": {
188
318
  googleFont: "Open Sans",
@@ -193,7 +323,11 @@ const FONT_MAPPINGS = {
193
323
  "Arial",
194
324
  "sans-serif"
195
325
  ],
196
- singleLineRatio: 1.1655
326
+ singleLineRatio: singleLineRatioOf({
327
+ source: "legacy",
328
+ ratio: 1.1655,
329
+ note: "Unverified hand-transcribed ratio; not yet measured against real Word output."
330
+ })
197
331
  },
198
332
  "lucida console": {
199
333
  googleFont: "Cousine",
@@ -204,7 +338,13 @@ const FONT_MAPPINGS = {
204
338
  "Courier New",
205
339
  "monospace"
206
340
  ],
207
- singleLineRatio: 1.1387
341
+ singleLineRatio: singleLineRatioOf({
342
+ source: "hhea",
343
+ hheaAscent: 1616,
344
+ hheaDescent: -432,
345
+ hheaLineGap: 0,
346
+ unitsPerEm: 2048
347
+ })
208
348
  },
209
349
  consolas: {
210
350
  googleFont: "Inconsolata",
@@ -216,7 +356,13 @@ const FONT_MAPPINGS = {
216
356
  "Courier New",
217
357
  "monospace"
218
358
  ],
219
- singleLineRatio: 1.1626
359
+ singleLineRatio: singleLineRatioOf({
360
+ source: "hhea",
361
+ hheaAscent: 1521,
362
+ hheaDescent: -527,
363
+ hheaLineGap: 350,
364
+ unitsPerEm: 2048
365
+ })
220
366
  },
221
367
  "ms mincho": {
222
368
  googleFont: "Noto Serif JP",
@@ -226,7 +372,7 @@ const FONT_MAPPINGS = {
226
372
  "Noto Serif JP",
227
373
  "serif"
228
374
  ],
229
- singleLineRatio: DEFAULT_SINGLE_LINE_RATIO
375
+ singleLineRatio: singleLineRatioOf(JP_MEASURED_LINE_HEIGHT)
230
376
  },
231
377
  "ms 明朝": {
232
378
  googleFont: "Noto Serif JP",
@@ -237,7 +383,7 @@ const FONT_MAPPINGS = {
237
383
  "Noto Serif JP",
238
384
  "serif"
239
385
  ],
240
- singleLineRatio: DEFAULT_SINGLE_LINE_RATIO
386
+ singleLineRatio: singleLineRatioOf(JP_MEASURED_LINE_HEIGHT)
241
387
  },
242
388
  "ms p明朝": {
243
389
  googleFont: "Noto Serif JP",
@@ -248,7 +394,7 @@ const FONT_MAPPINGS = {
248
394
  "Noto Serif JP",
249
395
  "serif"
250
396
  ],
251
- singleLineRatio: DEFAULT_SINGLE_LINE_RATIO
397
+ singleLineRatio: singleLineRatioOf(JP_MEASURED_LINE_HEIGHT)
252
398
  },
253
399
  "ms gothic": {
254
400
  googleFont: "Noto Sans JP",
@@ -258,7 +404,7 @@ const FONT_MAPPINGS = {
258
404
  "Noto Sans JP",
259
405
  "sans-serif"
260
406
  ],
261
- singleLineRatio: DEFAULT_SINGLE_LINE_RATIO
407
+ singleLineRatio: singleLineRatioOf(JP_MEASURED_LINE_HEIGHT)
262
408
  },
263
409
  "ms ゴシック": {
264
410
  googleFont: "Noto Sans JP",
@@ -269,7 +415,7 @@ const FONT_MAPPINGS = {
269
415
  "Noto Sans JP",
270
416
  "sans-serif"
271
417
  ],
272
- singleLineRatio: DEFAULT_SINGLE_LINE_RATIO
418
+ singleLineRatio: singleLineRatioOf(JP_MEASURED_LINE_HEIGHT)
273
419
  },
274
420
  "ms pゴシック": {
275
421
  googleFont: "Noto Sans JP",
@@ -280,7 +426,7 @@ const FONT_MAPPINGS = {
280
426
  "Noto Sans JP",
281
427
  "sans-serif"
282
428
  ],
283
- singleLineRatio: DEFAULT_SINGLE_LINE_RATIO
429
+ singleLineRatio: singleLineRatioOf(JP_MEASURED_LINE_HEIGHT)
284
430
  },
285
431
  simhei: {
286
432
  googleFont: "Noto Sans SC",
@@ -530,6 +676,45 @@ const CJK_FONT_ALIASES = {
530
676
  游明朝: "ms mincho"
531
677
  };
532
678
  /**
679
+ * The Noto families every CJK entry in `FONT_MAPPINGS` maps to. A mapping
680
+ * whose `googleFont` is one of these is an East-Asian face; this is the single
681
+ * classification source for `isCjkFont`, kept as an explicit set (not a name
682
+ * heuristic) so a future non-Noto CJK mapping must extend it deliberately.
683
+ */
684
+ const CJK_NOTO_FAMILIES = /* @__PURE__ */ new Set([
685
+ "Noto Serif JP",
686
+ "Noto Sans JP",
687
+ "Noto Serif SC",
688
+ "Noto Sans SC",
689
+ "Noto Serif TC",
690
+ "Noto Sans TC",
691
+ "Noto Serif KR",
692
+ "Noto Sans KR"
693
+ ]);
694
+ /**
695
+ * True when `family` is a known East-Asian (CJK) typeface — a direct
696
+ * `FONT_MAPPINGS` CJK entry or a romanized alias of one. Used by the measurer
697
+ * to decide whether a run's `w:eastAsia` font can supply the line height for
698
+ * CJK text, or whether Word would font-link to a default East-Asian face
699
+ * instead (see `CJK_FALLBACK_FONT_FAMILY`). Unmapped families return false:
700
+ * we cannot know their metrics, so the caller falls back.
701
+ */
702
+ function isCjkFont(family) {
703
+ const normalizedName = family.trim().toLowerCase();
704
+ const mapping = FONT_MAPPINGS[CJK_FONT_ALIASES[normalizedName] ?? normalizedName];
705
+ return mapping !== void 0 && CJK_NOTO_FAMILIES.has(mapping.googleFont);
706
+ }
707
+ /**
708
+ * Font family whose `singleLineRatio` stands in for Word's default East-Asian
709
+ * face when a CJK-text run declares no usable CJK font (its `w:eastAsia` slot
710
+ * is absent or names a Latin face like "Century"). Word font-links those
711
+ * glyphs to the OS default East-Asian font and takes THAT face's taller line
712
+ * height; MS Mincho carries the measured ratio for it
713
+ * (`JP_MEASURED_LINE_HEIGHT`, ≈1.303). Line-height resolution only — never
714
+ * used for width measurement or painting.
715
+ */
716
+ const CJK_FALLBACK_FONT_FAMILY = "MS Mincho";
717
+ /**
533
718
  * Resolve a DOCX font name to a Google Font and CSS fallback stack
534
719
  *
535
720
  * @param docxFontName - The font name from the DOCX file
@@ -648,4 +833,4 @@ function hasGoogleFontEquivalent(docxFontName) {
648
833
  return docxFontName.trim().toLowerCase() in FONT_MAPPINGS;
649
834
  }
650
835
  //#endregion
651
- export { DEFAULT_SINGLE_LINE_RATIO, buildFontFamilyString, getGoogleFontEquivalent, getGoogleFontsToLoad, hasGoogleFontEquivalent, resolveFontFamily, resolveThemeFont };
836
+ export { CJK_FALLBACK_FONT_FAMILY, DEFAULT_SINGLE_LINE_RATIO, buildFontFamilyString, getGoogleFontEquivalent, getGoogleFontsToLoad, hasGoogleFontEquivalent, isCjkFont, resolveFontFamily, resolveThemeFont };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/folio-core",
3
- "version": "0.1.3",
3
+ "version": "0.3.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",
@@ -69,10 +69,18 @@
69
69
  "types": "./dist/prosemirror/conversion/index.d.ts",
70
70
  "import": "./dist/prosemirror/conversion/index.js"
71
71
  },
72
+ "./prosemirror/extensions": {
73
+ "types": "./dist/prosemirror/extensions/index.d.ts",
74
+ "import": "./dist/prosemirror/extensions/index.js"
75
+ },
72
76
  "./prosemirror/schema": {
73
77
  "types": "./dist/prosemirror/schema/index.d.ts",
74
78
  "import": "./dist/prosemirror/schema/index.js"
75
79
  },
80
+ "./docx": {
81
+ "types": "./dist/docx/index.d.ts",
82
+ "import": "./dist/docx/index.js"
83
+ },
76
84
  "./*": {
77
85
  "types": "./dist/*.d.ts",
78
86
  "import": "./dist/*.js"
@@ -118,6 +126,7 @@
118
126
  "types": "./dist/index.d.ts",
119
127
  "files": [
120
128
  "dist",
129
+ "skills",
121
130
  "README.md",
122
131
  "LICENSE",
123
132
  "NOTICE.md"