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

Statements: 83.54% (66 / 79)      Branches: 75% (21 / 28)      Functions: 100% (2 / 2)      Lines: 84.51% (60 / 71)      Ignored: none     

All files » fontkit/src/opentype/ » GSUBProcessor.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 1421 1   1   17695   145 145   118 118   13     105   118     48 48 8 8   8 8 1 1 1   8 8                   566 566   47 178 178   8     8 8 8     8 8                                                   8   8 8 8 8       8   8 2   6 1 1 1 1   8 8 8 8     8                 8 8   8 8     48     16481     407         17015   1  
OTProcessor = require './OTProcessor'
GlyphInfo = require './GlyphInfo'
 
class GSUBProcessor extends OTProcessor
  applyLookup: (lookupType, table) ->
    switch lookupType
      when 1 # Single Substitution
        index = @coverageIndex table.coverage
        return false if index is -1
        
        glyph = @glyphIterator.cur
        switch table.version
          when 1
            glyph.id = (glyph.id + table.deltaGlyphID) & 0xffff
            
          when 2
            glyph.id = table.substitute.get(index)
            
        return true
            
      when 2 # Multiple Substitution
        index = @coverageIndex table.coverage
        unless index is -1
          sequence = table.sequences.get(index)
          @glyphIterator.cur.id = sequence[0]
          
          replacement = []
          for gid in sequence[1...]
            g = new GlyphInfo gid
            g.features = @glyphIterator.cur.features
            replacement.push g
          
          @glyphs.splice @glyphIterator.index + 1, 0, replacement...
          return true
          
      when 3 # Alternate Substitution
        index = @coverageIndex table.coverage
        unless index is -1
          USER_INDEX = 0 # TODO
          @glyphIterator.cur.id = table.alternateSet.get(index)[USER_INDEX]
          return true
    
      when 4 # Ligature Substitution
        index = @coverageIndex table.coverage
        return false if index is -1
        
        for ligature in table.ligatureSets.get(index)
          matched = @sequenceMatchIndices 1, ligature.components
          continue unless matched
          
          curGlyph = @glyphIterator.cur
          
          # Concatenate all of the characters the new ligature will represent
          characters = [curGlyph.codePoints...]
          for index in matched
            characters.push @glyphs[index].codePoints...
            
          # Create the replacement ligature glyph
          ligatureGlyph = new GlyphInfo ligature.glyph, characters
          ligatureGlyph.features = curGlyph.features
          
          # From Harfbuzz:
          # - If it *is* a mark ligature, we don't allocate a new ligature id, and leave
          #   the ligature to keep its old ligature id.  This will allow it to attach to
          #   a base ligature in GPOS.  Eg. if the sequence is: LAM,LAM,SHADDA,FATHA,HEH,
          #   and LAM,LAM,HEH for a ligature, they will leave SHADDA and FATHA with a
          #   ligature id and component value of 2.  Then if SHADDA,FATHA form a ligature
          #   later, we don't want them to lose their ligature id/component, otherwise
          #   GPOS will fail to correctly position the mark ligature on top of the
          #   LAM,LAM,HEH ligature. See https://bugzilla.gnome.org/show_bug.cgi?id=676343
          #
          # - If a ligature is formed of components that some of which are also ligatures
          #   themselves, and those ligature components had marks attached to *their*
          #   components, we have to attach the marks to the new ligature component
          #   positions!  Now *that*'s tricky!  And these marks may be following the
          #   last component of the whole sequence, so we should loop forward looking
          #   for them and update them.
          #
          #   Eg. the sequence is LAM,LAM,SHADDA,FATHA,HEH, and the font first forms a
          #   'calt' ligature of LAM,HEH, leaving the SHADDA and FATHA with a ligature
          #   id and component == 1.  Now, during 'liga', the LAM and the LAM-HEH ligature
          #   form a LAM-LAM-HEH ligature.  We need to reassign the SHADDA and FATHA to
          #   the new ligature with a component value of 2.
          #
          #   This in fact happened to a font...  See https://bugzilla.gnome.org/show_bug.cgi?id=437633
          ligatureGlyph.ligatureID = if ligatureGlyph.isMark then 0 else @ligatureID++
          
          lastLigID = curGlyph.ligatureID
          lastNumComps = curGlyph.codePoints.length
          curComps = lastNumComps
          idx = @glyphIterator.index + 1
          
          # Set ligatureID and ligatureComponent on glyphs that were skipped in the matched sequence.
          # This allows GPOS to attach marks to the correct ligature components.
          for matchIndex in matched
            # Don't assign new ligature components for mark ligatures (see above)
            if ligatureGlyph.isMark
              idx = matchIndex
            else
              while idx < matchIndex
                ligatureComponent = curComps - lastNumComps + Math.min @glyphs[idx].ligatureComponent or 1, lastNumComps
                @glyphs[idx].ligatureID = ligatureGlyph.ligatureID
                @glyphs[idx].ligatureComponent = ligatureComponent
                idx++
              
            lastLigID = @glyphs[idx].ligatureID
            lastNumComps = @glyphs[idx].codePoints.length
            curComps += lastNumComps
            idx++ # skip base glyph
            
          # Adjust ligature components for any marks following
          Iif lastLigID and not ligatureGlyph.isMark
            for i in [idx...@glyphs.length] by 1
              if @glyphs[i].ligatureID is lastLigID
                ligatureComponent = curComps - lastNumComps + Math.min @glyphs[i].ligatureComponent or 1, lastNumComps
                @glyphs[i].ligatureComponent = ligatureComponent
              else
                break
          
          # Delete the matched glyphs, and replace the current glyph with the ligature glyph
          for index in matched by -1
            @glyphs.splice index, 1
            
          @glyphs[@glyphIterator.index] = ligatureGlyph
          return true
                    
      when 5 # Contextual Substitution
        @applyContext table
            
      when 6 # Chaining Contextual Substitution
        @applyChainingContext table
            
      when 7 # Extension Substitution
        @applyLookup table.lookupType, table.extension
        
      else
        throw new Error "GSUB lookupType #{lookupType} is not supported"
        
    return false
        
module.exports = GSUBProcessor