all files / fontkit/src/cff/ CFFDict.coffee

95.45% Statements 84/88
88.57% Branches 31/35
100% Functions 7/7
96.15% Lines 75/78
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     54× 54×   372× 369× 72×   297×   191×       106×   633× 24× 625× 101× 524× 320× 204×   204× 204×         57× 57× 57×     57×       57× 1094× 1094× 363× 113×   363× 363×   363× 363×   731×     57× 802×   57×     69×           69×   69× 1050× 1050×   549× 549× 1233×   549× 549×   69× 40×   69×                   128× 128×   60× 60× 117×   60× 60× 81×        
isEqual = require 'deep-equal'
r = require 'restructure'
CFFOperand = require './CFFOperand'
 
class CFFDict
  constructor: (@ops = []) ->
    @fields = {}
    for field in @ops
      key = if Array.isArray(field[0]) then field[0][0] << 8 | field[0][1] else field[0]
      @fields[key] = field
      
  decodeOperands = (type, stream, ret, operands) ->
    if Array.isArray(type)
      for op, i in operands
        decodeOperands type[i], stream, ret, [op]
    else if type.decode?
      type.decode(stream, ret, operands)
    else
      switch type
        when 'number', 'offset', 'sid'
          operands[0]
        when 'boolean'
          !!operands[0]
        else
          operands
          
  encodeOperands = (type, stream, ctx, operands) ->
    if Array.isArray(type)
      for op, i in operands
        encodeOperands(type[i], stream, ctx, op)[0]
    else if type.encode?
      type.encode(stream, operands, ctx)
    else if typeof operands is 'number'
      [operands]
    else Iif typeof operands is 'boolean'
      [+operands]
    else Eif Array.isArray(operands)
      operands
    else
      [operands]
              
  decode: (stream, parent) ->
    end = stream.pos + parent.length
    ret = {}
    operands = []
    
    # define hidden properties    
    Object.defineProperties ret,
      parent:         { value: parent }
      _startOffset:   { value: stream.pos }
    
    while stream.pos < end
      b = stream.readUInt8()
      if b <= 21
        if b is 12
          b = (b << 8) | stream.readUInt8()
          
        field = @fields[b]
        Ithrow new Error "Unknown operator " + b unless field
        
        ret[field[1]] = decodeOperands field[2], stream, ret, operands
        operands = []
      else
        operands.push CFFOperand.decode(stream, b)
        
    # fill in defaults
    for key, field of @fields
      ret[field[1]] ?= field[3]
        
    return ret
    
  size: (dict, parent, includePointers = true) ->
    ctx = 
      parent: parent
      val: dict
      pointerSize: 0
      startOffset: parent.startOffset or 0
    
    len = 0
        
    for k, field of @fields
      val = dict[field[1]]
      continue if not val? or isEqual val, field[3]
      
      operands = encodeOperands field[2], null, ctx, val
      for op in operands
        len += CFFOperand.size op
        
      key = if Array.isArray(field[0]) then field[0] else [field[0]]
      len += key.length
      
    if includePointers
      len += ctx.pointerSize
        
    return len
    
  encode: (stream, dict, parent) ->
    ctx = 
      pointers: []
      startOffset: stream.pos
      parent: parent
      val: dict
      pointerSize: 0
      
    ctx.pointerOffset = stream.pos + @size(dict, ctx, false)
    
    for field in @ops
      val = dict[field[1]]
      continue if not val? or isEqual val, field[3]
              
      operands = encodeOperands field[2], stream, ctx, val
      for op in operands
        CFFOperand.encode stream, op
        
      key = if Array.isArray(field[0]) then field[0] else [field[0]]
      for op in key
        stream.writeUInt8 op
        
    i = 0
    while i < ctx.pointers.length
      ptr = ctx.pointers[i++]
      ptr.type.encode(stream, ptr.val, ptr.parent)
        
    return
    
module.exports = CFFDict