Code coverage report for fontkit/src/WOFF2Font.coffee

Statements: 94.85% (129 / 136)      Branches: 77.78% (28 / 36)      Functions: 100% (15 / 15)      Lines: 93.64% (103 / 110)      Ignored: none     

All files » fontkit/src/ » WOFF2Font.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 1871 1 1 1 1 1       1   11     2 2       3 1 1   1 1 18 18   1 1     1 1     2 2 2         1 1 1 1           1   7     7     1                                       1 1 1 1   1 1944 1944 1944   1944 819 819   819 1249 1249 1249   819 819 1249   819   1125 1116 1116     1944   1   1 1 1 1   1 2068   2068     2068 1   2067     2067     1   23045   1 35757   1 819 819   819 23045 23045 23045   23045 4661 4661   18384 5672 5672   12712 6979 6979 6979 6979   5733 5652 5652 5652   81 81 81 81 81           23045 23045 23045   819   1  
r = require 'restructure'
brotli = require 'brotli/decompress'
TTFFont = require './TTFFont'
TTFGlyph = require './glyph/TTFGlyph'
WOFF2Glyph = require './glyph/WOFF2Glyph'
WOFF2Directory = require './tables/WOFF2Directory'
 
# Subclass of TTFFont that represents a TTF/OTF font compressed by WOFF2
# See spec here: http://www.w3.org/TR/WOFF2/
class WOFF2Font extends TTFFont
  @probe: (buffer) ->
    return buffer.toString('ascii', 0, 4) is 'wOF2'
  
  _decodeDirectory: ->
    @directory = WOFF2Directory.decode @stream
    @_dataPos = @stream.pos
    
  _decompress: ->
    # decompress data and setup table offsets if we haven't already
    unless @_decompressed
      @stream.pos = @_dataPos
      buffer = @stream.readBuffer @directory.totalCompressedSize
      
      decompressedSize = 0
      for tag, entry of @directory.tables
        entry.offset = decompressedSize
        decompressedSize += if entry.transformLength? then entry.transformLength else entry.length
        
      decompressed = brotli buffer, decompressedSize
      Iunless decompressed
        throw new Error 'Error decoding compressed data in WOFF2'
        
      @stream = new r.DecodeStream new Buffer decompressed
      @_decompressed = true
    
  _decodeTable: (table) ->
    @_decompress()
    @stream.pos = table.offset
    super
    
  # Override this method to get a glyph and return our
  # custom subclass if there is a glyf table.
  _getBaseGlyph: (glyph, characters = []) ->
    Eunless @_glyphs[glyph]
      Eif @directory.tables.glyf?.transformed
        E@_transformGlyfTable() unless @_transformedGlyphs
        @_glyphs[glyph] = new WOFF2Glyph glyph, characters, this
        
      else
        super
        
  # Special class that accepts a length and returns a sub-stream for that data
  class Substream
    constructor: (@length) ->
      @_buf = new r.Buffer @length
      
    decode: (stream, parent) ->
      return new r.DecodeStream @_buf.decode(stream, parent)
      
  # This struct represents the entire glyf table
  GlyfTable = new r.Struct
    version: r.uint32
    numGlyphs: r.uint16
    indexFormat: r.uint16
    nContourStreamSize: r.uint32
    nPointsStreamSize: r.uint32
    flagStreamSize: r.uint32
    glyphStreamSize: r.uint32
    compositeStreamSize: r.uint32
    bboxStreamSize: r.uint32
    instructionStreamSize: r.uint32
    nContours: new Substream 'nContourStreamSize'
    nPoints: new Substream 'nPointsStreamSize'
    flags: new Substream 'flagStreamSize'
    glyphs: new Substream 'glyphStreamSize'
    composites: new Substream 'compositeStreamSize'
    bboxes: new Substream 'bboxStreamSize'
    instructions: new Substream 'instructionStreamSize'
      
  _transformGlyfTable: ->
    @_decompress()
    @stream.pos = @directory.tables.glyf.offset
    table = GlyfTable.decode @stream
    glyphs = []
        
    for index in [0...table.numGlyphs] by 1
      glyph = {}
      nContours = table.nContours.readInt16BE()
      glyph.numberOfContours = nContours
            
      if nContours > 0 # simple glyph
        nPoints = []
        totalPoints = 0
                
        for i in [0...nContours] by 1
          r = read255UInt16 table.nPoints
          nPoints.push r
          totalPoints += r
          
        glyph.points = decodeTriplet table.flags, table.glyphs, totalPoints
        for i in [0...nContours] by 1
          glyph.points[nPoints[i] - 1].endContour = true
        
        instructionSize = read255UInt16 table.glyphs
        
      else if nContours < 0 # composite glyph          
        haveInstructions = TTFGlyph::_decodeComposite.call { _font: this }, glyph, table.composites          
        Iif haveInstructions
          instructionSize = read255UInt16 table.glyphs
          
      glyphs.push glyph
      
    @_transformedGlyphs = glyphs
    
  WORD_CODE = 253
  ONE_MORE_BYTE_CODE2 = 254
  ONE_MORE_BYTE_CODE1 = 255
  LOWEST_U_CODE = 253
    
  read255UInt16 = (stream) ->
    code = stream.readUInt8()
    
    Iif code is WORD_CODE
      return stream.readUInt16BE()
      
    if code is ONE_MORE_BYTE_CODE1
      return stream.readUInt8() + LOWEST_U_CODE
      
    Iif code is ONE_MORE_BYTE_CODE2
      return stream.readUInt8() + LOWEST_U_CODE * 2
      
    return code
    
  # Represents a glyph point
  class Point
    constructor: (@x, @y, @onCurve) ->
      @endContour = false
  
  withSign = (flag, baseval) ->
    return if (flag & 1) then baseval else -baseval
    
  decodeTriplet = (flags, glyphs, nPoints) ->
    x = y = 0
    res = []
    
    for i in [0...nPoints] by 1
      flag = flags.readUInt8()
      onCurve = !(flag >> 7)
      flag &= 0x7f
        
      if flag < 10
        dx = 0
        dy = withSign flag, ((flag & 14) << 7) + glyphs.readUInt8()
        
      else if flag < 20
        dx = withSign flag, (((flag - 10) & 14) << 7) + glyphs.readUInt8()
        dy = 0
        
      else if flag < 84
        b0 = flag - 20
        b1 = glyphs.readUInt8()
        dx = withSign flag, 1 + (b0 & 0x30) + (b1 >> 4)
        dy = withSign flag >> 1, 1 + ((b0 & 0x0c) << 2) + (b1 & 0x0f)
        
      else if flag < 120
        b0 = flag - 84
        dx = withSign flag, 1 + ((b0 / 12) << 8) + glyphs.readUInt8()
        dy = withSign flag >> 1, 1 + (((b0 % 12) >> 2) << 8) + glyphs.readUInt8()
        
      else Eif flag < 124
        b1 = glyphs.readUInt8()
        b2 = glyphs.readUInt8()
        dx = withSign flag, (b1 << 4) + (b2 >> 4)
        dy = withSign flag >> 1, ((b2 & 0x0f) << 8) + glyphs.readUInt8()
        
      else
        dx = withSign flag, glyphs.readUInt16BE()
        dy = withSign flag >> 1, glyphs.readUInt16BE()
        
      x += dx
      y += dy
      res.push new Point x, y, onCurve
      
    return res
    
module.exports = WOFF2Font