imagescript 3.2.1 → 3.4.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.
- package/README.md +35 -10
- package/THIRD_PARTY_NOTICES.md +298 -2
- package/dist/animation.d.ts +25 -8
- package/dist/animation.js +44 -13
- package/dist/codecs/av1/cdef.js +197 -0
- package/dist/codecs/av1/data.js +141 -0
- package/dist/codecs/av1/decode.js +1769 -0
- package/dist/codecs/av1/filmgrain.js +219 -0
- package/dist/codecs/av1/frame.js +114 -0
- package/dist/codecs/av1/ipred.js +491 -0
- package/dist/codecs/av1/itx.js +950 -0
- package/dist/codecs/av1/loopfilter.js +149 -0
- package/dist/codecs/av1/msac.js +161 -0
- package/dist/codecs/av1/obu.js +516 -0
- package/dist/codecs/av1/postfilter.js +28 -0
- package/dist/codecs/av1/restoration.js +193 -0
- package/dist/codecs/av1/tables.js +160 -0
- package/dist/codecs/avif.js +581 -0
- package/dist/codecs/bmp.js +317 -0
- package/dist/codecs/crc.js +5 -0
- package/dist/codecs/gif.js +10 -35
- package/dist/codecs/hdr.d.ts +25 -0
- package/dist/codecs/hdr.js +201 -0
- package/dist/codecs/ico.js +48 -0
- package/dist/codecs/jpeg.js +11 -28
- package/dist/codecs/jxl/bits.js +126 -0
- package/dist/codecs/jxl/blend.js +117 -0
- package/dist/codecs/jxl/color.js +116 -0
- package/dist/codecs/jxl/dct.js +404 -0
- package/dist/codecs/jxl/entropy.js +556 -0
- package/dist/codecs/jxl/features.js +325 -0
- package/dist/codecs/jxl/filters.js +119 -0
- package/dist/codecs/jxl/frame-header.js +195 -0
- package/dist/codecs/jxl/frame.js +319 -0
- package/dist/codecs/jxl/headers.js +249 -0
- package/dist/codecs/jxl/idct.js +484 -0
- package/dist/codecs/jxl/modular.js +688 -0
- package/dist/codecs/jxl/quant.js +219 -0
- package/dist/codecs/jxl/upsample.js +52 -0
- package/dist/codecs/jxl/vardct.js +472 -0
- package/dist/codecs/jxl.js +89 -0
- package/dist/codecs/netpbm.js +239 -0
- package/dist/codecs/png.js +22 -10
- package/dist/codecs/qoi.js +150 -0
- package/dist/codecs/tga.js +3 -1
- package/dist/codecs/tiff.js +7 -2
- package/dist/codecs/vp8-enc-dsp.js +932 -0
- package/dist/codecs/vp8-enc-tables.js +78 -0
- package/dist/codecs/vp8-enc.js +1930 -0
- package/dist/codecs/vp8.js +3 -1
- package/dist/codecs/vp8l.js +126 -58
- package/dist/codecs/webp.js +47 -11
- package/dist/codecs/zlib.js +81 -69
- package/dist/frame.d.ts +1 -0
- package/dist/frame.js +4 -2
- package/dist/gif.d.ts +1 -0
- package/dist/gif.js +2 -2
- package/dist/image-type.d.ts +15 -1
- package/dist/image-type.js +45 -2
- package/dist/image.d.ts +89 -13
- package/dist/image.js +266 -113
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -4
- package/dist/render/bidi.js +350 -0
- package/dist/render/buffer.js +362 -0
- package/dist/render/color.js +164 -0
- package/dist/render/colr.js +523 -0
- package/dist/render/font.js +173 -241
- package/dist/render/hinting.js +38 -22
- package/dist/render/layout.js +1553 -0
- package/dist/render/linebreak.js +210 -0
- package/dist/render/segment.js +106 -0
- package/dist/render/shape.js +984 -0
- package/dist/render/shapers/arabic.js +284 -0
- package/dist/render/shapers/hangul.js +142 -0
- package/dist/render/shapers/hebrew.js +49 -0
- package/dist/render/shapers/index.js +52 -0
- package/dist/render/shapers/indic.js +579 -0
- package/dist/render/shapers/khmer.js +104 -0
- package/dist/render/shapers/machine.js +129 -0
- package/dist/render/shapers/myanmar.js +121 -0
- package/dist/render/shapers/syllabic.js +100 -0
- package/dist/render/shapers/thai.js +125 -0
- package/dist/render/shapers/use.js +235 -0
- package/dist/render/svg.js +102 -23
- package/dist/render/text.js +373 -103
- package/dist/render/unicode-data.js +63 -0
- package/dist/render/unicode.js +50 -0
- package/dist/text-layout.d.ts +4 -1
- package/dist/text-layout.js +2 -2
- package/dist/util/alpha.js +43 -0
- package/dist/util/blur.js +20 -33
- package/dist/util/bytes.js +40 -0
- package/dist/util/limits.js +34 -0
- package/dist/util/options.js +7 -0
- package/dist/util/resample.js +126 -7
- package/dist/util/rotate.js +43 -0
- package/dist/version.js +1 -1
- package/licenses/HarfBuzz.txt +42 -0
- package/licenses/Unicode-3.0.txt +39 -0
- package/package.json +25 -2
|
@@ -0,0 +1,984 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* OpenType shaping, ported from HarfBuzz (hb-ot-shape.cc, hb-ot-shape-normalize.cc, hb-ot-shape-fallback.cc,
|
|
3
|
+
* hb-ot-tag.cc, hb-unicode)
|
|
4
|
+
* Copyright © 2009,2010 Red Hat, Inc., © 2010,2011,2012 Google, Inc., Old MIT license (see THIRD_PARTY_NOTICES.md
|
|
5
|
+
* and licenses/HarfBuzz.txt)
|
|
6
|
+
*/
|
|
7
|
+
import { Buffer, GlyphInfo, CATEGORY_NAMES, isMark, generalCategory, setGeneralCategory, isUnicodeMark, combiningClass, setCombiningClass, isUnicodeSpace, spaceFallback, setSpaceFallback, isDefaultIgnorable, isHidden, isContinuation, isMarkGlyph, isLigated, ligId, ligComp, ligNumComps, } from "./buffer.js";
|
|
8
|
+
import { GDEF, LayoutTable, MapBuilder, ApplyContext, propagateAttachmentOffsets } from "./layout.js";
|
|
9
|
+
import { generalCategory as gcTable, combiningClass as cccTable, script as scriptTable, extendedPictographic, decompositions as decompositionData } from "./unicode-data.js";
|
|
10
|
+
import { value, valueIndex, varints } from "./unicode.js";
|
|
11
|
+
import { mirror } from "./bidi.js";
|
|
12
|
+
const categoryIndex = new Map(CATEGORY_NAMES.map((name, i) => [name, i]));
|
|
13
|
+
export const unicodeCategory = (u) => categoryIndex.get(value(gcTable, u));
|
|
14
|
+
const combiningClassOf = (u) => +value(cccTable, u);
|
|
15
|
+
export const scriptOf = (u) => value(scriptTable, u);
|
|
16
|
+
const MODIFIED = {
|
|
17
|
+
10: 22, 11: 15, 12: 16, 13: 17, 14: 23, 15: 18, 16: 19, 17: 20, 18: 21, 19: 14, 20: 24, 21: 12, 22: 25, 23: 13, 24: 10, 25: 11, 26: 26,
|
|
18
|
+
27: 28, 28: 29, 29: 30, 30: 31, 31: 32, 32: 33, 33: 27, 34: 34, 35: 35, 36: 36, 84: 4, 91: 5, 103: 3, 107: 107, 118: 118, 122: 122,
|
|
19
|
+
129: 129, 130: 132, 132: 131,
|
|
20
|
+
};
|
|
21
|
+
export function modifiedCombiningClass(u) {
|
|
22
|
+
if (u === 0x1a60 || u === 0x0fc6)
|
|
23
|
+
return 254;
|
|
24
|
+
if (u === 0x0f39)
|
|
25
|
+
return 127;
|
|
26
|
+
const cc = combiningClassOf(u);
|
|
27
|
+
return MODIFIED[cc] ?? cc;
|
|
28
|
+
}
|
|
29
|
+
export function isDefaultIgnorableCodepoint(ch) {
|
|
30
|
+
const plane = ch >> 16;
|
|
31
|
+
if (!plane) {
|
|
32
|
+
switch (ch >> 8) {
|
|
33
|
+
case 0x00: return ch === 0x00ad;
|
|
34
|
+
case 0x03: return ch === 0x034f;
|
|
35
|
+
case 0x06: return ch === 0x061c;
|
|
36
|
+
case 0x17: return ch >= 0x17b4 && ch <= 0x17b5;
|
|
37
|
+
case 0x18: return ch >= 0x180b && ch <= 0x180f;
|
|
38
|
+
case 0x20: return ch >= 0x200b && ch <= 0x200f || ch >= 0x202a && ch <= 0x202e || ch >= 0x2060 && ch <= 0x206f;
|
|
39
|
+
case 0xfe: return ch >= 0xfe00 && ch <= 0xfe0f || ch === 0xfeff;
|
|
40
|
+
case 0xff: return ch >= 0xfff0 && ch <= 0xfff8;
|
|
41
|
+
default: return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (plane === 1)
|
|
45
|
+
return ch >= 0x1d173 && ch <= 0x1d17a;
|
|
46
|
+
if (plane === 0xe)
|
|
47
|
+
return ch >= 0xe0000 && ch <= 0xe0fff;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const isVariationSelectorCodepoint = (u) => u >= 0xfe00 && u <= 0xfe0f || u >= 0xe0100 && u <= 0xe01ef;
|
|
51
|
+
const isRegionalIndicator = (u) => u >= 0x1f1e6 && u <= 0x1f1ff;
|
|
52
|
+
const SPACES = {
|
|
53
|
+
0x20: 18, 0xa0: 18, 0x2000: 2, 0x2001: 1, 0x2002: 2, 0x2003: 1, 0x2004: 3, 0x2005: 4,
|
|
54
|
+
0x2006: 6, 0x2007: 19, 0x2008: 20, 0x2009: 5, 0x200a: 16, 0x202f: 21, 0x205f: 17,
|
|
55
|
+
0x3000: 1,
|
|
56
|
+
};
|
|
57
|
+
let decompositionMap = null, compositionMap = null;
|
|
58
|
+
function loadDecompositions() {
|
|
59
|
+
decompositionMap = new Map();
|
|
60
|
+
compositionMap = new Map();
|
|
61
|
+
const numbers = varints(decompositionData);
|
|
62
|
+
const unzigzag = (n) => n & 1 ? -(n + 1) / 2 : n / 2;
|
|
63
|
+
for (let i = 0, u = 0; i < numbers.length; i += 3) {
|
|
64
|
+
u += numbers[i];
|
|
65
|
+
const a = u + unzigzag(numbers[i + 1]);
|
|
66
|
+
const second = numbers[i + 2];
|
|
67
|
+
if (!second)
|
|
68
|
+
decompositionMap.set(u, [a, 0]);
|
|
69
|
+
else {
|
|
70
|
+
const b = u + unzigzag(second - 2 >> 1);
|
|
71
|
+
decompositionMap.set(u, [a, b]);
|
|
72
|
+
if (second - 2 & 1)
|
|
73
|
+
compositionMap.set(a * 0x110000 + b, u);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const S_BASE = 0xac00, L_BASE = 0x1100, V_BASE = 0x1161, T_BASE = 0x11a7, L_COUNT = 19, V_COUNT = 21, T_COUNT = 28, N_COUNT = V_COUNT * T_COUNT, S_COUNT = L_COUNT * N_COUNT;
|
|
78
|
+
export function decompose(ab) {
|
|
79
|
+
const si = ab - S_BASE;
|
|
80
|
+
if (si >= 0 && si < S_COUNT) {
|
|
81
|
+
const ti = si % T_COUNT;
|
|
82
|
+
if (ti)
|
|
83
|
+
return [S_BASE + si - ti, T_BASE + ti];
|
|
84
|
+
return [L_BASE + Math.floor(si / N_COUNT), V_BASE + Math.floor(si % N_COUNT / T_COUNT)];
|
|
85
|
+
}
|
|
86
|
+
if (!decompositionMap)
|
|
87
|
+
loadDecompositions();
|
|
88
|
+
return decompositionMap.get(ab) ?? null;
|
|
89
|
+
}
|
|
90
|
+
export function compose(a, b) {
|
|
91
|
+
const si = a - S_BASE;
|
|
92
|
+
if (si >= 0 && si < S_COUNT) {
|
|
93
|
+
if (si % T_COUNT || b <= T_BASE || b >= T_BASE + T_COUNT)
|
|
94
|
+
return 0;
|
|
95
|
+
return a + b - T_BASE;
|
|
96
|
+
}
|
|
97
|
+
if (a >= L_BASE && a < L_BASE + L_COUNT && b >= V_BASE && b < V_BASE + V_COUNT)
|
|
98
|
+
return S_BASE + ((a - L_BASE) * V_COUNT + b - V_BASE) * T_COUNT;
|
|
99
|
+
if (!compositionMap)
|
|
100
|
+
loadDecompositions();
|
|
101
|
+
return compositionMap.get(a * 0x110000 + b) ?? 0;
|
|
102
|
+
}
|
|
103
|
+
const RTL_SCRIPTS = new Set(['Arab', 'Hebr', 'Syrc', 'Thaa', 'Cprt', 'Khar', 'Phnx', 'Nkoo', 'Lydi', 'Avst', 'Armi', 'Phli', 'Prti', 'Sarb', 'Orkh', 'Samr', 'Mand',
|
|
104
|
+
'Merc', 'Mero', 'Mani', 'Mend', 'Nbat', 'Narb', 'Palm', 'Phlp', 'Hatr', 'Adlm', 'Rohg', 'Sogo', 'Sogd', 'Elym', 'Chrs', 'Yezi', 'Ougr', 'Gara', 'Sidt']);
|
|
105
|
+
const BIDIRECTIONAL_SCRIPTS = new Set(['Hung', 'Ital', 'Runr', 'Tfng']);
|
|
106
|
+
export const scriptDirection = (script) => RTL_SCRIPTS.has(script) ? 5 : BIDIRECTIONAL_SCRIPTS.has(script) ? 0 : 4;
|
|
107
|
+
const NEW_TAGS = { Beng: 'bng2', Deva: 'dev2', Gujr: 'gjr2', Guru: 'gur2', Knda: 'knd2', Mlym: 'mlm2', Orya: 'ory2', Taml: 'tml2', Telu: 'tel2', Mymr: 'mym2' };
|
|
108
|
+
const OLD_TAGS = { Hira: 'kana', Hrkt: 'kana', Laoo: 'lao ', Yiii: 'yi ', Nkoo: 'nko ', Vaii: 'vai ', Zmth: 'math' };
|
|
109
|
+
export function scriptTags(script) {
|
|
110
|
+
const tags = [];
|
|
111
|
+
const newTag = NEW_TAGS[script];
|
|
112
|
+
if (newTag) {
|
|
113
|
+
if (newTag !== 'mym2')
|
|
114
|
+
tags.push(newTag.slice(0, 3) + '3');
|
|
115
|
+
tags.push(newTag);
|
|
116
|
+
}
|
|
117
|
+
if (script && script !== 'Zyyy' && script !== 'Zinh' && script !== 'Zzzz')
|
|
118
|
+
tags.push(OLD_TAGS[script] ?? script.toLowerCase());
|
|
119
|
+
return tags;
|
|
120
|
+
}
|
|
121
|
+
export const defaultShaper = { normalization: 4, zeroWidthMarks: 2, fallbackPosition: true };
|
|
122
|
+
export const shaperFor = {
|
|
123
|
+
categorize: () => defaultShaper,
|
|
124
|
+
};
|
|
125
|
+
export class ShapeFont {
|
|
126
|
+
font;
|
|
127
|
+
gdef;
|
|
128
|
+
gsub;
|
|
129
|
+
gpos;
|
|
130
|
+
plans = new Map();
|
|
131
|
+
constructor(font) {
|
|
132
|
+
this.font = font;
|
|
133
|
+
this.gdef = new GDEF(font);
|
|
134
|
+
this.gsub = font.tables.GSUB ? new LayoutTable(font, 'GSUB', font.tables.GSUB.offset) : null;
|
|
135
|
+
this.gpos = font.tables.GPOS ? new LayoutTable(font, 'GPOS', font.tables.GPOS.offset) : null;
|
|
136
|
+
}
|
|
137
|
+
glyph(u) {
|
|
138
|
+
return this.font.glyphIndex(u);
|
|
139
|
+
}
|
|
140
|
+
hasGlyph(u) {
|
|
141
|
+
return this.font.glyphIndex(u) !== 0;
|
|
142
|
+
}
|
|
143
|
+
advance(glyph) {
|
|
144
|
+
return glyph < this.font.numGlyphs ? this.font.advance(glyph) : 0;
|
|
145
|
+
}
|
|
146
|
+
plan(script, direction) {
|
|
147
|
+
const key = `${script}:${direction}`;
|
|
148
|
+
let plan = this.plans.get(key);
|
|
149
|
+
if (!plan)
|
|
150
|
+
this.plans.set(key, plan = new Planner(this, script, direction).compile());
|
|
151
|
+
return plan;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const shapeFonts = new WeakMap();
|
|
155
|
+
export function shapeFont(font) {
|
|
156
|
+
let f = shapeFonts.get(font);
|
|
157
|
+
if (!f)
|
|
158
|
+
shapeFonts.set(font, f = new ShapeFont(font));
|
|
159
|
+
return f;
|
|
160
|
+
}
|
|
161
|
+
const COMMON_FEATURES = [['abvm', 1], ['blwm', 1], ['ccmp', 1], ['locl', 1],
|
|
162
|
+
['mark', 13], ['mkmk', 13], ['rlig', 1]];
|
|
163
|
+
const HORIZONTAL_FEATURES = [['calt', 1], ['clig', 1], ['curs', 1],
|
|
164
|
+
['dist', 1], ['kern', 3], ['liga', 1], ['rclt', 1]];
|
|
165
|
+
export class Planner {
|
|
166
|
+
font;
|
|
167
|
+
script;
|
|
168
|
+
direction;
|
|
169
|
+
map;
|
|
170
|
+
shaper;
|
|
171
|
+
constructor(font, script, direction) {
|
|
172
|
+
this.font = font;
|
|
173
|
+
this.script = script;
|
|
174
|
+
this.direction = direction;
|
|
175
|
+
this.map = new MapBuilder([font.gsub, font.gpos], scriptTags(script));
|
|
176
|
+
this.shaper = shaperFor.categorize(script, direction, this.map.chosenScript[0]);
|
|
177
|
+
}
|
|
178
|
+
compile() {
|
|
179
|
+
const map = this.map, shaper = this.shaper;
|
|
180
|
+
map.enableFeature('rvrn');
|
|
181
|
+
map.addGsubPause(null);
|
|
182
|
+
if (this.direction === 4) {
|
|
183
|
+
map.enableFeature('ltra');
|
|
184
|
+
map.enableFeature('ltrm');
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
map.enableFeature('rtla');
|
|
188
|
+
map.addFeature('rtlm');
|
|
189
|
+
}
|
|
190
|
+
map.addFeature('frac');
|
|
191
|
+
map.addFeature('numr');
|
|
192
|
+
map.addFeature('dnom');
|
|
193
|
+
map.enableFeature('rand', 32, 255);
|
|
194
|
+
map.enableFeature('Harf');
|
|
195
|
+
map.enableFeature('HARF');
|
|
196
|
+
if (shaper.collectFeatures) {
|
|
197
|
+
map.isSimple = false;
|
|
198
|
+
shaper.collectFeatures(this);
|
|
199
|
+
}
|
|
200
|
+
map.enableFeature('Buzz');
|
|
201
|
+
map.enableFeature('BUZZ');
|
|
202
|
+
for (const [tag, flags] of COMMON_FEATURES)
|
|
203
|
+
map.addFeature(tag, flags);
|
|
204
|
+
for (const [tag, flags] of HORIZONTAL_FEATURES)
|
|
205
|
+
map.addFeature(tag, flags);
|
|
206
|
+
if (shaper.overrideFeatures)
|
|
207
|
+
shaper.overrideFeatures(this);
|
|
208
|
+
const m = map.compile();
|
|
209
|
+
const font = this.font;
|
|
210
|
+
const plan = {
|
|
211
|
+
font, script: this.script, direction: this.direction, shaper, map: m, data: null,
|
|
212
|
+
fracMask: m.get1Mask('frac'), numrMask: m.get1Mask('numr'), dnomMask: m.get1Mask('dnom'), hasFrac: false,
|
|
213
|
+
rtlmMask: m.get1Mask('rtlm'), kernMask: m.getMask('kern')[0],
|
|
214
|
+
applyGpos: false, applyKern: false, zeroMarks: false, hasGposMark: false, adjustMarkPositioningWhenZeroing: false, fallbackMarkPositioning: false,
|
|
215
|
+
fallbackGlyphClasses: !font.gdef.hasGlyphClasses,
|
|
216
|
+
};
|
|
217
|
+
plan.hasFrac = !!plan.fracMask || !!plan.numrMask && !!plan.dnomMask;
|
|
218
|
+
const hasGposKern = m.featureIndex(1, 'kern') >= 0;
|
|
219
|
+
const disableGpos = !!shaper.gposTag && shaper.gposTag !== m.chosenScript[1];
|
|
220
|
+
const hasGpos = !disableGpos && !!font.gpos;
|
|
221
|
+
if (hasGpos)
|
|
222
|
+
plan.applyGpos = true;
|
|
223
|
+
if (!hasGposKern || !plan.applyGpos) {
|
|
224
|
+
if (font.font.kernTable)
|
|
225
|
+
plan.applyKern = shaper.fallbackPosition;
|
|
226
|
+
}
|
|
227
|
+
plan.zeroMarks = shaper.zeroWidthMarks !== 0;
|
|
228
|
+
plan.hasGposMark = !!m.get1Mask('mark');
|
|
229
|
+
plan.adjustMarkPositioningWhenZeroing = !plan.applyGpos;
|
|
230
|
+
plan.fallbackMarkPositioning = plan.adjustMarkPositioningWhenZeroing && shaper.fallbackPosition;
|
|
231
|
+
if (shaper.dataCreate)
|
|
232
|
+
plan.data = shaper.dataCreate(plan);
|
|
233
|
+
return plan;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
export function setUnicodeProps(info, buffer) {
|
|
237
|
+
const u = info.codepoint;
|
|
238
|
+
const category = unicodeCategory(u);
|
|
239
|
+
let props = category;
|
|
240
|
+
if (u >= 0x80) {
|
|
241
|
+
if (isDefaultIgnorableCodepoint(u)) {
|
|
242
|
+
buffer.hasIgnorables = true;
|
|
243
|
+
props |= 32;
|
|
244
|
+
if (u === 0x200c)
|
|
245
|
+
props |= 512;
|
|
246
|
+
else if (u === 0x200d)
|
|
247
|
+
props |= 256;
|
|
248
|
+
else if (u >= 0x180b && u <= 0x180d || u === 0x180f)
|
|
249
|
+
props |= 64;
|
|
250
|
+
else if (u >= 0xe0020 && u <= 0xe007f)
|
|
251
|
+
props |= 64;
|
|
252
|
+
else if (u === 0x034f) {
|
|
253
|
+
buffer.hasCGJ = true;
|
|
254
|
+
props |= 64;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (isMark(category)) {
|
|
258
|
+
buffer.hasContinuations = true;
|
|
259
|
+
props |= 128;
|
|
260
|
+
props |= modifiedCombiningClass(u) << 8;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
info.unicodeProps = props;
|
|
264
|
+
}
|
|
265
|
+
function setUnicodePropsAll(buffer, globalMask) {
|
|
266
|
+
const info = buffer.info, count = info.length;
|
|
267
|
+
for (let i = 0; i < count; i++) {
|
|
268
|
+
info[i].mask = globalMask;
|
|
269
|
+
setUnicodeProps(info[i], buffer);
|
|
270
|
+
const u = info[i].codepoint;
|
|
271
|
+
if (u < 0x80)
|
|
272
|
+
continue;
|
|
273
|
+
const category = generalCategory(info[i]);
|
|
274
|
+
if (category === 5 || category === 9 || category === 8 || category === 7 || category === 29)
|
|
275
|
+
continue;
|
|
276
|
+
if (category === 24 && u >= 0x1f3fb && u <= 0x1f3ff)
|
|
277
|
+
setContinuation(info[i], buffer);
|
|
278
|
+
else if (i && isRegionalIndicator(u)) {
|
|
279
|
+
if (isRegionalIndicator(info[i - 1].codepoint) && !isContinuation(info[i - 1]))
|
|
280
|
+
setContinuation(info[i], buffer);
|
|
281
|
+
}
|
|
282
|
+
else if (u === 0x200d && isDefaultIgnorable(info[i])) {
|
|
283
|
+
setContinuation(info[i], buffer);
|
|
284
|
+
if (i + 1 < count && valueIndex(extendedPictographic, info[i + 1].codepoint)) {
|
|
285
|
+
i++;
|
|
286
|
+
info[i].mask = globalMask;
|
|
287
|
+
setUnicodeProps(info[i], buffer);
|
|
288
|
+
setContinuation(info[i], buffer);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else if (u >= 0xff9e && u <= 0xff9f || u >= 0xe0020 && u <= 0xe007f)
|
|
292
|
+
setContinuation(info[i], buffer);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const setContinuation = (info, buffer) => {
|
|
296
|
+
buffer.hasContinuations = true;
|
|
297
|
+
info.unicodeProps |= 128;
|
|
298
|
+
};
|
|
299
|
+
const graphemeGroup = (_a, b) => isContinuation(b);
|
|
300
|
+
function insertDottedCircle(buffer, font) {
|
|
301
|
+
if (!buffer.beginning || !buffer.len || !isUnicodeMark(buffer.info[0]))
|
|
302
|
+
return;
|
|
303
|
+
if (!font.hasGlyph(0x25cc))
|
|
304
|
+
return;
|
|
305
|
+
const dottedCircle = new GlyphInfo();
|
|
306
|
+
dottedCircle.codepoint = 0x25cc;
|
|
307
|
+
setUnicodeProps(dottedCircle, buffer);
|
|
308
|
+
buffer.clearOutput();
|
|
309
|
+
buffer.idx = 0;
|
|
310
|
+
dottedCircle.cluster = buffer.cur().cluster;
|
|
311
|
+
dottedCircle.mask = buffer.cur().mask;
|
|
312
|
+
buffer.outputInfo(dottedCircle);
|
|
313
|
+
buffer.sync();
|
|
314
|
+
}
|
|
315
|
+
function formClusters(buffer) {
|
|
316
|
+
if (!buffer.hasContinuations)
|
|
317
|
+
return;
|
|
318
|
+
for (let start = 0; start < buffer.len;) {
|
|
319
|
+
const end = buffer.groupEnd(start, graphemeGroup);
|
|
320
|
+
buffer.mergeClusters(start, end);
|
|
321
|
+
start = end;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function ensureNativeDirection(buffer) {
|
|
325
|
+
let horizontal = scriptDirection(buffer.script);
|
|
326
|
+
if (horizontal === 5 && buffer.direction === 4) {
|
|
327
|
+
let number = false, letter = false, ri = false;
|
|
328
|
+
for (const info of buffer.info) {
|
|
329
|
+
const c = generalCategory(info);
|
|
330
|
+
if (c === 5 || c === 6 || c === 7 || c === 8 || c === 9) {
|
|
331
|
+
letter = true;
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
else if (c === 13)
|
|
335
|
+
number = true;
|
|
336
|
+
else if (isRegionalIndicator(info.codepoint))
|
|
337
|
+
ri = true;
|
|
338
|
+
}
|
|
339
|
+
if ((number || ri) && !letter)
|
|
340
|
+
horizontal = 4;
|
|
341
|
+
}
|
|
342
|
+
if (horizontal && buffer.direction !== horizontal) {
|
|
343
|
+
buffer.reverseGroups(graphemeGroup);
|
|
344
|
+
buffer.direction = buffer.direction === 4 ? 5 : 4;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function rotateChars(plan, buffer, targetDirection) {
|
|
348
|
+
if (targetDirection !== 5)
|
|
349
|
+
return;
|
|
350
|
+
for (const info of buffer.info) {
|
|
351
|
+
const mirrored = mirror(info.codepoint);
|
|
352
|
+
if (mirrored !== info.codepoint && plan.font.hasGlyph(mirrored))
|
|
353
|
+
info.codepoint = mirrored;
|
|
354
|
+
else
|
|
355
|
+
info.mask |= plan.rtlmMask;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function setupMasksFraction(plan, buffer, hasFractionSlash) {
|
|
359
|
+
if (!hasFractionSlash || !plan.hasFrac)
|
|
360
|
+
return;
|
|
361
|
+
const forward = buffer.direction === 4;
|
|
362
|
+
const pre = forward ? plan.numrMask | plan.fracMask : plan.fracMask | plan.dnomMask;
|
|
363
|
+
const post = forward ? plan.fracMask | plan.dnomMask : plan.numrMask | plan.fracMask;
|
|
364
|
+
const info = buffer.info, count = info.length;
|
|
365
|
+
for (let i = 0; i < count; i++) {
|
|
366
|
+
if (info[i].codepoint !== 0x2044)
|
|
367
|
+
continue;
|
|
368
|
+
let start = i, end = i + 1;
|
|
369
|
+
while (start && generalCategory(info[start - 1]) === 13)
|
|
370
|
+
start--;
|
|
371
|
+
while (end < count && generalCategory(info[end]) === 13)
|
|
372
|
+
end++;
|
|
373
|
+
if (start === i || end === i + 1)
|
|
374
|
+
continue;
|
|
375
|
+
for (let j = start; j < i; j++)
|
|
376
|
+
info[j].mask |= pre;
|
|
377
|
+
info[i].mask |= plan.fracMask;
|
|
378
|
+
for (let j = i + 1; j < end; j++)
|
|
379
|
+
info[j].mask |= post;
|
|
380
|
+
i = end - 1;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const MAX_COMBINING_MARKS = 32;
|
|
384
|
+
function normalize(plan, buffer, font) {
|
|
385
|
+
if (!buffer.len)
|
|
386
|
+
return;
|
|
387
|
+
let mode = plan.shaper.normalization;
|
|
388
|
+
if (mode === 4)
|
|
389
|
+
mode = 2;
|
|
390
|
+
const c = {
|
|
391
|
+
plan, font,
|
|
392
|
+
decompose: plan.shaper.decompose ?? ((_c, ab) => decompose(ab)),
|
|
393
|
+
compose: plan.shaper.compose ?? ((_c, a, b) => compose(a, b)),
|
|
394
|
+
};
|
|
395
|
+
const alwaysShortCircuit = mode === 0;
|
|
396
|
+
const mightShortCircuit = alwaysShortCircuit || mode !== 1 && mode !== 3;
|
|
397
|
+
let allSimple = true;
|
|
398
|
+
buffer.clearOutput();
|
|
399
|
+
const count = buffer.len;
|
|
400
|
+
buffer.idx = 0;
|
|
401
|
+
do {
|
|
402
|
+
let end = buffer.idx + 1;
|
|
403
|
+
for (; end < count; end++)
|
|
404
|
+
if (isUnicodeMark(buffer.info[end]))
|
|
405
|
+
break;
|
|
406
|
+
if (end < count)
|
|
407
|
+
end--;
|
|
408
|
+
if (mightShortCircuit) {
|
|
409
|
+
let done = 0;
|
|
410
|
+
for (let i = buffer.idx; i < end; i++, done++) {
|
|
411
|
+
const glyph = font.glyph(buffer.info[i].codepoint);
|
|
412
|
+
if (!glyph)
|
|
413
|
+
break;
|
|
414
|
+
buffer.info[i].glyphIndex = glyph;
|
|
415
|
+
}
|
|
416
|
+
buffer.nextGlyphs(done);
|
|
417
|
+
}
|
|
418
|
+
while (buffer.idx < end && buffer.successful)
|
|
419
|
+
decomposeCurrentCharacter(c, buffer, mightShortCircuit);
|
|
420
|
+
if (buffer.idx === count || !buffer.successful)
|
|
421
|
+
break;
|
|
422
|
+
allSimple = false;
|
|
423
|
+
for (end = buffer.idx + 1; end < count; end++)
|
|
424
|
+
if (!isUnicodeMark(buffer.info[end]))
|
|
425
|
+
break;
|
|
426
|
+
decomposeMultiCharCluster(c, buffer, end, alwaysShortCircuit);
|
|
427
|
+
} while (buffer.idx < count && buffer.successful);
|
|
428
|
+
buffer.sync();
|
|
429
|
+
if (!allSimple) {
|
|
430
|
+
const info = buffer.info, n = buffer.len;
|
|
431
|
+
for (let i = 0; i < n; i++) {
|
|
432
|
+
if (!combiningClass(info[i]))
|
|
433
|
+
continue;
|
|
434
|
+
let end = i + 1;
|
|
435
|
+
for (; end < n; end++)
|
|
436
|
+
if (!combiningClass(info[end]))
|
|
437
|
+
break;
|
|
438
|
+
if (end - i > MAX_COMBINING_MARKS) {
|
|
439
|
+
i = end;
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
buffer.sort(i, end, (a, b) => combiningClass(a) - combiningClass(b));
|
|
443
|
+
if (plan.shaper.reorderMarks)
|
|
444
|
+
plan.shaper.reorderMarks(plan, buffer, i, end);
|
|
445
|
+
i = end;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (buffer.hasCGJ) {
|
|
449
|
+
const info = buffer.info;
|
|
450
|
+
for (let i = 1; i + 1 < info.length; i++)
|
|
451
|
+
if (info[i].codepoint === 0x034f && (!combiningClass(info[i + 1]) || combiningClass(info[i - 1]) <= combiningClass(info[i + 1])))
|
|
452
|
+
info[i].unicodeProps &= ~64;
|
|
453
|
+
}
|
|
454
|
+
if (!allSimple && buffer.successful && (mode === 2 || mode === 3)) {
|
|
455
|
+
buffer.clearOutput();
|
|
456
|
+
const n = buffer.len;
|
|
457
|
+
let starter = 0;
|
|
458
|
+
buffer.nextGlyph();
|
|
459
|
+
while (buffer.idx < n) {
|
|
460
|
+
const cur = buffer.cur();
|
|
461
|
+
if (isUnicodeMark(cur) && (starter === buffer.outLen - 1 || combiningClass(buffer.prev()) < combiningClass(cur))) {
|
|
462
|
+
const composed = c.compose(c, buffer.outInfo[starter].codepoint, cur.codepoint);
|
|
463
|
+
const glyph = composed ? font.glyph(composed) : 0;
|
|
464
|
+
if (composed && glyph) {
|
|
465
|
+
buffer.nextGlyph();
|
|
466
|
+
buffer.mergeOutClusters(starter, buffer.outLen);
|
|
467
|
+
buffer.outLen--;
|
|
468
|
+
const s = buffer.outInfo[starter];
|
|
469
|
+
s.codepoint = composed;
|
|
470
|
+
s.glyphIndex = glyph;
|
|
471
|
+
setUnicodeProps(s, buffer);
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
buffer.nextGlyph();
|
|
476
|
+
if (!combiningClass(buffer.prev()))
|
|
477
|
+
starter = buffer.outLen - 1;
|
|
478
|
+
}
|
|
479
|
+
buffer.sync();
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function outputChar(buffer, u, glyph) {
|
|
483
|
+
buffer.cur().glyphIndex = glyph;
|
|
484
|
+
const info = buffer.outputGlyph(u);
|
|
485
|
+
setUnicodeProps(info, buffer);
|
|
486
|
+
}
|
|
487
|
+
function nextChar(buffer, glyph) {
|
|
488
|
+
buffer.cur().glyphIndex = glyph;
|
|
489
|
+
buffer.nextGlyph();
|
|
490
|
+
}
|
|
491
|
+
function decomposeInto(c, buffer, shortest, ab) {
|
|
492
|
+
const parts = c.decompose(c, ab);
|
|
493
|
+
if (!parts)
|
|
494
|
+
return 0;
|
|
495
|
+
const [a, b] = parts;
|
|
496
|
+
const bGlyph = b ? c.font.glyph(b) : 0;
|
|
497
|
+
if (b && !bGlyph)
|
|
498
|
+
return 0;
|
|
499
|
+
const aGlyph = c.font.glyph(a);
|
|
500
|
+
if (shortest && aGlyph) {
|
|
501
|
+
outputChar(buffer, a, aGlyph);
|
|
502
|
+
if (b) {
|
|
503
|
+
outputChar(buffer, b, bGlyph);
|
|
504
|
+
return 2;
|
|
505
|
+
}
|
|
506
|
+
return 1;
|
|
507
|
+
}
|
|
508
|
+
const ret = decomposeInto(c, buffer, shortest, a);
|
|
509
|
+
if (ret) {
|
|
510
|
+
if (b) {
|
|
511
|
+
outputChar(buffer, b, bGlyph);
|
|
512
|
+
return ret + 1;
|
|
513
|
+
}
|
|
514
|
+
return ret;
|
|
515
|
+
}
|
|
516
|
+
if (aGlyph) {
|
|
517
|
+
outputChar(buffer, a, aGlyph);
|
|
518
|
+
if (b) {
|
|
519
|
+
outputChar(buffer, b, bGlyph);
|
|
520
|
+
return 2;
|
|
521
|
+
}
|
|
522
|
+
return 1;
|
|
523
|
+
}
|
|
524
|
+
return 0;
|
|
525
|
+
}
|
|
526
|
+
function decomposeCurrentCharacter(c, buffer, shortest) {
|
|
527
|
+
const u = buffer.cur().codepoint;
|
|
528
|
+
const glyph = c.font.glyph(u);
|
|
529
|
+
if (shortest && glyph)
|
|
530
|
+
return nextChar(buffer, glyph);
|
|
531
|
+
if (decomposeInto(c, buffer, shortest, u))
|
|
532
|
+
return buffer.skipGlyph();
|
|
533
|
+
if (!shortest && glyph)
|
|
534
|
+
return nextChar(buffer, glyph);
|
|
535
|
+
if (isUnicodeSpace(buffer.cur())) {
|
|
536
|
+
const type = SPACES[u] ?? 0;
|
|
537
|
+
const space = c.font.glyph(0x20);
|
|
538
|
+
if (type !== 0 && space) {
|
|
539
|
+
setSpaceFallback(buffer.cur(), type);
|
|
540
|
+
nextChar(buffer, space);
|
|
541
|
+
buffer.hasSpaceFallback = true;
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (u === 0x2011) {
|
|
546
|
+
const other = c.font.glyph(0x2010);
|
|
547
|
+
if (other)
|
|
548
|
+
return nextChar(buffer, other);
|
|
549
|
+
}
|
|
550
|
+
nextChar(buffer, glyph);
|
|
551
|
+
}
|
|
552
|
+
function handleVariationSelectorCluster(c, buffer, end) {
|
|
553
|
+
const font = c.font;
|
|
554
|
+
const setGlyph = (info) => {
|
|
555
|
+
info.glyphIndex = font.glyph(info.codepoint);
|
|
556
|
+
};
|
|
557
|
+
while (buffer.idx < end - 1 && buffer.successful) {
|
|
558
|
+
if (isVariationSelectorCodepoint(buffer.cur(1).codepoint)) {
|
|
559
|
+
const glyph = font.font.variationGlyph(buffer.cur().codepoint, buffer.cur(1).codepoint);
|
|
560
|
+
if (glyph) {
|
|
561
|
+
buffer.cur().glyphIndex = glyph;
|
|
562
|
+
const u = buffer.cur().codepoint;
|
|
563
|
+
buffer.replaceGlyphs(2, 1, [u]);
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
setGlyph(buffer.cur());
|
|
567
|
+
buffer.nextGlyph();
|
|
568
|
+
setGeneralCategory(buffer.cur(), 1);
|
|
569
|
+
buffer.cur().unicodeProps |= 1024;
|
|
570
|
+
setGlyph(buffer.cur());
|
|
571
|
+
buffer.nextGlyph();
|
|
572
|
+
}
|
|
573
|
+
while (buffer.idx < end && buffer.successful && isVariationSelectorCodepoint(buffer.cur().codepoint)) {
|
|
574
|
+
setGlyph(buffer.cur());
|
|
575
|
+
buffer.nextGlyph();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
setGlyph(buffer.cur());
|
|
580
|
+
buffer.nextGlyph();
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (buffer.idx < end) {
|
|
584
|
+
setGlyph(buffer.cur());
|
|
585
|
+
buffer.nextGlyph();
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
function decomposeMultiCharCluster(c, buffer, end, shortCircuit) {
|
|
589
|
+
for (let i = buffer.idx; i < end && buffer.successful; i++)
|
|
590
|
+
if (isVariationSelectorCodepoint(buffer.info[i].codepoint))
|
|
591
|
+
return handleVariationSelectorCluster(c, buffer, end);
|
|
592
|
+
while (buffer.idx < end && buffer.successful)
|
|
593
|
+
decomposeCurrentCharacter(c, buffer, shortCircuit);
|
|
594
|
+
}
|
|
595
|
+
function synthesizeGlyphClasses(buffer) {
|
|
596
|
+
for (const info of buffer.info)
|
|
597
|
+
info.glyphProps = generalCategory(info) !== 12 || isDefaultIgnorable(info) ? 2 : 8;
|
|
598
|
+
}
|
|
599
|
+
function zeroMarkWidthsByGdef(buffer, adjustOffsets) {
|
|
600
|
+
for (const info of buffer.info)
|
|
601
|
+
if (isMarkGlyph(info)) {
|
|
602
|
+
if (adjustOffsets) {
|
|
603
|
+
info.xOffset -= info.xAdvance;
|
|
604
|
+
info.yOffset -= info.yAdvance;
|
|
605
|
+
}
|
|
606
|
+
info.xAdvance = info.yAdvance = 0;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function zeroWidthDefaultIgnorables(buffer) {
|
|
610
|
+
if (!buffer.hasIgnorables)
|
|
611
|
+
return;
|
|
612
|
+
for (const info of buffer.info)
|
|
613
|
+
if (isDefaultIgnorable(info))
|
|
614
|
+
info.xAdvance = info.yAdvance = info.xOffset = 0;
|
|
615
|
+
}
|
|
616
|
+
function hideDefaultIgnorables(buffer, font) {
|
|
617
|
+
if (!buffer.hasIgnorables)
|
|
618
|
+
return;
|
|
619
|
+
const invisible = font.glyph(0x20);
|
|
620
|
+
if (invisible) {
|
|
621
|
+
for (const info of buffer.info)
|
|
622
|
+
if (isDefaultIgnorable(info))
|
|
623
|
+
info.codepoint = invisible;
|
|
624
|
+
}
|
|
625
|
+
else
|
|
626
|
+
buffer.deleteGlyphsInplace(isDefaultIgnorable);
|
|
627
|
+
}
|
|
628
|
+
function fallbackSpaces(font, buffer) {
|
|
629
|
+
const upem = font.font.unitsPerEm;
|
|
630
|
+
for (const info of buffer.info) {
|
|
631
|
+
if (!isUnicodeSpace(info) || isLigated(info))
|
|
632
|
+
continue;
|
|
633
|
+
const type = spaceFallback(info);
|
|
634
|
+
switch (type) {
|
|
635
|
+
case 1:
|
|
636
|
+
case 2:
|
|
637
|
+
case 3:
|
|
638
|
+
case 4:
|
|
639
|
+
case 5:
|
|
640
|
+
case 6:
|
|
641
|
+
case 16:
|
|
642
|
+
info.xAdvance = Math.trunc((upem + (type >> 1)) / type);
|
|
643
|
+
break;
|
|
644
|
+
case 17:
|
|
645
|
+
info.xAdvance = Math.trunc(upem * 4 / 18);
|
|
646
|
+
break;
|
|
647
|
+
case 19:
|
|
648
|
+
for (let u = 0x30; u <= 0x39; u++) {
|
|
649
|
+
const glyph = font.glyph(u);
|
|
650
|
+
if (glyph) {
|
|
651
|
+
info.xAdvance = font.advance(glyph);
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
break;
|
|
656
|
+
case 20: {
|
|
657
|
+
const glyph = font.glyph(0x2e) || font.glyph(0x2c);
|
|
658
|
+
if (glyph)
|
|
659
|
+
info.xAdvance = font.advance(glyph);
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
case 21:
|
|
663
|
+
info.xAdvance = Math.trunc(info.xAdvance / 2);
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function kernTable(plan, buffer) {
|
|
669
|
+
const kern = plan.font.font.kernTable;
|
|
670
|
+
if (!kern)
|
|
671
|
+
return;
|
|
672
|
+
const c = new ApplyContext(1, plan.font.gpos, buffer, plan.font.gdef);
|
|
673
|
+
c.lookupMask = plan.kernMask;
|
|
674
|
+
c.setLookupProps(8);
|
|
675
|
+
const it = c.iterInput, info = buffer.info;
|
|
676
|
+
for (let idx = 0; idx < info.length;) {
|
|
677
|
+
if (!(info[idx].mask & plan.kernMask)) {
|
|
678
|
+
idx++;
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
it.resetFast(idx);
|
|
682
|
+
it.setMatchFunc(null, null);
|
|
683
|
+
if (!it.next()) {
|
|
684
|
+
idx = it.idx + 1;
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
const i = idx, j = it.idx;
|
|
688
|
+
const k = kern(info[i].codepoint, info[j].codepoint);
|
|
689
|
+
if (k) {
|
|
690
|
+
const k1 = k >> 1, k2 = k - k1;
|
|
691
|
+
info[i].xAdvance += k1;
|
|
692
|
+
info[j].xAdvance += k2;
|
|
693
|
+
info[j].xOffset += k2;
|
|
694
|
+
}
|
|
695
|
+
idx = it.idx;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
function recategorizeCombiningClass(u, klass) {
|
|
699
|
+
if (klass >= 200)
|
|
700
|
+
return klass;
|
|
701
|
+
if ((u & ~0xff) === 0x0e00) {
|
|
702
|
+
if (!klass) {
|
|
703
|
+
if ([0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37, 0x0e47, 0x0e4c, 0x0e4d, 0x0e4e].includes(u))
|
|
704
|
+
klass = 232;
|
|
705
|
+
else if ([0x0eb1, 0x0eb4, 0x0eb5, 0x0eb6, 0x0eb7, 0x0ebb, 0x0ecc, 0x0ecd].includes(u))
|
|
706
|
+
klass = 230;
|
|
707
|
+
else if (u === 0x0ebc)
|
|
708
|
+
klass = 220;
|
|
709
|
+
}
|
|
710
|
+
else if (u === 0x0e3a)
|
|
711
|
+
klass = 222;
|
|
712
|
+
}
|
|
713
|
+
switch (klass) {
|
|
714
|
+
case 22:
|
|
715
|
+
case 15:
|
|
716
|
+
case 16:
|
|
717
|
+
case 17:
|
|
718
|
+
case 23:
|
|
719
|
+
case 18:
|
|
720
|
+
case 19:
|
|
721
|
+
case 20:
|
|
722
|
+
case 21:
|
|
723
|
+
case 24:
|
|
724
|
+
case 25: return 220;
|
|
725
|
+
case 13: return 214;
|
|
726
|
+
case 10: return 232;
|
|
727
|
+
case 11:
|
|
728
|
+
case 14: return 228;
|
|
729
|
+
case 26: return 230;
|
|
730
|
+
case 12: break;
|
|
731
|
+
case 28:
|
|
732
|
+
case 29:
|
|
733
|
+
case 31:
|
|
734
|
+
case 32:
|
|
735
|
+
case 27:
|
|
736
|
+
case 34:
|
|
737
|
+
case 35:
|
|
738
|
+
case 36: return 230;
|
|
739
|
+
case 30:
|
|
740
|
+
case 33: return 220;
|
|
741
|
+
case 3: return 222;
|
|
742
|
+
case 107: return 232;
|
|
743
|
+
case 118: return 220;
|
|
744
|
+
case 122: return 230;
|
|
745
|
+
case 129: return 220;
|
|
746
|
+
case 132: return 230;
|
|
747
|
+
case 131: return 220;
|
|
748
|
+
}
|
|
749
|
+
return klass;
|
|
750
|
+
}
|
|
751
|
+
function recategorizeMarks(buffer) {
|
|
752
|
+
for (const info of buffer.info)
|
|
753
|
+
if (generalCategory(info) === 12)
|
|
754
|
+
setCombiningClass(info, recategorizeCombiningClass(info.codepoint, combiningClass(info)));
|
|
755
|
+
}
|
|
756
|
+
function glyphExtents(font, glyph) {
|
|
757
|
+
const e = font.font.extents(glyph);
|
|
758
|
+
if (!e)
|
|
759
|
+
return null;
|
|
760
|
+
return { xBearing: Math.min(e.xMin, e.xMax), yBearing: e.yMax, width: e.xMax - e.xMin, height: e.yMin - e.yMax };
|
|
761
|
+
}
|
|
762
|
+
function positionMark(font, buffer, base, i, klass) {
|
|
763
|
+
const info = buffer.info[i];
|
|
764
|
+
const mark = glyphExtents(font, info.codepoint);
|
|
765
|
+
if (!mark)
|
|
766
|
+
return;
|
|
767
|
+
const yGap = Math.trunc(font.font.unitsPerEm / 16);
|
|
768
|
+
info.xOffset = info.yOffset = 0;
|
|
769
|
+
switch (klass) {
|
|
770
|
+
case 233:
|
|
771
|
+
case 234:
|
|
772
|
+
if (buffer.direction === 4) {
|
|
773
|
+
info.xOffset = base.xBearing + base.width - Math.trunc(mark.width / 2) - mark.xBearing;
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
info.xOffset = base.xBearing - Math.trunc(mark.width / 2) - mark.xBearing;
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
default:
|
|
781
|
+
info.xOffset = base.xBearing + Math.trunc((base.width - mark.width) / 2) - mark.xBearing;
|
|
782
|
+
break;
|
|
783
|
+
case 200:
|
|
784
|
+
case 218:
|
|
785
|
+
case 228:
|
|
786
|
+
info.xOffset = base.xBearing - mark.xBearing;
|
|
787
|
+
break;
|
|
788
|
+
case 216:
|
|
789
|
+
case 222:
|
|
790
|
+
case 232:
|
|
791
|
+
info.xOffset = base.xBearing + base.width - mark.width - mark.xBearing;
|
|
792
|
+
break;
|
|
793
|
+
}
|
|
794
|
+
const below = klass === 233 || klass === 218 || klass === 220 || klass === 222;
|
|
795
|
+
const above = klass === 234 || klass === 228 || klass === 230 || klass === 232;
|
|
796
|
+
if (below || klass === 200 || klass === 202) {
|
|
797
|
+
if (below)
|
|
798
|
+
base.height -= yGap;
|
|
799
|
+
info.yOffset = base.yBearing + base.height - mark.yBearing;
|
|
800
|
+
if (yGap > 0 === info.yOffset > 0) {
|
|
801
|
+
base.height -= info.yOffset;
|
|
802
|
+
info.yOffset = 0;
|
|
803
|
+
}
|
|
804
|
+
base.height += mark.height;
|
|
805
|
+
}
|
|
806
|
+
else if (above || klass === 214 || klass === 216) {
|
|
807
|
+
if (above) {
|
|
808
|
+
base.yBearing += yGap;
|
|
809
|
+
base.height -= yGap;
|
|
810
|
+
}
|
|
811
|
+
info.yOffset = base.yBearing - mark.yBearing - mark.height;
|
|
812
|
+
if (yGap > 0 !== info.yOffset > 0) {
|
|
813
|
+
const correction = -Math.trunc(info.yOffset / 2);
|
|
814
|
+
base.yBearing += correction;
|
|
815
|
+
base.height -= correction;
|
|
816
|
+
info.yOffset += correction;
|
|
817
|
+
}
|
|
818
|
+
base.yBearing -= mark.height;
|
|
819
|
+
base.height += mark.height;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function positionAroundBase(plan, font, buffer, base, end, adjustOffsets) {
|
|
823
|
+
const info = buffer.info;
|
|
824
|
+
const extents = glyphExtents(font, info[base].codepoint);
|
|
825
|
+
if (!extents) {
|
|
826
|
+
for (let i = base + 1; i < end; i++)
|
|
827
|
+
if (generalCategory(info[i]) === 12) {
|
|
828
|
+
if (adjustOffsets) {
|
|
829
|
+
info[i].xOffset -= info[i].xAdvance;
|
|
830
|
+
info[i].yOffset -= info[i].yAdvance;
|
|
831
|
+
}
|
|
832
|
+
info[i].xAdvance = info[i].yAdvance = 0;
|
|
833
|
+
}
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
extents.yBearing += info[base].yOffset;
|
|
837
|
+
extents.xBearing = 0;
|
|
838
|
+
extents.width = font.advance(info[base].codepoint);
|
|
839
|
+
const id = ligId(info[base]), components = ligNumComps(info[base]);
|
|
840
|
+
let xOffset = 0, yOffset = 0;
|
|
841
|
+
if (buffer.direction === 4) {
|
|
842
|
+
xOffset = -info[base].xAdvance;
|
|
843
|
+
yOffset = -info[base].yAdvance;
|
|
844
|
+
}
|
|
845
|
+
let componentExtents = { ...extents }, lastComponent = -1, lastClass = 255, clusterExtents = { ...extents };
|
|
846
|
+
for (let i = base + 1; i < end; i++) {
|
|
847
|
+
if (combiningClass(info[i])) {
|
|
848
|
+
if (components > 1) {
|
|
849
|
+
const thisId = ligId(info[i]);
|
|
850
|
+
let thisComponent = ligComp(info[i]) - 1;
|
|
851
|
+
if (!id || id !== thisId || thisComponent >= components)
|
|
852
|
+
thisComponent = components - 1;
|
|
853
|
+
if (lastComponent !== thisComponent) {
|
|
854
|
+
lastComponent = thisComponent;
|
|
855
|
+
lastClass = 255;
|
|
856
|
+
componentExtents = { ...extents };
|
|
857
|
+
if (plan.direction === 4)
|
|
858
|
+
componentExtents.xBearing += Math.trunc(thisComponent * componentExtents.width / components);
|
|
859
|
+
else
|
|
860
|
+
componentExtents.xBearing += Math.trunc((components - 1 - thisComponent) * componentExtents.width / components);
|
|
861
|
+
componentExtents.width = Math.trunc(componentExtents.width / components);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
const thisClass = combiningClass(info[i]);
|
|
865
|
+
if (lastClass !== thisClass) {
|
|
866
|
+
lastClass = thisClass;
|
|
867
|
+
clusterExtents = { ...componentExtents };
|
|
868
|
+
}
|
|
869
|
+
positionMark(font, buffer, clusterExtents, i, thisClass);
|
|
870
|
+
info[i].xAdvance = info[i].yAdvance = 0;
|
|
871
|
+
info[i].xOffset += xOffset;
|
|
872
|
+
info[i].yOffset += yOffset;
|
|
873
|
+
}
|
|
874
|
+
else if (buffer.direction === 4) {
|
|
875
|
+
xOffset -= info[i].xAdvance;
|
|
876
|
+
yOffset -= info[i].yAdvance;
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
xOffset += info[i].xAdvance;
|
|
880
|
+
yOffset += info[i].yAdvance;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
function positionCluster(plan, font, buffer, start, end, adjustOffsets) {
|
|
885
|
+
if (end - start < 2)
|
|
886
|
+
return;
|
|
887
|
+
const info = buffer.info;
|
|
888
|
+
for (let i = start; i < end; i++) {
|
|
889
|
+
if (isUnicodeMark(info[i]))
|
|
890
|
+
continue;
|
|
891
|
+
let j = i + 1;
|
|
892
|
+
for (; j < end; j++) {
|
|
893
|
+
if (isHidden(info[j]) || isDefaultIgnorable(info[j]))
|
|
894
|
+
continue;
|
|
895
|
+
if (!isUnicodeMark(info[j]))
|
|
896
|
+
break;
|
|
897
|
+
}
|
|
898
|
+
positionAroundBase(plan, font, buffer, i, j, adjustOffsets);
|
|
899
|
+
i = j - 1;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
function fallbackMarkPosition(plan, font, buffer, adjustOffsets) {
|
|
903
|
+
const info = buffer.info;
|
|
904
|
+
let start = 0;
|
|
905
|
+
for (let i = 1; i < info.length; i++)
|
|
906
|
+
if (!isUnicodeMark(info[i]) && !isHidden(info[i]) && !isDefaultIgnorable(info[i])) {
|
|
907
|
+
positionCluster(plan, font, buffer, start, i, adjustOffsets);
|
|
908
|
+
start = i;
|
|
909
|
+
}
|
|
910
|
+
positionCluster(plan, font, buffer, start, info.length, adjustOffsets);
|
|
911
|
+
}
|
|
912
|
+
function position(plan, font, buffer) {
|
|
913
|
+
for (const info of buffer.info) {
|
|
914
|
+
info.xAdvance = font.advance(info.codepoint);
|
|
915
|
+
info.yAdvance = info.xOffset = info.yOffset = info.attachChain = info.attachType = 0;
|
|
916
|
+
}
|
|
917
|
+
if (buffer.hasSpaceFallback)
|
|
918
|
+
fallbackSpaces(font, buffer);
|
|
919
|
+
const adjustOffsetsWhenZeroing = plan.adjustMarkPositioningWhenZeroing && buffer.direction === 4;
|
|
920
|
+
if (plan.zeroMarks && plan.shaper.zeroWidthMarks === 1)
|
|
921
|
+
zeroMarkWidthsByGdef(buffer, adjustOffsetsWhenZeroing);
|
|
922
|
+
if (plan.applyGpos)
|
|
923
|
+
plan.map.apply(1, font.gpos, buffer, font.gdef, plan, font);
|
|
924
|
+
if (plan.applyKern)
|
|
925
|
+
kernTable(plan, buffer);
|
|
926
|
+
if (plan.zeroMarks && plan.shaper.zeroWidthMarks === 2)
|
|
927
|
+
zeroMarkWidthsByGdef(buffer, adjustOffsetsWhenZeroing);
|
|
928
|
+
zeroWidthDefaultIgnorables(buffer);
|
|
929
|
+
propagateAttachmentOffsets(buffer);
|
|
930
|
+
if (plan.fallbackMarkPositioning)
|
|
931
|
+
fallbackMarkPosition(plan, font, buffer, adjustOffsetsWhenZeroing);
|
|
932
|
+
if (buffer.direction === 5)
|
|
933
|
+
buffer.reverse();
|
|
934
|
+
}
|
|
935
|
+
export function shapeRun(font, codepoints, script, direction, clusters, context = { before: [], after: [] }) {
|
|
936
|
+
const buffer = new Buffer();
|
|
937
|
+
buffer.direction = direction;
|
|
938
|
+
buffer.script = script;
|
|
939
|
+
buffer.beginning = !context.before.length;
|
|
940
|
+
buffer.context = [context.before, context.after];
|
|
941
|
+
for (let i = 0; i < codepoints.length; i++) {
|
|
942
|
+
const info = new GlyphInfo();
|
|
943
|
+
info.codepoint = codepoints[i];
|
|
944
|
+
info.cluster = clusters ? clusters[i] : i;
|
|
945
|
+
buffer.info.push(info);
|
|
946
|
+
}
|
|
947
|
+
buffer.limit();
|
|
948
|
+
if (!buffer.len)
|
|
949
|
+
return [];
|
|
950
|
+
const plan = font.plan(script, direction);
|
|
951
|
+
const targetDirection = buffer.direction;
|
|
952
|
+
let hasFractionSlash = false;
|
|
953
|
+
for (const info of buffer.info)
|
|
954
|
+
if (info.codepoint === 0x2044)
|
|
955
|
+
hasFractionSlash = true;
|
|
956
|
+
setUnicodePropsAll(buffer, plan.map.globalMask);
|
|
957
|
+
insertDottedCircle(buffer, font);
|
|
958
|
+
formClusters(buffer);
|
|
959
|
+
ensureNativeDirection(buffer);
|
|
960
|
+
if (plan.shaper.preprocessText)
|
|
961
|
+
plan.shaper.preprocessText(plan, buffer, font);
|
|
962
|
+
rotateChars(plan, buffer, targetDirection);
|
|
963
|
+
normalize(plan, buffer, font);
|
|
964
|
+
setupMasksFraction(plan, buffer, hasFractionSlash);
|
|
965
|
+
if (plan.shaper.setupMasks)
|
|
966
|
+
plan.shaper.setupMasks(plan, buffer, font);
|
|
967
|
+
if (plan.fallbackMarkPositioning)
|
|
968
|
+
recategorizeMarks(buffer);
|
|
969
|
+
for (const info of buffer.info)
|
|
970
|
+
info.codepoint = info.glyphIndex;
|
|
971
|
+
for (const info of buffer.info) {
|
|
972
|
+
info.glyphProps = font.gdef.hasGlyphClasses ? font.gdef.glyphProps(info.codepoint) : 0;
|
|
973
|
+
info.ligProps = 0;
|
|
974
|
+
}
|
|
975
|
+
if (plan.fallbackGlyphClasses)
|
|
976
|
+
synthesizeGlyphClasses(buffer);
|
|
977
|
+
plan.map.apply(0, font.gsub, buffer, font.gdef, plan, font);
|
|
978
|
+
position(plan, font, buffer);
|
|
979
|
+
hideDefaultIgnorables(buffer, font);
|
|
980
|
+
if (plan.shaper.postprocessGlyphs)
|
|
981
|
+
plan.shaper.postprocessGlyphs(plan, buffer, font);
|
|
982
|
+
buffer.direction = targetDirection;
|
|
983
|
+
return buffer.info;
|
|
984
|
+
}
|