Code coverage report for fontkit/src/layout/LayoutEngine.coffee

Statements: 87.76% (86 / 98)      Branches: 78.95% (30 / 38)      Functions: 100% (8 / 8)      Lines: 87.5% (84 / 96)      Ignored: none     

All files » fontkit/src/layout/ » LayoutEngine.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 1861 1 1 1 1 1 1 1 1 1 1   1         23           23   23 23                         23     23 19 19       19 205   19     23 23 358   23     23 23                 23                                           23     4 4     4 4         4       19 19 19 19   23   1       23   20 224   3   23 23 239   23 18 18 18   23   20 224     20 4 4   23     23 5 5     23 3 3   23     2   2 1 1 1   2 1 1 1   2 1 1 1   2 1   2   1  
GSUBProcessor = require '../opentype/GSUBProcessor'
GPOSProcessor = require '../opentype/GPOSProcessor'
GlyphInfo = require '../opentype/GlyphInfo'
Shapers = require '../opentype/shapers'
AATFeatureMap = require '../aat/AATFeatureMap'
AATMorxProcessor = require '../aat/AATMorxProcessor'
KernProcessor = require './KernProcessor'
UnicodeLayoutEngine = require './UnicodeLayoutEngine'
GlyphRun = require './GlyphRun'
Script = require './Script'
unicode = require 'unicode-properties'
 
