]> git.parisson.com Git - pdf.js.git/commitdiff
Merge from gal's master branch (got a regression on the mapping between char->glyph)
authorVivien Nicolas <21@vingtetun.org>
Thu, 16 Jun 2011 01:05:55 +0000 (03:05 +0200)
committerVivien Nicolas <21@vingtetun.org>
Thu, 16 Jun 2011 01:05:55 +0000 (03:05 +0200)
1  2 
PDFFont.js
pdf.js
test.js

diff --cc PDFFont.js
index 8c3abc7ec5c547f489de357ad03b90c26e27444d,0000000000000000000000000000000000000000..d106e0b23812b2c82cb53404f0ee5bf02e47f497
mode 100644,000000..100644
--- /dev/null
@@@ -1,1551 -1,0 +1,1551 @@@
-   set active(aFontName) {
-     this._active = this[aFontName];
 +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
 +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 +
 +/**
 + * Maximum file size of the font.
 + */
 +var kMaxFontFileSize = 40000;
 +
 +/**
 + * Maximum number of glyphs per font.
 +*/
 +var kMaxGlyphsCount = 65526;
 +
 +/**
 + * Maximum time to wait for a font to be loaded by @font-face
 + */
 +var kMaxWaitForFontFace = 1000;
 +
 + /*
 +  * Useful for debugging when you want to certains operations depending on how
 +  * many fonts are loaded.
 +  */
 +var fontCount = 0;
 +
 +/**
 + * Hold a map of decoded fonts and of the standard fourteen Type1 fonts and
 + * their acronyms.
 + * TODO Add the standard fourteen Type1 fonts list by default
 + *      http://cgit.freedesktop.org/poppler/poppler/tree/poppler/GfxFont.cc#n65
 + */
 +var Fonts = {
 +  _active: null,
 +  get active() {
 +    return this._active || { encoding: {} };
 +  },
 +
++  set active(aName) {
++    this._active = this[aName];
 +  },
 +
 +  unicodeFromCode: function fonts_unicodeFromCode(aCode) {
 +    var unicode = GlyphsUnicode[this.active.encoding[aCode]];
 +    return unicode ? "0x" + unicode : aCode;
 +  }
 +};
 +
 +/**
 + * '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", binaryData, aFontEncoding, "Type1");
 + *   type1Font.bind();
 + *
 + * As an improvment the last parameter can be replaced by an automatic guess
 + * of the font type based on the first byte of the file.
 + *
 + * XXX There is now too many parameters, this should be turned into an
 + * object containing all the required informations about the font
 + */
 +var Font = function(aName, aFile, aEncoding, aCharset, aBBox, aType) {
 +  this.name = aName;
 +
 +  // If the font has already been decoded simply return
 +  if (Fonts[aName]) {
 +    this.font = Fonts[aName].data;
 +    return;
 +  }
 +  fontCount++;
 +
 +  var start = Date.now();
 +  switch (aType) {
 +    case "Type1":
 +      var cff = new CFF(aName, aBBox, aFile);
 +      this.mimetype = "font/otf";
 +
 +      // Wrap the CFF data inside an OTF font file
 +      this.font = this.cover(cff);
 +      break;
 +
 +    case "TrueType":
 +      return Fonts[aName] = {
 +        data: null,
 +        encoding: {},
 +        charset: null,
 +        loading: false
 +      };
 +
 +      // TrueType is disabled for the moment since the sanitizer prevent it
 +      // from loading
 +      this.mimetype = "font/ttf";
 +      var ttf = new TrueType(aFile);
 +      this.font = ttf.data;
 +      break;
 +
 +    default:
 +      warn("Font " + aType + " is not supported");
 +      break;
 +  }
 +  var end = Date.now();
 +
 +  Fonts[aName] = {
 +    data: this.font,
 +    encoding: aEncoding,
 +    charset: aCharset ? aCharset.slice() : null,
 +    loading: true
 +  }
 +
 +  // Attach the font to the document
 +  this.bind();
 +};
 +
 +Font.prototype = {
 +  name: null,
 +  font: null,
 +  mimetype: 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);
 +    }
 +
 +    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 || [];
 +    // 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 = new Number("0x" + GlyphsUnicode[charset[i]]);
 +        if (!unicode)
 +          error("Unicode for " + charset[i] + " is has not been found in the glyphs list");
 +        testString += String.fromCharCode(unicode);
 +      }
 +    }
 +    ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
 +    var textWidth = ctx.mozMeasureText(testString);
 +
 +    if (debug)
 +      ctx.fillText(testString, 20, 20);
 +
 +    var start = Date.now();
 +    var interval = window.setInterval(function(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.mozMeasureText(testString)) {
 +        window.clearInterval(interval);
 +        Fonts[fontName].loading = false;
 +      }
 +
 +      if (debug)
 +        ctx.fillText(testString, 20, 50);
 +    }, 20, 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;
 +
 +    // Per spec tables must be 4-bytes align so add some 0x00 if needed
 +    while (aData.length & 3)
 +      aData.push(0x00);
 +
 +    // 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;
 +  },
 +
 +  _createCMAPTable: function font_createCMAPTable(aGlyphs) {
 +    var characters = new Array(kMaxGlyphsCount);
 +    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 = [];
 +    for (var i = 0; i < characters.length; 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);
 +  },
 +
 +  cover: function font_cover(aFont) {
 +    var otf = new Uint8Array(kMaxFontFileSize);
 +
 +    // 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
 +    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(aFont.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++) {
 +      // XXX this can easily broke
 +      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 */
 +    name = [
 +      0x00, 0x00, // format
 +      0x00, 0x00, // Number of names Record
 +      0x00, 0x00  // Storage
 +    ];
 +    this._createTableEntry(otf, offsets, "name", name);
 +
 +    /** POST */
 +    // XXX get those info from the Font dict!
 +    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);
 +
 +    // 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]);
 +
 +    //writeToFile(fontData, "/tmp/pdf.js." + fontCount + ".otf");
 +    return fontData;
 +  }
 +};
 +
 +
 +var FontsUtils = {
 +  integerToBytes: function fu_integerToBytes(aValue, aBytesCount) {
 +    var bytes = [];
 +    for (var i = 0; i < aBytesCount; i++)
 +      bytes[i] = 0x00;
 +
 +    do {
 +      bytes[--aBytesCount] = (aValue & 0xFF);
 +      aValue = aValue >> 8;
 +    } while (aBytesCount && aValue > 0);
 +
 +    return bytes;
 +  },
 +
 +  bytesToInteger: function(aBytesArray) {
 +    var value = 0;
 +    for (var i = 0; i < aBytesArray.length; i++)
 +      value = (value << 8) + aBytesArray[i];
 +    return value;
 +  },
 +
 +  getMaxPower2: function fu_getMaxPower2(aNumber) {
 +    var maxPower = 0;
 +    var value = aNumber;
 +    while (value >= 2) {
 +      value /= 2;
 +      maxPower++;
 +    }
 +
 +    value = 2;
 +    for (var i = 1; i < maxPower; i++)
 +      value *= 2;
 +    return value;
 +  }
 +};
 +
 +
 +/** Implementation dirty logic starts here */
 +
 +/**
 + * At the moment TrueType is just a stub that does mostly nothing but in a
 + * (near?) future this class will rewrite the font to ensure it is well formed
 + * and valid in the point of view of the sanitizer.
 + */
 +var TrueType = function(aFile) {
 +  var header = this._readOpenTypeHeader(aFile);
 +  var numTables = header.numTables;
 +
 +  // Check that required tables are present
 +  var requiredTables = [
 +    "OS/2",
 +    "cmap",
 +    "head",
 +    "hhea",
 +    "hmtx",
 +    "maxp",
 +    "name",
 +    "post"
 +  ];
 +
 +  var tables = [];
 +  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);
 +
 +    tables.push(table);
 +  }
 +  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.
 +  if (requiredTables.length && requiredTables[0] == "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
 +    ];
 +
 +    // Create a new file to hold the new version of our truetype with a new
 +    // header and new offsets
 +    var stream = aFile.stream || aFile;
 +    var ttf = new Uint8Array(stream.length + 16 + OS2.length);
 +
 +    // The new numbers of tables will be the last one plus the num of missing
 +    // tables
 +    var numTables = header.numTables + 1;
 +
 +    // 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: numTables * (4 * 4)
 +    };
 +
 +    // Write the sfnt header with one more table
 +    this._createOpenTypeHeader(ttf, offsets, numTables);
 +
 +    // Insert the missing table
 +    tables.unshift({
 +      tag: "OS/2",
 +      data: OS2
 +    });
 +
 +    // rewrite the tables but tweak offsets
 +    for (var i = 0; i < tables.length; i++) {
 +      var table = tables[i];
 +      var data = [];
 +
 +      var tableData = table.data;
 +      for (var j = 0; j < tableData.length; j++)
 +        data.push(tableData[j]);
 +      this._createTableEntry(ttf, offsets, table.tag, data);
 +    }
 +
 +    // Add the table datas
 +    for (var i = 0; i < tables.length; i++) {
 +      var table = tables[i];
 +      var tableData = table.data;
 +      ttf.set(tableData, offsets.currentOffset);
 +      offsets.currentOffset += tableData.length;
 +
 +      if (0) {
 +        var data = [];
 +        for (var j = 0; j < tableData.length; j++)
 +          d.push(tableData[j]);
 +        log("data for table: " + table.tag + ": " + data);
 +      }
 +
 +      // 4-byte aligned data
 +      while (offsets.currentOffset & 3)
 +        offsets.currentOffset++;
 +    }
 +
 +    var fontData = [];
 +    for (var i = 0; i < ttf.length; i++)
 +      fontData.push(ttf[i]);
 +
 +    this.data = ttf;
 +    //writeToFile(fontData, "/tmp/pdf.js." + fontCount + ".ttf");
 +    return;
 +  } else if (requiredTables.lenght) {
 +    error("Table " + requiredTables[0] + " is missing from the TruType font");
 +  } else {
 +    this.data = aFile;
 +  }
 +};
 +
 +TrueType.prototype = {
 +  _createOpenTypeHeader: function tt_createOpenTypeHeader(aFile, aOffsets, aNumTables) {
 +    // sfnt version (4 bytes)
 +    // XXX if we want to merge this function and the one from the Font class
 +    // XXX this need to be adapted
 +    var version = [0x00, 0x01, 0x00, 0X00];
 +
 +    // 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)
 +    ];
 +
 +    // 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
 +    }
 +  }
 +};
 +
 +/**
 + * This dictionary holds decoded fonts data.
 + */
 +var PSFonts = new Dict();
 +
 +var Stack = function(aStackSize) {
 +  var innerStack = new Array(aStackSize || 0);
 +
 +  this.push = function(aOperand) {
 +    innerStack.push(aOperand);
 +  };
 +
 +  this.pop = function() {
 +    if (!this.count())
 +      throw new Error("stackunderflow");
 +    return innerStack.pop();
 +  };
 +
 +  this.peek = function() {
 +    if (!this.count())
 +      return null;
 +    return innerStack[innerStack.length - 1];
 +  };
 +
 +  this.get = function(aIndex) {
 +    return innerStack[aIndex];
 +  };
 +
 +  this.clear = function() {
 +    innerStack = [];
 +  };
 +
 +  this.count = function() {
 +    return innerStack.length;
 +  };
 +
 +  this.dump = function() {
 +    for (var i = 0; i < this.length; i++)
 +      log(innerStack[i]);
 +  };
 +
 +  this.clone = function() {
 +    return innerStack.slice();
 +  };
 +};
 +
 +var Type1Parser = function() {
 +  // Turn on this flag for additional debugging logs
 +  var debug = false;
 +
 +  var dump = function(aData) {
 +    if (debug)
 +      log(aData);
 +  };
 +
 +  /*
 +   * 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 kEexecEncryptionKey = 55665;
 +  var kCharStringsEncryptionKey = 4330;
 +
 +  function decrypt(aStream, aKey, aDiscardNumber, aByteArray) {
 +    var start = Date.now();
 +    var r = aKey, c1 = 52845, c2 = 22719;
 +    var decryptedString = [];
 +
 +    var value = "";
 +    var count = aStream.length;
 +    for (var i = 0; i < count; i++) {
 +      value = aStream.getByte();
 +      if (aByteArray)
 +        decryptedString[i] = value ^ (r >> 8);
 +      else
 +        decryptedString[i] = String.fromCharCode(value ^ (r >> 8));
 +      r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
 +    }
 +    var end = Date.now();
 +    dump("Time to decrypt string of length " + count + " is " + (end - start));
 +    return decryptedString.slice(aDiscardNumber);
 +  };
 +
 +  /*
 +   * 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 charStringDictionary = {
 +    "1": "hstem",
 +    "3": "vstem",
 +    "4": "vmoveto",
 +    "5": "rlineto",
 +    "6": "hlineto",
 +    "7": "vlineto",
 +    "8": "rrcurveto",
 +    "9": "closepath",
 +    "10": "callsubr",
 +    "11": "return",
 +    "12": {
 +      "0": "dotsection",
 +      "1": "vstem3",
 +      "3": "hstem3",
 +      "6": "seac",
 +      "7": "sbw",
 +      "12": "div",
 +      "16": "callothersubr",
 +      "17": "pop",
 +      "33": "setcurrentpoint"
 +    },
 +    "13": "hsbw",
 +    "14": "endchar",
 +    "21": "rmoveto",
 +    "22": "hmoveto",
 +    "30": "vhcurveto",
 +    "31": "hvcurveto"
 +  };
 +
 +  function decodeCharString(aStream) {
 +    var start = Date.now();
 +    var charString = [];
 +
 +    var value = "";
 +    var count = aStream.length;
 +    for (var i = 0; i < count; i++) {
 +      value = aStream.getByte();
 +
 +      if (value < 32) {
 +        if (value == 12) {
 +          value = charStringDictionary["12"][aStream.getByte()];
 +          i++;
 +        } else {
 +          value = charStringDictionary[value];
 +        }
 +      } else if (value <= 246) {
 +        value = parseInt(value) - 139;
 +      } else if (value <= 250) {
 +        value = ((value - 247) * 256) + parseInt(aStream.getByte()) + 108;
 +        i++;
 +      } else if (value <= 254) {
 +        value = -((value - 251) * 256) - parseInt(aStream.getByte()) - 108;
 +        i++;
 +      } else {
 +        var byte = aStream.getByte();
 +        var high = (byte >> 1);
 +        value = (byte - high) << 24 | aStream.getByte() << 16 |
 +                aStream.getByte() << 8 | aStream.getByte();
 +        i += 4;
 +      }
 +
 +      charString.push(value);
 +    }
 +
 +    var end = Date.now();
 +    dump("Time to decode charString of length " + count + " is " + (end - start));
 +    return charString;
 +  };
 +
 +  /*
 +   * The operand stack holds arbitrary PostScript objects that are the operands
 +   * and results of PostScript operators being executed. The interpreter pushes
 +   * objects on the operand stack when it encounters them as literal data in a
 +   * program being executed. When an operator requires one or more operands, it
 +   * obtains them by popping them off the top of the operand stack. When an
 +   * operator returns one or more results, it does so by pushing them on the
 +   * operand stack.
 +   */
 +   var operandStack = new Stack(40);
 +
 +   // Flag indicating if the topmost operand of the operandStack is an array
 +   var operandIsArray = 0;
 +
 +  /*
 +   * The dictionary stack holds only dictionary objects. The current set of
 +   * dictionaries on the dictionary stack defines the environment for all
 +   * implicit name searches, such as those that occur when the interpreter
 +   * encounters an executable name. The role of the dictionary stack is
 +   * introduced in Section 3.3, “Data Types and Objects,” and is further
 +   * explained in Section 3.5, “Execution.” of the PostScript Language
 +   * Reference.
 +   */
 +  var systemDict = new Dict(),
 +      globalDict = new Dict(),
 +      userDict   = new Dict();
 +
 +  var dictionaryStack = new Stack();
 +  dictionaryStack.push(systemDict);
 +  dictionaryStack.push(globalDict);
 +  dictionaryStack.push(userDict);
 +
 +  /*
 +   * The execution stack holds executable objects (mainly procedures and files)
 +   * that are in intermediate stages of execution. At any point in the
 +   * execution of a PostScript program, this stack represents the program’s
 +   * call stack. Whenever the interpreter suspends execution of an object to
 +   * execute some other object, it pushes the new object on the execution
 +   * stack. When the interpreter finishes executing an object, it pops that
 +   * object off the execution stack and resumes executing the suspended object
 +   * beneath it.
 +   */
 +  var executionStack = new Stack();
 +
 +  /*
 +   * Return the next token in the execution stack
 +   */
 +  function nextInStack() {
 +    var currentProcedure = executionStack.peek();
 +    var command = currentProcedure.shift();
 +    if (!currentProcedure.length)
 +      executionStack.pop();
 +    return command;
 +  };
 +
 +  /**
 +   * Returns an object containing a Subrs array and a CharStrings array
 +   * extracted from and eexec encrypted block of data
 +   */
 +  this.extractFontInfo = function(aStream) {
 +    var eexecString = decrypt(new Stream(aStream), kEexecEncryptionKey, 4, true);
 +    var subrs = [],  glyphs = [];
 +    var inSubrs = inGlyphs = false;
 +    var glyph = "";
 +
 +    var token = "";
 +    var index = 0;
 +    var length = 0;
 +
 +    var count = eexecString.length;
 +    var c = "";
 +    for (var i = 0; i < count; i++) {
 +      var c = eexecString[i];
 +
 +      if (inSubrs && c == 0x52) {
 +        length = parseInt(length);
 +        var stream = new Stream(eexecString.slice(i + 3, i + 3 + length));
 +        var encodedSubr = decrypt(stream, kCharStringsEncryptionKey, 4).join("");
 +        var subr = decodeCharString(new StringStream(encodedSubr));
 +
 +        subrs.push(subr);
 +        i += 3 + length;
 +      } else if (inGlyphs && c == 0x52) {
 +        length = parseInt(length);
 +        var stream = new Stream(eexecString.slice(i + 3, i + 3 + length));
 +        var encodedCharstring = decrypt(stream, kCharStringsEncryptionKey, 4).join("");
 +        var subr = decodeCharString(new StringStream(encodedCharstring));
 +
 +        glyphs.push({
 +            glyph: glyph,
 +            data: subr
 +        });
 +        i += 3 + length;
 +      } else if (inGlyphs && c == 0x2F) {
 +        token = "";
 +        glyph = "";
 +
 +        while ((c = eexecString[++i]) != 0x20)
 +          glyph += String.fromCharCode(c);
 +      } else if (!inSubrs && !inGlyphs && c == 0x2F && eexecString[i+1] == 0x53) {
 +        while ((c = eexecString[++i]) != 0x20) {};
 +        inSubrs = true;
 +      } else if (c == 0x20) {
 +        index = length;
 +        length = token;
 +        token = "";
 +      } else if (c == 0x2F && eexecString[i+1] == 0x43 && eexecString[i+2] == 0x68) {
 +        while ((c = eexecString[++i]) != 0x20) {};
 +        inSubrs = false;
 +        inGlyphs = true;
 +      } else {
 +        token += String.fromCharCode(c);
 +      }
 +    }
 +    return {
 +      subrs: subrs,
 +      charstrings: glyphs
 +    }
 +  };
 +
 +  /*
 +   * Flatten the commands by interpreting the postscript code and replacing
 +   * every 'callsubr', 'callothersubr' by the real commands.
 +   * At the moment OtherSubrs are not fully supported and only otherSubrs 0-4
 +   * as descrived in 'Using Subroutines' of 'Adobe Type 1 Font Format',
 +   * chapter 8.
 +   */
 +  this.flattenCharstring = function(aCharstring, aSubrs) {
 +    operandStack.clear();
 +    executionStack.clear();
 +    executionStack.push(aCharstring.slice());
 +
 +    var leftSidebearing = 0;
 +    var lastPoint = 0;
 +    while (true) {
 +      var obj = nextInStack();
 +      if (IsInt(obj) || IsBool(obj)) {
 +        operandStack.push(obj);
 +      } else {
 +        switch (obj) {
 +          case "vstem3":
 +          case "hstem3":
 +            operandStack.push(obj.slice(0, 5));
 +            break;
 +
 +          case "callsubr":
 +            var index = operandStack.pop();
 +            executionStack.push(aSubrs[index].slice());
 +            break;
 +
 +          case "callothersubr":
 +            var index = operandStack.pop();
 +            var count = operandStack.pop();
 +            var data = operandStack.pop();
 +            // XXX The callothersubr needs to support at least the 3 defaults
 +            // otherSubrs of the spec
 +            if (index != 3)
 +              error("callothersubr for index: " + index);
 +            operandStack.push(3);
 +            operandStack.push("callothersubr");
 +            break;
 +
 +          case "div":
 +            var num2 = operandStack.pop();
 +            var num1 = operandStack.pop();
 +            operandStack.push(num2 / num1);
 +            break;
 +
 +          case "pop":
 +            operandStack.pop();
 +            break;
 +
 +          case "closepath":
 +          case "return":
 +            break;
 +
 +          case "hsbw":
 +            var charWidthVector = operandStack.pop();
 +            var leftSidebearing = operandStack.pop();
 +            operandStack.push(charWidthVector);
 +
 +            if (leftSidebearing) {
 +              operandStack.push(leftSidebearing);
 +              operandStack.push("hmoveto");
 +            }
 +            break;
 +
 +          case "endchar":
 +            operandStack.push("endchar");
 +            return operandStack.clone();
 +
 +          case "setcurrentpoint":
 +          case "dotsection":
 +          case "seac":
 +          case "sbw":
 +            error(obj + " parsing is not implemented (yet)");
 +            break;
 +
 +          default:
 +            operandStack.push(obj);
 +            break;
 +        }
 +      }
 +    }
 +  }
 +};
 +
 +var CFF = function(aFontName, aFontBBox, aFontFile) {
 +  // Get the data block containing glyphs and subrs informations
 +  var length1 = aFontFile.dict.get("Length1");
 +  var length2 = aFontFile.dict.get("Length2");
 +  aFontFile.skip(length1);
 +  var eexecBlock = aFontFile.getBytes(length2);
 +
 +  // Extract informations from it
 +  var start = Date.now();
 +  var parser = new Type1Parser();
 +  var fontInfo = parser.extractFontInfo(eexecBlock);
 +  fontInfo.name = aFontName;
 +  fontInfo.bbox = aFontBBox;
 +
 +  // XXX
 +  this.glyphs = fontInfo.charstrings;
 +
 +  this.data = this.convertToCFF(fontInfo);
 +  var end = Date.now();
 +  //log("Time to parse font is:" + (end - start));
 +};
 +
 +CFF.prototype = {
 +  createCFFIndexHeader: function(aObjects, aIsByte) {
 +    var data = [];
 +
 +    // First 2 bytes contains the number of objects contained into this index
 +    var count = aObjects.length;
 +    if (count ==0)
 +      return [0x00, 0x00, 0x00];
 +
 +    var bytes = FontsUtils.integerToBytes(count, 2);
 +    for (var i = 0; i < bytes.length; i++)
 +      data.push(bytes[i]);
 +
 +    // Next byte contains the offset size use to reference object in the file
 +    // Actually we're using 0x04 to be sure to be able to store everything
 +    // without thinking of it while coding.
 +    data.push(0x04);
 +
 +    // Add another offset after this one because we need a new offset
 +    var relativeOffset = 1;
 +    for (var i = 0; i < count + 1; i++) {
 +      var bytes = FontsUtils.integerToBytes(relativeOffset, 4);
 +      for (var j = 0; j < bytes.length; j++)
 +        data.push(bytes[j]);
 +
 +      if (aObjects[i])
 +        relativeOffset += aObjects[i].length;
 +    }
 +
 +    for (var i =0; i < count; i++) {
 +      for (var j = 0; j < aObjects[i].length; j++)
 +        data.push(aIsByte ? aObjects[i][j] : aObjects[i].charCodeAt(j));
 +    }
 +    return data;
 +  },
 +
 +  encodeNumber: function(aValue) {
 +    var x = 0;
 +    // XXX we don't really care about Type2 optimization here...
 +    if (aValue >= -32768 && aValue <= 32767) {
 +      return [
 +        28,
 +        FontsUtils.integerToBytes(aValue >> 8, 1),
 +        FontsUtils.integerToBytes(aValue, 1)
 +      ];
 +    } else if (aValue >= (-2147483647-1) && aValue <= 2147483647) {
 +      return [
 +        0xFF,
 +        FontsUtils.integerToBytes(aValue >> 24, 1),
 +        FontsUtils.integerToBytes(aValue >> 16, 1),
 +        FontsUtils.integerToBytes(aValue >> 8, 1),
 +        FontsUtils.integerToBytes(aValue, 1)
 +      ];
 +    } else {
 +      error("Value: " + aValue + " is not allowed");
 +    }
 +  },
 +
 +  getOrderedCharStrings: function(aGlyphs) {
 +    var charstrings = [];
 +
 +    for (var i = 0; i < aGlyphs.length; i++) {
 +      var glyph = aGlyphs[i].glyph;
 +      var unicode = GlyphsUnicode[glyph];
 +      if (!unicode) {
 +        if (glyph != ".notdef")
 +          warn(glyph + " does not have an entry in the glyphs unicode dictionary");
 +      } else {
 +        var b1 = parseInt("0x" + unicode[0] + unicode[1]);
 +        var b2 = parseInt("0x" + unicode[2] + unicode[3]);
 +        unicode = FontsUtils.bytesToInteger([b1, b2]);
 +
 +        charstrings.push({
 +          glyph: glyph,
 +          unicode: unicode,
 +          charstring: aGlyphs[i].data.slice()
 +        });
 +      }
 +    };
 +
 +    charstrings.sort(function(a, b) {
 +      return a.unicode > b.unicode;
 +    });
 +    return charstrings;
 +  },
 +
 +  convertToCFF: function(aFontInfo) {
 +    var debug = false;
 +    function dump(aMsg) {
 +      if (debug)
 +        log(aMsg);
 +    };
 +
 +    var charstrings = this.getOrderedCharStrings(aFontInfo.charstrings);
 +
 +    var charstringsCount = 0;
 +    var charstringsDataLength = 0;
 +    var glyphs = [];
 +    var glyphsChecker = {};
 +    var subrs = aFontInfo.subrs;
 +    var parser = new Type1Parser();
 +    for (var i = 0; i < charstrings.length; i++) {
 +      var charstring = charstrings[i].charstring.slice();
 +      var glyph = charstrings[i].glyph;
 +      if (glyphsChecker[glyph])
 +        error("glyphs already exists!");
 +      glyphsChecker[glyph] = true;
 +
 +      var flattened = parser.flattenCharstring(charstring, subrs);
 +      glyphs.push(flattened);
 +      charstringsCount++;
 +      charstringsDataLength += flattened.length;
 +    }
 +    dump("There is " + charstringsCount + " glyphs (size: " + charstringsDataLength + ")");
 +
 +    // Create a CFF font data
 +    var cff = new Uint8Array(kMaxFontFileSize);
 +    var currentOffset = 0;
 +
 +    // Font header (major version, minor version, header size, offset size)
 +    var header = [0x01, 0x00, 0x04, 0x04];
 +    currentOffset += header.length;
 +    cff.set(header);
 +
 +    // Names Index
 +    var nameIndex = this.createCFFIndexHeader([aFontInfo.name]);
 +    cff.set(nameIndex, currentOffset);
 +    currentOffset += nameIndex.length;
 +
 +    // Calculate strings before writing the TopDICT index in order
 +    // to calculate correct relative offsets for storing 'charset'
 +    // and 'charstrings' data
 +    var version = "";
 +    var notice = "";
 +    var fullName = "";
 +    var familyName = "";
 +    var weight = "";
 +    var strings = [version, notice, fullName,
 +                   familyName, weight];
 +    var stringsIndex = this.createCFFIndexHeader(strings);
 +    var stringsDataLength = stringsIndex.length;
 +
 +    // Create the global subroutines index
 +    var globalSubrsIndex = this.createCFFIndexHeader([]);
 +
 +    // Fill the charset header (first byte is the encoding)
 +    var charset = [0x00];
 +    for (var i = 0; i < glyphs.length; i++) {
 +      var index = CFFStrings.indexOf(charstrings[i].glyph);
 +      if (index == -1)
 +        index = CFFStrings.length + strings.indexOf(glyph);
 +      var bytes = FontsUtils.integerToBytes(index, 2);
 +      charset.push(bytes[0]);
 +      charset.push(bytes[1]);
 +    }
 +
 +    // Convert charstrings
 +    var getNumFor = {
 +      "hstem": 1,
 +      "vstem": 3,
 +      "vmoveto": 4,
 +      "rlineto": 5,
 +      "hlineto": 6,
 +      "vlineto": 7,
 +      "rrcurveto": 8,
 +      "endchar": 14,
 +      "rmoveto": 21,
 +      "hmoveto": 22,
 +      "vhcurveto": 30,
 +      "hvcurveto": 31,
 +    };
 +
 +    // Encode the glyph and add it to the FUX
 +    var r = [[0x40, 0x0E]];
 +    for (var i = 0; i < glyphs.length; i++) {
 +      var data = glyphs[i].slice();
 +      var charstring = [];
 +      for (var j = 0; j < data.length; j++) {
 +        var c = data[j];
 +        if (!IsNum(c)) {
 +          var token = getNumFor[c];
 +          if (!token)
 +            error(c);
 +          charstring.push(token);
 +        } else {
 +          try {
 +            var bytes = this.encodeNumber(c);
 +          } catch(e) {
 +            log("Glyph " + i + " has a wrong value: " + c + " in charstring: " + data);
 +            log("the default value is glyph " + charstrings[i].glyph + " and is supposed to be: " + charstrings[i].charstring);
 +          }
 +          charstring = charstring.concat(bytes);
 +        }
 +      }
 +      r.push(charstring);
 +    }
 +
 +    var charstringsIndex = this.createCFFIndexHeader(r, true);
 +    charstringsIndex = charstringsIndex.join(" ").split(" "); // XXX why?
 +
 +
 +    //Top Dict Index
 +    var topDictIndex = [
 +      0x00, 0x01, 0x01, 0x01, 0x30,
 +      248, 27, 0, // version
 +      248, 28, 1, // Notice
 +      248, 29, 2, // FullName
 +      248, 30, 3, // FamilyName
 +      248, 31, 4  // Weight
 +    ];
 +
 +    var fontBBox = aFontInfo.bbox;
 +    for (var i = 0; i < fontBBox.length; i++)
 +      topDictIndex = topDictIndex.concat(this.encodeNumber(fontBBox[i]));
 +    topDictIndex.push(5) // FontBBox;
 +
 +    var charsetOffset = currentOffset +
 +                        (topDictIndex.length + (4 + 4 + 4 + 7)) +
 +                        stringsIndex.length +
 +                        globalSubrsIndex.length;
 +    topDictIndex = topDictIndex.concat(this.encodeNumber(charsetOffset));
 +    topDictIndex.push(15); // charset
 +
 +    topDictIndex = topDictIndex.concat([28, 0, 0, 16]) // Encoding
 +
 +    var charstringsOffset = charsetOffset + (charstringsCount * 2) + 1;
 +    topDictIndex = topDictIndex.concat(this.encodeNumber(charstringsOffset));
 +    topDictIndex.push(17); // charstrings
 +
 +    topDictIndex = topDictIndex.concat([28, 0, 55])
 +    var privateOffset = charstringsOffset + charstringsIndex.length;
 +    topDictIndex = topDictIndex.concat(this.encodeNumber(privateOffset));
 +    topDictIndex.push(18); // Private
 +    topDictIndex = topDictIndex.join(" ").split(" ");
 +
 +    // Top Dict Index
 +    cff.set(topDictIndex, currentOffset);
 +    currentOffset += topDictIndex.length;
 +
 +    // Strings Index
 +    cff.set(stringsIndex, currentOffset);
 +    currentOffset += stringsIndex.length;
 +
 +    // Global Subrs Index
 +    cff.set(globalSubrsIndex, currentOffset);
 +    currentOffset += globalSubrsIndex.length;
 +
 +    // Charset Index
 +    cff.set(charset, currentOffset);
 +    currentOffset += charset.length;
 +
 +    // Fill charstrings data
 +    cff.set(charstringsIndex, currentOffset);
 +    currentOffset += charstringsIndex.length;
 +
 +    // Private Data
 +    var defaultWidth = this.encodeNumber(0);
 +    var privateData = [].concat(
 +      defaultWidth, [20],
 +      [139, 21], // nominalWidth
 +      [
 +      119, 159, 248, 97, 159, 247, 87, 159, 6,
 +      30, 10, 3, 150, 37, 255, 12, 9,
 +      139, 12,
 +      10, 172, 10,
 +      172, 150, 143, 146, 150, 146, 12, 12,
 +      247, 32, 11,
 +      247, 10, 161, 147, 154, 150, 143, 12, 13,
 +      139, 12, 14,
 +      28, 0, 55, 19
 +    ]);
 +    privateData = privateData.join(" ").split(" ");
 +    cff.set(privateData, currentOffset);
 +    currentOffset += privateData.length;
 +
 +    // Dump shit at the end of the file
 +    var shit = [
 +      0x00, 0x01, 0x01, 0x01,
 +      0x13, 0x5D, 0x65, 0x64,
 +      0x5E, 0x5B, 0xAF, 0x66,
 +      0xBA, 0xBB, 0xB1, 0xB0,
 +      0xB9, 0xBA, 0x65, 0xB2,
 +      0x5C, 0x1F, 0x0B
 +    ];
 +    cff.set(shit, currentOffset);
 +    currentOffset += shit.length;
 +
 +
 +    dump("==================== debug ====================");
 +    //var file = new Uint8Array(cff, 0, currentOffset);
 +    //var parser = new Type2Parser();
 +    //parser.parse(new Stream(file));
 +
 +    var fontData = [];
 +    for (var i = 0; i < currentOffset; i++)
 +      fontData.push(cff[i]);
 +
 +    //log("== write to file");
 +    //writeToFile(fontData, "/tmp/pdf.js." + fontCount + ".cff");
 +
 +    return fontData;
 +  }
 +};
 +
