Code coverage report for fontkit/src/TTFFont.coffee

Statements: 94.77% (145 / 153)      Branches: 80.56% (29 / 36)      Functions: 94.74% (36 / 38)      Lines: 95.95% (142 / 148)      Ignored: none     

All files » fontkit/src/ » TTFFont.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 2461 1 1 1 1 1 1 1 1 1 1 1 1   1 1     41     55 55 55     55 859     55 4   1 1088 191 191 191 191   1088     168 168 168 168         51 51     188   1 22 22 22   1 1   1 1   1 1   1 1   1 1   1 1   1 1   1 1   1 1   1 1   1 1   1 1   1 1   1 3   1 1   1 1   1 2 2     16 16     311 311       45 45 45 45 296 296 7 7 7 7   296   45     23 23   1 2 2             282 279 220   59 59   282     680 279 1   278 1     277   680     6 3   3         1 1 1   1 2           1         1 4 4   4 40 40 80   40   4           4     4 3   4       4 8 7   1   4 4   4 4   4           1  
r = require 'restructure'
Directory = require './tables/directory'
tables = require './tables'
CmapProcessor = require './CmapProcessor'
LayoutEngine = require './layout/LayoutEngine'
TTFGlyph = require './glyph/TTFGlyph'
CFFGlyph = require './glyph/CFFGlyph'
SBIXGlyph = require './glyph/SBIXGlyph'
COLRGlyph = require './glyph/COLRGlyph'
GlyphVariationProcessor = require './glyph/GlyphVariationProcessor'
TTFSubset = require './subset/TTFSubset'
CFFSubset = require './subset/CFFSubset'
BBox = require './glyph/BBox'
 
