text: import Glyph from './Glyph';
  import Path from './Path';
  
  /**
   * Represents an OpenType PostScript glyph, in the Compact Font Format.
   */
  export default class CFFGlyph extends Glyph {
    _getName() {
      if (this._font.CFF2) {
        return super._getName();
      }
  
      return this._font['CFF '].getGlyphName(this.id);
    }
  
    bias(s) {
      if (s.length < 1240) {
        return 107;
      } else if (s.length < 33900) {
        return 1131;
      } else {
        return 32768;
      }
    }
  
    _getPath() {
      let cff = this._font.CFF2 || this._font['CFF '];
      let { stream } = cff;
      let str = cff.topDict.CharStrings[this.id];
      let end = str.offset + str.length;
      stream.pos = str.offset;
  
      let path = new Path;
      let stack = [];
      let trans = [];
  
      let width = null;
      let nStems = 0;
      let x = 0, y = 0;
      let usedGsubrs;
      let usedSubrs;
      let open = false;
  
      this._usedGsubrs = usedGsubrs = {};
      this._usedSubrs = usedSubrs = {};
  
      let gsubrs = cff.globalSubrIndex || [];
      let gsubrsBias = this.bias(gsubrs);
  
      let privateDict = cff.privateDictForGlyph(this.id) || {};
      let subrs = privateDict.Subrs || [];
      let subrsBias = this.bias(subrs);
  
      let vstore = cff.topDict.vstore && cff.topDict.vstore.itemVariationStore;
      let vsindex = privateDict.vsindex;
      let variationProcessor = this._font._variationProcessor;
  
      function checkWidth() {
        if (width == null) {
          width = stack.shift() + privateDict.nominalWidthX;
        }
      }
  
      function parseStems() {
        if (stack.length % 2 !== 0) {
          checkWidth();
        }
  
        nStems += stack.length >> 1;
        return stack.length = 0;
      }
  
      function moveTo(x, y) {
        if (open) {
          path.closePath();
        }
  
        path.moveTo(x, y);
        open = true;
      }
  
      let parse = function() {
        while (stream.pos < end) {
          let op = stream.readUInt8();
          if (op < 32) {
            switch (op) {
              case 1:  // hstem
              case 3:  // vstem
              case 18: // hstemhm
              case 23: // vstemhm
                parseStems();
                break;
  
              case 4: // vmoveto
                if (stack.length > 1) {
                  checkWidth();
                }
  
                y += stack.shift();
                moveTo(x, y);
                break;
  
              case 5: // rlineto
                while (stack.length >= 2) {
                  x += stack.shift();
                  y += stack.shift();
                  path.lineTo(x, y);
                }
                break;
  
              case 6: // hlineto
              case 7: // vlineto
                let phase = op === 6;
                while (stack.length >= 1) {
                  if (phase) {
                    x += stack.shift();
                  } else {
                    y += stack.shift();
                  }
  
                  path.lineTo(x, y);
                  phase = !phase;
                }
                break;
  
              case 8: // rrcurveto
                while (stack.length > 0) {
                  var c1x = x + stack.shift();
                  var c1y = y + stack.shift();
                  var c2x = c1x + stack.shift();
                  var c2y = c1y + stack.shift();
                  x = c2x + stack.shift();
                  y = c2y + stack.shift();
                  path.bezierCurveTo(c1x, c1y, c2x, c2y, x, y);
                }
                break;
  
              case 10: // callsubr
                let index = stack.pop() + subrsBias;
                let subr = subrs[index];
                if (subr) {
                  usedSubrs[index] = true;
                  var p = stream.pos;
                  var e = end;
                  stream.pos = subr.offset;
                  end = subr.offset + subr.length;
                  parse();
                  stream.pos = p;
                  end = e;
                }
                break;
  
              case 11: // return
                if (cff.version >= 2) {
                  break;
                }
                return;
  
              case 14: // endchar
                if (cff.version >= 2) {
                  break;
                }
  
                if (stack.length > 0) {
                  checkWidth();
                }
  
                if (open) {
                  path.closePath();
                  open = false;
                }
                break;
  
              case 15: { // vsindex
                if (cff.version < 2) {
                  throw new Error('vsindex operator not supported in CFF v1');
                }
  
                vsindex = stack.pop();
                break;
              }
  
              case 16: { // blend
                if (cff.version < 2) {
                  throw new Error('blend operator not supported in CFF v1');
                }
  
                if (!variationProcessor) {
                  throw new Error('blend operator in non-variation font');
                }
  
                let blendVector = variationProcessor.getBlendVector(vstore, vsindex);
                let numBlends = stack.pop();
                let numOperands = numBlends * blendVector.length;
                let delta = stack.length - numOperands;
                let base = delta - numBlends;
  
                for (let i = 0; i < numBlends; i++) {
                  let sum = stack[base + i];
                  for (let j = 0; j < blendVector.length; j++) {
                    sum += blendVector[j] * stack[delta++];
                  }
  
                  stack[base + i] = sum;
                }
  
                while (numOperands--) {
                  stack.pop();
                }
  
                break;
              }
  
              case 19: // hintmask
              case 20: // cntrmask
                parseStems();
                stream.pos += (nStems + 7) >> 3;
                break;
  
              case 21: // rmoveto
                if (stack.length > 2) {
                  checkWidth();
                }
  
                x += stack.shift();
                y += stack.shift();
                moveTo(x, y);
                break;
  
              case 22: // hmoveto
                if (stack.length > 1) {
                  checkWidth();
                }
  
                x += stack.shift();
                moveTo(x, y);
                break;
  
              case 24: // rcurveline
                while (stack.length >= 8) {
                  var c1x = x + stack.shift();
                  var c1y = y + stack.shift();
                  var c2x = c1x + stack.shift();
                  var c2y = c1y + stack.shift();
                  x = c2x + stack.shift();
                  y = c2y + stack.shift();
                  path.bezierCurveTo(c1x, c1y, c2x, c2y, x, y);
                }
  
                x += stack.shift();
                y += stack.shift();
                path.lineTo(x, y);
                break;
  
              case 25: // rlinecurve
                while (stack.length >= 8) {
                  x += stack.shift();
                  y += stack.shift();
                  path.lineTo(x, y);
                }
  
                var c1x = x + stack.shift();
                var c1y = y + stack.shift();
                var c2x = c1x + stack.shift();
                var c2y = c1y + stack.shift();
                x = c2x + stack.shift();
                y = c2y + stack.shift();
                path.bezierCurveTo(c1x, c1y, c2x, c2y, x, y);
                break;
  
              case 26: // vvcurveto
                if (stack.length % 2) {
                  x += stack.shift();
                }
  
                while (stack.length >= 4) {
                  c1x = x;
                  c1y = y + stack.shift();
                  c2x = c1x + stack.shift();
                  c2y = c1y + stack.shift();
                  x = c2x;
                  y = c2y + stack.shift();
                  path.bezierCurveTo(c1x, c1y, c2x, c2y, x, y);
                }
                break;
  
              case 27: // hhcurveto
                if (stack.length % 2) {
                  y += stack.shift();
                }
  
                while (stack.length >= 4) {
                  c1x = x + stack.shift();
                  c1y = y;
                  c2x = c1x + stack.shift();
                  c2y = c1y + stack.shift();
                  x = c2x + stack.shift();
                  y = c2y;
                  path.bezierCurveTo(c1x, c1y, c2x, c2y, x, y);
                }
                break;
  
              case 28: // shortint
                stack.push(stream.readInt16BE());
                break;
  
              case 29: // callgsubr
                let index = stack.pop() + gsubrsBias;
                subr = gsubrs[index];
                if (subr) {
                  usedGsubrs[index] = true;
                  var p = stream.pos;
                  var e = end;
                  stream.pos = subr.offset;
                  end = subr.offset + subr.length;
                  parse();
                  stream.pos = p;
                  end = e;
                }
                break;
  
              case 30: // vhcurveto
              case 31: // hvcurveto
                phase = op === 31;
                while (stack.length >= 4) {
                  if (phase) {
                    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);
                  } else {
                    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);
                  }
  
                  path.bezierCurveTo(c1x, c1y, c2x, c2y, x, y);
                  phase = !phase;
                }
                break;
  
              case 12:
                op = stream.readUInt8();
                switch (op) {
                  case 3: // and
                    let a = stack.pop();
                    let b = stack.pop();
                    stack.push(a && b ? 1 : 0);
                    break;
  
                  case 4: // or
                    a = stack.pop();
                    b = stack.pop();
                    stack.push(a || b ? 1 : 0);
                    break;
  
                  case 5: // not
                    a = stack.pop();
                    stack.push(a ? 0 : 1);
                    break;
  
                  case 9: // abs
                    a = stack.pop();
                    stack.push(Math.abs(a));
                    break;
  
                  case 10: // add
                    a = stack.pop();
                    b = stack.pop();
                    stack.push(a + b);
                    break;
  
                  case 11: // sub
                    a = stack.pop();
                    b = stack.pop();
                    stack.push(a - b);
                    break;
  
                  case 12: // div
                    a = stack.pop();
                    b = stack.pop();
                    stack.push(a / b);
                    break;
  
                  case 14: // neg
                    a = stack.pop();
                    stack.push(-a);
                    break;
  
                  case 15: // eq
                    a = stack.pop();
                    b = stack.pop();
                    stack.push(a === b ? 1 : 0);
                    break;
  
                  case 18: // drop
                    stack.pop();
                    break;
  
                  case 20: // put
                    let val = stack.pop();
                    let idx = stack.pop();
                    trans[idx] = val;
                    break;
  
                  case 21: // get
                    idx = stack.pop();
                    stack.push(trans[idx] || 0);
                    break;
  
                  case 22: // ifelse
                    let s1 = stack.pop();
                    let s2 = stack.pop();
                    let v1 = stack.pop();
                    let v2 = stack.pop();
                    stack.push(v1 <= v2 ? s1 : s2);
                    break;
  
                  case 23: // random
                    stack.push(Math.random());
                    break;
  
                  case 24: // mul
                    a = stack.pop();
                    b = stack.pop();
                    stack.push(a * b);
                    break;
  
                  case 26: // sqrt
                    a = stack.pop();
                    stack.push(Math.sqrt(a));
                    break;
  
                  case 27: // dup
                    a = stack.pop();
                    stack.push(a, a);
                    break;
  
                  case 28: // exch
                    a = stack.pop();
                    b = stack.pop();
                    stack.push(b, a);
                    break;
  
                  case 29: // index
                    idx = stack.pop();
                    if (idx < 0) {
                      idx = 0;
                    } else if (idx > stack.length - 1) {
                      idx = stack.length - 1;
                    }
  
                    stack.push(stack[idx]);
                    break;
  
                  case 30: // roll
                    let n = stack.pop();
                    let j = stack.pop();
  
                    if (j >= 0) {
                      while (j > 0) {
                        var t = stack[n - 1];
                        for (let i = n - 2; i >= 0; i--) {
                          stack[i + 1] = stack[i];
                        }
  
                        stack[0] = t;
                        j--;
                      }
                    } else {
                      while (j < 0) {
                        var t = stack[0];
                        for (let i = 0; i <= n; i++) {
                          stack[i] = stack[i + 1];
                        }
  
                        stack[n - 1] = t;
                        j++;
                      }
                    }
                    break;
  
                  case 34: // hflex
                    c1x = x + stack.shift();
                    c1y = y;
                    c2x = c1x + stack.shift();
                    c2y = c1y + stack.shift();
                    let c3x = c2x + stack.shift();
                    let c3y = c2y;
                    let c4x = c3x + stack.shift();
                    let c4y = c3y;
                    let c5x = c4x + stack.shift();
                    let c5y = c4y;
                    let c6x = c5x + stack.shift();
                    let c6y = c5y;
                    x = c6x;
                    y = c6y;
  
                    path.bezierCurveTo(c1x, c1y, c2x, c2y, c3x, c3y);
                    path.bezierCurveTo(c4x, c4y, c5x, c5y, c6x, c6y);
                    break;
  
                  case 35: // flex
                    let pts = [];
  
                    for (let i = 0; i <= 5; i++) {
                      x += stack.shift();
                      y += stack.shift();
                      pts.push(x, y);
                    }
  
                    path.bezierCurveTo(...pts.slice(0, 6));
                    path.bezierCurveTo(...pts.slice(6));
                    stack.shift(); // fd
                    break;
  
                  case 36: // hflex1
                    c1x = x + stack.shift();
                    c1y = y + stack.shift();
                    c2x = c1x + stack.shift();
                    c2y = c1y + stack.shift();
                    c3x = c2x + stack.shift();
                    c3y = c2y;
                    c4x = c3x + stack.shift();
                    c4y = c3y;
                    c5x = c4x + stack.shift();
                    c5y = c4y + stack.shift();
                    c6x = c5x + stack.shift();
                    c6y = c5y;
                    x = c6x;
                    y = c6y;
  
                    path.bezierCurveTo(c1x, c1y, c2x, c2y, c3x, c3y);
                    path.bezierCurveTo(c4x, c4y, c5x, c5y, c6x, c6y);
                    break;
  
                  case 37: // flex1
                    let startx = x;
                    let starty = y;
  
                    pts = [];
                    for (let i = 0; i <= 4; i++) {
                      x += stack.shift();
                      y += stack.shift();
                      pts.push(x, y);
                    }
  
                    if (Math.abs(x - startx) > Math.abs(y - starty)) { // horizontal
                      x += stack.shift();
                      y = starty;
                    } else {
                      x = startx;
                      y += stack.shift();
                    }
  
                    pts.push(x, y);
                    path.bezierCurveTo(...pts.slice(0, 6));
                    path.bezierCurveTo(...pts.slice(6));
                    break;
  
                  default:
                    throw new Error(`Unknown op: 12 ${op}`);
                }
                break;
  
              default:
                throw new Error(`Unknown op: ${op}`);
            }
  
          } else if (op < 247) {
            stack.push(op - 139);
          } else if (op < 251) {
            var b1 = stream.readUInt8();
            stack.push((op - 247) * 256 + b1 + 108);
          } else if (op < 255) {
            var b1 = stream.readUInt8();
            stack.push(-(op - 251) * 256 - b1 - 108);
          } else {
            stack.push(stream.readInt32BE() / 65536);
          }
        }
      };
  
      parse();
  
      if (open) {
        path.closePath();
      }
  
      return path;
    }
  }
  