@stll/folio-core 0.16.0 → 0.17.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.
@@ -1,19 +1,34 @@
1
1
  //#region src/utils/scriptSegments.d.ts
2
2
  /**
3
- * Script segmentation for per-character East-Asian font selection.
3
+ * Script segmentation for per-character font selection.
4
4
  *
5
- * Word resolves a run's font per character: East-Asian (CJK) code points use
6
- * the run's `w:eastAsia` font slot, everything else uses `w:ascii`/`w:hAnsi`.
5
+ * Word resolves a run's font per character across three slots: East-Asian code
6
+ * points use `w:eastAsia`, complex-script code points (Arabic, Hebrew, Indic,
7
+ * South-East Asian) use `w:cs`, and everything else uses `w:ascii`/`w:hAnsi`.
7
8
  * folio mirrors this by splitting a run's text into maximal same-script
8
9
  * segments that the measurer and the painter both consume, so line wrapping
9
10
  * stays in sync with rendering.
10
11
  *
12
+ * A three-value discriminator rather than the `isCjk` boolean this started as:
13
+ * the question is "which font slot does this character take?", which already
14
+ * has three answers and may grow again.
15
+ *
11
16
  * Ranges are authored with `\u` escapes only — never pasted glyphs (a pasted
12
17
  * glyph once silently corrupted an RTL character class here).
13
18
  */
19
+ /** Which of Word's font slots a code point selects. */
20
+ declare const SCRIPT_CLASS: {
21
+ /** `w:eastAsia` — CJK ideographs, kana, Hangul. */
22
+ readonly eastAsia: "eastAsia";
23
+ /** `w:cs` — Arabic, Hebrew, Indic, South-East Asian. */
24
+ readonly complex: "complex";
25
+ /** `w:ascii` / `w:hAnsi` — everything else. */
26
+ readonly western: "western";
27
+ };
28
+ type ScriptClass = (typeof SCRIPT_CLASS)[keyof typeof SCRIPT_CLASS];
14
29
  type ScriptSegment = {
15
30
  text: string;
16
- isCjk: boolean;
31
+ script: ScriptClass;
17
32
  };
18
33
  /**
19
34
  * True when a code point belongs to an East-Asian script that Word renders with
@@ -26,6 +41,22 @@ declare function isCjkCodePoint(cp: number): boolean;
26
41
  * this to skip segmentation entirely on the common all-Latin path.
27
42
  */
28
43
  declare function hasCjk(text: string): boolean;
44
+ /**
45
+ * True when a code point belongs to a script Word renders with the `w:cs`
46
+ * (complex script) font slot: the bidirectional scripts plus the Indic and
47
+ * South-East Asian scripts that need contextual shaping.
48
+ *
49
+ * Checked after CJK, which owns the ranges above U+2E80 that would otherwise
50
+ * overlap the presentation forms below.
51
+ */
52
+ declare function isComplexScriptCodePoint(cp: number): boolean;
53
+ /** Which font slot a single code point selects. CJK wins where ranges meet. */
54
+ declare function scriptClassOf(cp: number): ScriptClass;
55
+ /**
56
+ * True when the text contains at least one complex-script code point. Callers
57
+ * use this to skip segmentation entirely on the common all-Latin path.
58
+ */
59
+ declare function hasComplexScript(text: string): boolean;
29
60
  /**
30
61
  * Split text into maximal runs of one script class (CJK vs. non-CJK). Iterates
31
62
  * by code point so astral ideographs (surrogate pairs) are never split between
@@ -33,4 +64,4 @@ declare function hasCjk(text: string): boolean;
33
64
  */
34
65
  declare function segmentByScript(text: string): ScriptSegment[];
35
66
  //#endregion
36
- export { ScriptSegment, hasCjk, isCjkCodePoint, segmentByScript };
67
+ export { SCRIPT_CLASS, ScriptClass, ScriptSegment, hasCjk, hasComplexScript, isCjkCodePoint, isComplexScriptCodePoint, scriptClassOf, segmentByScript };
@@ -1,5 +1,31 @@
1
1
  //#region src/utils/scriptSegments.ts