class LayoutEngine
  constructor: (@font) ->
        
  layout: (string, features = [], script, language) ->
    # Make the userFeatures parameter optional
    Iif typeof features is 'string'
      script = features
      language = script
      features = []
    
    # Map string to glyphs if needed
    Eif typeof string is 'string'
      # Attempt to detect the script from the string if not provided.
      script ?= Script.forString string
      glyphs = @font.glyphsForString string
    else
      # Attempt to detect the script from the glyph code points if not provided.
      unless script?
        codePoints = []
        for glyph in string
          codePoints.push glyph.codePoints...
        
        script = Script.forCodePoints codePoints
        
      glyphs = string
            
    # Return early if there are no glyphs
    Iif glyphs.length is 0
      return new GlyphRun glyphs, []    
      
    if not @font.morx and (@font.GSUB or @font.GPOS)
      shaper = Shapers.choose script
      features.push shaper.getGlobalFeatures(script)...
      
      # Map glyphs to GlyphInfo objects so data can be passed between
      # GSUB and GPOS without mutating the real (shared) Glyph objects.
      glyphs = for glyph, i in glyphs
        new GlyphInfo glyph.id, [glyph.codePoints...], features
        
      features.push shaper.assignFeatures(glyphs, script, @font)...
      
    # Remove duplicate features
    featureMap = {}
    for feature in features
      featureMap[feature] = true
      
    features = Object.keys(featureMap)
      
    # Substitute and position the glyphs
    glyphs = @substitute glyphs, features, script
    positions = @position glyphs, features, script
    
    # Remove default ignorables
    # for i in [glyphs.length - 1..0] by -1
    #   glyph = glyphs[i]
    #   if glyph.codePoints.length is 1 and isDefaultIgnorable glyph.codePoints[0]
    #     glyphs.splice i, 1, @font.glyphForCodePoint(' '.charCodeAt(0))
    #     positions[i].xAdvance = 0
    
    return new GlyphRun glyphs, positions
    
  # isDefaultIgnorable = (codePoint) ->
  #   # From DerivedCoreProperties.txt in the Unicode database,
  #   # minus U+115F, U+1160, U+3164 and U+FFA0, which is what
  #   # Harfbuzz and Uniscribe do.
  #   codePoint in [0x00ad, 0x034f, 0x061c, 0xfeff] or
  #      0x17b4 <= codePoint <=  0x17b5 or
  #      0x180b <= codePoint <=  0x180e or
  #      0x200b <= codePoint <=  0x200f or
  #      0x202a <= codePoint <=  0x202e or
  #      0x2060 <= codePoint <=  0x206f or
  #      0xfe00 <= codePoint <=  0xfe0f or
  #      0xfff0 <= codePoint <=  0xfff8 or
  #     0x1bca0 <= codePoint <= 0x1bca3 or
  #     0x1d173 <= codePoint <= 0x1d17a or
  #     0xe0000 <= codePoint <= 0xe0fff
    
  substitute: (glyphs, features, script, language) ->
    # First, try AAT morx table.
    # We do this first since more scripts are currently supported by AAT
    # because the shaping logic is built into the font.
    if @font.morx
      # AAT expects the glyphs to be reversed prior to morx processing,
      # so reverse the glyphs if the script is right-to-left.
      isRTL = Script.direction(script) is 'rtl'
      Iif isRTL
        glyphs.reverse()
      
      @morxProcessor ?= new AATMorxProcessor(@font)
      @morxProcessor.process(glyphs, AATFeatureMap.mapOTToAAT(features))
      
      # It is very unlikely, but possible for a font to have an AAT morx table
      # along with an OpenType GPOS table. If so, reverse the glyphs again for
      # GPOS, which expects glyphs to be in logical order.
      Iif isRTL and @font.GPOS
        glyphs.reverse()
        
    # If not found, try the OpenType GSUB table.
    else Eif @font.GSUB
      @GSUBProcessor ?= new GSUBProcessor(@font, @font.GSUB)
      @GSUBProcessor.selectScript script, language
      @GSUBProcessor.applyFeatures(features, glyphs)
      
    return glyphs
    
  class GlyphPosition
    constructor: (@xAdvance = 0, @yAdvance = 0, @xOffset = 0, @yOffset = 0) ->
      
  position: (glyphs, features, script, language) ->
    realGlyphs = if @font.GPOS or @font.GSUB
      # Map the GlyphInfo objects back to real Glyph objects
      for glyph, i in glyphs
        @font.getGlyph glyph.id, glyph.codePoints
    else
      glyphs
    
    positions = []
    for glyph, i in glyphs
      positions.push new GlyphPosition realGlyphs[i].advanceWidth
      
    if @font.GPOS
      @GPOSProcessor ?= new GPOSProcessor(@font, @font.GPOS)
      @GPOSProcessor.selectScript script, language
      @GPOSProcessor.applyFeatures(features, glyphs, positions)
      
    if @font.GPOS or @font.GSUB
      # Restore the real Glyph objects
      for realGlyph, i in realGlyphs
        glyphs[i] = realGlyph
        
      # Reverse the glyphs and positions if the script is right-to-left
      if Script.direction(script) is 'rtl'
        glyphs.reverse()
        positions.reverse()
      
    gposFeatures = @GPOSProcessor?.features or {}
        
    # if there is no GPOS table, use unicode properties to position marks.
    unless @font.GPOS
      @unicodeLayoutEngine ?= new UnicodeLayoutEngine @font
      @unicodeLayoutEngine.positionGlyphs glyphs, positions
      
    # if kerning is not supported by GPOS, do kerning with the TrueType/AAT kern table
    if 'kern' not of gposFeatures and @font.kern
      @kernProcessor ?= new KernProcessor @font
      @kernProcessor.process glyphs, positions
          
    return positions
    
  getAvailableFeatures: (script, language) ->
    features = []
  
    if @font.GSUB
      @GSUBProcessor ?= new GSUBProcessor @font, @font.GSUB
      @GSUBProcessor.selectScript script, language
      features.push Object.keys(@GSUBProcessor.features)...
  
    if @font.GPOS
      @GPOSProcessor ?= new GPOSProcessor @font, @font.GPOS
      @GPOSProcessor.selectScript script, language
      features.push Object.keys(@GPOSProcessor.features)...
    
    if @font.morx
      @morxProcessor ?= new AATMorxProcessor @font
      aatFeatures = AATFeatureMap.mapAATToOT @morxProcessor.getSupportedFeatures()
      features.push aatFeatures...
    
    if @font.kern and (not @font.GPOS or 'kern' not of @GPOSProcessor.features)
      features.push 'kern'
    
    return features
    
module.exports = LayoutEngine