diff --cc pdf.js
index f46362f7f41147bed373bcc5ac36e11a28b82f9a,17537d233ac3f0d063bc64b445e6c79c07408c96..2b7eb1e1bd908369181fc86e53800ca7e7262a19
--- 1/pdf.js
--- 2/pdf.js
+++ b/pdf.js
@@@ -1726,6 -1705,10 +1726,52 @@@ var CanvasGraphics = (function() 
      const EO_CLIP = {};
  
      constructor.prototype = {
 -            return "translated";
+         translateFont: function(fontDict, xref, resources) {
++            var descriptor = xref.fetch(fontDict.get("FontDescriptor"));
++            var fontName = descriptor.get("FontName").name;
++            fontName = fontName.replace("+", "_");
++            
++            var font = Fonts[fontName];
++            if (!font) {
++                var fontFile = descriptor.get2("FontFile", "FontFile2");
++                fontFile = xref.fetchIfRef(fontFile);
++
++                // Generate the custom cmap of the font if needed
++                var encodingMap = {};
++                if (fontDict.has("Encoding")) {
++                  var encoding = xref.fetchIfRef(fontDict.get("Encoding"));
++                  if (IsDict(encoding)) {
++                      // Build an map between codes and glyphs
++                      var differences = encoding.get("Differences");
++                      var index = 0;
++                      for (var j = 0; j < differences.length; j++) {
++                          var data = differences[j];
++                          IsNum(data) ? index = data : encodingMap[index++] = data;
++                      }
++
++                      // Get the font charset
++                      var charset = descriptor.get("CharSet").split("/");
++                  } else if (IsName(encoding)) {
++                      var encoding = Encodings[encoding];
++                      var widths = xref.fetchIfRef(fontDict.get("Widths"));
++                      var firstchar = xref.fetchIfRef(fontDict.get("FirstChar"));
++
++                      var charset = [];
++                      for (var j = 0; j < widths.length; j++) {
++                          var index = widths[j];
++                          if (index)
++                          charset.push(encoding[j + firstchar]);
++                      }
++                  }
++              }
++
++              var fontBBox = descriptor.get("FontBBox");
++              var subtype = fontDict.get("Subtype").name;
++              new Font(fontName, fontFile, encodingMap, charset, fontBBox, subtype);
++          }
++          return Fonts[fontName];
+         },
          beginDrawing: function(mediaBox) {
              var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height;
              this.ctx.save();
              this.current.leading = leading;
          },
          setFont: function(fontRef, size) {
 -            var fontRes = this.res.get("Font");
 -            if (!fontRes)
 -                return;
 -            fontRes = this.xref.fetchIfRef(fontRes);
 -            var font = fontRes.get(fontRef.name);
 +            var font = this.res.get("Font");
 +            if (!IsDict(font))
 +              return;
 +
 +            font = font.get(fontRef.name);
 +            font = this.xref.fetchIfRef(font);
              if (!font)
                  return;
 +
 +            var fontName = "";
 +            var fontDescriptor = font.get("FontDescriptor");
 +            if (fontDescriptor.num) {
 +                var fontDescriptor = this.xref.fetchIfRef(fontDescriptor);
 +                fontName = fontDescriptor.get("FontName").name.replace("+", "_");
 +                Fonts.active = fontName;
 +            }
 +
              this.current.fontSize = size;
 -            TODO("using hard-coded font for testing");
 -            this.ctx.font = this.current.fontSize +'px "Nimbus Roman No9 L"';
 +            this.ctx.font = this.current.fontSize +'px "' + fontName + '"';
          },
+         setTextRenderingMode: function(mode) {
+             TODO("text rendering mode");
+         },
+         setTextRise: function(rise) {
+             TODO("text rise");
+         },
          moveText: function (x, y) {
              this.current.x = this.current.lineX += x;
              this.current.y = this.current.lineY += y;
diff --cc test.js
index 0fdb2aacf297b4a4f48106b666747081ac1b47ad,0000000000000000000000000000000000000000..75b7200027523aef2fea12129d20ee5af30b4907
mode 100644,000000..100644
--- /dev/null
+++ b/test.js
@@@ -1,156 -1,0 +1,104 @@@
-     function display() {
-       var t1 = Date.now();
-       var ctx = canvas.getContext("2d");
-       ctx.save();
-       ctx.fillStyle = "rgb(255, 255, 255)";
-       ctx.fillRect(0, 0, canvas.width, canvas.height);
-       ctx.restore();
-       var gfx = new CanvasGraphics(ctx);
-       page.display(gfx);
-       var t2 = Date.now();
-       var infoDisplay = document.getElementById("info");
-       infoDisplay.innerHTML = "Time to render: "+ (t1 - t0) + "/" + (t2 - t1) + " ms";
-     }
-     // Loading a font via data uri is asynchronous, so wait for all font
-     // of the page to be fully loaded before loading the page
-     var fontsReady = true;
-     var fonts = page.fonts;
-     var xref = page.xref;
-     fonts.forEach(function(fontKey, fontDict) {
-         var descriptor = xref.fetch(fontDict.get("FontDescriptor"));
-         var fontName = descriptor.get("FontName").name;
-         fontName = fontName.replace("+", "_");
-         // Check if the font has been loaded or is still loading
-         var font = Fonts[fontName];
-         if (!font) {
-             var fontFile = descriptor.get2("FontFile", "FontFile2");
-             fontFile = xref.fetchIfRef(fontFile);
-             // Generate the custom cmap of the font if needed
-             var encodingMap = {};
-             if (fontDict.has("Encoding")) {
-               var encoding = xref.fetchIfRef(fontDict.get("Encoding"));
-               if (IsDict(encoding)) {
-                 // Build an map between codes and glyphs
-                 var differences = encoding.get("Differences");
-                 var index = 0;
-                 for (var j = 0; j < differences.length; j++) {
-                   var data = differences[j];
-                   IsNum(data) ? index = data : encodingMap[index++] = data;
-                 }
-                 // Get the font charset
-                 var charset = descriptor.get("CharSet").split("/");
-               } else if (IsName(encoding)) {
-                 var encoding = Encodings[encoding];
-                 var widths = xref.fetchIfRef(fontDict.get("Widths"));
-                 var firstchar = xref.fetchIfRef(fontDict.get("FirstChar"));
-                 var charset = [];
-                 for (var j = 0; j < widths.length; j++) {
-                   var index = widths[j];
-                   if (index)
-                     charset.push(encoding[j + firstchar]);
-                 }
-               }
-             }
-             var fontBBox = descriptor.get("FontBBox");
-             var subtype = fontDict.get("Subtype").name;
-             new Font(fontName, fontFile, encodingMap, charset, fontBBox, subtype);
-             return fontsReady = false;
-         } else if (font.loading) {
-             return fontsReady = false;
 +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
 +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
 +
 +var pdfDocument, canvas, pageDisplay, pageNum, pageTimeout;
 +function load() {
 +    canvas = document.getElementById("canvas");
 +    canvas.mozOpaque = true;
 +    pageNum = parseInt(queryParams().page) || 1;
 +    fileName = queryParams().file || "compressed.tracemonkey-pldi-09.pdf";
 +    open(fileName);
 +}
 +
 +function queryParams() {
 +    var qs = window.location.search.substring(1);
 +    var kvs = qs.split("&");
 +    var params = { };
 +    for (var i = 0; i < kvs.length; ++i) {
 +        var kv = kvs[i].split("=");
 +        params[unescape(kv[0])] = unescape(kv[1]);
 +    }
 +    return params;
 +}
 +
 +function open(url) {
 +    document.title = url;
 +    req = new XMLHttpRequest();
 +    req.open("GET", url);
 +    req.mozResponseType = req.responseType = "arraybuffer";
 +    req.expected = (document.URL.indexOf("file:") == 0) ? 0 : 200;
 +    req.onreadystatechange = function() {
 +      if (req.readyState == 4 && req.status == req.expected) {
 +        var data = req.mozResponseArrayBuffer || req.mozResponse ||
 +                   req.responseArrayBuffer || req.response;
 +        pdfDocument = new PDFDoc(new Stream(data));
 +        numPages = pdfDocument.numPages;
 +        document.getElementById("numPages").innerHTML = numPages.toString();
 +        goToPage(pageNum);
 +      }
 +    };
 +    req.send(null);
 +}
 +
 +function gotoPage(num) {
 +    if (0 <= num && num <= numPages)
 +        pageNum = num;
 +    displayPage(pageNum);
 +}
 +
 +function displayPage(num) {
 +    if (pageNum != num)
 +      window.clearTimeout(pageTimeout);
 +
 +    document.getElementById("pageNumber").value = num;
 +
 +    var t0 = Date.now();
 +
 +    var page = pdfDocument.getPage(pageNum = num);
 +
-       });
-     // If everything is ready do not delayed the page loading any more
-     if (fontsReady)
-       display();
-     else {
-       // FIXME Relying on an event seems much more cleaner here instead
-       // of a setTimeout...
-       pageTimeout = window.setTimeout(displayPage, 150, num);
-     }
++    var t1 = Date.now();
++
++    var ctx = canvas.getContext("2d");
++    ctx.save();
++    ctx.fillStyle = "rgb(255, 255, 255)";
++    ctx.fillRect(0, 0, canvas.width, canvas.height);
++    ctx.restore();
++
++    var gfx = new CanvasGraphics(ctx);
++
++    // page.compile will collect all fonts for us, once we have loaded them
++    // we can trigger the actual page rendering with page.display
++    var fonts = [];
++    
++    page.compile(gfx, fonts);
++    var t2 = Date.now();
++
++    var interval = setInterval(function() {
++        for (var i = 0; i < fonts.length; i++) {
++            if (fonts[i].loading)
++                return;
 +        }
++
++        page.display(gfx);
++        var t3 = Date.now();
++        var infoDisplay = document.getElementById("info");
++        infoDisplay.innerHTML = "Time to load/compile/render: "+ (t1 - t0) + "/" + (t2 - t1) + "/" + (t3 - t2) + " ms";
++        clearInterval(interval);
++    }, 10);
 +}
 +
 +function nextPage() {
 +    if (pageNum < pdfDocument.numPages)
 +      displayPage(++pageNum);
 +}
 +
 +function prevPage() {
 +    if (pageNum > 1)
 +      displayPage(--pageNum);
 +}
 +
 +function goToPage(num) {
 +  if (0 <= num && num <= numPages)
 +    displayPage(pageNum = num);
 +}
 +