Browse Source

PDF.js version 1.4.192 - See mozilla/pdf.js@2dd03e178594ff9958819114c0e5f097e9e1f1ec

Pdf Bot 9 years ago
parent
commit
e7ef89390b
5 changed files with 6582 additions and 5182 deletions
  1. 1 1
      bower.json
  2. 5657 4957
      build/pdf.combined.js
  3. 2 2
      build/pdf.js
  4. 921 221
      build/pdf.worker.js
  5. 1 1
      package.json

+ 1 - 1
bower.json

@@ -1,6 +1,6 @@
 {
   "name": "pdfjs-dist",
-  "version": "1.4.190",
+  "version": "1.4.192",
   "main": [
     "build/pdf.js",
     "build/pdf.worker.js"

+ 5657 - 4957
build/pdf.combined.js

@@ -28,8 +28,8 @@ factory((root.pdfjsDistBuildPdfCombined = {}));
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.4.190';
-var pdfjsBuild = '34aa915';
+var pdfjsVersion = '1.4.192';
+var pdfjsBuild = '2dd03e1';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
@@ -30967,5729 +30967,6429 @@ exports.IdentityCMap = IdentityCMap;
 
 (function (root, factory) {
   {
-    factory((root.pdfjsCoreFonts = {}), root.pdfjsSharedUtil,
-      root.pdfjsCorePrimitives, root.pdfjsCoreStream, root.pdfjsCoreParser,
-      root.pdfjsCoreGlyphList, root.pdfjsCoreCharsets,
-      root.pdfjsCoreFontRenderer, root.pdfjsCoreEncodings,
-      root.pdfjsCoreStandardFonts, root.pdfjsCoreUnicode,
-      root.pdfjsCoreType1Parser, root.pdfjsCoreCFFParser);
+    factory((root.pdfjsCorePsParser = {}), root.pdfjsSharedUtil,
+      root.pdfjsCoreParser);
   }
-}(this, function (exports, sharedUtil, corePrimitives, coreStream, coreParser,
-                  coreGlyphList, coreCharsets, coreFontRenderer,
-                  coreEncodings, coreStandardFonts, coreUnicode,
-                  coreType1Parser, coreCFFParser) {
+}(this, function (exports, sharedUtil, coreParser) {
 
-var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
-var FontType = sharedUtil.FontType;
-var Util = sharedUtil.Util;
-var assert = sharedUtil.assert;
-var bytesToString = sharedUtil.bytesToString;
 var error = sharedUtil.error;
-var info = sharedUtil.info;
-var isArray = sharedUtil.isArray;
-var isInt = sharedUtil.isInt;
-var isNum = sharedUtil.isNum;
-var readUint32 = sharedUtil.readUint32;
-var shadow = sharedUtil.shadow;
-var stringToBytes = sharedUtil.stringToBytes;
-var string32 = sharedUtil.string32;
-var warn = sharedUtil.warn;
-var MissingDataException = sharedUtil.MissingDataException;
-var Stream = coreStream.Stream;
+var EOF = coreParser.EOF;
 var Lexer = coreParser.Lexer;
-var getGlyphsUnicode = coreGlyphList.getGlyphsUnicode;
-var getDingbatsGlyphsUnicode = coreGlyphList.getDingbatsGlyphsUnicode;
-var ISOAdobeCharset = coreCharsets.ISOAdobeCharset;
-var ExpertCharset = coreCharsets.ExpertCharset;
-var ExpertSubsetCharset = coreCharsets.ExpertSubsetCharset;
-var FontRendererFactory = coreFontRenderer.FontRendererFactory;
-var WinAnsiEncoding = coreEncodings.WinAnsiEncoding;
-var StandardEncoding = coreEncodings.StandardEncoding;
-var MacRomanEncoding = coreEncodings.MacRomanEncoding;
-var SymbolSetEncoding = coreEncodings.SymbolSetEncoding;
-var ZapfDingbatsEncoding = coreEncodings.ZapfDingbatsEncoding;
-var ExpertEncoding = coreEncodings.ExpertEncoding;
-var getEncoding = coreEncodings.getEncoding;
-var getStdFontMap = coreStandardFonts.getStdFontMap;
-var getNonStdFontMap = coreStandardFonts.getNonStdFontMap;
-var getGlyphMapForStandardFonts = coreStandardFonts.getGlyphMapForStandardFonts;
-var getSupplementalGlyphMapForArialBlack =
-  coreStandardFonts.getSupplementalGlyphMapForArialBlack;
-var getUnicodeRangeFor = coreUnicode.getUnicodeRangeFor;
-var mapSpecialUnicodeValues = coreUnicode.mapSpecialUnicodeValues;
-var getUnicodeForGlyph = coreUnicode.getUnicodeForGlyph;
-var Type1Parser = coreType1Parser.Type1Parser;
-var CFFStandardStrings = coreCFFParser.CFFStandardStrings;
-var CFFParser = coreCFFParser.CFFParser;
-var CFFCompiler = coreCFFParser.CFFCompiler;
-var CFF = coreCFFParser.CFF;
-var CFFHeader = coreCFFParser.CFFHeader;
-var CFFTopDict = coreCFFParser.CFFTopDict;
-var CFFPrivateDict = coreCFFParser.CFFPrivateDict;
-var CFFStrings = coreCFFParser.CFFStrings;
-var CFFIndex = coreCFFParser.CFFIndex;
-var CFFCharset = coreCFFParser.CFFCharset;
 
-// Unicode Private Use Area
-var PRIVATE_USE_OFFSET_START = 0xE000;
-var PRIVATE_USE_OFFSET_END = 0xF8FF;
-var SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = false;
+var PostScriptParser = (function PostScriptParserClosure() {
+  function PostScriptParser(lexer) {
+    this.lexer = lexer;
+    this.operators = [];
+    this.token = null;
+    this.prev = null;
+  }
+  PostScriptParser.prototype = {
+    nextToken: function PostScriptParser_nextToken() {
+      this.prev = this.token;
+      this.token = this.lexer.getToken();
+    },
+    accept: function PostScriptParser_accept(type) {
+      if (this.token.type === type) {
+        this.nextToken();
+        return true;
+      }
+      return false;
+    },
+    expect: function PostScriptParser_expect(type) {
+      if (this.accept(type)) {
+        return true;
+      }
+      error('Unexpected symbol: found ' + this.token.type + ' expected ' +
+        type + '.');
+    },
+    parse: function PostScriptParser_parse() {
+      this.nextToken();
+      this.expect(PostScriptTokenTypes.LBRACE);
+      this.parseBlock();
+      this.expect(PostScriptTokenTypes.RBRACE);
+      return this.operators;
+    },
+    parseBlock: function PostScriptParser_parseBlock() {
+      while (true) {
+        if (this.accept(PostScriptTokenTypes.NUMBER)) {
+          this.operators.push(this.prev.value);
+        } else if (this.accept(PostScriptTokenTypes.OPERATOR)) {
+          this.operators.push(this.prev.value);
+        } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
+          this.parseCondition();
+        } else {
+          return;
+        }
+      }
+    },
+    parseCondition: function PostScriptParser_parseCondition() {
+      // Add two place holders that will be updated later
+      var conditionLocation = this.operators.length;
+      this.operators.push(null, null);
 
-// PDF Glyph Space Units are one Thousandth of a TextSpace Unit
-// except for Type 3 fonts
-var PDF_GLYPH_SPACE_UNITS = 1000;
+      this.parseBlock();
+      this.expect(PostScriptTokenTypes.RBRACE);
+      if (this.accept(PostScriptTokenTypes.IF)) {
+        // The true block is right after the 'if' so it just falls through on
+        // true else it jumps and skips the true block.
+        this.operators[conditionLocation] = this.operators.length;
+        this.operators[conditionLocation + 1] = 'jz';
+      } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
+        var jumpLocation = this.operators.length;
+        this.operators.push(null, null);
+        var endOfTrue = this.operators.length;
+        this.parseBlock();
+        this.expect(PostScriptTokenTypes.RBRACE);
+        this.expect(PostScriptTokenTypes.IFELSE);
+        // The jump is added at the end of the true block to skip the false
+        // block.
+        this.operators[jumpLocation] = this.operators.length;
+        this.operators[jumpLocation + 1] = 'j';
 
-// Accented charactars are not displayed properly on Windows, using this flag
-// to control analysis of seac charstrings.
-var SEAC_ANALYSIS_ENABLED = false;
+        this.operators[conditionLocation] = endOfTrue;
+        this.operators[conditionLocation + 1] = 'jz';
+      } else {
+        error('PS Function: error parsing conditional.');
+      }
+    }
+  };
+  return PostScriptParser;
+})();
 
-var FontFlags = {
-  FixedPitch: 1,
-  Serif: 2,
-  Symbolic: 4,
-  Script: 8,
-  Nonsymbolic: 32,
-  Italic: 64,
-  AllCap: 65536,
-  SmallCap: 131072,
-  ForceBold: 262144
+var PostScriptTokenTypes = {
+  LBRACE: 0,
+  RBRACE: 1,
+  NUMBER: 2,
+  OPERATOR: 3,
+  IF: 4,
+  IFELSE: 5
 };
 
-var MacStandardGlyphOrdering = [
-  '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl',
-  'numbersign', 'dollar', 'percent', 'ampersand', 'quotesingle', 'parenleft',
-  'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash',
-  'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
-  'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at',
-  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
-  'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft',
-  'backslash', 'bracketright', 'asciicircum', 'underscore', 'grave', 'a', 'b',
-  'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-  'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
-  'asciitilde', 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde',
-  'Odieresis', 'Udieresis', 'aacute', 'agrave', 'acircumflex', 'adieresis',
-  'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis',
-  'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve',
-  'ocircumflex', 'odieresis', 'otilde', 'uacute', 'ugrave', 'ucircumflex',
-  'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', 'bullet',
-  'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute',
-  'dieresis', 'notequal', 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal',
-  'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', 'product', 'pi',
-  'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash',
-  'questiondown', 'exclamdown', 'logicalnot', 'radical', 'florin',
-  'approxequal', 'Delta', 'guillemotleft', 'guillemotright', 'ellipsis',
-  'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash',
-  'emdash', 'quotedblleft', 'quotedblright', 'quoteleft', 'quoteright',
-  'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', 'currency',
-  'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered',
-  'quotesinglbase', 'quotedblbase', 'perthousand', 'Acircumflex',
-  'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex',
-  'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute',
-  'Ucircumflex', 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron',
-  'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
-  'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar',
-  'Eth', 'eth', 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply',
-  'onesuperior', 'twosuperior', 'threesuperior', 'onehalf', 'onequarter',
-  'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla',
-  'scedilla', 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat'];
-
-function adjustWidths(properties) {
-  if (!properties.fontMatrix) {
-    return;
-  }
-  if (properties.fontMatrix[0] === FONT_IDENTITY_MATRIX[0]) {
-    return;
-  }
-  // adjusting width to fontMatrix scale
-  var scale = 0.001 / properties.fontMatrix[0];
-  var glyphsWidths = properties.widths;
-  for (var glyph in glyphsWidths) {
-    glyphsWidths[glyph] *= scale;
-  }
-  properties.defaultWidth *= scale;
-}
-
-function getFontType(type, subtype) {
-  switch (type) {
-    case 'Type1':
-      return subtype === 'Type1C' ? FontType.TYPE1C : FontType.TYPE1;
-    case 'CIDFontType0':
-      return subtype === 'CIDFontType0C' ? FontType.CIDFONTTYPE0C :
-        FontType.CIDFONTTYPE0;
-    case 'OpenType':
-      return FontType.OPENTYPE;
-    case 'TrueType':
-      return FontType.TRUETYPE;
-    case 'CIDFontType2':
-      return FontType.CIDFONTTYPE2;
-    case 'MMType1':
-      return FontType.MMTYPE1;
-    case 'Type0':
-      return FontType.TYPE0;
-    default:
-      return FontType.UNKNOWN;
+var PostScriptToken = (function PostScriptTokenClosure() {
+  function PostScriptToken(type, value) {
+    this.type = type;
+    this.value = value;
   }
-}
 
-var Glyph = (function GlyphClosure() {
-  function Glyph(fontChar, unicode, accent, width, vmetric, operatorListId,
-                 isSpace, isInFont) {
-    this.fontChar = fontChar;
-    this.unicode = unicode;
-    this.accent = accent;
-    this.width = width;
-    this.vmetric = vmetric;
-    this.operatorListId = operatorListId;
-    this.isSpace = isSpace;
-    this.isInFont = isInFont;
-  }
+  var opCache = Object.create(null);
 
-  Glyph.prototype.matchesForCache = function(fontChar, unicode, accent, width,
-                                             vmetric, operatorListId, isSpace,
-                                             isInFont) {
-    return this.fontChar === fontChar &&
-           this.unicode === unicode &&
-           this.accent === accent &&
-           this.width === width &&
-           this.vmetric === vmetric &&
-           this.operatorListId === operatorListId &&
-           this.isSpace === isSpace &&
-           this.isInFont === isInFont;
+  PostScriptToken.getOperator = function PostScriptToken_getOperator(op) {
+    var opValue = opCache[op];
+    if (opValue) {
+      return opValue;
+    }
+    return opCache[op] = new PostScriptToken(PostScriptTokenTypes.OPERATOR, op);
   };
 
-  return Glyph;
+  PostScriptToken.LBRACE = new PostScriptToken(PostScriptTokenTypes.LBRACE,
+    '{');
+  PostScriptToken.RBRACE = new PostScriptToken(PostScriptTokenTypes.RBRACE,
+    '}');
+  PostScriptToken.IF = new PostScriptToken(PostScriptTokenTypes.IF, 'IF');
+  PostScriptToken.IFELSE = new PostScriptToken(PostScriptTokenTypes.IFELSE,
+    'IFELSE');
+  return PostScriptToken;
 })();
 
-var ToUnicodeMap = (function ToUnicodeMapClosure() {
-  function ToUnicodeMap(cmap) {
-    // The elements of this._map can be integers or strings, depending on how
-    // |cmap| was created.
-    this._map = cmap;
-  }
+var PostScriptLexer = (function PostScriptLexerClosure() {
+  function PostScriptLexer(stream) {
+    this.stream = stream;
+    this.nextChar();
 
-  ToUnicodeMap.prototype = {
-    get length() {
-      return this._map.length;
+    this.strBuf = [];
+  }
+  PostScriptLexer.prototype = {
+    nextChar: function PostScriptLexer_nextChar() {
+      return (this.currentChar = this.stream.getByte());
     },
+    getToken: function PostScriptLexer_getToken() {
+      var comment = false;
+      var ch = this.currentChar;
 
-    forEach: function(callback) {
-      for (var charCode in this._map) {
-        callback(charCode, this._map[charCode].charCodeAt(0));
-      }
-    },
+      // skip comments
+      while (true) {
+        if (ch < 0) {
+          return EOF;
+        }
 
-    has: function(i) {
-      return this._map[i] !== undefined;
-    },
+        if (comment) {
+          if (ch === 0x0A || ch === 0x0D) {
+            comment = false;
+          }
+        } else if (ch === 0x25) { // '%'
+          comment = true;
+        } else if (!Lexer.isSpace(ch)) {
+          break;
+        }
+        ch = this.nextChar();
+      }
+      switch (ch | 0) {
+        case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: // '0'-'4'
+        case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: // '5'-'9'
+        case 0x2B: case 0x2D: case 0x2E: // '+', '-', '.'
+          return new PostScriptToken(PostScriptTokenTypes.NUMBER,
+                                     this.getNumber());
+        case 0x7B: // '{'
+          this.nextChar();
+          return PostScriptToken.LBRACE;
+        case 0x7D: // '}'
+          this.nextChar();
+          return PostScriptToken.RBRACE;
+      }
+      // operator
+      var strBuf = this.strBuf;
+      strBuf.length = 0;
+      strBuf[0] = String.fromCharCode(ch);
 
-    get: function(i) {
-      return this._map[i];
+      while ((ch = this.nextChar()) >= 0 && // and 'A'-'Z', 'a'-'z'
+             ((ch >= 0x41 && ch <= 0x5A) || (ch >= 0x61 && ch <= 0x7A))) {
+        strBuf.push(String.fromCharCode(ch));
+      }
+      var str = strBuf.join('');
+      switch (str.toLowerCase()) {
+        case 'if':
+          return PostScriptToken.IF;
+        case 'ifelse':
+          return PostScriptToken.IFELSE;
+        default:
+          return PostScriptToken.getOperator(str);
+      }
     },
+    getNumber: function PostScriptLexer_getNumber() {
+      var ch = this.currentChar;
+      var strBuf = this.strBuf;
+      strBuf.length = 0;
+      strBuf[0] = String.fromCharCode(ch);
 
-    charCodeOf: function(v) {
-      return this._map.indexOf(v);
+      while ((ch = this.nextChar()) >= 0) {
+        if ((ch >= 0x30 && ch <= 0x39) || // '0'-'9'
+            ch === 0x2D || ch === 0x2E) { // '-', '.'
+          strBuf.push(String.fromCharCode(ch));
+        } else {
+          break;
+        }
+      }
+      var value = parseFloat(strBuf.join(''));
+      if (isNaN(value)) {
+        error('Invalid floating point number: ' + value);
+      }
+      return value;
     }
   };
-
-  return ToUnicodeMap;
+  return PostScriptLexer;
 })();
 
-var IdentityToUnicodeMap = (function IdentityToUnicodeMapClosure() {
-  function IdentityToUnicodeMap(firstChar, lastChar) {
-    this.firstChar = firstChar;
-    this.lastChar = lastChar;
+exports.PostScriptLexer = PostScriptLexer;
+exports.PostScriptParser = PostScriptParser;
+}));
+
+
+(function (root, factory) {
+  {
+    factory((root.pdfjsCoreType1Parser = {}), root.pdfjsSharedUtil,
+      root.pdfjsCoreStream, root.pdfjsCoreParser, root.pdfjsCoreEncodings);
   }
+}(this, function (exports, sharedUtil, coreStream, coreParser, coreEncodings) {
 
-  IdentityToUnicodeMap.prototype = {
-    get length() {
-      return (this.lastChar + 1) - this.firstChar;
-    },
+var warn = sharedUtil.warn;
+var Stream = coreStream.Stream;
+var Lexer = coreParser.Lexer;
+var getEncoding = coreEncodings.getEncoding;
 
-    forEach: function (callback) {
-      for (var i = this.firstChar, ii = this.lastChar; i <= ii; i++) {
-        callback(i, i);
-      }
-    },
+// Hinting is currently disabled due to unknown problems on windows
+// in tracemonkey and various other pdfs with type1 fonts.
+var HINTING_ENABLED = false;
 
-    has: function (i) {
-      return this.firstChar <= i && i <= this.lastChar;
-    },
+/*
+ * CharStrings are encoded following the the CharString Encoding sequence
+ * describe in Chapter 6 of the "Adobe Type1 Font Format" specification.
+ * The value in a byte indicates a command, a number, or subsequent bytes
+ * that are to be interpreted in a special way.
+ *
+ * CharString Number Encoding:
+ *  A CharString byte containing the values from 32 through 255 inclusive
+ *  indicate an integer. These values are decoded in four ranges.
+ *
+ * 1. A CharString byte containing a value, v, between 32 and 246 inclusive,
+ * indicate the integer v - 139. Thus, the integer values from -107 through
+ * 107 inclusive may be encoded in single byte.
+ *
+ * 2. A CharString byte containing a value, v, between 247 and 250 inclusive,
+ * indicates an integer involving the next byte, w, according to the formula:
+ * [(v - 247) x 256] + w + 108
+ *
+ * 3. A CharString byte containing a value, v, between 251 and 254 inclusive,
+ * indicates an integer involving the next byte, w, according to the formula:
+ * -[(v - 251) * 256] - w - 108
+ *
+ * 4. A CharString containing the value 255 indicates that the next 4 bytes
+ * are a two complement signed integer. The first of these bytes contains the
+ * highest order bits, the second byte contains the next higher order bits
+ * and the fourth byte contain the lowest order bits.
+ *
+ *
+ * CharString Command Encoding:
+ *  CharStrings commands are encoded in 1 or 2 bytes.
+ *
+ *  Single byte commands are encoded in 1 byte that contains a value between
+ *  0 and 31 inclusive.
+ *  If a command byte contains the value 12, then the value in the next byte
+ *  indicates a command. This "escape" mechanism allows many extra commands
+ * to be encoded and this encoding technique helps to minimize the length of
+ * the charStrings.
+ */
+var Type1CharString = (function Type1CharStringClosure() {
+  var COMMAND_MAP = {
+    'hstem': [1],
+    'vstem': [3],
+    'vmoveto': [4],
+    'rlineto': [5],
+    'hlineto': [6],
+    'vlineto': [7],
+    'rrcurveto': [8],
+    'callsubr': [10],
+    'flex': [12, 35],
+    'drop' : [12, 18],
+    'endchar': [14],
+    'rmoveto': [21],
+    'hmoveto': [22],
+    'vhcurveto': [30],
+    'hvcurveto': [31]
+  };
 
-    get: function (i) {
-      if (this.firstChar <= i && i <= this.lastChar) {
-        return String.fromCharCode(i);
+  function Type1CharString() {
+    this.width = 0;
+    this.lsb = 0;
+    this.flexing = false;
+    this.output = [];
+    this.stack = [];
+  }
+
+  Type1CharString.prototype = {
+    convert: function Type1CharString_convert(encoded, subrs,
+                                              seacAnalysisEnabled) {
+      var count = encoded.length;
+      var error = false;
+      var wx, sbx, subrNumber;
+      for (var i = 0; i < count; i++) {
+        var value = encoded[i];
+        if (value < 32) {
+          if (value === 12) {
+            value = (value << 8) + encoded[++i];
+          }
+          switch (value) {
+            case 1: // hstem
+              if (!HINTING_ENABLED) {
+                this.stack = [];
+                break;
+              }
+              error = this.executeCommand(2, COMMAND_MAP.hstem);
+              break;
+            case 3: // vstem
+              if (!HINTING_ENABLED) {
+                this.stack = [];
+                break;
+              }
+              error = this.executeCommand(2, COMMAND_MAP.vstem);
+              break;
+            case 4: // vmoveto
+              if (this.flexing) {
+                if (this.stack.length < 1) {
+                  error = true;
+                  break;
+                }
+                // Add the dx for flex and but also swap the values so they are
+                // the right order.
+                var dy = this.stack.pop();
+                this.stack.push(0, dy);
+                break;
+              }
+              error = this.executeCommand(1, COMMAND_MAP.vmoveto);
+              break;
+            case 5: // rlineto
+              error = this.executeCommand(2, COMMAND_MAP.rlineto);
+              break;
+            case 6: // hlineto
+              error = this.executeCommand(1, COMMAND_MAP.hlineto);
+              break;
+            case 7: // vlineto
+              error = this.executeCommand(1, COMMAND_MAP.vlineto);
+              break;
+            case 8: // rrcurveto
+              error = this.executeCommand(6, COMMAND_MAP.rrcurveto);
+              break;
+            case 9: // closepath
+              // closepath is a Type1 command that does not take argument and is
+              // useless in Type2 and it can simply be ignored.
+              this.stack = [];
+              break;
+            case 10: // callsubr
+              if (this.stack.length < 1) {
+                error = true;
+                break;
+              }
+              subrNumber = this.stack.pop();
+              error = this.convert(subrs[subrNumber], subrs,
+                                   seacAnalysisEnabled);
+              break;
+            case 11: // return
+              return error;
+            case 13: // hsbw
+              if (this.stack.length < 2) {
+                error = true;
+                break;
+              }
+              // To convert to type2 we have to move the width value to the
+              // first part of the charstring and then use hmoveto with lsb.
+              wx = this.stack.pop();
+              sbx = this.stack.pop();
+              this.lsb = sbx;
+              this.width = wx;
+              this.stack.push(wx, sbx);
+              error = this.executeCommand(2, COMMAND_MAP.hmoveto);
+              break;
+            case 14: // endchar
+              this.output.push(COMMAND_MAP.endchar[0]);
+              break;
+            case 21: // rmoveto
+              if (this.flexing) {
+                break;
+              }
+              error = this.executeCommand(2, COMMAND_MAP.rmoveto);
+              break;
+            case 22: // hmoveto
+              if (this.flexing) {
+                // Add the dy for flex.
+                this.stack.push(0);
+                break;
+              }
+              error = this.executeCommand(1, COMMAND_MAP.hmoveto);
+              break;
+            case 30: // vhcurveto
+              error = this.executeCommand(4, COMMAND_MAP.vhcurveto);
+              break;
+            case 31: // hvcurveto
+              error = this.executeCommand(4, COMMAND_MAP.hvcurveto);
+              break;
+            case (12 << 8) + 0: // dotsection
+              // dotsection is a Type1 command to specify some hinting feature
+              // for dots that do not take a parameter and it can safely be
+              // ignored for Type2.
+              this.stack = [];
+              break;
+            case (12 << 8) + 1: // vstem3
+              if (!HINTING_ENABLED) {
+                this.stack = [];
+                break;
+              }
+              // [vh]stem3 are Type1 only and Type2 supports [vh]stem with
+              // multiple parameters, so instead of returning [vh]stem3 take a
+              // shortcut and return [vhstem] instead.
+              error = this.executeCommand(2, COMMAND_MAP.vstem);
+              break;
+            case (12 << 8) + 2: // hstem3
+              if (!HINTING_ENABLED) {
+                 this.stack = [];
+                break;
+              }
+              // See vstem3.
+              error = this.executeCommand(2, COMMAND_MAP.hstem);
+              break;
+            case (12 << 8) + 6: // seac
+              // seac is like type 2's special endchar but it doesn't use the
+              // first argument asb, so remove it.
+              if (seacAnalysisEnabled) {
+                this.seac = this.stack.splice(-4, 4);
+                error = this.executeCommand(0, COMMAND_MAP.endchar);
+              } else {
+                error = this.executeCommand(4, COMMAND_MAP.endchar);
+              }
+              break;
+            case (12 << 8) + 7: // sbw
+              if (this.stack.length < 4) {
+                error = true;
+                break;
+              }
+              // To convert to type2 we have to move the width value to the
+              // first part of the charstring and then use rmoveto with
+              // (dx, dy). The height argument will not be used for vmtx and
+              // vhea tables reconstruction -- ignoring it.
+              var wy = this.stack.pop();
+              wx = this.stack.pop();
+              var sby = this.stack.pop();
+              sbx = this.stack.pop();
+              this.lsb = sbx;
+              this.width = wx;
+              this.stack.push(wx, sbx, sby);
+              error = this.executeCommand(3, COMMAND_MAP.rmoveto);
+              break;
+            case (12 << 8) + 12: // div
+              if (this.stack.length < 2) {
+                error = true;
+                break;
+              }
+              var num2 = this.stack.pop();
+              var num1 = this.stack.pop();
+              this.stack.push(num1 / num2);
+              break;
+            case (12 << 8) + 16: // callothersubr
+              if (this.stack.length < 2) {
+                error = true;
+                break;
+              }
+              subrNumber = this.stack.pop();
+              var numArgs = this.stack.pop();
+              if (subrNumber === 0 && numArgs === 3) {
+                var flexArgs = this.stack.splice(this.stack.length - 17, 17);
+                this.stack.push(
+                  flexArgs[2] + flexArgs[0], // bcp1x + rpx
+                  flexArgs[3] + flexArgs[1], // bcp1y + rpy
+                  flexArgs[4], // bcp2x
+                  flexArgs[5], // bcp2y
+                  flexArgs[6], // p2x
+                  flexArgs[7], // p2y
+                  flexArgs[8], // bcp3x
+                  flexArgs[9], // bcp3y
+                  flexArgs[10], // bcp4x
+                  flexArgs[11], // bcp4y
+                  flexArgs[12], // p3x
+                  flexArgs[13], // p3y
+                  flexArgs[14] // flexDepth
+                  // 15 = finalx unused by flex
+                  // 16 = finaly unused by flex
+                );
+                error = this.executeCommand(13, COMMAND_MAP.flex, true);
+                this.flexing = false;
+                this.stack.push(flexArgs[15], flexArgs[16]);
+              } else if (subrNumber === 1 && numArgs === 0) {
+                this.flexing = true;
+              }
+              break;
+            case (12 << 8) + 17: // pop
+              // Ignore this since it is only used with othersubr.
+              break;
+            case (12 << 8) + 33: // setcurrentpoint
+              // Ignore for now.
+              this.stack = [];
+              break;
+            default:
+              warn('Unknown type 1 charstring command of "' + value + '"');
+              break;
+          }
+          if (error) {
+            break;
+          }
+          continue;
+        } else if (value <= 246) {
+          value = value - 139;
+        } else if (value <= 250) {
+          value = ((value - 247) * 256) + encoded[++i] + 108;
+        } else if (value <= 254) {
+          value = -((value - 251) * 256) - encoded[++i] - 108;
+        } else {
+          value = (encoded[++i] & 0xff) << 24 | (encoded[++i] & 0xff) << 16 |
+                  (encoded[++i] & 0xff) << 8 | (encoded[++i] & 0xff) << 0;
+        }
+        this.stack.push(value);
       }
-      return undefined;
+      return error;
     },
 
-    charCodeOf: function (v) {
-      return (isInt(v) && v >= this.firstChar && v <= this.lastChar) ? v : -1;
+    executeCommand: function(howManyArgs, command, keepStack) {
+      var stackLength = this.stack.length;
+      if (howManyArgs > stackLength) {
+        return true;
+      }
+      var start = stackLength - howManyArgs;
+      for (var i = start; i < stackLength; i++) {
+        var value = this.stack[i];
+        if (value === (value | 0)) { // int
+          this.output.push(28, (value >> 8) & 0xff, value & 0xff);
+        } else { // fixed point
+          value = (65536 * value) | 0;
+          this.output.push(255,
+                           (value >> 24) & 0xFF,
+                           (value >> 16) & 0xFF,
+                           (value >> 8) & 0xFF,
+                           value & 0xFF);
+        }
+      }
+      this.output.push.apply(this.output, command);
+      if (keepStack) {
+        this.stack.splice(start, howManyArgs);
+      } else {
+        this.stack.length = 0;
+      }
+      return false;
     }
   };
 
-  return IdentityToUnicodeMap;
+  return Type1CharString;
 })();
 
-var OpenTypeFileBuilder = (function OpenTypeFileBuilderClosure() {
-  function writeInt16(dest, offset, num) {
-    dest[offset] = (num >> 8) & 0xFF;
-    dest[offset + 1] = num & 0xFF;
-  }
+/*
+ * Type1Parser encapsulate the needed code for parsing a Type1 font
+ * program. Some of its logic depends on the Type2 charstrings
+ * structure.
+ * Note: this doesn't really parse the font since that would require evaluation
+ * of PostScript, but it is possible in most cases to extract what we need
+ * without a full parse.
+ */
+var Type1Parser = (function Type1ParserClosure() {
+  /*
+   * Decrypt a Sequence of Ciphertext Bytes to Produce the Original Sequence
+   * of Plaintext Bytes. The function took a key as a parameter which can be
+   * for decrypting the eexec block of for decoding charStrings.
+   */
+  var EEXEC_ENCRYPT_KEY = 55665;
+  var CHAR_STRS_ENCRYPT_KEY = 4330;
 
-  function writeInt32(dest, offset, num) {
-    dest[offset] = (num >> 24) & 0xFF;
-    dest[offset + 1] = (num >> 16) & 0xFF;
-    dest[offset + 2] = (num >> 8) & 0xFF;
-    dest[offset + 3] = num & 0xFF;
+  function isHexDigit(code) {
+    return code >= 48 && code <= 57 || // '0'-'9'
+           code >= 65 && code <= 70 || // 'A'-'F'
+           code >= 97 && code <= 102;  // 'a'-'f'
   }
 
-  function writeData(dest, offset, data) {
-    var i, ii;
-    if (data instanceof Uint8Array) {
-      dest.set(data, offset);
-    } else if (typeof data === 'string') {
-      for (i = 0, ii = data.length; i < ii; i++) {
-        dest[offset++] = data.charCodeAt(i) & 0xFF;
+  function decrypt(data, key, discardNumber) {
+    if (discardNumber >= data.length) {
+      return new Uint8Array(0);
+    }
+    var r = key | 0, c1 = 52845, c2 = 22719, i, j;
+    for (i = 0; i < discardNumber; i++) {
+      r = ((data[i] + r) * c1 + c2) & ((1 << 16) - 1);
+    }
+    var count = data.length - discardNumber;
+    var decrypted = new Uint8Array(count);
+    for (i = discardNumber, j = 0; j < count; i++, j++) {
+      var value = data[i];
+      decrypted[j] = value ^ (r >> 8);
+      r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
+    }
+    return decrypted;
+  }
+
+  function decryptAscii(data, key, discardNumber) {
+    var r = key | 0, c1 = 52845, c2 = 22719;
+    var count = data.length, maybeLength = count >>> 1;
+    var decrypted = new Uint8Array(maybeLength);
+    var i, j;
+    for (i = 0, j = 0; i < count; i++) {
+      var digit1 = data[i];
+      if (!isHexDigit(digit1)) {
+        continue;
       }
-    } else {
-      // treating everything else as array
-      for (i = 0, ii = data.length; i < ii; i++) {
-        dest[offset++] = data[i] & 0xFF;
+      i++;
+      var digit2;
+      while (i < count && !isHexDigit(digit2 = data[i])) {
+        i++;
+      }
+      if (i < count) {
+        var value = parseInt(String.fromCharCode(digit1, digit2), 16);
+        decrypted[j++] = value ^ (r >> 8);
+        r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
       }
     }
+    return Array.prototype.slice.call(decrypted, discardNumber, j);
   }
 
-  function OpenTypeFileBuilder(sfnt) {
-    this.sfnt = sfnt;
-    this.tables = Object.create(null);
+  function isSpecial(c) {
+    return c === 0x2F || // '/'
+           c === 0x5B || c === 0x5D || // '[', ']'
+           c === 0x7B || c === 0x7D || // '{', '}'
+           c === 0x28 || c === 0x29; // '(', ')'
   }
 
-  OpenTypeFileBuilder.getSearchParams =
-      function OpenTypeFileBuilder_getSearchParams(entriesCount, entrySize) {
-    var maxPower2 = 1, log2 = 0;
-    while ((maxPower2 ^ entriesCount) > maxPower2) {
-      maxPower2 <<= 1;
-      log2++;
+  function Type1Parser(stream, encrypted, seacAnalysisEnabled) {
+    if (encrypted) {
+      var data = stream.getBytes();
+      var isBinary = !(isHexDigit(data[0]) && isHexDigit(data[1]) &&
+                       isHexDigit(data[2]) && isHexDigit(data[3]));
+      stream = new Stream(isBinary ? decrypt(data, EEXEC_ENCRYPT_KEY, 4) :
+                          decryptAscii(data, EEXEC_ENCRYPT_KEY, 4));
     }
-    var searchRange = maxPower2 * entrySize;
-    return {
-      range: searchRange,
-      entry: log2,
-      rangeShift: entrySize * entriesCount - searchRange
-    };
-  };
+    this.seacAnalysisEnabled = !!seacAnalysisEnabled;
 
-  var OTF_HEADER_SIZE = 12;
-  var OTF_TABLE_ENTRY_SIZE = 16;
+    this.stream = stream;
+    this.nextChar();
+  }
 
-  OpenTypeFileBuilder.prototype = {
-    toArray: function OpenTypeFileBuilder_toArray() {
-      var sfnt = this.sfnt;
+  Type1Parser.prototype = {
+    readNumberArray: function Type1Parser_readNumberArray() {
+      this.getToken(); // read '[' or '{' (arrays can start with either)
+      var array = [];
+      while (true) {
+        var token = this.getToken();
+        if (token === null || token === ']' || token === '}') {
+          break;
+        }
+        array.push(parseFloat(token || 0));
+      }
+      return array;
+    },
 
-      // Tables needs to be written by ascendant alphabetic order
-      var tables = this.tables;
-      var tablesNames = Object.keys(tables);
-      tablesNames.sort();
-      var numTables = tablesNames.length;
+    readNumber: function Type1Parser_readNumber() {
+      var token = this.getToken();
+      return parseFloat(token || 0);
+    },
 
-      var i, j, jj, table, tableName;
-      // layout the tables data
-      var offset = OTF_HEADER_SIZE + numTables * OTF_TABLE_ENTRY_SIZE;
-      var tableOffsets = [offset];
-      for (i = 0; i < numTables; i++) {
-        table = tables[tablesNames[i]];
-        var paddedLength = ((table.length + 3) & ~3) >>> 0;
-        offset += paddedLength;
-        tableOffsets.push(offset);
-      }
+    readInt: function Type1Parser_readInt() {
+      // Use '| 0' to prevent setting a double into length such as the double
+      // does not flow into the loop variable.
+      var token = this.getToken();
+      return parseInt(token || 0, 10) | 0;
+    },
 
-      var file = new Uint8Array(offset);
-      // write the table data first (mostly for checksum)
-      for (i = 0; i < numTables; i++) {
-        table = tables[tablesNames[i]];
-        writeData(file, tableOffsets[i], table);
-      }
+    readBoolean: function Type1Parser_readBoolean() {
+      var token = this.getToken();
 
-      // sfnt version (4 bytes)
-      if (sfnt === 'true') {
-        // Windows hates the Mac TrueType sfnt version number
-        sfnt = string32(0x00010000);
-      }
-      file[0] = sfnt.charCodeAt(0) & 0xFF;
-      file[1] = sfnt.charCodeAt(1) & 0xFF;
-      file[2] = sfnt.charCodeAt(2) & 0xFF;
-      file[3] = sfnt.charCodeAt(3) & 0xFF;
+      // Use 1 and 0 since that's what type2 charstrings use.
+      return token === 'true' ? 1 : 0;
+    },
 
-      // numTables (2 bytes)
-      writeInt16(file, 4, numTables);
+    nextChar : function Type1_nextChar() {
+      return (this.currentChar = this.stream.getByte());
+    },
 
-      var searchParams = OpenTypeFileBuilder.getSearchParams(numTables, 16);
+    getToken: function Type1Parser_getToken() {
+      // Eat whitespace and comments.
+      var comment = false;
+      var ch = this.currentChar;
+      while (true) {
+        if (ch === -1) {
+          return null;
+        }
 
-      // searchRange (2 bytes)
-      writeInt16(file, 6, searchParams.range);
-      // entrySelector (2 bytes)
-      writeInt16(file, 8, searchParams.entry);
-      // rangeShift (2 bytes)
-      writeInt16(file, 10, searchParams.rangeShift);
+        if (comment) {
+          if (ch === 0x0A || ch === 0x0D) {
+            comment = false;
+          }
+        } else if (ch === 0x25) { // '%'
+          comment = true;
+        } else if (!Lexer.isSpace(ch)) {
+          break;
+        }
+        ch = this.nextChar();
+      }
+      if (isSpecial(ch)) {
+        this.nextChar();
+        return String.fromCharCode(ch);
+      }
+      var token = '';
+      do {
+        token += String.fromCharCode(ch);
+        ch = this.nextChar();
+      } while (ch >= 0 && !Lexer.isSpace(ch) && !isSpecial(ch));
+      return token;
+    },
 
-      offset = OTF_HEADER_SIZE;
-      // writing table entries
-      for (i = 0; i < numTables; i++) {
-        tableName = tablesNames[i];
-        file[offset] = tableName.charCodeAt(0) & 0xFF;
-        file[offset + 1] = tableName.charCodeAt(1) & 0xFF;
-        file[offset + 2] = tableName.charCodeAt(2) & 0xFF;
-        file[offset + 3] = tableName.charCodeAt(3) & 0xFF;
+    /*
+     * Returns an object containing a Subrs array and a CharStrings
+     * array extracted from and eexec encrypted block of data
+     */
+    extractFontProgram: function Type1Parser_extractFontProgram() {
+      var stream = this.stream;
 
-        // checksum
-        var checksum = 0;
-        for (j = tableOffsets[i], jj = tableOffsets[i + 1]; j < jj; j += 4) {
-          var quad = readUint32(file, j);
-          checksum = (checksum + quad) >>> 0;
+      var subrs = [], charstrings = [];
+      var privateData = Object.create(null);
+      privateData['lenIV'] = 4;
+      var program = {
+        subrs: [],
+        charstrings: [],
+        properties: {
+          'privateData': privateData
         }
-        writeInt32(file, offset + 4, checksum);
+      };
+      var token, length, data, lenIV, encoded;
+      while ((token = this.getToken()) !== null) {
+        if (token !== '/') {
+          continue;
+        }
+        token = this.getToken();
+        switch (token) {
+          case 'CharStrings':
+            // The number immediately following CharStrings must be greater or
+            // equal to the number of CharStrings.
+            this.getToken();
+            this.getToken(); // read in 'dict'
+            this.getToken(); // read in 'dup'
+            this.getToken(); // read in 'begin'
+            while(true) {
+              token = this.getToken();
+              if (token === null || token === 'end') {
+                break;
+              }
 
-        // offset
-        writeInt32(file, offset + 8, tableOffsets[i]);
-        // length
-        writeInt32(file, offset + 12, tables[tableName].length);
+              if (token !== '/') {
+                continue;
+              }
+              var glyph = this.getToken();
+              length = this.readInt();
+              this.getToken(); // read in 'RD' or '-|'
+              data = stream.makeSubStream(stream.pos, length);
+              lenIV = program.properties.privateData['lenIV'];
+              encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV);
+              // Skip past the required space and binary data.
+              stream.skip(length);
+              this.nextChar();
+              token = this.getToken(); // read in 'ND' or '|-'
+              if (token === 'noaccess') {
+                this.getToken(); // read in 'def'
+              }
+              charstrings.push({
+                glyph: glyph,
+                encoded: encoded
+              });
+            }
+            break;
+          case 'Subrs':
+            var num = this.readInt();
+            this.getToken(); // read in 'array'
+            while ((token = this.getToken()) === 'dup') {
+              var index = this.readInt();
+              length = this.readInt();
+              this.getToken(); // read in 'RD' or '-|'
+              data = stream.makeSubStream(stream.pos, length);
+              lenIV = program.properties.privateData['lenIV'];
+              encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV);
+              // Skip past the required space and binary data.
+              stream.skip(length);
+              this.nextChar();
+              token = this.getToken(); // read in 'NP' or '|'
+              if (token === 'noaccess') {
+                this.getToken(); // read in 'put'
+              }
+              subrs[index] = encoded;
+            }
+            break;
+          case 'BlueValues':
+          case 'OtherBlues':
+          case 'FamilyBlues':
+          case 'FamilyOtherBlues':
+            var blueArray = this.readNumberArray();
+            // *Blue* values may contain invalid data: disables reading of
+            // those values when hinting is disabled.
+            if (blueArray.length > 0 && (blueArray.length % 2) === 0 &&
+                HINTING_ENABLED) {
+              program.properties.privateData[token] = blueArray;
+            }
+            break;
+          case 'StemSnapH':
+          case 'StemSnapV':
+            program.properties.privateData[token] = this.readNumberArray();
+            break;
+          case 'StdHW':
+          case 'StdVW':
+            program.properties.privateData[token] =
+              this.readNumberArray()[0];
+            break;
+          case 'BlueShift':
+          case 'lenIV':
+          case 'BlueFuzz':
+          case 'BlueScale':
+          case 'LanguageGroup':
+          case 'ExpansionFactor':
+            program.properties.privateData[token] = this.readNumber();
+            break;
+          case 'ForceBold':
+            program.properties.privateData[token] = this.readBoolean();
+            break;
+        }
+      }
 
-        offset += OTF_TABLE_ENTRY_SIZE;
+      for (var i = 0; i < charstrings.length; i++) {
+        glyph = charstrings[i].glyph;
+        encoded = charstrings[i].encoded;
+        var charString = new Type1CharString();
+        var error = charString.convert(encoded, subrs,
+                                       this.seacAnalysisEnabled);
+        var output = charString.output;
+        if (error) {
+          // It seems when FreeType encounters an error while evaluating a glyph
+          // that it completely ignores the glyph so we'll mimic that behaviour
+          // here and put an endchar to make the validator happy.
+          output = [14];
+        }
+        program.charstrings.push({
+          glyphName: glyph,
+          charstring: output,
+          width: charString.width,
+          lsb: charString.lsb,
+          seac: charString.seac
+        });
       }
-      return file;
+
+      return program;
     },
 
-    addTable: function OpenTypeFileBuilder_addTable(tag, data) {
-      if (tag in this.tables) {
-        throw new Error('Table ' + tag + ' already exists');
+    extractFontHeader: function Type1Parser_extractFontHeader(properties) {
+      var token;
+      while ((token = this.getToken()) !== null) {
+        if (token !== '/') {
+          continue;
+        }
+        token = this.getToken();
+        switch (token) {
+          case 'FontMatrix':
+            var matrix = this.readNumberArray();
+            properties.fontMatrix = matrix;
+            break;
+          case 'Encoding':
+            var encodingArg = this.getToken();
+            var encoding;
+            if (!/^\d+$/.test(encodingArg)) {
+              // encoding name is specified
+              encoding = getEncoding(encodingArg);
+            } else {
+              encoding = [];
+              var size = parseInt(encodingArg, 10) | 0;
+              this.getToken(); // read in 'array'
+
+              for (var j = 0; j < size; j++) {
+                token = this.getToken();
+                // skipping till first dup or def (e.g. ignoring for statement)
+                while (token !== 'dup' && token !== 'def') {
+                  token = this.getToken();
+                  if (token === null) {
+                    return; // invalid header
+                  }
+                }
+                if (token === 'def') {
+                  break; // read all array data
+                }
+                var index = this.readInt();
+                this.getToken(); // read in '/'
+                var glyph = this.getToken();
+                encoding[index] = glyph;
+                this.getToken(); // read the in 'put'
+              }
+            }
+            properties.builtInEncoding = encoding;
+            break;
+          case 'FontBBox':
+            var fontBBox = this.readNumberArray();
+            // adjusting ascent/descent
+            properties.ascent = fontBBox[3];
+            properties.descent = fontBBox[1];
+            properties.ascentScaled = true;
+            break;
+        }
       }
-      this.tables[tag] = data;
     }
   };
 
-  return OpenTypeFileBuilder;
+  return Type1Parser;
 })();
 
-// Problematic Unicode characters in the fonts that needs to be moved to avoid
-// issues when they are painted on the canvas, e.g. complex-script shaping or
-// control/whitespace characters. The ranges are listed in pairs: the first item
-// is a code of the first problematic code, the second one is the next
-// non-problematic code. The ranges must be in sorted order.
-var ProblematicCharRanges = new Int32Array([
-  // Control characters.
-  0x0000, 0x0020,
-  0x007F, 0x00A1,
-  0x00AD, 0x00AE,
-  // Chars that is used in complex-script shaping.
-  0x0600, 0x0780,
-  0x08A0, 0x10A0,
-  0x1780, 0x1800,
-  // General punctuation chars.
-  0x2000, 0x2010,
-  0x2011, 0x2012,
-  0x2028, 0x2030,
-  0x205F, 0x2070,
-  0x25CC, 0x25CD,
-  // Chars that is used in complex-script shaping.
-  0xAA60, 0xAA80,
-  // Specials Unicode block.
-  0xFFF0, 0x10000
-]);
+exports.Type1Parser = Type1Parser;
+}));
 
-/**
- * 'Font' is the class the outside world should use, it encapsulate all the font
- * decoding logics whatever type it is (assuming the font type is supported).
- *
- * For example to read a Type1 font and to attach it to the document:
- *   var type1Font = new Font("MyFontName", binaryFile, propertiesObject);
- *   type1Font.bind();
- */
-var Font = (function FontClosure() {
-  function Font(name, file, properties) {
-    var charCode, glyphName, unicode;
 
-    this.name = name;
-    this.loadedName = properties.loadedName;
-    this.isType3Font = properties.isType3Font;
-    this.sizes = [];
-    this.missingFile = false;
+(function (root, factory) {
+  {
+    factory((root.pdfjsDisplayCanvas = {}), root.pdfjsSharedUtil,
+      root.pdfjsDisplayDOMUtils, root.pdfjsDisplayPatternHelper,
+      root.pdfjsDisplayWebGL);
+  }
+}(this, function (exports, sharedUtil, displayDOMUtils, displayPatternHelper,
+                  displayWebGL) {
 
-    this.glyphCache = Object.create(null);
+var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
+var IDENTITY_MATRIX = sharedUtil.IDENTITY_MATRIX;
+var ImageKind = sharedUtil.ImageKind;
+var OPS = sharedUtil.OPS;
+var TextRenderingMode = sharedUtil.TextRenderingMode;
+var Uint32ArrayView = sharedUtil.Uint32ArrayView;
+var Util = sharedUtil.Util;
+var assert = sharedUtil.assert;
+var info = sharedUtil.info;
+var isNum = sharedUtil.isNum;
+var isArray = sharedUtil.isArray;
+var error = sharedUtil.error;
+var shadow = sharedUtil.shadow;
+var warn = sharedUtil.warn;
+var TilingPattern = displayPatternHelper.TilingPattern;
+var getShadingPatternFromIR = displayPatternHelper.getShadingPatternFromIR;
+var WebGLUtils = displayWebGL.WebGLUtils;
 
-    var names = name.split('+');
-    names = names.length > 1 ? names[1] : names[0];
-    names = names.split(/[-,_]/g)[0];
-    this.isSerifFont = !!(properties.flags & FontFlags.Serif);
-    this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
-    this.isMonospace = !!(properties.flags & FontFlags.FixedPitch);
+// <canvas> contexts store most of the state we need natively.
+// However, PDF needs a bit more state, which we store here.
 
-    var type = properties.type;
-    var subtype = properties.subtype;
-    this.type = type;
+// Minimal font size that would be used during canvas fillText operations.
+var MIN_FONT_SIZE = 16;
+// Maximum font size that would be used during canvas fillText operations.
+var MAX_FONT_SIZE = 100;
+var MAX_GROUP_SIZE = 4096;
 
-    this.fallbackName = (this.isMonospace ? 'monospace' :
-                         (this.isSerifFont ? 'serif' : 'sans-serif'));
+// Heuristic value used when enforcing minimum line widths.
+var MIN_WIDTH_FACTOR = 0.65;
 
-    this.differences = properties.differences;
-    this.widths = properties.widths;
-    this.defaultWidth = properties.defaultWidth;
-    this.composite = properties.composite;
-    this.wideChars = properties.wideChars;
-    this.cMap = properties.cMap;
-    this.ascent = properties.ascent / PDF_GLYPH_SPACE_UNITS;
-    this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS;
-    this.fontMatrix = properties.fontMatrix;
-    this.bbox = properties.bbox;
+var COMPILE_TYPE3_GLYPHS = true;
+var MAX_SIZE_TO_COMPILE = 1000;
 
-    this.toUnicode = properties.toUnicode;
+var FULL_CHUNK_HEIGHT = 16;
 
-    this.toFontChar = [];
+function createScratchCanvas(width, height) {
+  var canvas = document.createElement('canvas');
+  canvas.width = width;
+  canvas.height = height;
+  return canvas;
+}
 
-    if (properties.type === 'Type3') {
-      for (charCode = 0; charCode < 256; charCode++) {
-        this.toFontChar[charCode] = (this.differences[charCode] ||
-                                     properties.defaultEncoding[charCode]);
-      }
-      this.fontType = FontType.TYPE3;
-      return;
-    }
+function addContextCurrentTransform(ctx) {
+  // If the context doesn't expose a `mozCurrentTransform`, add a JS based one.
+  if (!ctx.mozCurrentTransform) {
+    ctx._originalSave = ctx.save;
+    ctx._originalRestore = ctx.restore;
+    ctx._originalRotate = ctx.rotate;
+    ctx._originalScale = ctx.scale;
+    ctx._originalTranslate = ctx.translate;
+    ctx._originalTransform = ctx.transform;
+    ctx._originalSetTransform = ctx.setTransform;
 
-    this.cidEncoding = properties.cidEncoding;
-    this.vertical = properties.vertical;
-    if (this.vertical) {
-      this.vmetrics = properties.vmetrics;
-      this.defaultVMetrics = properties.defaultVMetrics;
-    }
-    var glyphsUnicodeMap;
-    if (!file || file.isEmpty) {
-      if (file) {
-        // Some bad PDF generators will include empty font files,
-        // attempting to recover by assuming that no file exists.
-        warn('Font file is empty in "' + name + '" (' + this.loadedName + ')');
+    ctx._transformMatrix = ctx._transformMatrix || [1, 0, 0, 1, 0, 0];
+    ctx._transformStack = [];
+
+    Object.defineProperty(ctx, 'mozCurrentTransform', {
+      get: function getCurrentTransform() {
+        return this._transformMatrix;
       }
+    });
 
-      this.missingFile = true;
-      // The file data is not specified. Trying to fix the font name
-      // to be used with the canvas.font.
-      var fontName = name.replace(/[,_]/g, '-');
-      var stdFontMap = getStdFontMap(), nonStdFontMap = getNonStdFontMap();
-      var isStandardFont = !!stdFontMap[fontName] ||
-        !!(nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]);
-      fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;
+    Object.defineProperty(ctx, 'mozCurrentTransformInverse', {
+      get: function getCurrentTransformInverse() {
+        // Calculation done using WolframAlpha:
+        // http://www.wolframalpha.com/input/?
+        //   i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}}
 
-      this.bold = (fontName.search(/bold/gi) !== -1);
-      this.italic = ((fontName.search(/oblique/gi) !== -1) ||
-                     (fontName.search(/italic/gi) !== -1));
+        var m = this._transformMatrix;
+        var a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5];
 
-      // Use 'name' instead of 'fontName' here because the original
-      // name ArialBlack for example will be replaced by Helvetica.
-      this.black = (name.search(/Black/g) !== -1);
+        var ad_bc = a * d - b * c;
+        var bc_ad = b * c - a * d;
 
-      // if at least one width is present, remeasure all chars when exists
-      this.remeasure = Object.keys(this.widths).length > 0;
-      if (isStandardFont && type === 'CIDFontType2' &&
-          properties.cidEncoding.indexOf('Identity-') === 0) {
-        var GlyphMapForStandardFonts = getGlyphMapForStandardFonts();
-        // Standard fonts might be embedded as CID font without glyph mapping.
-        // Building one based on GlyphMapForStandardFonts.
-        var map = [];
-        for (charCode in GlyphMapForStandardFonts) {
-          map[+charCode] = GlyphMapForStandardFonts[charCode];
-        }
-        if (/ArialBlack/i.test(name)) {
-          var SupplementalGlyphMapForArialBlack =
-            getSupplementalGlyphMapForArialBlack();
-          for (charCode in SupplementalGlyphMapForArialBlack) {
-            map[+charCode] = SupplementalGlyphMapForArialBlack[charCode];
-          }
-        }
-        var isIdentityUnicode = this.toUnicode instanceof IdentityToUnicodeMap;
-        if (!isIdentityUnicode) {
-          this.toUnicode.forEach(function(charCode, unicodeCharCode) {
-            map[+charCode] = unicodeCharCode;
-          });
-        }
-        this.toFontChar = map;
-        this.toUnicode = new ToUnicodeMap(map);
-      } else if (/Symbol/i.test(fontName)) {
-        this.toFontChar = buildToFontChar(SymbolSetEncoding, getGlyphsUnicode(),
-                                          properties.differences);
-      } else if (/Dingbats/i.test(fontName)) {
-        if (/Wingdings/i.test(name)) {
-          warn('Non-embedded Wingdings font, falling back to ZapfDingbats.');
-        }
-        this.toFontChar = buildToFontChar(ZapfDingbatsEncoding,
-                                          getDingbatsGlyphsUnicode(),
-                                          properties.differences);
-      } else if (isStandardFont) {
-        this.toFontChar = buildToFontChar(properties.defaultEncoding,
-                                          getGlyphsUnicode(),
-                                          properties.differences);
-      } else {
-        glyphsUnicodeMap = getGlyphsUnicode();
-        this.toUnicode.forEach(function(charCode, unicodeCharCode) {
-          if (!this.composite) {
-            glyphName = (properties.differences[charCode] ||
-                         properties.defaultEncoding[charCode]);
-            unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
-            if (unicode !== -1) {
-              unicodeCharCode = unicode;
-            }
-          }
-          this.toFontChar[charCode] = unicodeCharCode;
-        }.bind(this));
+        return [
+          d / ad_bc,
+          b / bc_ad,
+          c / bc_ad,
+          a / ad_bc,
+          (d * e - c * f) / bc_ad,
+          (b * e - a * f) / ad_bc
+        ];
       }
-      this.loadedName = fontName.split('-')[0];
-      this.loading = false;
-      this.fontType = getFontType(type, subtype);
-      return;
-    }
+    });
 
-    // Some fonts might use wrong font types for Type1C or CIDFontType0C
-    if (subtype === 'Type1C' && (type !== 'Type1' && type !== 'MMType1')) {
-      // Some TrueType fonts by mistake claim Type1C
-      if (isTrueTypeFile(file)) {
-        subtype = 'TrueType';
-      } else {
-        type = 'Type1';
-      }
-    }
-    if (subtype === 'CIDFontType0C' && type !== 'CIDFontType0') {
-      type = 'CIDFontType0';
-    }
-    if (subtype === 'OpenType') {
-      type = 'OpenType';
-    }
-    // Some CIDFontType0C fonts by mistake claim CIDFontType0.
-    if (type === 'CIDFontType0') {
-      if (isType1File(file)) {
-        subtype = 'CIDFontType0';
-      } else if (isOpenTypeFile(file)) {
-        // Sometimes the type/subtype can be a complete lie (see issue6782.pdf).
-        type = subtype = 'OpenType';
-      } else {
-        subtype = 'CIDFontType0C';
-      }
-    }
+    ctx.save = function ctxSave() {
+      var old = this._transformMatrix;
+      this._transformStack.push(old);
+      this._transformMatrix = old.slice(0, 6);
 
-    var data;
-    switch (type) {
-      case 'MMType1':
-        info('MMType1 font (' + name + '), falling back to Type1.');
-        /* falls through */
-      case 'Type1':
-      case 'CIDFontType0':
-        this.mimetype = 'font/opentype';
+      this._originalSave();
+    };
 
-        var cff = (subtype === 'Type1C' || subtype === 'CIDFontType0C') ?
-          new CFFFont(file, properties) : new Type1Font(name, file, properties);
+    ctx.restore = function ctxRestore() {
+      var prev = this._transformStack.pop();
+      if (prev) {
+        this._transformMatrix = prev;
+        this._originalRestore();
+      }
+    };
 
-        adjustWidths(properties);
+    ctx.translate = function ctxTranslate(x, y) {
+      var m = this._transformMatrix;
+      m[4] = m[0] * x + m[2] * y + m[4];
+      m[5] = m[1] * x + m[3] * y + m[5];
 
-        // Wrap the CFF data inside an OTF font file
-        data = this.convert(name, cff, properties);
-        break;
+      this._originalTranslate(x, y);
+    };
 
-      case 'OpenType':
-      case 'TrueType':
-      case 'CIDFontType2':
-        this.mimetype = 'font/opentype';
+    ctx.scale = function ctxScale(x, y) {
+      var m = this._transformMatrix;
+      m[0] = m[0] * x;
+      m[1] = m[1] * x;
+      m[2] = m[2] * y;
+      m[3] = m[3] * y;
 
-        // Repair the TrueType file. It is can be damaged in the point of
-        // view of the sanitizer
-        data = this.checkAndRepair(name, file, properties);
-        if (this.isOpenType) {
-          adjustWidths(properties);
+      this._originalScale(x, y);
+    };
 
-          type = 'OpenType';
-        }
-        break;
+    ctx.transform = function ctxTransform(a, b, c, d, e, f) {
+      var m = this._transformMatrix;
+      this._transformMatrix = [
+        m[0] * a + m[2] * b,
+        m[1] * a + m[3] * b,
+        m[0] * c + m[2] * d,
+        m[1] * c + m[3] * d,
+        m[0] * e + m[2] * f + m[4],
+        m[1] * e + m[3] * f + m[5]
+      ];
 
-      default:
-        error('Font ' + type + ' is not supported');
-        break;
-    }
+      ctx._originalTransform(a, b, c, d, e, f);
+    };
 
-    this.data = data;
-    this.fontType = getFontType(type, subtype);
+    ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
+      this._transformMatrix = [a, b, c, d, e, f];
 
-    // Transfer some properties again that could change during font conversion
-    this.fontMatrix = properties.fontMatrix;
-    this.widths = properties.widths;
-    this.defaultWidth = properties.defaultWidth;
-    this.encoding = properties.baseEncoding;
-    this.seacMap = properties.seacMap;
-
-    this.loading = true;
-  }
-
-  Font.getFontID = (function () {
-    var ID = 1;
-    return function Font_getFontID() {
-      return String(ID++);
+      ctx._originalSetTransform(a, b, c, d, e, f);
     };
-  })();
 
-  function int16(b0, b1) {
-    return (b0 << 8) + b1;
-  }
+    ctx.rotate = function ctxRotate(angle) {
+      var cosValue = Math.cos(angle);
+      var sinValue = Math.sin(angle);
 
-  function signedInt16(b0, b1) {
-    var value = (b0 << 8) + b1;
-    return value & (1 << 15) ? value - 0x10000 : value;
-  }
+      var m = this._transformMatrix;
+      this._transformMatrix = [
+        m[0] * cosValue + m[2] * sinValue,
+        m[1] * cosValue + m[3] * sinValue,
+        m[0] * (-sinValue) + m[2] * cosValue,
+        m[1] * (-sinValue) + m[3] * cosValue,
+        m[4],
+        m[5]
+      ];
 
-  function int32(b0, b1, b2, b3) {
-    return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
+      this._originalRotate(angle);
+    };
   }
+}
 
-  function string16(value) {
-    return String.fromCharCode((value >> 8) & 0xff, value & 0xff);
+var CachedCanvases = (function CachedCanvasesClosure() {
+  function CachedCanvases() {
+    this.cache = Object.create(null);
   }
+  CachedCanvases.prototype = {
+    getCanvas: function CachedCanvases_getCanvas(id, width, height,
+                                                 trackTransform) {
+      var canvasEntry;
+      if (this.cache[id] !== undefined) {
+        canvasEntry = this.cache[id];
+        canvasEntry.canvas.width = width;
+        canvasEntry.canvas.height = height;
+        // reset canvas transform for emulated mozCurrentTransform, if needed
+        canvasEntry.context.setTransform(1, 0, 0, 1, 0, 0);
+      } else {
+        var canvas = createScratchCanvas(width, height);
+        var ctx = canvas.getContext('2d');
+        if (trackTransform) {
+          addContextCurrentTransform(ctx);
+        }
+        this.cache[id] = canvasEntry = {canvas: canvas, context: ctx};
+      }
+      return canvasEntry;
+    },
+    clear: function () {
+      for (var id in this.cache) {
+        var canvasEntry = this.cache[id];
+        // Zeroing the width and height causes Firefox to release graphics
+        // resources immediately, which can greatly reduce memory consumption.
+        canvasEntry.canvas.width = 0;
+        canvasEntry.canvas.height = 0;
+        delete this.cache[id];
+      }
+    }
+  };
+  return CachedCanvases;
+})();
 
-  function safeString16(value) {
-    // clamp value to the 16-bit int range
-    value = (value > 0x7FFF ? 0x7FFF : (value < -0x8000 ? -0x8000 : value));
-    return String.fromCharCode((value >> 8) & 0xff, value & 0xff);
-  }
+function compileType3Glyph(imgData) {
+  var POINT_TO_PROCESS_LIMIT = 1000;
 
-  function isTrueTypeFile(file) {
-    var header = file.peekBytes(4);
-    return readUint32(header, 0) === 0x00010000;
-  }
+  var width = imgData.width, height = imgData.height;
+  var i, j, j0, width1 = width + 1;
+  var points = new Uint8Array(width1 * (height + 1));
+  var POINT_TYPES =
+      new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]);
 
-  function isOpenTypeFile(file) {
-    var header = file.peekBytes(4);
-    return bytesToString(header) === 'OTTO';
+  // decodes bit-packed mask data
+  var lineSize = (width + 7) & ~7, data0 = imgData.data;
+  var data = new Uint8Array(lineSize * height), pos = 0, ii;
+  for (i = 0, ii = data0.length; i < ii; i++) {
+    var mask = 128, elem = data0[i];
+    while (mask > 0) {
+      data[pos++] = (elem & mask) ? 0 : 255;
+      mask >>= 1;
+    }
   }
 
-  function isType1File(file) {
-    var header = file.peekBytes(2);
-    // All Type1 font programs must begin with the comment '%!' (0x25 + 0x21).
-    if (header[0] === 0x25 && header[1] === 0x21) {
-      return true;
-    }
-    // ... obviously some fonts violate that part of the specification,
-    // please refer to the comment in |Type1Font| below.
-    if (header[0] === 0x80 && header[1] === 0x01) { // pfb file header.
-      return true;
+  // finding iteresting points: every point is located between mask pixels,
+  // so there will be points of the (width + 1)x(height + 1) grid. Every point
+  // will have flags assigned based on neighboring mask pixels:
+  //   4 | 8
+  //   --P--
+  //   2 | 1
+  // We are interested only in points with the flags:
+  //   - outside corners: 1, 2, 4, 8;
+  //   - inside corners: 7, 11, 13, 14;
+  //   - and, intersections: 5, 10.
+  var count = 0;
+  pos = 0;
+  if (data[pos] !== 0) {
+    points[0] = 1;
+    ++count;
+  }
+  for (j = 1; j < width; j++) {
+    if (data[pos] !== data[pos + 1]) {
+      points[j] = data[pos] ? 2 : 1;
+      ++count;
     }
-    return false;
+    pos++;
   }
-
-  function buildToFontChar(encoding, glyphsUnicodeMap, differences) {
-    var toFontChar = [], unicode;
-    for (var i = 0, ii = encoding.length; i < ii; i++) {
-      unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap);
-      if (unicode !== -1) {
-        toFontChar[i] = unicode;
-      }
+  if (data[pos] !== 0) {
+    points[j] = 2;
+    ++count;
+  }
+  for (i = 1; i < height; i++) {
+    pos = i * lineSize;
+    j0 = i * width1;
+    if (data[pos - lineSize] !== data[pos]) {
+      points[j0] = data[pos] ? 1 : 8;
+      ++count;
     }
-    for (var charCode in differences) {
-      unicode = getUnicodeForGlyph(differences[charCode], glyphsUnicodeMap);
-      if (unicode !== -1) {
-        toFontChar[+charCode] = unicode;
+    // 'sum' is the position of the current pixel configuration in the 'TYPES'
+    // array (in order 8-1-2-4, so we can use '>>2' to shift the column).
+    var sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
+    for (j = 1; j < width; j++) {
+      sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) +
+            (data[pos - lineSize + 1] ? 8 : 0);
+      if (POINT_TYPES[sum]) {
+        points[j0 + j] = POINT_TYPES[sum];
+        ++count;
       }
+      pos++;
+    }
+    if (data[pos - lineSize] !== data[pos]) {
+      points[j0 + j] = data[pos] ? 2 : 4;
+      ++count;
+    }
+
+    if (count > POINT_TO_PROCESS_LIMIT) {
+      return null;
     }
-    return toFontChar;
   }
 
-  /**
-   * Helper function for |adjustMapping|.
-   * @return {boolean}
-   */
-  function isProblematicUnicodeLocation(code) {
-    // Using binary search to find a range start.
-    var i = 0, j = ProblematicCharRanges.length - 1;
-    while (i < j) {
-      var c = (i + j + 1) >> 1;
-      if (code < ProblematicCharRanges[c]) {
-        j = c - 1;
-      } else {
-        i = c;
-      }
+  pos = lineSize * (height - 1);
+  j0 = i * width1;
+  if (data[pos] !== 0) {
+    points[j0] = 8;
+    ++count;
+  }
+  for (j = 1; j < width; j++) {
+    if (data[pos] !== data[pos + 1]) {
+      points[j0 + j] = data[pos] ? 4 : 8;
+      ++count;
     }
-    // Even index means code in problematic range.
-    return !(i & 1);
+    pos++;
+  }
+  if (data[pos] !== 0) {
+    points[j0 + j] = 4;
+    ++count;
+  }
+  if (count > POINT_TO_PROCESS_LIMIT) {
+    return null;
   }
 
-  /**
-   * Rebuilds the char code to glyph ID map by trying to replace the char codes
-   * with their unicode value. It also moves char codes that are in known
-   * problematic locations.
-   * @return {Object} Two properties:
-   * 'toFontChar' - maps original char codes(the value that will be read
-   * from commands such as show text) to the char codes that will be used in the
-   * font that we build
-   * 'charCodeToGlyphId' - maps the new font char codes to glyph ids
-   */
-  function adjustMapping(charCodeToGlyphId, properties) {
-    var toUnicode = properties.toUnicode;
-    var isSymbolic = !!(properties.flags & FontFlags.Symbolic);
-    var isIdentityUnicode =
-      properties.toUnicode instanceof IdentityToUnicodeMap;
-    var newMap = Object.create(null);
-    var toFontChar = [];
-    var usedFontCharCodes = [];
-    var nextAvailableFontCharCode = PRIVATE_USE_OFFSET_START;
-    for (var originalCharCode in charCodeToGlyphId) {
-      originalCharCode |= 0;
-      var glyphId = charCodeToGlyphId[originalCharCode];
-      var fontCharCode = originalCharCode;
-      // First try to map the value to a unicode position if a non identity map
-      // was created.
-      if (!isIdentityUnicode && toUnicode.has(originalCharCode)) {
-        var unicode = toUnicode.get(fontCharCode);
-        // TODO: Try to map ligatures to the correct spot.
-        if (unicode.length === 1) {
-          fontCharCode = unicode.charCodeAt(0);
-        }
-      }
-      // Try to move control characters, special characters and already mapped
-      // characters to the private use area since they will not be drawn by
-      // canvas if left in their current position. Also, move characters if the
-      // font was symbolic and there is only an identity unicode map since the
-      // characters probably aren't in the correct position (fixes an issue
-      // with firefox and thuluthfont).
-      if ((usedFontCharCodes[fontCharCode] !== undefined ||
-           isProblematicUnicodeLocation(fontCharCode) ||
-           (isSymbolic && isIdentityUnicode)) &&
-          nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END) { // Room left.
-        // Loop to try and find a free spot in the private use area.
-        do {
-          fontCharCode = nextAvailableFontCharCode++;
+  // building outlines
+  var steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]);
+  var outlines = [];
+  for (i = 0; count && i <= height; i++) {
+    var p = i * width1;
+    var end = p + width;
+    while (p < end && !points[p]) {
+      p++;
+    }
+    if (p === end) {
+      continue;
+    }
+    var coords = [p % width1, i];
 
-          if (SKIP_PRIVATE_USE_RANGE_F000_TO_F01F && fontCharCode === 0xF000) {
-            fontCharCode = 0xF020;
-            nextAvailableFontCharCode = fontCharCode + 1;
-          }
+    var type = points[p], p0 = p, pp;
+    do {
+      var step = steps[type];
+      do {
+        p += step;
+      } while (!points[p]);
 
-        } while (usedFontCharCodes[fontCharCode] !== undefined &&
-                 nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END);
+      pp = points[p];
+      if (pp !== 5 && pp !== 10) {
+        // set new direction
+        type = pp;
+        // delete mark
+        points[p] = 0;
+      } else { // type is 5 or 10, ie, a crossing
+        // set new direction
+        type = pp & ((0x33 * type) >> 4);
+        // set new type for "future hit"
+        points[p] &= (type >> 2 | type << 2);
       }
 
-      newMap[fontCharCode] = glyphId;
-      toFontChar[originalCharCode] = fontCharCode;
-      usedFontCharCodes[fontCharCode] = true;
-    }
-    return {
-      toFontChar: toFontChar,
-      charCodeToGlyphId: newMap,
-      nextAvailableFontCharCode: nextAvailableFontCharCode
-    };
+      coords.push(p % width1);
+      coords.push((p / width1) | 0);
+      --count;
+    } while (p0 !== p);
+    outlines.push(coords);
+    --i;
   }
 
-  function getRanges(glyphs, numGlyphs) {
-    // Array.sort() sorts by characters, not numerically, so convert to an
-    // array of characters.
-    var codes = [];
-    for (var charCode in glyphs) {
-      // Remove an invalid glyph ID mappings to make OTS happy.
-      if (glyphs[charCode] >= numGlyphs) {
-        continue;
+  var drawOutline = function(c) {
+    c.save();
+    // the path shall be painted in [0..1]x[0..1] space
+    c.scale(1 / width, -1 / height);
+    c.translate(0, -height);
+    c.beginPath();
+    for (var i = 0, ii = outlines.length; i < ii; i++) {
+      var o = outlines[i];
+      c.moveTo(o[0], o[1]);
+      for (var j = 2, jj = o.length; j < jj; j += 2) {
+        c.lineTo(o[j], o[j+1]);
       }
-      codes.push({ fontCharCode: charCode | 0, glyphId: glyphs[charCode] });
     }
-    codes.sort(function fontGetRangesSort(a, b) {
-      return a.fontCharCode - b.fontCharCode;
-    });
+    c.fill();
+    c.beginPath();
+    c.restore();
+  };
 
-    // Split the sorted codes into ranges.
-    var ranges = [];
-    var length = codes.length;
-    for (var n = 0; n < length; ) {
-      var start = codes[n].fontCharCode;
-      var codeIndices = [codes[n].glyphId];
-      ++n;
-      var end = start;
-      while (n < length && end + 1 === codes[n].fontCharCode) {
-        codeIndices.push(codes[n].glyphId);
-        ++end;
-        ++n;
-        if (end === 0xFFFF) {
-          break;
-        }
-      }
-      ranges.push([start, end, codeIndices]);
-    }
+  return drawOutline;
+}
 
-    return ranges;
-  }
+var CanvasExtraState = (function CanvasExtraStateClosure() {
+  function CanvasExtraState(old) {
+    // Are soft masks and alpha values shapes or opacities?
+    this.alphaIsShape = false;
+    this.fontSize = 0;
+    this.fontSizeScale = 1;
+    this.textMatrix = IDENTITY_MATRIX;
+    this.textMatrixScale = 1;
+    this.fontMatrix = FONT_IDENTITY_MATRIX;
+    this.leading = 0;
+    // Current point (in user coordinates)
+    this.x = 0;
+    this.y = 0;
+    // Start of text line (in text coordinates)
+    this.lineX = 0;
+    this.lineY = 0;
+    // Character and word spacing
+    this.charSpacing = 0;
+    this.wordSpacing = 0;
+    this.textHScale = 1;
+    this.textRenderingMode = TextRenderingMode.FILL;
+    this.textRise = 0;
+    // Default fore and background colors
+    this.fillColor = '#000000';
+    this.strokeColor = '#000000';
+    this.patternFill = false;
+    // Note: fill alpha applies to all non-stroking operations
+    this.fillAlpha = 1;
+    this.strokeAlpha = 1;
+    this.lineWidth = 1;
+    this.activeSMask = null; // nonclonable field (see the save method below)
 
-  function createCmapTable(glyphs, numGlyphs) {
-    var ranges = getRanges(glyphs, numGlyphs);
-    var numTables = ranges[ranges.length - 1][1] > 0xFFFF ? 2 : 1;
-    var cmap = '\x00\x00' + // version
-               string16(numTables) +  // numTables
-               '\x00\x03' + // platformID
-               '\x00\x01' + // encodingID
-               string32(4 + numTables * 8); // start of the table record
+    this.old = old;
+  }
 
-    var i, ii, j, jj;
-    for (i = ranges.length - 1; i >= 0; --i) {
-      if (ranges[i][0] <= 0xFFFF) { break; }
+  CanvasExtraState.prototype = {
+    clone: function CanvasExtraState_clone() {
+      return Object.create(this);
+    },
+    setCurrentPoint: function CanvasExtraState_setCurrentPoint(x, y) {
+      this.x = x;
+      this.y = y;
     }
-    var bmpLength = i + 1;
+  };
+  return CanvasExtraState;
+})();
 
-    if (ranges[i][0] < 0xFFFF && ranges[i][1] === 0xFFFF) {
-      ranges[i][1] = 0xFFFE;
+var CanvasGraphics = (function CanvasGraphicsClosure() {
+  // Defines the time the executeOperatorList is going to be executing
+  // before it stops and shedules a continue of execution.
+  var EXECUTION_TIME = 15;
+  // Defines the number of steps before checking the execution time
+  var EXECUTION_STEPS = 10;
+
+  function CanvasGraphics(canvasCtx, commonObjs, objs, imageLayer) {
+    this.ctx = canvasCtx;
+    this.current = new CanvasExtraState();
+    this.stateStack = [];
+    this.pendingClip = null;
+    this.pendingEOFill = false;
+    this.res = null;
+    this.xobjs = null;
+    this.commonObjs = commonObjs;
+    this.objs = objs;
+    this.imageLayer = imageLayer;
+    this.groupStack = [];
+    this.processingType3 = null;
+    // Patterns are painted relative to the initial page/form transform, see pdf
+    // spec 8.7.2 NOTE 1.
+    this.baseTransform = null;
+    this.baseTransformStack = [];
+    this.groupLevel = 0;
+    this.smaskStack = [];
+    this.smaskCounter = 0;
+    this.tempSMask = null;
+    this.cachedCanvases = new CachedCanvases();
+    if (canvasCtx) {
+      // NOTE: if mozCurrentTransform is polyfilled, then the current state of
+      // the transformation must already be set in canvasCtx._transformMatrix.
+      addContextCurrentTransform(canvasCtx);
     }
-    var trailingRangesCount = ranges[i][1] < 0xFFFF ? 1 : 0;
-    var segCount = bmpLength + trailingRangesCount;
-    var searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2);
+    this.cachedGetSinglePixelWidth = null;
+  }
 
-    // Fill up the 4 parallel arrays describing the segments.
-    var startCount = '';
-    var endCount = '';
-    var idDeltas = '';
-    var idRangeOffsets = '';
-    var glyphsIds = '';
-    var bias = 0;
+  function putBinaryImageData(ctx, imgData) {
+    if (typeof ImageData !== 'undefined' && imgData instanceof ImageData) {
+      ctx.putImageData(imgData, 0, 0);
+      return;
+    }
 
-    var range, start, end, codes;
-    for (i = 0, ii = bmpLength; i < ii; i++) {
-      range = ranges[i];
-      start = range[0];
-      end = range[1];
-      startCount += string16(start);
-      endCount += string16(end);
-      codes = range[2];
-      var contiguous = true;
-      for (j = 1, jj = codes.length; j < jj; ++j) {
-        if (codes[j] !== codes[j - 1] + 1) {
-          contiguous = false;
-          break;
-        }
-      }
-      if (!contiguous) {
-        var offset = (segCount - i) * 2 + bias * 2;
-        bias += (end - start + 1);
+    // Put the image data to the canvas in chunks, rather than putting the
+    // whole image at once.  This saves JS memory, because the ImageData object
+    // is smaller. It also possibly saves C++ memory within the implementation
+    // of putImageData(). (E.g. in Firefox we make two short-lived copies of
+    // the data passed to putImageData()). |n| shouldn't be too small, however,
+    // because too many putImageData() calls will slow things down.
+    //
+    // Note: as written, if the last chunk is partial, the putImageData() call
+    // will (conceptually) put pixels past the bounds of the canvas.  But
+    // that's ok; any such pixels are ignored.
 
-        idDeltas += string16(0);
-        idRangeOffsets += string16(offset);
+    var height = imgData.height, width = imgData.width;
+    var partialChunkHeight = height % FULL_CHUNK_HEIGHT;
+    var fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
+    var totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
 
-        for (j = 0, jj = codes.length; j < jj; ++j) {
-          glyphsIds += string16(codes[j]);
-        }
-      } else {
-        var startCode = codes[0];
+    var chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
+    var srcPos = 0, destPos;
+    var src = imgData.data;
+    var dest = chunkImgData.data;
+    var i, j, thisChunkHeight, elemsInThisChunk;
 
-        idDeltas += string16((startCode - start) & 0xFFFF);
-        idRangeOffsets += string16(0);
-      }
-    }
+    // There are multiple forms in which the pixel data can be passed, and
+    // imgData.kind tells us which one this is.
+    if (imgData.kind === ImageKind.GRAYSCALE_1BPP) {
+      // Grayscale, 1 bit per pixel (i.e. black-and-white).
+      var srcLength = src.byteLength;
+      var dest32 = PDFJS.hasCanvasTypedArrays ? new Uint32Array(dest.buffer) :
+        new Uint32ArrayView(dest);
+      var dest32DataLength = dest32.length;
+      var fullSrcDiff = (width + 7) >> 3;
+      var white = 0xFFFFFFFF;
+      var black = (PDFJS.isLittleEndian || !PDFJS.hasCanvasTypedArrays) ?
+        0xFF000000 : 0x000000FF;
+      for (i = 0; i < totalChunks; i++) {
+        thisChunkHeight =
+          (i < fullChunks) ? FULL_CHUNK_HEIGHT : partialChunkHeight;
+        destPos = 0;
+        for (j = 0; j < thisChunkHeight; j++) {
+          var srcDiff = srcLength - srcPos;
+          var k = 0;
+          var kEnd = (srcDiff > fullSrcDiff) ? width : srcDiff * 8 - 7;
+          var kEndUnrolled = kEnd & ~7;
+          var mask = 0;
+          var srcByte = 0;
+          for (; k < kEndUnrolled; k += 8) {
+            srcByte = src[srcPos++];
+            dest32[destPos++] = (srcByte & 128) ? white : black;
+            dest32[destPos++] = (srcByte & 64) ? white : black;
+            dest32[destPos++] = (srcByte & 32) ? white : black;
+            dest32[destPos++] = (srcByte & 16) ? white : black;
+            dest32[destPos++] = (srcByte & 8) ? white : black;
+            dest32[destPos++] = (srcByte & 4) ? white : black;
+            dest32[destPos++] = (srcByte & 2) ? white : black;
+            dest32[destPos++] = (srcByte & 1) ? white : black;
+          }
+          for (; k < kEnd; k++) {
+             if (mask === 0) {
+               srcByte = src[srcPos++];
+               mask = 128;
+             }
 
-    if (trailingRangesCount > 0) {
-      endCount += '\xFF\xFF';
-      startCount += '\xFF\xFF';
-      idDeltas += '\x00\x01';
-      idRangeOffsets += '\x00\x00';
+            dest32[destPos++] = (srcByte & mask) ? white : black;
+            mask >>= 1;
+          }
+        }
+        // We ran out of input. Make all remaining pixels transparent.
+        while (destPos < dest32DataLength) {
+          dest32[destPos++] = 0;
+        }
+
+        ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
+      }
+    } else if (imgData.kind === ImageKind.RGBA_32BPP) {
+      // RGBA, 32-bits per pixel.
+
+      j = 0;
+      elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
+      for (i = 0; i < fullChunks; i++) {
+        dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
+        srcPos += elemsInThisChunk;
+
+        ctx.putImageData(chunkImgData, 0, j);
+        j += FULL_CHUNK_HEIGHT;
+      }
+      if (i < totalChunks) {
+        elemsInThisChunk = width * partialChunkHeight * 4;
+        dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
+        ctx.putImageData(chunkImgData, 0, j);
+      }
+
+    } else if (imgData.kind === ImageKind.RGB_24BPP) {
+      // RGB, 24-bits per pixel.
+      thisChunkHeight = FULL_CHUNK_HEIGHT;
+      elemsInThisChunk = width * thisChunkHeight;
+      for (i = 0; i < totalChunks; i++) {
+        if (i >= fullChunks) {
+          thisChunkHeight = partialChunkHeight;
+          elemsInThisChunk = width * thisChunkHeight;
+        }
+
+        destPos = 0;
+        for (j = elemsInThisChunk; j--;) {
+          dest[destPos++] = src[srcPos++];
+          dest[destPos++] = src[srcPos++];
+          dest[destPos++] = src[srcPos++];
+          dest[destPos++] = 255;
+        }
+        ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
+      }
+    } else {
+      error('bad image kind: ' + imgData.kind);
     }
+  }
 
-    var format314 = '\x00\x00' + // language
-                    string16(2 * segCount) +
-                    string16(searchParams.range) +
-                    string16(searchParams.entry) +
-                    string16(searchParams.rangeShift) +
-                    endCount + '\x00\x00' + startCount +
-                    idDeltas + idRangeOffsets + glyphsIds;
+  function putBinaryImageMask(ctx, imgData) {
+    var height = imgData.height, width = imgData.width;
+    var partialChunkHeight = height % FULL_CHUNK_HEIGHT;
+    var fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
+    var totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
 
-    var format31012 = '';
-    var header31012 = '';
-    if (numTables > 1) {
-      cmap += '\x00\x03' + // platformID
-              '\x00\x0A' + // encodingID
-              string32(4 + numTables * 8 +
-                       4 + format314.length); // start of the table record
-      format31012 = '';
-      for (i = 0, ii = ranges.length; i < ii; i++) {
-        range = ranges[i];
-        start = range[0];
-        codes = range[2];
-        var code = codes[0];
-        for (j = 1, jj = codes.length; j < jj; ++j) {
-          if (codes[j] !== codes[j - 1] + 1) {
-            end = range[0] + j - 1;
-            format31012 += string32(start) + // startCharCode
-                           string32(end) + // endCharCode
-                           string32(code); // startGlyphID
-            start = end + 1;
-            code = codes[j];
+    var chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
+    var srcPos = 0;
+    var src = imgData.data;
+    var dest = chunkImgData.data;
+
+    for (var i = 0; i < totalChunks; i++) {
+      var thisChunkHeight =
+        (i < fullChunks) ? FULL_CHUNK_HEIGHT : partialChunkHeight;
+
+      // Expand the mask so it can be used by the canvas.  Any required
+      // inversion has already been handled.
+      var destPos = 3; // alpha component offset
+      for (var j = 0; j < thisChunkHeight; j++) {
+        var mask = 0;
+        for (var k = 0; k < width; k++) {
+          if (!mask) {
+            var elem = src[srcPos++];
+            mask = 128;
           }
+          dest[destPos] = (elem & mask) ? 0 : 255;
+          destPos += 4;
+          mask >>= 1;
         }
-        format31012 += string32(start) + // startCharCode
-                       string32(range[1]) + // endCharCode
-                       string32(code); // startGlyphID
       }
-      header31012 = '\x00\x0C' + // format
-                    '\x00\x00' + // reserved
-                    string32(format31012.length + 16) + // length
-                    '\x00\x00\x00\x00' + // language
-                    string32(format31012.length / 12); // nGroups
+      ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
     }
-
-    return cmap + '\x00\x04' + // format
-                  string16(format314.length + 4) + // length
-                  format314 + header31012 + format31012;
   }
 
-  function validateOS2Table(os2) {
-    var stream = new Stream(os2.data);
-    var version = stream.getUint16();
-    // TODO verify all OS/2 tables fields, but currently we validate only those
-    // that give us issues
-    stream.getBytes(60); // skipping type, misc sizes, panose, unicode ranges
-    var selection = stream.getUint16();
-    if (version < 4 && (selection & 0x0300)) {
-      return false;
-    }
-    var firstChar = stream.getUint16();
-    var lastChar = stream.getUint16();
-    if (firstChar > lastChar) {
-      return false;
+  function copyCtxState(sourceCtx, destCtx) {
+    var properties = ['strokeStyle', 'fillStyle', 'fillRule', 'globalAlpha',
+                      'lineWidth', 'lineCap', 'lineJoin', 'miterLimit',
+                      'globalCompositeOperation', 'font'];
+    for (var i = 0, ii = properties.length; i < ii; i++) {
+      var property = properties[i];
+      if (sourceCtx[property] !== undefined) {
+        destCtx[property] = sourceCtx[property];
+      }
     }
-    stream.getBytes(6); // skipping sTypoAscender/Descender/LineGap
-    var usWinAscent = stream.getUint16();
-    if (usWinAscent === 0) { // makes font unreadable by windows
-      return false;
+    if (sourceCtx.setLineDash !== undefined) {
+      destCtx.setLineDash(sourceCtx.getLineDash());
+      destCtx.lineDashOffset =  sourceCtx.lineDashOffset;
+    } else if (sourceCtx.mozDashOffset !== undefined) {
+      destCtx.mozDash = sourceCtx.mozDash;
+      destCtx.mozDashOffset = sourceCtx.mozDashOffset;
     }
-
-    // OS/2 appears to be valid, resetting some fields
-    os2.data[8] = os2.data[9] = 0; // IE rejects fonts if fsType != 0
-    return true;
   }
 
-  function createOS2Table(properties, charstrings, override) {
-    override = override || {
-      unitsPerEm: 0,
-      yMax: 0,
-      yMin: 0,
-      ascent: 0,
-      descent: 0
-    };
+  function composeSMaskBackdrop(bytes, r0, g0, b0) {
+    var length = bytes.length;
+    for (var i = 3; i < length; i += 4) {
+      var alpha = bytes[i];
+      if (alpha === 0) {
+        bytes[i - 3] = r0;
+        bytes[i - 2] = g0;
+        bytes[i - 1] = b0;
+      } else if (alpha < 255) {
+        var alpha_ = 255 - alpha;
+        bytes[i - 3] = (bytes[i - 3] * alpha + r0 * alpha_) >> 8;
+        bytes[i - 2] = (bytes[i - 2] * alpha + g0 * alpha_) >> 8;
+        bytes[i - 1] = (bytes[i - 1] * alpha + b0 * alpha_) >> 8;
+      }
+    }
+  }
 
-    var ulUnicodeRange1 = 0;
-    var ulUnicodeRange2 = 0;
-    var ulUnicodeRange3 = 0;
-    var ulUnicodeRange4 = 0;
+  function composeSMaskAlpha(maskData, layerData, transferMap) {
+    var length = maskData.length;
+    var scale = 1 / 255;
+    for (var i = 3; i < length; i += 4) {
+      var alpha = transferMap ? transferMap[maskData[i]] : maskData[i];
+      layerData[i] = (layerData[i] * alpha * scale) | 0;
+    }
+  }
 
-    var firstCharIndex = null;
-    var lastCharIndex = 0;
+  function composeSMaskLuminosity(maskData, layerData, transferMap) {
+    var length = maskData.length;
+    for (var i = 3; i < length; i += 4) {
+      var y = (maskData[i - 3] * 77) +  // * 0.3 / 255 * 0x10000
+              (maskData[i - 2] * 152) + // * 0.59 ....
+              (maskData[i - 1] * 28);   // * 0.11 ....
+      layerData[i] = transferMap ?
+        (layerData[i] * transferMap[y >> 8]) >> 8 :
+        (layerData[i] * y) >> 16;
+    }
+  }
 
-    if (charstrings) {
-      for (var code in charstrings) {
-        code |= 0;
-        if (firstCharIndex > code || !firstCharIndex) {
-          firstCharIndex = code;
-        }
-        if (lastCharIndex < code) {
-          lastCharIndex = code;
-        }
+  function genericComposeSMask(maskCtx, layerCtx, width, height,
+                               subtype, backdrop, transferMap) {
+    var hasBackdrop = !!backdrop;
+    var r0 = hasBackdrop ? backdrop[0] : 0;
+    var g0 = hasBackdrop ? backdrop[1] : 0;
+    var b0 = hasBackdrop ? backdrop[2] : 0;
 
-        var position = getUnicodeRangeFor(code);
-        if (position < 32) {
-          ulUnicodeRange1 |= 1 << position;
-        } else if (position < 64) {
-          ulUnicodeRange2 |= 1 << position - 32;
-        } else if (position < 96) {
-          ulUnicodeRange3 |= 1 << position - 64;
-        } else if (position < 123) {
-          ulUnicodeRange4 |= 1 << position - 96;
-        } else {
-          error('Unicode ranges Bits > 123 are reserved for internal usage');
-        }
-      }
+    var composeFn;
+    if (subtype === 'Luminosity') {
+      composeFn = composeSMaskLuminosity;
     } else {
-      // TODO
-      firstCharIndex = 0;
-      lastCharIndex = 255;
+      composeFn = composeSMaskAlpha;
     }
 
-    var bbox = properties.bbox || [0, 0, 0, 0];
-    var unitsPerEm = (override.unitsPerEm ||
-                      1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0]);
+    // processing image in chunks to save memory
+    var PIXELS_TO_PROCESS = 1048576;
+    var chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width));
+    for (var row = 0; row < height; row += chunkSize) {
+      var chunkHeight = Math.min(chunkSize, height - row);
+      var maskData = maskCtx.getImageData(0, row, width, chunkHeight);
+      var layerData = layerCtx.getImageData(0, row, width, chunkHeight);
 
-    // if the font units differ to the PDF glyph space units
-    // then scale up the values
-    var scale = (properties.ascentScaled ? 1.0 :
-                 unitsPerEm / PDF_GLYPH_SPACE_UNITS);
+      if (hasBackdrop) {
+        composeSMaskBackdrop(maskData.data, r0, g0, b0);
+      }
+      composeFn(maskData.data, layerData.data, transferMap);
 
-    var typoAscent = (override.ascent ||
-                      Math.round(scale * (properties.ascent || bbox[3])));
-    var typoDescent = (override.descent ||
-                       Math.round(scale * (properties.descent || bbox[1])));
-    if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) {
-      typoDescent = -typoDescent; // fixing incorrect descent
+      maskCtx.putImageData(layerData, 0, row);
     }
-    var winAscent = override.yMax || typoAscent;
-    var winDescent = -override.yMin || -typoDescent;
-
-    return '\x00\x03' + // version
-           '\x02\x24' + // xAvgCharWidth
-           '\x01\xF4' + // usWeightClass
-           '\x00\x05' + // usWidthClass
-           '\x00\x00' + // fstype (0 to let the font loads via font-face on IE)
-           '\x02\x8A' + // ySubscriptXSize
-           '\x02\xBB' + // ySubscriptYSize
-           '\x00\x00' + // ySubscriptXOffset
-           '\x00\x8C' + // ySubscriptYOffset
-           '\x02\x8A' + // ySuperScriptXSize
-           '\x02\xBB' + // ySuperScriptYSize
-           '\x00\x00' + // ySuperScriptXOffset
-           '\x01\xDF' + // ySuperScriptYOffset
-           '\x00\x31' + // yStrikeOutSize
-           '\x01\x02' + // yStrikeOutPosition
-           '\x00\x00' + // sFamilyClass
-           '\x00\x00\x06' +
-           String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) +
-           '\x00\x00\x00\x00\x00\x00' + // Panose
-           string32(ulUnicodeRange1) + // ulUnicodeRange1 (Bits 0-31)
-           string32(ulUnicodeRange2) + // ulUnicodeRange2 (Bits 32-63)
-           string32(ulUnicodeRange3) + // ulUnicodeRange3 (Bits 64-95)
-           string32(ulUnicodeRange4) + // ulUnicodeRange4 (Bits 96-127)
-           '\x2A\x32\x31\x2A' + // achVendID
-           string16(properties.italicAngle ? 1 : 0) + // fsSelection
-           string16(firstCharIndex ||
-                    properties.firstChar) + // usFirstCharIndex
-           string16(lastCharIndex || properties.lastChar) +  // usLastCharIndex
-           string16(typoAscent) + // sTypoAscender
-           string16(typoDescent) + // sTypoDescender
-           '\x00\x64' + // sTypoLineGap (7%-10% of the unitsPerEM value)
-           string16(winAscent) + // usWinAscent
-           string16(winDescent) + // usWinDescent
-           '\x00\x00\x00\x00' + // ulCodePageRange1 (Bits 0-31)
-           '\x00\x00\x00\x00' + // ulCodePageRange2 (Bits 32-63)
-           string16(properties.xHeight) + // sxHeight
-           string16(properties.capHeight) + // sCapHeight
-           string16(0) + // usDefaultChar
-           string16(firstCharIndex || properties.firstChar) + // usBreakChar
-           '\x00\x03';  // usMaxContext
   }
 
-  function createPostTable(properties) {
-    var angle = Math.floor(properties.italicAngle * (Math.pow(2, 16)));
-    return ('\x00\x03\x00\x00' + // Version number
-            string32(angle) + // italicAngle
-            '\x00\x00' + // underlinePosition
-            '\x00\x00' + // underlineThickness
-            string32(properties.fixedPitch) + // isFixedPitch
-            '\x00\x00\x00\x00' + // minMemType42
-            '\x00\x00\x00\x00' + // maxMemType42
-            '\x00\x00\x00\x00' + // minMemType1
-            '\x00\x00\x00\x00');  // maxMemType1
-  }
+  function composeSMask(ctx, smask, layerCtx) {
+    var mask = smask.canvas;
+    var maskCtx = smask.context;
 
-  function createNameTable(name, proto) {
-    if (!proto) {
-      proto = [[], []]; // no strings and unicode strings
-    }
+    ctx.setTransform(smask.scaleX, 0, 0, smask.scaleY,
+                     smask.offsetX, smask.offsetY);
 
-    var strings = [
-      proto[0][0] || 'Original licence',  // 0.Copyright
-      proto[0][1] || name,                // 1.Font family
-      proto[0][2] || 'Unknown',           // 2.Font subfamily (font weight)
-      proto[0][3] || 'uniqueID',          // 3.Unique ID
-      proto[0][4] || name,                // 4.Full font name
-      proto[0][5] || 'Version 0.11',      // 5.Version
-      proto[0][6] || '',                  // 6.Postscript name
-      proto[0][7] || 'Unknown',           // 7.Trademark
-      proto[0][8] || 'Unknown',           // 8.Manufacturer
-      proto[0][9] || 'Unknown'            // 9.Designer
-    ];
+    var backdrop = smask.backdrop || null;
+    if (!smask.transferMap && WebGLUtils.isEnabled) {
+      var composed = WebGLUtils.composeSMask(layerCtx.canvas, mask,
+        {subtype: smask.subtype, backdrop: backdrop});
+      ctx.setTransform(1, 0, 0, 1, 0, 0);
+      ctx.drawImage(composed, smask.offsetX, smask.offsetY);
+      return;
+    }
+    genericComposeSMask(maskCtx, layerCtx, mask.width, mask.height,
+                        smask.subtype, backdrop, smask.transferMap);
+    ctx.drawImage(mask, 0, 0);
+  }
 
-    // Mac want 1-byte per character strings while Windows want
-    // 2-bytes per character, so duplicate the names table
-    var stringsUnicode = [];
-    var i, ii, j, jj, str;
-    for (i = 0, ii = strings.length; i < ii; i++) {
-      str = proto[1][i] || strings[i];
+  var LINE_CAP_STYLES = ['butt', 'round', 'square'];
+  var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
+  var NORMAL_CLIP = {};
+  var EO_CLIP = {};
 
-      var strBufUnicode = [];
-      for (j = 0, jj = str.length; j < jj; j++) {
-        strBufUnicode.push(string16(str.charCodeAt(j)));
-      }
-      stringsUnicode.push(strBufUnicode.join(''));
-    }
+  CanvasGraphics.prototype = {
 
-    var names = [strings, stringsUnicode];
-    var platforms = ['\x00\x01', '\x00\x03'];
-    var encodings = ['\x00\x00', '\x00\x01'];
-    var languages = ['\x00\x00', '\x04\x09'];
+    beginDrawing: function CanvasGraphics_beginDrawing(transform, viewport,
+                                                       transparency) {
+      // For pdfs that use blend modes we have to clear the canvas else certain
+      // blend modes can look wrong since we'd be blending with a white
+      // backdrop. The problem with a transparent backdrop though is we then
+      // don't get sub pixel anti aliasing on text, creating temporary
+      // transparent canvas when we have blend modes.
+      var width = this.ctx.canvas.width;
+      var height = this.ctx.canvas.height;
 
-    var namesRecordCount = strings.length * platforms.length;
-    var nameTable =
-      '\x00\x00' +                           // format
-      string16(namesRecordCount) +           // Number of names Record
-      string16(namesRecordCount * 12 + 6);   // Storage
+      this.ctx.save();
+      this.ctx.fillStyle = 'rgb(255, 255, 255)';
+      this.ctx.fillRect(0, 0, width, height);
+      this.ctx.restore();
 
-    // Build the name records field
-    var strOffset = 0;
-    for (i = 0, ii = platforms.length; i < ii; i++) {
-      var strs = names[i];
-      for (j = 0, jj = strs.length; j < jj; j++) {
-        str = strs[j];
-        var nameRecord =
-          platforms[i] + // platform ID
-          encodings[i] + // encoding ID
-          languages[i] + // language ID
-          string16(j) + // name ID
-          string16(str.length) +
-          string16(strOffset);
-        nameTable += nameRecord;
-        strOffset += str.length;
+      if (transparency) {
+        var transparentCanvas = this.cachedCanvases.getCanvas(
+          'transparent', width, height, true);
+        this.compositeCtx = this.ctx;
+        this.transparentCanvas = transparentCanvas.canvas;
+        this.ctx = transparentCanvas.context;
+        this.ctx.save();
+        // The transform can be applied before rendering, transferring it to
+        // the new canvas.
+        this.ctx.transform.apply(this.ctx,
+                                 this.compositeCtx.mozCurrentTransform);
       }
-    }
 
-    nameTable += strings.join('') + stringsUnicode.join('');
-    return nameTable;
-  }
+      this.ctx.save();
+      if (transform) {
+        this.ctx.transform.apply(this.ctx, transform);
+      }
+      this.ctx.transform.apply(this.ctx, viewport.transform);
 
-  Font.prototype = {
-    name: null,
-    font: null,
-    mimetype: null,
-    encoding: null,
-    get renderer() {
-      var renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED);
-      return shadow(this, 'renderer', renderer);
-    },
+      this.baseTransform = this.ctx.mozCurrentTransform.slice();
 
-    exportData: function Font_exportData() {
-      // TODO remove enumerating of the properties, e.g. hardcode exact names.
-      var data = {};
-      for (var i in this) {
-        if (this.hasOwnProperty(i)) {
-          data[i] = this[i];
-        }
+      if (this.imageLayer) {
+        this.imageLayer.beginLayout();
       }
-      return data;
     },
 
-    checkAndRepair: function Font_checkAndRepair(name, font, properties) {
-      function readTableEntry(file) {
-        var tag = bytesToString(file.getBytes(4));
+    executeOperatorList: function CanvasGraphics_executeOperatorList(
+                                    operatorList,
+                                    executionStartIdx, continueCallback,
+                                    stepper) {
+      var argsArray = operatorList.argsArray;
+      var fnArray = operatorList.fnArray;
+      var i = executionStartIdx || 0;
+      var argsArrayLen = argsArray.length;
 
-        var checksum = file.getInt32() >>> 0;
-        var offset = file.getInt32() >>> 0;
-        var length = file.getInt32() >>> 0;
+      // Sometimes the OperatorList to execute is empty.
+      if (argsArrayLen === i) {
+        return i;
+      }
 
-        // Read the table associated data
-        var previousPosition = file.pos;
-        file.pos = file.start ? file.start : 0;
-        file.skip(offset);
-        var data = file.getBytes(length);
-        file.pos = previousPosition;
-
-        if (tag === 'head') {
-          // clearing checksum adjustment
-          data[8] = data[9] = data[10] = data[11] = 0;
-          data[17] |= 0x20; //Set font optimized for cleartype flag
-        }
-
-        return {
-          tag: tag,
-          checksum: checksum,
-          length: length,
-          offset: offset,
-          data: data
-        };
-      }
+      var chunkOperations = (argsArrayLen - i > EXECUTION_STEPS &&
+                             typeof continueCallback === 'function');
+      var endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0;
+      var steps = 0;
 
-      function readOpenTypeHeader(ttf) {
-        return {
-          version: bytesToString(ttf.getBytes(4)),
-          numTables: ttf.getUint16(),
-          searchRange: ttf.getUint16(),
-          entrySelector: ttf.getUint16(),
-          rangeShift: ttf.getUint16()
-        };
-      }
+      var commonObjs = this.commonObjs;
+      var objs = this.objs;
+      var fnId;
 
-      /**
-       * Read the appropriate subtable from the cmap according to 9.6.6.4 from
-       * PDF spec
-       */
-      function readCmapTable(cmap, font, isSymbolicFont, hasEncoding) {
-        if (!cmap) {
-          warn('No cmap table available.');
-          return {
-            platformId: -1,
-            encodingId: -1,
-            mappings: [],
-            hasShortCmap: false
-          };
+      while (true) {
+        if (stepper !== undefined && i === stepper.nextBreakPoint) {
+          stepper.breakIt(i, continueCallback);
+          return i;
         }
-        var segment;
-        var start = (font.start ? font.start : 0) + cmap.offset;
-        font.pos = start;
 
-        var version = font.getUint16();
-        var numTables = font.getUint16();
+        fnId = fnArray[i];
 
-        var potentialTable;
-        var canBreak = false;
-        // There's an order of preference in terms of which cmap subtable to
-        // use:
-        // - non-symbolic fonts the preference is a 3,1 table then a 1,0 table
-        // - symbolic fonts the preference is a 3,0 table then a 1,0 table
-        // The following takes advantage of the fact that the tables are sorted
-        // to work.
-        for (var i = 0; i < numTables; i++) {
-          var platformId = font.getUint16();
-          var encodingId = font.getUint16();
-          var offset = font.getInt32() >>> 0;
-          var useTable = false;
+        if (fnId !== OPS.dependency) {
+          this[fnId].apply(this, argsArray[i]);
+        } else {
+          var deps = argsArray[i];
+          for (var n = 0, nn = deps.length; n < nn; n++) {
+            var depObjId = deps[n];
+            var common = depObjId[0] === 'g' && depObjId[1] === '_';
+            var objsPool = common ? commonObjs : objs;
 
-          if (platformId === 0 && encodingId === 0) {
-            useTable = true;
-            // Continue the loop since there still may be a higher priority
-            // table.
-          } else if (platformId === 1 && encodingId === 0) {
-            useTable = true;
-            // Continue the loop since there still may be a higher priority
-            // table.
-          } else if (platformId === 3 && encodingId === 1 &&
-                     ((!isSymbolicFont && hasEncoding) || !potentialTable)) {
-            useTable = true;
-            if (!isSymbolicFont) {
-              canBreak = true;
+            // If the promise isn't resolved yet, add the continueCallback
+            // to the promise and bail out.
+            if (!objsPool.isResolved(depObjId)) {
+              objsPool.get(depObjId, continueCallback);
+              return i;
             }
-          } else if (isSymbolicFont && platformId === 3 && encodingId === 0) {
-            useTable = true;
-            canBreak = true;
-          }
-
-          if (useTable) {
-            potentialTable = {
-              platformId: platformId,
-              encodingId: encodingId,
-              offset: offset
-            };
-          }
-          if (canBreak) {
-            break;
           }
         }
 
-        if (potentialTable) {
-          font.pos = start + potentialTable.offset;
-        }
-        if (!potentialTable || font.peekByte() === -1) {
-          warn('Could not find a preferred cmap table.');
-          return {
-            platformId: -1,
-            encodingId: -1,
-            mappings: [],
-            hasShortCmap: false
-          };
-        }
-
-        var format = font.getUint16();
-        var length = font.getUint16();
-        var language = font.getUint16();
+        i++;
 
-        var hasShortCmap = false;
-        var mappings = [];
-        var j, glyphId;
+        // If the entire operatorList was executed, stop as were done.
+        if (i === argsArrayLen) {
+          return i;
+        }
 
-        // TODO(mack): refactor this cmap subtable reading logic out
-        if (format === 0) {
-          for (j = 0; j < 256; j++) {
-            var index = font.getByte();
-            if (!index) {
-              continue;
-            }
-            mappings.push({
-              charCode: j,
-              glyphId: index
-            });
-          }
-          hasShortCmap = true;
-        } else if (format === 4) {
-          // re-creating the table in format 4 since the encoding
-          // might be changed
-          var segCount = (font.getUint16() >> 1);
-          font.getBytes(6); // skipping range fields
-          var segIndex, segments = [];
-          for (segIndex = 0; segIndex < segCount; segIndex++) {
-            segments.push({ end: font.getUint16() });
-          }
-          font.getUint16();
-          for (segIndex = 0; segIndex < segCount; segIndex++) {
-            segments[segIndex].start = font.getUint16();
+        // If the execution took longer then a certain amount of time and
+        // `continueCallback` is specified, interrupt the execution.
+        if (chunkOperations && ++steps > EXECUTION_STEPS) {
+          if (Date.now() > endTime) {
+            continueCallback();
+            return i;
           }
+          steps = 0;
+        }
 
-          for (segIndex = 0; segIndex < segCount; segIndex++) {
-            segments[segIndex].delta = font.getUint16();
-          }
+        // If the operatorList isn't executed completely yet OR the execution
+        // time was short enough, do another execution round.
+      }
+    },
 
-          var offsetsCount = 0;
-          for (segIndex = 0; segIndex < segCount; segIndex++) {
-            segment = segments[segIndex];
-            var rangeOffset = font.getUint16();
-            if (!rangeOffset) {
-              segment.offsetIndex = -1;
-              continue;
-            }
+    endDrawing: function CanvasGraphics_endDrawing() {
+      this.ctx.restore();
 
-            var offsetIndex = (rangeOffset >> 1) - (segCount - segIndex);
-            segment.offsetIndex = offsetIndex;
-            offsetsCount = Math.max(offsetsCount, offsetIndex +
-                                    segment.end - segment.start + 1);
-          }
+      if (this.transparentCanvas) {
+        this.ctx = this.compositeCtx;
+        this.ctx.save();
+        this.ctx.setTransform(1, 0, 0, 1, 0, 0); // Avoid apply transform twice
+        this.ctx.drawImage(this.transparentCanvas, 0, 0);
+        this.ctx.restore();
+        this.transparentCanvas = null;
+      }
 
-          var offsets = [];
-          for (j = 0; j < offsetsCount; j++) {
-            offsets.push(font.getUint16());
-          }
+      this.cachedCanvases.clear();
+      WebGLUtils.clear();
 
-          for (segIndex = 0; segIndex < segCount; segIndex++) {
-            segment = segments[segIndex];
-            start = segment.start;
-            var end = segment.end;
-            var delta = segment.delta;
-            offsetIndex = segment.offsetIndex;
+      if (this.imageLayer) {
+        this.imageLayer.endLayout();
+      }
+    },
 
-            for (j = start; j <= end; j++) {
-              if (j === 0xFFFF) {
-                continue;
-              }
+    // Graphics state
+    setLineWidth: function CanvasGraphics_setLineWidth(width) {
+      this.current.lineWidth = width;
+      this.ctx.lineWidth = width;
+    },
+    setLineCap: function CanvasGraphics_setLineCap(style) {
+      this.ctx.lineCap = LINE_CAP_STYLES[style];
+    },
+    setLineJoin: function CanvasGraphics_setLineJoin(style) {
+      this.ctx.lineJoin = LINE_JOIN_STYLES[style];
+    },
+    setMiterLimit: function CanvasGraphics_setMiterLimit(limit) {
+      this.ctx.miterLimit = limit;
+    },
+    setDash: function CanvasGraphics_setDash(dashArray, dashPhase) {
+      var ctx = this.ctx;
+      if (ctx.setLineDash !== undefined) {
+        ctx.setLineDash(dashArray);
+        ctx.lineDashOffset = dashPhase;
+      } else {
+        ctx.mozDash = dashArray;
+        ctx.mozDashOffset = dashPhase;
+      }
+    },
+    setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) {
+      // Maybe if we one day fully support color spaces this will be important
+      // for now we can ignore.
+      // TODO set rendering intent?
+    },
+    setFlatness: function CanvasGraphics_setFlatness(flatness) {
+      // There's no way to control this with canvas, but we can safely ignore.
+      // TODO set flatness?
+    },
+    setGState: function CanvasGraphics_setGState(states) {
+      for (var i = 0, ii = states.length; i < ii; i++) {
+        var state = states[i];
+        var key = state[0];
+        var value = state[1];
 
-              glyphId = (offsetIndex < 0 ?
-                         j : offsets[offsetIndex + j - start]);
-              glyphId = (glyphId + delta) & 0xFFFF;
-              if (glyphId === 0) {
-                continue;
+        switch (key) {
+          case 'LW':
+            this.setLineWidth(value);
+            break;
+          case 'LC':
+            this.setLineCap(value);
+            break;
+          case 'LJ':
+            this.setLineJoin(value);
+            break;
+          case 'ML':
+            this.setMiterLimit(value);
+            break;
+          case 'D':
+            this.setDash(value[0], value[1]);
+            break;
+          case 'RI':
+            this.setRenderingIntent(value);
+            break;
+          case 'FL':
+            this.setFlatness(value);
+            break;
+          case 'Font':
+            this.setFont(value[0], value[1]);
+            break;
+          case 'CA':
+            this.current.strokeAlpha = state[1];
+            break;
+          case 'ca':
+            this.current.fillAlpha = state[1];
+            this.ctx.globalAlpha = state[1];
+            break;
+          case 'BM':
+            if (value && value.name && (value.name !== 'Normal')) {
+              var mode = value.name.replace(/([A-Z])/g,
+                function(c) {
+                  return '-' + c.toLowerCase();
+                }
+              ).substring(1);
+              this.ctx.globalCompositeOperation = mode;
+              if (this.ctx.globalCompositeOperation !== mode) {
+                warn('globalCompositeOperation "' + mode +
+                     '" is not supported');
               }
-              mappings.push({
-                charCode: j,
-                glyphId: glyphId
-              });
+            } else {
+              this.ctx.globalCompositeOperation = 'source-over';
             }
-          }
-        } else if (format === 6) {
-          // Format 6 is a 2-bytes dense mapping, which means the font data
-          // lives glue together even if they are pretty far in the unicode
-          // table. (This looks weird, so I can have missed something), this
-          // works on Linux but seems to fails on Mac so let's rewrite the
-          // cmap table to a 3-1-4 style
-          var firstCode = font.getUint16();
-          var entryCount = font.getUint16();
-
-          for (j = 0; j < entryCount; j++) {
-            glyphId = font.getUint16();
-            var charCode = firstCode + j;
-
-            mappings.push({
-              charCode: charCode,
-              glyphId: glyphId
-            });
-          }
-        } else {
-          warn('cmap table has unsupported format: ' + format);
-          return {
-            platformId: -1,
-            encodingId: -1,
-            mappings: [],
-            hasShortCmap: false
-          };
+            break;
+          case 'SMask':
+            if (this.current.activeSMask) {
+              this.endSMaskGroup();
+            }
+            this.current.activeSMask = value ? this.tempSMask : null;
+            if (this.current.activeSMask) {
+              this.beginSMaskGroup();
+            }
+            this.tempSMask = null;
+            break;
         }
+      }
+    },
+    beginSMaskGroup: function CanvasGraphics_beginSMaskGroup() {
 
-        // removing duplicate entries
-        mappings.sort(function (a, b) {
-          return a.charCode - b.charCode;
-        });
-        for (i = 1; i < mappings.length; i++) {
-          if (mappings[i - 1].charCode === mappings[i].charCode) {
-            mappings.splice(i, 1);
-            i--;
-          }
-        }
+      var activeSMask = this.current.activeSMask;
+      var drawnWidth = activeSMask.canvas.width;
+      var drawnHeight = activeSMask.canvas.height;
+      var cacheId = 'smaskGroupAt' + this.groupLevel;
+      var scratchCanvas = this.cachedCanvases.getCanvas(
+        cacheId, drawnWidth, drawnHeight, true);
 
-        return {
-          platformId: potentialTable.platformId,
-          encodingId: potentialTable.encodingId,
-          mappings: mappings,
-          hasShortCmap: hasShortCmap
-        };
-      }
+      var currentCtx = this.ctx;
+      var currentTransform = currentCtx.mozCurrentTransform;
+      this.ctx.save();
 
-      function sanitizeMetrics(font, header, metrics, numGlyphs) {
-        if (!header) {
-          if (metrics) {
-            metrics.data = null;
-          }
-          return;
-        }
+      var groupCtx = scratchCanvas.context;
+      groupCtx.scale(1 / activeSMask.scaleX, 1 / activeSMask.scaleY);
+      groupCtx.translate(-activeSMask.offsetX, -activeSMask.offsetY);
+      groupCtx.transform.apply(groupCtx, currentTransform);
 
-        font.pos = (font.start ? font.start : 0) + header.offset;
-        font.pos += header.length - 2;
-        var numOfMetrics = font.getUint16();
+      copyCtxState(currentCtx, groupCtx);
+      this.ctx = groupCtx;
+      this.setGState([
+        ['BM', 'Normal'],
+        ['ca', 1],
+        ['CA', 1]
+      ]);
+      this.groupStack.push(currentCtx);
+      this.groupLevel++;
+    },
+    endSMaskGroup: function CanvasGraphics_endSMaskGroup() {
+      var groupCtx = this.ctx;
+      this.groupLevel--;
+      this.ctx = this.groupStack.pop();
 
-        if (numOfMetrics > numGlyphs) {
-          info('The numOfMetrics (' + numOfMetrics + ') should not be ' +
-               'greater than the numGlyphs (' + numGlyphs + ')');
-          // Reduce numOfMetrics if it is greater than numGlyphs
-          numOfMetrics = numGlyphs;
-          header.data[34] = (numOfMetrics & 0xff00) >> 8;
-          header.data[35] = numOfMetrics & 0x00ff;
+      composeSMask(this.ctx, this.current.activeSMask, groupCtx);
+      this.ctx.restore();
+      copyCtxState(groupCtx, this.ctx);
+    },
+    save: function CanvasGraphics_save() {
+      this.ctx.save();
+      var old = this.current;
+      this.stateStack.push(old);
+      this.current = old.clone();
+      this.current.activeSMask = null;
+    },
+    restore: function CanvasGraphics_restore() {
+      if (this.stateStack.length !== 0) {
+        if (this.current.activeSMask !== null) {
+          this.endSMaskGroup();
         }
 
-        var numOfSidebearings = numGlyphs - numOfMetrics;
-        var numMissing = numOfSidebearings -
-          ((metrics.length - numOfMetrics * 4) >> 1);
+        this.current = this.stateStack.pop();
+        this.ctx.restore();
 
-        if (numMissing > 0) {
-          // For each missing glyph, we set both the width and lsb to 0 (zero).
-          // Since we need to add two properties for each glyph, this explains
-          // the use of |numMissing * 2| when initializing the typed array.
-          var entries = new Uint8Array(metrics.length + numMissing * 2);
-          entries.set(metrics.data);
-          metrics.data = entries;
-        }
+        // Ensure that the clipping path is reset (fixes issue6413.pdf).
+        this.pendingClip = null;
+
+        this.cachedGetSinglePixelWidth = null;
       }
+    },
+    transform: function CanvasGraphics_transform(a, b, c, d, e, f) {
+      this.ctx.transform(a, b, c, d, e, f);
 
-      function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart,
-                             hintsValid) {
-        if (sourceEnd - sourceStart <= 12) {
-          // glyph with data less than 12 is invalid one
-          return 0;
-        }
-        var glyf = source.subarray(sourceStart, sourceEnd);
-        var contoursCount = (glyf[0] << 8) | glyf[1];
-        if (contoursCount & 0x8000) {
-          // complex glyph, writing as is
-          dest.set(glyf, destStart);
-          return glyf.length;
-        }
+      this.cachedGetSinglePixelWidth = null;
+    },
 
-        var i, j = 10, flagsCount = 0;
-        for (i = 0; i < contoursCount; i++) {
-          var endPoint = (glyf[j] << 8) | glyf[j + 1];
-          flagsCount = endPoint + 1;
-          j += 2;
-        }
-        // skipping instructions
-        var instructionsStart = j;
-        var instructionsLength = (glyf[j] << 8) | glyf[j + 1];
-        j += 2 + instructionsLength;
-        var instructionsEnd = j;
-        // validating flags
-        var coordinatesLength = 0;
-        for (i = 0; i < flagsCount; i++) {
-          var flag = glyf[j++];
-          if (flag & 0xC0) {
-            // reserved flags must be zero, cleaning up
-            glyf[j - 1] = flag & 0x3F;
-          }
-          var xyLength = ((flag & 2) ? 1 : (flag & 16) ? 0 : 2) +
-                         ((flag & 4) ? 1 : (flag & 32) ? 0 : 2);
-          coordinatesLength += xyLength;
-          if (flag & 8) {
-            var repeat = glyf[j++];
-            i += repeat;
-            coordinatesLength += repeat * xyLength;
-          }
-        }
-        // glyph without coordinates will be rejected
-        if (coordinatesLength === 0) {
-          return 0;
-        }
-        var glyphDataLength = j + coordinatesLength;
-        if (glyphDataLength > glyf.length) {
-          // not enough data for coordinates
-          return 0;
-        }
-        if (!hintsValid && instructionsLength > 0) {
-          dest.set(glyf.subarray(0, instructionsStart), destStart);
-          dest.set([0, 0], destStart + instructionsStart);
-          dest.set(glyf.subarray(instructionsEnd, glyphDataLength),
-                   destStart + instructionsStart + 2);
-          glyphDataLength -= instructionsLength;
-          if (glyf.length - glyphDataLength > 3) {
-            glyphDataLength = (glyphDataLength + 3) & ~3;
-          }
-          return glyphDataLength;
-        }
-        if (glyf.length - glyphDataLength > 3) {
-          // truncating and aligning to 4 bytes the long glyph data
-          glyphDataLength = (glyphDataLength + 3) & ~3;
-          dest.set(glyf.subarray(0, glyphDataLength), destStart);
-          return glyphDataLength;
+    // Path
+    constructPath: function CanvasGraphics_constructPath(ops, args) {
+      var ctx = this.ctx;
+      var current = this.current;
+      var x = current.x, y = current.y;
+      for (var i = 0, j = 0, ii = ops.length; i < ii; i++) {
+        switch (ops[i] | 0) {
+          case OPS.rectangle:
+            x = args[j++];
+            y = args[j++];
+            var width = args[j++];
+            var height = args[j++];
+            if (width === 0) {
+              width = this.getSinglePixelWidth();
+            }
+            if (height === 0) {
+              height = this.getSinglePixelWidth();
+            }
+            var xw = x + width;
+            var yh = y + height;
+            this.ctx.moveTo(x, y);
+            this.ctx.lineTo(xw, y);
+            this.ctx.lineTo(xw, yh);
+            this.ctx.lineTo(x, yh);
+            this.ctx.lineTo(x, y);
+            this.ctx.closePath();
+            break;
+          case OPS.moveTo:
+            x = args[j++];
+            y = args[j++];
+            ctx.moveTo(x, y);
+            break;
+          case OPS.lineTo:
+            x = args[j++];
+            y = args[j++];
+            ctx.lineTo(x, y);
+            break;
+          case OPS.curveTo:
+            x = args[j + 4];
+            y = args[j + 5];
+            ctx.bezierCurveTo(args[j], args[j + 1], args[j + 2], args[j + 3],
+                              x, y);
+            j += 6;
+            break;
+          case OPS.curveTo2:
+            ctx.bezierCurveTo(x, y, args[j], args[j + 1],
+                              args[j + 2], args[j + 3]);
+            x = args[j + 2];
+            y = args[j + 3];
+            j += 4;
+            break;
+          case OPS.curveTo3:
+            x = args[j + 2];
+            y = args[j + 3];
+            ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y);
+            j += 4;
+            break;
+          case OPS.closePath:
+            ctx.closePath();
+            break;
         }
-        // glyph data is fine
-        dest.set(glyf, destStart);
-        return glyf.length;
       }
+      current.setCurrentPoint(x, y);
+    },
+    closePath: function CanvasGraphics_closePath() {
+      this.ctx.closePath();
+    },
+    stroke: function CanvasGraphics_stroke(consumePath) {
+      consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
+      var ctx = this.ctx;
+      var strokeColor = this.current.strokeColor;
+      // Prevent drawing too thin lines by enforcing a minimum line width.
+      ctx.lineWidth = Math.max(this.getSinglePixelWidth() * MIN_WIDTH_FACTOR,
+                               this.current.lineWidth);
+      // For stroke we want to temporarily change the global alpha to the
+      // stroking alpha.
+      ctx.globalAlpha = this.current.strokeAlpha;
+      if (strokeColor && strokeColor.hasOwnProperty('type') &&
+          strokeColor.type === 'Pattern') {
+        // for patterns, we transform to pattern space, calculate
+        // the pattern, call stroke, and restore to user space
+        ctx.save();
+        ctx.strokeStyle = strokeColor.getPattern(ctx, this);
+        ctx.stroke();
+        ctx.restore();
+      } else {
+        ctx.stroke();
+      }
+      if (consumePath) {
+        this.consumePath();
+      }
+      // Restore the global alpha to the fill alpha
+      ctx.globalAlpha = this.current.fillAlpha;
+    },
+    closeStroke: function CanvasGraphics_closeStroke() {
+      this.closePath();
+      this.stroke();
+    },
+    fill: function CanvasGraphics_fill(consumePath) {
+      consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
+      var ctx = this.ctx;
+      var fillColor = this.current.fillColor;
+      var isPatternFill = this.current.patternFill;
+      var needRestore = false;
 
-      function sanitizeHead(head, numGlyphs, locaLength) {
-        var data = head.data;
-
-        // Validate version:
-        // Should always be 0x00010000
-        var version = int32(data[0], data[1], data[2], data[3]);
-        if (version >> 16 !== 1) {
-          info('Attempting to fix invalid version in head table: ' + version);
-          data[0] = 0;
-          data[1] = 1;
-          data[2] = 0;
-          data[3] = 0;
-        }
-
-        var indexToLocFormat = int16(data[50], data[51]);
-        if (indexToLocFormat < 0 || indexToLocFormat > 1) {
-          info('Attempting to fix invalid indexToLocFormat in head table: ' +
-               indexToLocFormat);
-
-          // The value of indexToLocFormat should be 0 if the loca table
-          // consists of short offsets, and should be 1 if the loca table
-          // consists of long offsets.
-          //
-          // The number of entries in the loca table should be numGlyphs + 1.
-          //
-          // Using this information, we can work backwards to deduce if the
-          // size of each offset in the loca table, and thus figure out the
-          // appropriate value for indexToLocFormat.
-
-          var numGlyphsPlusOne = numGlyphs + 1;
-          if (locaLength === numGlyphsPlusOne << 1) {
-            // 0x0000 indicates the loca table consists of short offsets
-            data[50] = 0;
-            data[51] = 0;
-          } else if (locaLength === numGlyphsPlusOne << 2) {
-            // 0x0001 indicates the loca table consists of long offsets
-            data[50] = 0;
-            data[51] = 1;
-          } else {
-            warn('Could not fix indexToLocFormat: ' + indexToLocFormat);
-          }
+      if (isPatternFill) {
+        ctx.save();
+        if (this.baseTransform) {
+          ctx.setTransform.apply(ctx, this.baseTransform);
         }
+        ctx.fillStyle = fillColor.getPattern(ctx, this);
+        needRestore = true;
       }
 
-      function sanitizeGlyphLocations(loca, glyf, numGlyphs,
-                                      isGlyphLocationsLong, hintsValid,
-                                      dupFirstEntry) {
-        var itemSize, itemDecode, itemEncode;
-        if (isGlyphLocationsLong) {
-          itemSize = 4;
-          itemDecode = function fontItemDecodeLong(data, offset) {
-            return (data[offset] << 24) | (data[offset + 1] << 16) |
-                   (data[offset + 2] << 8) | data[offset + 3];
-          };
-          itemEncode = function fontItemEncodeLong(data, offset, value) {
-            data[offset] = (value >>> 24) & 0xFF;
-            data[offset + 1] = (value >> 16) & 0xFF;
-            data[offset + 2] = (value >> 8) & 0xFF;
-            data[offset + 3] = value & 0xFF;
-          };
+      if (this.pendingEOFill) {
+        if (ctx.mozFillRule !== undefined) {
+          ctx.mozFillRule = 'evenodd';
+          ctx.fill();
+          ctx.mozFillRule = 'nonzero';
         } else {
-          itemSize = 2;
-          itemDecode = function fontItemDecode(data, offset) {
-            return (data[offset] << 9) | (data[offset + 1] << 1);
-          };
-          itemEncode = function fontItemEncode(data, offset, value) {
-            data[offset] = (value >> 9) & 0xFF;
-            data[offset + 1] = (value >> 1) & 0xFF;
-          };
-        }
-        var locaData = loca.data;
-        var locaDataSize = itemSize * (1 + numGlyphs);
-        // is loca.data too short or long?
-        if (locaData.length !== locaDataSize) {
-          locaData = new Uint8Array(locaDataSize);
-          locaData.set(loca.data.subarray(0, locaDataSize));
-          loca.data = locaData;
+          ctx.fill('evenodd');
         }
-        // removing the invalid glyphs
-        var oldGlyfData = glyf.data;
-        var oldGlyfDataLength = oldGlyfData.length;
-        var newGlyfData = new Uint8Array(oldGlyfDataLength);
-        var startOffset = itemDecode(locaData, 0);
-        var writeOffset = 0;
-        var missingGlyphData = Object.create(null);
-        itemEncode(locaData, 0, writeOffset);
-        var i, j;
-        for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
-          var endOffset = itemDecode(locaData, j);
-          if (endOffset > oldGlyfDataLength &&
-              ((oldGlyfDataLength + 3) & ~3) === endOffset) {
-            // Aspose breaks fonts by aligning the glyphs to the qword, but not
-            // the glyf table size, which makes last glyph out of range.
-            endOffset = oldGlyfDataLength;
-          }
-          if (endOffset > oldGlyfDataLength) {
-            // glyph end offset points outside glyf data, rejecting the glyph
-            itemEncode(locaData, j, writeOffset);
-            startOffset = endOffset;
-            continue;
-          }
+        this.pendingEOFill = false;
+      } else {
+        ctx.fill();
+      }
 
-          if (startOffset === endOffset) {
-            missingGlyphData[i] = true;
-          }
+      if (needRestore) {
+        ctx.restore();
+      }
+      if (consumePath) {
+        this.consumePath();
+      }
+    },
+    eoFill: function CanvasGraphics_eoFill() {
+      this.pendingEOFill = true;
+      this.fill();
+    },
+    fillStroke: function CanvasGraphics_fillStroke() {
+      this.fill(false);
+      this.stroke(false);
 
-          var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset,
-                                        newGlyfData, writeOffset, hintsValid);
-          writeOffset += newLength;
-          itemEncode(locaData, j, writeOffset);
-          startOffset = endOffset;
-        }
+      this.consumePath();
+    },
+    eoFillStroke: function CanvasGraphics_eoFillStroke() {
+      this.pendingEOFill = true;
+      this.fillStroke();
+    },
+    closeFillStroke: function CanvasGraphics_closeFillStroke() {
+      this.closePath();
+      this.fillStroke();
+    },
+    closeEOFillStroke: function CanvasGraphics_closeEOFillStroke() {
+      this.pendingEOFill = true;
+      this.closePath();
+      this.fillStroke();
+    },
+    endPath: function CanvasGraphics_endPath() {
+      this.consumePath();
+    },
 
-        if (writeOffset === 0) {
-          // glyf table cannot be empty -- redoing the glyf and loca tables
-          // to have single glyph with one point
-          var simpleGlyph = new Uint8Array(
-            [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]);
-          for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
-            itemEncode(locaData, j, simpleGlyph.length);
-          }
-          glyf.data = simpleGlyph;
-          return missingGlyphData;
-        }
+    // Clipping
+    clip: function CanvasGraphics_clip() {
+      this.pendingClip = NORMAL_CLIP;
+    },
+    eoClip: function CanvasGraphics_eoClip() {
+      this.pendingClip = EO_CLIP;
+    },
 
-        if (dupFirstEntry) {
-          var firstEntryLength = itemDecode(locaData, itemSize);
-          if (newGlyfData.length > firstEntryLength + writeOffset) {
-            glyf.data = newGlyfData.subarray(0, firstEntryLength + writeOffset);
-          } else {
-            glyf.data = new Uint8Array(firstEntryLength + writeOffset);
-            glyf.data.set(newGlyfData.subarray(0, writeOffset));
-          }
-          glyf.data.set(newGlyfData.subarray(0, firstEntryLength), writeOffset);
-          itemEncode(loca.data, locaData.length - itemSize,
-                     writeOffset + firstEntryLength);
-        } else {
-          glyf.data = newGlyfData.subarray(0, writeOffset);
-        }
-        return missingGlyphData;
+    // Text
+    beginText: function CanvasGraphics_beginText() {
+      this.current.textMatrix = IDENTITY_MATRIX;
+      this.current.textMatrixScale = 1;
+      this.current.x = this.current.lineX = 0;
+      this.current.y = this.current.lineY = 0;
+    },
+    endText: function CanvasGraphics_endText() {
+      var paths = this.pendingTextPaths;
+      var ctx = this.ctx;
+      if (paths === undefined) {
+        ctx.beginPath();
+        return;
       }
 
-      function readPostScriptTable(post, properties, maxpNumGlyphs) {
-        var start = (font.start ? font.start : 0) + post.offset;
-        font.pos = start;
+      ctx.save();
+      ctx.beginPath();
+      for (var i = 0; i < paths.length; i++) {
+        var path = paths[i];
+        ctx.setTransform.apply(ctx, path.transform);
+        ctx.translate(path.x, path.y);
+        path.addToPath(ctx, path.fontSize);
+      }
+      ctx.restore();
+      ctx.clip();
+      ctx.beginPath();
+      delete this.pendingTextPaths;
+    },
+    setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) {
+      this.current.charSpacing = spacing;
+    },
+    setWordSpacing: function CanvasGraphics_setWordSpacing(spacing) {
+      this.current.wordSpacing = spacing;
+    },
+    setHScale: function CanvasGraphics_setHScale(scale) {
+      this.current.textHScale = scale / 100;
+    },
+    setLeading: function CanvasGraphics_setLeading(leading) {
+      this.current.leading = -leading;
+    },
+    setFont: function CanvasGraphics_setFont(fontRefName, size) {
+      var fontObj = this.commonObjs.get(fontRefName);
+      var current = this.current;
 
-        var length = post.length, end = start + length;
-        var version = font.getInt32();
-        // skip rest to the tables
-        font.getBytes(28);
+      if (!fontObj) {
+        error('Can\'t find font for ' + fontRefName);
+      }
 
-        var glyphNames;
-        var valid = true;
-        var i;
+      current.fontMatrix = (fontObj.fontMatrix ?
+                            fontObj.fontMatrix : FONT_IDENTITY_MATRIX);
 
-        switch (version) {
-          case 0x00010000:
-            glyphNames = MacStandardGlyphOrdering;
-            break;
-          case 0x00020000:
-            var numGlyphs = font.getUint16();
-            if (numGlyphs !== maxpNumGlyphs) {
-              valid = false;
-              break;
-            }
-            var glyphNameIndexes = [];
-            for (i = 0; i < numGlyphs; ++i) {
-              var index = font.getUint16();
-              if (index >= 32768) {
-                valid = false;
-                break;
-              }
-              glyphNameIndexes.push(index);
-            }
-            if (!valid) {
-              break;
-            }
-            var customNames = [];
-            var strBuf = [];
-            while (font.pos < end) {
-              var stringLength = font.getByte();
-              strBuf.length = stringLength;
-              for (i = 0; i < stringLength; ++i) {
-                strBuf[i] = String.fromCharCode(font.getByte());
-              }
-              customNames.push(strBuf.join(''));
-            }
-            glyphNames = [];
-            for (i = 0; i < numGlyphs; ++i) {
-              var j = glyphNameIndexes[i];
-              if (j < 258) {
-                glyphNames.push(MacStandardGlyphOrdering[j]);
-                continue;
-              }
-              glyphNames.push(customNames[j - 258]);
-            }
-            break;
-          case 0x00030000:
-            break;
-          default:
-            warn('Unknown/unsupported post table version ' + version);
-            valid = false;
-            if (properties.defaultEncoding) {
-              glyphNames = properties.defaultEncoding;
-            }
-            break;
-        }
-        properties.glyphNames = glyphNames;
-        return valid;
+      // A valid matrix needs all main diagonal elements to be non-zero
+      // This also ensures we bypass FF bugzilla bug #719844.
+      if (current.fontMatrix[0] === 0 ||
+          current.fontMatrix[3] === 0) {
+        warn('Invalid font matrix for font ' + fontRefName);
       }
 
-      function readNameTable(nameTable) {
-        var start = (font.start ? font.start : 0) + nameTable.offset;
-        font.pos = start;
+      // The spec for Tf (setFont) says that 'size' specifies the font 'scale',
+      // and in some docs this can be negative (inverted x-y axes).
+      if (size < 0) {
+        size = -size;
+        current.fontDirection = -1;
+      } else {
+        current.fontDirection = 1;
+      }
 
-        var names = [[], []];
-        var length = nameTable.length, end = start + length;
-        var format = font.getUint16();
-        var FORMAT_0_HEADER_LENGTH = 6;
-        if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) {
-          // unsupported name table format or table "too" small
-          return names;
-        }
-        var numRecords = font.getUint16();
-        var stringsStart = font.getUint16();
-        var records = [];
-        var NAME_RECORD_LENGTH = 12;
-        var i, ii;
+      this.current.font = fontObj;
+      this.current.fontSize = size;
 
-        for (i = 0; i < numRecords &&
-                        font.pos + NAME_RECORD_LENGTH <= end; i++) {
-          var r = {
-            platform: font.getUint16(),
-            encoding: font.getUint16(),
-            language: font.getUint16(),
-            name: font.getUint16(),
-            length: font.getUint16(),
-            offset: font.getUint16()
-          };
-          // using only Macintosh and Windows platform/encoding names
-          if ((r.platform === 1 && r.encoding === 0 && r.language === 0) ||
-              (r.platform === 3 && r.encoding === 1 && r.language === 0x409)) {
-            records.push(r);
-          }
-        }
-        for (i = 0, ii = records.length; i < ii; i++) {
-          var record = records[i];
-          if (record.length <= 0) {
-            continue; // Nothing to process, ignoring.
-          }
-          var pos = start + stringsStart + record.offset;
-          if (pos + record.length > end) {
-            continue; // outside of name table, ignoring
-          }
-          font.pos = pos;
-          var nameIndex = record.name;
-          if (record.encoding) {
-            // unicode
-            var str = '';
-            for (var j = 0, jj = record.length; j < jj; j += 2) {
-              str += String.fromCharCode(font.getUint16());
-            }
-            names[1][nameIndex] = str;
-          } else {
-            names[0][nameIndex] = bytesToString(font.getBytes(record.length));
-          }
-        }
-        return names;
+      if (fontObj.isType3Font) {
+        return; // we don't need ctx.font for Type3 fonts
       }
 
-      var TTOpsStackDeltas = [
-        0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5,
-        -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1,
-        1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1,
-        0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -999, 0, -2, -2,
-        0, 0, -2, 0, -2, 0, 0, 0, -2, -1, -1, 1, 1, 0, 0, -1,
-        -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, 0, -999, -1, -1,
-        -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-        -2, -999, -999, -999, -999, -999, -1, -1, -2, -2, 0, 0, 0, 0, -1, -1,
-        -999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2];
-        // 0xC0-DF == -1 and 0xE0-FF == -2
+      var name = fontObj.loadedName || 'sans-serif';
+      var bold = fontObj.black ? (fontObj.bold ? '900' : 'bold') :
+                                 (fontObj.bold ? 'bold' : 'normal');
 
-      function sanitizeTTProgram(table, ttContext) {
-        var data = table.data;
-        var i = 0, j, n, b, funcId, pc, lastEndf = 0, lastDeff = 0;
-        var stack = [];
-        var callstack = [];
-        var functionsCalled = [];
-        var tooComplexToFollowFunctions =
-          ttContext.tooComplexToFollowFunctions;
-        var inFDEF = false, ifLevel = 0, inELSE = 0;
-        for (var ii = data.length; i < ii;) {
-          var op = data[i++];
-          // The TrueType instruction set docs can be found at
-          // https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html
-          if (op === 0x40) { // NPUSHB - pushes n bytes
-            n = data[i++];
-            if (inFDEF || inELSE) {
-              i += n;
-            } else {
-              for (j = 0; j < n; j++) {
-                stack.push(data[i++]);
-              }
-            }
-          } else if (op === 0x41) { // NPUSHW - pushes n words
-            n = data[i++];
-            if (inFDEF || inELSE) {
-              i += n * 2;
-            } else {
-              for (j = 0; j < n; j++) {
-                b = data[i++];
-                stack.push((b << 8) | data[i++]);
-              }
-            }
-          } else if ((op & 0xF8) === 0xB0) { // PUSHB - pushes bytes
-            n = op - 0xB0 + 1;
-            if (inFDEF || inELSE) {
-              i += n;
-            } else {
-              for (j = 0; j < n; j++) {
-                stack.push(data[i++]);
-              }
-            }
-          } else if ((op & 0xF8) === 0xB8) { // PUSHW - pushes words
-            n = op - 0xB8 + 1;
-            if (inFDEF || inELSE) {
-              i += n * 2;
-            } else {
-              for (j = 0; j < n; j++) {
-                b = data[i++];
-                stack.push((b << 8) | data[i++]);
-              }
-            }
-          } else if (op === 0x2B && !tooComplexToFollowFunctions) { // CALL
-            if (!inFDEF && !inELSE) {
-              // collecting inforamtion about which functions are used
-              funcId = stack[stack.length - 1];
-              ttContext.functionsUsed[funcId] = true;
-              if (funcId in ttContext.functionsStackDeltas) {
-                stack.length += ttContext.functionsStackDeltas[funcId];
-              } else if (funcId in ttContext.functionsDefined &&
-                         functionsCalled.indexOf(funcId) < 0) {
-                callstack.push({data: data, i: i, stackTop: stack.length - 1});
-                functionsCalled.push(funcId);
-                pc = ttContext.functionsDefined[funcId];
-                if (!pc) {
-                  warn('TT: CALL non-existent function');
-                  ttContext.hintsValid = false;
-                  return;
-                }
-                data = pc.data;
-                i = pc.i;
-              }
-            }
-          } else if (op === 0x2C && !tooComplexToFollowFunctions) { // FDEF
-            if (inFDEF || inELSE) {
-              warn('TT: nested FDEFs not allowed');
-              tooComplexToFollowFunctions = true;
-            }
-            inFDEF = true;
-            // collecting inforamtion about which functions are defined
-            lastDeff = i;
-            funcId = stack.pop();
-            ttContext.functionsDefined[funcId] = {data: data, i: i};
-          } else if (op === 0x2D) { // ENDF - end of function
-            if (inFDEF) {
-              inFDEF = false;
-              lastEndf = i;
-            } else {
-              pc = callstack.pop();
-              if (!pc) {
-                warn('TT: ENDF bad stack');
-                ttContext.hintsValid = false;
-                return;
-              }
-              funcId = functionsCalled.pop();
-              data = pc.data;
-              i = pc.i;
-              ttContext.functionsStackDeltas[funcId] =
-                stack.length - pc.stackTop;
-            }
-          } else if (op === 0x89) { // IDEF - instruction definition
-            if (inFDEF || inELSE) {
-              warn('TT: nested IDEFs not allowed');
-              tooComplexToFollowFunctions = true;
-            }
-            inFDEF = true;
-            // recording it as a function to track ENDF
-            lastDeff = i;
-          } else if (op === 0x58) { // IF
-            ++ifLevel;
-          } else if (op === 0x1B) { // ELSE
-            inELSE = ifLevel;
-          } else if (op === 0x59) { // EIF
-            if (inELSE === ifLevel) {
-              inELSE = 0;
-            }
-            --ifLevel;
-          } else if (op === 0x1C) { // JMPR
-            if (!inFDEF && !inELSE) {
-              var offset = stack[stack.length - 1];
-              // only jumping forward to prevent infinite loop
-              if (offset > 0) {
-                i += offset - 1;
-              }
-            }
-          }
-          // Adjusting stack not extactly, but just enough to get function id
-          if (!inFDEF && !inELSE) {
-            var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] :
-              op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0;
-            if (op >= 0x71 && op <= 0x75) {
-              n = stack.pop();
-              if (n === n) {
-                stackDelta = -n * 2;
-              }
-            }
-            while (stackDelta < 0 && stack.length > 0) {
-              stack.pop();
-              stackDelta++;
-            }
-            while (stackDelta > 0) {
-              stack.push(NaN); // pushing any number into stack
-              stackDelta--;
-            }
-          }
-        }
-        ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions;
-        var content = [data];
-        if (i > data.length) {
-          content.push(new Uint8Array(i - data.length));
-        }
-        if (lastDeff > lastEndf) {
-          warn('TT: complementing a missing function tail');
-          // new function definition started, but not finished
-          // complete function by [CLEAR, ENDF]
-          content.push(new Uint8Array([0x22, 0x2D]));
-        }
-        foldTTTable(table, content);
-      }
-
-      function checkInvalidFunctions(ttContext, maxFunctionDefs) {
-        if (ttContext.tooComplexToFollowFunctions) {
-          return;
-        }
-        if (ttContext.functionsDefined.length > maxFunctionDefs) {
-          warn('TT: more functions defined than expected');
-          ttContext.hintsValid = false;
-          return;
-        }
-        for (var j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {
-          if (j > maxFunctionDefs) {
-            warn('TT: invalid function id: ' + j);
-            ttContext.hintsValid = false;
-            return;
-          }
-          if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) {
-            warn('TT: undefined function: ' + j);
-            ttContext.hintsValid = false;
-            return;
-          }
-        }
-      }
-
-      function foldTTTable(table, content) {
-        if (content.length > 1) {
-          // concatenating the content items
-          var newLength = 0;
-          var j, jj;
-          for (j = 0, jj = content.length; j < jj; j++) {
-            newLength += content[j].length;
-          }
-          newLength = (newLength + 3) & ~3;
-          var result = new Uint8Array(newLength);
-          var pos = 0;
-          for (j = 0, jj = content.length; j < jj; j++) {
-            result.set(content[j], pos);
-            pos += content[j].length;
-          }
-          table.data = result;
-          table.length = newLength;
-        }
-      }
-
-      function sanitizeTTPrograms(fpgm, prep, cvt) {
-        var ttContext = {
-          functionsDefined: [],
-          functionsUsed: [],
-          functionsStackDeltas: [],
-          tooComplexToFollowFunctions: false,
-          hintsValid: true
-        };
-        if (fpgm) {
-          sanitizeTTProgram(fpgm, ttContext);
-        }
-        if (prep) {
-          sanitizeTTProgram(prep, ttContext);
-        }
-        if (fpgm) {
-          checkInvalidFunctions(ttContext, maxFunctionDefs);
-        }
-        if (cvt && (cvt.length & 1)) {
-          var cvtData = new Uint8Array(cvt.length + 1);
-          cvtData.set(cvt.data);
-          cvt.data = cvtData;
-        }
-        return ttContext.hintsValid;
-      }
+      var italic = fontObj.italic ? 'italic' : 'normal';
+      var typeface = '"' + name + '", ' + fontObj.fallbackName;
 
-      // The following steps modify the original font data, making copy
-      font = new Stream(new Uint8Array(font.getBytes()));
+      // Some font backends cannot handle fonts below certain size.
+      // Keeping the font at minimal size and using the fontSizeScale to change
+      // the current transformation matrix before the fillText/strokeText.
+      // See https://bugzilla.mozilla.org/show_bug.cgi?id=726227
+      var browserFontSize = size < MIN_FONT_SIZE ? MIN_FONT_SIZE :
+                            size > MAX_FONT_SIZE ? MAX_FONT_SIZE : size;
+      this.current.fontSizeScale = size / browserFontSize;
 
-      var VALID_TABLES = ['OS/2', 'cmap', 'head', 'hhea', 'hmtx', 'maxp',
-        'name', 'post', 'loca', 'glyf', 'fpgm', 'prep', 'cvt ', 'CFF '];
+      var rule = italic + ' ' + bold + ' ' + browserFontSize + 'px ' + typeface;
+      this.ctx.font = rule;
+    },
+    setTextRenderingMode: function CanvasGraphics_setTextRenderingMode(mode) {
+      this.current.textRenderingMode = mode;
+    },
+    setTextRise: function CanvasGraphics_setTextRise(rise) {
+      this.current.textRise = rise;
+    },
+    moveText: function CanvasGraphics_moveText(x, y) {
+      this.current.x = this.current.lineX += x;
+      this.current.y = this.current.lineY += y;
+    },
+    setLeadingMoveText: function CanvasGraphics_setLeadingMoveText(x, y) {
+      this.setLeading(-y);
+      this.moveText(x, y);
+    },
+    setTextMatrix: function CanvasGraphics_setTextMatrix(a, b, c, d, e, f) {
+      this.current.textMatrix = [a, b, c, d, e, f];
+      this.current.textMatrixScale = Math.sqrt(a * a + b * b);
 
-      var header = readOpenTypeHeader(font);
-      var numTables = header.numTables;
-      var cff, cffFile;
+      this.current.x = this.current.lineX = 0;
+      this.current.y = this.current.lineY = 0;
+    },
+    nextLine: function CanvasGraphics_nextLine() {
+      this.moveText(0, this.current.leading);
+    },
 
-      var tables = Object.create(null);
-      tables['OS/2'] = null;
-      tables['cmap'] = null;
-      tables['head'] = null;
-      tables['hhea'] = null;
-      tables['hmtx'] = null;
-      tables['maxp'] = null;
-      tables['name'] = null;
-      tables['post'] = null;
+    paintChar: function CanvasGraphics_paintChar(character, x, y) {
+      var ctx = this.ctx;
+      var current = this.current;
+      var font = current.font;
+      var textRenderingMode = current.textRenderingMode;
+      var fontSize = current.fontSize / current.fontSizeScale;
+      var fillStrokeMode = textRenderingMode &
+        TextRenderingMode.FILL_STROKE_MASK;
+      var isAddToPathSet = !!(textRenderingMode &
+        TextRenderingMode.ADD_TO_PATH_FLAG);
 
-      var table;
-      for (var i = 0; i < numTables; i++) {
-        table = readTableEntry(font);
-        if (VALID_TABLES.indexOf(table.tag) < 0) {
-          continue; // skipping table if it's not a required or optional table
-        }
-        if (table.length === 0) {
-          continue; // skipping empty tables
-        }
-        tables[table.tag] = table;
+      var addToPath;
+      if (font.disableFontFace || isAddToPathSet) {
+        addToPath = font.getPathGenerator(this.commonObjs, character);
       }
 
-      var isTrueType = !tables['CFF '];
-      if (!isTrueType) {
-        // OpenType font
-        if ((header.version === 'OTTO' && properties.type !== 'CIDFontType2') ||
-            !tables['head'] || !tables['hhea'] || !tables['maxp'] ||
-            !tables['post']) {
-          // no major tables: throwing everything at CFFFont
-          cffFile = new Stream(tables['CFF '].data);
-          cff = new CFFFont(cffFile, properties);
-
-          adjustWidths(properties);
-
-          return this.convert(name, cff, properties);
+      if (font.disableFontFace) {
+        ctx.save();
+        ctx.translate(x, y);
+        ctx.beginPath();
+        addToPath(ctx, fontSize);
+        if (fillStrokeMode === TextRenderingMode.FILL ||
+            fillStrokeMode === TextRenderingMode.FILL_STROKE) {
+          ctx.fill();
         }
-
-        delete tables['glyf'];
-        delete tables['loca'];
-        delete tables['fpgm'];
-        delete tables['prep'];
-        delete tables['cvt '];
-        this.isOpenType = true;
+        if (fillStrokeMode === TextRenderingMode.STROKE ||
+            fillStrokeMode === TextRenderingMode.FILL_STROKE) {
+          ctx.stroke();
+        }
+        ctx.restore();
       } else {
-        if (!tables['loca']) {
-          error('Required "loca" table is not found');
+        if (fillStrokeMode === TextRenderingMode.FILL ||
+            fillStrokeMode === TextRenderingMode.FILL_STROKE) {
+          ctx.fillText(character, x, y);
         }
-        if (!tables['glyf']) {
-          warn('Required "glyf" table is not found -- trying to recover.');
-          // Note: We use `sanitizeGlyphLocations` to add dummy glyf data below.
-          tables['glyf'] = {
-            tag: 'glyf',
-            data: new Uint8Array(0),
-          };
+        if (fillStrokeMode === TextRenderingMode.STROKE ||
+            fillStrokeMode === TextRenderingMode.FILL_STROKE) {
+          ctx.strokeText(character, x, y);
         }
-        this.isOpenType = false;
       }
 
-      if (!tables['maxp']) {
-        error('Required "maxp" table is not found');
+      if (isAddToPathSet) {
+        var paths = this.pendingTextPaths || (this.pendingTextPaths = []);
+        paths.push({
+          transform: ctx.mozCurrentTransform,
+          x: x,
+          y: y,
+          fontSize: fontSize,
+          addToPath: addToPath
+        });
       }
+    },
 
-      font.pos = (font.start || 0) + tables['maxp'].offset;
-      var version = font.getInt32();
-      var numGlyphs = font.getUint16();
-      var maxFunctionDefs = 0;
-      if (version >= 0x00010000 && tables['maxp'].length >= 22) {
-        // maxZones can be invalid
-        font.pos += 8;
-        var maxZones = font.getUint16();
-        if (maxZones > 2) { // reset to 2 if font has invalid maxZones
-          tables['maxp'].data[14] = 0;
-          tables['maxp'].data[15] = 2;
+    get isFontSubpixelAAEnabled() {
+      // Checks if anti-aliasing is enabled when scaled text is painted.
+      // On Windows GDI scaled fonts looks bad.
+      var ctx = document.createElement('canvas').getContext('2d');
+      ctx.scale(1.5, 1);
+      ctx.fillText('I', 0, 10);
+      var data = ctx.getImageData(0, 0, 10, 10).data;
+      var enabled = false;
+      for (var i = 3; i < data.length; i += 4) {
+        if (data[i] > 0 && data[i] < 255) {
+          enabled = true;
+          break;
         }
-        font.pos += 4;
-        maxFunctionDefs = font.getUint16();
       }
+      return shadow(this, 'isFontSubpixelAAEnabled', enabled);
+    },
 
-      var dupFirstEntry = false;
-      if (properties.type === 'CIDFontType2' && properties.toUnicode &&
-          properties.toUnicode.get(0) > '\u0000') {
-        // oracle's defect (see 3427), duplicating first entry
-        dupFirstEntry = true;
-        numGlyphs++;
-        tables['maxp'].data[4] = numGlyphs >> 8;
-        tables['maxp'].data[5] = numGlyphs & 255;
+    showText: function CanvasGraphics_showText(glyphs) {
+      var current = this.current;
+      var font = current.font;
+      if (font.isType3Font) {
+        return this.showType3Text(glyphs);
       }
 
-      var hintsValid = sanitizeTTPrograms(tables['fpgm'], tables['prep'],
-                                          tables['cvt '], maxFunctionDefs);
-      if (!hintsValid) {
-        delete tables['fpgm'];
-        delete tables['prep'];
-        delete tables['cvt '];
+      var fontSize = current.fontSize;
+      if (fontSize === 0) {
+        return;
       }
 
-      // Ensure the hmtx table contains the advance width and
-      // sidebearings information for numGlyphs in the maxp table
-      sanitizeMetrics(font, tables['hhea'], tables['hmtx'], numGlyphs);
+      var ctx = this.ctx;
+      var fontSizeScale = current.fontSizeScale;
+      var charSpacing = current.charSpacing;
+      var wordSpacing = current.wordSpacing;
+      var fontDirection = current.fontDirection;
+      var textHScale = current.textHScale * fontDirection;
+      var glyphsLength = glyphs.length;
+      var vertical = font.vertical;
+      var spacingDir = vertical ? 1 : -1;
+      var defaultVMetrics = font.defaultVMetrics;
+      var widthAdvanceScale = fontSize * current.fontMatrix[0];
 
-      if (!tables['head']) {
-        error('Required "head" table is not found');
-      }
+      var simpleFillText =
+        current.textRenderingMode === TextRenderingMode.FILL &&
+        !font.disableFontFace;
 
-      sanitizeHead(tables['head'], numGlyphs,
-                   isTrueType ? tables['loca'].length : 0);
+      ctx.save();
+      ctx.transform.apply(ctx, current.textMatrix);
+      ctx.translate(current.x, current.y + current.textRise);
 
-      var missingGlyphs = Object.create(null);
-      if (isTrueType) {
-        var isGlyphLocationsLong = int16(tables['head'].data[50],
-                                         tables['head'].data[51]);
-        missingGlyphs = sanitizeGlyphLocations(tables['loca'], tables['glyf'],
-                                               numGlyphs, isGlyphLocationsLong,
-                                               hintsValid, dupFirstEntry);
+      if (current.patternFill) {
+        // TODO: Some shading patterns are not applied correctly to text,
+        //       e.g. issues 3988 and 5432, and ShowText-ShadingPattern.pdf.
+        ctx.fillStyle = current.fillColor.getPattern(ctx, this);
       }
 
-      if (!tables['hhea']) {
-        error('Required "hhea" table is not found');
+      if (fontDirection > 0) {
+        ctx.scale(textHScale, -1);
+      } else {
+        ctx.scale(textHScale, 1);
       }
 
-      // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth
-      // Sometimes it's 0. That needs to be fixed
-      if (tables['hhea'].data[10] === 0 && tables['hhea'].data[11] === 0) {
-        tables['hhea'].data[10] = 0xFF;
-        tables['hhea'].data[11] = 0xFF;
+      var lineWidth = current.lineWidth;
+      var scale = current.textMatrixScale;
+      if (scale === 0 || lineWidth === 0) {
+        var fillStrokeMode = current.textRenderingMode &
+          TextRenderingMode.FILL_STROKE_MASK;
+        if (fillStrokeMode === TextRenderingMode.STROKE ||
+            fillStrokeMode === TextRenderingMode.FILL_STROKE) {
+          this.cachedGetSinglePixelWidth = null;
+          lineWidth = this.getSinglePixelWidth() * MIN_WIDTH_FACTOR;
+        }
+      } else {
+        lineWidth /= scale;
       }
 
-      // Extract some more font properties from the OpenType head and
-      // hhea tables; yMin and descent value are always negative.
-      var metricsOverride = {
-        unitsPerEm: int16(tables['head'].data[18], tables['head'].data[19]),
-        yMax: int16(tables['head'].data[42], tables['head'].data[43]),
-        yMin: signedInt16(tables['head'].data[38], tables['head'].data[39]),
-        ascent: int16(tables['hhea'].data[4], tables['hhea'].data[5]),
-        descent: signedInt16(tables['hhea'].data[6], tables['hhea'].data[7])
-      };
+      if (fontSizeScale !== 1.0) {
+        ctx.scale(fontSizeScale, fontSizeScale);
+        lineWidth /= fontSizeScale;
+      }
 
-      // PDF FontDescriptor metrics lie -- using data from actual font.
-      this.ascent = metricsOverride.ascent / metricsOverride.unitsPerEm;
-      this.descent = metricsOverride.descent / metricsOverride.unitsPerEm;
+      ctx.lineWidth = lineWidth;
 
-      // The 'post' table has glyphs names.
-      if (tables['post']) {
-        var valid = readPostScriptTable(tables['post'], properties, numGlyphs);
-        if (!valid) {
-          tables['post'] = null;
+      var x = 0, i;
+      for (i = 0; i < glyphsLength; ++i) {
+        var glyph = glyphs[i];
+        if (isNum(glyph)) {
+          x += spacingDir * glyph * fontSize / 1000;
+          continue;
         }
-      }
 
-      var charCodeToGlyphId = [], charCode;
-      var toUnicode = properties.toUnicode, widths = properties.widths;
-      var skipToUnicode = (toUnicode instanceof IdentityToUnicodeMap ||
-                           toUnicode.length === 0x10000);
+        var restoreNeeded = false;
+        var spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
+        var character = glyph.fontChar;
+        var accent = glyph.accent;
+        var scaledX, scaledY, scaledAccentX, scaledAccentY;
+        var width = glyph.width;
+        if (vertical) {
+          var vmetric, vx, vy;
+          vmetric = glyph.vmetric || defaultVMetrics;
+          vx = glyph.vmetric ? vmetric[1] : width * 0.5;
+          vx = -vx * widthAdvanceScale;
+          vy = vmetric[2] * widthAdvanceScale;
 
-      // Helper function to try to skip mapping of empty glyphs.
-      // Note: In some cases, just relying on the glyph data doesn't work,
-      //       hence we also use a few heuristics to fix various PDF files.
-      function hasGlyph(glyphId, charCode, widthCode) {
-        if (!missingGlyphs[glyphId]) {
-          return true;
-        }
-        if (!skipToUnicode && charCode >= 0 && toUnicode.has(charCode)) {
-          return true;
-        }
-        if (widths && widthCode >= 0 && isNum(widths[widthCode])) {
-          return true;
+          width = vmetric ? -vmetric[0] : width;
+          scaledX = vx / fontSizeScale;
+          scaledY = (x + vy) / fontSizeScale;
+        } else {
+          scaledX = x / fontSizeScale;
+          scaledY = 0;
         }
-        return false;
-      }
 
-      // Some bad PDF generators, e.g. Scribus PDF, include glyph names
-      // in a 'uniXXXX' format -- attempting to recover proper ones.
-      function recoverGlyphName(name, glyphsUnicodeMap) {
-        if (glyphsUnicodeMap[name] !== undefined) {
-          return name;
+        if (font.remeasure && width > 0) {
+          // Some standard fonts may not have the exact width: rescale per
+          // character if measured width is greater than expected glyph width
+          // and subpixel-aa is enabled, otherwise just center the glyph.
+          var measuredWidth = ctx.measureText(character).width * 1000 /
+            fontSize * fontSizeScale;
+          if (width < measuredWidth && this.isFontSubpixelAAEnabled) {
+            var characterScaleX = width / measuredWidth;
+            restoreNeeded = true;
+            ctx.save();
+            ctx.scale(characterScaleX, 1);
+            scaledX /= characterScaleX;
+          } else if (width !== measuredWidth) {
+            scaledX += (width - measuredWidth) / 2000 *
+              fontSize / fontSizeScale;
+          }
         }
-        // The glyph name is non-standard, trying to recover.
-        var unicode = getUnicodeForGlyph(name, glyphsUnicodeMap);
-        if (unicode !== -1) {
-          for (var key in glyphsUnicodeMap) {
-            if (glyphsUnicodeMap[key] === unicode) {
-              return key;
+
+        // Only attempt to draw the glyph if it is actually in the embedded font
+        // file or if there isn't a font file so the fallback font is shown.
+        if (glyph.isInFont || font.missingFile) {
+          if (simpleFillText && !accent) {
+            // common case
+            ctx.fillText(character, scaledX, scaledY);
+          } else {
+            this.paintChar(character, scaledX, scaledY);
+            if (accent) {
+              scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
+              scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
+              this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY);
             }
           }
         }
-        warn('Unable to recover a standard glyph name for: ' + name);
-        return name;
-      }
-
-
-      if (properties.type === 'CIDFontType2') {
-        var cidToGidMap = properties.cidToGidMap || [];
-        var isCidToGidMapEmpty = cidToGidMap.length === 0;
 
-        properties.cMap.forEach(function(charCode, cid) {
-          assert(cid <= 0xffff, 'Max size of CID is 65,535');
-          var glyphId = -1;
-          if (isCidToGidMapEmpty) {
-            glyphId = cid;
-          } else if (cidToGidMap[cid] !== undefined) {
-            glyphId = cidToGidMap[cid];
-          }
+        var charWidth = width * widthAdvanceScale + spacing * fontDirection;
+        x += charWidth;
 
-          if (glyphId >= 0 && glyphId < numGlyphs &&
-              hasGlyph(glyphId, charCode, cid)) {
-            charCodeToGlyphId[charCode] = glyphId;
-          }
-        });
-        if (dupFirstEntry) {
-          charCodeToGlyphId[0] = numGlyphs - 1;
+        if (restoreNeeded) {
+          ctx.restore();
         }
+      }
+      if (vertical) {
+        current.y -= x * textHScale;
       } else {
-        // Most of the following logic in this code branch is based on the
-        // 9.6.6.4 of the PDF spec.
-        var hasEncoding =
-          properties.differences.length > 0 || !!properties.baseEncodingName;
-        var cmapTable =
-          readCmapTable(tables['cmap'], font, this.isSymbolicFont, hasEncoding);
-        var cmapPlatformId = cmapTable.platformId;
-        var cmapEncodingId = cmapTable.encodingId;
-        var cmapMappings = cmapTable.mappings;
-        var cmapMappingsLength = cmapMappings.length;
+        current.x += x * textHScale;
+      }
+      ctx.restore();
+    },
 
-        // The spec seems to imply that if the font is symbolic the encoding
-        // should be ignored, this doesn't appear to work for 'preistabelle.pdf'
-        // where the the font is symbolic and it has an encoding.
-        if (hasEncoding &&
-            (cmapPlatformId === 3 && cmapEncodingId === 1 ||
-             cmapPlatformId === 1 && cmapEncodingId === 0) ||
-            (cmapPlatformId === -1 && cmapEncodingId === -1 && // Temporary hack
-             !!getEncoding(properties.baseEncodingName))) {    // Temporary hack
-          // When no preferred cmap table was found and |baseEncodingName| is
-          // one of the predefined encodings, we seem to obtain a better
-          // |charCodeToGlyphId| map from the code below (fixes bug 1057544).
-          // TODO: Note that this is a hack which should be removed as soon as
-          //       we have proper support for more exotic cmap tables.
+    showType3Text: function CanvasGraphics_showType3Text(glyphs) {
+      // Type3 fonts - each glyph is a "mini-PDF"
+      var ctx = this.ctx;
+      var current = this.current;
+      var font = current.font;
+      var fontSize = current.fontSize;
+      var fontDirection = current.fontDirection;
+      var spacingDir = font.vertical ? 1 : -1;
+      var charSpacing = current.charSpacing;
+      var wordSpacing = current.wordSpacing;
+      var textHScale = current.textHScale * fontDirection;
+      var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
+      var glyphsLength = glyphs.length;
+      var isTextInvisible =
+        current.textRenderingMode === TextRenderingMode.INVISIBLE;
+      var i, glyph, width, spacingLength;
 
-          var baseEncoding = [];
-          if (properties.baseEncodingName === 'MacRomanEncoding' ||
-              properties.baseEncodingName === 'WinAnsiEncoding') {
-            baseEncoding = getEncoding(properties.baseEncodingName);
-          }
-          var glyphsUnicodeMap = getGlyphsUnicode();
-          for (charCode = 0; charCode < 256; charCode++) {
-            var glyphName, standardGlyphName;
-            if (this.differences && charCode in this.differences) {
-              glyphName = this.differences[charCode];
-            } else if (charCode in baseEncoding &&
-                       baseEncoding[charCode] !== '') {
-              glyphName = baseEncoding[charCode];
-            } else {
-              glyphName = StandardEncoding[charCode];
-            }
-            if (!glyphName) {
-              continue;
-            }
-            // Ensure that non-standard glyph names are resolved to valid ones.
-            standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap);
+      if (isTextInvisible || fontSize === 0) {
+        return;
+      }
+      this.cachedGetSinglePixelWidth = null;
 
-            var unicodeOrCharCode, isUnicode = false;
-            if (cmapPlatformId === 3 && cmapEncodingId === 1) {
-              unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName];
-              isUnicode = true;
-            } else if (cmapPlatformId === 1 && cmapEncodingId === 0) {
-              // TODO: the encoding needs to be updated with mac os table.
-              unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName);
-            }
+      ctx.save();
+      ctx.transform.apply(ctx, current.textMatrix);
+      ctx.translate(current.x, current.y);
 
-            var found = false;
-            for (i = 0; i < cmapMappingsLength; ++i) {
-              if (cmapMappings[i].charCode !== unicodeOrCharCode) {
-                continue;
-              }
-              var code = isUnicode ? charCode : unicodeOrCharCode;
-              if (hasGlyph(cmapMappings[i].glyphId, code, -1)) {
-                charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
-                found = true;
-                break;
-              }
-            }
-            if (!found && properties.glyphNames) {
-              // Try to map using the post table.
-              var glyphId = properties.glyphNames.indexOf(glyphName);
-              // The post table ought to use the same kind of glyph names as the
-              // `differences` array, but check the standard ones as a fallback.
-              if (glyphId === -1 && standardGlyphName !== glyphName) {
-                glyphId = properties.glyphNames.indexOf(standardGlyphName);
-              }
-              if (glyphId > 0 && hasGlyph(glyphId, -1, -1)) {
-                charCodeToGlyphId[charCode] = glyphId;
-                found = true;
-              }
-            }
-            if (!found) {
-              charCodeToGlyphId[charCode] = 0; // notdef
-            }
-          }
-        } else if (cmapPlatformId === 0 && cmapEncodingId === 0) {
-          // Default Unicode semantics, use the charcodes as is.
-          for (i = 0; i < cmapMappingsLength; ++i) {
-            charCodeToGlyphId[cmapMappings[i].charCode] =
-              cmapMappings[i].glyphId;
-          }
-        } else {
-          // For (3, 0) cmap tables:
-          // The charcode key being stored in charCodeToGlyphId is the lower
-          // byte of the two-byte charcodes of the cmap table since according to
-          // the spec: 'each byte from the string shall be prepended with the
-          // high byte of the range [of charcodes in the cmap table], to form
-          // a two-byte character, which shall be used to select the
-          // associated glyph description from the subtable'.
-          //
-          // For (1, 0) cmap tables:
-          // 'single bytes from the string shall be used to look up the
-          // associated glyph descriptions from the subtable'. This means
-          // charcodes in the cmap will be single bytes, so no-op since
-          // glyph.charCode & 0xFF === glyph.charCode
-          for (i = 0; i < cmapMappingsLength; ++i) {
-            charCode = cmapMappings[i].charCode & 0xFF;
-            charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
-          }
+      ctx.scale(textHScale, fontDirection);
+
+      for (i = 0; i < glyphsLength; ++i) {
+        glyph = glyphs[i];
+        if (isNum(glyph)) {
+          spacingLength = spacingDir * glyph * fontSize / 1000;
+          this.ctx.translate(spacingLength, 0);
+          current.x += spacingLength * textHScale;
+          continue;
         }
-      }
 
-      if (charCodeToGlyphId.length === 0) {
-        // defines at least one glyph
-        charCodeToGlyphId[0] = 0;
-      }
+        var spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
+        var operatorList = font.charProcOperatorList[glyph.operatorListId];
+        if (!operatorList) {
+          warn('Type3 character \"' + glyph.operatorListId +
+               '\" is not available');
+          continue;
+        }
+        this.processingType3 = glyph;
+        this.save();
+        ctx.scale(fontSize, fontSize);
+        ctx.transform.apply(ctx, fontMatrix);
+        this.executeOperatorList(operatorList);
+        this.restore();
 
-      // Converting glyphs and ids into font's cmap table
-      var newMapping = adjustMapping(charCodeToGlyphId, properties);
-      this.toFontChar = newMapping.toFontChar;
-      tables['cmap'] = {
-        tag: 'cmap',
-        data: createCmapTable(newMapping.charCodeToGlyphId, numGlyphs)
-      };
+        var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
+        width = transformed[0] * fontSize + spacing;
 
-      if (!tables['OS/2'] || !validateOS2Table(tables['OS/2'])) {
-        tables['OS/2'] = {
-          tag: 'OS/2',
-          data: createOS2Table(properties, newMapping.charCodeToGlyphId,
-                               metricsOverride)
-        };
+        ctx.translate(width, 0);
+        current.x += width * textHScale;
       }
+      ctx.restore();
+      this.processingType3 = null;
+    },
 
-      // Rewrite the 'post' table if needed
-      if (!tables['post']) {
-        tables['post'] = {
-          tag: 'post',
-          data: createPostTable(properties)
+    // Type3 fonts
+    setCharWidth: function CanvasGraphics_setCharWidth(xWidth, yWidth) {
+      // We can safely ignore this since the width should be the same
+      // as the width in the Widths array.
+    },
+    setCharWidthAndBounds: function CanvasGraphics_setCharWidthAndBounds(xWidth,
+                                                                        yWidth,
+                                                                        llx,
+                                                                        lly,
+                                                                        urx,
+                                                                        ury) {
+      // TODO According to the spec we're also suppose to ignore any operators
+      // that set color or include images while processing this type3 font.
+      this.ctx.rect(llx, lly, urx - llx, ury - lly);
+      this.clip();
+      this.endPath();
+    },
+
+    // Color
+    getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR) {
+      var pattern;
+      if (IR[0] === 'TilingPattern') {
+        var color = IR[1];
+        var baseTransform = this.baseTransform ||
+                            this.ctx.mozCurrentTransform.slice();
+        var self = this;
+        var canvasGraphicsFactory = {
+          createCanvasGraphics: function (ctx) {
+            return new CanvasGraphics(ctx, self.commonObjs, self.objs);
+          }
         };
+        pattern = new TilingPattern(IR, color, this.ctx, canvasGraphicsFactory,
+                                    baseTransform);
+      } else {
+        pattern = getShadingPatternFromIR(IR);
       }
+      return pattern;
+    },
+    setStrokeColorN: function CanvasGraphics_setStrokeColorN(/*...*/) {
+      this.current.strokeColor = this.getColorN_Pattern(arguments);
+    },
+    setFillColorN: function CanvasGraphics_setFillColorN(/*...*/) {
+      this.current.fillColor = this.getColorN_Pattern(arguments);
+      this.current.patternFill = true;
+    },
+    setStrokeRGBColor: function CanvasGraphics_setStrokeRGBColor(r, g, b) {
+      var color = Util.makeCssRgb(r, g, b);
+      this.ctx.strokeStyle = color;
+      this.current.strokeColor = color;
+    },
+    setFillRGBColor: function CanvasGraphics_setFillRGBColor(r, g, b) {
+      var color = Util.makeCssRgb(r, g, b);
+      this.ctx.fillStyle = color;
+      this.current.fillColor = color;
+      this.current.patternFill = false;
+    },
 
-      if (!isTrueType) {
-        try {
-          // Trying to repair CFF file
-          cffFile = new Stream(tables['CFF '].data);
-          var parser = new CFFParser(cffFile, properties,
-                                     SEAC_ANALYSIS_ENABLED);
-          cff = parser.parse();
-          var compiler = new CFFCompiler(cff);
-          tables['CFF '].data = compiler.compile();
-        } catch (e) {
-          warn('Failed to compile font ' + properties.loadedName);
-        }
-      }
+    shadingFill: function CanvasGraphics_shadingFill(patternIR) {
+      var ctx = this.ctx;
 
-      // Re-creating 'name' table
-      if (!tables['name']) {
-        tables['name'] = {
-          tag: 'name',
-          data: createNameTable(this.name)
-        };
+      this.save();
+      var pattern = getShadingPatternFromIR(patternIR);
+      ctx.fillStyle = pattern.getPattern(ctx, this, true);
+
+      var inv = ctx.mozCurrentTransformInverse;
+      if (inv) {
+        var canvas = ctx.canvas;
+        var width = canvas.width;
+        var height = canvas.height;
+
+        var bl = Util.applyTransform([0, 0], inv);
+        var br = Util.applyTransform([0, height], inv);
+        var ul = Util.applyTransform([width, 0], inv);
+        var ur = Util.applyTransform([width, height], inv);
+
+        var x0 = Math.min(bl[0], br[0], ul[0], ur[0]);
+        var y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
+        var x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
+        var y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
+
+        this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
       } else {
-        // ... using existing 'name' table as prototype
-        var namePrototype = readNameTable(tables['name']);
-        tables['name'].data = createNameTable(name, namePrototype);
-      }
+        // HACK to draw the gradient onto an infinite rectangle.
+        // PDF gradients are drawn across the entire image while
+        // Canvas only allows gradients to be drawn in a rectangle
+        // The following bug should allow us to remove this.
+        // https://bugzilla.mozilla.org/show_bug.cgi?id=664884
 
-      var builder = new OpenTypeFileBuilder(header.version);
-      for (var tableTag in tables) {
-        builder.addTable(tableTag, tables[tableTag].data);
+        this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
       }
-      return builder.toArray();
+
+      this.restore();
     },
 
-    convert: function Font_convert(fontName, font, properties) {
-      // TODO: Check the charstring widths to determine this.
-      properties.fixedPitch = false;
+    // Images
+    beginInlineImage: function CanvasGraphics_beginInlineImage() {
+      error('Should not call beginInlineImage');
+    },
+    beginImageData: function CanvasGraphics_beginImageData() {
+      error('Should not call beginImageData');
+    },
 
-      var mapping = font.getGlyphMapping(properties);
-      var newMapping = adjustMapping(mapping, properties);
-      this.toFontChar = newMapping.toFontChar;
-      var numGlyphs = font.numGlyphs;
+    paintFormXObjectBegin: function CanvasGraphics_paintFormXObjectBegin(matrix,
+                                                                        bbox) {
+      this.save();
+      this.baseTransformStack.push(this.baseTransform);
 
-      function getCharCodes(charCodeToGlyphId, glyphId) {
-        var charCodes = null;
-        for (var charCode in charCodeToGlyphId) {
-          if (glyphId === charCodeToGlyphId[charCode]) {
-            if (!charCodes) {
-              charCodes = [];
-            }
-            charCodes.push(charCode | 0);
-          }
-        }
-        return charCodes;
+      if (isArray(matrix) && 6 === matrix.length) {
+        this.transform.apply(this, matrix);
       }
 
-      function createCharCode(charCodeToGlyphId, glyphId) {
-        for (var charCode in charCodeToGlyphId) {
-          if (glyphId === charCodeToGlyphId[charCode]) {
-            return charCode | 0;
-          }
-        }
-        newMapping.charCodeToGlyphId[newMapping.nextAvailableFontCharCode] =
-            glyphId;
-        return newMapping.nextAvailableFontCharCode++;
+      this.baseTransform = this.ctx.mozCurrentTransform;
+
+      if (isArray(bbox) && 4 === bbox.length) {
+        var width = bbox[2] - bbox[0];
+        var height = bbox[3] - bbox[1];
+        this.ctx.rect(bbox[0], bbox[1], width, height);
+        this.clip();
+        this.endPath();
       }
+    },
 
-      var seacs = font.seacs;
-      if (SEAC_ANALYSIS_ENABLED && seacs && seacs.length) {
-        var matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX;
-        var charset = font.getCharset();
-        var seacMap = Object.create(null);
-        for (var glyphId in seacs) {
-          glyphId |= 0;
-          var seac = seacs[glyphId];
-          var baseGlyphName = StandardEncoding[seac[2]];
-          var accentGlyphName = StandardEncoding[seac[3]];
-          var baseGlyphId = charset.indexOf(baseGlyphName);
-          var accentGlyphId = charset.indexOf(accentGlyphName);
-          if (baseGlyphId < 0 || accentGlyphId < 0) {
-            continue;
-          }
-          var accentOffset = {
-            x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4],
-            y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5]
-          };
+    paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() {
+      this.restore();
+      this.baseTransform = this.baseTransformStack.pop();
+    },
 
-          var charCodes = getCharCodes(mapping, glyphId);
-          if (!charCodes) {
-            // There's no point in mapping it if the char code was never mapped
-            // to begin with.
-            continue;
-          }
-          for (var i = 0, ii = charCodes.length; i < ii; i++) {
-            var charCode = charCodes[i];
-            // Find a fontCharCode that maps to the base and accent glyphs.
-            // If one doesn't exists, create it.
-            var charCodeToGlyphId = newMapping.charCodeToGlyphId;
-            var baseFontCharCode = createCharCode(charCodeToGlyphId,
-                                                  baseGlyphId);
-            var accentFontCharCode = createCharCode(charCodeToGlyphId,
-                                                    accentGlyphId);
-            seacMap[charCode] = {
-              baseFontCharCode: baseFontCharCode,
-              accentFontCharCode: accentFontCharCode,
-              accentOffset: accentOffset
-            };
-          }
-        }
-        properties.seacMap = seacMap;
+    beginGroup: function CanvasGraphics_beginGroup(group) {
+      this.save();
+      var currentCtx = this.ctx;
+      // TODO non-isolated groups - according to Rik at adobe non-isolated
+      // group results aren't usually that different and they even have tools
+      // that ignore this setting. Notes from Rik on implmenting:
+      // - When you encounter an transparency group, create a new canvas with
+      // the dimensions of the bbox
+      // - copy the content from the previous canvas to the new canvas
+      // - draw as usual
+      // - remove the backdrop alpha:
+      // alphaNew = 1 - (1 - alpha)/(1 - alphaBackdrop) with 'alpha' the alpha
+      // value of your transparency group and 'alphaBackdrop' the alpha of the
+      // backdrop
+      // - remove background color:
+      // colorNew = color - alphaNew *colorBackdrop /(1 - alphaNew)
+      if (!group.isolated) {
+        info('TODO: Support non-isolated groups.');
       }
 
-      var unitsPerEm = 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0];
+      // TODO knockout - supposedly possible with the clever use of compositing
+      // modes.
+      if (group.knockout) {
+        warn('Knockout groups not supported.');
+      }
 
-      var builder = new OpenTypeFileBuilder('\x4F\x54\x54\x4F');
-      // PostScript Font Program
-      builder.addTable('CFF ', font.data);
-      // OS/2 and Windows Specific metrics
-      builder.addTable('OS/2', createOS2Table(properties,
-                                              newMapping.charCodeToGlyphId));
-      // Character to glyphs mapping
-      builder.addTable('cmap', createCmapTable(newMapping.charCodeToGlyphId,
-                       numGlyphs));
-      // Font header
-      builder.addTable('head',
-            '\x00\x01\x00\x00' + // Version number
-            '\x00\x00\x10\x00' + // fontRevision
-            '\x00\x00\x00\x00' + // checksumAdjustement
-            '\x5F\x0F\x3C\xF5' + // magicNumber
-            '\x00\x00' + // Flags
-            safeString16(unitsPerEm) + // unitsPerEM
-            '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // creation date
-            '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // modifification date
-            '\x00\x00' + // xMin
-            safeString16(properties.descent) + // yMin
-            '\x0F\xFF' + // xMax
-            safeString16(properties.ascent) + // yMax
-            string16(properties.italicAngle ? 2 : 0) + // macStyle
-            '\x00\x11' + // lowestRecPPEM
-            '\x00\x00' + // fontDirectionHint
-            '\x00\x00' + // indexToLocFormat
-            '\x00\x00');  // glyphDataFormat
-
-      // Horizontal header
-      builder.addTable('hhea',
-            '\x00\x01\x00\x00' + // Version number
-            safeString16(properties.ascent) + // Typographic Ascent
-            safeString16(properties.descent) + // Typographic Descent
-            '\x00\x00' + // Line Gap
-            '\xFF\xFF' + // advanceWidthMax
-            '\x00\x00' + // minLeftSidebearing
-            '\x00\x00' + // minRightSidebearing
-            '\x00\x00' + // xMaxExtent
-            safeString16(properties.capHeight) + // caretSlopeRise
-            safeString16(Math.tan(properties.italicAngle) *
-                         properties.xHeight) + // caretSlopeRun
-            '\x00\x00' + // caretOffset
-            '\x00\x00' + // -reserved-
-            '\x00\x00' + // -reserved-
-            '\x00\x00' + // -reserved-
-            '\x00\x00' + // -reserved-
-            '\x00\x00' + // metricDataFormat
-            string16(numGlyphs)); // Number of HMetrics
-
-      // Horizontal metrics
-      builder.addTable('hmtx', (function fontFieldsHmtx() {
-          var charstrings = font.charstrings;
-          var cffWidths = font.cff ? font.cff.widths : null;
-          var hmtx = '\x00\x00\x00\x00'; // Fake .notdef
-          for (var i = 1, ii = numGlyphs; i < ii; i++) {
-            var width = 0;
-            if (charstrings) {
-              var charstring = charstrings[i - 1];
-              width = 'width' in charstring ? charstring.width : 0;
-            } else if (cffWidths) {
-              width = Math.ceil(cffWidths[i] || 0);
-            }
-            hmtx += string16(width) + string16(0);
-          }
-          return hmtx;
-        })());
+      var currentTransform = currentCtx.mozCurrentTransform;
+      if (group.matrix) {
+        currentCtx.transform.apply(currentCtx, group.matrix);
+      }
+      assert(group.bbox, 'Bounding box is required.');
 
-      // Maximum profile
-      builder.addTable('maxp',
-            '\x00\x00\x50\x00' + // Version number
-            string16(numGlyphs)); // Num of glyphs
+      // Based on the current transform figure out how big the bounding box
+      // will actually be.
+      var bounds = Util.getAxialAlignedBoundingBox(
+                    group.bbox,
+                    currentCtx.mozCurrentTransform);
+      // Clip the bounding box to the current canvas.
+      var canvasBounds = [0,
+                          0,
+                          currentCtx.canvas.width,
+                          currentCtx.canvas.height];
+      bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0];
+      // Use ceil in case we're between sizes so we don't create canvas that is
+      // too small and make the canvas at least 1x1 pixels.
+      var offsetX = Math.floor(bounds[0]);
+      var offsetY = Math.floor(bounds[1]);
+      var drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1);
+      var drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1);
+      var scaleX = 1, scaleY = 1;
+      if (drawnWidth > MAX_GROUP_SIZE) {
+        scaleX = drawnWidth / MAX_GROUP_SIZE;
+        drawnWidth = MAX_GROUP_SIZE;
+      }
+      if (drawnHeight > MAX_GROUP_SIZE) {
+        scaleY = drawnHeight / MAX_GROUP_SIZE;
+        drawnHeight = MAX_GROUP_SIZE;
+      }
 
-      // Naming tables
-      builder.addTable('name', createNameTable(fontName));
+      var cacheId = 'groupAt' + this.groupLevel;
+      if (group.smask) {
+        // Using two cache entries is case if masks are used one after another.
+        cacheId +=  '_smask_' + ((this.smaskCounter++) % 2);
+      }
+      var scratchCanvas = this.cachedCanvases.getCanvas(
+        cacheId, drawnWidth, drawnHeight, true);
+      var groupCtx = scratchCanvas.context;
 
-      // PostScript informations
-      builder.addTable('post', createPostTable(properties));
+      // Since we created a new canvas that is just the size of the bounding box
+      // we have to translate the group ctx.
+      groupCtx.scale(1 / scaleX, 1 / scaleY);
+      groupCtx.translate(-offsetX, -offsetY);
+      groupCtx.transform.apply(groupCtx, currentTransform);
 
-      return builder.toArray();
+      if (group.smask) {
+        // Saving state and cached mask to be used in setGState.
+        this.smaskStack.push({
+          canvas: scratchCanvas.canvas,
+          context: groupCtx,
+          offsetX: offsetX,
+          offsetY: offsetY,
+          scaleX: scaleX,
+          scaleY: scaleY,
+          subtype: group.smask.subtype,
+          backdrop: group.smask.backdrop,
+          transferMap: group.smask.transferMap || null
+        });
+      } else {
+        // Setup the current ctx so when the group is popped we draw it at the
+        // right location.
+        currentCtx.setTransform(1, 0, 0, 1, 0, 0);
+        currentCtx.translate(offsetX, offsetY);
+        currentCtx.scale(scaleX, scaleY);
+      }
+      // The transparency group inherits all off the current graphics state
+      // except the blend mode, soft mask, and alpha constants.
+      copyCtxState(currentCtx, groupCtx);
+      this.ctx = groupCtx;
+      this.setGState([
+        ['BM', 'Normal'],
+        ['ca', 1],
+        ['CA', 1]
+      ]);
+      this.groupStack.push(currentCtx);
+      this.groupLevel++;
     },
 
-    get spaceWidth() {
-      if ('_shadowWidth' in this) {
-        return this._shadowWidth;
+    endGroup: function CanvasGraphics_endGroup(group) {
+      this.groupLevel--;
+      var groupCtx = this.ctx;
+      this.ctx = this.groupStack.pop();
+      // Turn off image smoothing to avoid sub pixel interpolation which can
+      // look kind of blurry for some pdfs.
+      if (this.ctx.imageSmoothingEnabled !== undefined) {
+        this.ctx.imageSmoothingEnabled = false;
+      } else {
+        this.ctx.mozImageSmoothingEnabled = false;
       }
-
-      // trying to estimate space character width
-      var possibleSpaceReplacements = ['space', 'minus', 'one', 'i'];
-      var width;
-      for (var i = 0, ii = possibleSpaceReplacements.length; i < ii; i++) {
-        var glyphName = possibleSpaceReplacements[i];
-        // if possible, getting width by glyph name
-        if (glyphName in this.widths) {
-          width = this.widths[glyphName];
-          break;
-        }
-        var glyphsUnicodeMap = getGlyphsUnicode();
-        var glyphUnicode = glyphsUnicodeMap[glyphName];
-        // finding the charcode via unicodeToCID map
-        var charcode = 0;
-        if (this.composite) {
-          if (this.cMap.contains(glyphUnicode)) {
-            charcode = this.cMap.lookup(glyphUnicode);
-          }
-        }
-        // ... via toUnicode map
-        if (!charcode && this.toUnicode) {
-          charcode = this.toUnicode.charCodeOf(glyphUnicode);
-        }
-        // setting it to unicode if negative or undefined
-        if (charcode <= 0) {
-          charcode = glyphUnicode;
-        }
-        // trying to get width via charcode
-        width = this.widths[charcode];
-        if (width) {
-          break; // the non-zero width found
-        }
+      if (group.smask) {
+        this.tempSMask = this.smaskStack.pop();
+      } else {
+        this.ctx.drawImage(groupCtx.canvas, 0, 0);
       }
-      width = width || this.defaultWidth;
-      // Do not shadow the property here. See discussion:
-      // https://github.com/mozilla/pdf.js/pull/2127#discussion_r1662280
-      this._shadowWidth = width;
-      return width;
+      this.restore();
     },
 
-    charToGlyph: function Font_charToGlyph(charcode, isSpace) {
-      var fontCharCode, width, operatorListId;
+    beginAnnotations: function CanvasGraphics_beginAnnotations() {
+      this.save();
+      this.current = new CanvasExtraState();
 
-      var widthCode = charcode;
-      if (this.cMap && this.cMap.contains(charcode)) {
-        widthCode = this.cMap.lookup(charcode);
+      if (this.baseTransform) {
+        this.ctx.setTransform.apply(this.ctx, this.baseTransform);
       }
-      width = this.widths[widthCode];
-      width = isNum(width) ? width : this.defaultWidth;
-      var vmetric = this.vmetrics && this.vmetrics[widthCode];
+    },
 
-      var unicode = this.toUnicode.get(charcode) || charcode;
-      if (typeof unicode === 'number') {
-        unicode = String.fromCharCode(unicode);
-      }
+    endAnnotations: function CanvasGraphics_endAnnotations() {
+      this.restore();
+    },
 
-      var isInFont = charcode in this.toFontChar;
-      // First try the toFontChar map, if it's not there then try falling
-      // back to the char code.
-      fontCharCode = this.toFontChar[charcode] || charcode;
-      if (this.missingFile) {
-        fontCharCode = mapSpecialUnicodeValues(fontCharCode);
-      }
+    beginAnnotation: function CanvasGraphics_beginAnnotation(rect, transform,
+                                                             matrix) {
+      this.save();
 
-      if (this.isType3Font) {
-        // Font char code in this case is actually a glyph name.
-        operatorListId = fontCharCode;
+      if (isArray(rect) && 4 === rect.length) {
+        var width = rect[2] - rect[0];
+        var height = rect[3] - rect[1];
+        this.ctx.rect(rect[0], rect[1], width, height);
+        this.clip();
+        this.endPath();
       }
 
-      var accent = null;
-      if (this.seacMap && this.seacMap[charcode]) {
-        isInFont = true;
-        var seac = this.seacMap[charcode];
-        fontCharCode = seac.baseFontCharCode;
-        accent = {
-          fontChar: String.fromCharCode(seac.accentFontCharCode),
-          offset: seac.accentOffset
-        };
+      this.transform.apply(this, transform);
+      this.transform.apply(this, matrix);
+    },
+
+    endAnnotation: function CanvasGraphics_endAnnotation() {
+      this.restore();
+    },
+
+    paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) {
+      var domImage = this.objs.get(objId);
+      if (!domImage) {
+        warn('Dependent image isn\'t ready yet');
+        return;
       }
 
-      var fontChar = String.fromCharCode(fontCharCode);
+      this.save();
 
-      var glyph = this.glyphCache[charcode];
-      if (!glyph ||
-          !glyph.matchesForCache(fontChar, unicode, accent, width, vmetric,
-                                 operatorListId, isSpace, isInFont)) {
-        glyph = new Glyph(fontChar, unicode, accent, width, vmetric,
-                          operatorListId, isSpace, isInFont);
-        this.glyphCache[charcode] = glyph;
+      var ctx = this.ctx;
+      // scale the image to the unit square
+      ctx.scale(1 / w, -1 / h);
+
+      ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height,
+                    0, -h, w, h);
+      if (this.imageLayer) {
+        var currentTransform = ctx.mozCurrentTransformInverse;
+        var position = this.getCanvasPosition(0, 0);
+        this.imageLayer.appendImage({
+          objId: objId,
+          left: position[0],
+          top: position[1],
+          width: w / currentTransform[0],
+          height: h / currentTransform[3]
+        });
       }
-      return glyph;
+      this.restore();
     },
 
-    charsToGlyphs: function Font_charsToGlyphs(chars) {
-      var charsCache = this.charsCache;
-      var glyphs, glyph, charcode;
+    paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) {
+      var ctx = this.ctx;
+      var width = img.width, height = img.height;
+      var fillColor = this.current.fillColor;
+      var isPatternFill = this.current.patternFill;
 
-      // if we translated this string before, just grab it from the cache
-      if (charsCache) {
-        glyphs = charsCache[chars];
-        if (glyphs) {
-          return glyphs;
+      var glyph = this.processingType3;
+
+      if (COMPILE_TYPE3_GLYPHS && glyph && glyph.compiled === undefined) {
+        if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) {
+          glyph.compiled =
+            compileType3Glyph({data: img.data, width: width, height: height});
+        } else {
+          glyph.compiled = null;
         }
       }
 
-      // lazily create the translation cache
-      if (!charsCache) {
-        charsCache = this.charsCache = Object.create(null);
+      if (glyph && glyph.compiled) {
+        glyph.compiled(ctx);
+        return;
       }
 
-      glyphs = [];
-      var charsCacheKey = chars;
-      var i = 0, ii;
+      var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas',
+                                                     width, height);
+      var maskCtx = maskCanvas.context;
+      maskCtx.save();
 
-      if (this.cMap) {
-        // composite fonts have multi-byte strings convert the string from
-        // single-byte to multi-byte
-        var c = Object.create(null);
-        while (i < chars.length) {
-          this.cMap.readCharCode(chars, i, c);
-          charcode = c.charcode;
-          var length = c.length;
-          i += length;
-          // Space is char with code 0x20 and length 1 in multiple-byte codes.
-          var isSpace = length === 1 && chars.charCodeAt(i - 1) === 0x20;
-          glyph = this.charToGlyph(charcode, isSpace);
-          glyphs.push(glyph);
-        }
-      } else {
-        for (i = 0, ii = chars.length; i < ii; ++i) {
-          charcode = chars.charCodeAt(i);
-          glyph = this.charToGlyph(charcode, charcode === 0x20);
-          glyphs.push(glyph);
-        }
-      }
+      putBinaryImageMask(maskCtx, img);
 
-      // Enter the translated string into the cache
-      return (charsCache[charsCacheKey] = glyphs);
-    }
-  };
+      maskCtx.globalCompositeOperation = 'source-in';
 
-  return Font;
-})();
+      maskCtx.fillStyle = isPatternFill ?
+                          fillColor.getPattern(maskCtx, this) : fillColor;
+      maskCtx.fillRect(0, 0, width, height);
 
-var ErrorFont = (function ErrorFontClosure() {
-  function ErrorFont(error) {
-    this.error = error;
-    this.loadedName = 'g_font_error';
-    this.loading = false;
-  }
+      maskCtx.restore();
 
-  ErrorFont.prototype = {
-    charsToGlyphs: function ErrorFont_charsToGlyphs() {
-      return [];
+      this.paintInlineImageXObject(maskCanvas.canvas);
     },
-    exportData: function ErrorFont_exportData() {
-      return {error: this.error};
-    }
-  };
 
-  return ErrorFont;
-})();
+    paintImageMaskXObjectRepeat:
+      function CanvasGraphics_paintImageMaskXObjectRepeat(imgData, scaleX,
+                                                          scaleY, positions) {
+      var width = imgData.width;
+      var height = imgData.height;
+      var fillColor = this.current.fillColor;
+      var isPatternFill = this.current.patternFill;
 
-/**
- * Shared logic for building a char code to glyph id mapping for Type1 and
- * simple CFF fonts. See section 9.6.6.2 of the spec.
- * @param {Object} properties Font properties object.
- * @param {Object} builtInEncoding The encoding contained within the actual font
- * data.
- * @param {Array} Array of glyph names where the index is the glyph ID.
- * @returns {Object} A char code to glyph ID map.
- */
-function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) {
-  var charCodeToGlyphId = Object.create(null);
-  var glyphId, charCode, baseEncoding;
+      var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas',
+                                                     width, height);
+      var maskCtx = maskCanvas.context;
+      maskCtx.save();
 
-  if (properties.baseEncodingName) {
-    // If a valid base encoding name was used, the mapping is initialized with
-    // that.
-    baseEncoding = getEncoding(properties.baseEncodingName);
-    for (charCode = 0; charCode < baseEncoding.length; charCode++) {
-      glyphId = glyphNames.indexOf(baseEncoding[charCode]);
-      if (glyphId >= 0) {
-        charCodeToGlyphId[charCode] = glyphId;
-      } else {
-        charCodeToGlyphId[charCode] = 0; // notdef
-      }
-    }
-  } else if (!!(properties.flags & FontFlags.Symbolic)) {
-    // For a symbolic font the encoding should be the fonts built-in
-    // encoding.
-    for (charCode in builtInEncoding) {
-      charCodeToGlyphId[charCode] = builtInEncoding[charCode];
-    }
-  } else {
-    // For non-symbolic fonts that don't have a base encoding the standard
-    // encoding should be used.
-    baseEncoding = StandardEncoding;
-    for (charCode = 0; charCode < baseEncoding.length; charCode++) {
-      glyphId = glyphNames.indexOf(baseEncoding[charCode]);
-      if (glyphId >= 0) {
-        charCodeToGlyphId[charCode] = glyphId;
-      } else {
-        charCodeToGlyphId[charCode] = 0; // notdef
-      }
-    }
-  }
+      putBinaryImageMask(maskCtx, imgData);
 
-  // Lastly, merge in the differences.
-  var differences = properties.differences;
-  if (differences) {
-    for (charCode in differences) {
-      var glyphName = differences[charCode];
-      glyphId = glyphNames.indexOf(glyphName);
-      if (glyphId >= 0) {
-        charCodeToGlyphId[charCode] = glyphId;
-      } else {
-        charCodeToGlyphId[charCode] = 0; // notdef
-      }
-    }
-  }
-  return charCodeToGlyphId;
-}
+      maskCtx.globalCompositeOperation = 'source-in';
 
-// Type1Font is also a CIDFontType0.
-var Type1Font = (function Type1FontClosure() {
-  function findBlock(streamBytes, signature, startIndex) {
-    var streamBytesLength = streamBytes.length;
-    var signatureLength = signature.length;
-    var scanLength = streamBytesLength - signatureLength;
+      maskCtx.fillStyle = isPatternFill ?
+                          fillColor.getPattern(maskCtx, this) : fillColor;
+      maskCtx.fillRect(0, 0, width, height);
 
-    var i = startIndex, j, found = false;
-    while (i < scanLength) {
-      j = 0;
-      while (j < signatureLength && streamBytes[i + j] === signature[j]) {
-        j++;
-      }
-      if (j >= signatureLength) { // `signature` found, skip over whitespace.
-        i += j;
-        while (i < streamBytesLength && Lexer.isSpace(streamBytes[i])) {
-          i++;
-        }
-        found = true;
-        break;
+      maskCtx.restore();
+
+      var ctx = this.ctx;
+      for (var i = 0, ii = positions.length; i < ii; i += 2) {
+        ctx.save();
+        ctx.transform(scaleX, 0, 0, scaleY, positions[i], positions[i + 1]);
+        ctx.scale(1, -1);
+        ctx.drawImage(maskCanvas.canvas, 0, 0, width, height,
+          0, -1, 1, 1);
+        ctx.restore();
       }
-      i++;
-    }
-    return {
-      found: found,
-      length: i,
-    };
-  }
+    },
 
-  function getHeaderBlock(stream, suggestedLength) {
-    var EEXEC_SIGNATURE = [0x65, 0x65, 0x78, 0x65, 0x63];
+    paintImageMaskXObjectGroup:
+      function CanvasGraphics_paintImageMaskXObjectGroup(images) {
+      var ctx = this.ctx;
 
-    var streamStartPos = stream.pos; // Save the initial stream position.
-    var headerBytes, headerBytesLength, block;
-    try {
-      headerBytes = stream.getBytes(suggestedLength);
-      headerBytesLength = headerBytes.length;
-    } catch (ex) {
-      if (ex instanceof MissingDataException) {
-        throw ex;
-      }
-      // Ignore errors if the `suggestedLength` is huge enough that a Uint8Array
-      // cannot hold the result of `getBytes`, and fallback to simply checking
-      // the entire stream (fixes issue3928.pdf).
-    }
+      var fillColor = this.current.fillColor;
+      var isPatternFill = this.current.patternFill;
+      for (var i = 0, ii = images.length; i < ii; i++) {
+        var image = images[i];
+        var width = image.width, height = image.height;
 
-    if (headerBytesLength === suggestedLength) {
-      // Most of the time `suggestedLength` is correct, so to speed things up we
-      // initially only check the last few bytes to see if the header was found.
-      // Otherwise we (potentially) check the entire stream to prevent errors in
-      // `Type1Parser` (fixes issue5686.pdf).
-      block = findBlock(headerBytes, EEXEC_SIGNATURE,
-                        suggestedLength - 2 * EEXEC_SIGNATURE.length);
+        var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas',
+                                                       width, height);
+        var maskCtx = maskCanvas.context;
+        maskCtx.save();
 
-      if (block.found && block.length === suggestedLength) {
-        return {
-          stream: new Stream(headerBytes),
-          length: suggestedLength,
-        };
-      }
-    }
-    warn('Invalid "Length1" property in Type1 font -- trying to recover.');
-    stream.pos = streamStartPos; // Reset the stream position.
+        putBinaryImageMask(maskCtx, image);
 
-    var SCAN_BLOCK_LENGTH = 2048;
-    var actualLength;
-    while (true) {
-      var scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH);
-      block = findBlock(scanBytes, EEXEC_SIGNATURE, 0);
+        maskCtx.globalCompositeOperation = 'source-in';
 
-      if (block.length === 0) {
-        break;
-      }
-      stream.pos += block.length; // Update the stream position.
-
-      if (block.found) {
-        actualLength = stream.pos - streamStartPos;
-        break;
-      }
-    }
-    stream.pos = streamStartPos; // Reset the stream position.
-
-    if (actualLength) {
-      return {
-        stream: new Stream(stream.getBytes(actualLength)),
-        length: actualLength,
-      };
-    }
-    warn('Unable to recover "Length1" property in Type1 font -- using as is.');
-    return {
-      stream: new Stream(stream.getBytes(suggestedLength)),
-      length: suggestedLength,
-    };
-  }
-
-  function getEexecBlock(stream, suggestedLength) {
-    // We should ideally parse the eexec block to ensure that `suggestedLength`
-    // is correct, so we don't truncate the block data if it's too small.
-    // However, this would also require checking if the fixed-content portion
-    // exists (using the 'Length3' property), and ensuring that it's valid.
-    //
-    // Given that `suggestedLength` almost always is correct, all the validation
-    // would require a great deal of unnecessary parsing for most fonts.
-    // To save time, we always fetch the entire stream instead, which also avoid
-    // issues if `suggestedLength` is huge (see comment in `getHeaderBlock`).
-    //
-    // NOTE: This means that the function can include the fixed-content portion
-    // in the returned eexec block. In practice this does *not* seem to matter,
-    // since `Type1Parser_extractFontProgram` will skip over any non-commands.
-    var eexecBytes = stream.getBytes();
-    return {
-      stream: new Stream(eexecBytes),
-      length: eexecBytes.length,
-    };
-  }
-
-  function Type1Font(name, file, properties) {
-    // Some bad generators embed pfb file as is, we have to strip 6-byte header.
-    // Also, length1 and length2 might be off by 6 bytes as well.
-    // http://www.math.ubc.ca/~cass/piscript/type1.pdf
-    var PFB_HEADER_SIZE = 6;
-    var headerBlockLength = properties.length1;
-    var eexecBlockLength = properties.length2;
-    var pfbHeader = file.peekBytes(PFB_HEADER_SIZE);
-    var pfbHeaderPresent = pfbHeader[0] === 0x80 && pfbHeader[1] === 0x01;
-    if (pfbHeaderPresent) {
-      file.skip(PFB_HEADER_SIZE);
-      headerBlockLength = (pfbHeader[5] << 24) | (pfbHeader[4] << 16) |
-                          (pfbHeader[3] << 8) | pfbHeader[2];
-    }
-
-    // Get the data block containing glyphs and subrs informations
-    var headerBlock = getHeaderBlock(file, headerBlockLength);
-    headerBlockLength = headerBlock.length;
-    var headerBlockParser = new Type1Parser(headerBlock.stream, false,
-                                            SEAC_ANALYSIS_ENABLED);
-    headerBlockParser.extractFontHeader(properties);
-
-    if (pfbHeaderPresent) {
-      pfbHeader = file.getBytes(PFB_HEADER_SIZE);
-      eexecBlockLength = (pfbHeader[5] << 24) | (pfbHeader[4] << 16) |
-                         (pfbHeader[3] << 8) | pfbHeader[2];
-    }
-
-    // Decrypt the data blocks and retrieve it's content
-    var eexecBlock = getEexecBlock(file, eexecBlockLength);
-    eexecBlockLength = eexecBlock.length;
-    var eexecBlockParser = new Type1Parser(eexecBlock.stream, true,
-                                           SEAC_ANALYSIS_ENABLED);
-    var data = eexecBlockParser.extractFontProgram();
-    for (var info in data.properties) {
-      properties[info] = data.properties[info];
-    }
-
-    var charstrings = data.charstrings;
-    var type2Charstrings = this.getType2Charstrings(charstrings);
-    var subrs = this.getType2Subrs(data.subrs);
-
-    this.charstrings = charstrings;
-    this.data = this.wrap(name, type2Charstrings, this.charstrings,
-                          subrs, properties);
-    this.seacs = this.getSeacs(data.charstrings);
-  }
+        maskCtx.fillStyle = isPatternFill ?
+                            fillColor.getPattern(maskCtx, this) : fillColor;
+        maskCtx.fillRect(0, 0, width, height);
 
-  Type1Font.prototype = {
-    get numGlyphs() {
-      return this.charstrings.length + 1;
-    },
+        maskCtx.restore();
 
-    getCharset: function Type1Font_getCharset() {
-      var charset = ['.notdef'];
-      var charstrings = this.charstrings;
-      for (var glyphId = 0; glyphId < charstrings.length; glyphId++) {
-        charset.push(charstrings[glyphId].glyphName);
+        ctx.save();
+        ctx.transform.apply(ctx, image.transform);
+        ctx.scale(1, -1);
+        ctx.drawImage(maskCanvas.canvas, 0, 0, width, height,
+                      0, -1, 1, 1);
+        ctx.restore();
       }
-      return charset;
     },
 
-    getGlyphMapping: function Type1Font_getGlyphMapping(properties) {
-      var charstrings = this.charstrings;
-      var glyphNames = ['.notdef'], glyphId;
-      for (glyphId = 0; glyphId < charstrings.length; glyphId++) {
-        glyphNames.push(charstrings[glyphId].glyphName);
-      }
-      var encoding = properties.builtInEncoding;
-      if (encoding) {
-        var builtInEncoding = Object.create(null);
-        for (var charCode in encoding) {
-          glyphId = glyphNames.indexOf(encoding[charCode]);
-          if (glyphId >= 0) {
-            builtInEncoding[charCode] = glyphId;
-          }
-        }
+    paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
+      var imgData = this.objs.get(objId);
+      if (!imgData) {
+        warn('Dependent image isn\'t ready yet');
+        return;
       }
 
-      return type1FontGlyphMapping(properties, builtInEncoding, glyphNames);
+      this.paintInlineImageXObject(imgData);
     },
 
-    getSeacs: function Type1Font_getSeacs(charstrings) {
-      var i, ii;
-      var seacMap = [];
-      for (i = 0, ii = charstrings.length; i < ii; i++) {
-        var charstring = charstrings[i];
-        if (charstring.seac) {
-          // Offset by 1 for .notdef
-          seacMap[i + 1] = charstring.seac;
-        }
+    paintImageXObjectRepeat:
+      function CanvasGraphics_paintImageXObjectRepeat(objId, scaleX, scaleY,
+                                                          positions) {
+      var imgData = this.objs.get(objId);
+      if (!imgData) {
+        warn('Dependent image isn\'t ready yet');
+        return;
       }
-      return seacMap;
-    },
 
-    getType2Charstrings: function Type1Font_getType2Charstrings(
-                                    type1Charstrings) {
-      var type2Charstrings = [];
-      for (var i = 0, ii = type1Charstrings.length; i < ii; i++) {
-        type2Charstrings.push(type1Charstrings[i].charstring);
+      var width = imgData.width;
+      var height = imgData.height;
+      var map = [];
+      for (var i = 0, ii = positions.length; i < ii; i += 2) {
+        map.push({transform: [scaleX, 0, 0, scaleY, positions[i],
+                 positions[i + 1]], x: 0, y: 0, w: width, h: height});
       }
-      return type2Charstrings;
+      this.paintInlineImageXObjectGroup(imgData, map);
     },
 
-    getType2Subrs: function Type1Font_getType2Subrs(type1Subrs) {
-      var bias = 0;
-      var count = type1Subrs.length;
-      if (count < 1133) {
-        bias = 107;
-      } else if (count < 33769) {
-        bias = 1131;
+    paintInlineImageXObject:
+      function CanvasGraphics_paintInlineImageXObject(imgData) {
+      var width = imgData.width;
+      var height = imgData.height;
+      var ctx = this.ctx;
+
+      this.save();
+      // scale the image to the unit square
+      ctx.scale(1 / width, -1 / height);
+
+      var currentTransform = ctx.mozCurrentTransformInverse;
+      var a = currentTransform[0], b = currentTransform[1];
+      var widthScale = Math.max(Math.sqrt(a * a + b * b), 1);
+      var c = currentTransform[2], d = currentTransform[3];
+      var heightScale = Math.max(Math.sqrt(c * c + d * d), 1);
+
+      var imgToPaint, tmpCanvas;
+      // instanceof HTMLElement does not work in jsdom node.js module
+      if (imgData instanceof HTMLElement || !imgData.data) {
+        imgToPaint = imgData;
       } else {
-        bias = 32768;
+        tmpCanvas = this.cachedCanvases.getCanvas('inlineImage',
+                                                  width, height);
+        var tmpCtx = tmpCanvas.context;
+        putBinaryImageData(tmpCtx, imgData);
+        imgToPaint = tmpCanvas.canvas;
       }
 
-      // Add a bunch of empty subrs to deal with the Type2 bias
-      var type2Subrs = [];
-      var i;
-      for (i = 0; i < bias; i++) {
-        type2Subrs.push([0x0B]);
+      var paintWidth = width, paintHeight = height;
+      var tmpCanvasId = 'prescale1';
+      // Vertial or horizontal scaling shall not be more than 2 to not loose the
+      // pixels during drawImage operation, painting on the temporary canvas(es)
+      // that are twice smaller in size
+      while ((widthScale > 2 && paintWidth > 1) ||
+             (heightScale > 2 && paintHeight > 1)) {
+        var newWidth = paintWidth, newHeight = paintHeight;
+        if (widthScale > 2 && paintWidth > 1) {
+          newWidth = Math.ceil(paintWidth / 2);
+          widthScale /= paintWidth / newWidth;
+        }
+        if (heightScale > 2 && paintHeight > 1) {
+          newHeight = Math.ceil(paintHeight / 2);
+          heightScale /= paintHeight / newHeight;
+        }
+        tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId,
+                                                  newWidth, newHeight);
+        tmpCtx = tmpCanvas.context;
+        tmpCtx.clearRect(0, 0, newWidth, newHeight);
+        tmpCtx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight,
+                                     0, 0, newWidth, newHeight);
+        imgToPaint = tmpCanvas.canvas;
+        paintWidth = newWidth;
+        paintHeight = newHeight;
+        tmpCanvasId = tmpCanvasId === 'prescale1' ? 'prescale2' : 'prescale1';
       }
+      ctx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight,
+                                0, -height, width, height);
 
-      for (i = 0; i < count; i++) {
-        type2Subrs.push(type1Subrs[i]);
+      if (this.imageLayer) {
+        var position = this.getCanvasPosition(0, -height);
+        this.imageLayer.appendImage({
+          imgData: imgData,
+          left: position[0],
+          top: position[1],
+          width: width / currentTransform[0],
+          height: height / currentTransform[3]
+        });
       }
-
-      return type2Subrs;
+      this.restore();
     },
 
-    wrap: function Type1Font_wrap(name, glyphs, charstrings, subrs,
-                                  properties) {
-      var cff = new CFF();
-      cff.header = new CFFHeader(1, 0, 4, 4);
-
-      cff.names = [name];
-
-      var topDict = new CFFTopDict();
-      // CFF strings IDs 0...390 are predefined names, so refering
-      // to entries in our own String INDEX starts at SID 391.
-      topDict.setByName('version', 391);
-      topDict.setByName('Notice', 392);
-      topDict.setByName('FullName', 393);
-      topDict.setByName('FamilyName', 394);
-      topDict.setByName('Weight', 395);
-      topDict.setByName('Encoding', null); // placeholder
-      topDict.setByName('FontMatrix', properties.fontMatrix);
-      topDict.setByName('FontBBox', properties.bbox);
-      topDict.setByName('charset', null); // placeholder
-      topDict.setByName('CharStrings', null); // placeholder
-      topDict.setByName('Private', null); // placeholder
-      cff.topDict = topDict;
-
-      var strings = new CFFStrings();
-      strings.add('Version 0.11'); // Version
-      strings.add('See original notice'); // Notice
-      strings.add(name); // FullName
-      strings.add(name); // FamilyName
-      strings.add('Medium'); // Weight
-      cff.strings = strings;
-
-      cff.globalSubrIndex = new CFFIndex();
-
-      var count = glyphs.length;
-      var charsetArray = [0];
-      var i, ii;
-      for (i = 0; i < count; i++) {
-        var index = CFFStandardStrings.indexOf(charstrings[i].glyphName);
-        // TODO: Insert the string and correctly map it.  Previously it was
-        // thought mapping names that aren't in the standard strings to .notdef
-        // was fine, however in issue818 when mapping them all to .notdef the
-        // adieresis glyph no longer worked.
-        if (index === -1) {
-          index = 0;
-        }
-        charsetArray.push((index >> 8) & 0xff, index & 0xff);
-      }
-      cff.charset = new CFFCharset(false, 0, [], charsetArray);
+    paintInlineImageXObjectGroup:
+      function CanvasGraphics_paintInlineImageXObjectGroup(imgData, map) {
+      var ctx = this.ctx;
+      var w = imgData.width;
+      var h = imgData.height;
 
-      var charStringsIndex = new CFFIndex();
-      charStringsIndex.add([0x8B, 0x0E]); // .notdef
-      for (i = 0; i < count; i++) {
-        charStringsIndex.add(glyphs[i]);
-      }
-      cff.charStrings = charStringsIndex;
+      var tmpCanvas = this.cachedCanvases.getCanvas('inlineImage', w, h);
+      var tmpCtx = tmpCanvas.context;
+      putBinaryImageData(tmpCtx, imgData);
 
-      var privateDict = new CFFPrivateDict();
-      privateDict.setByName('Subrs', null); // placeholder
-      var fields = [
-        'BlueValues',
-        'OtherBlues',
-        'FamilyBlues',
-        'FamilyOtherBlues',
-        'StemSnapH',
-        'StemSnapV',
-        'BlueShift',
-        'BlueFuzz',
-        'BlueScale',
-        'LanguageGroup',
-        'ExpansionFactor',
-        'ForceBold',
-        'StdHW',
-        'StdVW'
-      ];
-      for (i = 0, ii = fields.length; i < ii; i++) {
-        var field = fields[i];
-        if (!(field in properties.privateData)) {
-          continue;
-        }
-        var value = properties.privateData[field];
-        if (isArray(value)) {
-          // All of the private dictionary array data in CFF must be stored as
-          // "delta-encoded" numbers.
-          for (var j = value.length - 1; j > 0; j--) {
-            value[j] -= value[j - 1]; // ... difference from previous value
-          }
+      for (var i = 0, ii = map.length; i < ii; i++) {
+        var entry = map[i];
+        ctx.save();
+        ctx.transform.apply(ctx, entry.transform);
+        ctx.scale(1, -1);
+        ctx.drawImage(tmpCanvas.canvas, entry.x, entry.y, entry.w, entry.h,
+                      0, -1, 1, 1);
+        if (this.imageLayer) {
+          var position = this.getCanvasPosition(entry.x, entry.y);
+          this.imageLayer.appendImage({
+            imgData: imgData,
+            left: position[0],
+            top: position[1],
+            width: w,
+            height: h
+          });
         }
-        privateDict.setByName(field, value);
+        ctx.restore();
       }
-      cff.topDict.privateDict = privateDict;
+    },
 
-      var subrIndex = new CFFIndex();
-      for (i = 0, ii = subrs.length; i < ii; i++) {
-        subrIndex.add(subrs[i]);
-      }
-      privateDict.subrsIndex = subrIndex;
+    paintSolidColorImageMask:
+      function CanvasGraphics_paintSolidColorImageMask() {
+        this.ctx.fillRect(0, 0, 1, 1);
+    },
 
-      var compiler = new CFFCompiler(cff);
-      return compiler.compile();
-    }
-  };
+    paintXObject: function CanvasGraphics_paintXObject() {
+      warn('Unsupported \'paintXObject\' command.');
+    },
 
-  return Type1Font;
-})();
+    // Marked content
 
-var CFFFont = (function CFFFontClosure() {
-  function CFFFont(file, properties) {
-    this.properties = properties;
+    markPoint: function CanvasGraphics_markPoint(tag) {
+      // TODO Marked content.
+    },
+    markPointProps: function CanvasGraphics_markPointProps(tag, properties) {
+      // TODO Marked content.
+    },
+    beginMarkedContent: function CanvasGraphics_beginMarkedContent(tag) {
+      // TODO Marked content.
+    },
+    beginMarkedContentProps: function CanvasGraphics_beginMarkedContentProps(
+                                        tag, properties) {
+      // TODO Marked content.
+    },
+    endMarkedContent: function CanvasGraphics_endMarkedContent() {
+      // TODO Marked content.
+    },
 
-    var parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED);
-    this.cff = parser.parse();
-    var compiler = new CFFCompiler(this.cff);
-    this.seacs = this.cff.seacs;
-    try {
-      this.data = compiler.compile();
-    } catch (e) {
-      warn('Failed to compile font ' + properties.loadedName);
-      // There may have just been an issue with the compiler, set the data
-      // anyway and hope the font loaded.
-      this.data = file;
-    }
-  }
+    // Compatibility
 
-  CFFFont.prototype = {
-    get numGlyphs() {
-      return this.cff.charStrings.count;
+    beginCompat: function CanvasGraphics_beginCompat() {
+      // TODO ignore undefined operators (should we do that anyway?)
     },
-    getCharset: function CFFFont_getCharset() {
-      return this.cff.charset.charset;
+    endCompat: function CanvasGraphics_endCompat() {
+      // TODO stop ignoring undefined operators
     },
-    getGlyphMapping: function CFFFont_getGlyphMapping() {
-      var cff = this.cff;
-      var properties = this.properties;
-      var charsets = cff.charset.charset;
-      var charCodeToGlyphId;
-      var glyphId;
 
-      if (properties.composite) {
-        charCodeToGlyphId = Object.create(null);
-        if (cff.isCIDFont) {
-          // If the font is actually a CID font then we should use the charset
-          // to map CIDs to GIDs.
-          for (glyphId = 0; glyphId < charsets.length; glyphId++) {
-            var cid = charsets[glyphId];
-            var charCode = properties.cMap.charCodeOf(cid);
-            charCodeToGlyphId[charCode] = glyphId;
+    // Helper functions
+
+    consumePath: function CanvasGraphics_consumePath() {
+      var ctx = this.ctx;
+      if (this.pendingClip) {
+        if (this.pendingClip === EO_CLIP) {
+          if (ctx.mozFillRule !== undefined) {
+            ctx.mozFillRule = 'evenodd';
+            ctx.clip();
+            ctx.mozFillRule = 'nonzero';
+          } else {
+            ctx.clip('evenodd');
           }
         } else {
-          // If it is NOT actually a CID font then CIDs should be mapped
-          // directly to GIDs.
-          for (glyphId = 0; glyphId < cff.charStrings.count; glyphId++) {
-            charCodeToGlyphId[glyphId] = glyphId;
-          }
+          ctx.clip();
         }
-        return charCodeToGlyphId;
+        this.pendingClip = null;
       }
-
-      var encoding = cff.encoding ? cff.encoding.encoding : null;
-      charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets);
-      return charCodeToGlyphId;
+      ctx.beginPath();
+    },
+    getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) {
+      if (this.cachedGetSinglePixelWidth === null) {
+        var inverse = this.ctx.mozCurrentTransformInverse;
+        // max of the current horizontal and vertical scale
+        this.cachedGetSinglePixelWidth = Math.sqrt(Math.max(
+          (inverse[0] * inverse[0] + inverse[1] * inverse[1]),
+          (inverse[2] * inverse[2] + inverse[3] * inverse[3])));
+      }
+      return this.cachedGetSinglePixelWidth;
+    },
+    getCanvasPosition: function CanvasGraphics_getCanvasPosition(x, y) {
+      var transform = this.ctx.mozCurrentTransform;
+      return [
+        transform[0] * x + transform[2] * y + transform[4],
+        transform[1] * x + transform[3] * y + transform[5]
+      ];
     }
   };
 
-  return CFFFont;
-})();
-
-// Workaround for seac on Windows.
-(function checkSeacSupport() {
-  if (typeof navigator !== 'undefined' && /Windows/.test(navigator.userAgent)) {
-    SEAC_ANALYSIS_ENABLED = true;
+  for (var op in OPS) {
+    CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op];
   }
-})();
 
-// Workaround for Private Use Area characters in Chrome on Windows
-// http://code.google.com/p/chromium/issues/detail?id=122465
-// https://github.com/mozilla/pdf.js/issues/1689
-(function checkChromeWindows() {
-  if (typeof navigator !== 'undefined' &&
-      /Windows.*Chrome/.test(navigator.userAgent)) {
-    SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = true;
-  }
+  return CanvasGraphics;
 })();
 
-exports.SEAC_ANALYSIS_ENABLED = SEAC_ANALYSIS_ENABLED;
-exports.ErrorFont = ErrorFont;
-exports.Font = Font;
-exports.FontFlags = FontFlags;
-exports.IdentityToUnicodeMap = IdentityToUnicodeMap;
-exports.ToUnicodeMap = ToUnicodeMap;
-exports.getFontType = getFontType;
+exports.CanvasGraphics = CanvasGraphics;
+exports.createScratchCanvas = createScratchCanvas;
 }));
 
 
 (function (root, factory) {
   {
-    factory((root.pdfjsCorePsParser = {}), root.pdfjsSharedUtil,
-      root.pdfjsCoreParser);
+    factory((root.pdfjsCoreFonts = {}), root.pdfjsSharedUtil,
+      root.pdfjsCorePrimitives, root.pdfjsCoreStream, root.pdfjsCoreParser,
+      root.pdfjsCoreGlyphList, root.pdfjsCoreCharsets,
+      root.pdfjsCoreFontRenderer, root.pdfjsCoreEncodings,
+      root.pdfjsCoreStandardFonts, root.pdfjsCoreUnicode,
+      root.pdfjsCoreType1Parser, root.pdfjsCoreCFFParser);
   }
-}(this, function (exports, sharedUtil, coreParser) {
+}(this, function (exports, sharedUtil, corePrimitives, coreStream, coreParser,
+                  coreGlyphList, coreCharsets, coreFontRenderer,
+                  coreEncodings, coreStandardFonts, coreUnicode,
+                  coreType1Parser, coreCFFParser) {
 
+var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
+var FontType = sharedUtil.FontType;
+var Util = sharedUtil.Util;
+var assert = sharedUtil.assert;
+var bytesToString = sharedUtil.bytesToString;
 var error = sharedUtil.error;
-var EOF = coreParser.EOF;
+var info = sharedUtil.info;
+var isArray = sharedUtil.isArray;
+var isInt = sharedUtil.isInt;
+var isNum = sharedUtil.isNum;
+var readUint32 = sharedUtil.readUint32;
+var shadow = sharedUtil.shadow;
+var stringToBytes = sharedUtil.stringToBytes;
+var string32 = sharedUtil.string32;
+var warn = sharedUtil.warn;
+var MissingDataException = sharedUtil.MissingDataException;
+var Stream = coreStream.Stream;
 var Lexer = coreParser.Lexer;
+var getGlyphsUnicode = coreGlyphList.getGlyphsUnicode;
+var getDingbatsGlyphsUnicode = coreGlyphList.getDingbatsGlyphsUnicode;
+var ISOAdobeCharset = coreCharsets.ISOAdobeCharset;
+var ExpertCharset = coreCharsets.ExpertCharset;
+var ExpertSubsetCharset = coreCharsets.ExpertSubsetCharset;
+var FontRendererFactory = coreFontRenderer.FontRendererFactory;
+var WinAnsiEncoding = coreEncodings.WinAnsiEncoding;
+var StandardEncoding = coreEncodings.StandardEncoding;
+var MacRomanEncoding = coreEncodings.MacRomanEncoding;
+var SymbolSetEncoding = coreEncodings.SymbolSetEncoding;
+var ZapfDingbatsEncoding = coreEncodings.ZapfDingbatsEncoding;
+var ExpertEncoding = coreEncodings.ExpertEncoding;
+var getEncoding = coreEncodings.getEncoding;
+var getStdFontMap = coreStandardFonts.getStdFontMap;
+var getNonStdFontMap = coreStandardFonts.getNonStdFontMap;
+var getGlyphMapForStandardFonts = coreStandardFonts.getGlyphMapForStandardFonts;
+var getSupplementalGlyphMapForArialBlack =
+  coreStandardFonts.getSupplementalGlyphMapForArialBlack;
+var getUnicodeRangeFor = coreUnicode.getUnicodeRangeFor;
+var mapSpecialUnicodeValues = coreUnicode.mapSpecialUnicodeValues;
+var getUnicodeForGlyph = coreUnicode.getUnicodeForGlyph;
+var Type1Parser = coreType1Parser.Type1Parser;
+var CFFStandardStrings = coreCFFParser.CFFStandardStrings;
+var CFFParser = coreCFFParser.CFFParser;
+var CFFCompiler = coreCFFParser.CFFCompiler;
+var CFF = coreCFFParser.CFF;
+var CFFHeader = coreCFFParser.CFFHeader;
+var CFFTopDict = coreCFFParser.CFFTopDict;
+var CFFPrivateDict = coreCFFParser.CFFPrivateDict;
+var CFFStrings = coreCFFParser.CFFStrings;
+var CFFIndex = coreCFFParser.CFFIndex;
+var CFFCharset = coreCFFParser.CFFCharset;
 
-var PostScriptParser = (function PostScriptParserClosure() {
-  function PostScriptParser(lexer) {
-    this.lexer = lexer;
-    this.operators = [];
-    this.token = null;
-    this.prev = null;
-  }
-  PostScriptParser.prototype = {
-    nextToken: function PostScriptParser_nextToken() {
-      this.prev = this.token;
-      this.token = this.lexer.getToken();
-    },
-    accept: function PostScriptParser_accept(type) {
-      if (this.token.type === type) {
-        this.nextToken();
-        return true;
-      }
-      return false;
-    },
-    expect: function PostScriptParser_expect(type) {
-      if (this.accept(type)) {
-        return true;
-      }
-      error('Unexpected symbol: found ' + this.token.type + ' expected ' +
-        type + '.');
-    },
-    parse: function PostScriptParser_parse() {
-      this.nextToken();
-      this.expect(PostScriptTokenTypes.LBRACE);
-      this.parseBlock();
-      this.expect(PostScriptTokenTypes.RBRACE);
-      return this.operators;
-    },
-    parseBlock: function PostScriptParser_parseBlock() {
-      while (true) {
-        if (this.accept(PostScriptTokenTypes.NUMBER)) {
-          this.operators.push(this.prev.value);
-        } else if (this.accept(PostScriptTokenTypes.OPERATOR)) {
-          this.operators.push(this.prev.value);
-        } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
-          this.parseCondition();
-        } else {
-          return;
-        }
-      }
-    },
-    parseCondition: function PostScriptParser_parseCondition() {
-      // Add two place holders that will be updated later
-      var conditionLocation = this.operators.length;
-      this.operators.push(null, null);
+// Unicode Private Use Area
+var PRIVATE_USE_OFFSET_START = 0xE000;
+var PRIVATE_USE_OFFSET_END = 0xF8FF;
+var SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = false;
 
-      this.parseBlock();
-      this.expect(PostScriptTokenTypes.RBRACE);
-      if (this.accept(PostScriptTokenTypes.IF)) {
-        // The true block is right after the 'if' so it just falls through on
-        // true else it jumps and skips the true block.
-        this.operators[conditionLocation] = this.operators.length;
-        this.operators[conditionLocation + 1] = 'jz';
-      } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
-        var jumpLocation = this.operators.length;
-        this.operators.push(null, null);
-        var endOfTrue = this.operators.length;
-        this.parseBlock();
-        this.expect(PostScriptTokenTypes.RBRACE);
-        this.expect(PostScriptTokenTypes.IFELSE);
-        // The jump is added at the end of the true block to skip the false
-        // block.
-        this.operators[jumpLocation] = this.operators.length;
-        this.operators[jumpLocation + 1] = 'j';
+// PDF Glyph Space Units are one Thousandth of a TextSpace Unit
+// except for Type 3 fonts
+var PDF_GLYPH_SPACE_UNITS = 1000;
 
-        this.operators[conditionLocation] = endOfTrue;
-        this.operators[conditionLocation + 1] = 'jz';
-      } else {
-        error('PS Function: error parsing conditional.');
-      }
-    }
-  };
-  return PostScriptParser;
-})();
+// Accented charactars are not displayed properly on Windows, using this flag
+// to control analysis of seac charstrings.
+var SEAC_ANALYSIS_ENABLED = false;
 
-var PostScriptTokenTypes = {
-  LBRACE: 0,
-  RBRACE: 1,
-  NUMBER: 2,
-  OPERATOR: 3,
-  IF: 4,
-  IFELSE: 5
+var FontFlags = {
+  FixedPitch: 1,
+  Serif: 2,
+  Symbolic: 4,
+  Script: 8,
+  Nonsymbolic: 32,
+  Italic: 64,
+  AllCap: 65536,
+  SmallCap: 131072,
+  ForceBold: 262144
 };
 
-var PostScriptToken = (function PostScriptTokenClosure() {
-  function PostScriptToken(type, value) {
-    this.type = type;
-    this.value = value;
+var MacStandardGlyphOrdering = [
+  '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl',
+  'numbersign', 'dollar', 'percent', 'ampersand', 'quotesingle', 'parenleft',
+  'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash',
+  'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
+  'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at',
+  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+  'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft',
+  'backslash', 'bracketright', 'asciicircum', 'underscore', 'grave', 'a', 'b',
+  'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
+  'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
+  'asciitilde', 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde',
+  'Odieresis', 'Udieresis', 'aacute', 'agrave', 'acircumflex', 'adieresis',
+  'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis',
+  'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve',
+  'ocircumflex', 'odieresis', 'otilde', 'uacute', 'ugrave', 'ucircumflex',
+  'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', 'bullet',
+  'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute',
+  'dieresis', 'notequal', 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal',
+  'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', 'product', 'pi',
+  'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash',
+  'questiondown', 'exclamdown', 'logicalnot', 'radical', 'florin',
+  'approxequal', 'Delta', 'guillemotleft', 'guillemotright', 'ellipsis',
+  'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash',
+  'emdash', 'quotedblleft', 'quotedblright', 'quoteleft', 'quoteright',
+  'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', 'currency',
+  'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered',
+  'quotesinglbase', 'quotedblbase', 'perthousand', 'Acircumflex',
+  'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex',
+  'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute',
+  'Ucircumflex', 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron',
+  'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
+  'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar',
+  'Eth', 'eth', 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply',
+  'onesuperior', 'twosuperior', 'threesuperior', 'onehalf', 'onequarter',
+  'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla',
+  'scedilla', 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat'];
+
+function adjustWidths(properties) {
+  if (!properties.fontMatrix) {
+    return;
+  }
+  if (properties.fontMatrix[0] === FONT_IDENTITY_MATRIX[0]) {
+    return;
+  }
+  // adjusting width to fontMatrix scale
+  var scale = 0.001 / properties.fontMatrix[0];
+  var glyphsWidths = properties.widths;
+  for (var glyph in glyphsWidths) {
+    glyphsWidths[glyph] *= scale;
   }
+  properties.defaultWidth *= scale;
+}
 
-  var opCache = Object.create(null);
+function getFontType(type, subtype) {
+  switch (type) {
+    case 'Type1':
+      return subtype === 'Type1C' ? FontType.TYPE1C : FontType.TYPE1;
+    case 'CIDFontType0':
+      return subtype === 'CIDFontType0C' ? FontType.CIDFONTTYPE0C :
+        FontType.CIDFONTTYPE0;
+    case 'OpenType':
+      return FontType.OPENTYPE;
+    case 'TrueType':
+      return FontType.TRUETYPE;
+    case 'CIDFontType2':
+      return FontType.CIDFONTTYPE2;
+    case 'MMType1':
+      return FontType.MMTYPE1;
+    case 'Type0':
+      return FontType.TYPE0;
+    default:
+      return FontType.UNKNOWN;
+  }
+}
 
-  PostScriptToken.getOperator = function PostScriptToken_getOperator(op) {
-    var opValue = opCache[op];
-    if (opValue) {
-      return opValue;
-    }
-    return opCache[op] = new PostScriptToken(PostScriptTokenTypes.OPERATOR, op);
+var Glyph = (function GlyphClosure() {
+  function Glyph(fontChar, unicode, accent, width, vmetric, operatorListId,
+                 isSpace, isInFont) {
+    this.fontChar = fontChar;
+    this.unicode = unicode;
+    this.accent = accent;
+    this.width = width;
+    this.vmetric = vmetric;
+    this.operatorListId = operatorListId;
+    this.isSpace = isSpace;
+    this.isInFont = isInFont;
+  }
+
+  Glyph.prototype.matchesForCache = function(fontChar, unicode, accent, width,
+                                             vmetric, operatorListId, isSpace,
+                                             isInFont) {
+    return this.fontChar === fontChar &&
+           this.unicode === unicode &&
+           this.accent === accent &&
+           this.width === width &&
+           this.vmetric === vmetric &&
+           this.operatorListId === operatorListId &&
+           this.isSpace === isSpace &&
+           this.isInFont === isInFont;
   };
 
-  PostScriptToken.LBRACE = new PostScriptToken(PostScriptTokenTypes.LBRACE,
-    '{');
-  PostScriptToken.RBRACE = new PostScriptToken(PostScriptTokenTypes.RBRACE,
-    '}');
-  PostScriptToken.IF = new PostScriptToken(PostScriptTokenTypes.IF, 'IF');
-  PostScriptToken.IFELSE = new PostScriptToken(PostScriptTokenTypes.IFELSE,
-    'IFELSE');
-  return PostScriptToken;
+  return Glyph;
 })();
 
-var PostScriptLexer = (function PostScriptLexerClosure() {
-  function PostScriptLexer(stream) {
-    this.stream = stream;
-    this.nextChar();
-
-    this.strBuf = [];
+var ToUnicodeMap = (function ToUnicodeMapClosure() {
+  function ToUnicodeMap(cmap) {
+    // The elements of this._map can be integers or strings, depending on how
+    // |cmap| was created.
+    this._map = cmap;
   }
-  PostScriptLexer.prototype = {
-    nextChar: function PostScriptLexer_nextChar() {
-      return (this.currentChar = this.stream.getByte());
-    },
-    getToken: function PostScriptLexer_getToken() {
-      var comment = false;
-      var ch = this.currentChar;
 
-      // skip comments
-      while (true) {
-        if (ch < 0) {
-          return EOF;
-        }
+  ToUnicodeMap.prototype = {
+    get length() {
+      return this._map.length;
+    },
 
-        if (comment) {
-          if (ch === 0x0A || ch === 0x0D) {
-            comment = false;
-          }
-        } else if (ch === 0x25) { // '%'
-          comment = true;
-        } else if (!Lexer.isSpace(ch)) {
-          break;
-        }
-        ch = this.nextChar();
-      }
-      switch (ch | 0) {
-        case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: // '0'-'4'
-        case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: // '5'-'9'
-        case 0x2B: case 0x2D: case 0x2E: // '+', '-', '.'
-          return new PostScriptToken(PostScriptTokenTypes.NUMBER,
-                                     this.getNumber());
-        case 0x7B: // '{'
-          this.nextChar();
-          return PostScriptToken.LBRACE;
-        case 0x7D: // '}'
-          this.nextChar();
-          return PostScriptToken.RBRACE;
+    forEach: function(callback) {
+      for (var charCode in this._map) {
+        callback(charCode, this._map[charCode].charCodeAt(0));
       }
-      // operator
-      var strBuf = this.strBuf;
-      strBuf.length = 0;
-      strBuf[0] = String.fromCharCode(ch);
+    },
 
-      while ((ch = this.nextChar()) >= 0 && // and 'A'-'Z', 'a'-'z'
-             ((ch >= 0x41 && ch <= 0x5A) || (ch >= 0x61 && ch <= 0x7A))) {
-        strBuf.push(String.fromCharCode(ch));
-      }
-      var str = strBuf.join('');
-      switch (str.toLowerCase()) {
-        case 'if':
-          return PostScriptToken.IF;
-        case 'ifelse':
-          return PostScriptToken.IFELSE;
-        default:
-          return PostScriptToken.getOperator(str);
-      }
+    has: function(i) {
+      return this._map[i] !== undefined;
     },
-    getNumber: function PostScriptLexer_getNumber() {
-      var ch = this.currentChar;
-      var strBuf = this.strBuf;
-      strBuf.length = 0;
-      strBuf[0] = String.fromCharCode(ch);
 
-      while ((ch = this.nextChar()) >= 0) {
-        if ((ch >= 0x30 && ch <= 0x39) || // '0'-'9'
-            ch === 0x2D || ch === 0x2E) { // '-', '.'
-          strBuf.push(String.fromCharCode(ch));
-        } else {
-          break;
-        }
-      }
-      var value = parseFloat(strBuf.join(''));
-      if (isNaN(value)) {
-        error('Invalid floating point number: ' + value);
-      }
-      return value;
+    get: function(i) {
+      return this._map[i];
+    },
+
+    charCodeOf: function(v) {
+      return this._map.indexOf(v);
     }
   };
-  return PostScriptLexer;
-})();
-
-exports.PostScriptLexer = PostScriptLexer;
-exports.PostScriptParser = PostScriptParser;
-}));
 
+  return ToUnicodeMap;
+})();
 
-(function (root, factory) {
-  {
-    factory((root.pdfjsDisplayCanvas = {}), root.pdfjsSharedUtil,
-      root.pdfjsDisplayDOMUtils, root.pdfjsDisplayPatternHelper,
-      root.pdfjsDisplayWebGL);
+var IdentityToUnicodeMap = (function IdentityToUnicodeMapClosure() {
+  function IdentityToUnicodeMap(firstChar, lastChar) {
+    this.firstChar = firstChar;
+    this.lastChar = lastChar;
   }
-}(this, function (exports, sharedUtil, displayDOMUtils, displayPatternHelper,
-                  displayWebGL) {
-
-var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
-var IDENTITY_MATRIX = sharedUtil.IDENTITY_MATRIX;
-var ImageKind = sharedUtil.ImageKind;
-var OPS = sharedUtil.OPS;
-var TextRenderingMode = sharedUtil.TextRenderingMode;
-var Uint32ArrayView = sharedUtil.Uint32ArrayView;
-var Util = sharedUtil.Util;
-var assert = sharedUtil.assert;
-var info = sharedUtil.info;
-var isNum = sharedUtil.isNum;
-var isArray = sharedUtil.isArray;
-var error = sharedUtil.error;
-var shadow = sharedUtil.shadow;
-var warn = sharedUtil.warn;
-var TilingPattern = displayPatternHelper.TilingPattern;
-var getShadingPatternFromIR = displayPatternHelper.getShadingPatternFromIR;
-var WebGLUtils = displayWebGL.WebGLUtils;
 
-// <canvas> contexts store most of the state we need natively.
-// However, PDF needs a bit more state, which we store here.
+  IdentityToUnicodeMap.prototype = {
+    get length() {
+      return (this.lastChar + 1) - this.firstChar;
+    },
 
-// Minimal font size that would be used during canvas fillText operations.
-var MIN_FONT_SIZE = 16;
-// Maximum font size that would be used during canvas fillText operations.
-var MAX_FONT_SIZE = 100;
-var MAX_GROUP_SIZE = 4096;
+    forEach: function (callback) {
+      for (var i = this.firstChar, ii = this.lastChar; i <= ii; i++) {
+        callback(i, i);
+      }
+    },
 
-// Heuristic value used when enforcing minimum line widths.
-var MIN_WIDTH_FACTOR = 0.65;
+    has: function (i) {
+      return this.firstChar <= i && i <= this.lastChar;
+    },
 
-var COMPILE_TYPE3_GLYPHS = true;
-var MAX_SIZE_TO_COMPILE = 1000;
+    get: function (i) {
+      if (this.firstChar <= i && i <= this.lastChar) {
+        return String.fromCharCode(i);
+      }
+      return undefined;
+    },
 
-var FULL_CHUNK_HEIGHT = 16;
+    charCodeOf: function (v) {
+      return (isInt(v) && v >= this.firstChar && v <= this.lastChar) ? v : -1;
+    }
+  };
 
-function createScratchCanvas(width, height) {
-  var canvas = document.createElement('canvas');
-  canvas.width = width;
-  canvas.height = height;
-  return canvas;
-}
+  return IdentityToUnicodeMap;
+})();
 
-function addContextCurrentTransform(ctx) {
-  // If the context doesn't expose a `mozCurrentTransform`, add a JS based one.
-  if (!ctx.mozCurrentTransform) {
-    ctx._originalSave = ctx.save;
-    ctx._originalRestore = ctx.restore;
-    ctx._originalRotate = ctx.rotate;
-    ctx._originalScale = ctx.scale;
-    ctx._originalTranslate = ctx.translate;
-    ctx._originalTransform = ctx.transform;
-    ctx._originalSetTransform = ctx.setTransform;
+var OpenTypeFileBuilder = (function OpenTypeFileBuilderClosure() {
+  function writeInt16(dest, offset, num) {
+    dest[offset] = (num >> 8) & 0xFF;
+    dest[offset + 1] = num & 0xFF;
+  }
 
-    ctx._transformMatrix = ctx._transformMatrix || [1, 0, 0, 1, 0, 0];
-    ctx._transformStack = [];
+  function writeInt32(dest, offset, num) {
+    dest[offset] = (num >> 24) & 0xFF;
+    dest[offset + 1] = (num >> 16) & 0xFF;
+    dest[offset + 2] = (num >> 8) & 0xFF;
+    dest[offset + 3] = num & 0xFF;
+  }
 
-    Object.defineProperty(ctx, 'mozCurrentTransform', {
-      get: function getCurrentTransform() {
-        return this._transformMatrix;
+  function writeData(dest, offset, data) {
+    var i, ii;
+    if (data instanceof Uint8Array) {
+      dest.set(data, offset);
+    } else if (typeof data === 'string') {
+      for (i = 0, ii = data.length; i < ii; i++) {
+        dest[offset++] = data.charCodeAt(i) & 0xFF;
       }
-    });
-
-    Object.defineProperty(ctx, 'mozCurrentTransformInverse', {
-      get: function getCurrentTransformInverse() {
-        // Calculation done using WolframAlpha:
-        // http://www.wolframalpha.com/input/?
-        //   i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}}
-
-        var m = this._transformMatrix;
-        var a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5];
+    } else {
+      // treating everything else as array
+      for (i = 0, ii = data.length; i < ii; i++) {
+        dest[offset++] = data[i] & 0xFF;
+      }
+    }
+  }
 
-        var ad_bc = a * d - b * c;
-        var bc_ad = b * c - a * d;
+  function OpenTypeFileBuilder(sfnt) {
+    this.sfnt = sfnt;
+    this.tables = Object.create(null);
+  }
 
-        return [
-          d / ad_bc,
-          b / bc_ad,
-          c / bc_ad,
-          a / ad_bc,
-          (d * e - c * f) / bc_ad,
-          (b * e - a * f) / ad_bc
-        ];
-      }
-    });
-
-    ctx.save = function ctxSave() {
-      var old = this._transformMatrix;
-      this._transformStack.push(old);
-      this._transformMatrix = old.slice(0, 6);
-
-      this._originalSave();
+  OpenTypeFileBuilder.getSearchParams =
+      function OpenTypeFileBuilder_getSearchParams(entriesCount, entrySize) {
+    var maxPower2 = 1, log2 = 0;
+    while ((maxPower2 ^ entriesCount) > maxPower2) {
+      maxPower2 <<= 1;
+      log2++;
+    }
+    var searchRange = maxPower2 * entrySize;
+    return {
+      range: searchRange,
+      entry: log2,
+      rangeShift: entrySize * entriesCount - searchRange
     };
+  };
 
-    ctx.restore = function ctxRestore() {
-      var prev = this._transformStack.pop();
-      if (prev) {
-        this._transformMatrix = prev;
-        this._originalRestore();
-      }
-    };
+  var OTF_HEADER_SIZE = 12;
+  var OTF_TABLE_ENTRY_SIZE = 16;
 
-    ctx.translate = function ctxTranslate(x, y) {
-      var m = this._transformMatrix;
-      m[4] = m[0] * x + m[2] * y + m[4];
-      m[5] = m[1] * x + m[3] * y + m[5];
+  OpenTypeFileBuilder.prototype = {
+    toArray: function OpenTypeFileBuilder_toArray() {
+      var sfnt = this.sfnt;
 
-      this._originalTranslate(x, y);
-    };
+      // Tables needs to be written by ascendant alphabetic order
+      var tables = this.tables;
+      var tablesNames = Object.keys(tables);
+      tablesNames.sort();
+      var numTables = tablesNames.length;
 
-    ctx.scale = function ctxScale(x, y) {
-      var m = this._transformMatrix;
-      m[0] = m[0] * x;
-      m[1] = m[1] * x;
-      m[2] = m[2] * y;
-      m[3] = m[3] * y;
+      var i, j, jj, table, tableName;
+      // layout the tables data
+      var offset = OTF_HEADER_SIZE + numTables * OTF_TABLE_ENTRY_SIZE;
+      var tableOffsets = [offset];
+      for (i = 0; i < numTables; i++) {
+        table = tables[tablesNames[i]];
+        var paddedLength = ((table.length + 3) & ~3) >>> 0;
+        offset += paddedLength;
+        tableOffsets.push(offset);
+      }
 
-      this._originalScale(x, y);
-    };
+      var file = new Uint8Array(offset);
+      // write the table data first (mostly for checksum)
+      for (i = 0; i < numTables; i++) {
+        table = tables[tablesNames[i]];
+        writeData(file, tableOffsets[i], table);
+      }
 
-    ctx.transform = function ctxTransform(a, b, c, d, e, f) {
-      var m = this._transformMatrix;
-      this._transformMatrix = [
-        m[0] * a + m[2] * b,
-        m[1] * a + m[3] * b,
-        m[0] * c + m[2] * d,
-        m[1] * c + m[3] * d,
-        m[0] * e + m[2] * f + m[4],
-        m[1] * e + m[3] * f + m[5]
-      ];
+      // sfnt version (4 bytes)
+      if (sfnt === 'true') {
+        // Windows hates the Mac TrueType sfnt version number
+        sfnt = string32(0x00010000);
+      }
+      file[0] = sfnt.charCodeAt(0) & 0xFF;
+      file[1] = sfnt.charCodeAt(1) & 0xFF;
+      file[2] = sfnt.charCodeAt(2) & 0xFF;
+      file[3] = sfnt.charCodeAt(3) & 0xFF;
 
-      ctx._originalTransform(a, b, c, d, e, f);
-    };
+      // numTables (2 bytes)
+      writeInt16(file, 4, numTables);
 
-    ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
-      this._transformMatrix = [a, b, c, d, e, f];
+      var searchParams = OpenTypeFileBuilder.getSearchParams(numTables, 16);
 
-      ctx._originalSetTransform(a, b, c, d, e, f);
-    };
+      // searchRange (2 bytes)
+      writeInt16(file, 6, searchParams.range);
+      // entrySelector (2 bytes)
+      writeInt16(file, 8, searchParams.entry);
+      // rangeShift (2 bytes)
+      writeInt16(file, 10, searchParams.rangeShift);
 
-    ctx.rotate = function ctxRotate(angle) {
-      var cosValue = Math.cos(angle);
-      var sinValue = Math.sin(angle);
+      offset = OTF_HEADER_SIZE;
+      // writing table entries
+      for (i = 0; i < numTables; i++) {
+        tableName = tablesNames[i];
+        file[offset] = tableName.charCodeAt(0) & 0xFF;
+        file[offset + 1] = tableName.charCodeAt(1) & 0xFF;
+        file[offset + 2] = tableName.charCodeAt(2) & 0xFF;
+        file[offset + 3] = tableName.charCodeAt(3) & 0xFF;
 
-      var m = this._transformMatrix;
-      this._transformMatrix = [
-        m[0] * cosValue + m[2] * sinValue,
-        m[1] * cosValue + m[3] * sinValue,
-        m[0] * (-sinValue) + m[2] * cosValue,
-        m[1] * (-sinValue) + m[3] * cosValue,
-        m[4],
-        m[5]
-      ];
+        // checksum
+        var checksum = 0;
+        for (j = tableOffsets[i], jj = tableOffsets[i + 1]; j < jj; j += 4) {
+          var quad = readUint32(file, j);
+          checksum = (checksum + quad) >>> 0;
+        }
+        writeInt32(file, offset + 4, checksum);
 
-      this._originalRotate(angle);
-    };
-  }
-}
+        // offset
+        writeInt32(file, offset + 8, tableOffsets[i]);
+        // length
+        writeInt32(file, offset + 12, tables[tableName].length);
 
-var CachedCanvases = (function CachedCanvasesClosure() {
-  function CachedCanvases() {
-    this.cache = Object.create(null);
-  }
-  CachedCanvases.prototype = {
-    getCanvas: function CachedCanvases_getCanvas(id, width, height,
-                                                 trackTransform) {
-      var canvasEntry;
-      if (this.cache[id] !== undefined) {
-        canvasEntry = this.cache[id];
-        canvasEntry.canvas.width = width;
-        canvasEntry.canvas.height = height;
-        // reset canvas transform for emulated mozCurrentTransform, if needed
-        canvasEntry.context.setTransform(1, 0, 0, 1, 0, 0);
-      } else {
-        var canvas = createScratchCanvas(width, height);
-        var ctx = canvas.getContext('2d');
-        if (trackTransform) {
-          addContextCurrentTransform(ctx);
-        }
-        this.cache[id] = canvasEntry = {canvas: canvas, context: ctx};
+        offset += OTF_TABLE_ENTRY_SIZE;
       }
-      return canvasEntry;
+      return file;
     },
-    clear: function () {
-      for (var id in this.cache) {
-        var canvasEntry = this.cache[id];
-        // Zeroing the width and height causes Firefox to release graphics
-        // resources immediately, which can greatly reduce memory consumption.
-        canvasEntry.canvas.width = 0;
-        canvasEntry.canvas.height = 0;
-        delete this.cache[id];
+
+    addTable: function OpenTypeFileBuilder_addTable(tag, data) {
+      if (tag in this.tables) {
+        throw new Error('Table ' + tag + ' already exists');
       }
+      this.tables[tag] = data;
     }
   };
-  return CachedCanvases;
+
+  return OpenTypeFileBuilder;
 })();
 
-function compileType3Glyph(imgData) {
-  var POINT_TO_PROCESS_LIMIT = 1000;
+// Problematic Unicode characters in the fonts that needs to be moved to avoid
+// issues when they are painted on the canvas, e.g. complex-script shaping or
+// control/whitespace characters. The ranges are listed in pairs: the first item
+// is a code of the first problematic code, the second one is the next
+// non-problematic code. The ranges must be in sorted order.
+var ProblematicCharRanges = new Int32Array([
+  // Control characters.
+  0x0000, 0x0020,
+  0x007F, 0x00A1,
+  0x00AD, 0x00AE,
+  // Chars that is used in complex-script shaping.
+  0x0600, 0x0780,
+  0x08A0, 0x10A0,
+  0x1780, 0x1800,
+  // General punctuation chars.
+  0x2000, 0x2010,
+  0x2011, 0x2012,
+  0x2028, 0x2030,
+  0x205F, 0x2070,
+  0x25CC, 0x25CD,
+  // Chars that is used in complex-script shaping.
+  0xAA60, 0xAA80,
+  // Specials Unicode block.
+  0xFFF0, 0x10000
+]);
 
-  var width = imgData.width, height = imgData.height;
-  var i, j, j0, width1 = width + 1;
-  var points = new Uint8Array(width1 * (height + 1));
-  var POINT_TYPES =
-      new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]);
+/**
+ * 'Font' is the class the outside world should use, it encapsulate all the font
+ * decoding logics whatever type it is (assuming the font type is supported).
+ *
+ * For example to read a Type1 font and to attach it to the document:
+ *   var type1Font = new Font("MyFontName", binaryFile, propertiesObject);
+ *   type1Font.bind();
+ */
+var Font = (function FontClosure() {
+  function Font(name, file, properties) {
+    var charCode, glyphName, unicode;
 
-  // decodes bit-packed mask data
-  var lineSize = (width + 7) & ~7, data0 = imgData.data;
-  var data = new Uint8Array(lineSize * height), pos = 0, ii;
-  for (i = 0, ii = data0.length; i < ii; i++) {
-    var mask = 128, elem = data0[i];
-    while (mask > 0) {
-      data[pos++] = (elem & mask) ? 0 : 255;
-      mask >>= 1;
-    }
-  }
+    this.name = name;
+    this.loadedName = properties.loadedName;
+    this.isType3Font = properties.isType3Font;
+    this.sizes = [];
+    this.missingFile = false;
 
-  // finding iteresting points: every point is located between mask pixels,
-  // so there will be points of the (width + 1)x(height + 1) grid. Every point
-  // will have flags assigned based on neighboring mask pixels:
-  //   4 | 8
-  //   --P--
-  //   2 | 1
-  // We are interested only in points with the flags:
-  //   - outside corners: 1, 2, 4, 8;
-  //   - inside corners: 7, 11, 13, 14;
-  //   - and, intersections: 5, 10.
-  var count = 0;
-  pos = 0;
-  if (data[pos] !== 0) {
-    points[0] = 1;
-    ++count;
-  }
-  for (j = 1; j < width; j++) {
-    if (data[pos] !== data[pos + 1]) {
-      points[j] = data[pos] ? 2 : 1;
-      ++count;
-    }
-    pos++;
-  }
-  if (data[pos] !== 0) {
-    points[j] = 2;
-    ++count;
-  }
-  for (i = 1; i < height; i++) {
-    pos = i * lineSize;
-    j0 = i * width1;
-    if (data[pos - lineSize] !== data[pos]) {
-      points[j0] = data[pos] ? 1 : 8;
-      ++count;
-    }
-    // 'sum' is the position of the current pixel configuration in the 'TYPES'
-    // array (in order 8-1-2-4, so we can use '>>2' to shift the column).
-    var sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
-    for (j = 1; j < width; j++) {
-      sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) +
-            (data[pos - lineSize + 1] ? 8 : 0);
-      if (POINT_TYPES[sum]) {
-        points[j0 + j] = POINT_TYPES[sum];
-        ++count;
-      }
-      pos++;
-    }
-    if (data[pos - lineSize] !== data[pos]) {
-      points[j0 + j] = data[pos] ? 2 : 4;
-      ++count;
-    }
+    this.glyphCache = Object.create(null);
 
-    if (count > POINT_TO_PROCESS_LIMIT) {
-      return null;
-    }
-  }
+    var names = name.split('+');
+    names = names.length > 1 ? names[1] : names[0];
+    names = names.split(/[-,_]/g)[0];
+    this.isSerifFont = !!(properties.flags & FontFlags.Serif);
+    this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
+    this.isMonospace = !!(properties.flags & FontFlags.FixedPitch);
 
-  pos = lineSize * (height - 1);
-  j0 = i * width1;
-  if (data[pos] !== 0) {
-    points[j0] = 8;
-    ++count;
-  }
-  for (j = 1; j < width; j++) {
-    if (data[pos] !== data[pos + 1]) {
-      points[j0 + j] = data[pos] ? 4 : 8;
-      ++count;
-    }
-    pos++;
-  }
-  if (data[pos] !== 0) {
-    points[j0 + j] = 4;
-    ++count;
-  }
-  if (count > POINT_TO_PROCESS_LIMIT) {
-    return null;
-  }
+    var type = properties.type;
+    var subtype = properties.subtype;
+    this.type = type;
 
-  // building outlines
-  var steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]);
-  var outlines = [];
-  for (i = 0; count && i <= height; i++) {
-    var p = i * width1;
-    var end = p + width;
-    while (p < end && !points[p]) {
-      p++;
-    }
-    if (p === end) {
-      continue;
-    }
-    var coords = [p % width1, i];
+    this.fallbackName = (this.isMonospace ? 'monospace' :
+                         (this.isSerifFont ? 'serif' : 'sans-serif'));
 
-    var type = points[p], p0 = p, pp;
-    do {
-      var step = steps[type];
-      do {
-        p += step;
-      } while (!points[p]);
+    this.differences = properties.differences;
+    this.widths = properties.widths;
+    this.defaultWidth = properties.defaultWidth;
+    this.composite = properties.composite;
+    this.wideChars = properties.wideChars;
+    this.cMap = properties.cMap;
+    this.ascent = properties.ascent / PDF_GLYPH_SPACE_UNITS;
+    this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS;
+    this.fontMatrix = properties.fontMatrix;
+    this.bbox = properties.bbox;
 
-      pp = points[p];
-      if (pp !== 5 && pp !== 10) {
-        // set new direction
-        type = pp;
-        // delete mark
-        points[p] = 0;
-      } else { // type is 5 or 10, ie, a crossing
-        // set new direction
-        type = pp & ((0x33 * type) >> 4);
-        // set new type for "future hit"
-        points[p] &= (type >> 2 | type << 2);
-      }
+    this.toUnicode = properties.toUnicode;
 
-      coords.push(p % width1);
-      coords.push((p / width1) | 0);
-      --count;
-    } while (p0 !== p);
-    outlines.push(coords);
-    --i;
-  }
+    this.toFontChar = [];
 
-  var drawOutline = function(c) {
-    c.save();
-    // the path shall be painted in [0..1]x[0..1] space
-    c.scale(1 / width, -1 / height);
-    c.translate(0, -height);
-    c.beginPath();
-    for (var i = 0, ii = outlines.length; i < ii; i++) {
-      var o = outlines[i];
-      c.moveTo(o[0], o[1]);
-      for (var j = 2, jj = o.length; j < jj; j += 2) {
-        c.lineTo(o[j], o[j+1]);
+    if (properties.type === 'Type3') {
+      for (charCode = 0; charCode < 256; charCode++) {
+        this.toFontChar[charCode] = (this.differences[charCode] ||
+                                     properties.defaultEncoding[charCode]);
       }
+      this.fontType = FontType.TYPE3;
+      return;
     }
-    c.fill();
-    c.beginPath();
-    c.restore();
-  };
-
-  return drawOutline;
-}
-
-var CanvasExtraState = (function CanvasExtraStateClosure() {
-  function CanvasExtraState(old) {
-    // Are soft masks and alpha values shapes or opacities?
-    this.alphaIsShape = false;
-    this.fontSize = 0;
-    this.fontSizeScale = 1;
-    this.textMatrix = IDENTITY_MATRIX;
-    this.textMatrixScale = 1;
-    this.fontMatrix = FONT_IDENTITY_MATRIX;
-    this.leading = 0;
-    // Current point (in user coordinates)
-    this.x = 0;
-    this.y = 0;
-    // Start of text line (in text coordinates)
-    this.lineX = 0;
-    this.lineY = 0;
-    // Character and word spacing
-    this.charSpacing = 0;
-    this.wordSpacing = 0;
-    this.textHScale = 1;
-    this.textRenderingMode = TextRenderingMode.FILL;
-    this.textRise = 0;
-    // Default fore and background colors
-    this.fillColor = '#000000';
-    this.strokeColor = '#000000';
-    this.patternFill = false;
-    // Note: fill alpha applies to all non-stroking operations
-    this.fillAlpha = 1;
-    this.strokeAlpha = 1;
-    this.lineWidth = 1;
-    this.activeSMask = null; // nonclonable field (see the save method below)
-
-    this.old = old;
-  }
 
-  CanvasExtraState.prototype = {
-    clone: function CanvasExtraState_clone() {
-      return Object.create(this);
-    },
-    setCurrentPoint: function CanvasExtraState_setCurrentPoint(x, y) {
-      this.x = x;
-      this.y = y;
+    this.cidEncoding = properties.cidEncoding;
+    this.vertical = properties.vertical;
+    if (this.vertical) {
+      this.vmetrics = properties.vmetrics;
+      this.defaultVMetrics = properties.defaultVMetrics;
     }
-  };
-  return CanvasExtraState;
-})();
+    var glyphsUnicodeMap;
+    if (!file || file.isEmpty) {
+      if (file) {
+        // Some bad PDF generators will include empty font files,
+        // attempting to recover by assuming that no file exists.
+        warn('Font file is empty in "' + name + '" (' + this.loadedName + ')');
+      }
 
-var CanvasGraphics = (function CanvasGraphicsClosure() {
-  // Defines the time the executeOperatorList is going to be executing
-  // before it stops and shedules a continue of execution.
-  var EXECUTION_TIME = 15;
-  // Defines the number of steps before checking the execution time
-  var EXECUTION_STEPS = 10;
+      this.missingFile = true;
+      // The file data is not specified. Trying to fix the font name
+      // to be used with the canvas.font.
+      var fontName = name.replace(/[,_]/g, '-');
+      var stdFontMap = getStdFontMap(), nonStdFontMap = getNonStdFontMap();
+      var isStandardFont = !!stdFontMap[fontName] ||
+        !!(nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]);
+      fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;
 
-  function CanvasGraphics(canvasCtx, commonObjs, objs, imageLayer) {
-    this.ctx = canvasCtx;
-    this.current = new CanvasExtraState();
-    this.stateStack = [];
-    this.pendingClip = null;
-    this.pendingEOFill = false;
-    this.res = null;
-    this.xobjs = null;
-    this.commonObjs = commonObjs;
-    this.objs = objs;
-    this.imageLayer = imageLayer;
-    this.groupStack = [];
-    this.processingType3 = null;
-    // Patterns are painted relative to the initial page/form transform, see pdf
-    // spec 8.7.2 NOTE 1.
-    this.baseTransform = null;
-    this.baseTransformStack = [];
-    this.groupLevel = 0;
-    this.smaskStack = [];
-    this.smaskCounter = 0;
-    this.tempSMask = null;
-    this.cachedCanvases = new CachedCanvases();
-    if (canvasCtx) {
-      // NOTE: if mozCurrentTransform is polyfilled, then the current state of
-      // the transformation must already be set in canvasCtx._transformMatrix.
-      addContextCurrentTransform(canvasCtx);
-    }
-    this.cachedGetSinglePixelWidth = null;
-  }
+      this.bold = (fontName.search(/bold/gi) !== -1);
+      this.italic = ((fontName.search(/oblique/gi) !== -1) ||
+                     (fontName.search(/italic/gi) !== -1));
 
-  function putBinaryImageData(ctx, imgData) {
-    if (typeof ImageData !== 'undefined' && imgData instanceof ImageData) {
-      ctx.putImageData(imgData, 0, 0);
-      return;
-    }
+      // Use 'name' instead of 'fontName' here because the original
+      // name ArialBlack for example will be replaced by Helvetica.
+      this.black = (name.search(/Black/g) !== -1);
 
-    // Put the image data to the canvas in chunks, rather than putting the
-    // whole image at once.  This saves JS memory, because the ImageData object
-    // is smaller. It also possibly saves C++ memory within the implementation
-    // of putImageData(). (E.g. in Firefox we make two short-lived copies of
-    // the data passed to putImageData()). |n| shouldn't be too small, however,
-    // because too many putImageData() calls will slow things down.
-    //
-    // Note: as written, if the last chunk is partial, the putImageData() call
-    // will (conceptually) put pixels past the bounds of the canvas.  But
-    // that's ok; any such pixels are ignored.
-
-    var height = imgData.height, width = imgData.width;
-    var partialChunkHeight = height % FULL_CHUNK_HEIGHT;
-    var fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
-    var totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
-
-    var chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
-    var srcPos = 0, destPos;
-    var src = imgData.data;
-    var dest = chunkImgData.data;
-    var i, j, thisChunkHeight, elemsInThisChunk;
-
-    // There are multiple forms in which the pixel data can be passed, and
-    // imgData.kind tells us which one this is.
-    if (imgData.kind === ImageKind.GRAYSCALE_1BPP) {
-      // Grayscale, 1 bit per pixel (i.e. black-and-white).
-      var srcLength = src.byteLength;
-      var dest32 = PDFJS.hasCanvasTypedArrays ? new Uint32Array(dest.buffer) :
-        new Uint32ArrayView(dest);
-      var dest32DataLength = dest32.length;
-      var fullSrcDiff = (width + 7) >> 3;
-      var white = 0xFFFFFFFF;
-      var black = (PDFJS.isLittleEndian || !PDFJS.hasCanvasTypedArrays) ?
-        0xFF000000 : 0x000000FF;
-      for (i = 0; i < totalChunks; i++) {
-        thisChunkHeight =
-          (i < fullChunks) ? FULL_CHUNK_HEIGHT : partialChunkHeight;
-        destPos = 0;
-        for (j = 0; j < thisChunkHeight; j++) {
-          var srcDiff = srcLength - srcPos;
-          var k = 0;
-          var kEnd = (srcDiff > fullSrcDiff) ? width : srcDiff * 8 - 7;
-          var kEndUnrolled = kEnd & ~7;
-          var mask = 0;
-          var srcByte = 0;
-          for (; k < kEndUnrolled; k += 8) {
-            srcByte = src[srcPos++];
-            dest32[destPos++] = (srcByte & 128) ? white : black;
-            dest32[destPos++] = (srcByte & 64) ? white : black;
-            dest32[destPos++] = (srcByte & 32) ? white : black;
-            dest32[destPos++] = (srcByte & 16) ? white : black;
-            dest32[destPos++] = (srcByte & 8) ? white : black;
-            dest32[destPos++] = (srcByte & 4) ? white : black;
-            dest32[destPos++] = (srcByte & 2) ? white : black;
-            dest32[destPos++] = (srcByte & 1) ? white : black;
-          }
-          for (; k < kEnd; k++) {
-             if (mask === 0) {
-               srcByte = src[srcPos++];
-               mask = 128;
-             }
-
-            dest32[destPos++] = (srcByte & mask) ? white : black;
-            mask >>= 1;
+      // if at least one width is present, remeasure all chars when exists
+      this.remeasure = Object.keys(this.widths).length > 0;
+      if (isStandardFont && type === 'CIDFontType2' &&
+          properties.cidEncoding.indexOf('Identity-') === 0) {
+        var GlyphMapForStandardFonts = getGlyphMapForStandardFonts();
+        // Standard fonts might be embedded as CID font without glyph mapping.
+        // Building one based on GlyphMapForStandardFonts.
+        var map = [];
+        for (charCode in GlyphMapForStandardFonts) {
+          map[+charCode] = GlyphMapForStandardFonts[charCode];
+        }
+        if (/ArialBlack/i.test(name)) {
+          var SupplementalGlyphMapForArialBlack =
+            getSupplementalGlyphMapForArialBlack();
+          for (charCode in SupplementalGlyphMapForArialBlack) {
+            map[+charCode] = SupplementalGlyphMapForArialBlack[charCode];
           }
         }
-        // We ran out of input. Make all remaining pixels transparent.
-        while (destPos < dest32DataLength) {
-          dest32[destPos++] = 0;
+        var isIdentityUnicode = this.toUnicode instanceof IdentityToUnicodeMap;
+        if (!isIdentityUnicode) {
+          this.toUnicode.forEach(function(charCode, unicodeCharCode) {
+            map[+charCode] = unicodeCharCode;
+          });
         }
-
-        ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
+        this.toFontChar = map;
+        this.toUnicode = new ToUnicodeMap(map);
+      } else if (/Symbol/i.test(fontName)) {
+        this.toFontChar = buildToFontChar(SymbolSetEncoding, getGlyphsUnicode(),
+                                          properties.differences);
+      } else if (/Dingbats/i.test(fontName)) {
+        if (/Wingdings/i.test(name)) {
+          warn('Non-embedded Wingdings font, falling back to ZapfDingbats.');
+        }
+        this.toFontChar = buildToFontChar(ZapfDingbatsEncoding,
+                                          getDingbatsGlyphsUnicode(),
+                                          properties.differences);
+      } else if (isStandardFont) {
+        this.toFontChar = buildToFontChar(properties.defaultEncoding,
+                                          getGlyphsUnicode(),
+                                          properties.differences);
+      } else {
+        glyphsUnicodeMap = getGlyphsUnicode();
+        this.toUnicode.forEach(function(charCode, unicodeCharCode) {
+          if (!this.composite) {
+            glyphName = (properties.differences[charCode] ||
+                         properties.defaultEncoding[charCode]);
+            unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
+            if (unicode !== -1) {
+              unicodeCharCode = unicode;
+            }
+          }
+          this.toFontChar[charCode] = unicodeCharCode;
+        }.bind(this));
       }
-    } else if (imgData.kind === ImageKind.RGBA_32BPP) {
-      // RGBA, 32-bits per pixel.
-
-      j = 0;
-      elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
-      for (i = 0; i < fullChunks; i++) {
-        dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
-        srcPos += elemsInThisChunk;
+      this.loadedName = fontName.split('-')[0];
+      this.loading = false;
+      this.fontType = getFontType(type, subtype);
+      return;
+    }
 
-        ctx.putImageData(chunkImgData, 0, j);
-        j += FULL_CHUNK_HEIGHT;
+    // Some fonts might use wrong font types for Type1C or CIDFontType0C
+    if (subtype === 'Type1C' && (type !== 'Type1' && type !== 'MMType1')) {
+      // Some TrueType fonts by mistake claim Type1C
+      if (isTrueTypeFile(file)) {
+        subtype = 'TrueType';
+      } else {
+        type = 'Type1';
       }
-      if (i < totalChunks) {
-        elemsInThisChunk = width * partialChunkHeight * 4;
-        dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
-        ctx.putImageData(chunkImgData, 0, j);
+    }
+    if (subtype === 'CIDFontType0C' && type !== 'CIDFontType0') {
+      type = 'CIDFontType0';
+    }
+    if (subtype === 'OpenType') {
+      type = 'OpenType';
+    }
+    // Some CIDFontType0C fonts by mistake claim CIDFontType0.
+    if (type === 'CIDFontType0') {
+      if (isType1File(file)) {
+        subtype = 'CIDFontType0';
+      } else if (isOpenTypeFile(file)) {
+        // Sometimes the type/subtype can be a complete lie (see issue6782.pdf).
+        type = subtype = 'OpenType';
+      } else {
+        subtype = 'CIDFontType0C';
       }
+    }
 
-    } else if (imgData.kind === ImageKind.RGB_24BPP) {
-      // RGB, 24-bits per pixel.
-      thisChunkHeight = FULL_CHUNK_HEIGHT;
-      elemsInThisChunk = width * thisChunkHeight;
-      for (i = 0; i < totalChunks; i++) {
-        if (i >= fullChunks) {
-          thisChunkHeight = partialChunkHeight;
-          elemsInThisChunk = width * thisChunkHeight;
-        }
+    var data;
+    switch (type) {
+      case 'MMType1':
+        info('MMType1 font (' + name + '), falling back to Type1.');
+        /* falls through */
+      case 'Type1':
+      case 'CIDFontType0':
+        this.mimetype = 'font/opentype';
 
-        destPos = 0;
-        for (j = elemsInThisChunk; j--;) {
-          dest[destPos++] = src[srcPos++];
-          dest[destPos++] = src[srcPos++];
-          dest[destPos++] = src[srcPos++];
-          dest[destPos++] = 255;
-        }
-        ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
-      }
-    } else {
-      error('bad image kind: ' + imgData.kind);
-    }
-  }
+        var cff = (subtype === 'Type1C' || subtype === 'CIDFontType0C') ?
+          new CFFFont(file, properties) : new Type1Font(name, file, properties);
 
-  function putBinaryImageMask(ctx, imgData) {
-    var height = imgData.height, width = imgData.width;
-    var partialChunkHeight = height % FULL_CHUNK_HEIGHT;
-    var fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
-    var totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
+        adjustWidths(properties);
 
-    var chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
-    var srcPos = 0;
-    var src = imgData.data;
-    var dest = chunkImgData.data;
+        // Wrap the CFF data inside an OTF font file
+        data = this.convert(name, cff, properties);
+        break;
 
-    for (var i = 0; i < totalChunks; i++) {
-      var thisChunkHeight =
-        (i < fullChunks) ? FULL_CHUNK_HEIGHT : partialChunkHeight;
+      case 'OpenType':
+      case 'TrueType':
+      case 'CIDFontType2':
+        this.mimetype = 'font/opentype';
 
-      // Expand the mask so it can be used by the canvas.  Any required
-      // inversion has already been handled.
-      var destPos = 3; // alpha component offset
-      for (var j = 0; j < thisChunkHeight; j++) {
-        var mask = 0;
-        for (var k = 0; k < width; k++) {
-          if (!mask) {
-            var elem = src[srcPos++];
-            mask = 128;
-          }
-          dest[destPos] = (elem & mask) ? 0 : 255;
-          destPos += 4;
-          mask >>= 1;
+        // Repair the TrueType file. It is can be damaged in the point of
+        // view of the sanitizer
+        data = this.checkAndRepair(name, file, properties);
+        if (this.isOpenType) {
+          adjustWidths(properties);
+
+          type = 'OpenType';
         }
-      }
-      ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
-    }
-  }
+        break;
 
-  function copyCtxState(sourceCtx, destCtx) {
-    var properties = ['strokeStyle', 'fillStyle', 'fillRule', 'globalAlpha',
-                      'lineWidth', 'lineCap', 'lineJoin', 'miterLimit',
-                      'globalCompositeOperation', 'font'];
-    for (var i = 0, ii = properties.length; i < ii; i++) {
-      var property = properties[i];
-      if (sourceCtx[property] !== undefined) {
-        destCtx[property] = sourceCtx[property];
-      }
-    }
-    if (sourceCtx.setLineDash !== undefined) {
-      destCtx.setLineDash(sourceCtx.getLineDash());
-      destCtx.lineDashOffset =  sourceCtx.lineDashOffset;
-    } else if (sourceCtx.mozDashOffset !== undefined) {
-      destCtx.mozDash = sourceCtx.mozDash;
-      destCtx.mozDashOffset = sourceCtx.mozDashOffset;
+      default:
+        error('Font ' + type + ' is not supported');
+        break;
     }
+
+    this.data = data;
+    this.fontType = getFontType(type, subtype);
+
+    // Transfer some properties again that could change during font conversion
+    this.fontMatrix = properties.fontMatrix;
+    this.widths = properties.widths;
+    this.defaultWidth = properties.defaultWidth;
+    this.encoding = properties.baseEncoding;
+    this.seacMap = properties.seacMap;
+
+    this.loading = true;
   }
 
-  function composeSMaskBackdrop(bytes, r0, g0, b0) {
-    var length = bytes.length;
-    for (var i = 3; i < length; i += 4) {
-      var alpha = bytes[i];
-      if (alpha === 0) {
-        bytes[i - 3] = r0;
-        bytes[i - 2] = g0;
-        bytes[i - 1] = b0;
-      } else if (alpha < 255) {
-        var alpha_ = 255 - alpha;
-        bytes[i - 3] = (bytes[i - 3] * alpha + r0 * alpha_) >> 8;
-        bytes[i - 2] = (bytes[i - 2] * alpha + g0 * alpha_) >> 8;
-        bytes[i - 1] = (bytes[i - 1] * alpha + b0 * alpha_) >> 8;
-      }
-    }
+  Font.getFontID = (function () {
+    var ID = 1;
+    return function Font_getFontID() {
+      return String(ID++);
+    };
+  })();
+
+  function int16(b0, b1) {
+    return (b0 << 8) + b1;
   }
 
-  function composeSMaskAlpha(maskData, layerData, transferMap) {
-    var length = maskData.length;
-    var scale = 1 / 255;
-    for (var i = 3; i < length; i += 4) {
-      var alpha = transferMap ? transferMap[maskData[i]] : maskData[i];
-      layerData[i] = (layerData[i] * alpha * scale) | 0;
-    }
+  function signedInt16(b0, b1) {
+    var value = (b0 << 8) + b1;
+    return value & (1 << 15) ? value - 0x10000 : value;
   }
 
-  function composeSMaskLuminosity(maskData, layerData, transferMap) {
-    var length = maskData.length;
-    for (var i = 3; i < length; i += 4) {
-      var y = (maskData[i - 3] * 77) +  // * 0.3 / 255 * 0x10000
-              (maskData[i - 2] * 152) + // * 0.59 ....
-              (maskData[i - 1] * 28);   // * 0.11 ....
-      layerData[i] = transferMap ?
-        (layerData[i] * transferMap[y >> 8]) >> 8 :
-        (layerData[i] * y) >> 16;
-    }
+  function int32(b0, b1, b2, b3) {
+    return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
   }
 
-  function genericComposeSMask(maskCtx, layerCtx, width, height,
-                               subtype, backdrop, transferMap) {
-    var hasBackdrop = !!backdrop;
-    var r0 = hasBackdrop ? backdrop[0] : 0;
-    var g0 = hasBackdrop ? backdrop[1] : 0;
-    var b0 = hasBackdrop ? backdrop[2] : 0;
+  function string16(value) {
+    return String.fromCharCode((value >> 8) & 0xff, value & 0xff);
+  }
 
-    var composeFn;
-    if (subtype === 'Luminosity') {
-      composeFn = composeSMaskLuminosity;
-    } else {
-      composeFn = composeSMaskAlpha;
-    }
+  function safeString16(value) {
+    // clamp value to the 16-bit int range
+    value = (value > 0x7FFF ? 0x7FFF : (value < -0x8000 ? -0x8000 : value));
+    return String.fromCharCode((value >> 8) & 0xff, value & 0xff);
+  }
 
-    // processing image in chunks to save memory
-    var PIXELS_TO_PROCESS = 1048576;
-    var chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width));
-    for (var row = 0; row < height; row += chunkSize) {
-      var chunkHeight = Math.min(chunkSize, height - row);
-      var maskData = maskCtx.getImageData(0, row, width, chunkHeight);
-      var layerData = layerCtx.getImageData(0, row, width, chunkHeight);
+  function isTrueTypeFile(file) {
+    var header = file.peekBytes(4);
+    return readUint32(header, 0) === 0x00010000;
+  }
 
-      if (hasBackdrop) {
-        composeSMaskBackdrop(maskData.data, r0, g0, b0);
-      }
-      composeFn(maskData.data, layerData.data, transferMap);
+  function isOpenTypeFile(file) {
+    var header = file.peekBytes(4);
+    return bytesToString(header) === 'OTTO';
+  }
 
-      maskCtx.putImageData(layerData, 0, row);
+  function isType1File(file) {
+    var header = file.peekBytes(2);
+    // All Type1 font programs must begin with the comment '%!' (0x25 + 0x21).
+    if (header[0] === 0x25 && header[1] === 0x21) {
+      return true;
+    }
+    // ... obviously some fonts violate that part of the specification,
+    // please refer to the comment in |Type1Font| below.
+    if (header[0] === 0x80 && header[1] === 0x01) { // pfb file header.
+      return true;
     }
+    return false;
   }
 
-  function composeSMask(ctx, smask, layerCtx) {
-    var mask = smask.canvas;
-    var maskCtx = smask.context;
-
-    ctx.setTransform(smask.scaleX, 0, 0, smask.scaleY,
-                     smask.offsetX, smask.offsetY);
-
-    var backdrop = smask.backdrop || null;
-    if (!smask.transferMap && WebGLUtils.isEnabled) {
-      var composed = WebGLUtils.composeSMask(layerCtx.canvas, mask,
-        {subtype: smask.subtype, backdrop: backdrop});
-      ctx.setTransform(1, 0, 0, 1, 0, 0);
-      ctx.drawImage(composed, smask.offsetX, smask.offsetY);
-      return;
+  function buildToFontChar(encoding, glyphsUnicodeMap, differences) {
+    var toFontChar = [], unicode;
+    for (var i = 0, ii = encoding.length; i < ii; i++) {
+      unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap);
+      if (unicode !== -1) {
+        toFontChar[i] = unicode;
+      }
     }
-    genericComposeSMask(maskCtx, layerCtx, mask.width, mask.height,
-                        smask.subtype, backdrop, smask.transferMap);
-    ctx.drawImage(mask, 0, 0);
+    for (var charCode in differences) {
+      unicode = getUnicodeForGlyph(differences[charCode], glyphsUnicodeMap);
+      if (unicode !== -1) {
+        toFontChar[+charCode] = unicode;
+      }
+    }
+    return toFontChar;
   }
 
-  var LINE_CAP_STYLES = ['butt', 'round', 'square'];
-  var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
-  var NORMAL_CLIP = {};
-  var EO_CLIP = {};
-
-  CanvasGraphics.prototype = {
-
-    beginDrawing: function CanvasGraphics_beginDrawing(transform, viewport,
-                                                       transparency) {
-      // For pdfs that use blend modes we have to clear the canvas else certain
-      // blend modes can look wrong since we'd be blending with a white
-      // backdrop. The problem with a transparent backdrop though is we then
-      // don't get sub pixel anti aliasing on text, creating temporary
-      // transparent canvas when we have blend modes.
-      var width = this.ctx.canvas.width;
-      var height = this.ctx.canvas.height;
-
-      this.ctx.save();
-      this.ctx.fillStyle = 'rgb(255, 255, 255)';
-      this.ctx.fillRect(0, 0, width, height);
-      this.ctx.restore();
-
-      if (transparency) {
-        var transparentCanvas = this.cachedCanvases.getCanvas(
-          'transparent', width, height, true);
-        this.compositeCtx = this.ctx;
-        this.transparentCanvas = transparentCanvas.canvas;
-        this.ctx = transparentCanvas.context;
-        this.ctx.save();
-        // The transform can be applied before rendering, transferring it to
-        // the new canvas.
-        this.ctx.transform.apply(this.ctx,
-                                 this.compositeCtx.mozCurrentTransform);
+  /**
+   * Helper function for |adjustMapping|.
+   * @return {boolean}
+   */
+  function isProblematicUnicodeLocation(code) {
+    // Using binary search to find a range start.
+    var i = 0, j = ProblematicCharRanges.length - 1;
+    while (i < j) {
+      var c = (i + j + 1) >> 1;
+      if (code < ProblematicCharRanges[c]) {
+        j = c - 1;
+      } else {
+        i = c;
       }
+    }
+    // Even index means code in problematic range.
+    return !(i & 1);
+  }
 
-      this.ctx.save();
-      if (transform) {
-        this.ctx.transform.apply(this.ctx, transform);
+  /**
+   * Rebuilds the char code to glyph ID map by trying to replace the char codes
+   * with their unicode value. It also moves char codes that are in known
+   * problematic locations.
+   * @return {Object} Two properties:
+   * 'toFontChar' - maps original char codes(the value that will be read
+   * from commands such as show text) to the char codes that will be used in the
+   * font that we build
+   * 'charCodeToGlyphId' - maps the new font char codes to glyph ids
+   */
+  function adjustMapping(charCodeToGlyphId, properties) {
+    var toUnicode = properties.toUnicode;
+    var isSymbolic = !!(properties.flags & FontFlags.Symbolic);
+    var isIdentityUnicode =
+      properties.toUnicode instanceof IdentityToUnicodeMap;
+    var newMap = Object.create(null);
+    var toFontChar = [];
+    var usedFontCharCodes = [];
+    var nextAvailableFontCharCode = PRIVATE_USE_OFFSET_START;
+    for (var originalCharCode in charCodeToGlyphId) {
+      originalCharCode |= 0;
+      var glyphId = charCodeToGlyphId[originalCharCode];
+      var fontCharCode = originalCharCode;
+      // First try to map the value to a unicode position if a non identity map
+      // was created.
+      if (!isIdentityUnicode && toUnicode.has(originalCharCode)) {
+        var unicode = toUnicode.get(fontCharCode);
+        // TODO: Try to map ligatures to the correct spot.
+        if (unicode.length === 1) {
+          fontCharCode = unicode.charCodeAt(0);
+        }
       }
-      this.ctx.transform.apply(this.ctx, viewport.transform);
+      // Try to move control characters, special characters and already mapped
+      // characters to the private use area since they will not be drawn by
+      // canvas if left in their current position. Also, move characters if the
+      // font was symbolic and there is only an identity unicode map since the
+      // characters probably aren't in the correct position (fixes an issue
+      // with firefox and thuluthfont).
+      if ((usedFontCharCodes[fontCharCode] !== undefined ||
+           isProblematicUnicodeLocation(fontCharCode) ||
+           (isSymbolic && isIdentityUnicode)) &&
+          nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END) { // Room left.
+        // Loop to try and find a free spot in the private use area.
+        do {
+          fontCharCode = nextAvailableFontCharCode++;
 
-      this.baseTransform = this.ctx.mozCurrentTransform.slice();
+          if (SKIP_PRIVATE_USE_RANGE_F000_TO_F01F && fontCharCode === 0xF000) {
+            fontCharCode = 0xF020;
+            nextAvailableFontCharCode = fontCharCode + 1;
+          }
 
-      if (this.imageLayer) {
-        this.imageLayer.beginLayout();
+        } while (usedFontCharCodes[fontCharCode] !== undefined &&
+                 nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END);
       }
-    },
 
-    executeOperatorList: function CanvasGraphics_executeOperatorList(
-                                    operatorList,
-                                    executionStartIdx, continueCallback,
-                                    stepper) {
-      var argsArray = operatorList.argsArray;
-      var fnArray = operatorList.fnArray;
-      var i = executionStartIdx || 0;
-      var argsArrayLen = argsArray.length;
+      newMap[fontCharCode] = glyphId;
+      toFontChar[originalCharCode] = fontCharCode;
+      usedFontCharCodes[fontCharCode] = true;
+    }
+    return {
+      toFontChar: toFontChar,
+      charCodeToGlyphId: newMap,
+      nextAvailableFontCharCode: nextAvailableFontCharCode
+    };
+  }
 
-      // Sometimes the OperatorList to execute is empty.
-      if (argsArrayLen === i) {
-        return i;
+  function getRanges(glyphs, numGlyphs) {
+    // Array.sort() sorts by characters, not numerically, so convert to an
+    // array of characters.
+    var codes = [];
+    for (var charCode in glyphs) {
+      // Remove an invalid glyph ID mappings to make OTS happy.
+      if (glyphs[charCode] >= numGlyphs) {
+        continue;
       }
+      codes.push({ fontCharCode: charCode | 0, glyphId: glyphs[charCode] });
+    }
+    codes.sort(function fontGetRangesSort(a, b) {
+      return a.fontCharCode - b.fontCharCode;
+    });
 
-      var chunkOperations = (argsArrayLen - i > EXECUTION_STEPS &&
-                             typeof continueCallback === 'function');
-      var endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0;
-      var steps = 0;
+    // Split the sorted codes into ranges.
+    var ranges = [];
+    var length = codes.length;
+    for (var n = 0; n < length; ) {
+      var start = codes[n].fontCharCode;
+      var codeIndices = [codes[n].glyphId];
+      ++n;
+      var end = start;
+      while (n < length && end + 1 === codes[n].fontCharCode) {
+        codeIndices.push(codes[n].glyphId);
+        ++end;
+        ++n;
+        if (end === 0xFFFF) {
+          break;
+        }
+      }
+      ranges.push([start, end, codeIndices]);
+    }
 
-      var commonObjs = this.commonObjs;
-      var objs = this.objs;
-      var fnId;
+    return ranges;
+  }
 
-      while (true) {
-        if (stepper !== undefined && i === stepper.nextBreakPoint) {
-          stepper.breakIt(i, continueCallback);
-          return i;
-        }
+  function createCmapTable(glyphs, numGlyphs) {
+    var ranges = getRanges(glyphs, numGlyphs);
+    var numTables = ranges[ranges.length - 1][1] > 0xFFFF ? 2 : 1;
+    var cmap = '\x00\x00' + // version
+               string16(numTables) +  // numTables
+               '\x00\x03' + // platformID
+               '\x00\x01' + // encodingID
+               string32(4 + numTables * 8); // start of the table record
 
-        fnId = fnArray[i];
+    var i, ii, j, jj;
+    for (i = ranges.length - 1; i >= 0; --i) {
+      if (ranges[i][0] <= 0xFFFF) { break; }
+    }
+    var bmpLength = i + 1;
 
-        if (fnId !== OPS.dependency) {
-          this[fnId].apply(this, argsArray[i]);
-        } else {
-          var deps = argsArray[i];
-          for (var n = 0, nn = deps.length; n < nn; n++) {
-            var depObjId = deps[n];
-            var common = depObjId[0] === 'g' && depObjId[1] === '_';
-            var objsPool = common ? commonObjs : objs;
+    if (ranges[i][0] < 0xFFFF && ranges[i][1] === 0xFFFF) {
+      ranges[i][1] = 0xFFFE;
+    }
+    var trailingRangesCount = ranges[i][1] < 0xFFFF ? 1 : 0;
+    var segCount = bmpLength + trailingRangesCount;
+    var searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2);
 
-            // If the promise isn't resolved yet, add the continueCallback
-            // to the promise and bail out.
-            if (!objsPool.isResolved(depObjId)) {
-              objsPool.get(depObjId, continueCallback);
-              return i;
-            }
-          }
+    // Fill up the 4 parallel arrays describing the segments.
+    var startCount = '';
+    var endCount = '';
+    var idDeltas = '';
+    var idRangeOffsets = '';
+    var glyphsIds = '';
+    var bias = 0;
+
+    var range, start, end, codes;
+    for (i = 0, ii = bmpLength; i < ii; i++) {
+      range = ranges[i];
+      start = range[0];
+      end = range[1];
+      startCount += string16(start);
+      endCount += string16(end);
+      codes = range[2];
+      var contiguous = true;
+      for (j = 1, jj = codes.length; j < jj; ++j) {
+        if (codes[j] !== codes[j - 1] + 1) {
+          contiguous = false;
+          break;
         }
+      }
+      if (!contiguous) {
+        var offset = (segCount - i) * 2 + bias * 2;
+        bias += (end - start + 1);
 
-        i++;
+        idDeltas += string16(0);
+        idRangeOffsets += string16(offset);
 
-        // If the entire operatorList was executed, stop as were done.
-        if (i === argsArrayLen) {
-          return i;
+        for (j = 0, jj = codes.length; j < jj; ++j) {
+          glyphsIds += string16(codes[j]);
         }
+      } else {
+        var startCode = codes[0];
 
-        // If the execution took longer then a certain amount of time and
-        // `continueCallback` is specified, interrupt the execution.
-        if (chunkOperations && ++steps > EXECUTION_STEPS) {
-          if (Date.now() > endTime) {
-            continueCallback();
-            return i;
+        idDeltas += string16((startCode - start) & 0xFFFF);
+        idRangeOffsets += string16(0);
+      }
+    }
+
+    if (trailingRangesCount > 0) {
+      endCount += '\xFF\xFF';
+      startCount += '\xFF\xFF';
+      idDeltas += '\x00\x01';
+      idRangeOffsets += '\x00\x00';
+    }
+
+    var format314 = '\x00\x00' + // language
+                    string16(2 * segCount) +
+                    string16(searchParams.range) +
+                    string16(searchParams.entry) +
+                    string16(searchParams.rangeShift) +
+                    endCount + '\x00\x00' + startCount +
+                    idDeltas + idRangeOffsets + glyphsIds;
+
+    var format31012 = '';
+    var header31012 = '';
+    if (numTables > 1) {
+      cmap += '\x00\x03' + // platformID
+              '\x00\x0A' + // encodingID
+              string32(4 + numTables * 8 +
+                       4 + format314.length); // start of the table record
+      format31012 = '';
+      for (i = 0, ii = ranges.length; i < ii; i++) {
+        range = ranges[i];
+        start = range[0];
+        codes = range[2];
+        var code = codes[0];
+        for (j = 1, jj = codes.length; j < jj; ++j) {
+          if (codes[j] !== codes[j - 1] + 1) {
+            end = range[0] + j - 1;
+            format31012 += string32(start) + // startCharCode
+                           string32(end) + // endCharCode
+                           string32(code); // startGlyphID
+            start = end + 1;
+            code = codes[j];
           }
-          steps = 0;
         }
-
-        // If the operatorList isn't executed completely yet OR the execution
-        // time was short enough, do another execution round.
+        format31012 += string32(start) + // startCharCode
+                       string32(range[1]) + // endCharCode
+                       string32(code); // startGlyphID
       }
-    },
+      header31012 = '\x00\x0C' + // format
+                    '\x00\x00' + // reserved
+                    string32(format31012.length + 16) + // length
+                    '\x00\x00\x00\x00' + // language
+                    string32(format31012.length / 12); // nGroups
+    }
 
-    endDrawing: function CanvasGraphics_endDrawing() {
-      this.ctx.restore();
+    return cmap + '\x00\x04' + // format
+                  string16(format314.length + 4) + // length
+                  format314 + header31012 + format31012;
+  }
 
-      if (this.transparentCanvas) {
-        this.ctx = this.compositeCtx;
-        this.ctx.save();
-        this.ctx.setTransform(1, 0, 0, 1, 0, 0); // Avoid apply transform twice
-        this.ctx.drawImage(this.transparentCanvas, 0, 0);
-        this.ctx.restore();
-        this.transparentCanvas = null;
-      }
+  function validateOS2Table(os2) {
+    var stream = new Stream(os2.data);
+    var version = stream.getUint16();
+    // TODO verify all OS/2 tables fields, but currently we validate only those
+    // that give us issues
+    stream.getBytes(60); // skipping type, misc sizes, panose, unicode ranges
+    var selection = stream.getUint16();
+    if (version < 4 && (selection & 0x0300)) {
+      return false;
+    }
+    var firstChar = stream.getUint16();
+    var lastChar = stream.getUint16();
+    if (firstChar > lastChar) {
+      return false;
+    }
+    stream.getBytes(6); // skipping sTypoAscender/Descender/LineGap
+    var usWinAscent = stream.getUint16();
+    if (usWinAscent === 0) { // makes font unreadable by windows
+      return false;
+    }
 
-      this.cachedCanvases.clear();
-      WebGLUtils.clear();
+    // OS/2 appears to be valid, resetting some fields
+    os2.data[8] = os2.data[9] = 0; // IE rejects fonts if fsType != 0
+    return true;
+  }
 
-      if (this.imageLayer) {
-        this.imageLayer.endLayout();
-      }
-    },
+  function createOS2Table(properties, charstrings, override) {
+    override = override || {
+      unitsPerEm: 0,
+      yMax: 0,
+      yMin: 0,
+      ascent: 0,
+      descent: 0
+    };
 
-    // Graphics state
-    setLineWidth: function CanvasGraphics_setLineWidth(width) {
-      this.current.lineWidth = width;
-      this.ctx.lineWidth = width;
-    },
-    setLineCap: function CanvasGraphics_setLineCap(style) {
-      this.ctx.lineCap = LINE_CAP_STYLES[style];
-    },
-    setLineJoin: function CanvasGraphics_setLineJoin(style) {
-      this.ctx.lineJoin = LINE_JOIN_STYLES[style];
-    },
-    setMiterLimit: function CanvasGraphics_setMiterLimit(limit) {
-      this.ctx.miterLimit = limit;
-    },
-    setDash: function CanvasGraphics_setDash(dashArray, dashPhase) {
-      var ctx = this.ctx;
-      if (ctx.setLineDash !== undefined) {
-        ctx.setLineDash(dashArray);
-        ctx.lineDashOffset = dashPhase;
-      } else {
-        ctx.mozDash = dashArray;
-        ctx.mozDashOffset = dashPhase;
-      }
-    },
-    setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) {
-      // Maybe if we one day fully support color spaces this will be important
-      // for now we can ignore.
-      // TODO set rendering intent?
-    },
-    setFlatness: function CanvasGraphics_setFlatness(flatness) {
-      // There's no way to control this with canvas, but we can safely ignore.
-      // TODO set flatness?
-    },
-    setGState: function CanvasGraphics_setGState(states) {
-      for (var i = 0, ii = states.length; i < ii; i++) {
-        var state = states[i];
-        var key = state[0];
-        var value = state[1];
+    var ulUnicodeRange1 = 0;
+    var ulUnicodeRange2 = 0;
+    var ulUnicodeRange3 = 0;
+    var ulUnicodeRange4 = 0;
 
-        switch (key) {
-          case 'LW':
-            this.setLineWidth(value);
-            break;
-          case 'LC':
-            this.setLineCap(value);
-            break;
-          case 'LJ':
-            this.setLineJoin(value);
-            break;
-          case 'ML':
-            this.setMiterLimit(value);
-            break;
-          case 'D':
-            this.setDash(value[0], value[1]);
-            break;
-          case 'RI':
-            this.setRenderingIntent(value);
-            break;
-          case 'FL':
-            this.setFlatness(value);
-            break;
-          case 'Font':
-            this.setFont(value[0], value[1]);
-            break;
-          case 'CA':
-            this.current.strokeAlpha = state[1];
-            break;
-          case 'ca':
-            this.current.fillAlpha = state[1];
-            this.ctx.globalAlpha = state[1];
-            break;
-          case 'BM':
-            if (value && value.name && (value.name !== 'Normal')) {
-              var mode = value.name.replace(/([A-Z])/g,
-                function(c) {
-                  return '-' + c.toLowerCase();
-                }
-              ).substring(1);
-              this.ctx.globalCompositeOperation = mode;
-              if (this.ctx.globalCompositeOperation !== mode) {
-                warn('globalCompositeOperation "' + mode +
-                     '" is not supported');
-              }
-            } else {
-              this.ctx.globalCompositeOperation = 'source-over';
-            }
-            break;
-          case 'SMask':
-            if (this.current.activeSMask) {
-              this.endSMaskGroup();
-            }
-            this.current.activeSMask = value ? this.tempSMask : null;
-            if (this.current.activeSMask) {
-              this.beginSMaskGroup();
-            }
-            this.tempSMask = null;
-            break;
+    var firstCharIndex = null;
+    var lastCharIndex = 0;
+
+    if (charstrings) {
+      for (var code in charstrings) {
+        code |= 0;
+        if (firstCharIndex > code || !firstCharIndex) {
+          firstCharIndex = code;
+        }
+        if (lastCharIndex < code) {
+          lastCharIndex = code;
         }
-      }
-    },
-    beginSMaskGroup: function CanvasGraphics_beginSMaskGroup() {
 
-      var activeSMask = this.current.activeSMask;
-      var drawnWidth = activeSMask.canvas.width;
-      var drawnHeight = activeSMask.canvas.height;
-      var cacheId = 'smaskGroupAt' + this.groupLevel;
-      var scratchCanvas = this.cachedCanvases.getCanvas(
-        cacheId, drawnWidth, drawnHeight, true);
+        var position = getUnicodeRangeFor(code);
+        if (position < 32) {
+          ulUnicodeRange1 |= 1 << position;
+        } else if (position < 64) {
+          ulUnicodeRange2 |= 1 << position - 32;
+        } else if (position < 96) {
+          ulUnicodeRange3 |= 1 << position - 64;
+        } else if (position < 123) {
+          ulUnicodeRange4 |= 1 << position - 96;
+        } else {
+          error('Unicode ranges Bits > 123 are reserved for internal usage');
+        }
+      }
+    } else {
+      // TODO
+      firstCharIndex = 0;
+      lastCharIndex = 255;
+    }
 
-      var currentCtx = this.ctx;
-      var currentTransform = currentCtx.mozCurrentTransform;
-      this.ctx.save();
+    var bbox = properties.bbox || [0, 0, 0, 0];
+    var unitsPerEm = (override.unitsPerEm ||
+                      1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0]);
 
-      var groupCtx = scratchCanvas.context;
-      groupCtx.scale(1 / activeSMask.scaleX, 1 / activeSMask.scaleY);
-      groupCtx.translate(-activeSMask.offsetX, -activeSMask.offsetY);
-      groupCtx.transform.apply(groupCtx, currentTransform);
+    // if the font units differ to the PDF glyph space units
+    // then scale up the values
+    var scale = (properties.ascentScaled ? 1.0 :
+                 unitsPerEm / PDF_GLYPH_SPACE_UNITS);
 
-      copyCtxState(currentCtx, groupCtx);
-      this.ctx = groupCtx;
-      this.setGState([
-        ['BM', 'Normal'],
-        ['ca', 1],
-        ['CA', 1]
-      ]);
-      this.groupStack.push(currentCtx);
-      this.groupLevel++;
-    },
-    endSMaskGroup: function CanvasGraphics_endSMaskGroup() {
-      var groupCtx = this.ctx;
-      this.groupLevel--;
-      this.ctx = this.groupStack.pop();
+    var typoAscent = (override.ascent ||
+                      Math.round(scale * (properties.ascent || bbox[3])));
+    var typoDescent = (override.descent ||
+                       Math.round(scale * (properties.descent || bbox[1])));
+    if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) {
+      typoDescent = -typoDescent; // fixing incorrect descent
+    }
+    var winAscent = override.yMax || typoAscent;
+    var winDescent = -override.yMin || -typoDescent;
 
-      composeSMask(this.ctx, this.current.activeSMask, groupCtx);
-      this.ctx.restore();
-      copyCtxState(groupCtx, this.ctx);
-    },
-    save: function CanvasGraphics_save() {
-      this.ctx.save();
-      var old = this.current;
-      this.stateStack.push(old);
-      this.current = old.clone();
-      this.current.activeSMask = null;
-    },
-    restore: function CanvasGraphics_restore() {
-      if (this.stateStack.length !== 0) {
-        if (this.current.activeSMask !== null) {
-          this.endSMaskGroup();
-        }
+    return '\x00\x03' + // version
+           '\x02\x24' + // xAvgCharWidth
+           '\x01\xF4' + // usWeightClass
+           '\x00\x05' + // usWidthClass
+           '\x00\x00' + // fstype (0 to let the font loads via font-face on IE)
+           '\x02\x8A' + // ySubscriptXSize
+           '\x02\xBB' + // ySubscriptYSize
+           '\x00\x00' + // ySubscriptXOffset
+           '\x00\x8C' + // ySubscriptYOffset
+           '\x02\x8A' + // ySuperScriptXSize
+           '\x02\xBB' + // ySuperScriptYSize
+           '\x00\x00' + // ySuperScriptXOffset
+           '\x01\xDF' + // ySuperScriptYOffset
+           '\x00\x31' + // yStrikeOutSize
+           '\x01\x02' + // yStrikeOutPosition
+           '\x00\x00' + // sFamilyClass
+           '\x00\x00\x06' +
+           String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) +
+           '\x00\x00\x00\x00\x00\x00' + // Panose
+           string32(ulUnicodeRange1) + // ulUnicodeRange1 (Bits 0-31)
+           string32(ulUnicodeRange2) + // ulUnicodeRange2 (Bits 32-63)
+           string32(ulUnicodeRange3) + // ulUnicodeRange3 (Bits 64-95)
+           string32(ulUnicodeRange4) + // ulUnicodeRange4 (Bits 96-127)
+           '\x2A\x32\x31\x2A' + // achVendID
+           string16(properties.italicAngle ? 1 : 0) + // fsSelection
+           string16(firstCharIndex ||
+                    properties.firstChar) + // usFirstCharIndex
+           string16(lastCharIndex || properties.lastChar) +  // usLastCharIndex
+           string16(typoAscent) + // sTypoAscender
+           string16(typoDescent) + // sTypoDescender
+           '\x00\x64' + // sTypoLineGap (7%-10% of the unitsPerEM value)
+           string16(winAscent) + // usWinAscent
+           string16(winDescent) + // usWinDescent
+           '\x00\x00\x00\x00' + // ulCodePageRange1 (Bits 0-31)
+           '\x00\x00\x00\x00' + // ulCodePageRange2 (Bits 32-63)
+           string16(properties.xHeight) + // sxHeight
+           string16(properties.capHeight) + // sCapHeight
+           string16(0) + // usDefaultChar
+           string16(firstCharIndex || properties.firstChar) + // usBreakChar
+           '\x00\x03';  // usMaxContext
+  }
 
-        this.current = this.stateStack.pop();
-        this.ctx.restore();
+  function createPostTable(properties) {
+    var angle = Math.floor(properties.italicAngle * (Math.pow(2, 16)));
+    return ('\x00\x03\x00\x00' + // Version number
+            string32(angle) + // italicAngle
+            '\x00\x00' + // underlinePosition
+            '\x00\x00' + // underlineThickness
+            string32(properties.fixedPitch) + // isFixedPitch
+            '\x00\x00\x00\x00' + // minMemType42
+            '\x00\x00\x00\x00' + // maxMemType42
+            '\x00\x00\x00\x00' + // minMemType1
+            '\x00\x00\x00\x00');  // maxMemType1
+  }
 
-        // Ensure that the clipping path is reset (fixes issue6413.pdf).
-        this.pendingClip = null;
+  function createNameTable(name, proto) {
+    if (!proto) {
+      proto = [[], []]; // no strings and unicode strings
+    }
 
-        this.cachedGetSinglePixelWidth = null;
-      }
-    },
-    transform: function CanvasGraphics_transform(a, b, c, d, e, f) {
-      this.ctx.transform(a, b, c, d, e, f);
+    var strings = [
+      proto[0][0] || 'Original licence',  // 0.Copyright
+      proto[0][1] || name,                // 1.Font family
+      proto[0][2] || 'Unknown',           // 2.Font subfamily (font weight)
+      proto[0][3] || 'uniqueID',          // 3.Unique ID
+      proto[0][4] || name,                // 4.Full font name
+      proto[0][5] || 'Version 0.11',      // 5.Version
+      proto[0][6] || '',                  // 6.Postscript name
+      proto[0][7] || 'Unknown',           // 7.Trademark
+      proto[0][8] || 'Unknown',           // 8.Manufacturer
+      proto[0][9] || 'Unknown'            // 9.Designer
+    ];
 
-      this.cachedGetSinglePixelWidth = null;
-    },
+    // Mac want 1-byte per character strings while Windows want
+    // 2-bytes per character, so duplicate the names table
+    var stringsUnicode = [];
+    var i, ii, j, jj, str;
+    for (i = 0, ii = strings.length; i < ii; i++) {
+      str = proto[1][i] || strings[i];
 
-    // Path
-    constructPath: function CanvasGraphics_constructPath(ops, args) {
-      var ctx = this.ctx;
-      var current = this.current;
-      var x = current.x, y = current.y;
-      for (var i = 0, j = 0, ii = ops.length; i < ii; i++) {
-        switch (ops[i] | 0) {
-          case OPS.rectangle:
-            x = args[j++];
-            y = args[j++];
-            var width = args[j++];
-            var height = args[j++];
-            if (width === 0) {
-              width = this.getSinglePixelWidth();
-            }
-            if (height === 0) {
-              height = this.getSinglePixelWidth();
+      var strBufUnicode = [];
+      for (j = 0, jj = str.length; j < jj; j++) {
+        strBufUnicode.push(string16(str.charCodeAt(j)));
+      }
+      stringsUnicode.push(strBufUnicode.join(''));
+    }
+
+    var names = [strings, stringsUnicode];
+    var platforms = ['\x00\x01', '\x00\x03'];
+    var encodings = ['\x00\x00', '\x00\x01'];
+    var languages = ['\x00\x00', '\x04\x09'];
+
+    var namesRecordCount = strings.length * platforms.length;
+    var nameTable =
+      '\x00\x00' +                           // format
+      string16(namesRecordCount) +           // Number of names Record
+      string16(namesRecordCount * 12 + 6);   // Storage
+
+    // Build the name records field
+    var strOffset = 0;
+    for (i = 0, ii = platforms.length; i < ii; i++) {
+      var strs = names[i];
+      for (j = 0, jj = strs.length; j < jj; j++) {
+        str = strs[j];
+        var nameRecord =
+          platforms[i] + // platform ID
+          encodings[i] + // encoding ID
+          languages[i] + // language ID
+          string16(j) + // name ID
+          string16(str.length) +
+          string16(strOffset);
+        nameTable += nameRecord;
+        strOffset += str.length;
+      }
+    }
+
+    nameTable += strings.join('') + stringsUnicode.join('');
+    return nameTable;
+  }
+
+  Font.prototype = {
+    name: null,
+    font: null,
+    mimetype: null,
+    encoding: null,
+    get renderer() {
+      var renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED);
+      return shadow(this, 'renderer', renderer);
+    },
+
+    exportData: function Font_exportData() {
+      // TODO remove enumerating of the properties, e.g. hardcode exact names.
+      var data = {};
+      for (var i in this) {
+        if (this.hasOwnProperty(i)) {
+          data[i] = this[i];
+        }
+      }
+      return data;
+    },
+
+    checkAndRepair: function Font_checkAndRepair(name, font, properties) {
+      function readTableEntry(file) {
+        var tag = bytesToString(file.getBytes(4));
+
+        var checksum = file.getInt32() >>> 0;
+        var offset = file.getInt32() >>> 0;
+        var length = file.getInt32() >>> 0;
+
+        // Read the table associated data
+        var previousPosition = file.pos;
+        file.pos = file.start ? file.start : 0;
+        file.skip(offset);
+        var data = file.getBytes(length);
+        file.pos = previousPosition;
+
+        if (tag === 'head') {
+          // clearing checksum adjustment
+          data[8] = data[9] = data[10] = data[11] = 0;
+          data[17] |= 0x20; //Set font optimized for cleartype flag
+        }
+
+        return {
+          tag: tag,
+          checksum: checksum,
+          length: length,
+          offset: offset,
+          data: data
+        };
+      }
+
+      function readOpenTypeHeader(ttf) {
+        return {
+          version: bytesToString(ttf.getBytes(4)),
+          numTables: ttf.getUint16(),
+          searchRange: ttf.getUint16(),
+          entrySelector: ttf.getUint16(),
+          rangeShift: ttf.getUint16()
+        };
+      }
+
+      /**
+       * Read the appropriate subtable from the cmap according to 9.6.6.4 from
+       * PDF spec
+       */
+      function readCmapTable(cmap, font, isSymbolicFont, hasEncoding) {
+        if (!cmap) {
+          warn('No cmap table available.');
+          return {
+            platformId: -1,
+            encodingId: -1,
+            mappings: [],
+            hasShortCmap: false
+          };
+        }
+        var segment;
+        var start = (font.start ? font.start : 0) + cmap.offset;
+        font.pos = start;
+
+        var version = font.getUint16();
+        var numTables = font.getUint16();
+
+        var potentialTable;
+        var canBreak = false;
+        // There's an order of preference in terms of which cmap subtable to
+        // use:
+        // - non-symbolic fonts the preference is a 3,1 table then a 1,0 table
+        // - symbolic fonts the preference is a 3,0 table then a 1,0 table
+        // The following takes advantage of the fact that the tables are sorted
+        // to work.
+        for (var i = 0; i < numTables; i++) {
+          var platformId = font.getUint16();
+          var encodingId = font.getUint16();
+          var offset = font.getInt32() >>> 0;
+          var useTable = false;
+
+          if (platformId === 0 && encodingId === 0) {
+            useTable = true;
+            // Continue the loop since there still may be a higher priority
+            // table.
+          } else if (platformId === 1 && encodingId === 0) {
+            useTable = true;
+            // Continue the loop since there still may be a higher priority
+            // table.
+          } else if (platformId === 3 && encodingId === 1 &&
+                     ((!isSymbolicFont && hasEncoding) || !potentialTable)) {
+            useTable = true;
+            if (!isSymbolicFont) {
+              canBreak = true;
             }
-            var xw = x + width;
-            var yh = y + height;
-            this.ctx.moveTo(x, y);
-            this.ctx.lineTo(xw, y);
-            this.ctx.lineTo(xw, yh);
-            this.ctx.lineTo(x, yh);
-            this.ctx.lineTo(x, y);
-            this.ctx.closePath();
-            break;
-          case OPS.moveTo:
-            x = args[j++];
-            y = args[j++];
-            ctx.moveTo(x, y);
-            break;
-          case OPS.lineTo:
-            x = args[j++];
-            y = args[j++];
-            ctx.lineTo(x, y);
+          } else if (isSymbolicFont && platformId === 3 && encodingId === 0) {
+            useTable = true;
+            canBreak = true;
+          }
+
+          if (useTable) {
+            potentialTable = {
+              platformId: platformId,
+              encodingId: encodingId,
+              offset: offset
+            };
+          }
+          if (canBreak) {
             break;
-          case OPS.curveTo:
-            x = args[j + 4];
-            y = args[j + 5];
-            ctx.bezierCurveTo(args[j], args[j + 1], args[j + 2], args[j + 3],
-                              x, y);
-            j += 6;
+          }
+        }
+
+        if (potentialTable) {
+          font.pos = start + potentialTable.offset;
+        }
+        if (!potentialTable || font.peekByte() === -1) {
+          warn('Could not find a preferred cmap table.');
+          return {
+            platformId: -1,
+            encodingId: -1,
+            mappings: [],
+            hasShortCmap: false
+          };
+        }
+
+        var format = font.getUint16();
+        var length = font.getUint16();
+        var language = font.getUint16();
+
+        var hasShortCmap = false;
+        var mappings = [];
+        var j, glyphId;
+
+        // TODO(mack): refactor this cmap subtable reading logic out
+        if (format === 0) {
+          for (j = 0; j < 256; j++) {
+            var index = font.getByte();
+            if (!index) {
+              continue;
+            }
+            mappings.push({
+              charCode: j,
+              glyphId: index
+            });
+          }
+          hasShortCmap = true;
+        } else if (format === 4) {
+          // re-creating the table in format 4 since the encoding
+          // might be changed
+          var segCount = (font.getUint16() >> 1);
+          font.getBytes(6); // skipping range fields
+          var segIndex, segments = [];
+          for (segIndex = 0; segIndex < segCount; segIndex++) {
+            segments.push({ end: font.getUint16() });
+          }
+          font.getUint16();
+          for (segIndex = 0; segIndex < segCount; segIndex++) {
+            segments[segIndex].start = font.getUint16();
+          }
+
+          for (segIndex = 0; segIndex < segCount; segIndex++) {
+            segments[segIndex].delta = font.getUint16();
+          }
+
+          var offsetsCount = 0;
+          for (segIndex = 0; segIndex < segCount; segIndex++) {
+            segment = segments[segIndex];
+            var rangeOffset = font.getUint16();
+            if (!rangeOffset) {
+              segment.offsetIndex = -1;
+              continue;
+            }
+
+            var offsetIndex = (rangeOffset >> 1) - (segCount - segIndex);
+            segment.offsetIndex = offsetIndex;
+            offsetsCount = Math.max(offsetsCount, offsetIndex +
+                                    segment.end - segment.start + 1);
+          }
+
+          var offsets = [];
+          for (j = 0; j < offsetsCount; j++) {
+            offsets.push(font.getUint16());
+          }
+
+          for (segIndex = 0; segIndex < segCount; segIndex++) {
+            segment = segments[segIndex];
+            start = segment.start;
+            var end = segment.end;
+            var delta = segment.delta;
+            offsetIndex = segment.offsetIndex;
+
+            for (j = start; j <= end; j++) {
+              if (j === 0xFFFF) {
+                continue;
+              }
+
+              glyphId = (offsetIndex < 0 ?
+                         j : offsets[offsetIndex + j - start]);
+              glyphId = (glyphId + delta) & 0xFFFF;
+              if (glyphId === 0) {
+                continue;
+              }
+              mappings.push({
+                charCode: j,
+                glyphId: glyphId
+              });
+            }
+          }
+        } else if (format === 6) {
+          // Format 6 is a 2-bytes dense mapping, which means the font data
+          // lives glue together even if they are pretty far in the unicode
+          // table. (This looks weird, so I can have missed something), this
+          // works on Linux but seems to fails on Mac so let's rewrite the
+          // cmap table to a 3-1-4 style
+          var firstCode = font.getUint16();
+          var entryCount = font.getUint16();
+
+          for (j = 0; j < entryCount; j++) {
+            glyphId = font.getUint16();
+            var charCode = firstCode + j;
+
+            mappings.push({
+              charCode: charCode,
+              glyphId: glyphId
+            });
+          }
+        } else {
+          warn('cmap table has unsupported format: ' + format);
+          return {
+            platformId: -1,
+            encodingId: -1,
+            mappings: [],
+            hasShortCmap: false
+          };
+        }
+
+        // removing duplicate entries
+        mappings.sort(function (a, b) {
+          return a.charCode - b.charCode;
+        });
+        for (i = 1; i < mappings.length; i++) {
+          if (mappings[i - 1].charCode === mappings[i].charCode) {
+            mappings.splice(i, 1);
+            i--;
+          }
+        }
+
+        return {
+          platformId: potentialTable.platformId,
+          encodingId: potentialTable.encodingId,
+          mappings: mappings,
+          hasShortCmap: hasShortCmap
+        };
+      }
+
+      function sanitizeMetrics(font, header, metrics, numGlyphs) {
+        if (!header) {
+          if (metrics) {
+            metrics.data = null;
+          }
+          return;
+        }
+
+        font.pos = (font.start ? font.start : 0) + header.offset;
+        font.pos += header.length - 2;
+        var numOfMetrics = font.getUint16();
+
+        if (numOfMetrics > numGlyphs) {
+          info('The numOfMetrics (' + numOfMetrics + ') should not be ' +
+               'greater than the numGlyphs (' + numGlyphs + ')');
+          // Reduce numOfMetrics if it is greater than numGlyphs
+          numOfMetrics = numGlyphs;
+          header.data[34] = (numOfMetrics & 0xff00) >> 8;
+          header.data[35] = numOfMetrics & 0x00ff;
+        }
+
+        var numOfSidebearings = numGlyphs - numOfMetrics;
+        var numMissing = numOfSidebearings -
+          ((metrics.length - numOfMetrics * 4) >> 1);
+
+        if (numMissing > 0) {
+          // For each missing glyph, we set both the width and lsb to 0 (zero).
+          // Since we need to add two properties for each glyph, this explains
+          // the use of |numMissing * 2| when initializing the typed array.
+          var entries = new Uint8Array(metrics.length + numMissing * 2);
+          entries.set(metrics.data);
+          metrics.data = entries;
+        }
+      }
+
+      function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart,
+                             hintsValid) {
+        if (sourceEnd - sourceStart <= 12) {
+          // glyph with data less than 12 is invalid one
+          return 0;
+        }
+        var glyf = source.subarray(sourceStart, sourceEnd);
+        var contoursCount = (glyf[0] << 8) | glyf[1];
+        if (contoursCount & 0x8000) {
+          // complex glyph, writing as is
+          dest.set(glyf, destStart);
+          return glyf.length;
+        }
+
+        var i, j = 10, flagsCount = 0;
+        for (i = 0; i < contoursCount; i++) {
+          var endPoint = (glyf[j] << 8) | glyf[j + 1];
+          flagsCount = endPoint + 1;
+          j += 2;
+        }
+        // skipping instructions
+        var instructionsStart = j;
+        var instructionsLength = (glyf[j] << 8) | glyf[j + 1];
+        j += 2 + instructionsLength;
+        var instructionsEnd = j;
+        // validating flags
+        var coordinatesLength = 0;
+        for (i = 0; i < flagsCount; i++) {
+          var flag = glyf[j++];
+          if (flag & 0xC0) {
+            // reserved flags must be zero, cleaning up
+            glyf[j - 1] = flag & 0x3F;
+          }
+          var xyLength = ((flag & 2) ? 1 : (flag & 16) ? 0 : 2) +
+                         ((flag & 4) ? 1 : (flag & 32) ? 0 : 2);
+          coordinatesLength += xyLength;
+          if (flag & 8) {
+            var repeat = glyf[j++];
+            i += repeat;
+            coordinatesLength += repeat * xyLength;
+          }
+        }
+        // glyph without coordinates will be rejected
+        if (coordinatesLength === 0) {
+          return 0;
+        }
+        var glyphDataLength = j + coordinatesLength;
+        if (glyphDataLength > glyf.length) {
+          // not enough data for coordinates
+          return 0;
+        }
+        if (!hintsValid && instructionsLength > 0) {
+          dest.set(glyf.subarray(0, instructionsStart), destStart);
+          dest.set([0, 0], destStart + instructionsStart);
+          dest.set(glyf.subarray(instructionsEnd, glyphDataLength),
+                   destStart + instructionsStart + 2);
+          glyphDataLength -= instructionsLength;
+          if (glyf.length - glyphDataLength > 3) {
+            glyphDataLength = (glyphDataLength + 3) & ~3;
+          }
+          return glyphDataLength;
+        }
+        if (glyf.length - glyphDataLength > 3) {
+          // truncating and aligning to 4 bytes the long glyph data
+          glyphDataLength = (glyphDataLength + 3) & ~3;
+          dest.set(glyf.subarray(0, glyphDataLength), destStart);
+          return glyphDataLength;
+        }
+        // glyph data is fine
+        dest.set(glyf, destStart);
+        return glyf.length;
+      }
+
+      function sanitizeHead(head, numGlyphs, locaLength) {
+        var data = head.data;
+
+        // Validate version:
+        // Should always be 0x00010000
+        var version = int32(data[0], data[1], data[2], data[3]);
+        if (version >> 16 !== 1) {
+          info('Attempting to fix invalid version in head table: ' + version);
+          data[0] = 0;
+          data[1] = 1;
+          data[2] = 0;
+          data[3] = 0;
+        }
+
+        var indexToLocFormat = int16(data[50], data[51]);
+        if (indexToLocFormat < 0 || indexToLocFormat > 1) {
+          info('Attempting to fix invalid indexToLocFormat in head table: ' +
+               indexToLocFormat);
+
+          // The value of indexToLocFormat should be 0 if the loca table
+          // consists of short offsets, and should be 1 if the loca table
+          // consists of long offsets.
+          //
+          // The number of entries in the loca table should be numGlyphs + 1.
+          //
+          // Using this information, we can work backwards to deduce if the
+          // size of each offset in the loca table, and thus figure out the
+          // appropriate value for indexToLocFormat.
+
+          var numGlyphsPlusOne = numGlyphs + 1;
+          if (locaLength === numGlyphsPlusOne << 1) {
+            // 0x0000 indicates the loca table consists of short offsets
+            data[50] = 0;
+            data[51] = 0;
+          } else if (locaLength === numGlyphsPlusOne << 2) {
+            // 0x0001 indicates the loca table consists of long offsets
+            data[50] = 0;
+            data[51] = 1;
+          } else {
+            warn('Could not fix indexToLocFormat: ' + indexToLocFormat);
+          }
+        }
+      }
+
+      function sanitizeGlyphLocations(loca, glyf, numGlyphs,
+                                      isGlyphLocationsLong, hintsValid,
+                                      dupFirstEntry) {
+        var itemSize, itemDecode, itemEncode;
+        if (isGlyphLocationsLong) {
+          itemSize = 4;
+          itemDecode = function fontItemDecodeLong(data, offset) {
+            return (data[offset] << 24) | (data[offset + 1] << 16) |
+                   (data[offset + 2] << 8) | data[offset + 3];
+          };
+          itemEncode = function fontItemEncodeLong(data, offset, value) {
+            data[offset] = (value >>> 24) & 0xFF;
+            data[offset + 1] = (value >> 16) & 0xFF;
+            data[offset + 2] = (value >> 8) & 0xFF;
+            data[offset + 3] = value & 0xFF;
+          };
+        } else {
+          itemSize = 2;
+          itemDecode = function fontItemDecode(data, offset) {
+            return (data[offset] << 9) | (data[offset + 1] << 1);
+          };
+          itemEncode = function fontItemEncode(data, offset, value) {
+            data[offset] = (value >> 9) & 0xFF;
+            data[offset + 1] = (value >> 1) & 0xFF;
+          };
+        }
+        var locaData = loca.data;
+        var locaDataSize = itemSize * (1 + numGlyphs);
+        // is loca.data too short or long?
+        if (locaData.length !== locaDataSize) {
+          locaData = new Uint8Array(locaDataSize);
+          locaData.set(loca.data.subarray(0, locaDataSize));
+          loca.data = locaData;
+        }
+        // removing the invalid glyphs
+        var oldGlyfData = glyf.data;
+        var oldGlyfDataLength = oldGlyfData.length;
+        var newGlyfData = new Uint8Array(oldGlyfDataLength);
+        var startOffset = itemDecode(locaData, 0);
+        var writeOffset = 0;
+        var missingGlyphData = Object.create(null);
+        itemEncode(locaData, 0, writeOffset);
+        var i, j;
+        for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
+          var endOffset = itemDecode(locaData, j);
+          if (endOffset > oldGlyfDataLength &&
+              ((oldGlyfDataLength + 3) & ~3) === endOffset) {
+            // Aspose breaks fonts by aligning the glyphs to the qword, but not
+            // the glyf table size, which makes last glyph out of range.
+            endOffset = oldGlyfDataLength;
+          }
+          if (endOffset > oldGlyfDataLength) {
+            // glyph end offset points outside glyf data, rejecting the glyph
+            itemEncode(locaData, j, writeOffset);
+            startOffset = endOffset;
+            continue;
+          }
+
+          if (startOffset === endOffset) {
+            missingGlyphData[i] = true;
+          }
+
+          var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset,
+                                        newGlyfData, writeOffset, hintsValid);
+          writeOffset += newLength;
+          itemEncode(locaData, j, writeOffset);
+          startOffset = endOffset;
+        }
+
+        if (writeOffset === 0) {
+          // glyf table cannot be empty -- redoing the glyf and loca tables
+          // to have single glyph with one point
+          var simpleGlyph = new Uint8Array(
+            [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]);
+          for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
+            itemEncode(locaData, j, simpleGlyph.length);
+          }
+          glyf.data = simpleGlyph;
+          return missingGlyphData;
+        }
+
+        if (dupFirstEntry) {
+          var firstEntryLength = itemDecode(locaData, itemSize);
+          if (newGlyfData.length > firstEntryLength + writeOffset) {
+            glyf.data = newGlyfData.subarray(0, firstEntryLength + writeOffset);
+          } else {
+            glyf.data = new Uint8Array(firstEntryLength + writeOffset);
+            glyf.data.set(newGlyfData.subarray(0, writeOffset));
+          }
+          glyf.data.set(newGlyfData.subarray(0, firstEntryLength), writeOffset);
+          itemEncode(loca.data, locaData.length - itemSize,
+                     writeOffset + firstEntryLength);
+        } else {
+          glyf.data = newGlyfData.subarray(0, writeOffset);
+        }
+        return missingGlyphData;
+      }
+
+      function readPostScriptTable(post, properties, maxpNumGlyphs) {
+        var start = (font.start ? font.start : 0) + post.offset;
+        font.pos = start;
+
+        var length = post.length, end = start + length;
+        var version = font.getInt32();
+        // skip rest to the tables
+        font.getBytes(28);
+
+        var glyphNames;
+        var valid = true;
+        var i;
+
+        switch (version) {
+          case 0x00010000:
+            glyphNames = MacStandardGlyphOrdering;
             break;
-          case OPS.curveTo2:
-            ctx.bezierCurveTo(x, y, args[j], args[j + 1],
-                              args[j + 2], args[j + 3]);
-            x = args[j + 2];
-            y = args[j + 3];
-            j += 4;
+          case 0x00020000:
+            var numGlyphs = font.getUint16();
+            if (numGlyphs !== maxpNumGlyphs) {
+              valid = false;
+              break;
+            }
+            var glyphNameIndexes = [];
+            for (i = 0; i < numGlyphs; ++i) {
+              var index = font.getUint16();
+              if (index >= 32768) {
+                valid = false;
+                break;
+              }
+              glyphNameIndexes.push(index);
+            }
+            if (!valid) {
+              break;
+            }
+            var customNames = [];
+            var strBuf = [];
+            while (font.pos < end) {
+              var stringLength = font.getByte();
+              strBuf.length = stringLength;
+              for (i = 0; i < stringLength; ++i) {
+                strBuf[i] = String.fromCharCode(font.getByte());
+              }
+              customNames.push(strBuf.join(''));
+            }
+            glyphNames = [];
+            for (i = 0; i < numGlyphs; ++i) {
+              var j = glyphNameIndexes[i];
+              if (j < 258) {
+                glyphNames.push(MacStandardGlyphOrdering[j]);
+                continue;
+              }
+              glyphNames.push(customNames[j - 258]);
+            }
             break;
-          case OPS.curveTo3:
-            x = args[j + 2];
-            y = args[j + 3];
-            ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y);
-            j += 4;
+          case 0x00030000:
             break;
-          case OPS.closePath:
-            ctx.closePath();
+          default:
+            warn('Unknown/unsupported post table version ' + version);
+            valid = false;
+            if (properties.defaultEncoding) {
+              glyphNames = properties.defaultEncoding;
+            }
             break;
         }
+        properties.glyphNames = glyphNames;
+        return valid;
       }
-      current.setCurrentPoint(x, y);
-    },
-    closePath: function CanvasGraphics_closePath() {
-      this.ctx.closePath();
-    },
-    stroke: function CanvasGraphics_stroke(consumePath) {
-      consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
-      var ctx = this.ctx;
-      var strokeColor = this.current.strokeColor;
-      // Prevent drawing too thin lines by enforcing a minimum line width.
-      ctx.lineWidth = Math.max(this.getSinglePixelWidth() * MIN_WIDTH_FACTOR,
-                               this.current.lineWidth);
-      // For stroke we want to temporarily change the global alpha to the
-      // stroking alpha.
-      ctx.globalAlpha = this.current.strokeAlpha;
-      if (strokeColor && strokeColor.hasOwnProperty('type') &&
-          strokeColor.type === 'Pattern') {
-        // for patterns, we transform to pattern space, calculate
-        // the pattern, call stroke, and restore to user space
-        ctx.save();
-        ctx.strokeStyle = strokeColor.getPattern(ctx, this);
-        ctx.stroke();
-        ctx.restore();
-      } else {
-        ctx.stroke();
+
+      function readNameTable(nameTable) {
+        var start = (font.start ? font.start : 0) + nameTable.offset;
+        font.pos = start;
+
+        var names = [[], []];
+        var length = nameTable.length, end = start + length;
+        var format = font.getUint16();
+        var FORMAT_0_HEADER_LENGTH = 6;
+        if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) {
+          // unsupported name table format or table "too" small
+          return names;
+        }
+        var numRecords = font.getUint16();
+        var stringsStart = font.getUint16();
+        var records = [];
+        var NAME_RECORD_LENGTH = 12;
+        var i, ii;
+
+        for (i = 0; i < numRecords &&
+                        font.pos + NAME_RECORD_LENGTH <= end; i++) {
+          var r = {
+            platform: font.getUint16(),
+            encoding: font.getUint16(),
+            language: font.getUint16(),
+            name: font.getUint16(),
+            length: font.getUint16(),
+            offset: font.getUint16()
+          };
+          // using only Macintosh and Windows platform/encoding names
+          if ((r.platform === 1 && r.encoding === 0 && r.language === 0) ||
+              (r.platform === 3 && r.encoding === 1 && r.language === 0x409)) {
+            records.push(r);
+          }
+        }
+        for (i = 0, ii = records.length; i < ii; i++) {
+          var record = records[i];
+          if (record.length <= 0) {
+            continue; // Nothing to process, ignoring.
+          }
+          var pos = start + stringsStart + record.offset;
+          if (pos + record.length > end) {
+            continue; // outside of name table, ignoring
+          }
+          font.pos = pos;
+          var nameIndex = record.name;
+          if (record.encoding) {
+            // unicode
+            var str = '';
+            for (var j = 0, jj = record.length; j < jj; j += 2) {
+              str += String.fromCharCode(font.getUint16());
+            }
+            names[1][nameIndex] = str;
+          } else {
+            names[0][nameIndex] = bytesToString(font.getBytes(record.length));
+          }
+        }
+        return names;
       }
-      if (consumePath) {
-        this.consumePath();
+
+      var TTOpsStackDeltas = [
+        0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5,
+        -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1,
+        1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1,
+        0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -999, 0, -2, -2,
+        0, 0, -2, 0, -2, 0, 0, 0, -2, -1, -1, 1, 1, 0, 0, -1,
+        -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, 0, -999, -1, -1,
+        -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        -2, -999, -999, -999, -999, -999, -1, -1, -2, -2, 0, 0, 0, 0, -1, -1,
+        -999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2];
+        // 0xC0-DF == -1 and 0xE0-FF == -2
+
+      function sanitizeTTProgram(table, ttContext) {
+        var data = table.data;
+        var i = 0, j, n, b, funcId, pc, lastEndf = 0, lastDeff = 0;
+        var stack = [];
+        var callstack = [];
+        var functionsCalled = [];
+        var tooComplexToFollowFunctions =
+          ttContext.tooComplexToFollowFunctions;
+        var inFDEF = false, ifLevel = 0, inELSE = 0;
+        for (var ii = data.length; i < ii;) {
+          var op = data[i++];
+          // The TrueType instruction set docs can be found at
+          // https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html
+          if (op === 0x40) { // NPUSHB - pushes n bytes
+            n = data[i++];
+            if (inFDEF || inELSE) {
+              i += n;
+            } else {
+              for (j = 0; j < n; j++) {
+                stack.push(data[i++]);
+              }
+            }
+          } else if (op === 0x41) { // NPUSHW - pushes n words
+            n = data[i++];
+            if (inFDEF || inELSE) {
+              i += n * 2;
+            } else {
+              for (j = 0; j < n; j++) {
+                b = data[i++];
+                stack.push((b << 8) | data[i++]);
+              }
+            }
+          } else if ((op & 0xF8) === 0xB0) { // PUSHB - pushes bytes
+            n = op - 0xB0 + 1;
+            if (inFDEF || inELSE) {
+              i += n;
+            } else {
+              for (j = 0; j < n; j++) {
+                stack.push(data[i++]);
+              }
+            }
+          } else if ((op & 0xF8) === 0xB8) { // PUSHW - pushes words
+            n = op - 0xB8 + 1;
+            if (inFDEF || inELSE) {
+              i += n * 2;
+            } else {
+              for (j = 0; j < n; j++) {
+                b = data[i++];
+                stack.push((b << 8) | data[i++]);
+              }
+            }
+          } else if (op === 0x2B && !tooComplexToFollowFunctions) { // CALL
+            if (!inFDEF && !inELSE) {
+              // collecting inforamtion about which functions are used
+              funcId = stack[stack.length - 1];
+              ttContext.functionsUsed[funcId] = true;
+              if (funcId in ttContext.functionsStackDeltas) {
+                stack.length += ttContext.functionsStackDeltas[funcId];
+              } else if (funcId in ttContext.functionsDefined &&
+                         functionsCalled.indexOf(funcId) < 0) {
+                callstack.push({data: data, i: i, stackTop: stack.length - 1});
+                functionsCalled.push(funcId);
+                pc = ttContext.functionsDefined[funcId];
+                if (!pc) {
+                  warn('TT: CALL non-existent function');
+                  ttContext.hintsValid = false;
+                  return;
+                }
+                data = pc.data;
+                i = pc.i;
+              }
+            }
+          } else if (op === 0x2C && !tooComplexToFollowFunctions) { // FDEF
+            if (inFDEF || inELSE) {
+              warn('TT: nested FDEFs not allowed');
+              tooComplexToFollowFunctions = true;
+            }
+            inFDEF = true;
+            // collecting inforamtion about which functions are defined
+            lastDeff = i;
+            funcId = stack.pop();
+            ttContext.functionsDefined[funcId] = {data: data, i: i};
+          } else if (op === 0x2D) { // ENDF - end of function
+            if (inFDEF) {
+              inFDEF = false;
+              lastEndf = i;
+            } else {
+              pc = callstack.pop();
+              if (!pc) {
+                warn('TT: ENDF bad stack');
+                ttContext.hintsValid = false;
+                return;
+              }
+              funcId = functionsCalled.pop();
+              data = pc.data;
+              i = pc.i;
+              ttContext.functionsStackDeltas[funcId] =
+                stack.length - pc.stackTop;
+            }
+          } else if (op === 0x89) { // IDEF - instruction definition
+            if (inFDEF || inELSE) {
+              warn('TT: nested IDEFs not allowed');
+              tooComplexToFollowFunctions = true;
+            }
+            inFDEF = true;
+            // recording it as a function to track ENDF
+            lastDeff = i;
+          } else if (op === 0x58) { // IF
+            ++ifLevel;
+          } else if (op === 0x1B) { // ELSE
+            inELSE = ifLevel;
+          } else if (op === 0x59) { // EIF
+            if (inELSE === ifLevel) {
+              inELSE = 0;
+            }
+            --ifLevel;
+          } else if (op === 0x1C) { // JMPR
+            if (!inFDEF && !inELSE) {
+              var offset = stack[stack.length - 1];
+              // only jumping forward to prevent infinite loop
+              if (offset > 0) {
+                i += offset - 1;
+              }
+            }
+          }
+          // Adjusting stack not extactly, but just enough to get function id
+          if (!inFDEF && !inELSE) {
+            var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] :
+              op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0;
+            if (op >= 0x71 && op <= 0x75) {
+              n = stack.pop();
+              if (n === n) {
+                stackDelta = -n * 2;
+              }
+            }
+            while (stackDelta < 0 && stack.length > 0) {
+              stack.pop();
+              stackDelta++;
+            }
+            while (stackDelta > 0) {
+              stack.push(NaN); // pushing any number into stack
+              stackDelta--;
+            }
+          }
+        }
+        ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions;
+        var content = [data];
+        if (i > data.length) {
+          content.push(new Uint8Array(i - data.length));
+        }
+        if (lastDeff > lastEndf) {
+          warn('TT: complementing a missing function tail');
+          // new function definition started, but not finished
+          // complete function by [CLEAR, ENDF]
+          content.push(new Uint8Array([0x22, 0x2D]));
+        }
+        foldTTTable(table, content);
       }
-      // Restore the global alpha to the fill alpha
-      ctx.globalAlpha = this.current.fillAlpha;
-    },
-    closeStroke: function CanvasGraphics_closeStroke() {
-      this.closePath();
-      this.stroke();
-    },
-    fill: function CanvasGraphics_fill(consumePath) {
-      consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
-      var ctx = this.ctx;
-      var fillColor = this.current.fillColor;
-      var isPatternFill = this.current.patternFill;
-      var needRestore = false;
 
-      if (isPatternFill) {
-        ctx.save();
-        if (this.baseTransform) {
-          ctx.setTransform.apply(ctx, this.baseTransform);
+      function checkInvalidFunctions(ttContext, maxFunctionDefs) {
+        if (ttContext.tooComplexToFollowFunctions) {
+          return;
+        }
+        if (ttContext.functionsDefined.length > maxFunctionDefs) {
+          warn('TT: more functions defined than expected');
+          ttContext.hintsValid = false;
+          return;
+        }
+        for (var j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {
+          if (j > maxFunctionDefs) {
+            warn('TT: invalid function id: ' + j);
+            ttContext.hintsValid = false;
+            return;
+          }
+          if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) {
+            warn('TT: undefined function: ' + j);
+            ttContext.hintsValid = false;
+            return;
+          }
         }
-        ctx.fillStyle = fillColor.getPattern(ctx, this);
-        needRestore = true;
       }
 
-      if (this.pendingEOFill) {
-        if (ctx.mozFillRule !== undefined) {
-          ctx.mozFillRule = 'evenodd';
-          ctx.fill();
-          ctx.mozFillRule = 'nonzero';
-        } else {
-          ctx.fill('evenodd');
+      function foldTTTable(table, content) {
+        if (content.length > 1) {
+          // concatenating the content items
+          var newLength = 0;
+          var j, jj;
+          for (j = 0, jj = content.length; j < jj; j++) {
+            newLength += content[j].length;
+          }
+          newLength = (newLength + 3) & ~3;
+          var result = new Uint8Array(newLength);
+          var pos = 0;
+          for (j = 0, jj = content.length; j < jj; j++) {
+            result.set(content[j], pos);
+            pos += content[j].length;
+          }
+          table.data = result;
+          table.length = newLength;
         }
-        this.pendingEOFill = false;
-      } else {
-        ctx.fill();
       }
 
-      if (needRestore) {
-        ctx.restore();
-      }
-      if (consumePath) {
-        this.consumePath();
+      function sanitizeTTPrograms(fpgm, prep, cvt) {
+        var ttContext = {
+          functionsDefined: [],
+          functionsUsed: [],
+          functionsStackDeltas: [],
+          tooComplexToFollowFunctions: false,
+          hintsValid: true
+        };
+        if (fpgm) {
+          sanitizeTTProgram(fpgm, ttContext);
+        }
+        if (prep) {
+          sanitizeTTProgram(prep, ttContext);
+        }
+        if (fpgm) {
+          checkInvalidFunctions(ttContext, maxFunctionDefs);
+        }
+        if (cvt && (cvt.length & 1)) {
+          var cvtData = new Uint8Array(cvt.length + 1);
+          cvtData.set(cvt.data);
+          cvt.data = cvtData;
+        }
+        return ttContext.hintsValid;
       }
-    },
-    eoFill: function CanvasGraphics_eoFill() {
-      this.pendingEOFill = true;
-      this.fill();
-    },
-    fillStroke: function CanvasGraphics_fillStroke() {
-      this.fill(false);
-      this.stroke(false);
 
-      this.consumePath();
-    },
-    eoFillStroke: function CanvasGraphics_eoFillStroke() {
-      this.pendingEOFill = true;
-      this.fillStroke();
-    },
-    closeFillStroke: function CanvasGraphics_closeFillStroke() {
-      this.closePath();
-      this.fillStroke();
-    },
-    closeEOFillStroke: function CanvasGraphics_closeEOFillStroke() {
-      this.pendingEOFill = true;
-      this.closePath();
-      this.fillStroke();
-    },
-    endPath: function CanvasGraphics_endPath() {
-      this.consumePath();
-    },
+      // The following steps modify the original font data, making copy
+      font = new Stream(new Uint8Array(font.getBytes()));
 
-    // Clipping
-    clip: function CanvasGraphics_clip() {
-      this.pendingClip = NORMAL_CLIP;
-    },
-    eoClip: function CanvasGraphics_eoClip() {
-      this.pendingClip = EO_CLIP;
-    },
+      var VALID_TABLES = ['OS/2', 'cmap', 'head', 'hhea', 'hmtx', 'maxp',
+        'name', 'post', 'loca', 'glyf', 'fpgm', 'prep', 'cvt ', 'CFF '];
 
-    // Text
-    beginText: function CanvasGraphics_beginText() {
-      this.current.textMatrix = IDENTITY_MATRIX;
-      this.current.textMatrixScale = 1;
-      this.current.x = this.current.lineX = 0;
-      this.current.y = this.current.lineY = 0;
-    },
-    endText: function CanvasGraphics_endText() {
-      var paths = this.pendingTextPaths;
-      var ctx = this.ctx;
-      if (paths === undefined) {
-        ctx.beginPath();
-        return;
-      }
+      var header = readOpenTypeHeader(font);
+      var numTables = header.numTables;
+      var cff, cffFile;
 
-      ctx.save();
-      ctx.beginPath();
-      for (var i = 0; i < paths.length; i++) {
-        var path = paths[i];
-        ctx.setTransform.apply(ctx, path.transform);
-        ctx.translate(path.x, path.y);
-        path.addToPath(ctx, path.fontSize);
-      }
-      ctx.restore();
-      ctx.clip();
-      ctx.beginPath();
-      delete this.pendingTextPaths;
-    },
-    setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) {
-      this.current.charSpacing = spacing;
-    },
-    setWordSpacing: function CanvasGraphics_setWordSpacing(spacing) {
-      this.current.wordSpacing = spacing;
-    },
-    setHScale: function CanvasGraphics_setHScale(scale) {
-      this.current.textHScale = scale / 100;
-    },
-    setLeading: function CanvasGraphics_setLeading(leading) {
-      this.current.leading = -leading;
-    },
-    setFont: function CanvasGraphics_setFont(fontRefName, size) {
-      var fontObj = this.commonObjs.get(fontRefName);
-      var current = this.current;
+      var tables = Object.create(null);
+      tables['OS/2'] = null;
+      tables['cmap'] = null;
+      tables['head'] = null;
+      tables['hhea'] = null;
+      tables['hmtx'] = null;
+      tables['maxp'] = null;
+      tables['name'] = null;
+      tables['post'] = null;
 
-      if (!fontObj) {
-        error('Can\'t find font for ' + fontRefName);
+      var table;
+      for (var i = 0; i < numTables; i++) {
+        table = readTableEntry(font);
+        if (VALID_TABLES.indexOf(table.tag) < 0) {
+          continue; // skipping table if it's not a required or optional table
+        }
+        if (table.length === 0) {
+          continue; // skipping empty tables
+        }
+        tables[table.tag] = table;
       }
 
-      current.fontMatrix = (fontObj.fontMatrix ?
-                            fontObj.fontMatrix : FONT_IDENTITY_MATRIX);
+      var isTrueType = !tables['CFF '];
+      if (!isTrueType) {
+        // OpenType font
+        if ((header.version === 'OTTO' && properties.type !== 'CIDFontType2') ||
+            !tables['head'] || !tables['hhea'] || !tables['maxp'] ||
+            !tables['post']) {
+          // no major tables: throwing everything at CFFFont
+          cffFile = new Stream(tables['CFF '].data);
+          cff = new CFFFont(cffFile, properties);
 
-      // A valid matrix needs all main diagonal elements to be non-zero
-      // This also ensures we bypass FF bugzilla bug #719844.
-      if (current.fontMatrix[0] === 0 ||
-          current.fontMatrix[3] === 0) {
-        warn('Invalid font matrix for font ' + fontRefName);
-      }
+          adjustWidths(properties);
 
-      // The spec for Tf (setFont) says that 'size' specifies the font 'scale',
-      // and in some docs this can be negative (inverted x-y axes).
-      if (size < 0) {
-        size = -size;
-        current.fontDirection = -1;
+          return this.convert(name, cff, properties);
+        }
+
+        delete tables['glyf'];
+        delete tables['loca'];
+        delete tables['fpgm'];
+        delete tables['prep'];
+        delete tables['cvt '];
+        this.isOpenType = true;
       } else {
-        current.fontDirection = 1;
+        if (!tables['loca']) {
+          error('Required "loca" table is not found');
+        }
+        if (!tables['glyf']) {
+          warn('Required "glyf" table is not found -- trying to recover.');
+          // Note: We use `sanitizeGlyphLocations` to add dummy glyf data below.
+          tables['glyf'] = {
+            tag: 'glyf',
+            data: new Uint8Array(0),
+          };
+        }
+        this.isOpenType = false;
       }
 
-      this.current.font = fontObj;
-      this.current.fontSize = size;
-
-      if (fontObj.isType3Font) {
-        return; // we don't need ctx.font for Type3 fonts
+      if (!tables['maxp']) {
+        error('Required "maxp" table is not found');
       }
 
-      var name = fontObj.loadedName || 'sans-serif';
-      var bold = fontObj.black ? (fontObj.bold ? '900' : 'bold') :
-                                 (fontObj.bold ? 'bold' : 'normal');
-
-      var italic = fontObj.italic ? 'italic' : 'normal';
-      var typeface = '"' + name + '", ' + fontObj.fallbackName;
-
-      // Some font backends cannot handle fonts below certain size.
-      // Keeping the font at minimal size and using the fontSizeScale to change
-      // the current transformation matrix before the fillText/strokeText.
-      // See https://bugzilla.mozilla.org/show_bug.cgi?id=726227
-      var browserFontSize = size < MIN_FONT_SIZE ? MIN_FONT_SIZE :
-                            size > MAX_FONT_SIZE ? MAX_FONT_SIZE : size;
-      this.current.fontSizeScale = size / browserFontSize;
-
-      var rule = italic + ' ' + bold + ' ' + browserFontSize + 'px ' + typeface;
-      this.ctx.font = rule;
-    },
-    setTextRenderingMode: function CanvasGraphics_setTextRenderingMode(mode) {
-      this.current.textRenderingMode = mode;
-    },
-    setTextRise: function CanvasGraphics_setTextRise(rise) {
-      this.current.textRise = rise;
-    },
-    moveText: function CanvasGraphics_moveText(x, y) {
-      this.current.x = this.current.lineX += x;
-      this.current.y = this.current.lineY += y;
-    },
-    setLeadingMoveText: function CanvasGraphics_setLeadingMoveText(x, y) {
-      this.setLeading(-y);
-      this.moveText(x, y);
-    },
-    setTextMatrix: function CanvasGraphics_setTextMatrix(a, b, c, d, e, f) {
-      this.current.textMatrix = [a, b, c, d, e, f];
-      this.current.textMatrixScale = Math.sqrt(a * a + b * b);
-
-      this.current.x = this.current.lineX = 0;
-      this.current.y = this.current.lineY = 0;
-    },
-    nextLine: function CanvasGraphics_nextLine() {
-      this.moveText(0, this.current.leading);
-    },
-
-    paintChar: function CanvasGraphics_paintChar(character, x, y) {
-      var ctx = this.ctx;
-      var current = this.current;
-      var font = current.font;
-      var textRenderingMode = current.textRenderingMode;
-      var fontSize = current.fontSize / current.fontSizeScale;
-      var fillStrokeMode = textRenderingMode &
-        TextRenderingMode.FILL_STROKE_MASK;
-      var isAddToPathSet = !!(textRenderingMode &
-        TextRenderingMode.ADD_TO_PATH_FLAG);
-
-      var addToPath;
-      if (font.disableFontFace || isAddToPathSet) {
-        addToPath = font.getPathGenerator(this.commonObjs, character);
+      font.pos = (font.start || 0) + tables['maxp'].offset;
+      var version = font.getInt32();
+      var numGlyphs = font.getUint16();
+      var maxFunctionDefs = 0;
+      if (version >= 0x00010000 && tables['maxp'].length >= 22) {
+        // maxZones can be invalid
+        font.pos += 8;
+        var maxZones = font.getUint16();
+        if (maxZones > 2) { // reset to 2 if font has invalid maxZones
+          tables['maxp'].data[14] = 0;
+          tables['maxp'].data[15] = 2;
+        }
+        font.pos += 4;
+        maxFunctionDefs = font.getUint16();
       }
 
-      if (font.disableFontFace) {
-        ctx.save();
-        ctx.translate(x, y);
-        ctx.beginPath();
-        addToPath(ctx, fontSize);
-        if (fillStrokeMode === TextRenderingMode.FILL ||
-            fillStrokeMode === TextRenderingMode.FILL_STROKE) {
-          ctx.fill();
-        }
-        if (fillStrokeMode === TextRenderingMode.STROKE ||
-            fillStrokeMode === TextRenderingMode.FILL_STROKE) {
-          ctx.stroke();
-        }
-        ctx.restore();
-      } else {
-        if (fillStrokeMode === TextRenderingMode.FILL ||
-            fillStrokeMode === TextRenderingMode.FILL_STROKE) {
-          ctx.fillText(character, x, y);
-        }
-        if (fillStrokeMode === TextRenderingMode.STROKE ||
-            fillStrokeMode === TextRenderingMode.FILL_STROKE) {
-          ctx.strokeText(character, x, y);
-        }
+      var dupFirstEntry = false;
+      if (properties.type === 'CIDFontType2' && properties.toUnicode &&
+          properties.toUnicode.get(0) > '\u0000') {
+        // oracle's defect (see 3427), duplicating first entry
+        dupFirstEntry = true;
+        numGlyphs++;
+        tables['maxp'].data[4] = numGlyphs >> 8;
+        tables['maxp'].data[5] = numGlyphs & 255;
       }
 
-      if (isAddToPathSet) {
-        var paths = this.pendingTextPaths || (this.pendingTextPaths = []);
-        paths.push({
-          transform: ctx.mozCurrentTransform,
-          x: x,
-          y: y,
-          fontSize: fontSize,
-          addToPath: addToPath
-        });
+      var hintsValid = sanitizeTTPrograms(tables['fpgm'], tables['prep'],
+                                          tables['cvt '], maxFunctionDefs);
+      if (!hintsValid) {
+        delete tables['fpgm'];
+        delete tables['prep'];
+        delete tables['cvt '];
       }
-    },
 
-    get isFontSubpixelAAEnabled() {
-      // Checks if anti-aliasing is enabled when scaled text is painted.
-      // On Windows GDI scaled fonts looks bad.
-      var ctx = document.createElement('canvas').getContext('2d');
-      ctx.scale(1.5, 1);
-      ctx.fillText('I', 0, 10);
-      var data = ctx.getImageData(0, 0, 10, 10).data;
-      var enabled = false;
-      for (var i = 3; i < data.length; i += 4) {
-        if (data[i] > 0 && data[i] < 255) {
-          enabled = true;
-          break;
-        }
+      // Ensure the hmtx table contains the advance width and
+      // sidebearings information for numGlyphs in the maxp table
+      sanitizeMetrics(font, tables['hhea'], tables['hmtx'], numGlyphs);
+
+      if (!tables['head']) {
+        error('Required "head" table is not found');
       }
-      return shadow(this, 'isFontSubpixelAAEnabled', enabled);
-    },
 
-    showText: function CanvasGraphics_showText(glyphs) {
-      var current = this.current;
-      var font = current.font;
-      if (font.isType3Font) {
-        return this.showType3Text(glyphs);
+      sanitizeHead(tables['head'], numGlyphs,
+                   isTrueType ? tables['loca'].length : 0);
+
+      var missingGlyphs = Object.create(null);
+      if (isTrueType) {
+        var isGlyphLocationsLong = int16(tables['head'].data[50],
+                                         tables['head'].data[51]);
+        missingGlyphs = sanitizeGlyphLocations(tables['loca'], tables['glyf'],
+                                               numGlyphs, isGlyphLocationsLong,
+                                               hintsValid, dupFirstEntry);
       }
 
-      var fontSize = current.fontSize;
-      if (fontSize === 0) {
-        return;
+      if (!tables['hhea']) {
+        error('Required "hhea" table is not found');
       }
 
-      var ctx = this.ctx;
-      var fontSizeScale = current.fontSizeScale;
-      var charSpacing = current.charSpacing;
-      var wordSpacing = current.wordSpacing;
-      var fontDirection = current.fontDirection;
-      var textHScale = current.textHScale * fontDirection;
-      var glyphsLength = glyphs.length;
-      var vertical = font.vertical;
-      var spacingDir = vertical ? 1 : -1;
-      var defaultVMetrics = font.defaultVMetrics;
-      var widthAdvanceScale = fontSize * current.fontMatrix[0];
+      // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth
+      // Sometimes it's 0. That needs to be fixed
+      if (tables['hhea'].data[10] === 0 && tables['hhea'].data[11] === 0) {
+        tables['hhea'].data[10] = 0xFF;
+        tables['hhea'].data[11] = 0xFF;
+      }
 
-      var simpleFillText =
-        current.textRenderingMode === TextRenderingMode.FILL &&
-        !font.disableFontFace;
+      // Extract some more font properties from the OpenType head and
+      // hhea tables; yMin and descent value are always negative.
+      var metricsOverride = {
+        unitsPerEm: int16(tables['head'].data[18], tables['head'].data[19]),
+        yMax: int16(tables['head'].data[42], tables['head'].data[43]),
+        yMin: signedInt16(tables['head'].data[38], tables['head'].data[39]),
+        ascent: int16(tables['hhea'].data[4], tables['hhea'].data[5]),
+        descent: signedInt16(tables['hhea'].data[6], tables['hhea'].data[7])
+      };
 
-      ctx.save();
-      ctx.transform.apply(ctx, current.textMatrix);
-      ctx.translate(current.x, current.y + current.textRise);
+      // PDF FontDescriptor metrics lie -- using data from actual font.
+      this.ascent = metricsOverride.ascent / metricsOverride.unitsPerEm;
+      this.descent = metricsOverride.descent / metricsOverride.unitsPerEm;
 
-      if (current.patternFill) {
-        // TODO: Some shading patterns are not applied correctly to text,
-        //       e.g. issues 3988 and 5432, and ShowText-ShadingPattern.pdf.
-        ctx.fillStyle = current.fillColor.getPattern(ctx, this);
+      // The 'post' table has glyphs names.
+      if (tables['post']) {
+        var valid = readPostScriptTable(tables['post'], properties, numGlyphs);
+        if (!valid) {
+          tables['post'] = null;
+        }
       }
 
-      if (fontDirection > 0) {
-        ctx.scale(textHScale, -1);
-      } else {
-        ctx.scale(textHScale, 1);
+      var charCodeToGlyphId = [], charCode;
+      var toUnicode = properties.toUnicode, widths = properties.widths;
+      var skipToUnicode = (toUnicode instanceof IdentityToUnicodeMap ||
+                           toUnicode.length === 0x10000);
+
+      // Helper function to try to skip mapping of empty glyphs.
+      // Note: In some cases, just relying on the glyph data doesn't work,
+      //       hence we also use a few heuristics to fix various PDF files.
+      function hasGlyph(glyphId, charCode, widthCode) {
+        if (!missingGlyphs[glyphId]) {
+          return true;
+        }
+        if (!skipToUnicode && charCode >= 0 && toUnicode.has(charCode)) {
+          return true;
+        }
+        if (widths && widthCode >= 0 && isNum(widths[widthCode])) {
+          return true;
+        }
+        return false;
       }
 
-      var lineWidth = current.lineWidth;
-      var scale = current.textMatrixScale;
-      if (scale === 0 || lineWidth === 0) {
-        var fillStrokeMode = current.textRenderingMode &
-          TextRenderingMode.FILL_STROKE_MASK;
-        if (fillStrokeMode === TextRenderingMode.STROKE ||
-            fillStrokeMode === TextRenderingMode.FILL_STROKE) {
-          this.cachedGetSinglePixelWidth = null;
-          lineWidth = this.getSinglePixelWidth() * MIN_WIDTH_FACTOR;
+      // Some bad PDF generators, e.g. Scribus PDF, include glyph names
+      // in a 'uniXXXX' format -- attempting to recover proper ones.
+      function recoverGlyphName(name, glyphsUnicodeMap) {
+        if (glyphsUnicodeMap[name] !== undefined) {
+          return name;
+        }
+        // The glyph name is non-standard, trying to recover.
+        var unicode = getUnicodeForGlyph(name, glyphsUnicodeMap);
+        if (unicode !== -1) {
+          for (var key in glyphsUnicodeMap) {
+            if (glyphsUnicodeMap[key] === unicode) {
+              return key;
+            }
+          }
         }
-      } else {
-        lineWidth /= scale;
-      }
-
-      if (fontSizeScale !== 1.0) {
-        ctx.scale(fontSizeScale, fontSizeScale);
-        lineWidth /= fontSizeScale;
+        warn('Unable to recover a standard glyph name for: ' + name);
+        return name;
       }
 
-      ctx.lineWidth = lineWidth;
 
-      var x = 0, i;
-      for (i = 0; i < glyphsLength; ++i) {
-        var glyph = glyphs[i];
-        if (isNum(glyph)) {
-          x += spacingDir * glyph * fontSize / 1000;
-          continue;
-        }
+      if (properties.type === 'CIDFontType2') {
+        var cidToGidMap = properties.cidToGidMap || [];
+        var isCidToGidMapEmpty = cidToGidMap.length === 0;
 
-        var restoreNeeded = false;
-        var spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
-        var character = glyph.fontChar;
-        var accent = glyph.accent;
-        var scaledX, scaledY, scaledAccentX, scaledAccentY;
-        var width = glyph.width;
-        if (vertical) {
-          var vmetric, vx, vy;
-          vmetric = glyph.vmetric || defaultVMetrics;
-          vx = glyph.vmetric ? vmetric[1] : width * 0.5;
-          vx = -vx * widthAdvanceScale;
-          vy = vmetric[2] * widthAdvanceScale;
+        properties.cMap.forEach(function(charCode, cid) {
+          assert(cid <= 0xffff, 'Max size of CID is 65,535');
+          var glyphId = -1;
+          if (isCidToGidMapEmpty) {
+            glyphId = cid;
+          } else if (cidToGidMap[cid] !== undefined) {
+            glyphId = cidToGidMap[cid];
+          }
 
-          width = vmetric ? -vmetric[0] : width;
-          scaledX = vx / fontSizeScale;
-          scaledY = (x + vy) / fontSizeScale;
-        } else {
-          scaledX = x / fontSizeScale;
-          scaledY = 0;
+          if (glyphId >= 0 && glyphId < numGlyphs &&
+              hasGlyph(glyphId, charCode, cid)) {
+            charCodeToGlyphId[charCode] = glyphId;
+          }
+        });
+        if (dupFirstEntry) {
+          charCodeToGlyphId[0] = numGlyphs - 1;
         }
+      } else {
+        // Most of the following logic in this code branch is based on the
+        // 9.6.6.4 of the PDF spec.
+        var hasEncoding =
+          properties.differences.length > 0 || !!properties.baseEncodingName;
+        var cmapTable =
+          readCmapTable(tables['cmap'], font, this.isSymbolicFont, hasEncoding);
+        var cmapPlatformId = cmapTable.platformId;
+        var cmapEncodingId = cmapTable.encodingId;
+        var cmapMappings = cmapTable.mappings;
+        var cmapMappingsLength = cmapMappings.length;
 
-        if (font.remeasure && width > 0) {
-          // Some standard fonts may not have the exact width: rescale per
-          // character if measured width is greater than expected glyph width
-          // and subpixel-aa is enabled, otherwise just center the glyph.
-          var measuredWidth = ctx.measureText(character).width * 1000 /
-            fontSize * fontSizeScale;
-          if (width < measuredWidth && this.isFontSubpixelAAEnabled) {
-            var characterScaleX = width / measuredWidth;
-            restoreNeeded = true;
-            ctx.save();
-            ctx.scale(characterScaleX, 1);
-            scaledX /= characterScaleX;
-          } else if (width !== measuredWidth) {
-            scaledX += (width - measuredWidth) / 2000 *
-              fontSize / fontSizeScale;
+        // The spec seems to imply that if the font is symbolic the encoding
+        // should be ignored, this doesn't appear to work for 'preistabelle.pdf'
+        // where the the font is symbolic and it has an encoding.
+        if (hasEncoding &&
+            (cmapPlatformId === 3 && cmapEncodingId === 1 ||
+             cmapPlatformId === 1 && cmapEncodingId === 0) ||
+            (cmapPlatformId === -1 && cmapEncodingId === -1 && // Temporary hack
+             !!getEncoding(properties.baseEncodingName))) {    // Temporary hack
+          // When no preferred cmap table was found and |baseEncodingName| is
+          // one of the predefined encodings, we seem to obtain a better
+          // |charCodeToGlyphId| map from the code below (fixes bug 1057544).
+          // TODO: Note that this is a hack which should be removed as soon as
+          //       we have proper support for more exotic cmap tables.
+
+          var baseEncoding = [];
+          if (properties.baseEncodingName === 'MacRomanEncoding' ||
+              properties.baseEncodingName === 'WinAnsiEncoding') {
+            baseEncoding = getEncoding(properties.baseEncodingName);
           }
-        }
+          var glyphsUnicodeMap = getGlyphsUnicode();
+          for (charCode = 0; charCode < 256; charCode++) {
+            var glyphName, standardGlyphName;
+            if (this.differences && charCode in this.differences) {
+              glyphName = this.differences[charCode];
+            } else if (charCode in baseEncoding &&
+                       baseEncoding[charCode] !== '') {
+              glyphName = baseEncoding[charCode];
+            } else {
+              glyphName = StandardEncoding[charCode];
+            }
+            if (!glyphName) {
+              continue;
+            }
+            // Ensure that non-standard glyph names are resolved to valid ones.
+            standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap);
 
-        // Only attempt to draw the glyph if it is actually in the embedded font
-        // file or if there isn't a font file so the fallback font is shown.
-        if (glyph.isInFont || font.missingFile) {
-          if (simpleFillText && !accent) {
-            // common case
-            ctx.fillText(character, scaledX, scaledY);
-          } else {
-            this.paintChar(character, scaledX, scaledY);
-            if (accent) {
-              scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
-              scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
-              this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY);
+            var unicodeOrCharCode, isUnicode = false;
+            if (cmapPlatformId === 3 && cmapEncodingId === 1) {
+              unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName];
+              isUnicode = true;
+            } else if (cmapPlatformId === 1 && cmapEncodingId === 0) {
+              // TODO: the encoding needs to be updated with mac os table.
+              unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName);
+            }
+
+            var found = false;
+            for (i = 0; i < cmapMappingsLength; ++i) {
+              if (cmapMappings[i].charCode !== unicodeOrCharCode) {
+                continue;
+              }
+              var code = isUnicode ? charCode : unicodeOrCharCode;
+              if (hasGlyph(cmapMappings[i].glyphId, code, -1)) {
+                charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
+                found = true;
+                break;
+              }
+            }
+            if (!found && properties.glyphNames) {
+              // Try to map using the post table.
+              var glyphId = properties.glyphNames.indexOf(glyphName);
+              // The post table ought to use the same kind of glyph names as the
+              // `differences` array, but check the standard ones as a fallback.
+              if (glyphId === -1 && standardGlyphName !== glyphName) {
+                glyphId = properties.glyphNames.indexOf(standardGlyphName);
+              }
+              if (glyphId > 0 && hasGlyph(glyphId, -1, -1)) {
+                charCodeToGlyphId[charCode] = glyphId;
+                found = true;
+              }
+            }
+            if (!found) {
+              charCodeToGlyphId[charCode] = 0; // notdef
             }
           }
+        } else if (cmapPlatformId === 0 && cmapEncodingId === 0) {
+          // Default Unicode semantics, use the charcodes as is.
+          for (i = 0; i < cmapMappingsLength; ++i) {
+            charCodeToGlyphId[cmapMappings[i].charCode] =
+              cmapMappings[i].glyphId;
+          }
+        } else {
+          // For (3, 0) cmap tables:
+          // The charcode key being stored in charCodeToGlyphId is the lower
+          // byte of the two-byte charcodes of the cmap table since according to
+          // the spec: 'each byte from the string shall be prepended with the
+          // high byte of the range [of charcodes in the cmap table], to form
+          // a two-byte character, which shall be used to select the
+          // associated glyph description from the subtable'.
+          //
+          // For (1, 0) cmap tables:
+          // 'single bytes from the string shall be used to look up the
+          // associated glyph descriptions from the subtable'. This means
+          // charcodes in the cmap will be single bytes, so no-op since
+          // glyph.charCode & 0xFF === glyph.charCode
+          for (i = 0; i < cmapMappingsLength; ++i) {
+            charCode = cmapMappings[i].charCode & 0xFF;
+            charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
+          }
         }
+      }
 
-        var charWidth = width * widthAdvanceScale + spacing * fontDirection;
-        x += charWidth;
+      if (charCodeToGlyphId.length === 0) {
+        // defines at least one glyph
+        charCodeToGlyphId[0] = 0;
+      }
 
-        if (restoreNeeded) {
-          ctx.restore();
+      // Converting glyphs and ids into font's cmap table
+      var newMapping = adjustMapping(charCodeToGlyphId, properties);
+      this.toFontChar = newMapping.toFontChar;
+      tables['cmap'] = {
+        tag: 'cmap',
+        data: createCmapTable(newMapping.charCodeToGlyphId, numGlyphs)
+      };
+
+      if (!tables['OS/2'] || !validateOS2Table(tables['OS/2'])) {
+        tables['OS/2'] = {
+          tag: 'OS/2',
+          data: createOS2Table(properties, newMapping.charCodeToGlyphId,
+                               metricsOverride)
+        };
+      }
+
+      // Rewrite the 'post' table if needed
+      if (!tables['post']) {
+        tables['post'] = {
+          tag: 'post',
+          data: createPostTable(properties)
+        };
+      }
+
+      if (!isTrueType) {
+        try {
+          // Trying to repair CFF file
+          cffFile = new Stream(tables['CFF '].data);
+          var parser = new CFFParser(cffFile, properties,
+                                     SEAC_ANALYSIS_ENABLED);
+          cff = parser.parse();
+          var compiler = new CFFCompiler(cff);
+          tables['CFF '].data = compiler.compile();
+        } catch (e) {
+          warn('Failed to compile font ' + properties.loadedName);
         }
       }
-      if (vertical) {
-        current.y -= x * textHScale;
+
+      // Re-creating 'name' table
+      if (!tables['name']) {
+        tables['name'] = {
+          tag: 'name',
+          data: createNameTable(this.name)
+        };
       } else {
-        current.x += x * textHScale;
+        // ... using existing 'name' table as prototype
+        var namePrototype = readNameTable(tables['name']);
+        tables['name'].data = createNameTable(name, namePrototype);
       }
-      ctx.restore();
-    },
-
-    showType3Text: function CanvasGraphics_showType3Text(glyphs) {
-      // Type3 fonts - each glyph is a "mini-PDF"
-      var ctx = this.ctx;
-      var current = this.current;
-      var font = current.font;
-      var fontSize = current.fontSize;
-      var fontDirection = current.fontDirection;
-      var spacingDir = font.vertical ? 1 : -1;
-      var charSpacing = current.charSpacing;
-      var wordSpacing = current.wordSpacing;
-      var textHScale = current.textHScale * fontDirection;
-      var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
-      var glyphsLength = glyphs.length;
-      var isTextInvisible =
-        current.textRenderingMode === TextRenderingMode.INVISIBLE;
-      var i, glyph, width, spacingLength;
 
-      if (isTextInvisible || fontSize === 0) {
-        return;
+      var builder = new OpenTypeFileBuilder(header.version);
+      for (var tableTag in tables) {
+        builder.addTable(tableTag, tables[tableTag].data);
       }
-      this.cachedGetSinglePixelWidth = null;
+      return builder.toArray();
+    },
 
-      ctx.save();
-      ctx.transform.apply(ctx, current.textMatrix);
-      ctx.translate(current.x, current.y);
+    convert: function Font_convert(fontName, font, properties) {
+      // TODO: Check the charstring widths to determine this.
+      properties.fixedPitch = false;
 
-      ctx.scale(textHScale, fontDirection);
+      var mapping = font.getGlyphMapping(properties);
+      var newMapping = adjustMapping(mapping, properties);
+      this.toFontChar = newMapping.toFontChar;
+      var numGlyphs = font.numGlyphs;
 
-      for (i = 0; i < glyphsLength; ++i) {
-        glyph = glyphs[i];
-        if (isNum(glyph)) {
-          spacingLength = spacingDir * glyph * fontSize / 1000;
-          this.ctx.translate(spacingLength, 0);
-          current.x += spacingLength * textHScale;
-          continue;
+      function getCharCodes(charCodeToGlyphId, glyphId) {
+        var charCodes = null;
+        for (var charCode in charCodeToGlyphId) {
+          if (glyphId === charCodeToGlyphId[charCode]) {
+            if (!charCodes) {
+              charCodes = [];
+            }
+            charCodes.push(charCode | 0);
+          }
         }
+        return charCodes;
+      }
 
-        var spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
-        var operatorList = font.charProcOperatorList[glyph.operatorListId];
-        if (!operatorList) {
-          warn('Type3 character \"' + glyph.operatorListId +
-               '\" is not available');
-          continue;
+      function createCharCode(charCodeToGlyphId, glyphId) {
+        for (var charCode in charCodeToGlyphId) {
+          if (glyphId === charCodeToGlyphId[charCode]) {
+            return charCode | 0;
+          }
         }
-        this.processingType3 = glyph;
-        this.save();
-        ctx.scale(fontSize, fontSize);
-        ctx.transform.apply(ctx, fontMatrix);
-        this.executeOperatorList(operatorList);
-        this.restore();
-
-        var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
-        width = transformed[0] * fontSize + spacing;
-
-        ctx.translate(width, 0);
-        current.x += width * textHScale;
+        newMapping.charCodeToGlyphId[newMapping.nextAvailableFontCharCode] =
+            glyphId;
+        return newMapping.nextAvailableFontCharCode++;
       }
-      ctx.restore();
-      this.processingType3 = null;
-    },
 
-    // Type3 fonts
-    setCharWidth: function CanvasGraphics_setCharWidth(xWidth, yWidth) {
-      // We can safely ignore this since the width should be the same
-      // as the width in the Widths array.
-    },
-    setCharWidthAndBounds: function CanvasGraphics_setCharWidthAndBounds(xWidth,
-                                                                        yWidth,
-                                                                        llx,
-                                                                        lly,
-                                                                        urx,
-                                                                        ury) {
-      // TODO According to the spec we're also suppose to ignore any operators
-      // that set color or include images while processing this type3 font.
-      this.ctx.rect(llx, lly, urx - llx, ury - lly);
-      this.clip();
-      this.endPath();
-    },
+      var seacs = font.seacs;
+      if (SEAC_ANALYSIS_ENABLED && seacs && seacs.length) {
+        var matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX;
+        var charset = font.getCharset();
+        var seacMap = Object.create(null);
+        for (var glyphId in seacs) {
+          glyphId |= 0;
+          var seac = seacs[glyphId];
+          var baseGlyphName = StandardEncoding[seac[2]];
+          var accentGlyphName = StandardEncoding[seac[3]];
+          var baseGlyphId = charset.indexOf(baseGlyphName);
+          var accentGlyphId = charset.indexOf(accentGlyphName);
+          if (baseGlyphId < 0 || accentGlyphId < 0) {
+            continue;
+          }
+          var accentOffset = {
+            x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4],
+            y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5]
+          };
 
-    // Color
-    getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR) {
-      var pattern;
-      if (IR[0] === 'TilingPattern') {
-        var color = IR[1];
-        var baseTransform = this.baseTransform ||
-                            this.ctx.mozCurrentTransform.slice();
-        var self = this;
-        var canvasGraphicsFactory = {
-          createCanvasGraphics: function (ctx) {
-            return new CanvasGraphics(ctx, self.commonObjs, self.objs);
+          var charCodes = getCharCodes(mapping, glyphId);
+          if (!charCodes) {
+            // There's no point in mapping it if the char code was never mapped
+            // to begin with.
+            continue;
           }
-        };
-        pattern = new TilingPattern(IR, color, this.ctx, canvasGraphicsFactory,
-                                    baseTransform);
-      } else {
-        pattern = getShadingPatternFromIR(IR);
+          for (var i = 0, ii = charCodes.length; i < ii; i++) {
+            var charCode = charCodes[i];
+            // Find a fontCharCode that maps to the base and accent glyphs.
+            // If one doesn't exists, create it.
+            var charCodeToGlyphId = newMapping.charCodeToGlyphId;
+            var baseFontCharCode = createCharCode(charCodeToGlyphId,
+                                                  baseGlyphId);
+            var accentFontCharCode = createCharCode(charCodeToGlyphId,
+                                                    accentGlyphId);
+            seacMap[charCode] = {
+              baseFontCharCode: baseFontCharCode,
+              accentFontCharCode: accentFontCharCode,
+              accentOffset: accentOffset
+            };
+          }
+        }
+        properties.seacMap = seacMap;
       }
-      return pattern;
-    },
-    setStrokeColorN: function CanvasGraphics_setStrokeColorN(/*...*/) {
-      this.current.strokeColor = this.getColorN_Pattern(arguments);
-    },
-    setFillColorN: function CanvasGraphics_setFillColorN(/*...*/) {
-      this.current.fillColor = this.getColorN_Pattern(arguments);
-      this.current.patternFill = true;
-    },
-    setStrokeRGBColor: function CanvasGraphics_setStrokeRGBColor(r, g, b) {
-      var color = Util.makeCssRgb(r, g, b);
-      this.ctx.strokeStyle = color;
-      this.current.strokeColor = color;
-    },
-    setFillRGBColor: function CanvasGraphics_setFillRGBColor(r, g, b) {
-      var color = Util.makeCssRgb(r, g, b);
-      this.ctx.fillStyle = color;
-      this.current.fillColor = color;
-      this.current.patternFill = false;
-    },
 
-    shadingFill: function CanvasGraphics_shadingFill(patternIR) {
-      var ctx = this.ctx;
-
-      this.save();
-      var pattern = getShadingPatternFromIR(patternIR);
-      ctx.fillStyle = pattern.getPattern(ctx, this, true);
+      var unitsPerEm = 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0];
 
-      var inv = ctx.mozCurrentTransformInverse;
-      if (inv) {
-        var canvas = ctx.canvas;
-        var width = canvas.width;
-        var height = canvas.height;
+      var builder = new OpenTypeFileBuilder('\x4F\x54\x54\x4F');
+      // PostScript Font Program
+      builder.addTable('CFF ', font.data);
+      // OS/2 and Windows Specific metrics
+      builder.addTable('OS/2', createOS2Table(properties,
+                                              newMapping.charCodeToGlyphId));
+      // Character to glyphs mapping
+      builder.addTable('cmap', createCmapTable(newMapping.charCodeToGlyphId,
+                       numGlyphs));
+      // Font header
+      builder.addTable('head',
+            '\x00\x01\x00\x00' + // Version number
+            '\x00\x00\x10\x00' + // fontRevision
+            '\x00\x00\x00\x00' + // checksumAdjustement
+            '\x5F\x0F\x3C\xF5' + // magicNumber
+            '\x00\x00' + // Flags
+            safeString16(unitsPerEm) + // unitsPerEM
+            '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // creation date
+            '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // modifification date
+            '\x00\x00' + // xMin
+            safeString16(properties.descent) + // yMin
+            '\x0F\xFF' + // xMax
+            safeString16(properties.ascent) + // yMax
+            string16(properties.italicAngle ? 2 : 0) + // macStyle
+            '\x00\x11' + // lowestRecPPEM
+            '\x00\x00' + // fontDirectionHint
+            '\x00\x00' + // indexToLocFormat
+            '\x00\x00');  // glyphDataFormat
 
-        var bl = Util.applyTransform([0, 0], inv);
-        var br = Util.applyTransform([0, height], inv);
-        var ul = Util.applyTransform([width, 0], inv);
-        var ur = Util.applyTransform([width, height], inv);
+      // Horizontal header
+      builder.addTable('hhea',
+            '\x00\x01\x00\x00' + // Version number
+            safeString16(properties.ascent) + // Typographic Ascent
+            safeString16(properties.descent) + // Typographic Descent
+            '\x00\x00' + // Line Gap
+            '\xFF\xFF' + // advanceWidthMax
+            '\x00\x00' + // minLeftSidebearing
+            '\x00\x00' + // minRightSidebearing
+            '\x00\x00' + // xMaxExtent
+            safeString16(properties.capHeight) + // caretSlopeRise
+            safeString16(Math.tan(properties.italicAngle) *
+                         properties.xHeight) + // caretSlopeRun
+            '\x00\x00' + // caretOffset
+            '\x00\x00' + // -reserved-
+            '\x00\x00' + // -reserved-
+            '\x00\x00' + // -reserved-
+            '\x00\x00' + // -reserved-
+            '\x00\x00' + // metricDataFormat
+            string16(numGlyphs)); // Number of HMetrics
 
-        var x0 = Math.min(bl[0], br[0], ul[0], ur[0]);
-        var y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
-        var x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
-        var y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
+      // Horizontal metrics
+      builder.addTable('hmtx', (function fontFieldsHmtx() {
+          var charstrings = font.charstrings;
+          var cffWidths = font.cff ? font.cff.widths : null;
+          var hmtx = '\x00\x00\x00\x00'; // Fake .notdef
+          for (var i = 1, ii = numGlyphs; i < ii; i++) {
+            var width = 0;
+            if (charstrings) {
+              var charstring = charstrings[i - 1];
+              width = 'width' in charstring ? charstring.width : 0;
+            } else if (cffWidths) {
+              width = Math.ceil(cffWidths[i] || 0);
+            }
+            hmtx += string16(width) + string16(0);
+          }
+          return hmtx;
+        })());
 
-        this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
-      } else {
-        // HACK to draw the gradient onto an infinite rectangle.
-        // PDF gradients are drawn across the entire image while
-        // Canvas only allows gradients to be drawn in a rectangle
-        // The following bug should allow us to remove this.
-        // https://bugzilla.mozilla.org/show_bug.cgi?id=664884
+      // Maximum profile
+      builder.addTable('maxp',
+            '\x00\x00\x50\x00' + // Version number
+            string16(numGlyphs)); // Num of glyphs
 
-        this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
-      }
+      // Naming tables
+      builder.addTable('name', createNameTable(fontName));
 
-      this.restore();
-    },
+      // PostScript informations
+      builder.addTable('post', createPostTable(properties));
 
-    // Images
-    beginInlineImage: function CanvasGraphics_beginInlineImage() {
-      error('Should not call beginInlineImage');
-    },
-    beginImageData: function CanvasGraphics_beginImageData() {
-      error('Should not call beginImageData');
+      return builder.toArray();
     },
 
-    paintFormXObjectBegin: function CanvasGraphics_paintFormXObjectBegin(matrix,
-                                                                        bbox) {
-      this.save();
-      this.baseTransformStack.push(this.baseTransform);
-
-      if (isArray(matrix) && 6 === matrix.length) {
-        this.transform.apply(this, matrix);
+    get spaceWidth() {
+      if ('_shadowWidth' in this) {
+        return this._shadowWidth;
       }
 
-      this.baseTransform = this.ctx.mozCurrentTransform;
-
-      if (isArray(bbox) && 4 === bbox.length) {
-        var width = bbox[2] - bbox[0];
-        var height = bbox[3] - bbox[1];
-        this.ctx.rect(bbox[0], bbox[1], width, height);
-        this.clip();
-        this.endPath();
+      // trying to estimate space character width
+      var possibleSpaceReplacements = ['space', 'minus', 'one', 'i'];
+      var width;
+      for (var i = 0, ii = possibleSpaceReplacements.length; i < ii; i++) {
+        var glyphName = possibleSpaceReplacements[i];
+        // if possible, getting width by glyph name
+        if (glyphName in this.widths) {
+          width = this.widths[glyphName];
+          break;
+        }
+        var glyphsUnicodeMap = getGlyphsUnicode();
+        var glyphUnicode = glyphsUnicodeMap[glyphName];
+        // finding the charcode via unicodeToCID map
+        var charcode = 0;
+        if (this.composite) {
+          if (this.cMap.contains(glyphUnicode)) {
+            charcode = this.cMap.lookup(glyphUnicode);
+          }
+        }
+        // ... via toUnicode map
+        if (!charcode && this.toUnicode) {
+          charcode = this.toUnicode.charCodeOf(glyphUnicode);
+        }
+        // setting it to unicode if negative or undefined
+        if (charcode <= 0) {
+          charcode = glyphUnicode;
+        }
+        // trying to get width via charcode
+        width = this.widths[charcode];
+        if (width) {
+          break; // the non-zero width found
+        }
       }
+      width = width || this.defaultWidth;
+      // Do not shadow the property here. See discussion:
+      // https://github.com/mozilla/pdf.js/pull/2127#discussion_r1662280
+      this._shadowWidth = width;
+      return width;
     },
 
-    paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() {
-      this.restore();
-      this.baseTransform = this.baseTransformStack.pop();
-    },
+    charToGlyph: function Font_charToGlyph(charcode, isSpace) {
+      var fontCharCode, width, operatorListId;
 
-    beginGroup: function CanvasGraphics_beginGroup(group) {
-      this.save();
-      var currentCtx = this.ctx;
-      // TODO non-isolated groups - according to Rik at adobe non-isolated
-      // group results aren't usually that different and they even have tools
-      // that ignore this setting. Notes from Rik on implmenting:
-      // - When you encounter an transparency group, create a new canvas with
-      // the dimensions of the bbox
-      // - copy the content from the previous canvas to the new canvas
-      // - draw as usual
-      // - remove the backdrop alpha:
-      // alphaNew = 1 - (1 - alpha)/(1 - alphaBackdrop) with 'alpha' the alpha
-      // value of your transparency group and 'alphaBackdrop' the alpha of the
-      // backdrop
-      // - remove background color:
-      // colorNew = color - alphaNew *colorBackdrop /(1 - alphaNew)
-      if (!group.isolated) {
-        info('TODO: Support non-isolated groups.');
+      var widthCode = charcode;
+      if (this.cMap && this.cMap.contains(charcode)) {
+        widthCode = this.cMap.lookup(charcode);
       }
+      width = this.widths[widthCode];
+      width = isNum(width) ? width : this.defaultWidth;
+      var vmetric = this.vmetrics && this.vmetrics[widthCode];
 
-      // TODO knockout - supposedly possible with the clever use of compositing
-      // modes.
-      if (group.knockout) {
-        warn('Knockout groups not supported.');
+      var unicode = this.toUnicode.get(charcode) || charcode;
+      if (typeof unicode === 'number') {
+        unicode = String.fromCharCode(unicode);
       }
 
-      var currentTransform = currentCtx.mozCurrentTransform;
-      if (group.matrix) {
-        currentCtx.transform.apply(currentCtx, group.matrix);
+      var isInFont = charcode in this.toFontChar;
+      // First try the toFontChar map, if it's not there then try falling
+      // back to the char code.
+      fontCharCode = this.toFontChar[charcode] || charcode;
+      if (this.missingFile) {
+        fontCharCode = mapSpecialUnicodeValues(fontCharCode);
       }
-      assert(group.bbox, 'Bounding box is required.');
 
-      // Based on the current transform figure out how big the bounding box
-      // will actually be.
-      var bounds = Util.getAxialAlignedBoundingBox(
-                    group.bbox,
-                    currentCtx.mozCurrentTransform);
-      // Clip the bounding box to the current canvas.
-      var canvasBounds = [0,
-                          0,
-                          currentCtx.canvas.width,
-                          currentCtx.canvas.height];
-      bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0];
-      // Use ceil in case we're between sizes so we don't create canvas that is
-      // too small and make the canvas at least 1x1 pixels.
-      var offsetX = Math.floor(bounds[0]);
-      var offsetY = Math.floor(bounds[1]);
-      var drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1);
-      var drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1);
-      var scaleX = 1, scaleY = 1;
-      if (drawnWidth > MAX_GROUP_SIZE) {
-        scaleX = drawnWidth / MAX_GROUP_SIZE;
-        drawnWidth = MAX_GROUP_SIZE;
-      }
-      if (drawnHeight > MAX_GROUP_SIZE) {
-        scaleY = drawnHeight / MAX_GROUP_SIZE;
-        drawnHeight = MAX_GROUP_SIZE;
+      if (this.isType3Font) {
+        // Font char code in this case is actually a glyph name.
+        operatorListId = fontCharCode;
       }
 
-      var cacheId = 'groupAt' + this.groupLevel;
-      if (group.smask) {
-        // Using two cache entries is case if masks are used one after another.
-        cacheId +=  '_smask_' + ((this.smaskCounter++) % 2);
+      var accent = null;
+      if (this.seacMap && this.seacMap[charcode]) {
+        isInFont = true;
+        var seac = this.seacMap[charcode];
+        fontCharCode = seac.baseFontCharCode;
+        accent = {
+          fontChar: String.fromCharCode(seac.accentFontCharCode),
+          offset: seac.accentOffset
+        };
       }
-      var scratchCanvas = this.cachedCanvases.getCanvas(
-        cacheId, drawnWidth, drawnHeight, true);
-      var groupCtx = scratchCanvas.context;
 
-      // Since we created a new canvas that is just the size of the bounding box
-      // we have to translate the group ctx.
-      groupCtx.scale(1 / scaleX, 1 / scaleY);
-      groupCtx.translate(-offsetX, -offsetY);
-      groupCtx.transform.apply(groupCtx, currentTransform);
+      var fontChar = String.fromCharCode(fontCharCode);
 
-      if (group.smask) {
-        // Saving state and cached mask to be used in setGState.
-        this.smaskStack.push({
-          canvas: scratchCanvas.canvas,
-          context: groupCtx,
-          offsetX: offsetX,
-          offsetY: offsetY,
-          scaleX: scaleX,
-          scaleY: scaleY,
-          subtype: group.smask.subtype,
-          backdrop: group.smask.backdrop,
-          transferMap: group.smask.transferMap || null
-        });
-      } else {
-        // Setup the current ctx so when the group is popped we draw it at the
-        // right location.
-        currentCtx.setTransform(1, 0, 0, 1, 0, 0);
-        currentCtx.translate(offsetX, offsetY);
-        currentCtx.scale(scaleX, scaleY);
+      var glyph = this.glyphCache[charcode];
+      if (!glyph ||
+          !glyph.matchesForCache(fontChar, unicode, accent, width, vmetric,
+                                 operatorListId, isSpace, isInFont)) {
+        glyph = new Glyph(fontChar, unicode, accent, width, vmetric,
+                          operatorListId, isSpace, isInFont);
+        this.glyphCache[charcode] = glyph;
       }
-      // The transparency group inherits all off the current graphics state
-      // except the blend mode, soft mask, and alpha constants.
-      copyCtxState(currentCtx, groupCtx);
-      this.ctx = groupCtx;
-      this.setGState([
-        ['BM', 'Normal'],
-        ['ca', 1],
-        ['CA', 1]
-      ]);
-      this.groupStack.push(currentCtx);
-      this.groupLevel++;
+      return glyph;
     },
 
-    endGroup: function CanvasGraphics_endGroup(group) {
-      this.groupLevel--;
-      var groupCtx = this.ctx;
-      this.ctx = this.groupStack.pop();
-      // Turn off image smoothing to avoid sub pixel interpolation which can
-      // look kind of blurry for some pdfs.
-      if (this.ctx.imageSmoothingEnabled !== undefined) {
-        this.ctx.imageSmoothingEnabled = false;
-      } else {
-        this.ctx.mozImageSmoothingEnabled = false;
+    charsToGlyphs: function Font_charsToGlyphs(chars) {
+      var charsCache = this.charsCache;
+      var glyphs, glyph, charcode;
+
+      // if we translated this string before, just grab it from the cache
+      if (charsCache) {
+        glyphs = charsCache[chars];
+        if (glyphs) {
+          return glyphs;
+        }
       }
-      if (group.smask) {
-        this.tempSMask = this.smaskStack.pop();
-      } else {
-        this.ctx.drawImage(groupCtx.canvas, 0, 0);
+
+      // lazily create the translation cache
+      if (!charsCache) {
+        charsCache = this.charsCache = Object.create(null);
       }
-      this.restore();
-    },
 
-    beginAnnotations: function CanvasGraphics_beginAnnotations() {
-      this.save();
-      this.current = new CanvasExtraState();
+      glyphs = [];
+      var charsCacheKey = chars;
+      var i = 0, ii;
 
-      if (this.baseTransform) {
-        this.ctx.setTransform.apply(this.ctx, this.baseTransform);
+      if (this.cMap) {
+        // composite fonts have multi-byte strings convert the string from
+        // single-byte to multi-byte
+        var c = Object.create(null);
+        while (i < chars.length) {
+          this.cMap.readCharCode(chars, i, c);
+          charcode = c.charcode;
+          var length = c.length;
+          i += length;
+          // Space is char with code 0x20 and length 1 in multiple-byte codes.
+          var isSpace = length === 1 && chars.charCodeAt(i - 1) === 0x20;
+          glyph = this.charToGlyph(charcode, isSpace);
+          glyphs.push(glyph);
+        }
+      } else {
+        for (i = 0, ii = chars.length; i < ii; ++i) {
+          charcode = chars.charCodeAt(i);
+          glyph = this.charToGlyph(charcode, charcode === 0x20);
+          glyphs.push(glyph);
+        }
       }
-    },
 
-    endAnnotations: function CanvasGraphics_endAnnotations() {
-      this.restore();
-    },
+      // Enter the translated string into the cache
+      return (charsCache[charsCacheKey] = glyphs);
+    }
+  };
 
-    beginAnnotation: function CanvasGraphics_beginAnnotation(rect, transform,
-                                                             matrix) {
-      this.save();
+  return Font;
+})();
 
-      if (isArray(rect) && 4 === rect.length) {
-        var width = rect[2] - rect[0];
-        var height = rect[3] - rect[1];
-        this.ctx.rect(rect[0], rect[1], width, height);
-        this.clip();
-        this.endPath();
-      }
+var ErrorFont = (function ErrorFontClosure() {
+  function ErrorFont(error) {
+    this.error = error;
+    this.loadedName = 'g_font_error';
+    this.loading = false;
+  }
 
-      this.transform.apply(this, transform);
-      this.transform.apply(this, matrix);
+  ErrorFont.prototype = {
+    charsToGlyphs: function ErrorFont_charsToGlyphs() {
+      return [];
     },
+    exportData: function ErrorFont_exportData() {
+      return {error: this.error};
+    }
+  };
 
-    endAnnotation: function CanvasGraphics_endAnnotation() {
-      this.restore();
-    },
+  return ErrorFont;
+})();
 
-    paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) {
-      var domImage = this.objs.get(objId);
-      if (!domImage) {
-        warn('Dependent image isn\'t ready yet');
-        return;
+/**
+ * Shared logic for building a char code to glyph id mapping for Type1 and
+ * simple CFF fonts. See section 9.6.6.2 of the spec.
+ * @param {Object} properties Font properties object.
+ * @param {Object} builtInEncoding The encoding contained within the actual font
+ * data.
+ * @param {Array} Array of glyph names where the index is the glyph ID.
+ * @returns {Object} A char code to glyph ID map.
+ */
+function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) {
+  var charCodeToGlyphId = Object.create(null);
+  var glyphId, charCode, baseEncoding;
+
+  if (properties.baseEncodingName) {
+    // If a valid base encoding name was used, the mapping is initialized with
+    // that.
+    baseEncoding = getEncoding(properties.baseEncodingName);
+    for (charCode = 0; charCode < baseEncoding.length; charCode++) {
+      glyphId = glyphNames.indexOf(baseEncoding[charCode]);
+      if (glyphId >= 0) {
+        charCodeToGlyphId[charCode] = glyphId;
+      } else {
+        charCodeToGlyphId[charCode] = 0; // notdef
+      }
+    }
+  } else if (!!(properties.flags & FontFlags.Symbolic)) {
+    // For a symbolic font the encoding should be the fonts built-in
+    // encoding.
+    for (charCode in builtInEncoding) {
+      charCodeToGlyphId[charCode] = builtInEncoding[charCode];
+    }
+  } else {
+    // For non-symbolic fonts that don't have a base encoding the standard
+    // encoding should be used.
+    baseEncoding = StandardEncoding;
+    for (charCode = 0; charCode < baseEncoding.length; charCode++) {
+      glyphId = glyphNames.indexOf(baseEncoding[charCode]);
+      if (glyphId >= 0) {
+        charCodeToGlyphId[charCode] = glyphId;
+      } else {
+        charCodeToGlyphId[charCode] = 0; // notdef
       }
+    }
+  }
 
-      this.save();
+  // Lastly, merge in the differences.
+  var differences = properties.differences;
+  if (differences) {
+    for (charCode in differences) {
+      var glyphName = differences[charCode];
+      glyphId = glyphNames.indexOf(glyphName);
+      if (glyphId >= 0) {
+        charCodeToGlyphId[charCode] = glyphId;
+      } else {
+        charCodeToGlyphId[charCode] = 0; // notdef
+      }
+    }
+  }
+  return charCodeToGlyphId;
+}
 
-      var ctx = this.ctx;
-      // scale the image to the unit square
-      ctx.scale(1 / w, -1 / h);
+// Type1Font is also a CIDFontType0.
+var Type1Font = (function Type1FontClosure() {
+  function findBlock(streamBytes, signature, startIndex) {
+    var streamBytesLength = streamBytes.length;
+    var signatureLength = signature.length;
+    var scanLength = streamBytesLength - signatureLength;
 
-      ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height,
-                    0, -h, w, h);
-      if (this.imageLayer) {
-        var currentTransform = ctx.mozCurrentTransformInverse;
-        var position = this.getCanvasPosition(0, 0);
-        this.imageLayer.appendImage({
-          objId: objId,
-          left: position[0],
-          top: position[1],
-          width: w / currentTransform[0],
-          height: h / currentTransform[3]
-        });
+    var i = startIndex, j, found = false;
+    while (i < scanLength) {
+      j = 0;
+      while (j < signatureLength && streamBytes[i + j] === signature[j]) {
+        j++;
       }
-      this.restore();
-    },
-
-    paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) {
-      var ctx = this.ctx;
-      var width = img.width, height = img.height;
-      var fillColor = this.current.fillColor;
-      var isPatternFill = this.current.patternFill;
+      if (j >= signatureLength) { // `signature` found, skip over whitespace.
+        i += j;
+        while (i < streamBytesLength && Lexer.isSpace(streamBytes[i])) {
+          i++;
+        }
+        found = true;
+        break;
+      }
+      i++;
+    }
+    return {
+      found: found,
+      length: i,
+    };
+  }
 
-      var glyph = this.processingType3;
+  function getHeaderBlock(stream, suggestedLength) {
+    var EEXEC_SIGNATURE = [0x65, 0x65, 0x78, 0x65, 0x63];
 
-      if (COMPILE_TYPE3_GLYPHS && glyph && glyph.compiled === undefined) {
-        if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) {
-          glyph.compiled =
-            compileType3Glyph({data: img.data, width: width, height: height});
-        } else {
-          glyph.compiled = null;
-        }
+    var streamStartPos = stream.pos; // Save the initial stream position.
+    var headerBytes, headerBytesLength, block;
+    try {
+      headerBytes = stream.getBytes(suggestedLength);
+      headerBytesLength = headerBytes.length;
+    } catch (ex) {
+      if (ex instanceof MissingDataException) {
+        throw ex;
       }
+      // Ignore errors if the `suggestedLength` is huge enough that a Uint8Array
+      // cannot hold the result of `getBytes`, and fallback to simply checking
+      // the entire stream (fixes issue3928.pdf).
+    }
 
-      if (glyph && glyph.compiled) {
-        glyph.compiled(ctx);
-        return;
+    if (headerBytesLength === suggestedLength) {
+      // Most of the time `suggestedLength` is correct, so to speed things up we
+      // initially only check the last few bytes to see if the header was found.
+      // Otherwise we (potentially) check the entire stream to prevent errors in
+      // `Type1Parser` (fixes issue5686.pdf).
+      block = findBlock(headerBytes, EEXEC_SIGNATURE,
+                        suggestedLength - 2 * EEXEC_SIGNATURE.length);
+
+      if (block.found && block.length === suggestedLength) {
+        return {
+          stream: new Stream(headerBytes),
+          length: suggestedLength,
+        };
       }
+    }
+    warn('Invalid "Length1" property in Type1 font -- trying to recover.');
+    stream.pos = streamStartPos; // Reset the stream position.
 
-      var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas',
-                                                     width, height);
-      var maskCtx = maskCanvas.context;
-      maskCtx.save();
+    var SCAN_BLOCK_LENGTH = 2048;
+    var actualLength;
+    while (true) {
+      var scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH);
+      block = findBlock(scanBytes, EEXEC_SIGNATURE, 0);
 
-      putBinaryImageMask(maskCtx, img);
+      if (block.length === 0) {
+        break;
+      }
+      stream.pos += block.length; // Update the stream position.
 
-      maskCtx.globalCompositeOperation = 'source-in';
+      if (block.found) {
+        actualLength = stream.pos - streamStartPos;
+        break;
+      }
+    }
+    stream.pos = streamStartPos; // Reset the stream position.
 
-      maskCtx.fillStyle = isPatternFill ?
-                          fillColor.getPattern(maskCtx, this) : fillColor;
-      maskCtx.fillRect(0, 0, width, height);
+    if (actualLength) {
+      return {
+        stream: new Stream(stream.getBytes(actualLength)),
+        length: actualLength,
+      };
+    }
+    warn('Unable to recover "Length1" property in Type1 font -- using as is.');
+    return {
+      stream: new Stream(stream.getBytes(suggestedLength)),
+      length: suggestedLength,
+    };
+  }
 
-      maskCtx.restore();
+  function getEexecBlock(stream, suggestedLength) {
+    // We should ideally parse the eexec block to ensure that `suggestedLength`
+    // is correct, so we don't truncate the block data if it's too small.
+    // However, this would also require checking if the fixed-content portion
+    // exists (using the 'Length3' property), and ensuring that it's valid.
+    //
+    // Given that `suggestedLength` almost always is correct, all the validation
+    // would require a great deal of unnecessary parsing for most fonts.
+    // To save time, we always fetch the entire stream instead, which also avoid
+    // issues if `suggestedLength` is huge (see comment in `getHeaderBlock`).
+    //
+    // NOTE: This means that the function can include the fixed-content portion
+    // in the returned eexec block. In practice this does *not* seem to matter,
+    // since `Type1Parser_extractFontProgram` will skip over any non-commands.
+    var eexecBytes = stream.getBytes();
+    return {
+      stream: new Stream(eexecBytes),
+      length: eexecBytes.length,
+    };
+  }
 
-      this.paintInlineImageXObject(maskCanvas.canvas);
-    },
+  function Type1Font(name, file, properties) {
+    // Some bad generators embed pfb file as is, we have to strip 6-byte header.
+    // Also, length1 and length2 might be off by 6 bytes as well.
+    // http://www.math.ubc.ca/~cass/piscript/type1.pdf
+    var PFB_HEADER_SIZE = 6;
+    var headerBlockLength = properties.length1;
+    var eexecBlockLength = properties.length2;
+    var pfbHeader = file.peekBytes(PFB_HEADER_SIZE);
+    var pfbHeaderPresent = pfbHeader[0] === 0x80 && pfbHeader[1] === 0x01;
+    if (pfbHeaderPresent) {
+      file.skip(PFB_HEADER_SIZE);
+      headerBlockLength = (pfbHeader[5] << 24) | (pfbHeader[4] << 16) |
+                          (pfbHeader[3] << 8) | pfbHeader[2];
+    }
 
-    paintImageMaskXObjectRepeat:
-      function CanvasGraphics_paintImageMaskXObjectRepeat(imgData, scaleX,
-                                                          scaleY, positions) {
-      var width = imgData.width;
-      var height = imgData.height;
-      var fillColor = this.current.fillColor;
-      var isPatternFill = this.current.patternFill;
+    // Get the data block containing glyphs and subrs informations
+    var headerBlock = getHeaderBlock(file, headerBlockLength);
+    headerBlockLength = headerBlock.length;
+    var headerBlockParser = new Type1Parser(headerBlock.stream, false,
+                                            SEAC_ANALYSIS_ENABLED);
+    headerBlockParser.extractFontHeader(properties);
 
-      var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas',
-                                                     width, height);
-      var maskCtx = maskCanvas.context;
-      maskCtx.save();
+    if (pfbHeaderPresent) {
+      pfbHeader = file.getBytes(PFB_HEADER_SIZE);
+      eexecBlockLength = (pfbHeader[5] << 24) | (pfbHeader[4] << 16) |
+                         (pfbHeader[3] << 8) | pfbHeader[2];
+    }
 
-      putBinaryImageMask(maskCtx, imgData);
+    // Decrypt the data blocks and retrieve it's content
+    var eexecBlock = getEexecBlock(file, eexecBlockLength);
+    eexecBlockLength = eexecBlock.length;
+    var eexecBlockParser = new Type1Parser(eexecBlock.stream, true,
+                                           SEAC_ANALYSIS_ENABLED);
+    var data = eexecBlockParser.extractFontProgram();
+    for (var info in data.properties) {
+      properties[info] = data.properties[info];
+    }
 
-      maskCtx.globalCompositeOperation = 'source-in';
+    var charstrings = data.charstrings;
+    var type2Charstrings = this.getType2Charstrings(charstrings);
+    var subrs = this.getType2Subrs(data.subrs);
 
-      maskCtx.fillStyle = isPatternFill ?
-                          fillColor.getPattern(maskCtx, this) : fillColor;
-      maskCtx.fillRect(0, 0, width, height);
+    this.charstrings = charstrings;
+    this.data = this.wrap(name, type2Charstrings, this.charstrings,
+                          subrs, properties);
+    this.seacs = this.getSeacs(data.charstrings);
+  }
 
-      maskCtx.restore();
+  Type1Font.prototype = {
+    get numGlyphs() {
+      return this.charstrings.length + 1;
+    },
 
-      var ctx = this.ctx;
-      for (var i = 0, ii = positions.length; i < ii; i += 2) {
-        ctx.save();
-        ctx.transform(scaleX, 0, 0, scaleY, positions[i], positions[i + 1]);
-        ctx.scale(1, -1);
-        ctx.drawImage(maskCanvas.canvas, 0, 0, width, height,
-          0, -1, 1, 1);
-        ctx.restore();
+    getCharset: function Type1Font_getCharset() {
+      var charset = ['.notdef'];
+      var charstrings = this.charstrings;
+      for (var glyphId = 0; glyphId < charstrings.length; glyphId++) {
+        charset.push(charstrings[glyphId].glyphName);
       }
+      return charset;
     },
 
-    paintImageMaskXObjectGroup:
-      function CanvasGraphics_paintImageMaskXObjectGroup(images) {
-      var ctx = this.ctx;
-
-      var fillColor = this.current.fillColor;
-      var isPatternFill = this.current.patternFill;
-      for (var i = 0, ii = images.length; i < ii; i++) {
-        var image = images[i];
-        var width = image.width, height = image.height;
-
-        var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas',
-                                                       width, height);
-        var maskCtx = maskCanvas.context;
-        maskCtx.save();
-
-        putBinaryImageMask(maskCtx, image);
-
-        maskCtx.globalCompositeOperation = 'source-in';
-
-        maskCtx.fillStyle = isPatternFill ?
-                            fillColor.getPattern(maskCtx, this) : fillColor;
-        maskCtx.fillRect(0, 0, width, height);
+    getGlyphMapping: function Type1Font_getGlyphMapping(properties) {
+      var charstrings = this.charstrings;
+      var glyphNames = ['.notdef'], glyphId;
+      for (glyphId = 0; glyphId < charstrings.length; glyphId++) {
+        glyphNames.push(charstrings[glyphId].glyphName);
+      }
+      var encoding = properties.builtInEncoding;
+      if (encoding) {
+        var builtInEncoding = Object.create(null);
+        for (var charCode in encoding) {
+          glyphId = glyphNames.indexOf(encoding[charCode]);
+          if (glyphId >= 0) {
+            builtInEncoding[charCode] = glyphId;
+          }
+        }
+      }
 
-        maskCtx.restore();
+      return type1FontGlyphMapping(properties, builtInEncoding, glyphNames);
+    },
 
-        ctx.save();
-        ctx.transform.apply(ctx, image.transform);
-        ctx.scale(1, -1);
-        ctx.drawImage(maskCanvas.canvas, 0, 0, width, height,
-                      0, -1, 1, 1);
-        ctx.restore();
+    getSeacs: function Type1Font_getSeacs(charstrings) {
+      var i, ii;
+      var seacMap = [];
+      for (i = 0, ii = charstrings.length; i < ii; i++) {
+        var charstring = charstrings[i];
+        if (charstring.seac) {
+          // Offset by 1 for .notdef
+          seacMap[i + 1] = charstring.seac;
+        }
       }
+      return seacMap;
     },
 
-    paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
-      var imgData = this.objs.get(objId);
-      if (!imgData) {
-        warn('Dependent image isn\'t ready yet');
-        return;
+    getType2Charstrings: function Type1Font_getType2Charstrings(
+                                    type1Charstrings) {
+      var type2Charstrings = [];
+      for (var i = 0, ii = type1Charstrings.length; i < ii; i++) {
+        type2Charstrings.push(type1Charstrings[i].charstring);
       }
-
-      this.paintInlineImageXObject(imgData);
+      return type2Charstrings;
     },
 
-    paintImageXObjectRepeat:
-      function CanvasGraphics_paintImageXObjectRepeat(objId, scaleX, scaleY,
-                                                          positions) {
-      var imgData = this.objs.get(objId);
-      if (!imgData) {
-        warn('Dependent image isn\'t ready yet');
-        return;
+    getType2Subrs: function Type1Font_getType2Subrs(type1Subrs) {
+      var bias = 0;
+      var count = type1Subrs.length;
+      if (count < 1133) {
+        bias = 107;
+      } else if (count < 33769) {
+        bias = 1131;
+      } else {
+        bias = 32768;
       }
 
-      var width = imgData.width;
-      var height = imgData.height;
-      var map = [];
-      for (var i = 0, ii = positions.length; i < ii; i += 2) {
-        map.push({transform: [scaleX, 0, 0, scaleY, positions[i],
-                 positions[i + 1]], x: 0, y: 0, w: width, h: height});
+      // Add a bunch of empty subrs to deal with the Type2 bias
+      var type2Subrs = [];
+      var i;
+      for (i = 0; i < bias; i++) {
+        type2Subrs.push([0x0B]);
       }
-      this.paintInlineImageXObjectGroup(imgData, map);
+
+      for (i = 0; i < count; i++) {
+        type2Subrs.push(type1Subrs[i]);
+      }
+
+      return type2Subrs;
     },
 
-    paintInlineImageXObject:
-      function CanvasGraphics_paintInlineImageXObject(imgData) {
-      var width = imgData.width;
-      var height = imgData.height;
-      var ctx = this.ctx;
+    wrap: function Type1Font_wrap(name, glyphs, charstrings, subrs,
+                                  properties) {
+      var cff = new CFF();
+      cff.header = new CFFHeader(1, 0, 4, 4);
 
-      this.save();
-      // scale the image to the unit square
-      ctx.scale(1 / width, -1 / height);
+      cff.names = [name];
 
-      var currentTransform = ctx.mozCurrentTransformInverse;
-      var a = currentTransform[0], b = currentTransform[1];
-      var widthScale = Math.max(Math.sqrt(a * a + b * b), 1);
-      var c = currentTransform[2], d = currentTransform[3];
-      var heightScale = Math.max(Math.sqrt(c * c + d * d), 1);
+      var topDict = new CFFTopDict();
+      // CFF strings IDs 0...390 are predefined names, so refering
+      // to entries in our own String INDEX starts at SID 391.
+      topDict.setByName('version', 391);
+      topDict.setByName('Notice', 392);
+      topDict.setByName('FullName', 393);
+      topDict.setByName('FamilyName', 394);
+      topDict.setByName('Weight', 395);
+      topDict.setByName('Encoding', null); // placeholder
+      topDict.setByName('FontMatrix', properties.fontMatrix);
+      topDict.setByName('FontBBox', properties.bbox);
+      topDict.setByName('charset', null); // placeholder
+      topDict.setByName('CharStrings', null); // placeholder
+      topDict.setByName('Private', null); // placeholder
+      cff.topDict = topDict;
 
-      var imgToPaint, tmpCanvas;
-      // instanceof HTMLElement does not work in jsdom node.js module
-      if (imgData instanceof HTMLElement || !imgData.data) {
-        imgToPaint = imgData;
-      } else {
-        tmpCanvas = this.cachedCanvases.getCanvas('inlineImage',
-                                                  width, height);
-        var tmpCtx = tmpCanvas.context;
-        putBinaryImageData(tmpCtx, imgData);
-        imgToPaint = tmpCanvas.canvas;
-      }
+      var strings = new CFFStrings();
+      strings.add('Version 0.11'); // Version
+      strings.add('See original notice'); // Notice
+      strings.add(name); // FullName
+      strings.add(name); // FamilyName
+      strings.add('Medium'); // Weight
+      cff.strings = strings;
 
-      var paintWidth = width, paintHeight = height;
-      var tmpCanvasId = 'prescale1';
-      // Vertial or horizontal scaling shall not be more than 2 to not loose the
-      // pixels during drawImage operation, painting on the temporary canvas(es)
-      // that are twice smaller in size
-      while ((widthScale > 2 && paintWidth > 1) ||
-             (heightScale > 2 && paintHeight > 1)) {
-        var newWidth = paintWidth, newHeight = paintHeight;
-        if (widthScale > 2 && paintWidth > 1) {
-          newWidth = Math.ceil(paintWidth / 2);
-          widthScale /= paintWidth / newWidth;
-        }
-        if (heightScale > 2 && paintHeight > 1) {
-          newHeight = Math.ceil(paintHeight / 2);
-          heightScale /= paintHeight / newHeight;
+      cff.globalSubrIndex = new CFFIndex();
+
+      var count = glyphs.length;
+      var charsetArray = [0];
+      var i, ii;
+      for (i = 0; i < count; i++) {
+        var index = CFFStandardStrings.indexOf(charstrings[i].glyphName);
+        // TODO: Insert the string and correctly map it.  Previously it was
+        // thought mapping names that aren't in the standard strings to .notdef
+        // was fine, however in issue818 when mapping them all to .notdef the
+        // adieresis glyph no longer worked.
+        if (index === -1) {
+          index = 0;
         }
-        tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId,
-                                                  newWidth, newHeight);
-        tmpCtx = tmpCanvas.context;
-        tmpCtx.clearRect(0, 0, newWidth, newHeight);
-        tmpCtx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight,
-                                     0, 0, newWidth, newHeight);
-        imgToPaint = tmpCanvas.canvas;
-        paintWidth = newWidth;
-        paintHeight = newHeight;
-        tmpCanvasId = tmpCanvasId === 'prescale1' ? 'prescale2' : 'prescale1';
+        charsetArray.push((index >> 8) & 0xff, index & 0xff);
       }
-      ctx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight,
-                                0, -height, width, height);
+      cff.charset = new CFFCharset(false, 0, [], charsetArray);
 
-      if (this.imageLayer) {
-        var position = this.getCanvasPosition(0, -height);
-        this.imageLayer.appendImage({
-          imgData: imgData,
-          left: position[0],
-          top: position[1],
-          width: width / currentTransform[0],
-          height: height / currentTransform[3]
-        });
+      var charStringsIndex = new CFFIndex();
+      charStringsIndex.add([0x8B, 0x0E]); // .notdef
+      for (i = 0; i < count; i++) {
+        charStringsIndex.add(glyphs[i]);
       }
-      this.restore();
-    },
-
-    paintInlineImageXObjectGroup:
-      function CanvasGraphics_paintInlineImageXObjectGroup(imgData, map) {
-      var ctx = this.ctx;
-      var w = imgData.width;
-      var h = imgData.height;
-
-      var tmpCanvas = this.cachedCanvases.getCanvas('inlineImage', w, h);
-      var tmpCtx = tmpCanvas.context;
-      putBinaryImageData(tmpCtx, imgData);
+      cff.charStrings = charStringsIndex;
 
-      for (var i = 0, ii = map.length; i < ii; i++) {
-        var entry = map[i];
-        ctx.save();
-        ctx.transform.apply(ctx, entry.transform);
-        ctx.scale(1, -1);
-        ctx.drawImage(tmpCanvas.canvas, entry.x, entry.y, entry.w, entry.h,
-                      0, -1, 1, 1);
-        if (this.imageLayer) {
-          var position = this.getCanvasPosition(entry.x, entry.y);
-          this.imageLayer.appendImage({
-            imgData: imgData,
-            left: position[0],
-            top: position[1],
-            width: w,
-            height: h
-          });
+      var privateDict = new CFFPrivateDict();
+      privateDict.setByName('Subrs', null); // placeholder
+      var fields = [
+        'BlueValues',
+        'OtherBlues',
+        'FamilyBlues',
+        'FamilyOtherBlues',
+        'StemSnapH',
+        'StemSnapV',
+        'BlueShift',
+        'BlueFuzz',
+        'BlueScale',
+        'LanguageGroup',
+        'ExpansionFactor',
+        'ForceBold',
+        'StdHW',
+        'StdVW'
+      ];
+      for (i = 0, ii = fields.length; i < ii; i++) {
+        var field = fields[i];
+        if (!(field in properties.privateData)) {
+          continue;
         }
-        ctx.restore();
+        var value = properties.privateData[field];
+        if (isArray(value)) {
+          // All of the private dictionary array data in CFF must be stored as
+          // "delta-encoded" numbers.
+          for (var j = value.length - 1; j > 0; j--) {
+            value[j] -= value[j - 1]; // ... difference from previous value
+          }
+        }
+        privateDict.setByName(field, value);
       }
-    },
+      cff.topDict.privateDict = privateDict;
 
-    paintSolidColorImageMask:
-      function CanvasGraphics_paintSolidColorImageMask() {
-        this.ctx.fillRect(0, 0, 1, 1);
-    },
+      var subrIndex = new CFFIndex();
+      for (i = 0, ii = subrs.length; i < ii; i++) {
+        subrIndex.add(subrs[i]);
+      }
+      privateDict.subrsIndex = subrIndex;
 
-    paintXObject: function CanvasGraphics_paintXObject() {
-      warn('Unsupported \'paintXObject\' command.');
-    },
+      var compiler = new CFFCompiler(cff);
+      return compiler.compile();
+    }
+  };
 
-    // Marked content
+  return Type1Font;
+})();
 
-    markPoint: function CanvasGraphics_markPoint(tag) {
-      // TODO Marked content.
-    },
-    markPointProps: function CanvasGraphics_markPointProps(tag, properties) {
-      // TODO Marked content.
-    },
-    beginMarkedContent: function CanvasGraphics_beginMarkedContent(tag) {
-      // TODO Marked content.
-    },
-    beginMarkedContentProps: function CanvasGraphics_beginMarkedContentProps(
-                                        tag, properties) {
-      // TODO Marked content.
-    },
-    endMarkedContent: function CanvasGraphics_endMarkedContent() {
-      // TODO Marked content.
-    },
+var CFFFont = (function CFFFontClosure() {
+  function CFFFont(file, properties) {
+    this.properties = properties;
 
-    // Compatibility
+    var parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED);
+    this.cff = parser.parse();
+    var compiler = new CFFCompiler(this.cff);
+    this.seacs = this.cff.seacs;
+    try {
+      this.data = compiler.compile();
+    } catch (e) {
+      warn('Failed to compile font ' + properties.loadedName);
+      // There may have just been an issue with the compiler, set the data
+      // anyway and hope the font loaded.
+      this.data = file;
+    }
+  }
 
-    beginCompat: function CanvasGraphics_beginCompat() {
-      // TODO ignore undefined operators (should we do that anyway?)
+  CFFFont.prototype = {
+    get numGlyphs() {
+      return this.cff.charStrings.count;
     },
-    endCompat: function CanvasGraphics_endCompat() {
-      // TODO stop ignoring undefined operators
+    getCharset: function CFFFont_getCharset() {
+      return this.cff.charset.charset;
     },
+    getGlyphMapping: function CFFFont_getGlyphMapping() {
+      var cff = this.cff;
+      var properties = this.properties;
+      var charsets = cff.charset.charset;
+      var charCodeToGlyphId;
+      var glyphId;
 
-    // Helper functions
-
-    consumePath: function CanvasGraphics_consumePath() {
-      var ctx = this.ctx;
-      if (this.pendingClip) {
-        if (this.pendingClip === EO_CLIP) {
-          if (ctx.mozFillRule !== undefined) {
-            ctx.mozFillRule = 'evenodd';
-            ctx.clip();
-            ctx.mozFillRule = 'nonzero';
-          } else {
-            ctx.clip('evenodd');
+      if (properties.composite) {
+        charCodeToGlyphId = Object.create(null);
+        if (cff.isCIDFont) {
+          // If the font is actually a CID font then we should use the charset
+          // to map CIDs to GIDs.
+          for (glyphId = 0; glyphId < charsets.length; glyphId++) {
+            var cid = charsets[glyphId];
+            var charCode = properties.cMap.charCodeOf(cid);
+            charCodeToGlyphId[charCode] = glyphId;
           }
         } else {
-          ctx.clip();
+          // If it is NOT actually a CID font then CIDs should be mapped
+          // directly to GIDs.
+          for (glyphId = 0; glyphId < cff.charStrings.count; glyphId++) {
+            charCodeToGlyphId[glyphId] = glyphId;
+          }
         }
-        this.pendingClip = null;
-      }
-      ctx.beginPath();
-    },
-    getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) {
-      if (this.cachedGetSinglePixelWidth === null) {
-        var inverse = this.ctx.mozCurrentTransformInverse;
-        // max of the current horizontal and vertical scale
-        this.cachedGetSinglePixelWidth = Math.sqrt(Math.max(
-          (inverse[0] * inverse[0] + inverse[1] * inverse[1]),
-          (inverse[2] * inverse[2] + inverse[3] * inverse[3])));
+        return charCodeToGlyphId;
       }
-      return this.cachedGetSinglePixelWidth;
-    },
-    getCanvasPosition: function CanvasGraphics_getCanvasPosition(x, y) {
-      var transform = this.ctx.mozCurrentTransform;
-      return [
-        transform[0] * x + transform[2] * y + transform[4],
-        transform[1] * x + transform[3] * y + transform[5]
-      ];
+
+      var encoding = cff.encoding ? cff.encoding.encoding : null;
+      charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets);
+      return charCodeToGlyphId;
     }
   };
 
-  for (var op in OPS) {
-    CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op];
+  return CFFFont;
+})();
+
+// Workaround for seac on Windows.
+(function checkSeacSupport() {
+  if (typeof navigator !== 'undefined' && /Windows/.test(navigator.userAgent)) {
+    SEAC_ANALYSIS_ENABLED = true;
   }
+})();
 
-  return CanvasGraphics;
+// Workaround for Private Use Area characters in Chrome on Windows
+// http://code.google.com/p/chromium/issues/detail?id=122465
+// https://github.com/mozilla/pdf.js/issues/1689
+(function checkChromeWindows() {
+  if (typeof navigator !== 'undefined' &&
+      /Windows.*Chrome/.test(navigator.userAgent)) {
+    SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = true;
+  }
 })();
 
-exports.CanvasGraphics = CanvasGraphics;
-exports.createScratchCanvas = createScratchCanvas;
+exports.SEAC_ANALYSIS_ENABLED = SEAC_ANALYSIS_ENABLED;
+exports.ErrorFont = ErrorFont;
+exports.Font = Font;
+exports.FontFlags = FontFlags;
+exports.IdentityToUnicodeMap = IdentityToUnicodeMap;
+exports.ToUnicodeMap = ToUnicodeMap;
+exports.getFontType = getFontType;
 }));
 
 

+ 2 - 2
build/pdf.js

@@ -28,8 +28,8 @@ factory((root.pdfjsDistBuildPdf = {}));
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.4.190';
-var pdfjsBuild = '34aa915';
+var pdfjsVersion = '1.4.192';
+var pdfjsBuild = '2dd03e1';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?

+ 921 - 221
build/pdf.worker.js

@@ -28,8 +28,8 @@ factory((root.pdfjsDistBuildPdfWorker = {}));
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.4.190';
-var pdfjsBuild = '34aa915';
+var pdfjsVersion = '1.4.192';
+var pdfjsBuild = '2dd03e1';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
@@ -27062,6 +27062,925 @@ exports.IdentityCMap = IdentityCMap;
 }));
 
 
+(function (root, factory) {
+  {
+    factory((root.pdfjsCorePsParser = {}), root.pdfjsSharedUtil,
+      root.pdfjsCoreParser);
+  }
+}(this, function (exports, sharedUtil, coreParser) {
+
+var error = sharedUtil.error;
+var EOF = coreParser.EOF;
+var Lexer = coreParser.Lexer;
+
+var PostScriptParser = (function PostScriptParserClosure() {
+  function PostScriptParser(lexer) {
+    this.lexer = lexer;
+    this.operators = [];
+    this.token = null;
+    this.prev = null;
+  }
+  PostScriptParser.prototype = {
+    nextToken: function PostScriptParser_nextToken() {
+      this.prev = this.token;
+      this.token = this.lexer.getToken();
+    },
+    accept: function PostScriptParser_accept(type) {
+      if (this.token.type === type) {
+        this.nextToken();
+        return true;
+      }
+      return false;
+    },
+    expect: function PostScriptParser_expect(type) {
+      if (this.accept(type)) {
+        return true;
+      }
+      error('Unexpected symbol: found ' + this.token.type + ' expected ' +
+        type + '.');
+    },
+    parse: function PostScriptParser_parse() {
+      this.nextToken();
+      this.expect(PostScriptTokenTypes.LBRACE);
+      this.parseBlock();
+      this.expect(PostScriptTokenTypes.RBRACE);
+      return this.operators;
+    },
+    parseBlock: function PostScriptParser_parseBlock() {
+      while (true) {
+        if (this.accept(PostScriptTokenTypes.NUMBER)) {
+          this.operators.push(this.prev.value);
+        } else if (this.accept(PostScriptTokenTypes.OPERATOR)) {
+          this.operators.push(this.prev.value);
+        } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
+          this.parseCondition();
+        } else {
+          return;
+        }
+      }
+    },
+    parseCondition: function PostScriptParser_parseCondition() {
+      // Add two place holders that will be updated later
+      var conditionLocation = this.operators.length;
+      this.operators.push(null, null);
+
+      this.parseBlock();
+      this.expect(PostScriptTokenTypes.RBRACE);
+      if (this.accept(PostScriptTokenTypes.IF)) {
+        // The true block is right after the 'if' so it just falls through on
+        // true else it jumps and skips the true block.
+        this.operators[conditionLocation] = this.operators.length;
+        this.operators[conditionLocation + 1] = 'jz';
+      } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
+        var jumpLocation = this.operators.length;
+        this.operators.push(null, null);
+        var endOfTrue = this.operators.length;
+        this.parseBlock();
+        this.expect(PostScriptTokenTypes.RBRACE);
+        this.expect(PostScriptTokenTypes.IFELSE);
+        // The jump is added at the end of the true block to skip the false
+        // block.
+        this.operators[jumpLocation] = this.operators.length;
+        this.operators[jumpLocation + 1] = 'j';
+
+        this.operators[conditionLocation] = endOfTrue;
+        this.operators[conditionLocation + 1] = 'jz';
+      } else {
+        error('PS Function: error parsing conditional.');
+      }
+    }
+  };
+  return PostScriptParser;
+})();
+
+var PostScriptTokenTypes = {
+  LBRACE: 0,
+  RBRACE: 1,
+  NUMBER: 2,
+  OPERATOR: 3,
+  IF: 4,
+  IFELSE: 5
+};
+
+var PostScriptToken = (function PostScriptTokenClosure() {
+  function PostScriptToken(type, value) {
+    this.type = type;
+    this.value = value;
+  }
+
+  var opCache = Object.create(null);
+
+  PostScriptToken.getOperator = function PostScriptToken_getOperator(op) {
+    var opValue = opCache[op];
+    if (opValue) {
+      return opValue;
+    }
+    return opCache[op] = new PostScriptToken(PostScriptTokenTypes.OPERATOR, op);
+  };
+
+  PostScriptToken.LBRACE = new PostScriptToken(PostScriptTokenTypes.LBRACE,
+    '{');
+  PostScriptToken.RBRACE = new PostScriptToken(PostScriptTokenTypes.RBRACE,
+    '}');
+  PostScriptToken.IF = new PostScriptToken(PostScriptTokenTypes.IF, 'IF');
+  PostScriptToken.IFELSE = new PostScriptToken(PostScriptTokenTypes.IFELSE,
+    'IFELSE');
+  return PostScriptToken;
+})();
+
+var PostScriptLexer = (function PostScriptLexerClosure() {
+  function PostScriptLexer(stream) {
+    this.stream = stream;
+    this.nextChar();
+
+    this.strBuf = [];
+  }
+  PostScriptLexer.prototype = {
+    nextChar: function PostScriptLexer_nextChar() {
+      return (this.currentChar = this.stream.getByte());
+    },
+    getToken: function PostScriptLexer_getToken() {
+      var comment = false;
+      var ch = this.currentChar;
+
+      // skip comments
+      while (true) {
+        if (ch < 0) {
+          return EOF;
+        }
+
+        if (comment) {
+          if (ch === 0x0A || ch === 0x0D) {
+            comment = false;
+          }
+        } else if (ch === 0x25) { // '%'
+          comment = true;
+        } else if (!Lexer.isSpace(ch)) {
+          break;
+        }
+        ch = this.nextChar();
+      }
+      switch (ch | 0) {
+        case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: // '0'-'4'
+        case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: // '5'-'9'
+        case 0x2B: case 0x2D: case 0x2E: // '+', '-', '.'
+          return new PostScriptToken(PostScriptTokenTypes.NUMBER,
+                                     this.getNumber());
+        case 0x7B: // '{'
+          this.nextChar();
+          return PostScriptToken.LBRACE;
+        case 0x7D: // '}'
+          this.nextChar();
+          return PostScriptToken.RBRACE;
+      }
+      // operator
+      var strBuf = this.strBuf;
+      strBuf.length = 0;
+      strBuf[0] = String.fromCharCode(ch);
+
+      while ((ch = this.nextChar()) >= 0 && // and 'A'-'Z', 'a'-'z'
+             ((ch >= 0x41 && ch <= 0x5A) || (ch >= 0x61 && ch <= 0x7A))) {
+        strBuf.push(String.fromCharCode(ch));
+      }
+      var str = strBuf.join('');
+      switch (str.toLowerCase()) {
+        case 'if':
+          return PostScriptToken.IF;
+        case 'ifelse':
+          return PostScriptToken.IFELSE;
+        default:
+          return PostScriptToken.getOperator(str);
+      }
+    },
+    getNumber: function PostScriptLexer_getNumber() {
+      var ch = this.currentChar;
+      var strBuf = this.strBuf;
+      strBuf.length = 0;
+      strBuf[0] = String.fromCharCode(ch);
+
+      while ((ch = this.nextChar()) >= 0) {
+        if ((ch >= 0x30 && ch <= 0x39) || // '0'-'9'
+            ch === 0x2D || ch === 0x2E) { // '-', '.'
+          strBuf.push(String.fromCharCode(ch));
+        } else {
+          break;
+        }
+      }
+      var value = parseFloat(strBuf.join(''));
+      if (isNaN(value)) {
+        error('Invalid floating point number: ' + value);
+      }
+      return value;
+    }
+  };
+  return PostScriptLexer;
+})();
+
+exports.PostScriptLexer = PostScriptLexer;
+exports.PostScriptParser = PostScriptParser;
+}));
+
+
+(function (root, factory) {
+  {
+    factory((root.pdfjsCoreType1Parser = {}), root.pdfjsSharedUtil,
+      root.pdfjsCoreStream, root.pdfjsCoreParser, root.pdfjsCoreEncodings);
+  }
+}(this, function (exports, sharedUtil, coreStream, coreParser, coreEncodings) {
+
+var warn = sharedUtil.warn;
+var Stream = coreStream.Stream;
+var Lexer = coreParser.Lexer;
+var getEncoding = coreEncodings.getEncoding;
+
+// Hinting is currently disabled due to unknown problems on windows
+// in tracemonkey and various other pdfs with type1 fonts.
+var HINTING_ENABLED = false;
+
+/*
+ * CharStrings are encoded following the the CharString Encoding sequence
+ * describe in Chapter 6 of the "Adobe Type1 Font Format" specification.
+ * The value in a byte indicates a command, a number, or subsequent bytes
+ * that are to be interpreted in a special way.
+ *
+ * CharString Number Encoding:
+ *  A CharString byte containing the values from 32 through 255 inclusive
+ *  indicate an integer. These values are decoded in four ranges.
+ *
+ * 1. A CharString byte containing a value, v, between 32 and 246 inclusive,
+ * indicate the integer v - 139. Thus, the integer values from -107 through
+ * 107 inclusive may be encoded in single byte.
+ *
+ * 2. A CharString byte containing a value, v, between 247 and 250 inclusive,
+ * indicates an integer involving the next byte, w, according to the formula:
+ * [(v - 247) x 256] + w + 108
+ *
+ * 3. A CharString byte containing a value, v, between 251 and 254 inclusive,
+ * indicates an integer involving the next byte, w, according to the formula:
+ * -[(v - 251) * 256] - w - 108
+ *
+ * 4. A CharString containing the value 255 indicates that the next 4 bytes
+ * are a two complement signed integer. The first of these bytes contains the
+ * highest order bits, the second byte contains the next higher order bits
+ * and the fourth byte contain the lowest order bits.
+ *
+ *
+ * CharString Command Encoding:
+ *  CharStrings commands are encoded in 1 or 2 bytes.
+ *
+ *  Single byte commands are encoded in 1 byte that contains a value between
+ *  0 and 31 inclusive.
+ *  If a command byte contains the value 12, then the value in the next byte
+ *  indicates a command. This "escape" mechanism allows many extra commands
+ * to be encoded and this encoding technique helps to minimize the length of
+ * the charStrings.
+ */
+var Type1CharString = (function Type1CharStringClosure() {
+  var COMMAND_MAP = {
+    'hstem': [1],
+    'vstem': [3],
+    'vmoveto': [4],
+    'rlineto': [5],
+    'hlineto': [6],
+    'vlineto': [7],
+    'rrcurveto': [8],
+    'callsubr': [10],
+    'flex': [12, 35],
+    'drop' : [12, 18],
+    'endchar': [14],
+    'rmoveto': [21],
+    'hmoveto': [22],
+    'vhcurveto': [30],
+    'hvcurveto': [31]
+  };
+
+  function Type1CharString() {
+    this.width = 0;
+    this.lsb = 0;
+    this.flexing = false;
+    this.output = [];
+    this.stack = [];
+  }
+
+  Type1CharString.prototype = {
+    convert: function Type1CharString_convert(encoded, subrs,
+                                              seacAnalysisEnabled) {
+      var count = encoded.length;
+      var error = false;
+      var wx, sbx, subrNumber;
+      for (var i = 0; i < count; i++) {
+        var value = encoded[i];
+        if (value < 32) {
+          if (value === 12) {
+            value = (value << 8) + encoded[++i];
+          }
+          switch (value) {
+            case 1: // hstem
+              if (!HINTING_ENABLED) {
+                this.stack = [];
+                break;
+              }
+              error = this.executeCommand(2, COMMAND_MAP.hstem);
+              break;
+            case 3: // vstem
+              if (!HINTING_ENABLED) {
+                this.stack = [];
+                break;
+              }
+              error = this.executeCommand(2, COMMAND_MAP.vstem);
+              break;
+            case 4: // vmoveto
+              if (this.flexing) {
+                if (this.stack.length < 1) {
+                  error = true;
+                  break;
+                }
+                // Add the dx for flex and but also swap the values so they are
+                // the right order.
+                var dy = this.stack.pop();
+                this.stack.push(0, dy);
+                break;
+              }
+              error = this.executeCommand(1, COMMAND_MAP.vmoveto);
+              break;
+            case 5: // rlineto
+              error = this.executeCommand(2, COMMAND_MAP.rlineto);
+              break;
+            case 6: // hlineto
+              error = this.executeCommand(1, COMMAND_MAP.hlineto);
+              break;
+            case 7: // vlineto
+              error = this.executeCommand(1, COMMAND_MAP.vlineto);
+              break;
+            case 8: // rrcurveto
+              error = this.executeCommand(6, COMMAND_MAP.rrcurveto);
+              break;
+            case 9: // closepath
+              // closepath is a Type1 command that does not take argument and is
+              // useless in Type2 and it can simply be ignored.
+              this.stack = [];
+              break;
+            case 10: // callsubr
+              if (this.stack.length < 1) {
+                error = true;
+                break;
+              }
+              subrNumber = this.stack.pop();
+              error = this.convert(subrs[subrNumber], subrs,
+                                   seacAnalysisEnabled);
+              break;
+            case 11: // return
+              return error;
+            case 13: // hsbw
+              if (this.stack.length < 2) {
+                error = true;
+                break;
+              }
+              // To convert to type2 we have to move the width value to the
+              // first part of the charstring and then use hmoveto with lsb.
+              wx = this.stack.pop();
+              sbx = this.stack.pop();
+              this.lsb = sbx;
+              this.width = wx;
+              this.stack.push(wx, sbx);
+              error = this.executeCommand(2, COMMAND_MAP.hmoveto);
+              break;
+            case 14: // endchar
+              this.output.push(COMMAND_MAP.endchar[0]);
+              break;
+            case 21: // rmoveto
+              if (this.flexing) {
+                break;
+              }
+              error = this.executeCommand(2, COMMAND_MAP.rmoveto);
+              break;
+            case 22: // hmoveto
+              if (this.flexing) {
+                // Add the dy for flex.
+                this.stack.push(0);
+                break;
+              }
+              error = this.executeCommand(1, COMMAND_MAP.hmoveto);
+              break;
+            case 30: // vhcurveto
+              error = this.executeCommand(4, COMMAND_MAP.vhcurveto);
+              break;
+            case 31: // hvcurveto
+              error = this.executeCommand(4, COMMAND_MAP.hvcurveto);
+              break;
+            case (12 << 8) + 0: // dotsection
+              // dotsection is a Type1 command to specify some hinting feature
+              // for dots that do not take a parameter and it can safely be
+              // ignored for Type2.
+              this.stack = [];
+              break;
+            case (12 << 8) + 1: // vstem3
+              if (!HINTING_ENABLED) {
+                this.stack = [];
+                break;
+              }
+              // [vh]stem3 are Type1 only and Type2 supports [vh]stem with
+              // multiple parameters, so instead of returning [vh]stem3 take a
+              // shortcut and return [vhstem] instead.
+              error = this.executeCommand(2, COMMAND_MAP.vstem);
+              break;
+            case (12 << 8) + 2: // hstem3
+              if (!HINTING_ENABLED) {
+                 this.stack = [];
+                break;
+              }
+              // See vstem3.
+              error = this.executeCommand(2, COMMAND_MAP.hstem);
+              break;
+            case (12 << 8) + 6: // seac
+              // seac is like type 2's special endchar but it doesn't use the
+              // first argument asb, so remove it.
+              if (seacAnalysisEnabled) {
+                this.seac = this.stack.splice(-4, 4);
+                error = this.executeCommand(0, COMMAND_MAP.endchar);
+              } else {
+                error = this.executeCommand(4, COMMAND_MAP.endchar);
+              }
+              break;
+            case (12 << 8) + 7: // sbw
+              if (this.stack.length < 4) {
+                error = true;
+                break;
+              }
+              // To convert to type2 we have to move the width value to the
+              // first part of the charstring and then use rmoveto with
+              // (dx, dy). The height argument will not be used for vmtx and
+              // vhea tables reconstruction -- ignoring it.
+              var wy = this.stack.pop();
+              wx = this.stack.pop();
+              var sby = this.stack.pop();
+              sbx = this.stack.pop();
+              this.lsb = sbx;
+              this.width = wx;
+              this.stack.push(wx, sbx, sby);
+              error = this.executeCommand(3, COMMAND_MAP.rmoveto);
+              break;
+            case (12 << 8) + 12: // div
+              if (this.stack.length < 2) {
+                error = true;
+                break;
+              }
+              var num2 = this.stack.pop();
+              var num1 = this.stack.pop();
+              this.stack.push(num1 / num2);
+              break;
+            case (12 << 8) + 16: // callothersubr
+              if (this.stack.length < 2) {
+                error = true;
+                break;
+              }
+              subrNumber = this.stack.pop();
+              var numArgs = this.stack.pop();
+              if (subrNumber === 0 && numArgs === 3) {
+                var flexArgs = this.stack.splice(this.stack.length - 17, 17);
+                this.stack.push(
+                  flexArgs[2] + flexArgs[0], // bcp1x + rpx
+                  flexArgs[3] + flexArgs[1], // bcp1y + rpy
+                  flexArgs[4], // bcp2x
+                  flexArgs[5], // bcp2y
+                  flexArgs[6], // p2x
+                  flexArgs[7], // p2y
+                  flexArgs[8], // bcp3x
+                  flexArgs[9], // bcp3y
+                  flexArgs[10], // bcp4x
+                  flexArgs[11], // bcp4y
+                  flexArgs[12], // p3x
+                  flexArgs[13], // p3y
+                  flexArgs[14] // flexDepth
+                  // 15 = finalx unused by flex
+                  // 16 = finaly unused by flex
+                );
+                error = this.executeCommand(13, COMMAND_MAP.flex, true);
+                this.flexing = false;
+                this.stack.push(flexArgs[15], flexArgs[16]);
+              } else if (subrNumber === 1 && numArgs === 0) {
+                this.flexing = true;
+              }
+              break;
+            case (12 << 8) + 17: // pop
+              // Ignore this since it is only used with othersubr.
+              break;
+            case (12 << 8) + 33: // setcurrentpoint
+              // Ignore for now.
+              this.stack = [];
+              break;
+            default:
+              warn('Unknown type 1 charstring command of "' + value + '"');
+              break;
+          }
+          if (error) {
+            break;
+          }
+          continue;
+        } else if (value <= 246) {
+          value = value - 139;
+        } else if (value <= 250) {
+          value = ((value - 247) * 256) + encoded[++i] + 108;
+        } else if (value <= 254) {
+          value = -((value - 251) * 256) - encoded[++i] - 108;
+        } else {
+          value = (encoded[++i] & 0xff) << 24 | (encoded[++i] & 0xff) << 16 |
+                  (encoded[++i] & 0xff) << 8 | (encoded[++i] & 0xff) << 0;
+        }
+        this.stack.push(value);
+      }
+      return error;
+    },
+
+    executeCommand: function(howManyArgs, command, keepStack) {
+      var stackLength = this.stack.length;
+      if (howManyArgs > stackLength) {
+        return true;
+      }
+      var start = stackLength - howManyArgs;
+      for (var i = start; i < stackLength; i++) {
+        var value = this.stack[i];
+        if (value === (value | 0)) { // int
+          this.output.push(28, (value >> 8) & 0xff, value & 0xff);
+        } else { // fixed point
+          value = (65536 * value) | 0;
+          this.output.push(255,
+                           (value >> 24) & 0xFF,
+                           (value >> 16) & 0xFF,
+                           (value >> 8) & 0xFF,
+                           value & 0xFF);
+        }
+      }
+      this.output.push.apply(this.output, command);
+      if (keepStack) {
+        this.stack.splice(start, howManyArgs);
+      } else {
+        this.stack.length = 0;
+      }
+      return false;
+    }
+  };
+
+  return Type1CharString;
+})();
+
+/*
+ * Type1Parser encapsulate the needed code for parsing a Type1 font
+ * program. Some of its logic depends on the Type2 charstrings
+ * structure.
+ * Note: this doesn't really parse the font since that would require evaluation
+ * of PostScript, but it is possible in most cases to extract what we need
+ * without a full parse.
+ */
+var Type1Parser = (function Type1ParserClosure() {
+  /*
+   * Decrypt a Sequence of Ciphertext Bytes to Produce the Original Sequence
+   * of Plaintext Bytes. The function took a key as a parameter which can be
+   * for decrypting the eexec block of for decoding charStrings.
+   */
+  var EEXEC_ENCRYPT_KEY = 55665;
+  var CHAR_STRS_ENCRYPT_KEY = 4330;
+
+  function isHexDigit(code) {
+    return code >= 48 && code <= 57 || // '0'-'9'
+           code >= 65 && code <= 70 || // 'A'-'F'
+           code >= 97 && code <= 102;  // 'a'-'f'
+  }
+
+  function decrypt(data, key, discardNumber) {
+    if (discardNumber >= data.length) {
+      return new Uint8Array(0);
+    }
+    var r = key | 0, c1 = 52845, c2 = 22719, i, j;
+    for (i = 0; i < discardNumber; i++) {
+      r = ((data[i] + r) * c1 + c2) & ((1 << 16) - 1);
+    }
+    var count = data.length - discardNumber;
+    var decrypted = new Uint8Array(count);
+    for (i = discardNumber, j = 0; j < count; i++, j++) {
+      var value = data[i];
+      decrypted[j] = value ^ (r >> 8);
+      r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
+    }
+    return decrypted;
+  }
+
+  function decryptAscii(data, key, discardNumber) {
+    var r = key | 0, c1 = 52845, c2 = 22719;
+    var count = data.length, maybeLength = count >>> 1;
+    var decrypted = new Uint8Array(maybeLength);
+    var i, j;
+    for (i = 0, j = 0; i < count; i++) {
+      var digit1 = data[i];
+      if (!isHexDigit(digit1)) {
+        continue;
+      }
+      i++;
+      var digit2;
+      while (i < count && !isHexDigit(digit2 = data[i])) {
+        i++;
+      }
+      if (i < count) {
+        var value = parseInt(String.fromCharCode(digit1, digit2), 16);
+        decrypted[j++] = value ^ (r >> 8);
+        r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
+      }
+    }
+    return Array.prototype.slice.call(decrypted, discardNumber, j);
+  }
+
+  function isSpecial(c) {
+    return c === 0x2F || // '/'
+           c === 0x5B || c === 0x5D || // '[', ']'
+           c === 0x7B || c === 0x7D || // '{', '}'
+           c === 0x28 || c === 0x29; // '(', ')'
+  }
+
+  function Type1Parser(stream, encrypted, seacAnalysisEnabled) {
+    if (encrypted) {
+      var data = stream.getBytes();
+      var isBinary = !(isHexDigit(data[0]) && isHexDigit(data[1]) &&
+                       isHexDigit(data[2]) && isHexDigit(data[3]));
+      stream = new Stream(isBinary ? decrypt(data, EEXEC_ENCRYPT_KEY, 4) :
+                          decryptAscii(data, EEXEC_ENCRYPT_KEY, 4));
+    }
+    this.seacAnalysisEnabled = !!seacAnalysisEnabled;
+
+    this.stream = stream;
+    this.nextChar();
+  }
+
+  Type1Parser.prototype = {
+    readNumberArray: function Type1Parser_readNumberArray() {
+      this.getToken(); // read '[' or '{' (arrays can start with either)
+      var array = [];
+      while (true) {
+        var token = this.getToken();
+        if (token === null || token === ']' || token === '}') {
+          break;
+        }
+        array.push(parseFloat(token || 0));
+      }
+      return array;
+    },
+
+    readNumber: function Type1Parser_readNumber() {
+      var token = this.getToken();
+      return parseFloat(token || 0);
+    },
+
+    readInt: function Type1Parser_readInt() {
+      // Use '| 0' to prevent setting a double into length such as the double
+      // does not flow into the loop variable.
+      var token = this.getToken();
+      return parseInt(token || 0, 10) | 0;
+    },
+
+    readBoolean: function Type1Parser_readBoolean() {
+      var token = this.getToken();
+
+      // Use 1 and 0 since that's what type2 charstrings use.
+      return token === 'true' ? 1 : 0;
+    },
+
+    nextChar : function Type1_nextChar() {
+      return (this.currentChar = this.stream.getByte());
+    },
+
+    getToken: function Type1Parser_getToken() {
+      // Eat whitespace and comments.
+      var comment = false;
+      var ch = this.currentChar;
+      while (true) {
+        if (ch === -1) {
+          return null;
+        }
+
+        if (comment) {
+          if (ch === 0x0A || ch === 0x0D) {
+            comment = false;
+          }
+        } else if (ch === 0x25) { // '%'
+          comment = true;
+        } else if (!Lexer.isSpace(ch)) {
+          break;
+        }
+        ch = this.nextChar();
+      }
+      if (isSpecial(ch)) {
+        this.nextChar();
+        return String.fromCharCode(ch);
+      }
+      var token = '';
+      do {
+        token += String.fromCharCode(ch);
+        ch = this.nextChar();
+      } while (ch >= 0 && !Lexer.isSpace(ch) && !isSpecial(ch));
+      return token;
+    },
+
+    /*
+     * Returns an object containing a Subrs array and a CharStrings
+     * array extracted from and eexec encrypted block of data
+     */
+    extractFontProgram: function Type1Parser_extractFontProgram() {
+      var stream = this.stream;
+
+      var subrs = [], charstrings = [];
+      var privateData = Object.create(null);
+      privateData['lenIV'] = 4;
+      var program = {
+        subrs: [],
+        charstrings: [],
+        properties: {
+          'privateData': privateData
+        }
+      };
+      var token, length, data, lenIV, encoded;
+      while ((token = this.getToken()) !== null) {
+        if (token !== '/') {
+          continue;
+        }
+        token = this.getToken();
+        switch (token) {
+          case 'CharStrings':
+            // The number immediately following CharStrings must be greater or
+            // equal to the number of CharStrings.
+            this.getToken();
+            this.getToken(); // read in 'dict'
+            this.getToken(); // read in 'dup'
+            this.getToken(); // read in 'begin'
+            while(true) {
+              token = this.getToken();
+              if (token === null || token === 'end') {
+                break;
+              }
+
+              if (token !== '/') {
+                continue;
+              }
+              var glyph = this.getToken();
+              length = this.readInt();
+              this.getToken(); // read in 'RD' or '-|'
+              data = stream.makeSubStream(stream.pos, length);
+              lenIV = program.properties.privateData['lenIV'];
+              encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV);
+              // Skip past the required space and binary data.
+              stream.skip(length);
+              this.nextChar();
+              token = this.getToken(); // read in 'ND' or '|-'
+              if (token === 'noaccess') {
+                this.getToken(); // read in 'def'
+              }
+              charstrings.push({
+                glyph: glyph,
+                encoded: encoded
+              });
+            }
+            break;
+          case 'Subrs':
+            var num = this.readInt();
+            this.getToken(); // read in 'array'
+            while ((token = this.getToken()) === 'dup') {
+              var index = this.readInt();
+              length = this.readInt();
+              this.getToken(); // read in 'RD' or '-|'
+              data = stream.makeSubStream(stream.pos, length);
+              lenIV = program.properties.privateData['lenIV'];
+              encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV);
+              // Skip past the required space and binary data.
+              stream.skip(length);
+              this.nextChar();
+              token = this.getToken(); // read in 'NP' or '|'
+              if (token === 'noaccess') {
+                this.getToken(); // read in 'put'
+              }
+              subrs[index] = encoded;
+            }
+            break;
+          case 'BlueValues':
+          case 'OtherBlues':
+          case 'FamilyBlues':
+          case 'FamilyOtherBlues':
+            var blueArray = this.readNumberArray();
+            // *Blue* values may contain invalid data: disables reading of
+            // those values when hinting is disabled.
+            if (blueArray.length > 0 && (blueArray.length % 2) === 0 &&
+                HINTING_ENABLED) {
+              program.properties.privateData[token] = blueArray;
+            }
+            break;
+          case 'StemSnapH':
+          case 'StemSnapV':
+            program.properties.privateData[token] = this.readNumberArray();
+            break;
+          case 'StdHW':
+          case 'StdVW':
+            program.properties.privateData[token] =
+              this.readNumberArray()[0];
+            break;
+          case 'BlueShift':
+          case 'lenIV':
+          case 'BlueFuzz':
+          case 'BlueScale':
+          case 'LanguageGroup':
+          case 'ExpansionFactor':
+            program.properties.privateData[token] = this.readNumber();
+            break;
+          case 'ForceBold':
+            program.properties.privateData[token] = this.readBoolean();
+            break;
+        }
+      }
+
+      for (var i = 0; i < charstrings.length; i++) {
+        glyph = charstrings[i].glyph;
+        encoded = charstrings[i].encoded;
+        var charString = new Type1CharString();
+        var error = charString.convert(encoded, subrs,
+                                       this.seacAnalysisEnabled);
+        var output = charString.output;
+        if (error) {
+          // It seems when FreeType encounters an error while evaluating a glyph
+          // that it completely ignores the glyph so we'll mimic that behaviour
+          // here and put an endchar to make the validator happy.
+          output = [14];
+        }
+        program.charstrings.push({
+          glyphName: glyph,
+          charstring: output,
+          width: charString.width,
+          lsb: charString.lsb,
+          seac: charString.seac
+        });
+      }
+
+      return program;
+    },
+
+    extractFontHeader: function Type1Parser_extractFontHeader(properties) {
+      var token;
+      while ((token = this.getToken()) !== null) {
+        if (token !== '/') {
+          continue;
+        }
+        token = this.getToken();
+        switch (token) {
+          case 'FontMatrix':
+            var matrix = this.readNumberArray();
+            properties.fontMatrix = matrix;
+            break;
+          case 'Encoding':
+            var encodingArg = this.getToken();
+            var encoding;
+            if (!/^\d+$/.test(encodingArg)) {
+              // encoding name is specified
+              encoding = getEncoding(encodingArg);
+            } else {
+              encoding = [];
+              var size = parseInt(encodingArg, 10) | 0;
+              this.getToken(); // read in 'array'
+
+              for (var j = 0; j < size; j++) {
+                token = this.getToken();
+                // skipping till first dup or def (e.g. ignoring for statement)
+                while (token !== 'dup' && token !== 'def') {
+                  token = this.getToken();
+                  if (token === null) {
+                    return; // invalid header
+                  }
+                }
+                if (token === 'def') {
+                  break; // read all array data
+                }
+                var index = this.readInt();
+                this.getToken(); // read in '/'
+                var glyph = this.getToken();
+                encoding[index] = glyph;
+                this.getToken(); // read the in 'put'
+              }
+            }
+            properties.builtInEncoding = encoding;
+            break;
+          case 'FontBBox':
+            var fontBBox = this.readNumberArray();
+            // adjusting ascent/descent
+            properties.ascent = fontBBox[3];
+            properties.descent = fontBBox[1];
+            properties.ascentScaled = true;
+            break;
+        }
+      }
+    }
+  };
+
+  return Type1Parser;
+})();
+
+exports.Type1Parser = Type1Parser;
+}));
+
+
 (function (root, factory) {
   {
     factory((root.pdfjsCoreFonts = {}), root.pdfjsSharedUtil,
@@ -30353,225 +31272,6 @@ exports.getFontType = getFontType;
 }));
 
 
-(function (root, factory) {
-  {
-    factory((root.pdfjsCorePsParser = {}), root.pdfjsSharedUtil,
-      root.pdfjsCoreParser);
-  }
-}(this, function (exports, sharedUtil, coreParser) {
-
-var error = sharedUtil.error;
-var EOF = coreParser.EOF;
-var Lexer = coreParser.Lexer;
-
-var PostScriptParser = (function PostScriptParserClosure() {
-  function PostScriptParser(lexer) {
-    this.lexer = lexer;
-    this.operators = [];
-    this.token = null;
-    this.prev = null;
-  }
-  PostScriptParser.prototype = {
-    nextToken: function PostScriptParser_nextToken() {
-      this.prev = this.token;
-      this.token = this.lexer.getToken();
-    },
-    accept: function PostScriptParser_accept(type) {
-      if (this.token.type === type) {
-        this.nextToken();
-        return true;
-      }
-      return false;
-    },
-    expect: function PostScriptParser_expect(type) {
-      if (this.accept(type)) {
-        return true;
-      }
-      error('Unexpected symbol: found ' + this.token.type + ' expected ' +
-        type + '.');
-    },
-    parse: function PostScriptParser_parse() {
-      this.nextToken();
-      this.expect(PostScriptTokenTypes.LBRACE);
-      this.parseBlock();
-      this.expect(PostScriptTokenTypes.RBRACE);
-      return this.operators;
-    },
-    parseBlock: function PostScriptParser_parseBlock() {
-      while (true) {
-        if (this.accept(PostScriptTokenTypes.NUMBER)) {
-          this.operators.push(this.prev.value);
-        } else if (this.accept(PostScriptTokenTypes.OPERATOR)) {
-          this.operators.push(this.prev.value);
-        } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
-          this.parseCondition();
-        } else {
-          return;
-        }
-      }
-    },
-    parseCondition: function PostScriptParser_parseCondition() {
-      // Add two place holders that will be updated later
-      var conditionLocation = this.operators.length;
-      this.operators.push(null, null);
-
-      this.parseBlock();
-      this.expect(PostScriptTokenTypes.RBRACE);
-      if (this.accept(PostScriptTokenTypes.IF)) {
-        // The true block is right after the 'if' so it just falls through on
-        // true else it jumps and skips the true block.
-        this.operators[conditionLocation] = this.operators.length;
-        this.operators[conditionLocation + 1] = 'jz';
-      } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
-        var jumpLocation = this.operators.length;
-        this.operators.push(null, null);
-        var endOfTrue = this.operators.length;
-        this.parseBlock();
-        this.expect(PostScriptTokenTypes.RBRACE);
-        this.expect(PostScriptTokenTypes.IFELSE);
-        // The jump is added at the end of the true block to skip the false
-        // block.
-        this.operators[jumpLocation] = this.operators.length;
-        this.operators[jumpLocation + 1] = 'j';
-
-        this.operators[conditionLocation] = endOfTrue;
-        this.operators[conditionLocation + 1] = 'jz';
-      } else {
-        error('PS Function: error parsing conditional.');
-      }
-    }
-  };
-  return PostScriptParser;
-})();
-
-var PostScriptTokenTypes = {
-  LBRACE: 0,
-  RBRACE: 1,
-  NUMBER: 2,
-  OPERATOR: 3,
-  IF: 4,
-  IFELSE: 5
-};
-
-var PostScriptToken = (function PostScriptTokenClosure() {
-  function PostScriptToken(type, value) {
-    this.type = type;
-    this.value = value;
-  }
-
-  var opCache = Object.create(null);
-
-  PostScriptToken.getOperator = function PostScriptToken_getOperator(op) {
-    var opValue = opCache[op];
-    if (opValue) {
-      return opValue;
-    }
-    return opCache[op] = new PostScriptToken(PostScriptTokenTypes.OPERATOR, op);
-  };
-
-  PostScriptToken.LBRACE = new PostScriptToken(PostScriptTokenTypes.LBRACE,
-    '{');
-  PostScriptToken.RBRACE = new PostScriptToken(PostScriptTokenTypes.RBRACE,
-    '}');
-  PostScriptToken.IF = new PostScriptToken(PostScriptTokenTypes.IF, 'IF');
-  PostScriptToken.IFELSE = new PostScriptToken(PostScriptTokenTypes.IFELSE,
-    'IFELSE');
-  return PostScriptToken;
-})();
-
-var PostScriptLexer = (function PostScriptLexerClosure() {
-  function PostScriptLexer(stream) {
-    this.stream = stream;
-    this.nextChar();
-
-    this.strBuf = [];
-  }
-  PostScriptLexer.prototype = {
-    nextChar: function PostScriptLexer_nextChar() {
-      return (this.currentChar = this.stream.getByte());
-    },
-    getToken: function PostScriptLexer_getToken() {
-      var comment = false;
-      var ch = this.currentChar;
-
-      // skip comments
-      while (true) {
-        if (ch < 0) {
-          return EOF;
-        }
-
-        if (comment) {
-          if (ch === 0x0A || ch === 0x0D) {
-            comment = false;
-          }
-        } else if (ch === 0x25) { // '%'
-          comment = true;
-        } else if (!Lexer.isSpace(ch)) {
-          break;
-        }
-        ch = this.nextChar();
-      }
-      switch (ch | 0) {
-        case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: // '0'-'4'
-        case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: // '5'-'9'
-        case 0x2B: case 0x2D: case 0x2E: // '+', '-', '.'
-          return new PostScriptToken(PostScriptTokenTypes.NUMBER,
-                                     this.getNumber());
-        case 0x7B: // '{'
-          this.nextChar();
-          return PostScriptToken.LBRACE;
-        case 0x7D: // '}'
-          this.nextChar();
-          return PostScriptToken.RBRACE;
-      }
-      // operator
-      var strBuf = this.strBuf;
-      strBuf.length = 0;
-      strBuf[0] = String.fromCharCode(ch);
-
-      while ((ch = this.nextChar()) >= 0 && // and 'A'-'Z', 'a'-'z'
-             ((ch >= 0x41 && ch <= 0x5A) || (ch >= 0x61 && ch <= 0x7A))) {
-        strBuf.push(String.fromCharCode(ch));
-      }
-      var str = strBuf.join('');
-      switch (str.toLowerCase()) {
-        case 'if':
-          return PostScriptToken.IF;
-        case 'ifelse':
-          return PostScriptToken.IFELSE;
-        default:
-          return PostScriptToken.getOperator(str);
-      }
-    },
-    getNumber: function PostScriptLexer_getNumber() {
-      var ch = this.currentChar;
-      var strBuf = this.strBuf;
-      strBuf.length = 0;
-      strBuf[0] = String.fromCharCode(ch);
-
-      while ((ch = this.nextChar()) >= 0) {
-        if ((ch >= 0x30 && ch <= 0x39) || // '0'-'9'
-            ch === 0x2D || ch === 0x2E) { // '-', '.'
-          strBuf.push(String.fromCharCode(ch));
-        } else {
-          break;
-        }
-      }
-      var value = parseFloat(strBuf.join(''));
-      if (isNaN(value)) {
-        error('Invalid floating point number: ' + value);
-      }
-      return value;
-    }
-  };
-  return PostScriptLexer;
-})();
-
-exports.PostScriptLexer = PostScriptLexer;
-exports.PostScriptParser = PostScriptParser;
-}));
-
-
 (function (root, factory) {
   {
     factory((root.pdfjsCoreFunction = {}), root.pdfjsSharedUtil,

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "pdfjs-dist",
-  "version": "1.4.190",
+  "version": "1.4.192",
   "main": "build/pdf.js",
   "description": "Generic build of Mozilla's PDF.js library.",
   "keywords": [