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 | 1
1
1
1
1
1
1
6
6
42
6
1
1
1
1
1
1
1
1
1
1
1
124
124
95
29
10
19
6
6
6
6
6
124
124
10
10
114
114
64
114
114
6
124
95
6
1
| DefaultShaper = require './DefaultShaper'
unicode = require 'unicode-properties'
UnicodeTrie = require 'unicode-trie'
fs = require 'fs'
trie = new UnicodeTrie fs.readFileSync __dirname + '/data.trie'
#
# This is a shaper for Arabic, and other cursive scripts.
# It uses data from ArabicShaping.txt in the Unicode database,
# compiled to a UnicodeTrie by generate-data.coffee.
#
# The shaping state machine was ported from Harfbuzz.
# https://github.com/behdad/harfbuzz/blob/master/src/hb-ot-shape-complex-arabic.cc
#
class ArabicShaper extends DefaultShaper
FEATURES = ['isol', 'fina', 'fin2', 'fin3', 'medi', 'med2', 'init']
@planFeatures: (plan) ->
plan.add ['ccmp', 'locl']
for feature in FEATURES
plan.addStage feature, false
plan.addStage 'mset'
ShapingClasses =
Non_Joining: 0
Left_Joining: 1
Right_Joining: 2
Dual_Joining: 3
Join_Causing: 3
ALAPH: 4
'DALATH RISH': 5
Transparent: 6
ISOL = 'isol'
FINA = 'fina'
FIN2 = 'fin2'
FIN3 = 'fin3'
MEDI = 'medi'
MED2 = 'med2'
INIT = 'init'
NONE = null
# Each entry is [prevAction, curAction, nextState]
STATE_TABLE = [
# Non_Joining, Left_Joining, Right_Joining, Dual_Joining, ALAPH, DALATH RISH
# State 0: prev was U, not willing to join.
[ [ NONE, NONE, 0 ], [ NONE, ISOL, 2 ], [ NONE, ISOL, 1 ], [ NONE, ISOL, 2 ], [ NONE, ISOL, 1 ], [ NONE, ISOL, 6 ] ]
# State 1: prev was R or ISOL/ALAPH, not willing to join.
[ [ NONE, NONE, 0 ], [ NONE, ISOL, 2 ], [ NONE, ISOL, 1 ], [ NONE, ISOL, 2 ], [ NONE, FIN2, 5 ], [ NONE, ISOL, 6 ] ]
# State 2: prev was D/L in ISOL form, willing to join.
[ [ NONE, NONE, 0 ], [ NONE, ISOL, 2 ], [ INIT, FINA, 1 ], [ INIT, FINA, 3 ], [ INIT, FINA, 4 ], [ INIT, FINA, 6 ] ]
# State 3: prev was D in FINA form, willing to join.
[ [ NONE, NONE, 0 ], [ NONE, ISOL, 2 ], [ MEDI, FINA, 1 ], [ MEDI, FINA, 3 ], [ MEDI, FINA, 4 ], [ MEDI, FINA, 6 ] ]
# State 4: prev was FINA ALAPH, not willing to join.
[ [ NONE, NONE, 0 ], [ NONE, ISOL, 2 ], [ MED2, ISOL, 1 ], [ MED2, ISOL, 2 ], [ MED2, FIN2, 5 ], [ MED2, ISOL, 6 ] ]
# State 5: prev was FIN2/FIN3 ALAPH, not willing to join.
[ [ NONE, NONE, 0 ], [ NONE, ISOL, 2 ], [ ISOL, ISOL, 1 ], [ ISOL, ISOL, 2 ], [ ISOL, FIN2, 5 ], [ ISOL, ISOL, 6 ] ]
# State 6: prev was DALATH/RISH, not willing to join.
[ [ NONE, NONE, 0 ], [ NONE, ISOL, 2 ], [ NONE, ISOL, 1 ], [ NONE, ISOL, 2 ], [ NONE, FIN3, 5 ], [ NONE, ISOL, 6 ] ]
]
getShapingClass = (codePoint) ->
res = trie.get(codePoint)
if res
return res - 1
if unicode.getCategory(codePoint) in ['Mn', 'Me', 'Cf']
return ShapingClasses.Transparent
return ShapingClasses.Non_Joining
@assignFeatures: (plan, glyphs) ->
super
prev = -1
state = 0
actions = []
# Apply the state machine to map glyphs to features
for glyph, i in glyphs
type = getShapingClass glyph.codePoints[0]
if type is ShapingClasses.Transparent
actions[i] = NONE
continue
[prevAction, curAction, state] = STATE_TABLE[state][type]
if prevAction isnt NONE and prev isnt -1
actions[prev] = prevAction
actions[i] = curAction
prev = i
# Apply the chosen features to their respective glyphs
for glyph, index in glyphs
if feature = actions[index]
glyph.features[feature] = true
return
module.exports = ArabicShaper
|