all files / fontkit/src/opentype/shapers/ ArabicShaper.coffee

100% Statements 47/47
100% Branches 10/10
100% Functions 4/4
100% Lines 47/47
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                     42×                                                                         124× 124× 95×   29× 10×   19×           124× 124× 10× 10×   114×   114× 64×   114× 114×     124× 95×      
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