]> git.parisson.com Git - pdf.js.git/commitdiff
Merge with master
authorVivien Nicolas <21@vingtetun.org>
Tue, 21 Jun 2011 00:35:14 +0000 (02:35 +0200)
committerVivien Nicolas <21@vingtetun.org>
Tue, 21 Jun 2011 00:35:14 +0000 (02:35 +0200)
1  2 
fonts.js
pdf.js
utils/fonts_utils.js

diff --cc fonts.js
index 273ef5ea632532a6bcbeede546303a4265e67040,b8a49036932af2f1fed171fab124fd008e38228f..53db045f0ccef63e800c734d5f937f996369a37a
+++ b/fonts.js
@@@ -88,542 -79,489 +86,541 @@@ var Fonts = 
   *   var type1Font = new Font("MyFontName", binaryFile, propertiesObject);
   *   type1Font.bind();
   */
- var Font = function(aName, aFile, aProperties) {
-   this.name = aName;
-   this.encoding = aProperties.encoding;
-   // If the font has already been decoded simply return
-   if (Fonts[aName]) {
-     this.font = Fonts[aName].data;
-     return;
-   }
-   fontCount++;
-   if (aProperties.ignore || kDisableFonts) {
-     Fonts[aName] = {
-       data: aFile,
-       loading: false,
-       properties: {},
-       cache: Object.create(null)
+ var Font = (function () {
+   var constructor = function(aName, aFile, aProperties) {
+     this.name = aName;
++    this.encoding = aProperties.encoding;
+     // If the font has already been decoded simply return it
+     if (Fonts[aName]) {
+       this.font = Fonts[aName].data;
+       return;
      }
-     return;
-   }
-   switch (aProperties.type) {
-     case "Type1":
-       var cff = new CFF(aName, aFile, aProperties);
-       this.mimetype = "font/opentype";
-       // Wrap the CFF data inside an OTF font file
-       this.font = this.cover(aName, cff, aProperties);
-       break;
-     case "TrueType":
-       this.mimetype = "font/opentype";
-       var ttf = new TrueType(aName, aFile, aProperties);
-       this.font = ttf.data;
-       break;
+     fontCount++;
++    fontName = aName;
  
-     default:
-       warn("Font " + aProperties.type + " is not supported");
 -    switch (aProperties.type) {
 -    case "Type1":
 -      var cff = new CFF(aName, aFile, aProperties);
 -      this.mimetype = "font/otf";
 -
 -      // Wrap the CFF data inside an OTF font file
 -      this.font = this.convert(cff, aProperties);
--      break;
-   }
 -
 -    case "TrueType":
 -      // TrueType is disabled for the moment since the sanitizer prevent it
 -      // from loading because of an overdated cmap table
 -      return Fonts[aName] = {
 -        data: null,
 -        properties: {
 -          encoding: {},
 -          charset: null
 -        },
++    if (aProperties.ignore || kDisableFonts) {
++      Fonts[aName] = {
++        data: aFile,
+         loading: false,
++        properties: {},
+         cache: Object.create(null)
 -      };
 -
 -      this.mimetype = "font/ttf";
 -      var ttf = new TrueType(aFile);
 -      this.font = ttf.data;
 -      break;
++      }
++      return;
++    }
  
-   Fonts[aName] = {
-     data: this.font,
-     properties: aProperties,
-     loading: true,
-     cache: Object.create(null)
-   }
 -    default:
 -      warn("Font " + aProperties.type + " is not supported");
 -      break;
++    switch (aProperties.type) {
++      case "Type1":
++        var cff = new CFF(aName, aFile, aProperties);
++        this.mimetype = "font/opentype";
 +
-   // Attach the font to the document
-   this.bind();
- };
++        // Wrap the CFF data inside an OTF font file
++        this.font = this.cover(aName, cff, aProperties);
++        break;
 +
++      case "TrueType":
++        // TrueType is disabled for the moment since the sanitizer prevent it
++        // from loading due to missing tables
++        return Fonts[aName] = {
++          data: null,
++          properties: {
++            encoding: {},
++            charset: null
++          },
++          loading: false,
++          cache: Object.create(null)
++        };
++
++        this.mimetype = "font/opentype";
++        var ttf = new TrueType(aName, aFile, aProperties);
++        this.font = ttf.data;
++        break;
 +
- /**
-  * A bunch of the OpenType code is duplicate between this class and the
-  * TrueType code, this is intentional and will merge in a future version
-  * where all the code relative to OpenType will probably have its own
-  * class and will take decision without the Fonts consent.
-  * But at the moment it allows to develop around the TrueType rewriting
-  * on the fly without messing up with the 'regular' Type1 to OTF conversion.
-  */
- Font.prototype = {
-   name: null,
-   font: null,
-   mimetype: null,
-   encoding: null,
-   bind: function font_bind() {
-     var data = this.font;
-     // Compute the binary data to base 64
-     var str = [];
-     var count = data.length;
-     for (var i = 0; i < count; i++)
-       str.push(data.getChar ? data.getChar()
-                             : String.fromCharCode(data[i]));
-     var dataBase64 = window.btoa(str.join(""));
-     var fontName = this.name;
-     /** Hack begin */
-     // Actually there is not event when a font has finished downloading so
-     // the following tons of code are a dirty hack to 'guess' when a font is
-     // ready
-     var debug = false;
-     if (debug) {
-       var name = document.createElement("font");
-       name.setAttribute("style", "position: absolute; left: 20px; top: " +
-                                  (100 * fontCount + 60) + "px");
-       name.innerHTML = fontName;
-       document.body.appendChild(name);
++      default:
++        warn("Font " + aProperties.type + " is not supported");
++        break;
      }
  
-     var canvas = document.createElement("canvas");
-     var style = "border: 1px solid black; position:absolute; top: " +
-                 (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px";
-     canvas.setAttribute("style", style);
-     canvas.setAttribute("width", 340);
-     canvas.setAttribute("heigth", 100);
-     document.body.appendChild(canvas);
-     // Retrieve font charset
-     var charset = Fonts[fontName].properties.charset || [];
-     // if the charset is too small make it repeat a few times
-     var count = 30;
-     while (count-- && charset.length <= 30)
-      charset = charset.concat(charset.slice());
-     // Get the font size canvas think it will be for 'spaces'
-     var ctx = canvas.getContext("2d");
-     var testString = "     ";
-     // When debugging use the characters provided by the charsets to visually
-     // see what's happening
-     if (debug) {
-       var encoding = this.encoding;
-       for (var i = 0; i < charset.length; i++) {
-         var unicode = GlyphsUnicode[charset[i]];
-         if (!unicode)
-           continue;
-         testString += String.fromCharCode(unicode);
-       }
+     Fonts[aName] = {
+       data: this.font,
+       properties: aProperties,
+       loading: true,
+       cache: Object.create(null)
      }
-     ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
-     var textWidth = ctx.measureText(testString).width;
  
-     if (debug)
-       ctx.fillText(testString, 20, 20);
+     // Attach the font to the document
+     this.bind();
+   };
  
-     var interval = window.setInterval(function canvasInterval(self) {
-       this.start = this.start || Date.now();
-       ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
+   /**
+    * A bunch of the OpenType code is duplicate between this class and the
+    * TrueType code, this is intentional and will merge in a future version
+    * where all the code relative to OpenType will probably have its own
+    * class and will take decision without the Fonts consent.
+    * But at the moment it allows to develop around the TrueType rewriting
+    * on the fly without messing up with the 'regular' Type1 to OTF conversion.
+    */
+   constructor.prototype = {
+     name: null,
+     font: null,
+     mimetype: null,
++    encoding: null,
+     bind: function font_bind() {
+       var data = this.font;
+       // Get the base64 encoding of the binary font data
+       var str = "";
+       var length = data.length;
+       for (var i = 0; i < length; ++i)
+         str += String.fromCharCode(data[i]);
+       var dataBase64 = window.btoa(str);
+       var fontName = this.name;
+       /** Hack begin */
+       // Actually there is not event when a font has finished downloading so
+       // the following tons of code are a dirty hack to 'guess' when a font is
+       // ready
+       var debug = false;
+       if (debug) {
+         var name = document.createElement("font");
+         name.setAttribute("style", "position: absolute; left: 20px; top: " +
+                           (100 * fontCount + 60) + "px");
+         name.innerHTML = fontName;
+         document.body.appendChild(name);
+       }
  
-       // For some reasons the font has not loaded, so mark it loaded for the
-       // page to proceed but cry
-       if ((Date.now() - this.start) >= kMaxWaitForFontFace) {
-         window.clearInterval(interval);
-         Fonts[fontName].loading = false;
-         warn("Is " + fontName + " for charset: " + charset + " loaded?");
-         this.start = 0;
-       } else if (textWidth != ctx.measureText(testString).width) {
-         window.clearInterval(interval);
-         Fonts[fontName].loading = false;
-         this.start = 0;
+       var canvas = document.createElement("canvas");
+       var style = "border: 1px solid black; position:absolute; top: " +
+                    (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px";
+       canvas.setAttribute("style", style);
+       canvas.setAttribute("width", 340);
+       canvas.setAttribute("heigth", 100);
+       document.body.appendChild(canvas);
+       // Retrieve font charset
 -      var charset = Fonts[fontName].charset || [];
++      var charset = Fonts[fontName].properties.charset || [];
++
+       // if the charset is too small make it repeat a few times
+       var count = 30;
+       while (count-- && charset.length <= 30)
+         charset = charset.concat(charset.slice());
+       // Get the font size canvas think it will be for 'spaces'
+       var ctx = canvas.getContext("2d");
+       var testString = "     ";
+       // When debugging use the characters provided by the charsets to visually
+       // see what's happening
+       if (debug) {
+         for (var i = 0; i < charset.length; i++) {
+           var unicode = GlyphsUnicode[charset[i]];
+           if (!unicode)
 -            error("Unicode for " + charset[i] + " is has not been found in the glyphs list");
++            continue;
+           testString += String.fromCharCode(unicode);
+         }
        }
+       ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
+       var textWidth = ctx.measureText(testString).width;
  
        if (debug)
-         ctx.fillText(testString, 20, 50);
-     }, 50, this);
-     /** Hack end */
-     // Add the @font-face rule to the document
-     var url = "url(data:" + this.mimetype + ";base64," + dataBase64 + ");";
-     var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}";
-     var styleSheet = document.styleSheets[0];
-     styleSheet.insertRule(rule, styleSheet.length);
-   },
-   _createOpenTypeHeader: function font_createOpenTypeHeader(aFile, aOffsets, aNumTables) {
-     // sfnt version (4 bytes)
-     var version = [0x4F, 0x54, 0x54, 0X4F];
-     // numTables (2 bytes)
-     var numTables = aNumTables;
-     // searchRange (2 bytes)
-     var tablesMaxPower2 = FontsUtils.getMaxPower2(numTables);
-     var searchRange = tablesMaxPower2 * 16;
-     // entrySelector (2 bytes)
-     var entrySelector = Math.log(tablesMaxPower2) / Math.log(2);
-     // rangeShift (2 bytes)
-     var rangeShift = numTables * 16 - searchRange;
-     var header = [].concat(version,
-                            FontsUtils.integerToBytes(numTables, 2),
-                            FontsUtils.integerToBytes(searchRange, 2),
-                            FontsUtils.integerToBytes(entrySelector, 2),
-                            FontsUtils.integerToBytes(rangeShift, 2));
-     aFile.set(header, aOffsets.currentOffset);
-     aOffsets.currentOffset += header.length;
-     aOffsets.virtualOffset += header.length;
-   },
-   _createTableEntry: function font_createTableEntry(aFile, aOffsets, aTag, aData) {
-     // tag
-     var tag = [
-       aTag.charCodeAt(0),
-       aTag.charCodeAt(1),
-       aTag.charCodeAt(2),
-       aTag.charCodeAt(3)
-     ];
-     // offset
-     var offset = aOffsets.virtualOffset;
+         ctx.fillText(testString, 20, 20);
 -      var start = Date.now();
+       var interval = window.setInterval(function canvasInterval(self) {
 -          ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
 -
 -          // For some reasons the font has not loaded, so mark it loaded for the
 -          // page to proceed but cry
 -          if ((Date.now() - start) >= kMaxWaitForFontFace) {
 -            window.clearInterval(interval);
 -            Fonts[fontName].loading = false;
 -            warn("Is " + fontName + " for charset: " + charset + " loaded?");
 -          } else if (textWidth != ctx.measureText(testString).width) {
 -            window.clearInterval(interval);
 -            Fonts[fontName].loading = false;
 -          }
++        this.start = this.start || Date.now();
++        ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
++
++        // For some reasons the font has not loaded, so mark it loaded for the
++        // page to proceed but cry
++        if ((Date.now() - this.start) >= kMaxWaitForFontFace) {
++          window.clearInterval(interval);
++          Fonts[fontName].loading = false;
++          warn("Is " + fontName + " for charset: " + charset + " loaded?");
++          this.start = 0;
++        } else if (textWidth != ctx.measureText(testString).width) {
++          window.clearInterval(interval);
++          Fonts[fontName].loading = false;
++          this.start = 0;
++        }
  
-     // Per spec tables must be 4-bytes align so add some 0x00 if needed
-     while (aData.length & 3)
-       aData.push(0x00);
 -          if (debug)
 -            ctx.fillText(testString, 20, 50);
 -        }, 50, this);
++        if (debug)
++          ctx.fillText(testString, 20, 50);
++      }, 50, this);
  
-     // length
-     var length = aData.length;
+       /** Hack end */
  
-     // checksum
-     var checksum = FontsUtils.bytesToInteger(tag) + offset + length;
+       // Add the @font-face rule to the document
+       var url = "url(data:" + this.mimetype + ";base64," + dataBase64 + ");";
+       var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}";
+       var styleSheet = document.styleSheets[0];
+       styleSheet.insertRule(rule, styleSheet.length);
+     },
  
-     var tableEntry = [].concat(tag,
-                                FontsUtils.integerToBytes(checksum, 4),
-                                FontsUtils.integerToBytes(offset, 4),
-                                FontsUtils.integerToBytes(length, 4));
-     aFile.set(tableEntry, aOffsets.currentOffset);
-     aOffsets.currentOffset += tableEntry.length;
-     aOffsets.virtualOffset += aData.length;
-   },
 -    convert: function font_convert(aFont, aProperties) {
 -      var otf = new Uint8Array(kMaxFontFileSize);
++    cover: function font_cover(aName, aFont, aProperties) {
++      var otf = Uint8Array(kMaxFontFileSize);
  
-   _createCMAPTable: function font_createCMAPTable(aGlyphs) {
-     var characters = new Uint16Array(kMaxGlyphsCount);
-     for (var i = 0; i < aGlyphs.length; i++)
-       characters[aGlyphs[i].unicode] = i + 1;
+       function s2a(s) {
+         var a = [];
+         for (var i = 0; i < s.length; ++i)
+           a[i] = s.charCodeAt(i);
+         return a;
+       }
  
-     // Separate the glyphs into continuous range of codes, aka segment.
-     var ranges = [];
-     var range = [];
-     var count = characters.length;
-     for (var i = 0; i < count; i++) {
-       if (characters[i]) {
-         range.push(i);
-       } else if (range.length) {
-         ranges.push(range.slice());
-         range = [];
+       function s16(value) {
+         return String.fromCharCode((value >> 8) & 0xff) + String.fromCharCode(value & 0xff);
        }
-     }
  
-     // The size in bytes of the header is equal to the size of the
-     // different fields * length of a short + (size of the 4 parallels arrays
-     // describing segments * length of a short).
-     var headerSize = (12 * 2 + (ranges.length * 4 * 2));
+       function s32(value) {
+         return String.fromCharCode((value >> 24) & 0xff) + String.fromCharCode((value >> 16) & 0xff) +
+                String.fromCharCode((value >> 8) & 0xff) + String.fromCharCode(value & 0xff);
+       }
  
-     var segCount = ranges.length + 1;
-     var segCount2 = segCount * 2;
-     var searchRange = FontsUtils.getMaxPower2(segCount) * 2;
-     var searchEntry = Math.log(segCount) / Math.log(2);
-     var rangeShift = 2 * segCount - searchRange;
-     var cmap = [].concat(
-       [
-         0x00, 0x00, // version
-         0x00, 0x01, // numTables
-         0x00, 0x03, // platformID
-         0x00, 0x01, // encodingID
-         0x00, 0x00, 0x00, 0x0C, // start of the table record
-         0x00, 0x04  // format
-       ],
-       FontsUtils.integerToBytes(headerSize, 2), // length
-       [0x00, 0x00], // language
-       FontsUtils.integerToBytes(segCount2, 2),
-       FontsUtils.integerToBytes(searchRange, 2),
-       FontsUtils.integerToBytes(searchEntry, 2),
-       FontsUtils.integerToBytes(rangeShift, 2)
-     );
+       function createOpenTypeHeader(aFile, aOffsets, numTables) {
+         var header = "";
  
-     // Fill up the 4 parallel arrays describing the segments.
-     var startCount = [];
-     var endCount = [];
-     var idDeltas = [];
-     var idRangeOffsets = [];
-     var glyphsIdsArray = [];
-     var bias = 0;
-     for (var i = 0; i < segCount - 1; i++) {
-       var range = ranges[i];
-       var start = FontsUtils.integerToBytes(range[0], 2);
-       var end = FontsUtils.integerToBytes(range[range.length - 1], 2);
+         // sfnt version (4 bytes)
+         header += "\x4F\x54\x54\x4F";
  
-       var delta = FontsUtils.integerToBytes(((range[0] - 1) - bias) % 65536, 2);
-       bias += range.length;
+         // numTables (2 bytes)
+         header += s16(numTables);
  
-       // deltas are signed shorts
-       delta[0] ^= 0xFF;
-       delta[1] ^= 0xFF;
-       delta[1] += 1;
+         // searchRange (2 bytes)
+         var tablesMaxPower2 = FontsUtils.getMaxPower2(numTables);
+         var searchRange = tablesMaxPower2 * 16;
+         header += s16(searchRange);
  
-       startCount.push(start[0], start[1]);
-       endCount.push(end[0], end[1]);
-       idDeltas.push(delta[0], delta[1]);
-       idRangeOffsets.push(0x00, 0x00);
+         // entrySelector (2 bytes)
+         header += s16(Math.log(tablesMaxPower2) / Math.log(2));
  
-       for (var j = 0; j < range.length; j++)
-         glyphsIdsArray.push(range[j]);
-     }
-     startCount.push(0xFF, 0xFF);
-     endCount.push(0xFF, 0xFF);
-     idDeltas.push(0x00, 0x01);
-     idRangeOffsets.push(0x00, 0x00);
+         // rangeShift (2 bytes)
+         header += s16(numTables * 16 - searchRange);
  
-     return cmap.concat(endCount, [0x00, 0x00], startCount,
-                        idDeltas, idRangeOffsets, glyphsIdsArray);
-   },
+         aFile.set(s2a(header), aOffsets.currentOffset);
+         aOffsets.currentOffset += header.length;
+         aOffsets.virtualOffset += header.length;
+       }
  
-   _createNameTable: function font_createNameTable(aName) {
-     var names = [
-       "See original licence", // Copyright
-       aName,                  // Font family
-       "undefined",            // Font subfamily (font weight)
-       "uniqueID",             // Unique ID
-       aName,                  // Full font name
-       "0.1",                  // Version
-       "undefined",            // Postscript name
-       "undefined",            // Trademark
-       "undefined",            // Manufacturer
-       "undefined"             // Designer
-     ];
+       function createTableEntry(aFile, aOffsets, aTag, aData) {
+         // offset
+         var offset = aOffsets.virtualOffset;
+         // Per spec tables must be 4-bytes align so add padding as needed
+         while (aData.length & 3)
+           aData.push(0x00);
+         // length
+         var length = aData.length;
+         // checksum
+         var checksum = aTag.charCodeAt(0) +
+                        aTag.charCodeAt(1) +
 -                       aTag.charCodeAt(2) + 
 -                       aTag.charCodeAt(3) + 
++                       aTag.charCodeAt(2) +
++                       aTag.charCodeAt(3) +
+                        offset +
+                        length;
+         var tableEntry = aTag + s32(checksum) + s32(offset) + s32(length);
+         tableEntry = s2a(tableEntry);
+         aFile.set(tableEntry, aOffsets.currentOffset);
+         aOffsets.currentOffset += tableEntry.length;
+         aOffsets.virtualOffset += aData.length;
+       }
  
-     var name = [
-       0x00, 0x00, // format
-       0x00, 0x0A, // Number of names Record
-       0x00, 0x7E  // Storage
-     ];
++      function createNameTable(aName) {
++        var names =
++          "See original licence" + // Copyright
++          aName +                  // Font family
++          "undefined" +            // Font subfamily (font weight)
++          "uniqueID" +             // Unique ID
++          aName +                  // Full font name
++          "0.1" +                  // Version
++          "undefined" +            // Postscript name
++          "undefined" +            // Trademark
++          "undefined" +            // Manufacturer
++          "undefined";             // Designer
++
++        var name =
++          "\x00\x00" + // format
++          "\x00\x0A" + // Number of names Record
++          "\x00\x7E";  // Storage
++
++        // Build the name records field
++        var strOffset = 0;
++        for (var i = 0; i < names.length; i++) {
++          var str = names[i];
++
++          var nameRecord =
++            "\x00\x01" + // platform ID
++            "\x00\x00" + // encoding ID
++            "\x00\x00" + // language ID
++            "\x00\x00" + // name ID
++            s16(str.length) +
++            s16(strOffset);
++          name += nameRecord;
++
++          strOffset += str.length;
++        }
 +
-     // Build the name records field
-     var strOffset = 0;
-     for (var i = 0; i < names.length; i++) {
-       var str = names[i];
++        name += names;
++        return name;
++      }
 +
-       var nameRecord = [
-         0x00, 0x01, // platform ID
-         0x00, 0x00, // encoding ID
-         0x00, 0x00, // language ID
-         0x00, 0x00 // name ID
-       ];
+       function getRanges(glyphs) {
+         // Array.sort() sorts by characters, not numerically, so convert to an
+         // array of characters.
+         var codes = [];
+         var length = glyphs.length;
+         for (var n = 0; n < length; ++n)
+           codes.push(String.fromCharCode(glyphs[n].unicode))
+         codes.sort();
+         // Split the sorted codes into ranges.
+         var ranges = [];
+         for (var n = 0; n < length; ) {
+           var start = codes[n++].charCodeAt(0);
+           var end = start;
+           while (n < length && end + 1 == codes[n].charCodeAt(0)) {
+             ++end;
+             ++n;
+           }
+           ranges.push([start, end]);
+         }
+         return ranges;
+       }
  
-       nameRecord = nameRecord.concat(
-         FontsUtils.integerToBytes(str.length, 2),
-         FontsUtils.integerToBytes(strOffset, 2)
-       );
-       name = name.concat(nameRecord);
+       function createCMAPTable(aGlyphs) {
+         var ranges = getRanges(aGlyphs);
+         var headerSize = (12 * 2 + (ranges.length * 4 * 2));
+         var segCount = ranges.length + 1;
+         var segCount2 = segCount * 2;
+         var searchRange = FontsUtils.getMaxPower2(segCount) * 2;
+         var searchEntry = Math.log(segCount) / Math.log(2);
+         var rangeShift = 2 * segCount - searchRange;
+         var cmap = "\x00\x00" + // version
+                    "\x00\x01" + // numTables
+                    "\x00\x03" + // platformID
+                    "\x00\x01" + // encodingID
+                    "\x00\x00\x00\x0C" + // start of the table record
+                    "\x00\x04" + // format
+                    s16(headerSize) + // length
+                    "\x00\x00" + // languages
+                    s16(segCount2) +
+                    s16(searchRange) +
+                    s16(searchEntry) +
+                    s16(rangeShift);
+         // Fill up the 4 parallel arrays describing the segments.
+         var startCount = "";
+         var endCount = "";
+         var idDeltas = "";
+         var idRangeOffsets = "";
+         var glyphsIds = "";
+         var bias = 0;
+         for (var i = 0; i < segCount - 1; i++) {
+           var range = ranges[i];
+           var start = range[0];
+           var end = range[1];
+           var delta = (((start - 1) - bias) ^ 0xffff) + 1;
+           bias += (end - start + 1);
+           startCount += s16(start);
+           endCount += s16(end);
+           idDeltas += s16(delta);
+           idRangeOffsets += s16(0);
+           for (var j = start; j <= end; j++)
+             glyphsIds += String.fromCharCode(j);
+         }
  
-       strOffset += str.length;
-     }
+         startCount += "\xFF\xFF";
+         endCount += "\xFF\xFF";
+         idDeltas += "\x00\x01";
+         idRangeOffsets += "\x00\x00";
  
-     // Add the name records data
-     for (var i = 0; i < names.length; i++) {
-       var str = names[i];
-       var strBytes = [];
-       for (var j = 0; j < str.length; j++) {
-         strBytes.push(str.charCodeAt(j));
+         return s2a(cmap + endCount + "\x00\x00" + startCount +
+                    idDeltas + idRangeOffsets + glyphsIds);
        }
-       name = name.concat(strBytes);
-     }
-     return name;
-   },
  
-   cover: function font_cover(aName, aFont, aProperties) {
-     var otf = new Uint8Array(kMaxFontFileSize);
-     // Required Tables
-     var CFF = aFont.data, // PostScript Font Program
+       // Required Tables
+       var CFF = aFont.data, // PostScript Font Program
 -      OS2 = [],         // OS/2 and Windows Specific metrics
 -      cmap = [],        // Character to glyphs mapping
 -      head = [],        // Font eader
 -      hhea = [],        // Horizontal header
 -      hmtx = [],        // Horizontal metrics
 -      maxp = [],        // Maximum profile
 -      name = [],        // Naming tables
 -      post = [];        // PostScript informations
 +        OS2 = [],         // OS/2 and Windows Specific metrics
 +        cmap = [],        // Character to glyphs mapping
 +        head = [],        // Font eader
 +        hhea = [],        // Horizontal header
 +        hmtx = [],        // Horizontal metrics
 +        maxp = [],        // Maximum profile
 +        name = [],        // Naming tables
 +        post = [];        // PostScript informations
-     var tables = [CFF, OS2, cmap, head, hhea, hmtx, maxp, name, post];
-     // The offsets object holds at the same time a representation of where
-     // to write the table entry information about a table and another offset
-     // representing the offset where to draw the actual data of a particular
-     // table
-     var offsets = {
-       currentOffset: 0,
-       virtualOffset: tables.length * (4 * 4)
-     };
-     // For files with only one font the offset table is the first thing of the
-     // file
-     this._createOpenTypeHeader(otf, offsets, tables.length);
-     // XXX It is probable that in a future we want to get rid of this glue
-     // between the CFF and the OTF format in order to be able to embed TrueType
-     // data.
-     this._createTableEntry(otf, offsets, "CFF ", CFF);
-     /** OS/2 */
-     OS2 = [
-       0x00, 0x03, // version
-       0x02, 0x24, // xAvgCharWidth
-       0x01, 0xF4, // usWeightClass
-       0x00, 0x05, // usWidthClass
-       0x00, 0x00, // fstype
-       0x02, 0x8A, // ySubscriptXSize
-       0x02, 0xBB, // ySubscriptYSize
-       0x00, 0x00, // ySubscriptXOffset
-       0x00, 0x8C, // ySubscriptYOffset
-       0x02, 0x8A, // ySuperScriptXSize
-       0x02, 0xBB, // ySuperScriptYSize
-       0x00, 0x00, // ySuperScriptXOffset
-       0x01, 0xDF, // ySuperScriptYOffset
-       0x00, 0x31, // yStrikeOutSize
-       0x01, 0x02, // yStrikeOutPosition
-       0x00, 0x00, // sFamilyClass
-       0x02, 0x00, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Panose
-       0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 0-31)
-       0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 32-63)
-       0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 64-95)
-       0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 96-127)
-       0x2A, 0x32, 0x31, 0x2A, // achVendID
-       0x00, 0x20, // fsSelection
-       0x00, 0x2D, // usFirstCharIndex
-       0x00, 0x7A, // usLastCharIndex
-       0x00, 0x03, // sTypoAscender
-       0x00, 0x20, // sTypeDescender
-       0x00, 0x38, // sTypoLineGap
-       0x00, 0x5A, // usWinAscent
-       0x02, 0xB4, // usWinDescent
-       0x00, 0xCE, 0x00, 0x00, // ulCodePageRange1 (Bits 0-31)
-       0x00, 0x01, 0x00, 0x00, // ulCodePageRange2 (Bits 32-63)
-       0x00, 0x00, // sxHeight
-       0x00, 0x00, // sCapHeight
-       0x00, 0x01, // usDefaultChar
-       0x00, 0xCD, // usBreakChar
-       0x00, 0x02  // usMaxContext
-     ];
-     this._createTableEntry(otf, offsets, "OS/2", OS2);
-     //XXX Getting charstrings here seems wrong since this is another CFF glue
-     var charstrings = aFont.getOrderedCharStrings(aProperties.glyphs);
-     /** CMAP */
-     cmap = this._createCMAPTable(charstrings);
-     this._createTableEntry(otf, offsets, "cmap", cmap);
-     /** HEAD */
-     head = [
-       0x00, 0x01, 0x00, 0x00, // Version number
-       0x00, 0x00, 0x50, 0x00, // fontRevision
-       0x00, 0x00, 0x00, 0x00, // checksumAdjustement
-       0x5F, 0x0F, 0x3C, 0xF5, // magicNumber
-       0x00, 0x00, // Flags
-       0x03, 0xE8, // unitsPerEM (defaulting to 1000)
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // creation date
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // modifification date
-       0x00, 0x00, // xMin
-       0x00, 0x00, // yMin
-       0x00, 0x00, // xMax
-       0x00, 0x00, // yMax
-       0x00, 0x00, // macStyle
-       0x00, 0x00, // lowestRecPPEM
-       0x00, 0x00, // fontDirectionHint
-       0x00, 0x00, // indexToLocFormat
-       0x00, 0x00  // glyphDataFormat
-     ];
-     this._createTableEntry(otf, offsets, "head", head);
-     /** HHEA */
-     hhea = [].concat(
-       [
-         0x00, 0x01, 0x00, 0x00, // Version number
-         0x00, 0x00, // Typographic Ascent
-         0x00, 0x00, // Typographic Descent
-         0x00, 0x00, // Line Gap
-         0xFF, 0xFF, // advanceWidthMax
-         0x00, 0x00, // minLeftSidebearing
-         0x00, 0x00, // minRightSidebearing
-         0x00, 0x00, // xMaxExtent
-         0x00, 0x00, // caretSlopeRise
-         0x00, 0x00, // caretSlopeRun
-         0x00, 0x00, // caretOffset
-         0x00, 0x00, // -reserved-
-         0x00, 0x00, // -reserved-
-         0x00, 0x00, // -reserved-
-         0x00, 0x00, // -reserved-
-         0x00, 0x00 // metricDataFormat
-       ],
-       FontsUtils.integerToBytes(charstrings.length, 2) // numberOfHMetrics
-     );
-     this._createTableEntry(otf, offsets, "hhea", hhea);
-     /** HMTX */
-     hmtx = [0x01, 0xF4, 0x00, 0x00];
-     for (var i = 0; i < charstrings.length; i++) {
-       var charstring = charstrings[i].charstring;
-       var width = FontsUtils.integerToBytes(charstring[1], 2);
-       var lsb = FontsUtils.integerToBytes(charstring[0], 2);
-       hmtx = hmtx.concat(width, lsb);
-     }
-     this._createTableEntry(otf, offsets, "hmtx", hmtx);
-     /** MAXP */
-     maxp = [].concat(
-       [
-         0x00, 0x00, 0x50, 0x00, // Version number
-       ],
-       FontsUtils.integerToBytes(charstrings.length + 1, 2) // Num of glyphs (+1 to pass the sanitizer...)
-     );
-     this._createTableEntry(otf, offsets, "maxp", maxp);
-     /** NAME */
-     var name = this._createNameTable(aName);
-     this._createTableEntry(otf, offsets, "name", name);
-     /** POST */
-     // FIXME Get those informations from the FontInfo structure
-     post = [
-       0x00, 0x03, 0x00, 0x00, // Version number
-       0x00, 0x00, 0x01, 0x00, // italicAngle
-       0x00, 0x00, // underlinePosition
-       0x00, 0x00, // underlineThickness
-       0x00, 0x00, 0x00, 0x00, // isFixedPitch
-       0x00, 0x00, 0x00, 0x00, // minMemType42
-       0x00, 0x00, 0x00, 0x00, // maxMemType42
-       0x00, 0x00, 0x00, 0x00, // minMemType1
-       0x00, 0x00, 0x00, 0x00  // maxMemType1
-     ];
-     this._createTableEntry(otf, offsets, "post", post);
+       var tables = [CFF, OS2, cmap, head, hhea, hmtx, maxp, name, post];
+       // The offsets object holds at the same time a representation of where
+       // to write the table entry information about a table and another offset
+       // representing the offset where to draw the actual data of a particular
+       // table
+       var offsets = {
+         currentOffset: 0,
+         virtualOffset: tables.length * (4 * 4)
+       };
+       // For files with only one font the offset table is the first thing of the
+       // file
+       createOpenTypeHeader(otf, offsets, tables.length);
+       // TODO: It is probable that in a future we want to get rid of this glue
+       // between the CFF and the OTF format in order to be able to embed TrueType
+       // data.
+       createTableEntry(otf, offsets, "CFF ", CFF);
+       /** OS/2 */
+       OS2 = s2a(
+             "\x00\x03" + // version
+             "\x02\x24" + // xAvgCharWidth
+             "\x01\xF4" + // usWeightClass
+             "\x00\x05" + // usWidthClass
+             "\x00\x00" + // fstype
+             "\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
+             "\x02\x00\x06\x03\x00\x00\x00\x00\x00\x00" + // Panose
+             "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 0-31)
+             "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63)
+             "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95)
+             "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127)
+             "\x2A\x32\x31\x2A" + // achVendID
+             "\x00\x20" + // fsSelection
+             "\x00\x2D" + // usFirstCharIndex
+             "\x00\x7A" + // usLastCharIndex
+             "\x00\x03" + // sTypoAscender
+             "\x00\x20" + // sTypeDescender
+             "\x00\x38" + // sTypoLineGap
+             "\x00\x5A" + // usWinAscent
+             "\x02\xB4" + // usWinDescent
+             "\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31)
+             "\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63)
+             "\x00\x00" + // sxHeight
+             "\x00\x00" + // sCapHeight
+             "\x00\x01" + // usDefaultChar
+             "\x00\xCD" + // usBreakChar
+             "\x00\x02"   // usMaxContext
+       );
+       createTableEntry(otf, offsets, "OS/2", OS2);
+       //XXX Getting charstrings here seems wrong since this is another CFF glue
+       var charstrings = aFont.getOrderedCharStrings(aProperties.glyphs);
+       /** CMAP */
+       cmap = createCMAPTable(charstrings);
+       createTableEntry(otf, offsets, "cmap", cmap);
+       /** HEAD */
+       head = s2a(
+               "\x00\x01\x00\x00" + // Version number
+               "\x00\x00\x50\x00" + // fontRevision
+               "\x00\x00\x00\x00" + // checksumAdjustement
+               "\x5F\x0F\x3C\xF5" + // magicNumber
+               "\x00\x00" + // Flags
+               "\x03\xE8" + // unitsPerEM (defaulting to 1000)
+               "\x00\x00\x00\x00\x00\x00\x00\x00" + // creation date
+               "\x00\x00\x00\x00\x00\x00\x00\x00" + // modifification date
+               "\x00\x00" + // xMin
+               "\x00\x00" + // yMin
+               "\x00\x00" + // xMax
+               "\x00\x00" + // yMax
+               "\x00\x00" + // macStyle
+               "\x00\x00" + // lowestRecPPEM
+               "\x00\x00" + // fontDirectionHint
+               "\x00\x00" + // indexToLocFormat
+               "\x00\x00"   // glyphDataFormat
+       );
+       createTableEntry(otf, offsets, "head", head);
+       /** HHEA */
+       hhea = s2a(
+                  "\x00\x01\x00\x00" + // Version number
+                  "\x00\x00" + // Typographic Ascent
+                  "\x00\x00" + // Typographic Descent
+                  "\x00\x00" + // Line Gap
+                  "\xFF\xFF" + // advanceWidthMax
+                  "\x00\x00" + // minLeftSidebearing
+                  "\x00\x00" + // minRightSidebearing
+                  "\x00\x00" + // xMaxExtent
+                  "\x00\x00" + // caretSlopeRise
+                  "\x00\x00" + // caretSlopeRun
+                  "\x00\x00" + // caretOffset
+                  "\x00\x00" + // -reserved-
+                  "\x00\x00" + // -reserved-
+                  "\x00\x00" + // -reserved-
+                  "\x00\x00" + // -reserved-
+                  "\x00\x00" + // metricDataFormat
+                  s16(charstrings.length)
+       );
+       createTableEntry(otf, offsets, "hhea", hhea);
+       /** HMTX */
+       hmtx = "\x01\xF4\x00\x00";
+       for (var i = 0; i < charstrings.length; i++) {
+         var charstring = charstrings[i].charstring;
+         var width = charstring[1];
+         var lsb = charstring[0];
+         hmtx += s16(width) + s16(lsb);
+       }
+       hmtx = s2a(hmtx);
+       createTableEntry(otf, offsets, "hmtx", hmtx);
+       /** MAXP */
+       maxp = "\x00\x00\x50\x00" + // Version number
+              s16(charstrings.length + 1); // Num of glyphs (+1 to pass the sanitizer...)
+       maxp = s2a(maxp);
+       createTableEntry(otf, offsets, "maxp", maxp);
+       /** NAME */
 -      name = "\x00\x00" + // Format
 -             "\x00\x00" + // Number of name records
 -             "\x00\x00"; // Storage
 -      name = s2a(name);
++      name = s2a(createNameTable(aName));
+       createTableEntry(otf, offsets, "name", name);
+       /** POST */
+       // TODO: get those informations from the FontInfo structure
+       post = "\x00\x03\x00\x00" + // Version number
+              "\x00\x00\x01\x00" + // italicAngle
+              "\x00\x00" + // underlinePosition
+              "\x00\x00" + // underlineThickness
+              "\x00\x00\x00\x00" + // isFixedPitch
+              "\x00\x00\x00\x00" + // minMemType42
+              "\x00\x00\x00\x00" + // maxMemType42
+              "\x00\x00\x00\x00" + // minMemType1
+              "\x00\x00\x00\x00";  // maxMemType1
+       post = s2a(post);
+       createTableEntry(otf, offsets, "post", post);
+       // Once all the table entries header are written, dump the data!
+       var tables = [CFF, OS2, cmap, head, hhea, hmtx, maxp, name, post];
+       for (var i = 0; i < tables.length; i++) {
+         var table = tables[i];
+         otf.set(table, offsets.currentOffset);
+         offsets.currentOffset += table.length;
+       }
  
-     // Once all the table entries header are written, dump the data!
-     var tables = [CFF, OS2, cmap, head, hhea, hmtx, maxp, name, post];
-     for (var i = 0; i < tables.length; i++) {
-       var table = tables[i];
-       otf.set(table, offsets.currentOffset);
-       offsets.currentOffset += table.length;
+       var fontData = [];
+       for (var i = 0; i < offsets.currentOffset; i++)
+         fontData.push(otf[i]);
+       return fontData;
      }
+   };
  
-     var fontData = [];
-     for (var i = 0; i < offsets.currentOffset; i++)
-       fontData.push(otf[i]);
-     return fontData;
-   }
- };
+   return constructor;
+ })();
  
  /**
   * FontsUtils is a static class dedicated to hold codes that are not related
@@@ -701,16 -637,17 +698,14 @@@ var TrueType = function(aName, aFile, a
    for (var i = 0; i < numTables; i++) {
      var table = this._readTableEntry(aFile);
      var index = requiredTables.indexOf(table.tag);
 -    if (index != -1)
 -      requiredTables.splice(index, 1);
 +    if (index != -1) {
 +      if (table.tag == "cmap")
 +        originalCMAP = table;
  
-       requiredTables.splice(index, 1);
 -    tables.push(table);
++      tables.push(table);
 +    }
-     tables.push(table);
    }
  
 -  // Tables needs to be written by ascendant alphabetic order
 -  tables.sort(function(a, b) {
 -    return a.tag > b.tag;
 -  });
 -
    // If any tables are still in the array this means some required tables are
    // missing, which means that we need to rebuild the font in order to pass
    // the sanitizer.
        0x00, 0x02  // usMaxContext
      ];
  
-         0x00, 0x00, // underlinePosition
 +    // If the font is missing a OS/2 table it's could be an old mac font
 +    // without a 3-1-4 Unicode BMP table, so let's rewrite it.
 +    var charset = aProperties.charset;
 +    var glyphs = [];
 +    for (var i = 0; i < charset.length; i++) {
 +      glyphs.push({
 +          unicode: GlyphsUnicode[charset[i]]
 +      });
 +    }
 +
 +
 +    var offsetDelta = 0;
 +
 +    // Replace the old CMAP table
 +    var rewrittedCMAP = this._createCMAPTable(glyphs);
 +    offsetDelta = rewrittedCMAP.length - originalCMAP.data.length;
 +    originalCMAP.data = rewrittedCMAP;
 +
 +    // Rewrite the 'post' table if needed
 +    var postTable = null;
 +    for (var i = 0; i < tables.length; i++) {
 +      var table = tables[i];
 +      if (table.tag == "post") {
 +        postTable = table;
 +        break;
 +      }
 +    }
 +
 +    if (!postTable) {
 +      var post = [
 +        0x00, 0x03, 0x00, 0x00, // Version number
 +        0x00, 0x00, 0x01, 0x00, // italicAngle
++       0x00, 0x00, // underlinePosition
 +        0x00, 0x00, // underlineThickness
 +        0x00, 0x00, 0x00, 0x00, // isFixedPitch
 +        0x00, 0x00, 0x00, 0x00, // minMemType42
 +        0x00, 0x00, 0x00, 0x00, // maxMemType42
 +        0x00, 0x00, 0x00, 0x00, // minMemType1
 +        0x00, 0x00, 0x00, 0x00  // maxMemType1
 +      ];
 +
 +      offsetDelta += post.length;
 +      tables.unshift({
 +        tag: "post",
 +        data: post
 +      });
 +    }
 +
      // Create a new file to hold the new version of our truetype with a new
      // header and new offsets
      var stream = aFile.stream || aFile;
        data: OS2
      });
  
 +    // Tables needs to be written by ascendant alphabetic order
 +    tables.sort(function(a, b) {
 +      return a.tag > b.tag;
 +    });
 +
      // rewrite the tables but tweak offsets
      for (var i = 0; i < tables.length; i++) {
        var table = tables[i];
          offsets.currentOffset++;
      }
  
 -    this.data = ttf;
 +    var fontData = [];
 +    for (var i = 0; i < offsets.currentOffset; i++)
 +      fontData.push(ttf[i]);
 +
 +    this.data = fontData;
      return;
 -  } else if (requiredTables.lenght) {
 +  } else if (requiredTables.length) {
-     warn("Missing " + requiredTables + " in the TrueType font");
+     error("Table " + requiredTables[0] + " is missing from the TruType font");
+   } else {
+     this.data = aFile.getBytes();
    }
-   this.data = aFile;
  };
  
  TrueType.prototype = {
      aOffsets.virtualOffset += header.length;
    },
  
-   _createTableEntry: function font_createTableEntry(aFile, aOffsets, aTag, aData) {
-     // tag
-     var tag = [
-       aTag.charCodeAt(0),
-       aTag.charCodeAt(1),
-       aTag.charCodeAt(2),
-       aTag.charCodeAt(3)
-     ];
-     // Per spec tables must be 4-bytes align so add some 0x00 if needed
-     while (aData.length & 3)
-       aData.push(0x00);
-     while (aOffsets.virtualOffset & 3)
-       aOffsets.virtualOffset++;
-     // offset
-     var offset = aOffsets.virtualOffset;
-     // length
-     var length = aData.length;
-     // checksum
-     var checksum = FontsUtils.bytesToInteger(tag) + offset + length;
-     var tableEntry = [].concat(tag,
-                                FontsUtils.integerToBytes(checksum, 4),
-                                FontsUtils.integerToBytes(offset, 4),
-                                FontsUtils.integerToBytes(length, 4));
-     aFile.set(tableEntry, aOffsets.currentOffset);
-     aOffsets.currentOffset += tableEntry.length;
-     aOffsets.virtualOffset += aData.length;
-   },
-   _readOpenTypeHeader: function tt_readOpenTypeHeader(aFile) {
-     return {
-       version: aFile.getBytes(4),
-       numTables: FontsUtils.bytesToInteger(aFile.getBytes(2)),
-       searchRange: FontsUtils.bytesToInteger(aFile.getBytes(2)),
-       entrySelector: FontsUtils.bytesToInteger(aFile.getBytes(2)),
-       rangeShift: FontsUtils.bytesToInteger(aFile.getBytes(2))
-     }
-   },
-   _readTableEntry: function tt_readTableEntry(aFile) {
-     // tag
-     var tag = aFile.getBytes(4);
-     tag = String.fromCharCode(tag[0]) +
-           String.fromCharCode(tag[1]) +
-           String.fromCharCode(tag[2]) +
-           String.fromCharCode(tag[3]);
-     var checksum = FontsUtils.bytesToInteger(aFile.getBytes(4));
-     var offset = FontsUtils.bytesToInteger(aFile.getBytes(4));
-     var length = FontsUtils.bytesToInteger(aFile.getBytes(4));
-     // Read the table associated data
-     var currentPosition = aFile.pos;
-     aFile.pos = aFile.start + offset;
-     var data = aFile.getBytes(length);
-     aFile.pos = currentPosition;
-     return {
-       tag: tag,
-       checksum: checksum,
-       length: offset,
-       offset: length,
-       data: data
-     }
-   },
 +  _createCMAPTable: function font_createCMAPTable(aGlyphs) {
-     var characters = new Uint16Array(kMaxGlyphsCount);
++    var characters = Uint16Array(65535);
 +    for (var i = 0; i < aGlyphs.length; i++)
 +      characters[aGlyphs[i].unicode] = i + 1;
 +
 +    // Separate the glyphs into continuous range of codes, aka segment.
 +    var ranges = [];
 +    var range = [];
 +    var count = characters.length;
 +    for (var i = 0; i < count; i++) {
 +      if (characters[i]) {
 +        range.push(i);
 +      } else if (range.length) {
 +        ranges.push(range.slice());
 +        range = [];
 +      }
 +    }
 +
 +    // The size in bytes of the header is equal to the size of the
 +    // different fields * length of a short + (size of the 4 parallels arrays
 +    // describing segments * length of a short).
 +    var headerSize = (12 * 2 + (ranges.length * 4 * 2));
 +
 +    var segCount = ranges.length + 1;
 +    var segCount2 = segCount * 2;
 +    var searchRange = FontsUtils.getMaxPower2(segCount) * 2;
 +    var searchEntry = Math.log(segCount) / Math.log(2);
 +    var rangeShift = 2 * segCount - searchRange;
 +    var cmap = [].concat(
 +      [
 +        0x00, 0x00, // version
 +        0x00, 0x01, // numTables
 +        0x00, 0x03, // platformID
 +        0x00, 0x01, // encodingID
 +        0x00, 0x00, 0x00, 0x0C, // start of the table record
 +        0x00, 0x04  // format
 +      ],
 +      FontsUtils.integerToBytes(headerSize, 2), // length
 +      [0x00, 0x00], // language
 +      FontsUtils.integerToBytes(segCount2, 2),
 +      FontsUtils.integerToBytes(searchRange, 2),
 +      FontsUtils.integerToBytes(searchEntry, 2),
 +      FontsUtils.integerToBytes(rangeShift, 2)
 +    );
 +
 +    // Fill up the 4 parallel arrays describing the segments.
 +    var startCount = [];
 +    var endCount = [];
 +    var idDeltas = [];
 +    var idRangeOffsets = [];
 +    var glyphsIdsArray = [];
 +    var bias = 0;
 +    for (var i = 0; i < segCount - 1; i++) {
 +      var range = ranges[i];
 +      var start = FontsUtils.integerToBytes(range[0], 2);
 +      var end = FontsUtils.integerToBytes(range[range.length - 1], 2);
 +
 +      var delta = FontsUtils.integerToBytes(((range[0] - 1) - bias) % 65536, 2);
 +      bias += range.length;
 +
 +      // deltas are signed shorts
 +      delta[0] ^= 0xFF;
 +      delta[1] ^= 0xFF;
 +      delta[1] += 1;
 +
 +      startCount.push(start[0], start[1]);
 +      endCount.push(end[0], end[1]);
 +      idDeltas.push(delta[0], delta[1]);
 +      idRangeOffsets.push(0x00, 0x00);
 +
 +      for (var j = 0; j < range.length; j++)
 +        glyphsIdsArray.push(range[j]);
 +    }
 +    startCount.push(0xFF, 0xFF);
 +    endCount.push(0xFF, 0xFF);
 +    idDeltas.push(0x00, 0x01);
 +    idRangeOffsets.push(0x00, 0x00);
 +
 +    return cmap.concat(endCount, [0x00, 0x00], startCount,
 +                       idDeltas, idRangeOffsets, glyphsIdsArray);
++  },
++
+   _createTableEntry: function font_createTableEntry(aFile, aOffsets, aTag, aData) {
+     // tag
+     var tag = [
+       aTag.charCodeAt(0),
+       aTag.charCodeAt(1),
+       aTag.charCodeAt(2),
+       aTag.charCodeAt(3)
+     ];
+     // Per spec tables must be 4-bytes align so add some 0x00 if needed
+     while (aData.length & 3)
+       aData.push(0x00);
+     while (aOffsets.virtualOffset & 3)
+       aOffsets.virtualOffset++;
+     // offset
+     var offset = aOffsets.virtualOffset;
+     // length
+     var length = aData.length;
+     // checksum
+     var checksum = FontsUtils.bytesToInteger(tag) + offset + length;
+     var tableEntry = [].concat(tag,
+                                FontsUtils.integerToBytes(checksum, 4),
+                                FontsUtils.integerToBytes(offset, 4),
+                                FontsUtils.integerToBytes(length, 4));
+     aFile.set(tableEntry, aOffsets.currentOffset);
+     aOffsets.currentOffset += tableEntry.length;
+     aOffsets.virtualOffset += aData.length;
+   },
+   _readOpenTypeHeader: function tt_readOpenTypeHeader(aFile) {
+     return {
+       version: aFile.getBytes(4),
+       numTables: FontsUtils.bytesToInteger(aFile.getBytes(2)),
+       searchRange: FontsUtils.bytesToInteger(aFile.getBytes(2)),
+       entrySelector: FontsUtils.bytesToInteger(aFile.getBytes(2)),
+       rangeShift: FontsUtils.bytesToInteger(aFile.getBytes(2))
+     }
+   },
+   _readTableEntry: function tt_readTableEntry(aFile) {
+     // tag
+     var tag = aFile.getBytes(4);
+     tag = String.fromCharCode(tag[0]) +
+           String.fromCharCode(tag[1]) +
+           String.fromCharCode(tag[2]) +
+           String.fromCharCode(tag[3]);
+     var checksum = FontsUtils.bytesToInteger(aFile.getBytes(4));
+     var offset = FontsUtils.bytesToInteger(aFile.getBytes(4));
+     var length = FontsUtils.bytesToInteger(aFile.getBytes(4));
+     // Read the table associated data
+     var currentPosition = aFile.pos;
+     aFile.pos = aFile.start + offset;
+     var data = aFile.getBytes(length);
+     aFile.pos = currentPosition;
+     return {
+       tag: tag,
+       checksum: checksum,
+       length: offset,
+       offset: length,
+       data: data
+     }
    }
  };
  
diff --cc pdf.js
index d39579f7416ad78b134ee34caffc8ba42df08422,40044300cd0ce6b35356f20e2ce52eae7544952e..2ce6b48a8ca042c3c8d4c2f2fbad9b2840a0d3c0
--- 1/pdf.js
--- 2/pdf.js
+++ b/pdf.js
@@@ -48,9 -48,17 +48,9 @@@ function shadow(obj, prop, value) 
      return value;
  }
  
 -function bytesToString(bytes) {
 -    var str = "";
 -    var length = bytes.length;
 -    for (var n = 0; n < length; ++n)
 -        str += String.fromCharCode(bytes[n]);
 -    return str;
 -}
 -
  var Stream = (function() {
      function constructor(arrayBuffer, start, length, dict) {
-         this.bytes = new Uint8Array(arrayBuffer);
+         this.bytes = Uint8Array(arrayBuffer);
          this.start = start || 0;
          this.pos = this.start;
          this.end = (start + length) || this.bytes.byteLength;
@@@ -306,9 -325,8 +306,9 @@@ var FlateStream = (function() 
              return this.buffer = buffer2;
          },
          getByte: function() {
 +            var bufferLength = this.bufferLength;
              var pos = this.pos;
-             if (bufferLength == pos) {
 -            while (this.bufferLength <= pos) {
++            if (bufferLength <= pos) {
                  if (this.eof)
                      return;
                  this.readBlock();
              return this.buffer.subarray(pos, end)
          },
          lookChar: function() {
 +            var bufferLength = this.bufferLength;
              var pos = this.pos;
-             if (bufferLength == pos) {
 -            while (this.bufferLength <= pos) {
++            if (bufferLength <= pos) {
                  if (this.eof)
                      return;
                  this.readBlock();
              return [codes, maxLen];
          },
          readBlock: function() {
 -            var bytes = this.bytes;
 -            var bytesPos = this.bytesPos;
+             function repeat(stream, array, len, offset, what) {
+                 var repeat = stream.getBits(len) + offset;
+                 while (repeat-- > 0)
+                     array[i++] = what;
+             }
 +            var stream = this.stream;
  
              // read block header
              var hdr = this.getBits(3);
      return constructor;
  })();
  
 -// A JpegStream can't be read directly. We use the platform to render the underlying
 -// JPEG data for us.
 -var JpegStream = (function() {
 -    function constructor(bytes, dict) {
 -        // TODO: per poppler, some images may have "junk" before that need to be removed
 -        this.dict = dict;
 -
 -        // create DOM image
 -        var img = new Image();
 -        img.src = "data:image/jpeg;base64," + window.btoa(bytesToString(bytes));
 -        this.domImage = img;
 -    }
 -
 -    constructor.prototype = {
 -        getImage: function() {
 -            return this.domImage;
 -        }
 -    };
 -
 -    return constructor;
 -})();
 -
+ var PredictorStream = (function() {
+     function constructor(stream, params) {
+         this.stream = stream;
 -        this.dict = stream.dict;
+         this.predictor = params.get("Predictor") || 1;
+         if (this.predictor <= 1) {
+             return stream; // no prediction
+         }
+         if (params.has("EarlyChange")) {
+             error("EarlyChange predictor parameter is not supported");
+         }
+         this.colors = params.get("Colors") || 1;
+         this.bitsPerComponent = params.get("BitsPerComponent") || 8;
+         this.columns = params.get("Columns") || 1;
+         if (this.colors !== 1 || this.bitsPerComponent !== 8) {
+             error("Multi-color and multi-byte predictors are not supported");
+         }
+         if (this.predictor < 10 || this.predictor > 15) {
+             error("Unsupported predictor");
+         }
+         this.currentRow = new Uint8Array(this.columns);
+         this.pos = 0;
+         this.bufferLength = 0;
+     }
+     constructor.prototype = {
+         readRow : function() {
+             var lastRow = this.currentRow;
+             var predictor = this.stream.getByte();
+             var currentRow = this.stream.getBytes(this.columns), i;
+             switch (predictor) {
+             default:
+                 error("Unsupported predictor");
+                 break;
+             case 0:
+                 break;
+             case 2:
+                 for (i = 0; i < currentRow.length; ++i) {
+                   currentRow[i] = (lastRow[i] + currentRow[i]) & 0xFF;
+                 }
+                 break;
+             }
+             this.pos = 0;
+             this.bufferLength = currentRow.length;
+             this.currentRow = currentRow;
+         },
+         getByte : function() {
+             if (this.pos >= this.bufferLength) {
+                this.readRow();
+             }
+             return this.currentRow[this.pos++];
+         },
+         getBytes : function(n) {
+             var i, bytes;
+             bytes = new Uint8Array(n);
+             for (i = 0; i < n; ++i) {
+               if (this.pos >= this.bufferLength) {
+                 this.readRow();
+               }
+               bytes[i] = this.currentRow[this.pos++];
+             }
+             return bytes;
+         },
+         getChar : function() {
+             return String.formCharCode(this.getByte());
+         },
+         lookChar : function() {
+             if (this.pos >= this.bufferLength) {
+                this.readRow();
+             }
+             return String.formCharCode(this.currentRow[this.pos]);
+         },
+         skip : function(n) {
+             var i;
+             if (!n) {
+                 n = 1;
+             }
+             while (n > this.bufferLength - this.pos) {
+                 n -= this.bufferLength - this.pos;
+                 this.readRow();
+                 if (this.bufferLength === 0) break;
+             }
+             this.pos += n;
+         }
+     };
+     return constructor;
+ })();
  var DecryptStream = (function() {
      function constructor(str, fileKey, encAlgorithm, keyLength) {
-         // TODO
+         TODO("decrypt stream is not implemented");
      }
  
      constructor.prototype = Stream.prototype;
@@@ -1088,9 -1217,11 +1177,11 @@@ var Parser = (function() 
                                             this.encAlgorithm,
                                             this.keyLength);
              }
-             return this.filter(stream, dict);
 -            stream = this.filter(stream, dict, length);
 -            stream.parameters = dict; 
++            stream = this.filter(stream, dict);
++            stream.parameters = dict;
+             return stream;
          },
 -        filter: function(stream, dict, length) {
 +        filter: function(stream, dict) {
              var filter = dict.get2("Filter", "F");
              var params = dict.get2("DecodeParms", "DP");
              if (IsName(filter))
              }
              return stream;
          },
 -        makeFilter: function(stream, name, length, params) {
 +        makeFilter: function(stream, name, params) {
              if (name == "FlateDecode" || name == "Fl") {
-                 if (params)
-                     error("params not supported yet for FlateDecode");
+                 if (params) {
+                     return new PredictorStream(new FlateStream(stream), params);
+                 }
                  return new FlateStream(stream);
 -            } else if (name == "DCTDecode") {
 -                var bytes = stream.getBytes(length);
 -                return new JpegStream(bytes, stream.dict);
              } else {
                  error("filter '" + name + "' not supported yet");
              }
@@@ -1273,10 -1407,11 +1364,11 @@@ var XRef = (function() 
              } else if (IsRef(obj)) {
                  // certain buggy PDF generators generate "/Prev NNN 0 R" instead
                  // of "/Prev NNN"
-                 this.prev = obj.num;
-                 more = true;
+                 prev = obj.num;
+             }
+             if (prev) {
 -              this.readXRef(prev);
++                this.readXRef(prev);
              }
-             this.trailerDict = dict;
  
              // check for 'XRefStm' key
              if (IsInt(obj = dict.get("XRefStm"))) {
                  this.xrefstms[pos] = 1; // avoid infinite recursion
                  this.readXRef(pos);
              }
-             return more;
-         },
-         readXRefStream: function(parser) {
-             error("Invalid XRef stream");
 +
+             return dict;
+         },
+         readXRefStream: function(stream) {
+             var streamParameters = stream.parameters;
+             var length = streamParameters.get("Length");
+             var byteWidths = streamParameters.get("W");
+             var range = streamParameters.get("Index");
+             if (!range)
+                 range = [0, streamParameters.get("Size")];
+             var i, j;
+             while (range.length > 0) {
+                 var first = range[0], n = range[1];
+                 if (!IsInt(first) || !IsInt(n))
+                     error("Invalid XRef range fields");
+                 var typeFieldWidth = byteWidths[0], offsetFieldWidth = byteWidths[1], generationFieldWidth = byteWidths[2];
+                 if (!IsInt(typeFieldWidth) || !IsInt(offsetFieldWidth) || !IsInt(generationFieldWidth))
+                     error("Invalid XRef entry fields length");
+                 for (i = 0; i < n; ++i) {
+                     var type = 0, offset = 0, generation = 0;
+                     for (j = 0; j < typeFieldWidth; ++j)
+                        type = (type << 8) | stream.getByte();
+                     for (j = 0; j < offsetFieldWidth; ++j)
+                        offset = (offset << 8) | stream.getByte();
+                     for (j = 0; j < generationFieldWidth; ++j)
+                        generation = (generation << 8) | stream.getByte();
+                     var entry = new Ref(offset, generation);
+                     if (typeFieldWidth > 0) {
+                         switch (type) {
+                         case 0:
+                            entry.free = true;
+                            break;
+                         case 1:
+                            entry.uncompressed = true;
+                            break;
+                         case 2:
+                            break;
+                         default:
+                            error("Invalid XRef entry type");
+                            break;
+                         }
+                     }
+                     if (!this.entries[first + i])
+                         this.entries[first + i] = entry;
+                 }
+                 range.splice(0, 2);
+             }
+             var prev = streamParameters.get("Prev");
+             if (IsInt(prev))
+                 this.readXRef(prev);
+             return streamParameters;
          },
          readXRef: function(startXRef) {
              var stream = this.stream;
                      this.cache[num] = e;
                  return e;
              }
-             error("compressed entry");
++
+             // compressed entry
+             stream = this.fetch(new Ref(e.offset, 0));
+             if (!IsStream(stream))
+                 error("bad ObjStm stream");
+             var first = stream.parameters.get("First");
+             var n = stream.parameters.get("N");
+             if (!IsInt(first) || !IsInt(n)) {
+                 error("invalid first and n parameters for ObjStm stream");
+             }
+             parser = new Parser(new Lexer(stream), false);
+             var i, entries = [], nums = [];
+             // read the object numbers to populate cache
+             for (i = 0; i < n; ++i) {
+                 var num = parser.getObj();
+                 if (!IsInt(num)) {
+                     error("invalid object number in the ObjStm stream");
+                 }
+                 nums.push(num);
+                 var offset = parser.getObj();
+                 if (!IsInt(offset)) {
+                     error("invalid object offset in the ObjStm stream");
+                 }
+             }
+             // read stream objects for cache
+             for (i = 0; i < n; ++i) {
+                 entries.push(parser.getObj());
+                 this.cache[nums[i]] = entries[i];
+             }
+             e = entries[e.gen];
+             if (!e) {
+                 error("bad XRef entry for compressed object");
+             }
+             return e;
          },
          getCatalogObj: function() {
              return this.fetch(this.root);
@@@ -1389,20 -1602,39 +1561,40 @@@ var Page = (function() 
                                               : null));
          },
          compile: function(gfx, fonts) {
-             if (!this.code) {
-                 var xref = this.xref;
-                 var content = xref.fetchIfRef(this.content);
-                 var resources = xref.fetchIfRef(this.resources);
+             if (this.code) {
+                 // content was compiled
+                 return;
+             }
++
+             var xref = this.xref;
+             var content;
+             var resources = xref.fetchIfRef(this.resources);
+             if (!IsArray(this.content)) {
+                 // content is not an array, shortcut
+                 content = xref.fetchIfRef(this.content);
                  this.code = gfx.compile(content, xref, resources, fonts);
+                 return;
+             }
+             // the content is an array, compiling all items
+             var i, n = this.content.length, compiledItems = [];
+             for (i = 0; i < n; ++i) {
+                 content = xref.fetchIfRef(this.content[i]);
+                 compiledItems.push(gfx.compile(content, xref, resources, fonts));
              }
+             // creating the function that executes all compiled items
+             this.code = function(gfx) {
+                 var i, n = compiledItems.length;
+                 for (i = 0; i < n; ++i) {
+                     compiledItems[i](gfx);
+                 }
+             };
          },
          display: function(gfx) {
+             assert(this.code instanceof Function, "page content must be compiled first");
              var xref = this.xref;
-             var content = xref.fetchIfRef(this.content);
              var resources = xref.fetchIfRef(this.resources);
              var mediaBox = xref.fetchIfRef(this.mediaBox);
-             assertWellFormed(IsStream(content) && IsDict(resources),
-                              "invalid page content or resources");
+             assertWellFormed(IsDict(resources), "invalid page resources");
              gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1],
                                 width: mediaBox[2] - mediaBox[0],
                                 height: mediaBox[3] - mediaBox[1] });
@@@ -1922,9 -2151,11 +2118,10 @@@ var CanvasGraphics = (function() 
  
                      // Get the font charset if any
                      var charset = descriptor.get("CharSet");
-                     assertWellFormed(IsString(charset), "invalid charset");
 -                    if (charset) {
++                    if (charset)
+                         assertWellFormed(IsString(charset), "invalid charset");
  
 -                        charset = charset.split("/");
 -                    }
 +                    charset = charset.split("/");
                  } else if (IsName(encoding)) {
                      var encoding = Encodings[encoding.name];
                      if (!encoding)
                              charset.push(encoding[j + firstChar]);
                      }
                  }
-                     var cmap = cmapObj.getBytes(cmapObj.length);
 +            } else if (fontDict.has("ToUnicode")) {
 +                var cmapObj = xref.fetchIfRef(fontDict.get("ToUnicode"));
 +                if (IsName(cmapObj)) {
 +                    error("ToUnicode file cmap translation not implemented");
 +                } else if (IsStream(cmapObj)) {
 +                    var encoding = Encodings["WinAnsiEncoding"];
 +                    var firstChar = xref.fetchIfRef(fontDict.get("FirstChar"));
 +                    for (var i = firstChar; i < encoding.length; i++)
 +                        encodingMap[i] = new Name(encoding[i]);
 +
 +                    var tokens = [];
 +                    var token = "";
 +
++                    var buffer = cmapObj.ensureBuffer();
++                    var cmap = cmapObj.getBytes(buffer.byteLength);
 +                    for (var i =0; i < cmap.length; i++) {
 +                      var byte = cmap[i];
 +                      if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) {
 +                        switch (token) {
 +                          case "useCMap":
 +                            error("useCMap is not implemented");
 +                            break;
 +
 +                          case "beginbfrange":
 +                            ignoreFont = false;
 +                          case "begincodespacerange":
 +                            token = "";
 +                            tokens = [];
 +                            break;
 +
 +                          case "endcodespacerange":
 +                            TODO("Support CMap ranges");
 +                            break;
 +
 +                          case "endbfrange":
 +                            for (var j = 0; j < tokens.length; j+=3) {
 +                              var startRange = parseInt("0x" + tokens[j]);
 +                              var endRange = parseInt("0x" + tokens[j+1]);
 +                              var code = parseInt("0x" + tokens[j+2]);
 +
 +                              for (var k = startRange; k <= endRange; k++) {
 +                                encodingMap[k] = GlyphsUnicode[encoding[code]];
 +                                charset.push(encoding[code++]);
 +                              }
 +                            }
 +                            break;
 +
 +                          case "beginfbchar":
 +                          case "endfbchar":
 +                            error("fbchar parsing is not implemented");
 +                            break;
 +
 +                          default:
 +                            if (token.length) {
 +                              tokens.push(token);
 +                              token = "";
 +                            }
 +                            break;
 +                        }
 +                    } else if (byte == 0x5B || byte == 0x5D) {
 +                        error("CMAP list parsing is not implemented");
 +                    } else {
 +                        token += String.fromCharCode(byte);
 +                    }
 +                  }
 +               }
              }
  
              var subType = fontDict.get("Subtype");
          },
          setFillColorN: function(/*...*/) {
              // TODO real impl
-             this.setFillColor.apply(this, arguments);
+             var colorSpace = this.current.colorSpace;
+             if (!colorSpace) {
+                 var stateStack = this.stateStack;
+                 var i = stateStack.length - 1;
+                 while (!colorSpace && i >= 0) {
+                     colorSpace = stateStack[i--].colorSpace;
+                 }
+             }
+             if (this.current.colorSpace == "Pattern") {
+                 var patternName = arguments[0];
+                 if (IsName(patternName)) {
+                     var xref = this.xref;
+                     var patternRes = xref.fetchIfRef(this.res.get("Pattern"));
+                     if (!patternRes)
+                         error("Unable to find pattern resource");
+                     var pattern = xref.fetchIfRef(patternRes.get(patternName.name));
 -                   
++
+                     const types = [null, this.tilingFill];
+                     var typeNum = pattern.dict.get("PatternType");
+                     var patternFn = types[typeNum];
+                     if (!patternFn)
+                         error("Unhandled pattern type");
+                     patternFn.call(this, pattern);
+                 }
+             } else {
+                 // TODO real impl
+                 this.setFillColor.apply(this, arguments);
+             }
+         },
+         tilingFill: function(pattern) {
+             function applyMatrix(point, m) {
+                 var x = point[0] * m[0] + point[1] * m[2] + m[4];
+                 var y = point[0] * m[1] + point[1] * m[3] + m[5];
+                 return [x,y];
+             };
+             function multiply(m, tm) {
+                 var a = m[0] * tm[0] + m[1] * tm[2];
+                 var b = m[0] * tm[1] + m[1] * tm[3];
+                 var c = m[2] * tm[0] + m[3] * tm[2];
+                 var d = m[2] * tm[1] + m[3] * tm[3];
+                 var e = m[4] * tm[0] + m[5] * tm[2] + tm[4];
+                 var f = m[4] * tm[1] + m[5] * tm[3] + tm[5];
+                 return [a, b, c, d, e, f]
+             };
+             this.save();
+             var dict = pattern.dict;
+             var ctx = this.ctx;
+             var paintType = dict.get("PaintType");
+             switch (paintType) {
+             case PAINT_TYPE_COLORED:
+                 // should go to default for color space
+                 ctx.fillStyle = this.makeCssRgb(1, 1, 1);
+                 ctx.strokeStyle = this.makeCssRgb(0, 0, 0);
+                 break;
+             case PAINT_TYPE_UNCOLORED:
+             default:
+                 error("Unsupported paint type");
+             }
+             TODO("TilingType");
+             var matrix = dict.get("Matrix") || IDENTITY_MATRIX;
+             var bbox = dict.get("BBox");
+             var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
+             var xstep = dict.get("XStep");
+             var ystep = dict.get("YStep");
+             // top left corner should correspond to the top left of the bbox
+             var topLeft = applyMatrix([x0,y0], matrix);
+             // we want the canvas to be as large as the step size
+             var botRight = applyMatrix([x0 + xstep, y0 + ystep], matrix);
 -            
++
+             var tmpCanvas = document.createElement("canvas");
+             tmpCanvas.width = Math.ceil(botRight[0] - topLeft[0]);
+             tmpCanvas.height = Math.ceil(botRight[1] - topLeft[1]);
 -          
++
+             // set the new canvas element context as the graphics context
+             var tmpCtx = tmpCanvas.getContext("2d");
+             var savedCtx = ctx;
+             this.ctx = tmpCtx;
+             // normalize transform matrix so each step
+             // takes up the entire tmpCanvas (need to remove white borders)
+             if (matrix[1] === 0 && matrix[2] === 0) {
 -                matrix[0] = tmpCanvas.width / xstep;
++               matrix[0] = tmpCanvas.width / xstep;
+                 matrix[3] = tmpCanvas.height / ystep;
+                 topLeft = applyMatrix([x0,y0], matrix);
+             }
+             // move the top left corner of bounding box to [0,0]
+             matrix = multiply(matrix, [1, 0, 0, 1, -topLeft[0], -topLeft[1]]);
 -            
++
+             this.transform.apply(this, matrix);
 -            
++
+             if (bbox && IsArray(bbox) && 4 == bbox.length) {
+                 this.rectangle.apply(this, bbox);
+                 this.clip();
+                 this.endPath();
+             }
+             var xref = this.xref;
+             var res = xref.fetchIfRef(dict.get("Resources"));
+             if (!pattern.code)
+                 pattern.code = this.compile(pattern, xref, res, []);
+             this.execute(pattern.code, xref, res);
 -           
++
+             this.ctx = savedCtx;
+             this.restore();
+             TODO("Inverse pattern is painted");
+             var pattern = this.ctx.createPattern(tmpCanvas, "repeat");
+             this.ctx.fillStyle = pattern;
          },
          setStrokeGray: function(gray) {
              this.setStrokeRGBColor(gray, gray, gray);
              var fn = new PDFFunction(this.xref, fnObj);
  
              var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1);
 -            
++
+             // 10 samples seems good enough for now, but probably won't work
+             // if there are sharp color changes. Ideally, we would implement
+             // the spec faithfully and add lossless optimizations.
              var step = (t1 - t0) / 10;
--            
++
              for (var i = t0; i <= t1; i += step) {
                  var c = fn.func([i]);
                  gradient.addColorStop(i, this.makeCssRgb.apply(this, c));
              }
  
              this.ctx.fillStyle = gradient;
--            
++
              // 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
Simple merge