imagescript 3.2.0 → 3.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 (59) hide show
  1. package/README.md +8 -3
  2. package/THIRD_PARTY_NOTICES.md +131 -0
  3. package/dist/animation.d.ts +7 -0
  4. package/dist/animation.js +22 -12
  5. package/dist/codecs/crc.js +5 -0
  6. package/dist/codecs/gif.js +30 -52
  7. package/dist/codecs/jpeg.js +9 -27
  8. package/dist/codecs/png.js +22 -10
  9. package/dist/codecs/tga.js +3 -1
  10. package/dist/codecs/tiff.js +7 -2
  11. package/dist/codecs/vp8.js +3 -1
  12. package/dist/codecs/vp8l.js +126 -58
  13. package/dist/codecs/webp.js +11 -9
  14. package/dist/codecs/zlib.js +81 -69
  15. package/dist/frame.d.ts +1 -0
  16. package/dist/frame.js +4 -2
  17. package/dist/gif.d.ts +1 -0
  18. package/dist/gif.js +2 -2
  19. package/dist/image.d.ts +42 -7
  20. package/dist/image.js +224 -113
  21. package/dist/index.js +4 -4
  22. package/dist/render/bidi.js +350 -0
  23. package/dist/render/buffer.js +362 -0
  24. package/dist/render/color.js +164 -0
  25. package/dist/render/colr.js +523 -0
  26. package/dist/render/font.js +173 -241
  27. package/dist/render/hinting.js +12 -4
  28. package/dist/render/layout.js +1553 -0
  29. package/dist/render/linebreak.js +210 -0
  30. package/dist/render/segment.js +106 -0
  31. package/dist/render/shape.js +984 -0
  32. package/dist/render/shapers/arabic.js +284 -0
  33. package/dist/render/shapers/hangul.js +142 -0
  34. package/dist/render/shapers/hebrew.js +49 -0
  35. package/dist/render/shapers/index.js +52 -0
  36. package/dist/render/shapers/indic.js +579 -0
  37. package/dist/render/shapers/khmer.js +104 -0
  38. package/dist/render/shapers/machine.js +129 -0
  39. package/dist/render/shapers/myanmar.js +121 -0
  40. package/dist/render/shapers/syllabic.js +100 -0
  41. package/dist/render/shapers/thai.js +125 -0
  42. package/dist/render/shapers/use.js +235 -0
  43. package/dist/render/svg.js +101 -22
  44. package/dist/render/text.js +373 -103
  45. package/dist/render/unicode-data.js +63 -0
  46. package/dist/render/unicode.js +58 -0
  47. package/dist/text-layout.d.ts +4 -1
  48. package/dist/text-layout.js +2 -2
  49. package/dist/util/alpha.js +43 -0
  50. package/dist/util/blur.js +80 -5
  51. package/dist/util/bytes.js +31 -0
  52. package/dist/util/limits.js +34 -0
  53. package/dist/util/options.js +7 -0
  54. package/dist/util/resample.js +359 -79
  55. package/dist/util/rotate.js +43 -0
  56. package/dist/version.js +1 -1
  57. package/licenses/HarfBuzz.txt +42 -0
  58. package/licenses/Unicode-3.0.txt +39 -0
  59. package/package.json +1 -1
