Code coverage report for fontkit/src/opentype/OTProcessor.coffee

Statements: 88.05% (140 / 159)      Branches: 79.69% (51 / 64)      Functions: 95.65% (22 / 23)      Lines: 90.34% (131 / 145)      Ignored: none     

All files » fontkit/src/opentype/ » OTProcessor.coffee
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 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 2471 1   1   16 16   16 16   16 16     16     16 16 16     47 47 32   15     54 54 36 18         18   36 36 36   36   32 32 32 32 32   50             50     50 32 32 32 278 278   50     116 116 712 712   140 418 418         116 1007   116     116 116     116   116 418   418 4571 1138 1138   3433 21696 21696   3433   116           26   26 31   31 31 31   26 26     21699   21699   20962     737 7237 198   539     38702   38702 38702   38702 374 374 374   38702 38702 20932   17770     19 17     178 183       37121 19536     1490   939 939 501179     551 54202   207     1384 1365     56   16 16   1 1       40   12 12   12 12               19809   31 31   1 1 1           255   43 43   43 43 363     10     19523     16   1  
GlyphIterator = require './GlyphIterator'
Script = require '../layout/Script'
 
class OTProcessor
  constructor: (@font, @table) ->
    @script = null
    @scriptTag = null
    
    @language = null
    @languageTag = null
    
    @features = {}
    @lookups = {}
    
    # initialize to default script + language
    @selectScript()
        
    # current context (set by applyFeatures)
    @glyphs = []
    @positions = [] # only used by GPOS
    @ligatureID = 1
  
  findScript: (script) ->
    Ireturn null unless @table.scriptList?
    for entry in @table.scriptList when entry.tag is script
      return entry
      
    return null
    
  selectScript: (script, language) ->
    changed = false
    if not @script? or script isnt @scriptTag
      if script?
        Iif Array.isArray(script)
          for s in script
            entry = @findScript s
            break if entry
        else
          entry = @findScript script
      
      entry ?= @findScript 'DFLT'
      entry ?= @findScript 'dflt'
      entry ?= @findScript 'latn'
 
      return unless entry?
            
      @scriptTag = entry.tag
      @script = entry.script
      @direction = Script.direction script
      @language = null
      changed = true
    
    Iif language? and language isnt @langugeTag
      for lang in @script.langSysRecords when lang.tag is language
        @language = lang.langSys
        @langugeTag = lang.tag
        changed = true
        break
        
    @language ?= @script.defaultLangSys
    
    # Build a feature lookup table
    if changed
      @features = {}
      Eif @language?
        for featureIndex in @language.featureIndexes
          record = @table.featureList[featureIndex]
          @features[record.tag] = record.feature
        
    return
    
  lookupsForFeatures: (userFeatures = [], exclude) ->
    lookups = []
    for tag in userFeatures
      feature = @features[tag]
      continue unless feature
      
      for lookupIndex in feature.lookupListIndexes
        Icontinue if exclude and lookupIndex in exclude
        lookups.push 
          feature: tag
          index: lookupIndex
          lookup: @table.lookupList.get(lookupIndex)
          
    lookups.sort (a, b) ->
      a.index - b.index
      
    return lookups
    
  applyFeatures: (userFeatures, glyphs, advances) ->
    lookups = @lookupsForFeatures userFeatures
    @applyLookups lookups, glyphs, advances
    
  applyLookups: (lookups, @glyphs, @positions) ->
    @glyphIterator = new GlyphIterator @glyphs
    
    for {feature, lookup} in lookups
      @glyphIterator.reset lookup.flags
            
      while @glyphIterator.index < @glyphs.length
        unless feature of @glyphIterator.cur.features
          @glyphIterator.index++
          continue
        
        for table in lookup.subTables
          res = @applyLookup lookup.lookupType, table
          break if res
          
        @glyphIterator.index++
        
    return
    
  applyLookup: (lookup, table) ->
    throw new Error "applyLookup must be implemented by subclasses"
      
  applyLookupList: (lookupRecords) ->
    glyphIndex = @glyphIterator.index
    
    for lookupRecord in lookupRecords
      @glyphIterator.index = glyphIndex + lookupRecord.sequenceIndex
      
      lookup = @table.lookupList.get(lookupRecord.lookupListIndex)
      for table in lookup.subTables
        @applyLookup lookup.lookupType, table
    
    @glyphIterator.index = glyphIndex                        
    return
    
  coverageIndex: (coverage, glyph) ->
    glyph ?= @glyphIterator.cur.id
    
    switch coverage.version
      when 1
        return coverage.glyphs.indexOf(glyph)
        
      when 2
        for range, i in coverage.rangeRecords
          if range.start <= glyph <= range.end
            return range.startCoverageIndex + glyph - range.start
      
    return -1
    
  match: (sequenceIndex, sequence, fn, matched) ->    
    pos = @glyphIterator.index
    
    glyph = @glyphIterator.increment sequenceIndex    
    idx = 0
    
    while idx < sequence.length and glyph and fn(sequence[idx], glyph.id)
      matched?.push @glyphIterator.index
      idx++
      glyph = @glyphIterator.next()
      
    @glyphIterator.index = pos
    if idx < sequence.length
      return false
      
    return matched or true
        
  sequenceMatches: (sequenceIndex, sequence) ->
    @match sequenceIndex, sequence, (component, glyph) ->
      component is glyph
      
  sequenceMatchIndices: (sequenceIndex, sequence) ->
    @match sequenceIndex, sequence, (component, glyph) ->
      component is glyph
    , []
    
  coverageSequenceMatches: (sequenceIndex, sequence) ->
    @match sequenceIndex, sequence, (coverage, glyph) =>
      @coverageIndex(coverage, glyph) >= 0
    
  getClassID: (glyph, classDef) ->    
    switch classDef.version
      when 1 # Class array
        glyphID = classDef.startGlyph
        for classID in classDef.classValueArray
          return classID if glyph is glyphID++
          
      when 2
        for range in classDef.classRangeRecord
          return range.class if range.start <= glyph <= range.end
        
    return -1
    
  classSequenceMatches: (sequenceIndex, sequence, classDef) ->
    @match sequenceIndex, sequence, (classID, glyph) =>
      classID is @getClassID glyph, classDef
        
  applyContext: (table) ->
    switch table.version
      when 1
        index = @coverageIndex table.coverage
        return if index is -1
    
        set = table.ruleSets[index]
        for rule in set when @sequenceMatches 1, rule.input
          return @applyLookupList rule.lookupRecords
          
      when 2
        return if @coverageIndex(table.coverage) is -1
        
        index = @getClassID @glyphIterator.cur.id, table.classDef
        Ireturn if index is -1
        
        set = table.classSet[index]
        for rule in set when @classSequenceMatches 1, rule.classes, table.classDef
          return @applyLookupList rule.lookupRecords
          
      when 3
        if @coverageSequenceMatches 0, table.coverages
          @applyLookupList table.lookupRecords
          
  applyChainingContext: (table) ->
    switch table.version
      when 1
        index = @coverageIndex table.coverage
        return if index is -1
        
        set = table.chainRuleSets[index]
        for rule in set
          Iif @sequenceMatches(-rule.backtrack.length, rule.backtrack) and
             @sequenceMatches(1, rule.input) and
             @sequenceMatches(1 + rule.input.length, rule.lookahead)
              return @applyLookupList rule.lookupRecords
      
      when 2
        return if @coverageIndex(table.coverage) is -1
        
        index = @getClassID @glyphIterator.cur.id, table.inputClassDef
        Ireturn if index is -1
        
        rules = table.chainClassSet[index]
        for rule in rules
          if @classSequenceMatches(-rule.backtrack.length, rule.backtrack, table.backtrackClassDef) and
             @classSequenceMatches(1, rule.input, table.inputClassDef) and
             @classSequenceMatches(1 + rule.input.length, rule.lookahead, table.lookaheadClassDef)
               return @applyLookupList rule.lookupRecords
          
      when 3
        if @coverageSequenceMatches(-table.backtrackGlyphCount, table.backtrackCoverage) and
           @coverageSequenceMatches(0, table.inputCoverage) and
           @coverageSequenceMatches(table.inputGlyphCount, table.lookaheadCoverage)
             return @applyLookupList table.lookupRecords
    
module.exports = OTProcessor