2
2
  /**
3
+ * Script segmentation for per-character font selection.
4
+ *
5
+ * Word resolves a run's font per character across three slots: East-Asian code
6
+ * points use `w:eastAsia`, complex-script code points (Arabic, Hebrew, Indic,
7
+ * South-East Asian) use `w:cs`, and everything else uses `w:ascii`/`w:hAnsi`.
8
+ * folio mirrors this by splitting a run's text into maximal same-script
9
+ * segments that the measurer and the painter both consume, so line wrapping
10
+ * stays in sync with rendering.
11
+ *
12
+ * A three-value discriminator rather than the `isCjk` boolean this started as:
13
+ * the question is "which font slot does this character take?", which already
14
+ * has three answers and may grow again.
15
+ *
16
+ * Ranges are authored with `\u` escapes only — never pasted glyphs (a pasted
17
+ * glyph once silently corrupted an RTL character class here).
18
+ */
19
+ /** Which of Word's font slots a code point selects. */
20
+ const SCRIPT_CLASS = {
21
+ /** `w:eastAsia` — CJK ideographs, kana, Hangul. */
22
+ eastAsia: "eastAsia",
23
+ /** `w:cs` — Arabic, Hebrew, Indic, South-East Asian. */
24
+ complex: "complex",
25
+ /** `w:ascii` / `w:hAnsi` — everything else. */
26
+ western: "western"
27
+ };
28
+ /**
3
29
  * True when a code point belongs to an East-Asian script that Word renders with
4
30
  * the `w:eastAsia` font slot: CJK ideographs, kana, Hangul, CJK symbols and
5
31
  * punctuation, and fullwidth/halfwidth forms.
@@ -16,6 +42,31 @@ function hasCjk(text) {
16
42
  return false;
17
43
  }
18
44
  /**
45
+ * True when a code point belongs to a script Word renders with the `w:cs`
46
+ * (complex script) font slot: the bidirectional scripts plus the Indic and
47
+ * South-East Asian scripts that need contextual shaping.
48
+ *
49
+ * Checked after CJK, which owns the ranges above U+2E80 that would otherwise
50
+ * overlap the presentation forms below.
51
+ */
52
+ function isComplexScriptCodePoint(cp) {
53
+ return cp >= 1424 && cp <= 1535 || cp >= 1536 && cp <= 1791 || cp >= 1792 && cp <= 1871 || cp >= 1872 && cp <= 1919 || cp >= 1920 && cp <= 1983 || cp >= 1984 && cp <= 2047 || cp >= 2048 && cp <= 2111 || cp >= 2112 && cp <= 2143 || cp >= 2144 && cp <= 2159 || cp >= 2160 && cp <= 2207 || cp >= 2208 && cp <= 2303 || cp >= 2304 && cp <= 3455 || cp >= 3456 && cp <= 3583 || cp >= 3584 && cp <= 3711 || cp >= 3712 && cp <= 3839 || cp >= 3840 && cp <= 4095 || cp >= 4096 && cp <= 4255 || cp >= 6016 && cp <= 6143 || cp >= 64285 && cp <= 64335 || cp >= 64336 && cp <= 65023 || cp >= 65136 && cp <= 65279 || cp >= 68864 && cp <= 68927 || cp >= 69312 && cp <= 69375 || cp >= 69424 && cp <= 69487 || cp >= 69488 && cp <= 69551 || cp >= 125184 && cp <= 125279 || cp >= 126464 && cp <= 126719;
54
+ }
55
+ /** Which font slot a single code point selects. CJK wins where ranges meet. */
56
+ function scriptClassOf(cp) {
57
+ if (isCjkCodePoint(cp)) return SCRIPT_CLASS.eastAsia;
58
+ if (isComplexScriptCodePoint(cp)) return SCRIPT_CLASS.complex;
59
+ return SCRIPT_CLASS.western;
60
+ }
61
+ /**
62
+ * True when the text contains at least one complex-script code point. Callers
63
+ * use this to skip segmentation entirely on the common all-Latin path.
64
+ */
65
+ function hasComplexScript(text) {
66
+ for (const ch of text) if (isComplexScriptCodePoint(ch.codePointAt(0))) return true;
67
+ return false;
68
+ }
69
+ /**
19
70
  * Split text into maximal runs of one script class (CJK vs. non-CJK). Iterates
20
71
  * by code point so astral ideographs (surrogate pairs) are never split between
21
72
  * fonts. Empty input yields no segments; single-class input yields one.
@@ -23,30 +74,30 @@ function hasCjk(text) {
23
74
  function segmentByScript(text) {
24
75
  const segments = [];
25
76
  let current = "";
26
- let currentIsCjk = false;
77
+ let currentScript = SCRIPT_CLASS.western;
27
78
  for (const ch of text) {
28
- const cjk = isCjkCodePoint(ch.codePointAt(0));
79
+ const script = scriptClassOf(ch.codePointAt(0));
29
80
  if (current.length === 0) {
30
81
  current = ch;
31
- currentIsCjk = cjk;
82
+ currentScript = script;
32
83
  continue;
33
84
  }
34
- if (cjk === currentIsCjk) {
85
+ if (script === currentScript) {
35
86
  current += ch;
36
87
  continue;
37
88
  }
38
89
  segments.push({
39
90
  text: current,
40
- isCjk: currentIsCjk
91
+ script: currentScript
41
92
  });
42
93
  current = ch;
43
- currentIsCjk = cjk;
94
+ currentScript = script;
44
95
  }
45
96
  if (current.length > 0) segments.push({
46
97
  text: current,
47
- isCjk: currentIsCjk
98
+ script: currentScript
48
99
  });
49
100
  return segments;
50
101
  }
51
102
  //#endregion
52
- export { hasCjk, isCjkCodePoint, segmentByScript };
103
+ export { SCRIPT_CLASS, hasCjk, hasComplexScript, isCjkCodePoint, isComplexScriptCodePoint, scriptClassOf, segmentByScript };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/folio-core",
3
- "version": "0.16.0",
3
+ "version": "0.17.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",