forked from opentypejs/opentype.js
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbidi.js
184 lines (173 loc) · 8.08 KB
/
bidi.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import assert from 'assert';
import Bidi from '../src/bidi.js';
import { parse } from '../src/opentype.js';
import { readFileSync } from 'fs';
const loadSync = (url, opt) => parse(readFileSync(url), opt);
describe('bidi.js', function() {
let latinFont;
let arabicFont;
let scriptFont;
let bidiFira;
let bidiScheherazade;
let bidiPecita;
let bidiPecitaNoRlig;
let arabicTokenizer;
before(function () {
/**
* arab
*/
arabicFont = loadSync('./test/fonts/Scheherazade-Bold.ttf');
bidiScheherazade = new Bidi();
bidiScheherazade.registerModifier(
'glyphIndex', null, token => arabicFont.charToGlyphIndex(token.char)
);
const requiredArabicFeatures = [{
script: 'arab',
tags: ['init', 'medi', 'fina', 'rlig']
}];
bidiScheherazade.applyFeatures(arabicFont, requiredArabicFeatures);
bidiScheherazade.getTextGlyphs(''); // initialize bidi.
arabicTokenizer = bidiScheherazade.tokenizer;
/**
* latin
*/
latinFont = loadSync('./test/fonts/FiraSansMedium.woff');
bidiFira = new Bidi();
bidiFira.registerModifier(
'glyphIndex', null, token => latinFont.charToGlyphIndex(token.char)
);
const latinFeatures = [{
script: 'latn',
tags: ['liga', 'rlig']
}];
bidiFira.applyFeatures(latinFont, latinFeatures);
/**
* script font for rlig tests
*/
scriptFont = loadSync('./test/fonts/Pecita.ttf');
bidiPecita = new Bidi();
bidiPecita.registerModifier(
'glyphIndex', null, token => scriptFont.charToGlyphIndex(token.char)
);
bidiPecitaNoRlig = new Bidi();
bidiPecitaNoRlig.registerModifier(
'glyphIndex', null, token => scriptFont.charToGlyphIndex(token.char)
);
const scriptFeatures = [{
script: 'latn',
tags: ['liga', 'rlig']
}];
const scriptFeaturesNoRlig = [{
script: 'latn',
tags: ['liga']
}];
bidiPecita.applyFeatures(scriptFont, scriptFeatures);
bidiPecitaNoRlig.applyFeatures(scriptFont, scriptFeaturesNoRlig);
});
describe('arabic contexts', function() {
it('should match arabic words in a given text', function() {
const tokenizer = bidiScheherazade.tokenizer;
tokenizer.tokenize('Hello السلام عليكم');
const ranges = tokenizer.getContextRanges('arabicWord');
const words = ranges.map(range => tokenizer.rangeToText(range));
assert.deepEqual(words, ['السلام', 'عليكم']);
});
it('should match mixed arabic sentence', function() {
arabicTokenizer.tokenize('The king said: ائتوني به أستخلصه لنفسي');
const ranges = arabicTokenizer.getContextRanges('arabicSentence');
const sentences = ranges.map(range => arabicTokenizer.rangeToText(range))[0];
assert.equal(sentences, 'ائتوني به أستخلصه لنفسي');
});
});
describe('getBidiText', function() {
it('should adjust then render layout direction of bidi text', function() {
const bidiText = bidiScheherazade.getBidiText('Be kind, فما كان الرفق في شيء إلا زانه ، ولا نزع من شيء إلا شانه');
assert.equal(bidiText, 'Be kind, هناش الإ ءيش نم عزن الو ، هناز الإ ءيش يف قفرلا ناك امف');
});
});
describe('applyFeatures', function () {
it('should apply arabic presentation forms', function() {
bidiScheherazade.getTextGlyphs('Hello السلام عليكم');
const ranges = bidiScheherazade.tokenizer.getContextRanges('arabicWord');
const PeaceTokens = bidiScheherazade.tokenizer.getRangeTokens(ranges[1]);
const PeaceForms = PeaceTokens.map(token => {
if (token.state.init) return 'init';
if (token.state.medi) return 'medi';
if (token.state.fina) return 'fina';
return null;
});
assert.deepEqual(PeaceForms, [null, 'init', 'medi', 'medi', 'fina', null].reverse());
});
it('should apply arabic required letter ligature', function () {
let glyphIndexes = bidiScheherazade.getTextGlyphs('لا'); // Arabic word 'لا' : 'no'
assert.deepEqual(glyphIndexes, [1341, 1330]);
});
it('should apply arabic required composition ligature', function () {
let glyphIndexes = bidiScheherazade.getTextGlyphs('َّ'); // Arabic word 'َّ' : 'Fatha & Shadda'
assert.deepEqual(glyphIndexes, [1311]);
});
it('should apply required latin ligature', function () {
let glyphIndexes = bidiPecita.getTextGlyphs('quick');
assert.deepEqual(glyphIndexes, [4130, 79, 3676]); // "qu" and "ck" rlig
});
it('should render differently without required latin ligatures', function () {
let glyphIndexes = bidiPecitaNoRlig.getTextGlyphs('quick'); // no rligs
assert.deepEqual(glyphIndexes, [87, 91, 79, 73, 81]);
});
it('should apply latin ligature', function () {
let glyphIndexes = bidiFira.getTextGlyphs('fi'); // fi => fi
assert.deepEqual(glyphIndexes, [1145]);
});
});
describe('Unicode Variation Sequences (UVSes)', function() {
it('should be handled correctly', function() {
const font = loadSync('./test/fonts/TestCMAP14.otf');
// the string '芦芦󠄀芦󠄁芦󠄂≩≩︀', containing (invisible) variation selectors after some of the characters
const string = [33446, 33446, 917760, 33446, 917761, 33446, 917762, 8809, 8809, 65024].map(p => String.fromCodePoint(p)).join('');
assert.deepEqual(font.stringToGlyphIndexes(string), [1, 1, 2, 1, 4, 3]);
});
});
describe('thai scripts', () => {
let thaiFont;
let bidiThai;
before(()=> {
thaiFont = loadSync('./test/fonts/NotoSansThai-Medium-Testing-v1.ttf');
bidiThai = new Bidi();
bidiThai.registerModifier(
'glyphIndex', null, token => thaiFont.charToGlyphIndex(token.char)
);
const requiredThaiFeatures = [{
script: 'thai',
tags: ['liga', 'rlig', 'ccmp']
}];
bidiThai.applyFeatures(thaiFont, requiredThaiFeatures);
});
describe('thai features', () => {
it('should apply glyph composition', () => {
let glyphIndexes = bidiThai.getTextGlyphs('่ํ');
assert.deepEqual(glyphIndexes, [451]);
});
it('should apply glyph de-composition', () => {
let glyphIndexes = bidiThai.getTextGlyphs('น้ำ');
assert.deepEqual(glyphIndexes, [341, 453, 366]);
});
it('should apply glyph ligatures', () => {
let glyphIndexes = bidiThai.getTextGlyphs('ฤๅ');
assert.deepEqual(glyphIndexes, [459]);
});
it('should apply glyph required ligatures', () => {
let glyphIndexes = bidiThai.getTextGlyphs('ลล');
assert.deepEqual(glyphIndexes, [352]);
});
});
describe('thai contexts', () => {
it('should match thai words in a given text', () => {
const tokenizer = bidiThai.tokenizer;
tokenizer.tokenize('The king said: เป็นคนใจดีสำหรับทุกคน because ความรักคือทุกสิ่ง');
const ranges = tokenizer.getContextRanges('thaiWord');
const words = ranges.map(range => tokenizer.rangeToText(range));
assert.deepEqual(words, ['เป็นคนใจดีสำหรับทุกคน', 'ความรักคือทุกสิ่ง']);
});
});
});
});