class TTFFont
  get = require('./get')(this)
  
  @probe: (buffer) ->
    return buffer.toString('ascii', 0, 4) in ['true', 'OTTO', String.fromCharCode(0, 1, 0, 0)]
  
  constructor: (@stream, variationCoords = null) ->    
    @_tables = {}
    @_glyphs = {}
    @_decodeDirectory()
    
    # define properties for each table to lazily parse
    for tag, table of @directory.tables when tables[tag]
      Object.defineProperty this, tag,
        get: getTable.bind(this, table)
        
    if variationCoords
      @_variationProcessor = new GlyphVariationProcessor this, variationCoords
        
  getTable = (table) ->
    unless table.tag of @_tables
      pos = @stream.pos
      @stream.pos = table.offset
      @_tables[table.tag] = @_decodeTable table
      @stream.pos = pos
      
    return @_tables[table.tag]
    
  _getTableStream: (tag) ->
    table = @directory.tables[tag]
    Eif table
      @stream.pos = table.offset
      return @stream
      
    return null
    
  _decodeDirectory: ->
    @_directoryPos = @stream.pos
    @directory = Directory.decode(@stream, _startOffset: 0)
    
  _decodeTable: (table) ->
    return tables[table.tag].decode(@stream, this, table.length)
        
  get 'postscriptName', ->
    name = @name.records.postscriptName
    lang = Object.keys(name)[0]
    return name[lang]
    
  get 'fullName', ->
    @name.records.fullName?.English
    
  get 'familyName', ->
    @name.records.fontFamily?.English
    
  get 'subfamilyName', ->
    @name.records.fontSubfamily?.English
    
  get 'copyright', ->
    @name.records.copyright?.English
    
  get 'version', ->
    @name.records.version?.English
    
  get 'ascent', ->
    @hhea.ascent
    
  get 'descent', ->
    @hhea.descent
    
  get 'lineGap', ->
    @hhea.lineGap
    
  get 'underlinePosition', ->
    @post.underlinePosition
    
  get 'underlineThickness', ->
    @post.underlineThickness
    
  get 'italicAngle', ->
    @post.italicAngle
    
  get 'capHeight', ->
    this['OS/2']?.capHeight or @ascent
    
  get 'xHeight', ->
    this['OS/2']?.xHeight or 0
    
  get 'numGlyphs', ->
    @maxp.numGlyphs
    
  get 'unitsPerEm', ->
    @head.unitsPerEm
    
  get 'bbox', ->
    @_bbox ?= Object.freeze new BBox @head.xMin, @head.yMin, @head.xMax, @head.yMax
    
  get 'characterSet', ->
    @_cmapProcessor ?= new CmapProcessor @cmap
    return @_cmapProcessor.getCharacterSet()
        
  hasGlyphForCodePoint: (codePoint) ->
    @_cmapProcessor ?= new CmapProcessor @cmap
    return !!@_cmapProcessor.lookup codePoint
            
  glyphForCodePoint: (codePoint) ->
    @_cmapProcessor ?= new CmapProcessor @cmap
    return @getGlyph @_cmapProcessor.lookup(codePoint), [codePoint]
    
  glyphsForString: (string) ->
    # Map character codes to glyph ids
    glyphs = []    
    len = string.length
    idx = 0
    while idx < len
      code = string.charCodeAt idx++
      if 0xd800 <= code <= 0xdbff and idx < len
        next = string.charCodeAt idx
        Eif 0xdc00 <= next <= 0xdfff
          idx++
          code = ((code & 0x3FF) << 10) + (next & 0x3FF) + 0x10000
    
      glyphs.push @glyphForCodePoint code
      
    return glyphs
    
  layout: (string, userFeatures, script, language) ->
    @_layoutEngine ?= new LayoutEngine this
    return @_layoutEngine.layout string, userFeatures, script, language
    
  get 'availableFeatures', ->
    @_layoutEngine ?= new LayoutEngine this
    return @_layoutEngine.getAvailableFeatures()
            
  widthOfString: (string, features, script, language) ->
    @_layoutEngine ?= new LayoutEngine this
    return @_layoutEngine.layout(string, features, script, language).advanceWidth
    
  _getBaseGlyph: (glyph, characters = []) ->
    unless @_glyphs[glyph]
      if @directory.tables.glyf?
        @_glyphs[glyph] = new TTFGlyph glyph, characters, this
      
      else Eif @directory.tables['CFF ']?
        @_glyphs[glyph] = new CFFGlyph glyph, characters, this
    
    return @_glyphs[glyph] or null
    
  getGlyph: (glyph, characters = []) ->
    unless @_glyphs[glyph]
      if @directory.tables.sbix?
        @_glyphs[glyph] = new SBIXGlyph glyph, characters, this
        
      else if @directory.tables.COLR? and @directory.tables.CPAL?
        @_glyphs[glyph] = new COLRGlyph glyph, characters, this
        
      else
        @_getBaseGlyph glyph, characters
    
    return @_glyphs[glyph] or null
    
  createSubset: ->
    if @directory.tables['CFF ']?
      return new CFFSubset this
      
    return new TTFSubset this
    
  # Returns an object describing the available variation axes
  # that this font supports. Keys are setting tags, and values
  # contain the axis name, range, and default value.
  get 'variationAxes', ->
    res = {}
    Ireturn res unless @fvar
    
    for axis in @fvar.axis
      res[axis.axisTag] = 
        name: axis.name
        min: axis.minValue
        default: axis.defaultValue
        max: axis.maxValue
        
    return res
    
  # Returns an object describing the named variation instances
  # that the font designer has specified. Keys are variation names
  # and values are the variation settings for this instance.
  get 'namedVariations', ->
    res = {}
    Ireturn res unless @fvar
    
    for instance in @fvar.instance
      settings = {}
      for axis, i in @fvar.axis
        settings[axis.axisTag] = instance.coord[i]
      
      res[instance.name] = settings
        
    return res
    
  # Returns a new font with the given variation settings applied.
  # Settings can either be an instance name, or an object containing
  # variation tags as specified by the `variationAxes` property.
  getVariation: (settings) ->
    Iunless @directory.tables.fvar and @directory.tables.gvar and @directory.tables.glyf
      throw new Error 'Variations require a font with the fvar, gvar, and glyf tables.'
      
    if typeof settings is 'string'
      settings = @namedVariations[settings]
    
    Iif typeof settings isnt 'object'
      throw new Error 'Variation settings must be either a variation name or settings object.'  
    
    # normalize the coordinates
    coords = for axis, i in @fvar.axis
      if axis.axisTag of settings
        Math.max axis.minValue, Math.min axis.maxValue, settings[axis.axisTag]
      else
        axis.defaultValue
        
    stream = new r.DecodeStream @stream.buffer
    stream.pos = @_directoryPos
    
    font = new TTFFont stream, coords
    font._tables = @_tables
    
    return font
    
  # Standardized format plugin API
  getFont: (name) ->
    return @getVariation name
    
module.exports = TTFFont