text: // The `CFF` table contains the glyph outlines in PostScript format.
  // https://www.microsoft.com/typography/OTSPEC/cff.htm
  // http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/cff.pdf
  // http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/type2.pdf
  
  'use strict';
  
  var encoding = require('../encoding');
  var glyphset = require('../glyphset');
  var parse = require('../parse');
  var path = require('../path');
  var table = require('../table');
  
  // Custom equals function that can also check lists.
  function equals(a, b) {
      if (a === b) {
          return true;
      } else if (Array.isArray(a) && Array.isArray(b)) {
          if (a.length !== b.length) {
              return false;
          }
  
          for (var i = 0; i < a.length; i += 1) {
              if (!equals(a[i], b[i])) {
                  return false;
              }
          }
  
          return true;
      } else {
          return false;
      }
  }
  
  // Parse a `CFF` INDEX array.
  // An index array consists of a list of offsets, then a list of objects at those offsets.
  function parseCFFIndex(data, start, conversionFn) {
      //var i, objectOffset, endOffset;
      var offsets = [];
      var objects = [];
      var count = parse.getCard16(data, start);
      var i;
      var objectOffset;
      var endOffset;
      if (count !== 0) {
          var offsetSize = parse.getByte(data, start + 2);
          objectOffset = start + ((count + 1) * offsetSize) + 2;
          var pos = start + 3;
          for (i = 0; i < count + 1; i += 1) {
              offsets.push(parse.getOffset(data, pos, offsetSize));
              pos += offsetSize;
          }
  
          // The total size of the index array is 4 header bytes + the value of the last offset.
          endOffset = objectOffset + offsets[count];
      } else {
          endOffset = start + 2;
      }
  
      for (i = 0; i < offsets.length - 1; i += 1) {
          var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]);
          if (conversionFn) {
              value = conversionFn(value);
          }
  
          objects.push(value);
      }
  
      return {objects: objects, startOffset: start, endOffset: endOffset};
  }
  
  // Parse a `CFF` DICT real value.
  function parseFloatOperand(parser) {
      var s = '';
      var eof = 15;
      var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-'];
      while (true) {
          var b = parser.parseByte();
          var n1 = b >> 4;
          var n2 = b & 15;
  
          if (n1 === eof) {
              break;
          }
  
          s += lookup[n1];
  
          if (n2 === eof) {
              break;
          }
  
          s += lookup[n2];
      }
  
      return parseFloat(s);
  }
  
  // Parse a `CFF` DICT operand.
  function parseOperand(parser, b0) {
      var b1;
      var b2;
      var b3;
      var b4;
      if (b0 === 28) {
          b1 = parser.parseByte();
          b2 = parser.parseByte();
          return b1 << 8 | b2;
      }
  
      if (b0 === 29) {
          b1 = parser.parseByte();
          b2 = parser.parseByte();
          b3 = parser.parseByte();
          b4 = parser.parseByte();
          return b1 << 24 | b2 << 16 | b3 << 8 | b4;
      }
  
      if (b0 === 30) {
          return parseFloatOperand(parser);
      }
  
      if (b0 >= 32 && b0 <= 246) {
          return b0 - 139;
      }
  
      if (b0 >= 247 && b0 <= 250) {
          b1 = parser.parseByte();
          return (b0 - 247) * 256 + b1 + 108;
      }
  
      if (b0 >= 251 && b0 <= 254) {
          b1 = parser.parseByte();
          return -(b0 - 251) * 256 - b1 - 108;
      }
  
      throw new Error('Invalid b0 ' + b0);
  }
  
  // Convert the entries returned by `parseDict` to a proper dictionary.
  // If a value is a list of one, it is unpacked.
  function entriesToObject(entries) {
      var o = {};
      for (var i = 0; i < entries.length; i += 1) {
          var key = entries[i][0];
          var values = entries[i][1];
          var value;
          if (values.length === 1) {
              value = values[0];
          } else {
              value = values;
          }
  
          if (o.hasOwnProperty(key)) {
              throw new Error('Object ' + o + ' already has key ' + key);
          }
  
          o[key] = value;
      }
  
      return o;
  }
  
  // Parse a `CFF` DICT object.
  // A dictionary contains key-value pairs in a compact tokenized format.
  function parseCFFDict(data, start, size) {
      start = start !== undefined ? start : 0;
      var parser = new parse.Parser(data, start);
      var entries = [];
      var operands = [];
      size = size !== undefined ? size : data.length;
  
      while (parser.relativeOffset < size) {
          var op = parser.parseByte();
  
          // The first byte for each dict item distinguishes between operator (key) and operand (value).
          // Values <= 21 are operators.
          if (op <= 21) {
              // Two-byte operators have an initial escape byte of 12.
              if (op === 12) {
                  op = 1200 + parser.parseByte();
              }
  
              entries.push([op, operands]);
              operands = [];
          } else {
              // Since the operands (values) come before the operators (keys), we store all operands in a list
              // until we encounter an operator.
              operands.push(parseOperand(parser, op));
          }
      }
  
      return entriesToObject(entries);
  }
  
  // Given a String Index (SID), return the value of the string.
  // Strings below index 392 are standard CFF strings and are not encoded in the font.
  function getCFFString(strings, index) {
      if (index <= 390) {
          index = encoding.cffStandardStrings[index];
      } else {
          index = strings[index - 391];
      }
  
      return index;
  }
  
  // Interpret a dictionary and return a new dictionary with readable keys and values for missing entries.
  // This function takes `meta` which is a list of objects containing `operand`, `name` and `default`.
  function interpretDict(dict, meta, strings) {
      var newDict = {};
  
      // Because we also want to include missing values, we start out from the meta list
      // and lookup values in the dict.
      for (var i = 0; i < meta.length; i += 1) {
          var m = meta[i];
          var value = dict[m.op];
          if (value === undefined) {
              value = m.value !== undefined ? m.value : null;
          }
  
          if (m.type === 'SID') {
              value = getCFFString(strings, value);
          }
  
          newDict[m.name] = value;
      }
  
      return newDict;
  }
  
  // Parse the CFF header.
  function parseCFFHeader(data, start) {
      var header = {};
      header.formatMajor = parse.getCard8(data, start);
      header.formatMinor = parse.getCard8(data, start + 1);
      header.size = parse.getCard8(data, start + 2);
      header.offsetSize = parse.getCard8(data, start + 3);
      header.startOffset = start;
      header.endOffset = start + 4;
      return header;
  }
  
  var TOP_DICT_META = [
      {name: 'version', op: 0, type: 'SID'},
      {name: 'notice', op: 1, type: 'SID'},
      {name: 'copyright', op: 1200, type: 'SID'},
      {name: 'fullName', op: 2, type: 'SID'},
      {name: 'familyName', op: 3, type: 'SID'},
      {name: 'weight', op: 4, type: 'SID'},
      {name: 'isFixedPitch', op: 1201, type: 'number', value: 0},
      {name: 'italicAngle', op: 1202, type: 'number', value: 0},
      {name: 'underlinePosition', op: 1203, type: 'number', value: -100},
      {name: 'underlineThickness', op: 1204, type: 'number', value: 50},
      {name: 'paintType', op: 1205, type: 'number', value: 0},
      {name: 'charstringType', op: 1206, type: 'number', value: 2},
      {name: 'fontMatrix', op: 1207, type: ['real', 'real', 'real', 'real', 'real', 'real'], value: [0.001, 0, 0, 0.001, 0, 0]},
      {name: 'uniqueId', op: 13, type: 'number'},
      {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]},
      {name: 'strokeWidth', op: 1208, type: 'number', value: 0},
      {name: 'xuid', op: 14, type: [], value: null},
      {name: 'charset', op: 15, type: 'offset', value: 0},
      {name: 'encoding', op: 16, type: 'offset', value: 0},
      {name: 'charStrings', op: 17, type: 'offset', value: 0},
      {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]}
  ];
  
  var PRIVATE_DICT_META = [
      {name: 'subrs', op: 19, type: 'offset', value: 0},
      {name: 'defaultWidthX', op: 20, type: 'number', value: 0},
      {name: 'nominalWidthX', op: 21, type: 'number', value: 0}
  ];
  
  // Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary.
  // The top dictionary contains the essential metadata for the font, together with the private dictionary.
  function parseCFFTopDict(data, strings) {
      var dict = parseCFFDict(data, 0, data.byteLength);
      return interpretDict(dict, TOP_DICT_META, strings);
  }
  
  // Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need.
  function parseCFFPrivateDict(data, start, size, strings) {
      var dict = parseCFFDict(data, start, size);
      return interpretDict(dict, PRIVATE_DICT_META, strings);
  }
  
  // Parse the CFF charset table, which contains internal names for all the glyphs.
  // This function will return a list of glyph names.
  // See Adobe TN #5176 chapter 13, "Charsets".
  function parseCFFCharset(data, start, nGlyphs, strings) {
      var i;
      var sid;
      var count;
      var parser = new parse.Parser(data, start);
  
      // The .notdef glyph is not included, so subtract 1.
      nGlyphs -= 1;
      var charset = ['.notdef'];
  
      var format = parser.parseCard8();
      if (format === 0) {
          for (i = 0; i < nGlyphs; i += 1) {
              sid = parser.parseSID();
              charset.push(getCFFString(strings, sid));
          }
      } else if (format === 1) {
          while (charset.length <= nGlyphs) {
              sid = parser.parseSID();
              count = parser.parseCard8();
              for (i = 0; i <= count; i += 1) {
                  charset.push(getCFFString(strings, sid));
                  sid += 1;
              }
          }
      } else if (format === 2) {
          while (charset.length <= nGlyphs) {
              sid = parser.parseSID();
              count = parser.parseCard16();
              for (i = 0; i <= count; i += 1) {
                  charset.push(getCFFString(strings, sid));
                  sid += 1;
              }
          }
      } else {
          throw new Error('Unknown charset format ' + format);
      }
  
      return charset;
  }
  
  // Parse the CFF encoding data. Only one encoding can be specified per font.
  // See Adobe TN #5176 chapter 12, "Encodings".
  function parseCFFEncoding(data, start, charset) {
      var i;
      var code;
      var enc = {};
      var parser = new parse.Parser(data, start);
      var format = parser.parseCard8();
      if (format === 0) {
          var nCodes = parser.parseCard8();
          for (i = 0; i < nCodes; i += 1) {
              code = parser.parseCard8();
              enc[code] = i;
          }
      } else if (format === 1) {
          var nRanges = parser.parseCard8();
          code = 1;
          for (i = 0; i < nRanges; i += 1) {
              var first = parser.parseCard8();
              var nLeft = parser.parseCard8();
              for (var j = first; j <= first + nLeft; j += 1) {
                  enc[j] = code;
                  code += 1;
              }
          }
      } else {
          throw new Error('Unknown encoding format ' + format);
      }
  
      return new encoding.CffEncoding(enc, charset);
  }
  
  // Take in charstring code and return a Glyph object.
  // The encoding is described in the Type 2 Charstring Format
  // https://www.microsoft.com/typography/OTSPEC/charstr2.htm
  function parseCFFCharstring(font, glyph, code) {
      var c1x;
      var c1y;
      var c2x;
      var c2y;
      var p = new path.Path();
      var stack = [];
      var nStems = 0;
      var haveWidth = false;
      var width = font.defaultWidthX;
      var open = false;
      var x = 0;
      var y = 0;
  
      function newContour(x, y) {
          if (open) {
              p.closePath();
          }
  
          p.moveTo(x, y);
          open = true;
      }
  
      function parseStems() {
          var hasWidthArg;
  
          // The number of stem operators on the stack is always even.
          // If the value is uneven, that means a width is specified.
          hasWidthArg = stack.length % 2 !== 0;
          if (hasWidthArg && !haveWidth) {
              width = stack.shift() + font.nominalWidthX;
          }
  
          nStems += stack.length >> 1;
          stack.length = 0;
          haveWidth = true;
      }
  
      function parse(code) {
          var b1;
          var b2;
          var b3;
          var b4;
          var codeIndex;
          var subrCode;
          var jpx;
          var jpy;
          var c3x;
          var c3y;
          var c4x;
          var c4y;
  
          var i = 0;
          while (i < code.length) {
              var v = code[i];
              i += 1;
              switch (v) {
              case 1: // hstem
                  parseStems();
                  break;
              case 3: // vstem
                  parseStems();
                  break;
              case 4: // vmoveto
                  if (stack.length > 1 && !haveWidth) {
                      width = stack.shift() + font.nominalWidthX;
                      haveWidth = true;
                  }
  
                  y += stack.pop();
                  newContour(x, y);
                  break;
              case 5: // rlineto
                  while (stack.length > 0) {
                      x += stack.shift();
                      y += stack.shift();
                      p.lineTo(x, y);
                  }
  
                  break;
              case 6: // hlineto
                  while (stack.length > 0) {
                      x += stack.shift();
                      p.lineTo(x, y);
                      if (stack.length === 0) {
                          break;
                      }
  
                      y += stack.shift();
                      p.lineTo(x, y);
                  }
  
                  break;
              case 7: // vlineto
                  while (stack.length > 0) {
                      y += stack.shift();
                      p.lineTo(x, y);
                      if (stack.length === 0) {
                          break;
                      }
  
                      x += stack.shift();
                      p.lineTo(x, y);
                  }
  
                  break;
              case 8: // rrcurveto
                  while (stack.length > 0) {
                      c1x = x + stack.shift();
                      c1y = y + stack.shift();
                      c2x = c1x + stack.shift();
                      c2y = c1y + stack.shift();
                      x = c2x + stack.shift();
                      y = c2y + stack.shift();
                      p.curveTo(c1x, c1y, c2x, c2y, x, y);
                  }
  
                  break;
              case 10: // callsubr
                  codeIndex = stack.pop() + font.subrsBias;
                  subrCode = font.subrs[codeIndex];
                  if (subrCode) {
                      parse(subrCode);
                  }
  
                  break;
              case 11: // return
                  return;
              case 12: // flex operators
                  v = code[i];
                  i += 1;
                  switch (v) {
                  case 35: // flex
                      // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |-
                      c1x = x   + stack.shift();    // dx1
                      c1y = y   + stack.shift();    // dy1
                      c2x = c1x + stack.shift();    // dx2
                      c2y = c1y + stack.shift();    // dy2
                      jpx = c2x + stack.shift();    // dx3
                      jpy = c2y + stack.shift();    // dy3
                      c3x = jpx + stack.shift();    // dx4
                      c3y = jpy + stack.shift();    // dy4
                      c4x = c3x + stack.shift();    // dx5
                      c4y = c3y + stack.shift();    // dy5
                      x = c4x + stack.shift();      // dx6
                      y = c4y + stack.shift();      // dy6
                      stack.shift();                // flex depth
                      p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
                      p.curveTo(c3x, c3y, c4x, c4y, x, y);
                      break;
                  case 34: // hflex
                      // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |-
                      c1x = x   + stack.shift();    // dx1
                      c1y = y;                      // dy1
                      c2x = c1x + stack.shift();    // dx2
                      c2y = c1y + stack.shift();    // dy2
                      jpx = c2x + stack.shift();    // dx3
                      jpy = c2y;                    // dy3
                      c3x = jpx + stack.shift();    // dx4
                      c3y = c2y;                    // dy4
                      c4x = c3x + stack.shift();    // dx5
                      c4y = y;                      // dy5
                      x = c4x + stack.shift();      // dx6
                      p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
                      p.curveTo(c3x, c3y, c4x, c4y, x, y);
                      break;
                  case 36: // hflex1
                      // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |-
                      c1x = x   + stack.shift();    // dx1
                      c1y = y   + stack.shift();    // dy1
                      c2x = c1x + stack.shift();    // dx2
                      c2y = c1y + stack.shift();    // dy2
                      jpx = c2x + stack.shift();    // dx3
                      jpy = c2y;                    // dy3
                      c3x = jpx + stack.shift();    // dx4
                      c3y = c2y;                    // dy4
                      c4x = c3x + stack.shift();    // dx5
                      c4y = c3y + stack.shift();    // dy5
                      x = c4x + stack.shift();      // dx6
                      p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
                      p.curveTo(c3x, c3y, c4x, c4y, x, y);
                      break;
                  case 37: // flex1
                      // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |-
                      c1x = x   + stack.shift();    // dx1
                      c1y = y   + stack.shift();    // dy1
                      c2x = c1x + stack.shift();    // dx2
                      c2y = c1y + stack.shift();    // dy2
                      jpx = c2x + stack.shift();    // dx3
                      jpy = c2y + stack.shift();    // dy3
                      c3x = jpx + stack.shift();    // dx4
                      c3y = jpy + stack.shift();    // dy4
                      c4x = c3x + stack.shift();    // dx5
                      c4y = c3y + stack.shift();    // dy5
                      if (Math.abs(c4x - x) > Math.abs(c4y - y)) {
                          x = c4x + stack.shift();
                      } else {
                          y = c4y + stack.shift();
                      }
  
                      p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
                      p.curveTo(c3x, c3y, c4x, c4y, x, y);
                      break;
                  default:
                      console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v);
                      stack.length = 0;
                  }
                  break;
              case 14: // endchar
                  if (stack.length > 0 && !haveWidth) {
                      width = stack.shift() + font.nominalWidthX;
                      haveWidth = true;
                  }
  
                  if (open) {
                      p.closePath();
                      open = false;
                  }
  
                  break;
              case 18: // hstemhm
                  parseStems();
                  break;
              case 19: // hintmask
              case 20: // cntrmask
                  parseStems();
                  i += (nStems + 7) >> 3;
                  break;
              case 21: // rmoveto
                  if (stack.length > 2 && !haveWidth) {
                      width = stack.shift() + font.nominalWidthX;
                      haveWidth = true;
                  }
  
                  y += stack.pop();
                  x += stack.pop();
                  newContour(x, y);
                  break;
              case 22: // hmoveto
                  if (stack.length > 1 && !haveWidth) {
                      width = stack.shift() + font.nominalWidthX;
                      haveWidth = true;
                  }
  
                  x += stack.pop();
                  newContour(x, y);
                  break;
              case 23: // vstemhm
                  parseStems();
                  break;
              case 24: // rcurveline
                  while (stack.length > 2) {
                      c1x = x + stack.shift();
                      c1y = y + stack.shift();
                      c2x = c1x + stack.shift();
                      c2y = c1y + stack.shift();
                      x = c2x + stack.shift();
                      y = c2y + stack.shift();
                      p.curveTo(c1x, c1y, c2x, c2y, x, y);
                  }
  
                  x += stack.shift();
                  y += stack.shift();
                  p.lineTo(x, y);
                  break;
              case 25: // rlinecurve
                  while (stack.length > 6) {
                      x += stack.shift();
                      y += stack.shift();
                      p.lineTo(x, y);
                  }
  
                  c1x = x + stack.shift();
                  c1y = y + stack.shift();
                  c2x = c1x + stack.shift();
                  c2y = c1y + stack.shift();
                  x = c2x + stack.shift();
                  y = c2y + stack.shift();
                  p.curveTo(c1x, c1y, c2x, c2y, x, y);
                  break;
              case 26: // vvcurveto
                  if (stack.length % 2) {
                      x += stack.shift();
                  }
  
                  while (stack.length > 0) {
                      c1x = x;
                      c1y = y + stack.shift();
                      c2x = c1x + stack.shift();
                      c2y = c1y + stack.shift();
                      x = c2x;
                      y = c2y + stack.shift();
                      p.curveTo(c1x, c1y, c2x, c2y, x, y);
                  }
  
                  break;
              case 27: // hhcurveto
                  if (stack.length % 2) {
                      y += stack.shift();
                  }
  
                  while (stack.length > 0) {
                      c1x = x + stack.shift();
                      c1y = y;
                      c2x = c1x + stack.shift();
                      c2y = c1y + stack.shift();
                      x = c2x + stack.shift();
                      y = c2y;
                      p.curveTo(c1x, c1y, c2x, c2y, x, y);
                  }
  
                  break;
              case 28: // shortint
                  b1 = code[i];
                  b2 = code[i + 1];
                  stack.push(((b1 << 24) | (b2 << 16)) >> 16);
                  i += 2;
                  break;
              case 29: // callgsubr
                  codeIndex = stack.pop() + font.gsubrsBias;
                  subrCode = font.gsubrs[codeIndex];
                  if (subrCode) {
                      parse(subrCode);
                  }
  
                  break;
              case 30: // vhcurveto
                  while (stack.length > 0) {
                      c1x = x;
                      c1y = y + stack.shift();
                      c2x = c1x + stack.shift();
                      c2y = c1y + stack.shift();
                      x = c2x + stack.shift();
                      y = c2y + (stack.length === 1 ? stack.shift() : 0);
                      p.curveTo(c1x, c1y, c2x, c2y, x, y);
                      if (stack.length === 0) {
                          break;
                      }
  
                      c1x = x + stack.shift();
                      c1y = y;
                      c2x = c1x + stack.shift();
                      c2y = c1y + stack.shift();
                      y = c2y + stack.shift();
                      x = c2x + (stack.length === 1 ? stack.shift() : 0);
                      p.curveTo(c1x, c1y, c2x, c2y, x, y);
                  }
  
                  break;
              case 31: // hvcurveto
                  while (stack.length > 0) {
                      c1x = x + stack.shift();
                      c1y = y;
                      c2x = c1x + stack.shift();
                      c2y = c1y + stack.shift();
                      y = c2y + stack.shift();
                      x = c2x + (stack.length === 1 ? stack.shift() : 0);
                      p.curveTo(c1x, c1y, c2x, c2y, x, y);
                      if (stack.length === 0) {
                          break;
                      }
  
                      c1x = x;
                      c1y = y + stack.shift();
                      c2x = c1x + stack.shift();
                      c2y = c1y + stack.shift();
                      x = c2x + stack.shift();
                      y = c2y + (stack.length === 1 ? stack.shift() : 0);
                      p.curveTo(c1x, c1y, c2x, c2y, x, y);
                  }
  
                  break;
              default:
                  if (v < 32) {
                      console.log('Glyph ' + glyph.index + ': unknown operator ' + v);
                  } else if (v < 247) {
                      stack.push(v - 139);
                  } else if (v < 251) {
                      b1 = code[i];
                      i += 1;
                      stack.push((v - 247) * 256 + b1 + 108);
                  } else if (v < 255) {
                      b1 = code[i];
                      i += 1;
                      stack.push(-(v - 251) * 256 - b1 - 108);
                  } else {
                      b1 = code[i];
                      b2 = code[i + 1];
                      b3 = code[i + 2];
                      b4 = code[i + 3];
                      i += 4;
                      stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536);
                  }
              }
          }
      }
  
      parse(code);
  
      glyph.advanceWidth = width;
      return p;
  }
  
  // Subroutines are encoded using the negative half of the number space.
  // See type 2 chapter 4.7 "Subroutine operators".
  function calcCFFSubroutineBias(subrs) {
      var bias;
      if (subrs.length < 1240) {
          bias = 107;
      } else if (subrs.length < 33900) {
          bias = 1131;
      } else {
          bias = 32768;
      }
  
      return bias;
  }
  
  // Parse the `CFF` table, which contains the glyph outlines in PostScript format.
  function parseCFFTable(data, start, font) {
      font.tables.cff = {};
      var header = parseCFFHeader(data, start);
      var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString);
      var topDictIndex = parseCFFIndex(data, nameIndex.endOffset);
      var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString);
      var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset);
      font.gsubrs = globalSubrIndex.objects;
      font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs);
  
      var topDictData = new DataView(new Uint8Array(topDictIndex.objects[0]).buffer);
      var topDict = parseCFFTopDict(topDictData, stringIndex.objects);
      font.tables.cff.topDict = topDict;
  
      var privateDictOffset = start + topDict['private'][1];
      var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict['private'][0], stringIndex.objects);
      font.defaultWidthX = privateDict.defaultWidthX;
      font.nominalWidthX = privateDict.nominalWidthX;
  
      if (privateDict.subrs !== 0) {
          var subrOffset = privateDictOffset + privateDict.subrs;
          var subrIndex = parseCFFIndex(data, subrOffset);
          font.subrs = subrIndex.objects;
          font.subrsBias = calcCFFSubroutineBias(font.subrs);
      } else {
          font.subrs = [];
          font.subrsBias = 0;
      }
  
      // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset.
      var charStringsIndex = parseCFFIndex(data, start + topDict.charStrings);
      font.nGlyphs = charStringsIndex.objects.length;
  
      var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects);
      if (topDict.encoding === 0) { // Standard encoding
          font.cffEncoding = new encoding.CffEncoding(encoding.cffStandardEncoding, charset);
      } else if (topDict.encoding === 1) { // Expert encoding
          font.cffEncoding = new encoding.CffEncoding(encoding.cffExpertEncoding, charset);
      } else {
          font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset);
      }
  
      // Prefer the CMAP encoding to the CFF encoding.
      font.encoding = font.encoding || font.cffEncoding;
  
      font.glyphs = new glyphset.GlyphSet(font);
      for (var i = 0; i < font.nGlyphs; i += 1) {
          var charString = charStringsIndex.objects[i];
          font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString));
      }
  }
  
  // Convert a string to a String ID (SID).
  // The list of strings is modified in place.
  function encodeString(s, strings) {
      var sid;
  
      // Is the string in the CFF standard strings?
      var i = encoding.cffStandardStrings.indexOf(s);
      if (i >= 0) {
          sid = i;
      }
  
      // Is the string already in the string index?
      i = strings.indexOf(s);
      if (i >= 0) {
          sid = i + encoding.cffStandardStrings.length;
      } else {
          sid = encoding.cffStandardStrings.length + strings.length;
          strings.push(s);
      }
  
      return sid;
  }
  
  function makeHeader() {
      return new table.Table('Header', [
          {name: 'major', type: 'Card8', value: 1},
          {name: 'minor', type: 'Card8', value: 0},
          {name: 'hdrSize', type: 'Card8', value: 4},
          {name: 'major', type: 'Card8', value: 1}
      ]);
  }
  
  function makeNameIndex(fontNames) {
      var t = new table.Table('Name INDEX', [
          {name: 'names', type: 'INDEX', value: []}
      ]);
      t.names = [];
      for (var i = 0; i < fontNames.length; i += 1) {
          t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]});
      }
  
      return t;
  }
  
  // Given a dictionary's metadata, create a DICT structure.
  function makeDict(meta, attrs, strings) {
      var m = {};
      for (var i = 0; i < meta.length; i += 1) {
          var entry = meta[i];
          var value = attrs[entry.name];
          if (value !== undefined && !equals(value, entry.value)) {
              if (entry.type === 'SID') {
                  value = encodeString(value, strings);
              }
  
              m[entry.op] = {name: entry.name, type: entry.type, value: value};
          }
      }
  
      return m;
  }
  
  // The Top DICT houses the global font attributes.
  function makeTopDict(attrs, strings) {
      var t = new table.Table('Top DICT', [
          {name: 'dict', type: 'DICT', value: {}}
      ]);
      t.dict = makeDict(TOP_DICT_META, attrs, strings);
      return t;
  }
  
  function makeTopDictIndex(topDict) {
      var t = new table.Table('Top DICT INDEX', [
          {name: 'topDicts', type: 'INDEX', value: []}
      ]);
      t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}];
      return t;
  }
  
  function makeStringIndex(strings) {
      var t = new table.Table('String INDEX', [
          {name: 'strings', type: 'INDEX', value: []}
      ]);
      t.strings = [];
      for (var i = 0; i < strings.length; i += 1) {
          t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]});
      }
  
      return t;
  }
  
  function makeGlobalSubrIndex() {
      // Currently we don't use subroutines.
      return new table.Table('Global Subr INDEX', [
          {name: 'subrs', type: 'INDEX', value: []}
      ]);
  }
  
  function makeCharsets(glyphNames, strings) {
      var t = new table.Table('Charsets', [
          {name: 'format', type: 'Card8', value: 0}
      ]);
      for (var i = 0; i < glyphNames.length; i += 1) {
          var glyphName = glyphNames[i];
          var glyphSID = encodeString(glyphName, strings);
          t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID});
      }
  
      return t;
  }
  
  function glyphToOps(glyph) {
      var ops = [];
      var path = glyph.path;
      ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth});
      var x = 0;
      var y = 0;
      for (var i = 0; i < path.commands.length; i += 1) {
          var dx;
          var dy;
          var cmd = path.commands[i];
          if (cmd.type === 'Q') {
              // CFF only supports bézier curves, so convert the quad to a bézier.
              var _13 = 1 / 3;
              var _23 = 2 / 3;
  
              // We're going to create a new command so we don't change the original path.
              cmd = {
                  type: 'C',
                  x: cmd.x,
                  y: cmd.y,
                  x1: _13 * x + _23 * cmd.x1,
                  y1: _13 * y + _23 * cmd.y1,
                  x2: _13 * cmd.x + _23 * cmd.x1,
                  y2: _13 * cmd.y + _23 * cmd.y1
              };
          }
  
          if (cmd.type === 'M') {
              dx = Math.round(cmd.x - x);
              dy = Math.round(cmd.y - y);
              ops.push({name: 'dx', type: 'NUMBER', value: dx});
              ops.push({name: 'dy', type: 'NUMBER', value: dy});
              ops.push({name: 'rmoveto', type: 'OP', value: 21});
              x = Math.round(cmd.x);
              y = Math.round(cmd.y);
          } else if (cmd.type === 'L') {
              dx = Math.round(cmd.x - x);
              dy = Math.round(cmd.y - y);
              ops.push({name: 'dx', type: 'NUMBER', value: dx});
              ops.push({name: 'dy', type: 'NUMBER', value: dy});
              ops.push({name: 'rlineto', type: 'OP', value: 5});
              x = Math.round(cmd.x);
              y = Math.round(cmd.y);
          } else if (cmd.type === 'C') {
              var dx1 = Math.round(cmd.x1 - x);
              var dy1 = Math.round(cmd.y1 - y);
              var dx2 = Math.round(cmd.x2 - cmd.x1);
              var dy2 = Math.round(cmd.y2 - cmd.y1);
              dx = Math.round(cmd.x - cmd.x2);
              dy = Math.round(cmd.y - cmd.y2);
              ops.push({name: 'dx1', type: 'NUMBER', value: dx1});
              ops.push({name: 'dy1', type: 'NUMBER', value: dy1});
              ops.push({name: 'dx2', type: 'NUMBER', value: dx2});
              ops.push({name: 'dy2', type: 'NUMBER', value: dy2});
              ops.push({name: 'dx', type: 'NUMBER', value: dx});
              ops.push({name: 'dy', type: 'NUMBER', value: dy});
              ops.push({name: 'rrcurveto', type: 'OP', value: 8});
              x = Math.round(cmd.x);
              y = Math.round(cmd.y);
          }
  
          // Contours are closed automatically.
  
      }
  
      ops.push({name: 'endchar', type: 'OP', value: 14});
      return ops;
  }
  
  function makeCharStringsIndex(glyphs) {
      var t = new table.Table('CharStrings INDEX', [
          {name: 'charStrings', type: 'INDEX', value: []}
      ]);
  
      for (var i = 0; i < glyphs.length; i += 1) {
          var glyph = glyphs.get(i);
          var ops = glyphToOps(glyph);
          t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops});
      }
  
      return t;
  }
  
  function makePrivateDict(attrs, strings) {
      var t = new table.Table('Private DICT', [
          {name: 'dict', type: 'DICT', value: {}}
      ]);
      t.dict = makeDict(PRIVATE_DICT_META, attrs, strings);
      return t;
  }
  
  function makePrivateDictIndex(privateDict) {
      var t = new table.Table('Private DICT INDEX', [
          {name: 'privateDicts', type: 'INDEX', value: []}
      ]);
      t.privateDicts = [{name: 'privateDict_0', type: 'TABLE', value: privateDict}];
      return t;
  }
  
  function makeCFFTable(glyphs, options) {
      var t = new table.Table('CFF ', [
          {name: 'header', type: 'TABLE'},
          {name: 'nameIndex', type: 'TABLE'},
          {name: 'topDictIndex', type: 'TABLE'},
          {name: 'stringIndex', type: 'TABLE'},
          {name: 'globalSubrIndex', type: 'TABLE'},
          {name: 'charsets', type: 'TABLE'},
          {name: 'charStringsIndex', type: 'TABLE'},
          {name: 'privateDictIndex', type: 'TABLE'}
      ]);
  
      var fontScale = 1 / options.unitsPerEm;
      // We use non-zero values for the offsets so that the DICT encodes them.
      // This is important because the size of the Top DICT plays a role in offset calculation,
      // and the size shouldn't change after we've written correct offsets.
      var attrs = {
          version: options.version,
          fullName: options.fullName,
          familyName: options.familyName,
          weight: options.weightName,
          fontMatrix: [fontScale, 0, 0, fontScale, 0, 0],
          charset: 999,
          encoding: 0,
          charStrings: 999,
          private: [0, 999]
      };
  
      var privateAttrs = {};
  
      var glyphNames = [];
      var glyph;
  
      // Skip first glyph (.notdef)
      for (var i = 1; i < glyphs.length; i += 1) {
          glyph = glyphs.get(i);
          glyphNames.push(glyph.name);
      }
  
      var strings = [];
  
      t.header = makeHeader();
      t.nameIndex = makeNameIndex([options.postScriptName]);
      var topDict = makeTopDict(attrs, strings);
      t.topDictIndex = makeTopDictIndex(topDict);
      t.globalSubrIndex = makeGlobalSubrIndex();
      t.charsets = makeCharsets(glyphNames, strings);
      t.charStringsIndex = makeCharStringsIndex(glyphs);
      var privateDict = makePrivateDict(privateAttrs, strings);
      t.privateDictIndex = makePrivateDictIndex(privateDict);
  
      // Needs to come at the end, to encode all custom strings used in the font.
      t.stringIndex = makeStringIndex(strings);
  
      var startOffset = t.header.sizeOf() +
          t.nameIndex.sizeOf() +
          t.topDictIndex.sizeOf() +
          t.stringIndex.sizeOf() +
          t.globalSubrIndex.sizeOf();
      attrs.charset = startOffset;
  
      // We use the CFF standard encoding; proper encoding will be handled in cmap.
      attrs.encoding = 0;
      attrs.charStrings = attrs.charset + t.charsets.sizeOf();
      attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf();
  
      // Recreate the Top DICT INDEX with the correct offsets.
      topDict = makeTopDict(attrs, strings);
      t.topDictIndex = makeTopDictIndex(topDict);
  
      return t;
  }
  
  exports.parse = parseCFFTable;
  exports.make = makeCFFTable;
  