@@ -0,0 +1,284 @@
1
+ /*!
2
+ * The Arabic shaper (Arabic, Syriac, and joining for Mongolian, N'Ko and others through USE), ported from HarfBuzz
3
+ * (hb-ot-shaper-arabic.cc)
4
+ * Copyright © 2010,2012 Google, Inc., Old MIT license (see THIRD_PARTY_NOTICES.md and licenses/HarfBuzz.txt)
5
+ */
6
+ import { generalCategory, isDefaultIgnorable, isMultiplied, ligComp, combiningClass, setCombiningClass } from "../buffer.js";
7
+ import { syntheticLookup, applySynthetic } from "../layout.js";
8
+ import { unicodeCategory } from "../shape.js";
9
+ import { joiningType as joiningTable, ARABIC_SHAPING_FIRST, arabicShaping, arabicLigatures, arabicLigatures3, arabicMarkLigatures } from "../unicode-data.js";
10
+ import { value } from "../unicode.js";
11
+ const JOINING = { U: 0, L: 1, R: 2, D: 3, C: 3, T: 6, ALAPH: 4, DALATH_RISH: 5 };
12
+ export const joiningType = (u, category) => {
13
+ const type = JOINING[value(joiningTable, u)];
14
+ if (type !== undefined)
15
+ return type;
16
+ return category === 12 || category === 11 || category === 1 ? 6 : 0;
17
+ };
18
+ const FEATURES = ['isol', 'fina', 'fin2', 'fin3', 'medi', 'med2', 'init'];
19
+ const isSyriacFeature = (tag) => tag[3] === '2' || tag[3] === '3';
20
+ const N = 7, ISOL = 0, FINA = 1, FIN2 = 2, FIN3 = 3, MEDI = 4, MED2 = 5, INIT = 6;
21
+ const STATES = [
22
+ [[N, N, 0], [N, ISOL, 2], [N, ISOL, 1], [N, ISOL, 2], [N, ISOL, 1], [N, ISOL, 6]],
23
+ [[N, N, 0], [N, ISOL, 2], [N, ISOL, 1], [N, ISOL, 2], [N, FIN2, 5], [N, ISOL, 6]],
24
+ [[N, N, 0], [N, ISOL, 2], [INIT, FINA, 1], [INIT, FINA, 3], [INIT, FINA, 4], [INIT, FINA, 6]],
25
+ [[N, N, 0], [N, ISOL, 2], [MEDI, FINA, 1], [MEDI, FINA, 3], [MEDI, FINA, 4], [MEDI, FINA, 6]],
26
+ [[N, N, 0], [N, ISOL, 2], [MED2, ISOL, 1], [MED2, ISOL, 2], [MED2, FIN2, 5], [MED2, ISOL, 6]],
27
+ [[N, N, 0], [N, ISOL, 2], [ISOL, ISOL, 1], [ISOL, ISOL, 2], [ISOL, FIN2, 5], [ISOL, ISOL, 6]],
28
+ [[N, N, 0], [N, ISOL, 2], [N, ISOL, 1], [N, ISOL, 2], [N, FIN3, 5], [N, ISOL, 6]],
29
+ ];
30
+ function fallbackPlan(plan, font) {
31
+ const lookups = [];
32
+ const features = ['init', 'medi', 'fina', 'isol', 'rlig', 'rlig', 'rlig'];
33
+ for (let i = 0; i < features.length; i++) {
34
+ const mask = plan.map.get1Mask(features[i]);
35
+ if (!mask)
36
+ continue;
37
+ let lookup = null;
38
+ if (i < 4) {
39
+ const single = new Map();
40
+ for (let k = 0; k < arabicShaping.length / 4; k++) {
41
+ const u = ARABIC_SHAPING_FIRST + k, s = arabicShaping[k * 4 + i];
42
+ const ug = font.glyph(u), sg = s && font.glyph(s);
43
+ if (!s || !ug || !sg || ug === sg)
44
+ continue;
45
+ if (!single.has(ug))
46
+ single.set(ug, sg);
47
+ }
48
+ if (single.size)
49
+ lookup = syntheticLookup(8, single, null);
50
+ }
51
+ else {
52
+ const table = i === 4 ? arabicLigatures3 : i === 5 ? arabicLigatures : arabicMarkLigatures;
53
+ const ligatures = new Map();
54
+ for (const [first, list] of table) {
55
+ const fg = font.glyph(first);
56
+ if (!fg)
57
+ continue;
58
+ for (const entry of list) {
59
+ const lg = font.glyph(entry[entry.length - 1]);
60
+ if (!lg)
61
+ continue;
62
+ const components = entry.slice(0, -1).map(u => font.glyph(u));
63
+ if (components.some(g => !g))
64
+ continue;
65
+ if (!ligatures.has(fg))
66
+ ligatures.set(fg, []);
67
+ ligatures.get(fg).push([components, lg]);
68
+ }
69
+ }
70
+ if (ligatures.size)
71
+ lookup = syntheticLookup(i === 6 ? 0 : 8, null, ligatures);
72
+ }
73
+ if (lookup)
74
+ lookups.push([lookup, mask]);
75
+ }
76
+ return lookups;
77
+ }
78
+ function fallbackShape(plan, font, buffer) {
79
+ const arabic = plan.data;
80
+ if (!arabic.doFallback)
81
+ return;
82
+ arabic.fallback ??= fallbackPlan(plan, font);
83
+ for (const [lookup, mask] of arabic.fallback)
84
+ applySynthetic(buffer, font.gdef, lookup, mask);
85
+ }
86
+ function recordStch(plan, _font, buffer) {
87
+ if (!plan.data.hasStch)
88
+ return;
89
+ for (const info of buffer.info)
90
+ if (isMultiplied(info)) {
91
+ info.category = ligComp(info) % 2 ? 9 : 8;
92
+ buffer.hasStch = true;
93
+ }
94
+ }
95
+ function collectFeatures(planner) {
96
+ const map = planner.map;
97
+ map.enableFeature('stch');
98
+ map.addGsubPause(recordStch);
99
+ map.enableFeature('ccmp', 8);
100
+ map.enableFeature('locl', 8);
101
+ map.addGsubPause(null);
102
+ for (const tag of FEATURES) {
103
+ const fallback = planner.script === 'Arab' && !isSyriacFeature(tag);
104
+ map.addFeature(tag, 8 | (fallback ? 2 : 0));
105
+ map.addGsubPause(null);
106
+ }
107
+ map.addGsubPause(null);
108
+ map.enableFeature('rlig', 8 | 2);
109
+ if (planner.script === 'Arab')
110
+ map.addGsubPause(fallbackShape);
111
+ map.enableFeature('calt', 8);
112
+ if (!map.hasFeature('rclt'))
113
+ map.addGsubPause(null);
114
+ map.enableFeature('liga', 8);
115
+ map.enableFeature('clig', 8);
116
+ map.enableFeature('mset', 8);
117
+ }
118
+ export function dataCreate(plan) {
119
+ const doFallback = plan.script === 'Arab' && FEATURES.every(tag => isSyriacFeature(tag) || plan.map.needsFallback(tag));
120
+ return { masks: [...FEATURES.map(tag => plan.map.get1Mask(tag)), 0], hasStch: !!plan.map.get1Mask('stch'), doFallback, fallback: null };
121
+ }
122
+ export function arabicJoining(buffer) {
123
+ const info = buffer.info;
124
+ let prev = -1, state = 0;
125
+ for (const u of buffer.context[0]) {
126
+ const type = joiningType(u, unicodeCategory(u));
127
+ if (type === 6)
128
+ continue;
129
+ state = STATES[state][type][2];
130
+ break;
131
+ }
132
+ for (let i = 0; i < info.length; i++) {
133
+ const type = joiningType(info[i].codepoint, generalCategory(info[i]));
134
+ if (type === 6) {
135
+ info[i].category = 7;
136
+ continue;
137
+ }
138
+ const entry = STATES[state][type];
139
+ if (entry[0] !== 7 && prev >= 0)
140
+ info[prev].category = entry[0];
141
+ info[i].category = entry[1];
142
+ prev = i;
143
+ state = entry[2];
144
+ }
145
+ for (const u of buffer.context[1]) {
146
+ const type = joiningType(u, unicodeCategory(u));
147
+ if (type === 6)
148
+ continue;
149
+ const entry = STATES[state][type];
150
+ if (entry[0] !== 7 && prev >= 0)
151
+ info[prev].category = entry[0];
152
+ break;
153
+ }
154
+ }
155
+ export function setupMasksArabic(arabic, buffer, script) {
156
+ arabicJoining(buffer);
157
+ const info = buffer.info;
158
+ if (script === 'Mong')
159
+ for (let i = 1; i < info.length; i++) {
160
+ const u = info[i].codepoint;
161
+ if (u >= 0x180b && u <= 0x180d || u === 0x180f)
162
+ info[i].category = info[i - 1].category;
163
+ }
164
+ for (const g of info)
165
+ g.mask |= arabic.masks[g.category];
166
+ }
167
+ const isWord = (category) => [2, 3, 6, 7, 10, 11, 12, 13,
168
+ 14, 15, 23, 24, 25, 26].includes(category);
169
+ const isStch = (info) => info.category === 8 || info.category === 9;
170
+ function applyStch(buffer, font) {
171
+ if (!buffer.hasStch)
172
+ return;
173
+ const rtl = buffer.direction === 5;
174
+ if (!rtl)
175
+ buffer.reverse();
176
+ const MAX_GLYPHS = 256;
177
+ const info = buffer.info, out = [];
178
+ for (let i = info.length; i;) {
179
+ if (!isStch(info[i - 1])) {
180
+ out.push(info[--i]);
181
+ continue;
182
+ }
183
+ let wTotal = 0, wFixed = 0, wRepeating = 0, nFixed = 0, nRepeating = 0;
184
+ const end = i;
185
+ while (i && isStch(info[i - 1])) {
186
+ i--;
187
+ const width = font.advance(info[i].codepoint);
188
+ if (info[i].category === 8) {
189
+ wFixed += width;
190
+ nFixed++;
191
+ }
192
+ else {
193
+ wRepeating += width;
194
+ nRepeating++;
195
+ }
196
+ }
197
+ const start = i;
198
+ let context = i;
199
+ while (context && !isStch(info[context - 1]) && (isDefaultIgnorable(info[context - 1]) || isWord(generalCategory(info[context - 1])))) {
200
+ context--;
201
+ wTotal += info[context].xAdvance;
202
+ }
203
+ let copies = 0;
204
+ const wRemainingSigned = wTotal - wFixed;
205
+ let wRemaining = wTotal - wFixed;
206
+ if (wRemainingSigned > wRepeating && wRepeating > 0)
207
+ copies = Math.trunc(wRemainingSigned / wRepeating) - 1;
208
+ let overlap = 0;
209
+ const shortfall = wRemainingSigned - wRepeating * (copies + 1);
210
+ if (shortfall > 0 && nRepeating > 0) {
211
+ copies++;
212
+ const excess = (copies + 1) * wRepeating - wRemainingSigned;
213
+ if (excess > 0) {
214
+ overlap = Math.trunc(excess / (copies * nRepeating));
215
+ wRemaining = 0;
216
+ }
217
+ }
218
+ let maxCopies = 0;
219
+ if (nRepeating > 0 && nFixed + nRepeating < MAX_GLYPHS)
220
+ maxCopies = Math.trunc((MAX_GLYPHS - nFixed - nRepeating) / nRepeating);
221
+ copies = Math.min(copies, maxCopies);
222
+ let xOffset = Math.trunc(wRemaining / 2);
223
+ for (let k = end; k > start; k--) {
224
+ const width = font.advance(info[k - 1].codepoint);
225
+ const repeat = info[k - 1].category === 9 ? 1 + copies : 1;
226
+ info[k - 1].xAdvance = 0;
227
+ for (let n = 0; n < repeat; n++) {
228
+ if (rtl) {
229
+ xOffset -= width;
230
+ if (n > 0)
231
+ xOffset += overlap;
232
+ }
233
+ info[k - 1].xOffset = xOffset;
234
+ out.push(info[k - 1].clone());
235
+ if (!rtl) {
236
+ xOffset += width;
237
+ if (n > 0)
238
+ xOffset -= overlap;
239
+ }
240
+ }
241
+ }
242
+ }
243
+ out.reverse();
244
+ buffer.info = out;
245
+ if (!rtl)
246
+ buffer.reverse();
247
+ }
248
+ const MODIFIER_COMBINING_MARKS = new Set([0x0654, 0x0655, 0x0658, 0x06dc, 0x06e3, 0x06e7, 0x06e8, 0x08ca, 0x08cb, 0x08cd, 0x08ce, 0x08cf, 0x08d3, 0x08f3]);
249
+ function reorderMarks(_plan, buffer, start, end) {
250
+ const info = buffer.info;
251
+ let i = start;
252
+ for (let cc = 220; cc <= 230; cc += 10) {
253
+ while (i < end && combiningClass(info[i]) < cc)
254
+ i++;
255
+ if (i === end)
256
+ break;
257
+ if (combiningClass(info[i]) > cc)
258
+ continue;
259
+ let j = i;
260
+ while (j < end && combiningClass(info[j]) === cc && MODIFIER_COMBINING_MARKS.has(info[j].codepoint))
261
+ j++;
262
+ if (i === j)
263
+ continue;
264
+ buffer.mergeClusters(start, j);
265
+ const moved = info.slice(i, j);
266
+ info.copyWithin(start + j - i, start, i);
267
+ for (let k = 0; k < moved.length; k++)
268
+ info[start + k] = moved[k];
269
+ const newStart = start + j - i, newCC = cc === 220 ? 25 : 26;
270
+ while (start < newStart)
271
+ setCombiningClass(info[start++], newCC);
272
+ i = j;
273
+ }
274
+ }
275
+ export const arabicShaper = {
276
+ collectFeatures,
277
+ dataCreate,
278
+ setupMasks: (plan, buffer) => setupMasksArabic(plan.data, buffer, plan.script),
279
+ postprocessGlyphs: (_plan, buffer, font) => applyStch(buffer, font),
280
+ reorderMarks,
281
+ normalization: 4,
282
+ zeroWidthMarks: 2,
283
+ fallbackPosition: true,
284
+ };
@@ -0,0 +1,142 @@
1
+ /*!
2
+ * The Hangul shaper, ported from HarfBuzz (hb-ot-shaper-hangul.cc)
3
+ * Copyright © 2013 Google, Inc., Old MIT license (see THIRD_PARTY_NOTICES.md and licenses/HarfBuzz.txt)
4
+ */
5
+ const FEATURES = ['', 'ljmo', 'vjmo', 'tjmo'];
6
+ const L_BASE = 0x1100, V_BASE = 0x1161, T_BASE = 0x11a7, L_COUNT = 19, V_COUNT = 21, T_COUNT = 28, S_BASE = 0xac00;
7
+ const N_COUNT = V_COUNT * T_COUNT, S_COUNT = L_COUNT * N_COUNT;
8
+ const isCombiningL = (u) => u >= L_BASE && u < L_BASE + L_COUNT;
9
+ const isCombiningV = (u) => u >= V_BASE && u < V_BASE + V_COUNT;
10
+ const isCombiningT = (u) => u > T_BASE && u < T_BASE + T_COUNT;
11
+ const isCombinedS = (u) => u >= S_BASE && u < S_BASE + S_COUNT;
12
+ const isL = (u) => u >= 0x1100 && u <= 0x115f || u >= 0xa960 && u <= 0xa97c;
13
+ const isV = (u) => u >= 0x1160 && u <= 0x11a7 || u >= 0xd7b0 && u <= 0xd7c6;
14
+ const isT = (u) => u >= 0x11a8 && u <= 0x11ff || u >= 0xd7cb && u <= 0xd7fb;
15
+ const isTone = (u) => u >= 0x302e && u <= 0x302f;
16
+ const isZeroWidth = (font, u) => {
17
+ const glyph = font.glyph(u);
18
+ return glyph !== 0 && font.advance(glyph) === 0;
19
+ };
20
+ function preprocessText(_plan, buffer, font) {
21
+ buffer.clearOutput();
22
+ let start = 0, end = 0;
23
+ const count = buffer.len;
24
+ for (buffer.idx = 0; buffer.idx < count && buffer.successful;) {
25
+ const u = buffer.cur().codepoint;
26
+ if (isTone(u)) {
27
+ if (start < end && end === buffer.outLen) {
28
+ buffer.nextGlyph();
29
+ if (!isZeroWidth(font, u)) {
30
+ buffer.mergeOutClusters(start, end + 1);
31
+ const info = buffer.outInfo, tone = info[end];
32
+ for (let k = end; k > start; k--)
33
+ info[k] = info[k - 1];
34
+ info[start] = tone;
35
+ }
36
+ }
37
+ else if (font.hasGlyph(0x25cc))
38
+ buffer.replaceGlyphs(1, 2, isZeroWidth(font, u) ? [0x25cc, u] : [u, 0x25cc]);
39
+ else
40
+ buffer.nextGlyph();
41
+ start = end = buffer.outLen;
42
+ continue;
43
+ }
44
+ start = buffer.outLen;
45
+ if (isL(u) && buffer.idx + 1 < count) {
46
+ const l = u, v = buffer.cur(1).codepoint;
47
+ if (isV(v)) {
48
+ let t = 0, tindex = 0;
49
+ if (buffer.idx + 2 < count) {
50
+ t = buffer.cur(2).codepoint;
51
+ if (isT(t))
52
+ tindex = t - T_BASE;
53
+ else
54
+ t = 0;
55
+ }
56
+ if (isCombiningL(l) && isCombiningV(v) && (t === 0 || isCombiningT(t))) {
57
+ const s = S_BASE + (l - L_BASE) * N_COUNT + (v - V_BASE) * T_COUNT + tindex;
58
+ if (font.hasGlyph(s)) {
59
+ buffer.replaceGlyphs(t ? 3 : 2, 1, [s]);
60
+ end = start + 1;
61
+ continue;
62
+ }
63
+ }
64
+ buffer.cur().category = 1;
65
+ buffer.nextGlyph();
66
+ buffer.cur().category = 2;
67
+ buffer.nextGlyph();
68
+ if (t) {
69
+ buffer.cur().category = 3;
70
+ buffer.nextGlyph();
71
+ end = start + 3;
72
+ }
73
+ else
74
+ end = start + 2;
75
+ if (!buffer.successful)
76
+ break;
77
+ buffer.mergeOutClusters(start, end);
78
+ continue;
79
+ }
80
+ }
81
+ else if (isCombinedS(u)) {
82
+ const s = u, hasGlyph = font.hasGlyph(s);
83
+ const lindex = Math.floor((s - S_BASE) / N_COUNT), nindex = (s - S_BASE) % N_COUNT;
84
+ const vindex = Math.floor(nindex / T_COUNT), tindex = nindex % T_COUNT;
85
+ if (!tindex && buffer.idx + 1 < count && isCombiningT(buffer.cur(1).codepoint)) {
86
+ const combined = s + buffer.cur(1).codepoint - T_BASE;
87
+ if (font.hasGlyph(combined)) {
88
+ buffer.replaceGlyphs(2, 1, [combined]);
89
+ end = start + 1;
90
+ continue;
91
+ }
92
+ }
93
+ if (!hasGlyph || !tindex && buffer.idx + 1 < count && isT(buffer.cur(1).codepoint)) {
94
+ const decomposed = [L_BASE + lindex, V_BASE + vindex, T_BASE + tindex];
95
+ if (font.hasGlyph(decomposed[0]) && font.hasGlyph(decomposed[1]) && (!tindex || font.hasGlyph(decomposed[2]))) {
96
+ let length = tindex ? 3 : 2;
97
+ buffer.replaceGlyphs(1, length, decomposed);
98
+ if (hasGlyph && !tindex) {
99
+ buffer.nextGlyph();
100
+ length++;
101
+ }
102
+ if (!buffer.successful)
103
+ break;
104
+ const info = buffer.outInfo;
105
+ end = start + length;
106
+ let i = start;
107
+ info[i++].category = 1;
108
+ info[i++].category = 2;
109
+ if (i < end)
110
+ info[i++].category = 3;
111
+ buffer.mergeOutClusters(start, end);
112
+ continue;
113
+ }
114
+ }
115
+ if (hasGlyph)
116
+ end = start + 1;
117
+ }
118
+ buffer.nextGlyph();
119
+ }
120
+ buffer.sync();
121
+ }
122
+ export const hangulShaper = {
123
+ collectFeatures: planner => {
124
+ for (const tag of FEATURES.slice(1))
125
+ planner.map.addFeature(tag);
126
+ },
127
+ overrideFeatures: planner => planner.map.addFeature('calt'),
128
+ dataCreate: (plan) => ({ masks: FEATURES.map(tag => tag ? plan.map.get1Mask(tag) : 0), caltMask: plan.map.get1Mask('calt') }),
129
+ preprocessText,
130
+ setupMasks: (plan, buffer) => {
131
+ const hangul = plan.data;
132
+ for (const info of buffer.info) {
133
+ info.mask |= hangul.masks[info.category];
134
+ const u = info.codepoint;
135
+ if (isL(u) || isV(u) || isT(u))
136
+ info.mask &= ~hangul.caltMask;
137
+ }
138
+ },
139
+ normalization: 0,
140
+ zeroWidthMarks: 0,
141
+ fallbackPosition: true,
142
+ };
@@ -0,0 +1,49 @@
1
+ /*!
2
+ * The Hebrew shaper, ported from HarfBuzz (hb-ot-shaper-hebrew.cc)
3
+ * Copyright © 2010,2012 Google, Inc., Old MIT license (see THIRD_PARTY_NOTICES.md and licenses/HarfBuzz.txt)
4
+ */
5
+ import { combiningClass } from "../buffer.js";
6
+ import { compose } from "../shape.js";
7
+ const DAGESH_FORMS = [0xfb30, 0xfb31, 0xfb32, 0xfb33, 0xfb34, 0xfb35, 0xfb36, 0, 0xfb38, 0xfb39, 0xfb3a, 0xfb3b, 0xfb3c, 0, 0xfb3e, 0, 0xfb40, 0xfb41, 0,
8
+ 0xfb43, 0xfb44, 0, 0xfb46, 0xfb47, 0xfb48, 0xfb49, 0xfb4a];
9
+ const PRESENTATION = {
10
+ 0x05b4: { 0x05d9: 0xfb1d },
11
+ 0x05b7: { 0x05f2: 0xfb1f, 0x05d0: 0xfb2e },
12
+ 0x05b8: { 0x05d0: 0xfb2f },
13
+ 0x05b9: { 0x05d5: 0xfb4b },
14
+ 0x05bf: { 0x05d1: 0xfb4c, 0x05db: 0xfb4d, 0x05e4: 0xfb4e },
15
+ 0x05c1: { 0x05e9: 0xfb2a, 0xfb49: 0xfb2c },
16
+ 0x05c2: { 0x05e9: 0xfb2b, 0xfb49: 0xfb2d },
17
+ };
18
+ export const hebrewShaper = {
19
+ compose: (c, a, b) => {
20
+ const found = compose(a, b);
21
+ if (found || c.plan.hasGposMark)
22
+ return found;
23
+ if (b === 0x05bc) {
24
+ if (a >= 0x05d0 && a <= 0x05ea)
25
+ return DAGESH_FORMS[a - 0x05d0];
26
+ if (a === 0xfb2a)
27
+ return 0xfb2c;
28
+ if (a === 0xfb2b)
29
+ return 0xfb2d;
30
+ return 0;
31
+ }
32
+ return PRESENTATION[b]?.[a] ?? 0;
33
+ },
34
+ reorderMarks: (_plan, buffer, start, end) => {
35
+ const info = buffer.info;
36
+ for (let i = start + 2; i < end; i++) {
37
+ const c0 = combiningClass(info[i - 2]), c1 = combiningClass(info[i - 1]), c2 = combiningClass(info[i]);
38
+ if ((c0 === 20 || c0 === 21) && (c1 === 22 || c1 === 23) && (c2 === 25 || c2 === 220)) {
39
+ buffer.mergeClusters(i - 1, i + 1);
40
+ [info[i - 1], info[i]] = [info[i], info[i - 1]];
41
+ break;
42
+ }
43
+ }
44
+ },
45
+ gposTag: 'hebr',
46
+ normalization: 4,
47
+ zeroWidthMarks: 2,
48
+ fallbackPosition: true,
49
+ };
@@ -0,0 +1,52 @@
1
+ /*!
2
+ * The shaper for a script, ported from HarfBuzz (hb-ot-shaper.hh)
3
+ * Copyright © 2010,2012 Google, Inc., Old MIT license (see THIRD_PARTY_NOTICES.md and licenses/HarfBuzz.txt)
4
+ */
5
+ import { defaultShaper, shaperFor } from "../shape.js";
6
+ import { arabicShaper } from "./arabic.js";
7
+ import { hebrewShaper } from "./hebrew.js";
8
+ import { thaiShaper } from "./thai.js";
9
+ import { hangulShaper } from "./hangul.js";
10
+ import { indicShaper } from "./indic.js";
11
+ import { khmerShaper } from "./khmer.js";
12
+ import { myanmarShaper } from "./myanmar.js";
13
+ import { useShaper } from "./use.js";
14
+ const USE_SCRIPTS = new Set(['Tibt', 'Mong', 'Sinh', 'Buhd', 'Hano', 'Tglg', 'Tagb', 'Limb', 'Tale', 'Bugi', 'Khar', 'Sylo', 'Tfng', 'Bali', 'Nkoo', 'Phag',
15
+ 'Cham', 'Kali', 'Lepc', 'Rjng', 'Saur', 'Sund', 'Egyp', 'Java', 'Kthi', 'Mtei', 'Lana', 'Tavt', 'Batk', 'Brah', 'Mand', 'Cakm', 'Plrd', 'Shrd', 'Takr',
16
+ 'Dupl', 'Gran', 'Khoj', 'Sind', 'Mahj', 'Mani', 'Modi', 'Hmng', 'Phlp', 'Sidd', 'Tirh', 'Ahom', 'Mult', 'Adlm', 'Bhks', 'Marc', 'Newa', 'Gonm', 'Soyo',
17
+ 'Zanb', 'Dogr', 'Gong', 'Rohg', 'Maka', 'Medf', 'Sogo', 'Sogd', 'Elym', 'Nand', 'Hmnp', 'Wcho', 'Chrs', 'Diak', 'Kits', 'Yezi', 'Cpmn', 'Ougr', 'Tnsa',
18
+ 'Toto', 'Vith', 'Kawi', 'Nagm', 'Gara', 'Gukh', 'Krai', 'Onao', 'Sunu', 'Todr', 'Tutg', 'Berf', 'Sidt', 'Tayo', 'Tols', 'Jurc', 'Pcun', 'Seal']);
19
+ const INDIC_SCRIPTS = new Set(['Beng', 'Deva', 'Gujr', 'Guru', 'Knda', 'Mlym', 'Orya', 'Taml', 'Telu']);
20
+ const shapers = {};
21
+ export const registerShaper = (name, shaper) => {
22
+ shapers[name] = shaper;
23
+ };
24
+ registerShaper('arabic', arabicShaper);
25
+ registerShaper('hebrew', hebrewShaper);
26
+ registerShaper('thai', thaiShaper);
27
+ registerShaper('hangul', hangulShaper);
28
+ registerShaper('indic', indicShaper);
29
+ registerShaper('khmer', khmerShaper);
30
+ registerShaper('myanmar', myanmarShaper);
31
+ registerShaper('use', useShaper);
32
+ shaperFor.categorize = (script, _direction, gsubScript) => {
33
+ const pick = (name) => shapers[name] ?? defaultShaper;
34
+ const generic = gsubScript === 'DFLT' || gsubScript === 'latn';
35
+ if (script === 'Arab' || script === 'Syrc')
36
+ return gsubScript !== 'DFLT' || script === 'Arab' ? pick('arabic') : defaultShaper;
37
+ if (script === 'Thai' || script === 'Laoo')
38
+ return pick('thai');
39
+ if (script === 'Hang')
40
+ return pick('hangul');
41
+ if (script === 'Hebr')
42
+ return pick('hebrew');
43
+ if (INDIC_SCRIPTS.has(script))
44
+ return generic ? defaultShaper : gsubScript[3] === '3' ? pick('use') : pick('indic');
45
+ if (script === 'Khmr')
46
+ return pick('khmer');
47
+ if (script === 'Mymr')
48
+ return generic || gsubScript === 'mymr' ? defaultShaper : pick('myanmar');
49
+ if (USE_SCRIPTS.has(script))
50
+ return generic ? defaultShaper : pick('use');
51
+ return defaultShaper;
52
+ };