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.
- package/README.md +8 -3
- package/THIRD_PARTY_NOTICES.md +131 -0
- package/dist/animation.d.ts +7 -0
- package/dist/animation.js +22 -12
- package/dist/codecs/crc.js +5 -0
- package/dist/codecs/gif.js +30 -52
- package/dist/codecs/jpeg.js +9 -27
- package/dist/codecs/png.js +22 -10
- package/dist/codecs/tga.js +3 -1
- package/dist/codecs/tiff.js +7 -2
- package/dist/codecs/vp8.js +3 -1
- package/dist/codecs/vp8l.js +126 -58
- package/dist/codecs/webp.js +11 -9
- 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.d.ts +42 -7
- package/dist/image.js +224 -113
- 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 +12 -4
- 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 +101 -22
- package/dist/render/text.js +373 -103
- package/dist/render/unicode-data.js +63 -0
- package/dist/render/unicode.js +58 -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 +80 -5
- package/dist/util/bytes.js +31 -0
- package/dist/util/limits.js +34 -0
- package/dist/util/options.js +7 -0
- package/dist/util/resample.js +359 -79
- 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 +1 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
export const cat = (...categories) => ({ kind: 'set', categories: new Set(categories), negated: false });
|
|
2
|
+
export const any = { kind: 'set', categories: null, negated: false };
|
|
3
|
+
export const seq = (...items) => ({ kind: 'seq', items });
|
|
4
|
+
export const alt = (...items) => ({ kind: 'alt', items });
|
|
5
|
+
export const opt = (...items) => ({ kind: 'repeat', item: seq(...items), min: 0, max: 1 });
|
|
6
|
+
export const star = (...items) => ({ kind: 'repeat', item: seq(...items), min: 0, max: Infinity });
|
|
7
|
+
export const plus = (...items) => ({ kind: 'repeat', item: seq(...items), min: 1, max: Infinity });
|
|
8
|
+
export function scanner(patterns) {
|
|
9
|
+
const states = [];
|
|
10
|
+
const add = () => states.push({ test: null, next: [], accept: -1 }) - 1;
|
|
11
|
+
const build = (e, start) => {
|
|
12
|
+
switch (e.kind) {
|
|
13
|
+
case 'set': {
|
|
14
|
+
const end = add(), { categories, negated } = e;
|
|
15
|
+
const s = add();
|
|
16
|
+
states[start].next.push(s);
|
|
17
|
+
states[s].test = categories === null ? () => true : negated ? c => !categories.has(c) : c => categories.has(c);
|
|
18
|
+
states[s].next.push(end);
|
|
19
|
+
return end;
|
|
20
|
+
}
|
|
21
|
+
case 'seq': {
|
|
22
|
+
let s = start;
|
|
23
|
+
for (const item of e.items)
|
|
24
|
+
s = build(item, s);
|
|
25
|
+
return s;
|
|
26
|
+
}
|
|
27
|
+
case 'alt': {
|
|
28
|
+
const end = add();
|
|
29
|
+
for (const item of e.items) {
|
|
30
|
+
const s = add();
|
|
31
|
+
states[start].next.push(s);
|
|
32
|
+
states[build(item, s)].next.push(end);
|
|
33
|
+
}
|
|
34
|
+
return end;
|
|
35
|
+
}
|
|
36
|
+
case 'repeat': {
|
|
37
|
+
if (e.min === 1)
|
|
38
|
+
return build({ ...e, min: 0 }, build(e.item, start));
|
|
39
|
+
const end = add();
|
|
40
|
+
if (e.max === 1) {
|
|
41
|
+
states[start].next.push(end);
|
|
42
|
+
states[build(e.item, start)].next.push(end);
|
|
43
|
+
return end;
|
|
44
|
+
}
|
|
45
|
+
const loop = add();
|
|
46
|
+
states[start].next.push(loop);
|
|
47
|
+
states[loop].next.push(end);
|
|
48
|
+
states[build(e.item, loop)].next.push(loop);
|
|
49
|
+
return end;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const root = add();
|
|
54
|
+
for (const [i, [pattern]] of patterns.entries()) {
|
|
55
|
+
const s = add();
|
|
56
|
+
states[root].next.push(s);
|
|
57
|
+
states[build(pattern, s)].accept = i;
|
|
58
|
+
}
|
|
59
|
+
const closure = (set) => {
|
|
60
|
+
const seen = new Uint8Array(states.length), out = [];
|
|
61
|
+
const stack = set.slice();
|
|
62
|
+
while (stack.length) {
|
|
63
|
+
const s = stack.pop();
|
|
64
|
+
if (seen[s])
|
|
65
|
+
continue;
|
|
66
|
+
seen[s] = 1;
|
|
67
|
+
out.push(s);
|
|
68
|
+
if (states[s].test === null)
|
|
69
|
+
for (const n of states[s].next)
|
|
70
|
+
stack.push(n);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
};
|
|
74
|
+
const dfaStates = new Map();
|
|
75
|
+
const dfa = (set) => {
|
|
76
|
+
set.sort((a, b) => a - b);
|
|
77
|
+
const key = set.join(',');
|
|
78
|
+
let state = dfaStates.get(key);
|
|
79
|
+
if (!state) {
|
|
80
|
+
let accept = -1;
|
|
81
|
+
for (const s of set)
|
|
82
|
+
if (states[s].accept >= 0 && (accept < 0 || states[s].accept < accept))
|
|
83
|
+
accept = states[s].accept;
|
|
84
|
+
dfaStates.set(key, state = { next: new Map(), accept, states: set });
|
|
85
|
+
}
|
|
86
|
+
return state;
|
|
87
|
+
};
|
|
88
|
+
const step = (state, category) => {
|
|
89
|
+
let next = state.next.get(category);
|
|
90
|
+
if (next === undefined) {
|
|
91
|
+
const targets = [];
|
|
92
|
+
for (const s of state.states) {
|
|
93
|
+
const test = states[s].test;
|
|
94
|
+
if (test && test(category))
|
|
95
|
+
targets.push(...states[s].next);
|
|
96
|
+
}
|
|
97
|
+
next = targets.length ? dfa(closure(targets)) : null;
|
|
98
|
+
if (next && !next.states.length)
|
|
99
|
+
next = null;
|
|
100
|
+
state.next.set(category, next);
|
|
101
|
+
}
|
|
102
|
+
return next;
|
|
103
|
+
};
|
|
104
|
+
const start = dfa(closure([root]));
|
|
105
|
+
return categories => {
|
|
106
|
+
const syllables = [];
|
|
107
|
+
for (let p = 0; p < categories.length;) {
|
|
108
|
+
let state = start, bestLength = 0, bestPattern = -1;
|
|
109
|
+
for (let q = p; q < categories.length && state; q++) {
|
|
110
|
+
state = step(state, categories[q]);
|
|
111
|
+
if (state && state.accept >= 0) {
|
|
112
|
+
bestLength = q - p + 1;
|
|
113
|
+
bestPattern = state.accept;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (bestLength === 0) {
|
|
117
|
+
bestLength = 1;
|
|
118
|
+
bestPattern = patterns.length - 1;
|
|
119
|
+
}
|
|
120
|
+
syllables.push([p, p + bestLength, patterns[bestPattern][1]]);
|
|
121
|
+
p += bestLength;
|
|
122
|
+
}
|
|
123
|
+
return syllables;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export function lazyScanner(patterns) {
|
|
127
|
+
let scan = null;
|
|
128
|
+
return categories => (scan ??= scanner(patterns()))(categories);
|
|
129
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* The Myanmar shaper, ported from HarfBuzz (hb-ot-shaper-myanmar.cc, hb-ot-shaper-myanmar-machine.rl)
|
|
3
|
+
* Copyright © 2011,2012,2013 Google, Inc., Old MIT license (see THIRD_PARTY_NOTICES.md and licenses/HarfBuzz.txt)
|
|
4
|
+
*/
|
|
5
|
+
import { lazyScanner, cat, seq, alt, opt, star, any } from "./machine.js";
|
|
6
|
+
import { forEachSyllable, insertDottedCircles } from "./syllabic.js";
|
|
7
|
+
import { indicProperties, isOneOf, setSyllables } from "./indic.js";
|
|
8
|
+
const findSyllables = lazyScanner(() => {
|
|
9
|
+
const j = cat(6, 5);
|
|
10
|
+
const k = seq(cat(15), cat(32), cat(4));
|
|
11
|
+
const sm = cat(8, 57);
|
|
12
|
+
const c = cat(1, 15);
|
|
13
|
+
const medialGroup = seq(opt(cat(38)), opt(cat(32)), opt(cat(36)), opt(alt(seq(cat(37), opt(cat(35)), opt(cat(41))), seq(cat(35), opt(cat(41))), cat(41)), opt(cat(32))));
|
|
14
|
+
const mainVowelGroup = seq(star(cat(22), opt(cat(40))), star(cat(20)), star(cat(21)), star(cat(9)), opt(cat(3), opt(cat(32))));
|
|
15
|
+
const postVowelGroup = seq(cat(23), opt(cat(35)), opt(cat(41)), star(cat(32)), star(cat(20)), star(cat(9)), opt(cat(3), opt(cat(32))));
|
|
16
|
+
const toneGroup = alt(sm, seq(cat(39), star(cat(9)), opt(cat(3)), opt(cat(32))));
|
|
17
|
+
const complexSyllableTail = seq(star(cat(32)), medialGroup, mainVowelGroup, star(postVowelGroup), star(toneGroup), opt(j));
|
|
18
|
+
const syllableTail = seq(star(cat(4), alt(c, cat(2)), opt(cat(40))), alt(cat(4), complexSyllableTail));
|
|
19
|
+
const consonantSyllable = seq(opt(alt(k, cat(18))), alt(c, cat(2), cat(10), cat(11)), opt(cat(40)), syllableTail);
|
|
20
|
+
const brokenCluster = seq(opt(k), opt(cat(40)), syllableTail);
|
|
21
|
+
return [
|
|
22
|
+
[consonantSyllable, 0],
|
|
23
|
+
[alt(j, cat(57)), 2],
|
|
24
|
+
[brokenCluster, 1],
|
|
25
|
+
[any, 2],
|
|
26
|
+
];
|
|
27
|
+
});
|
|
28
|
+
const CONSONANTS = [1, 18, 15, 2, 10, 11];
|
|
29
|
+
const isConsonant = (info) => isOneOf(info, CONSONANTS);
|
|
30
|
+
function reorderConsonantSyllable(buffer, start, end) {
|
|
31
|
+
const info = buffer.info;
|
|
32
|
+
let base = end, hasReph = false, limit = start;
|
|
33
|
+
if (start + 3 <= end && info[start].category === 15 && info[start + 1].category === 32 && info[start + 2].category === 4) {
|
|
34
|
+
limit += 3;
|
|
35
|
+
base = start;
|
|
36
|
+
hasReph = true;
|
|
37
|
+
}
|
|
38
|
+
if (!hasReph)
|
|
39
|
+
base = limit;
|
|
40
|
+
for (let i = limit; i < end; i++)
|
|
41
|
+
if (isConsonant(info[i])) {
|
|
42
|
+
base = i;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
let i = start;
|
|
46
|
+
for (; i < start + (hasReph ? 3 : 0); i++)
|
|
47
|
+
info[i].position = 5;
|
|
48
|
+
for (; i < base; i++)
|
|
49
|
+
info[i].position = 3;
|
|
50
|
+
if (i < end)
|
|
51
|
+
info[i++].position = 4;
|
|
52
|
+
let pos = 5;
|
|
53
|
+
for (; i < end; i++) {
|
|
54
|
+
const category = info[i].category;
|
|
55
|
+
if (category === 36)
|
|
56
|
+
info[i].position = 3;
|
|
57
|
+
else if (category === 22)
|
|
58
|
+
info[i].position = 2;
|
|
59
|
+
else if (category === 40)
|
|
60
|
+
info[i].position = info[i - 1].position;
|
|
61
|
+
else if (pos === 5 && category === 21)
|
|
62
|
+
info[i].position = pos = 8;
|
|
63
|
+
else if (pos === 8 && category === 9)
|
|
64
|
+
info[i].position = 7;
|
|
65
|
+
else if (pos === 8 && category === 21)
|
|
66
|
+
info[i].position = pos;
|
|
67
|
+
else if (pos === 8)
|
|
68
|
+
info[i].position = pos = 9;
|
|
69
|
+
else
|
|
70
|
+
info[i].position = pos;
|
|
71
|
+
}
|
|
72
|
+
buffer.sort(start, end, (a, b) => a.position - b.position);
|
|
73
|
+
let firstLeftMatra = end, lastLeftMatra = end;
|
|
74
|
+
for (let k = start; k < end; k++)
|
|
75
|
+
if (info[k].position === 2) {
|
|
76
|
+
if (firstLeftMatra === end)
|
|
77
|
+
firstLeftMatra = k;
|
|
78
|
+
lastLeftMatra = k;
|
|
79
|
+
}
|
|
80
|
+
if (firstLeftMatra < lastLeftMatra) {
|
|
81
|
+
buffer.reverseRange(firstLeftMatra, lastLeftMatra + 1);
|
|
82
|
+
let k = firstLeftMatra;
|
|
83
|
+
for (let j = k; j <= lastLeftMatra; j++)
|
|
84
|
+
if (info[j].category === 22) {
|
|
85
|
+
buffer.reverseRange(k, j + 1);
|
|
86
|
+
k = j + 1;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function reorder(_plan, font, buffer) {
|
|
91
|
+
insertDottedCircles(font, buffer, 1, 11, -1, -1);
|
|
92
|
+
forEachSyllable(buffer, (start, end) => {
|
|
93
|
+
const type = buffer.info[start].syllable & 0x0f;
|
|
94
|
+
if (type === 0 || type === 1)
|
|
95
|
+
reorderConsonantSyllable(buffer, start, end);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function collectFeatures(planner) {
|
|
99
|
+
const map = planner.map;
|
|
100
|
+
map.addGsubPause((_plan, _font, buffer) => setSyllables(buffer, findSyllables(buffer.info.map(info => info.category)), 1));
|
|
101
|
+
map.enableFeature('locl', 64);
|
|
102
|
+
map.enableFeature('ccmp', 64);
|
|
103
|
+
map.addGsubPause(reorder);
|
|
104
|
+
for (const tag of ['rphf', 'pref', 'blwf', 'pstf']) {
|
|
105
|
+
map.enableFeature(tag, 8 | 64);
|
|
106
|
+
map.addGsubPause(null);
|
|
107
|
+
}
|
|
108
|
+
map.addGsubPause(null);
|
|
109
|
+
for (const tag of ['pres', 'abvs', 'blws', 'psts'])
|
|
110
|
+
map.enableFeature(tag, 8);
|
|
111
|
+
}
|
|
112
|
+
export const myanmarShaper = {
|
|
113
|
+
collectFeatures,
|
|
114
|
+
setupMasks: (_plan, buffer) => {
|
|
115
|
+
for (const info of buffer.info)
|
|
116
|
+
info.category = indicProperties(info.codepoint)[0];
|
|
117
|
+
},
|
|
118
|
+
normalization: 3,
|
|
119
|
+
zeroWidthMarks: 1,
|
|
120
|
+
fallbackPosition: false,
|
|
121
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Helpers of the syllabic shapers, ported from HarfBuzz (hb-ot-shaper-syllabic.cc, hb-ot-shaper-vowel-constraints.cc)
|
|
3
|
+
* Copyright © 2021 Behdad Esfahbod, © 2018 Google, Inc., Old MIT license (see THIRD_PARTY_NOTICES.md and
|
|
4
|
+
* licenses/HarfBuzz.txt)
|
|
5
|
+
*/
|
|
6
|
+
import { GlyphInfo } from "../buffer.js";
|
|
7
|
+
import { scriptOf } from "../shape.js";
|
|
8
|
+
import { vowelConstraints } from "../unicode-data.js";
|
|
9
|
+
export function nextSyllable(buffer, start) {
|
|
10
|
+
const info = buffer.info;
|
|
11
|
+
if (start >= info.length)
|
|
12
|
+
return info.length;
|
|
13
|
+
const syllable = info[start].syllable;
|
|
14
|
+
while (++start < info.length && syllable === info[start].syllable)
|
|
15
|
+
;
|
|
16
|
+
return start;
|
|
17
|
+
}
|
|
18
|
+
export function forEachSyllable(buffer, f) {
|
|
19
|
+
for (let start = 0, end = buffer.len ? nextSyllable(buffer, 0) : 0; start < buffer.len; start = end, end = nextSyllable(buffer, start))
|
|
20
|
+
f(start, end);
|
|
21
|
+
}
|
|
22
|
+
export function insertDottedCircles(font, buffer, brokenType, category, repha, position) {
|
|
23
|
+
if (!buffer.hasBrokenSyllable)
|
|
24
|
+
return false;
|
|
25
|
+
const glyph = font.glyph(0x25cc);
|
|
26
|
+
if (!glyph)
|
|
27
|
+
return false;
|
|
28
|
+
buffer.clearOutput();
|
|
29
|
+
buffer.idx = 0;
|
|
30
|
+
let last = 0;
|
|
31
|
+
while (buffer.idx < buffer.len && buffer.successful) {
|
|
32
|
+
const syllable = buffer.cur().syllable;
|
|
33
|
+
if (last !== syllable && (syllable & 0x0f) === brokenType) {
|
|
34
|
+
last = syllable;
|
|
35
|
+
const info = new GlyphInfo();
|
|
36
|
+
info.codepoint = glyph;
|
|
37
|
+
info.category = category;
|
|
38
|
+
if (position !== -1)
|
|
39
|
+
info.position = position;
|
|
40
|
+
info.cluster = buffer.cur().cluster;
|
|
41
|
+
info.mask = buffer.cur().mask;
|
|
42
|
+
info.syllable = syllable;
|
|
43
|
+
if (repha !== -1)
|
|
44
|
+
while (buffer.idx < buffer.len && buffer.successful && last === buffer.cur().syllable && buffer.cur().category === repha)
|
|
45
|
+
buffer.nextGlyph();
|
|
46
|
+
buffer.outputInfo(info);
|
|
47
|
+
}
|
|
48
|
+
else
|
|
49
|
+
buffer.nextGlyph();
|
|
50
|
+
}
|
|
51
|
+
buffer.sync();
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
let constraints = null;
|
|
55
|
+
function constraintsOf(script) {
|
|
56
|
+
if (!constraints) {
|
|
57
|
+
constraints = new Map();
|
|
58
|
+
for (const sequence of vowelConstraints) {
|
|
59
|
+
const s = scriptOf(sequence[0]);
|
|
60
|
+
let list = constraints.get(s);
|
|
61
|
+
if (!list)
|
|
62
|
+
constraints.set(s, list = []);
|
|
63
|
+
list.push(sequence);
|
|
64
|
+
}
|
|
65
|
+
for (const list of constraints.values())
|
|
66
|
+
list.sort((a, b) => a.length - b.length);
|
|
67
|
+
}
|
|
68
|
+
return constraints.get(script) ?? [];
|
|
69
|
+
}
|
|
70
|
+
export function vowelConstraintsPreprocess(buffer) {
|
|
71
|
+
const list = constraintsOf(buffer.script);
|
|
72
|
+
if (!list.length)
|
|
73
|
+
return;
|
|
74
|
+
buffer.clearOutput();
|
|
75
|
+
const count = buffer.len;
|
|
76
|
+
for (buffer.idx = 0; buffer.idx + 1 < count && buffer.successful;) {
|
|
77
|
+
let matched = null;
|
|
78
|
+
for (const sequence of list) {
|
|
79
|
+
if (buffer.idx + sequence.length > count)
|
|
80
|
+
continue;
|
|
81
|
+
let k = 0;
|
|
82
|
+
while (k < sequence.length && buffer.info[buffer.idx + k].codepoint === sequence[k])
|
|
83
|
+
k++;
|
|
84
|
+
if (k === sequence.length) {
|
|
85
|
+
matched = sequence;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!matched) {
|
|
90
|
+
buffer.nextGlyph();
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
for (let k = 0; k < matched.length - 1; k++)
|
|
94
|
+
buffer.nextGlyph();
|
|
95
|
+
const circle = buffer.outputGlyph(0x25cc);
|
|
96
|
+
circle.unicodeProps &= ~128;
|
|
97
|
+
buffer.nextGlyph();
|
|
98
|
+
}
|
|
99
|
+
buffer.sync();
|
|
100
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* The Thai and Lao shaper, ported from HarfBuzz (hb-ot-shaper-thai.cc)
|
|
3
|
+
* Copyright © 2010,2012 Google, Inc., Old MIT license (see THIRD_PARTY_NOTICES.md and licenses/HarfBuzz.txt)
|
|
4
|
+
*/
|
|
5
|
+
import { setGeneralCategory } from "../buffer.js";
|
|
6
|
+
const consonantType = (u) => {
|
|
7
|
+
if (u === 0x0e1b || u === 0x0e1d || u === 0x0e1f)
|
|
8
|
+
return 1;
|
|
9
|
+
if (u === 0x0e0d || u === 0x0e10)
|
|
10
|
+
return 2;
|
|
11
|
+
if (u === 0x0e0e || u === 0x0e0f)
|
|
12
|
+
return 3;
|
|
13
|
+
if (u >= 0x0e01 && u <= 0x0e2e)
|
|
14
|
+
return 0;
|
|
15
|
+
return 4;
|
|
16
|
+
};
|
|
17
|
+
const markType = (u) => {
|
|
18
|
+
if (u === 0x0e31 || u >= 0x0e34 && u <= 0x0e37 || u === 0x0e47 || u >= 0x0e4d && u <= 0x0e4e)
|
|
19
|
+
return 0;
|
|
20
|
+
if (u >= 0x0e38 && u <= 0x0e3a)
|
|
21
|
+
return 1;
|
|
22
|
+
if (u >= 0x0e48 && u <= 0x0e4c)
|
|
23
|
+
return 2;
|
|
24
|
+
return 3;
|
|
25
|
+
};
|
|
26
|
+
const PUA = {
|
|
27
|
+
[1]: [[0x0e48, 0xf70a, 0xf88b], [0x0e49, 0xf70b, 0xf88e], [0x0e4a, 0xf70c, 0xf891], [0x0e4b, 0xf70d, 0xf894], [0x0e4c, 0xf70e, 0xf897],
|
|
28
|
+
[0x0e38, 0xf718, 0xf89b], [0x0e39, 0xf719, 0xf89c], [0x0e3a, 0xf71a, 0xf89d]],
|
|
29
|
+
[3]: [[0x0e48, 0xf705, 0xf88c], [0x0e49, 0xf706, 0xf88f], [0x0e4a, 0xf707, 0xf892], [0x0e4b, 0xf708, 0xf895], [0x0e4c, 0xf709, 0xf898]],
|
|
30
|
+
[2]: [[0x0e48, 0xf713, 0xf88a], [0x0e49, 0xf714, 0xf88d], [0x0e4a, 0xf715, 0xf890], [0x0e4b, 0xf716, 0xf893], [0x0e4c, 0xf717, 0xf896],
|
|
31
|
+
[0x0e31, 0xf710, 0xf884], [0x0e34, 0xf701, 0xf885], [0x0e35, 0xf702, 0xf886], [0x0e36, 0xf703, 0xf887], [0x0e37, 0xf704, 0xf888],
|
|
32
|
+
[0x0e47, 0xf712, 0xf889], [0x0e4d, 0xf711, 0xf899]],
|
|
33
|
+
[4]: [[0x0e0d, 0xf70f, 0xf89a], [0x0e10, 0xf700, 0xf89e]],
|
|
34
|
+
};
|
|
35
|
+
function puaShape(u, action, font) {
|
|
36
|
+
if (action === 0)
|
|
37
|
+
return u;
|
|
38
|
+
for (const [from, win, mac] of PUA[action])
|
|
39
|
+
if (from === u) {
|
|
40
|
+
if (font.hasGlyph(win))
|
|
41
|
+
return win;
|
|
42
|
+
if (font.hasGlyph(mac))
|
|
43
|
+
return mac;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
return u;
|
|
47
|
+
}
|
|
48
|
+
const ABOVE_START = [0, 1, 0, 0, 3];
|
|
49
|
+
const ABOVE = [
|
|
50
|
+
[[0, 3], [0, 0], [1, 3]],
|
|
51
|
+
[[2, 2], [0, 1], [3, 2]],
|
|
52
|
+
[[0, 3], [0, 2], [2, 3]],
|
|
53
|
+
[[0, 3], [0, 3], [0, 3]],
|
|
54
|
+
];
|
|
55
|
+
const BELOW_START = [0, 0, 1, 2, 2];
|
|
56
|
+
const BELOW = [
|
|
57
|
+
[[0, 0], [0, 2], [0, 0]],
|
|
58
|
+
[[0, 1], [4, 2], [0, 1]],
|
|
59
|
+
[[0, 2], [1, 2], [0, 2]],
|
|
60
|
+
];
|
|
61
|
+
function puaShaping(buffer, font) {
|
|
62
|
+
let above = ABOVE_START[4], below = BELOW_START[4], base = 0;
|
|
63
|
+
const info = buffer.info;
|
|
64
|
+
for (let i = 0; i < info.length; i++) {
|
|
65
|
+
const mt = markType(info[i].codepoint);
|
|
66
|
+
if (mt === 3) {
|
|
67
|
+
const ct = consonantType(info[i].codepoint);
|
|
68
|
+
above = ABOVE_START[ct];
|
|
69
|
+
below = BELOW_START[ct];
|
|
70
|
+
base = i;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const aboveEdge = ABOVE[above][mt], belowEdge = BELOW[below][mt];
|
|
74
|
+
above = aboveEdge[1];
|
|
75
|
+
below = belowEdge[1];
|
|
76
|
+
const action = aboveEdge[0] !== 0 ? aboveEdge[0] : belowEdge[0];
|
|
77
|
+
if (action === 4)
|
|
78
|
+
info[base].codepoint = puaShape(info[base].codepoint, action, font);
|
|
79
|
+
else
|
|
80
|
+
info[i].codepoint = puaShape(info[i].codepoint, action, font);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const isSaraAm = (u) => (u & ~0x80) === 0x0e33;
|
|
84
|
+
const isAboveBaseMark = (u) => {
|
|
85
|
+
const v = u & ~0x80;
|
|
86
|
+
return v >= 0x0e34 && v <= 0x0e37 || v >= 0x0e47 && v <= 0x0e4e || v === 0x0e31 || v === 0x0e3b;
|
|
87
|
+
};
|
|
88
|
+
function preprocessText(plan, buffer, font) {
|
|
89
|
+
buffer.clearOutput();
|
|
90
|
+
const count = buffer.len;
|
|
91
|
+
for (buffer.idx = 0; buffer.idx < count;) {
|
|
92
|
+
const u = buffer.cur().codepoint;
|
|
93
|
+
if (!isSaraAm(u)) {
|
|
94
|
+
buffer.nextGlyph();
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const nikhahit = buffer.outputGlyph(u - 0x0e33 + 0x0e4d);
|
|
98
|
+
buffer.hasContinuations = true;
|
|
99
|
+
nikhahit.unicodeProps |= 128;
|
|
100
|
+
buffer.replaceGlyph(u - 1);
|
|
101
|
+
const end = buffer.outLen;
|
|
102
|
+
setGeneralCategory(buffer.outInfo[end - 2], 12);
|
|
103
|
+
let start = end - 2;
|
|
104
|
+
while (start > 0 && isAboveBaseMark(buffer.outInfo[start - 1].codepoint))
|
|
105
|
+
start--;
|
|
106
|
+
if (start + 2 < end) {
|
|
107
|
+
buffer.mergeOutClusters(start, end);
|
|
108
|
+
const t = buffer.outInfo[end - 2];
|
|
109
|
+
for (let k = end - 2; k > start; k--)
|
|
110
|
+
buffer.outInfo[k] = buffer.outInfo[k - 1];
|
|
111
|
+
buffer.outInfo[start] = t;
|
|
112
|
+
}
|
|
113
|
+
if (start)
|
|
114
|
+
buffer.mergeOutClusters(start - 1, end);
|
|
115
|
+
}
|
|
116
|
+
buffer.sync();
|
|
117
|
+
if (plan.script === 'Thai' && !plan.map.foundScript[0])
|
|
118
|
+
puaShaping(buffer, font);
|
|
119
|
+
}
|
|
120
|
+
export const thaiShaper = {
|
|
121
|
+
preprocessText,
|
|
122
|
+
normalization: 4,
|
|
123
|
+
zeroWidthMarks: 2,
|
|
124
|
+
fallbackPosition: false,
|
|
125
|
+
};
|