]> git.parisson.com Git - pdf.js.git/commitdiff
Rename PDFFonts.js to fonts.js
authorVivien Nicolas <21@vingtetun.org>
Fri, 17 Jun 2011 07:11:03 +0000 (09:11 +0200)
committerVivien Nicolas <21@vingtetun.org>
Fri, 17 Jun 2011 07:11:03 +0000 (09:11 +0200)
PDFFont.js [deleted file]
PDFFontUtils.js [deleted file]
fonts.js [new file with mode: 0644]
fonts_utils.js [new file with mode: 0644]
test.html

diff --git a/PDFFont.js b/PDFFont.js
deleted file mode 100644 (file)
index 2db7e74..0000000
+++ /dev/null
@@ -1,1488 +0,0 @@
-/* -*- 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.
- *
- * FIXME 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();
-};
-
-
-/**
- * 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,
-
-  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 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.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 Uint16Array(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 = [];
-    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);
-  },
-
-  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++) {
-      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 */
-    // 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);
-
-    // 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;
-  }
-};
-
-
-/**
- * FontsUtils is a static class dedicated to hold codes that are not related
- * to fonts in particular and needs to be share between them.
- */
-var FontsUtils = {
-  _bytesArray: new Uint8Array(4),
-  integerToBytes: function fu_integerToBytes(aValue, aBytesCount) {
-    var bytes = this._bytesArray;
-
-    if (aBytesCount == 1) {
-      bytes.set([aValue]);
-      return bytes[0];
-    } else if (aBytesCount == 2) {
-      bytes.set([aValue >> 8, aValue]);
-      return [bytes[0], bytes[1]];
-    } else if (aBytesCount == 4) {
-      bytes.set([aValue >> 24, aValue >> 16, aValue >> 8, aValue]);
-      return [bytes[0], bytes[1], bytes[2], bytes[3]];
-    }
-  },
-
-  bytesToInteger: function fu_bytesToInteger(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;
-  }
-};
-
-
-/**
- * The TrueType class verify that the ttf embedded inside the PDF is correct in
- * the point of view of the OTS sanitizer and rewrite it on the fly otherwise.
- *
- * At the moment the rewiting only support rewriting missing 'OS/2' table.
- * This class is unused at the moment since the 'cmap' table of the test 
- * document is not missing but use and old version of the 'cmap' table that
- * is deprecated and  not supported by 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 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.
-  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;
-    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 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[i];
-      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",
-
-    // closepath is a Type1 command that do not take argument and is useless
-    // in Type2 and it can simply be ignored.
-    "9": null, // closepath
-
-    "10": "callsubr",
-
-    // return is normally used inside sub-routines to tells to the execution
-    // flow that it can be back to normal.
-    // During the translation process Type1 charstrings will be flattened and
-    // sub-routines will be embedded directly into the charstring directly, so
-    // this can be ignored safely.
-    "11": "return",
-
-    "12": {
-      // dotsection is a Type1 command to specify some hinting feature for dots
-      // that do not take a parameter and it can safely be ignored for Type2.
-      "0": null, // dotsection
-
-      // [vh]stem3 are Type1 only and Type2 supports [vh]stem with multiple
-      // parameters, so instead of returning [vh]stem3 take a shortcut and
-      // return [vhstem] instead.
-      "1": "vstem",
-      "2": "hstem",
-
-      // Type1 only command with command not (yet) built-in ,throw an error
-      "6": -1, // seac
-      "7": -1, //sbw
-
-      "12": "div",
-
-      // callothersubr is a mechanism to make calls on the postscript
-      // interpreter.
-      // TODO When decodeCharstring encounter such a command it should
-      //      directly do:
-      //        - pop the previous charstring[] command into 'index'
-      //        - pop the previous charstring[] command and ignore it, it is
-      //          normally the number of element to push on the stack before
-      //          the command but since everything will be pushed on the stack
-      //          by the PS interpreter when it will read them that is safe to
-      //          ignore this command
-      //        - push the content of the OtherSubrs[index] inside charstring[]
-      "16": "callothersubr",
-
-      "17": "pop",
-
-      // setcurrentpoint sets the current point to x, y without performing a
-      // moveto (this is a one shot positionning command). This is used only
-      // with the return of an OtherSubrs call.
-      // TODO Implement the OtherSubrs charstring embedding and replace this
-      //      call by a no-op, like 2 'pop' commands for example.
-      "33": null, //setcurrentpoint
-    },
-    "13": "hsbw",
-    "14": "endchar",
-    "21": "rmoveto",
-    "22": "hmoveto",
-    "30": "vhcurveto",
-    "31": "hvcurveto"
-  };
-
-  function decodeCharString(aArray) {
-    var charString = [];
-
-    var value = "";
-    var count = aArray.length;
-    for (var i = 0; i < count; i++) {
-      value = parseInt(aArray[i]);
-
-      if (value < 32) {
-        var command = null;
-        if (value == 12) {
-          var escape = aArray[++i];
-          command = charStringDictionary["12"][escape];
-        } else {
-          command = charStringDictionary[value];
-        }
-
-        // Some charstring commands are meaningless in Type2 and will return
-        // a null, let's just ignored them
-        if (!command && i < count) {
-          continue;
-        } else if (!command) {
-          break;
-        } else if (command == -1) {
-          log("decodeCharstring: " + charString);
-          error("Support for Type1 command " + value + " (" + escape + ") is not implemented");
-        }
-
-        value = command;
-      } else if (value <= 246) {
-        value = parseInt(value) - 139;
-      } else if (value <= 250) {
-        value = ((value - 247) * 256) + parseInt(aArray[++i]) + 108;
-      } else if (value <= 254) {
-        value = -((value - 251) * 256) - parseInt(aArray[++i]) - 108;
-      } else {
-        var byte = aArray[++i];
-        var high = (byte >> 1);
-        value = (byte - high) << 24 | aArray[++i] << 16 |
-                aArray[++i] << 8 | aArray[++i];
-      }
-
-      charString.push(value);
-    }
-
-    return charString;
-  };
-
-  /**
-   * 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(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 data = eexecString.slice(i + 3, i + 3 + length);
-        var encodedSubr = decrypt(data, kCharStringsEncryptionKey, 4, true);
-        var subr = decodeCharString(encodedSubr);
-
-        subrs.push(subr);
-        i += 3 + length;
-      } else if (inGlyphs && c == 0x52) {
-        length = parseInt(length);
-        var data = eexecString.slice(i + 3, i + 3 + length);
-        var encodedCharstring = decrypt(data, kCharStringsEncryptionKey, 4, true);
-        var subr = decodeCharString(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
-    }
-  }
-};
-
-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 hold the glyph data as if, this should be improved
-  this.glyphs = fontInfo.charstrings;
-
-  this.data = this.convertToCFF(fontInfo);
-  var end = Date.now();
-};
-
-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;
-    if (aValue >= -32768 && aValue <= 32767) {
-      return [ 28, aValue >> 8, aValue ];
-    } else if (aValue >= (-2147483647-1) && aValue <= 2147483647) {
-      return [
-        0xFF, aValue >> 24, Value >> 16, aValue >> 8, aValue ];
-    } 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;
-  },
-
-  /*
-   * Flatten the commands by interpreting the postscript code and replacing
-   * every 'callsubr', 'callothersubr' by the real commands.
-   *
-   * TODO This function also do a string to command number transformation
-   * that can probably be avoided if the Type1 decodeCharstring code is smarter
-   */
-  commandsMap: {
-    "hstem": 1,
-    "vstem": 3,
-    "vmoveto": 4,
-    "rlineto": 5,
-    "hlineto": 6,
-    "vlineto": 7,
-    "rrcurveto": 8,
-    "endchar": 14,
-    "rmoveto": 21,
-    "hmoveto": 22,
-    "vhcurveto": 30,
-    "hvcurveto": 31,
-  },
-
-  flattenCharstring: function(aGlyph, aCharstring, aSubrs) {
-    var original = aCharstring.slice();
-    var i = 0;
-    while (true) {
-      var obj = aCharstring[i];
-      if (obj == null)
-        return [];
-
-      if (obj.charAt) {
-        switch (obj) {
-          case "callsubr":
-            var subr = aSubrs[aCharstring[i - 1]].slice();
-            if (subr.length > 1) {
-              subr = this.flattenCharstring(aGlyph, subr, aSubrs);
-              subr.pop();
-              aCharstring.splice(i - 1, 2, subr);
-            } else {
-              aCharstring.splice(i - 1, 2);
-            }
-            i -= 1;
-            break;
-
-          case "callothersubr":
-            var index = aCharstring[i - 1];
-            var count = aCharstring[i - 2];
-            var data = aCharstring[i - 3];
-
-            // XXX The callothersubr needs to support at least the 3 defaults
-            // otherSubrs of the spec
-            if (index != 3)
-              error("callothersubr for index: " + index + " (" + aCharstring + ")");
-
-            if (!data) {
-              aCharstring.splice(i - 2, 4, "pop", 3);
-              i -= 3;
-            } else {
-              // 5 to remove the arguments, the callothersubr call and the pop command
-              aCharstring.splice(i - 3, 5, 3);
-              i -= 3;
-            }
-            break;
-
-          case "div":
-            var num2 = aCharstring[i - 1];
-            var num1 = aCharstring[i - 2];
-            aCharstring.splice(i - 2, 3, num2 / num1);
-            i -= 2;
-            break;
-
-          case "pop":
-            if (i)
-              aCharstring.splice(i - 2, 2);
-            else
-              aCharstring.splice(i - 1, 1);
-            i -= 1;
-            break;
-
-
-          case "hsbw":
-            var charWidthVector = aCharstring[i - 1];
-            var leftSidebearing = aCharstring[i - 2];
-
-            if (leftSidebearing)
-              aCharstring.splice(i - 2, 3, charWidthVector, leftSidebearing, "hmoveto");
-            else
-              aCharstring.splice(i - 2, 3, charWidthVector);
-            break;
-
-          case "endchar":
-          case "return":
-            // CharString is ready to be re-encode to commands number at this point
-            for (var j = 0; j < aCharstring.length; j++) {
-              var command = aCharstring[j];
-              if (parseFloat(command) == command) {
-                aCharstring.splice(j, 1, 28, command >> 8, command);
-                j+= 2;
-              } else if (command.charAt) {
-                var command = this.commandsMap[command];
-                if (IsArray(command)) {
-                  aCharstring.splice(j - 1, 1, command[0], command[1]);
-                  j += 1;
-                } else {
-                  aCharstring[j] = command;
-                }
-              } else {
-                aCharstring.splice(j, 1);
-
-                // command has already been translated, just add them to the
-                // charstring directly
-                for (var k = 0; k < command.length; k++)
-                  aCharstring.splice(j + k, 0, command[k]);
-                j+= command.length - 1;
-              }
-            }
-            return aCharstring;
-
-          default:
-            break;
-        }
-      }
-      i++;
-    }
-    error("failing with i = " + i + " in charstring:" + aCharstring + "(" + aCharstring.length + ")");
-  },
-
-  convertToCFF: function(aFontInfo) {
-    var debug = false;
-    function dump(aMsg) {
-      if (debug)
-        log(aMsg);
-    };
-
-    var charstrings = this.getOrderedCharStrings(aFontInfo.charstrings);
-
-    // Starts the conversion of the Type1 charstrings to Type2
-    var start = Date.now();
-    var charstringsCount = 0;
-    var charstringsDataLength = 0;
-    var glyphs = [];
-    for (var i = 0; i < charstrings.length; i++) {
-      var charstring = charstrings[i].charstring.slice();
-      var glyph = charstrings[i].glyph;
-
-      var flattened = this.flattenCharstring(glyph, charstring, aFontInfo.subrs);
-      glyphs.push(flattened);
-      charstringsCount++;
-      charstringsDataLength += flattened.length;
-    }
-
-    var end = Date.now();
-    dump("There is " + charstringsCount + " glyphs (size: " + charstringsDataLength + ")");
-    dump("Time to flatten the strings is : " + (end -start));
-
-    // 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]);
-    }
-
-    var charstringsIndex = this.createCFFIndexHeader([[0x40, 0x0E]].concat(glyphs), 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(" ");
-
-    var indexes = [
-      topDictIndex, stringsIndex,
-      globalSubrsIndex, charset,
-      charstringsIndex
-    ];
-
-    for (var i = 0; i < indexes.length; i++) {
-      var index = indexes[i];
-      cff.set(index, currentOffset);
-      currentOffset += index.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;
-
-    var fontData = [];
-    for (var i = 0; i < currentOffset; i++)
-      fontData.push(cff[i]);
-
-    return fontData;
-  }
-};
-
diff --git a/PDFFontUtils.js b/PDFFontUtils.js
deleted file mode 100644 (file)
index 086648f..0000000
+++ /dev/null
@@ -1,391 +0,0 @@
-/**
- * The Type2 reader code below is only used for debugging purpose since Type2
- * is only a CharString format and is never used directly as a Font file.
- *
- * So the code here is useful for dumping the data content of a .cff file in
- * order to investigate the similarity between a Type1 CharString and a Type2
- * CharString or to understand the structure of the CFF format.
- */
-
-
-/**
- * Build a charset by assigning the glyph name and the human readable form
- * of the glyph data.
- */
-function readCharset(aStream, aCharstrings) {
-  var charset = {};
-
-  var format = aStream.getByte();
-  if (format == 0) {
-    charset[".notdef"] = readCharstringEncoding(aCharstrings[0]);
-
-    var count = aCharstrings.length - 1;
-    for (var i = 1; i < count + 1; i++) {
-      var sid = aStream.getByte() << 8 | aStream.getByte();
-      charset[CFFStrings[sid]] = readCharstringEncoding(aCharstrings[i]);
-      //log(CFFStrings[sid] + "::" + charset[CFFStrings[sid]]);
-    }
-  } else if (format == 1) {
-    error("Charset Range are not supported");
-  } else {
-    error("Invalid charset format");
-  }
-
-  return charset;
-};
-
-/**
- * Take a Type2 binary charstring as input and transform it to a human
- * readable representation as specified by the 'The Type 2 Charstring Format',
- * chapter 3.1.
- */
-function readCharstringEncoding(aString) {
-  var charstringTokens = [];
-
-  var count = aString.length;
-  for (var i = 0; i < count; ) {
-    var value = aString[i++];
-    var token = null;
-
-    if (value < 0) {
-      continue;
-    } else if (value <= 11) {
-      token = CFFEncodingMap[value];
-    } else if (value == 12) {
-      token = CFFEncodingMap[value][aString[i++]];
-    } else if (value <= 18) {
-      token = CFFEncodingMap[value];
-    } else if (value <= 20) {
-      var mask = aString[i++];
-      token = CFFEncodingMap[value];
-    } else if (value <= 27) {
-      token = CFFEncodingMap[value];
-    } else if (value == 28) {
-      token = aString[i++] << 8 | aString[i++];
-    } else if (value <= 31) {
-      token = CFFEncodingMap[value];
-    } else if (value < 247) {
-      token = parseInt(value) - 139;
-    } else if (value < 251) {
-      token = ((value - 247) * 256) + aString[i++] + 108;
-    } else if (value < 255) {
-      token = -((value - 251) * 256) - aString[i++] - 108;
-    } else {// value == 255
-      token = aString[i++] << 24 | aString[i++] << 16 |
-              aString[i++] << 8 | aString[i];
-    }
-
-    charstringTokens.push(token);
-  }
-
-  return charstringTokens;
-};
-
-
-/**
- * Take a binary DICT Data as input and transform it into a human readable
- * form as specified by 'The Compact Font Format Specification', chapter 5.
- */
-function readFontDictData(aString, aMap) {
-  var fontDictDataTokens = [];
-
-  var count = aString.length;
-  for (var i = 0; i < count; i) {
-    var value = aString[i++];
-    var token = null;
-
-    if (value == 12) {
-      token = aMap[value][aString[i++]];
-    } else if (value == 28) {
-      token = aString[i++] << 8 | aString[i++];
-    } else if (value == 29) {
-      token = aString[i++] << 24 |
-              aString[i++] << 16 |
-              aString[i++] << 8  |
-              aString[i++];
-    } else if (value == 30) {
-      token = "";
-      var parsed = false;
-      while (!parsed) {
-        var byte = aString[i++];
-
-        var nibbles = [parseInt(byte / 16), parseInt(byte % 16)];
-        for (var j = 0; j < nibbles.length; j++) {
-          var nibble = nibbles[j];
-          switch (nibble) {
-            case 0xA:
-              token += ".";
-              break;
-            case 0xB:
-              token += "E";
-              break;
-            case 0xC:
-              token += "E-";
-              break;
-            case 0xD:
-              break;
-            case 0xE:
-              token += "-";
-              break;
-            case 0xF:
-              parsed = true;
-              break;
-            default:
-              token += nibble;
-              break;
-          }
-        }
-      };
-      token = parseFloat(token);
-    } else if (value <= 31) {
-      token = aMap[value];
-    } else if (value <= 246) {
-      token = parseInt(value) - 139;
-    } else if (value <= 250) {
-      token = ((value - 247) * 256) + aString[i++] + 108;
-    } else if (value <= 254) {
-      token = -((value - 251) * 256) - aString[i++] - 108;
-    } else if (value == 255) {
-      error("255 is not a valid DICT command");
-    }
-
-    fontDictDataTokens.push(token);
-  }
-
-  return fontDictDataTokens;
-};
-
-
-/**
- * Take a stream as input and return an array of objects.
- * In CFF an INDEX is a structure with the following format:
- *  {
- *    count: 2 bytes (Number of objects stored in INDEX),
- *    offsize: 1 byte (Offset array element size),
- *    offset: [count + 1] bytes (Offsets array),
- *    data: - (Objects data)
- *  }
- *
- *  More explanation are given in the 'CFF Font Format Specification',
- *  chapter 5.
- */
-function readFontIndexData(aStream, aIsByte) {
-  var count = aStream.getByte() << 8 | aStream.getByte();
-  var offsize = aStream.getByte();
-
-  function getNextOffset() {
-    switch (offsize) {
-      case 0:
-        return 0;
-      case 1:
-        return aStream.getByte();
-      case 2:
-        return aStream.getByte() << 8 | aStream.getByte();
-      case 3:
-        return aStream.getByte() << 16 | aStream.getByte() << 8 |
-               aStream.getByte();
-      case 4:
-      return aStream.getByte() << 24 | aStream.getByte() << 16 |
-             aStream.getByte() << 8 | aStream.getByte();
-    }
-  };
-
-  var offsets = [];
-  for (var i = 0; i < count + 1; i++)
-    offsets.push(getNextOffset());
-
-  log("Found " + count + " objects at offsets :" + offsets + " (offsize: " + offsize + ")");
-
-  // Now extract the objects
-  var relativeOffset = aStream.pos;
-  var objects = [];
-  for (var i = 0; i < count; i++) {
-    var offset = offsets[i];
-    aStream.pos = relativeOffset + offset - 1;
-
-    var data = [];
-    var length = offsets[i + 1] - 1;
-    for (var j = offset - 1; j < length; j++)
-      data.push(aIsByte ? aStream.getByte() : aStream.getChar());
-    objects.push(data);
-  }
-
-  return objects;
-};
-
-var Type2Parser = function(aFilePath) {
-  var font = new Dict();
-
-  var xhr = new XMLHttpRequest();
-  xhr.open("GET", aFilePath, false);
-  xhr.mozResponseType = xhr.responseType = "arraybuffer";
-  xhr.expected = (document.URL.indexOf("file:") == 0) ? 0 : 200;
-  xhr.send(null);
-  this.data = new Stream(xhr.mozResponseArrayBuffer || xhr.mozResponse ||
-                         xhr.responseArrayBuffer || xhr.response);
-
-  // Turn on this flag for additional debugging logs
-  var debug = false;
-
-  function dump(aStr) {
-    if (debug)
-      log(aStr);
-  };
-
-  function parseAsToken(aString, aMap) {
-    var decoded = readFontDictData(aString, aMap);
-
-    var stack = [];
-    var count = decoded.length;
-    for (var i = 0; i < count; i++) {
-      var token = decoded[i];
-      if (IsNum(token)) {
-        stack.push(token);
-      } else {
-        switch (token.operand) {
-          case "SID":
-            font.set(token.name, CFFStrings[stack.pop()]);
-            break;
-          case "number number":
-            font.set(token.name, {
-              offset: stack.pop(),
-              size: stack.pop()
-            });
-            break;
-          case "boolean":
-            font.set(token.name, stack.pop());
-            break;
-          case "delta":
-            font.set(token.name, stack.pop());
-            break;
-          default:
-            if (token.operand && token.operand.length) {
-              var array = [];
-              for (var j = 0; j < token.operand.length; j++)
-                array.push(stack.pop());
-              font.set(token.name, array);
-            } else {
-              font.set(token.name, stack.pop());
-            }
-            break;
-        }
-      }
-    }
-  };
-
-  this.parse = function(aStream) {
-    font.set("major", aStream.getByte());
-    font.set("minor", aStream.getByte());
-    font.set("hdrSize", aStream.getByte());
-    font.set("offsize", aStream.getByte());
-
-    // Move the cursor after the header
-    aStream.skip(font.get("hdrSize") - aStream.pos);
-
-    // Read the NAME Index
-    dump("Reading Index: Names");
-    font.set("Names", readFontIndexData(aStream));
-    log("Names: " + font.get("Names"));
-
-    // Read the Top Dict Index
-    dump("Reading Index: TopDict");
-    var topDict = readFontIndexData(aStream, true);
-    log("TopDict: " + topDict);
-
-    // Read the String Index
-    dump("Reading Index: Strings");
-    var strings = readFontIndexData(aStream);
-    log("strings: " + strings);
-
-    // Fill up the Strings dictionary with the new unique strings
-    for (var i = 0; i < strings.length; i++)
-      CFFStrings.push(strings[i].join(""));
-
-    // Parse the TopDict operator
-    var objects = [];
-    var count = topDict.length;
-    for (var i = 0; i < count; i++)
-      parseAsToken(topDict[i], CFFDictDataMap);
-
-    // Read the Global Subr Index that comes just after the Strings Index
-    // (cf. "The Compact Font Format Specification" Chapter 16)
-    dump("Reading Global Subr Index");
-    var subrs = readFontIndexData(aStream, true);
-    dump(subrs);
-
-    // Reading Private Dict
-    var private = font.get("Private");
-    log("Reading Private Dict (offset: " + private.offset + " size: " + private.size + ")");
-    aStream.pos = private.offset;
-
-    var privateDict = [];
-    for (var i = 0; i < private.size; i++)
-      privateDict.push(aStream.getByte());
-    dump("private:" + privateDict);
-    parseAsToken(privateDict, CFFDictPrivateDataMap);
-
-    for (var p in font.map)
-      dump(p + "::" + font.get(p));
-
-    // Read CharStrings Index
-    var charStringsOffset = font.get("CharStrings");
-    dump("Read CharStrings Index (offset: " + charStringsOffset + ")");
-    aStream.pos = charStringsOffset;
-    var charStrings = readFontIndexData(aStream, true);
-
-    // Read Charset
-    dump("Read Charset for " + charStrings.length + " glyphs");
-    var charsetEntry = font.get("charset");
-    if (charsetEntry == 0) {
-      error("Need to support CFFISOAdobeCharset");
-    } else if (charsetEntry == 1) {
-      error("Need to support CFFExpert");
-    } else if (charsetEntry == 2) {
-      error("Need to support CFFExpertSubsetCharset");
-    } else {
-      aStream.pos = charsetEntry;
-      var charset = readCharset(aStream, charStrings);
-    }
-  }
-};
-
-/*
- * To try the Type2 decoder on a local file in the current directory:
- *
- *  var cff = new Type2Parser("file.cff");
- *  cff.parse(this.data);
- *
- * To try the Type2 decoder on a custom built CFF array:
- *
- *  var file = new Uint8Array(cffFileArray, 0, cffFileSize);
- *  var parser = new Type2Parser();
- *  parser.parse(new Stream(file));
- *
- */
-
-
-/**
- * Write to a file to the disk (works only on Firefox in privilege mode)
- * but this is useful for dumping a font file to the disk and check with
- * fontforge or the ots program what's wrong with the file.
- *
- * writeToFile(fontData, "/tmp/pdf.js." + fontCount + ".cff");
- */
-function writeToFile(aBytes, aFilePath) {
-  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-  var Cc = Components.classes,
-      Ci = Components.interfaces;
-  var file  = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
-  file.initWithPath(aFilePath);
-
-  var stream = Cc["@mozilla.org/network/file-output-stream;1"]
-                 .createInstance(Ci.nsIFileOutputStream);
-  stream.init(file, 0x04 | 0x08 | 0x20, 0600, 0);
-
-  var bos = Cc["@mozilla.org/binaryoutputstream;1"]
-              .createInstance(Ci.nsIBinaryOutputStream);
-  bos.setOutputStream(stream);
-  bos.writeByteArray(aBytes, aBytes.length);
-  stream.close();
-};
-
diff --git a/fonts.js b/fonts.js
new file mode 100644 (file)
index 0000000..2db7e74
--- /dev/null
+++ b/fonts.js
@@ -0,0 +1,1488 @@
+/* -*- 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.
+ *
+ * FIXME 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();
+};
+
+
+/**
+ * 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,
+
+  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 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.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 Uint16Array(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 = [];
+    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);
+  },
+
+  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++) {
+      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 */
+    // 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);
+
+    // 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;
+  }
+};
+
+
+/**
+ * FontsUtils is a static class dedicated to hold codes that are not related
+ * to fonts in particular and needs to be share between them.
+ */
+var FontsUtils = {
+  _bytesArray: new Uint8Array(4),
+  integerToBytes: function fu_integerToBytes(aValue, aBytesCount) {
+    var bytes = this._bytesArray;
+
+    if (aBytesCount == 1) {
+      bytes.set([aValue]);
+      return bytes[0];
+    } else if (aBytesCount == 2) {
+      bytes.set([aValue >> 8, aValue]);
+      return [bytes[0], bytes[1]];
+    } else if (aBytesCount == 4) {
+      bytes.set([aValue >> 24, aValue >> 16, aValue >> 8, aValue]);
+      return [bytes[0], bytes[1], bytes[2], bytes[3]];
+    }
+  },
+
+  bytesToInteger: function fu_bytesToInteger(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;
+  }
+};
+
+
+/**
+ * The TrueType class verify that the ttf embedded inside the PDF is correct in
+ * the point of view of the OTS sanitizer and rewrite it on the fly otherwise.
+ *
+ * At the moment the rewiting only support rewriting missing 'OS/2' table.
+ * This class is unused at the moment since the 'cmap' table of the test 
+ * document is not missing but use and old version of the 'cmap' table that
+ * is deprecated and  not supported by 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 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.
+  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;
+    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 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[i];
+      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",
+
+    // closepath is a Type1 command that do not take argument and is useless
+    // in Type2 and it can simply be ignored.
+    "9": null, // closepath
+
+    "10": "callsubr",
+
+    // return is normally used inside sub-routines to tells to the execution
+    // flow that it can be back to normal.
+    // During the translation process Type1 charstrings will be flattened and
+    // sub-routines will be embedded directly into the charstring directly, so
+    // this can be ignored safely.
+    "11": "return",
+
+    "12": {
+      // dotsection is a Type1 command to specify some hinting feature for dots
+      // that do not take a parameter and it can safely be ignored for Type2.
+      "0": null, // dotsection
+
+      // [vh]stem3 are Type1 only and Type2 supports [vh]stem with multiple
+      // parameters, so instead of returning [vh]stem3 take a shortcut and
+      // return [vhstem] instead.
+      "1": "vstem",
+      "2": "hstem",
+
+      // Type1 only command with command not (yet) built-in ,throw an error
+      "6": -1, // seac
+      "7": -1, //sbw
+
+      "12": "div",
+
+      // callothersubr is a mechanism to make calls on the postscript
+      // interpreter.
+      // TODO When decodeCharstring encounter such a command it should
+      //      directly do:
+      //        - pop the previous charstring[] command into 'index'
+      //        - pop the previous charstring[] command and ignore it, it is
+      //          normally the number of element to push on the stack before
+      //          the command but since everything will be pushed on the stack
+      //          by the PS interpreter when it will read them that is safe to
+      //          ignore this command
+      //        - push the content of the OtherSubrs[index] inside charstring[]
+      "16": "callothersubr",
+
+      "17": "pop",
+
+      // setcurrentpoint sets the current point to x, y without performing a
+      // moveto (this is a one shot positionning command). This is used only
+      // with the return of an OtherSubrs call.
+      // TODO Implement the OtherSubrs charstring embedding and replace this
+      //      call by a no-op, like 2 'pop' commands for example.
+      "33": null, //setcurrentpoint
+    },
+    "13": "hsbw",
+    "14": "endchar",
+    "21": "rmoveto",
+    "22": "hmoveto",
+    "30": "vhcurveto",
+    "31": "hvcurveto"
+  };
+
+  function decodeCharString(aArray) {
+    var charString = [];
+
+    var value = "";
+    var count = aArray.length;
+    for (var i = 0; i < count; i++) {
+      value = parseInt(aArray[i]);
+
+      if (value < 32) {
+        var command = null;
+        if (value == 12) {
+          var escape = aArray[++i];
+          command = charStringDictionary["12"][escape];
+        } else {
+          command = charStringDictionary[value];
+        }
+
+        // Some charstring commands are meaningless in Type2 and will return
+        // a null, let's just ignored them
+        if (!command && i < count) {
+          continue;
+        } else if (!command) {
+          break;
+        } else if (command == -1) {
+          log("decodeCharstring: " + charString);
+          error("Support for Type1 command " + value + " (" + escape + ") is not implemented");
+        }
+
+        value = command;
+      } else if (value <= 246) {
+        value = parseInt(value) - 139;
+      } else if (value <= 250) {
+        value = ((value - 247) * 256) + parseInt(aArray[++i]) + 108;
+      } else if (value <= 254) {
+        value = -((value - 251) * 256) - parseInt(aArray[++i]) - 108;
+      } else {
+        var byte = aArray[++i];
+        var high = (byte >> 1);
+        value = (byte - high) << 24 | aArray[++i] << 16 |
+                aArray[++i] << 8 | aArray[++i];
+      }
+
+      charString.push(value);
+    }
+
+    return charString;
+  };
+
+  /**
+   * 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(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 data = eexecString.slice(i + 3, i + 3 + length);
+        var encodedSubr = decrypt(data, kCharStringsEncryptionKey, 4, true);
+        var subr = decodeCharString(encodedSubr);
+
+        subrs.push(subr);
+        i += 3 + length;
+      } else if (inGlyphs && c == 0x52) {
+        length = parseInt(length);
+        var data = eexecString.slice(i + 3, i + 3 + length);
+        var encodedCharstring = decrypt(data, kCharStringsEncryptionKey, 4, true);
+        var subr = decodeCharString(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
+    }
+  }
+};
+
+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 hold the glyph data as if, this should be improved
+  this.glyphs = fontInfo.charstrings;
+
+  this.data = this.convertToCFF(fontInfo);
+  var end = Date.now();
+};
+
+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;
+    if (aValue >= -32768 && aValue <= 32767) {
+      return [ 28, aValue >> 8, aValue ];
+    } else if (aValue >= (-2147483647-1) && aValue <= 2147483647) {
+      return [
+        0xFF, aValue >> 24, Value >> 16, aValue >> 8, aValue ];
+    } 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;
+  },
+
+  /*
+   * Flatten the commands by interpreting the postscript code and replacing
+   * every 'callsubr', 'callothersubr' by the real commands.
+   *
+   * TODO This function also do a string to command number transformation
+   * that can probably be avoided if the Type1 decodeCharstring code is smarter
+   */
+  commandsMap: {
+    "hstem": 1,
+    "vstem": 3,
+    "vmoveto": 4,
+    "rlineto": 5,
+    "hlineto": 6,
+    "vlineto": 7,
+    "rrcurveto": 8,
+    "endchar": 14,
+    "rmoveto": 21,
+    "hmoveto": 22,
+    "vhcurveto": 30,
+    "hvcurveto": 31,
+  },
+
+  flattenCharstring: function(aGlyph, aCharstring, aSubrs) {
+    var original = aCharstring.slice();
+    var i = 0;
+    while (true) {
+      var obj = aCharstring[i];
+      if (obj == null)
+        return [];
+
+      if (obj.charAt) {
+        switch (obj) {
+          case "callsubr":
+            var subr = aSubrs[aCharstring[i - 1]].slice();
+            if (subr.length > 1) {
+              subr = this.flattenCharstring(aGlyph, subr, aSubrs);
+              subr.pop();
+              aCharstring.splice(i - 1, 2, subr);
+            } else {
+              aCharstring.splice(i - 1, 2);
+            }
+            i -= 1;
+            break;
+
+          case "callothersubr":
+            var index = aCharstring[i - 1];
+            var count = aCharstring[i - 2];
+            var data = aCharstring[i - 3];
+
+            // XXX The callothersubr needs to support at least the 3 defaults
+            // otherSubrs of the spec
+            if (index != 3)
+              error("callothersubr for index: " + index + " (" + aCharstring + ")");
+
+            if (!data) {
+              aCharstring.splice(i - 2, 4, "pop", 3);
+              i -= 3;
+            } else {
+              // 5 to remove the arguments, the callothersubr call and the pop command
+              aCharstring.splice(i - 3, 5, 3);
+              i -= 3;
+            }
+            break;
+
+          case "div":
+            var num2 = aCharstring[i - 1];
+            var num1 = aCharstring[i - 2];
+            aCharstring.splice(i - 2, 3, num2 / num1);
+            i -= 2;
+            break;
+
+          case "pop":
+            if (i)
+              aCharstring.splice(i - 2, 2);
+            else
+              aCharstring.splice(i - 1, 1);
+            i -= 1;
+            break;
+
+
+          case "hsbw":
+            var charWidthVector = aCharstring[i - 1];
+            var leftSidebearing = aCharstring[i - 2];
+
+            if (leftSidebearing)
+              aCharstring.splice(i - 2, 3, charWidthVector, leftSidebearing, "hmoveto");
+            else
+              aCharstring.splice(i - 2, 3, charWidthVector);
+            break;
+
+          case "endchar":
+          case "return":
+            // CharString is ready to be re-encode to commands number at this point
+            for (var j = 0; j < aCharstring.length; j++) {
+              var command = aCharstring[j];
+              if (parseFloat(command) == command) {
+                aCharstring.splice(j, 1, 28, command >> 8, command);
+                j+= 2;
+              } else if (command.charAt) {
+                var command = this.commandsMap[command];
+                if (IsArray(command)) {
+                  aCharstring.splice(j - 1, 1, command[0], command[1]);
+                  j += 1;
+                } else {
+                  aCharstring[j] = command;
+                }
+              } else {
+                aCharstring.splice(j, 1);
+
+                // command has already been translated, just add them to the
+                // charstring directly
+                for (var k = 0; k < command.length; k++)
+                  aCharstring.splice(j + k, 0, command[k]);
+                j+= command.length - 1;
+              }
+            }
+            return aCharstring;
+
+          default:
+            break;
+        }
+      }
+      i++;
+    }
+    error("failing with i = " + i + " in charstring:" + aCharstring + "(" + aCharstring.length + ")");
+  },
+
+  convertToCFF: function(aFontInfo) {
+    var debug = false;
+    function dump(aMsg) {
+      if (debug)
+        log(aMsg);
+    };
+
+    var charstrings = this.getOrderedCharStrings(aFontInfo.charstrings);
+
+    // Starts the conversion of the Type1 charstrings to Type2
+    var start = Date.now();
+    var charstringsCount = 0;
+    var charstringsDataLength = 0;
+    var glyphs = [];
+    for (var i = 0; i < charstrings.length; i++) {
+      var charstring = charstrings[i].charstring.slice();
+      var glyph = charstrings[i].glyph;
+
+      var flattened = this.flattenCharstring(glyph, charstring, aFontInfo.subrs);
+      glyphs.push(flattened);
+      charstringsCount++;
+      charstringsDataLength += flattened.length;
+    }
+
+    var end = Date.now();
+    dump("There is " + charstringsCount + " glyphs (size: " + charstringsDataLength + ")");
+    dump("Time to flatten the strings is : " + (end -start));
+
+    // 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]);
+    }
+
+    var charstringsIndex = this.createCFFIndexHeader([[0x40, 0x0E]].concat(glyphs), 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(" ");
+
+    var indexes = [
+      topDictIndex, stringsIndex,
+      globalSubrsIndex, charset,
+      charstringsIndex
+    ];
+
+    for (var i = 0; i < indexes.length; i++) {
+      var index = indexes[i];
+      cff.set(index, currentOffset);
+      currentOffset += index.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;
+
+    var fontData = [];
+    for (var i = 0; i < currentOffset; i++)
+      fontData.push(cff[i]);
+
+    return fontData;
+  }
+};
+
diff --git a/fonts_utils.js b/fonts_utils.js
new file mode 100644 (file)
index 0000000..086648f
--- /dev/null
@@ -0,0 +1,391 @@
+/**
+ * The Type2 reader code below is only used for debugging purpose since Type2
+ * is only a CharString format and is never used directly as a Font file.
+ *
+ * So the code here is useful for dumping the data content of a .cff file in
+ * order to investigate the similarity between a Type1 CharString and a Type2
+ * CharString or to understand the structure of the CFF format.
+ */
+
+
+/**
+ * Build a charset by assigning the glyph name and the human readable form
+ * of the glyph data.
+ */
+function readCharset(aStream, aCharstrings) {
+  var charset = {};
+
+  var format = aStream.getByte();
+  if (format == 0) {
+    charset[".notdef"] = readCharstringEncoding(aCharstrings[0]);
+
+    var count = aCharstrings.length - 1;
+    for (var i = 1; i < count + 1; i++) {
+      var sid = aStream.getByte() << 8 | aStream.getByte();
+      charset[CFFStrings[sid]] = readCharstringEncoding(aCharstrings[i]);
+      //log(CFFStrings[sid] + "::" + charset[CFFStrings[sid]]);
+    }
+  } else if (format == 1) {
+    error("Charset Range are not supported");
+  } else {
+    error("Invalid charset format");
+  }
+
+  return charset;
+};
+
+/**
+ * Take a Type2 binary charstring as input and transform it to a human
+ * readable representation as specified by the 'The Type 2 Charstring Format',
+ * chapter 3.1.
+ */
+function readCharstringEncoding(aString) {
+  var charstringTokens = [];
+
+  var count = aString.length;
+  for (var i = 0; i < count; ) {
+    var value = aString[i++];
+    var token = null;
+
+    if (value < 0) {
+      continue;
+    } else if (value <= 11) {
+      token = CFFEncodingMap[value];
+    } else if (value == 12) {
+      token = CFFEncodingMap[value][aString[i++]];
+    } else if (value <= 18) {
+      token = CFFEncodingMap[value];
+    } else if (value <= 20) {
+      var mask = aString[i++];
+      token = CFFEncodingMap[value];
+    } else if (value <= 27) {
+      token = CFFEncodingMap[value];
+    } else if (value == 28) {
+      token = aString[i++] << 8 | aString[i++];
+    } else if (value <= 31) {
+      token = CFFEncodingMap[value];
+    } else if (value < 247) {
+      token = parseInt(value) - 139;
+    } else if (value < 251) {
+      token = ((value - 247) * 256) + aString[i++] + 108;
+    } else if (value < 255) {
+      token = -((value - 251) * 256) - aString[i++] - 108;
+    } else {// value == 255
+      token = aString[i++] << 24 | aString[i++] << 16 |
+              aString[i++] << 8 | aString[i];
+    }
+
+    charstringTokens.push(token);
+  }
+
+  return charstringTokens;
+};
+
+
+/**
+ * Take a binary DICT Data as input and transform it into a human readable
+ * form as specified by 'The Compact Font Format Specification', chapter 5.
+ */
+function readFontDictData(aString, aMap) {
+  var fontDictDataTokens = [];
+
+  var count = aString.length;
+  for (var i = 0; i < count; i) {
+    var value = aString[i++];
+    var token = null;
+
+    if (value == 12) {
+      token = aMap[value][aString[i++]];
+    } else if (value == 28) {
+      token = aString[i++] << 8 | aString[i++];
+    } else if (value == 29) {
+      token = aString[i++] << 24 |
+              aString[i++] << 16 |
+              aString[i++] << 8  |
+              aString[i++];
+    } else if (value == 30) {
+      token = "";
+      var parsed = false;
+      while (!parsed) {
+        var byte = aString[i++];
+
+        var nibbles = [parseInt(byte / 16), parseInt(byte % 16)];
+        for (var j = 0; j < nibbles.length; j++) {
+          var nibble = nibbles[j];
+          switch (nibble) {
+            case 0xA:
+              token += ".";
+              break;
+            case 0xB:
+              token += "E";
+              break;
+            case 0xC:
+              token += "E-";
+              break;
+            case 0xD:
+              break;
+            case 0xE:
+              token += "-";
+              break;
+            case 0xF:
+              parsed = true;
+              break;
+            default:
+              token += nibble;
+              break;
+          }
+        }
+      };
+      token = parseFloat(token);
+    } else if (value <= 31) {
+      token = aMap[value];
+    } else if (value <= 246) {
+      token = parseInt(value) - 139;
+    } else if (value <= 250) {
+      token = ((value - 247) * 256) + aString[i++] + 108;
+    } else if (value <= 254) {
+      token = -((value - 251) * 256) - aString[i++] - 108;
+    } else if (value == 255) {
+      error("255 is not a valid DICT command");
+    }
+
+    fontDictDataTokens.push(token);
+  }
+
+  return fontDictDataTokens;
+};
+
+
+/**
+ * Take a stream as input and return an array of objects.
+ * In CFF an INDEX is a structure with the following format:
+ *  {
+ *    count: 2 bytes (Number of objects stored in INDEX),
+ *    offsize: 1 byte (Offset array element size),
+ *    offset: [count + 1] bytes (Offsets array),
+ *    data: - (Objects data)
+ *  }
+ *
+ *  More explanation are given in the 'CFF Font Format Specification',
+ *  chapter 5.
+ */
+function readFontIndexData(aStream, aIsByte) {
+  var count = aStream.getByte() << 8 | aStream.getByte();
+  var offsize = aStream.getByte();
+
+  function getNextOffset() {
+    switch (offsize) {
+      case 0:
+        return 0;
+      case 1:
+        return aStream.getByte();
+      case 2:
+        return aStream.getByte() << 8 | aStream.getByte();
+      case 3:
+        return aStream.getByte() << 16 | aStream.getByte() << 8 |
+               aStream.getByte();
+      case 4:
+      return aStream.getByte() << 24 | aStream.getByte() << 16 |
+             aStream.getByte() << 8 | aStream.getByte();
+    }
+  };
+
+  var offsets = [];
+  for (var i = 0; i < count + 1; i++)
+    offsets.push(getNextOffset());
+
+  log("Found " + count + " objects at offsets :" + offsets + " (offsize: " + offsize + ")");
+
+  // Now extract the objects
+  var relativeOffset = aStream.pos;
+  var objects = [];
+  for (var i = 0; i < count; i++) {
+    var offset = offsets[i];
+    aStream.pos = relativeOffset + offset - 1;
+
+    var data = [];
+    var length = offsets[i + 1] - 1;
+    for (var j = offset - 1; j < length; j++)
+      data.push(aIsByte ? aStream.getByte() : aStream.getChar());
+    objects.push(data);
+  }
+
+  return objects;
+};
+
+var Type2Parser = function(aFilePath) {
+  var font = new Dict();
+
+  var xhr = new XMLHttpRequest();
+  xhr.open("GET", aFilePath, false);
+  xhr.mozResponseType = xhr.responseType = "arraybuffer";
+  xhr.expected = (document.URL.indexOf("file:") == 0) ? 0 : 200;
+  xhr.send(null);
+  this.data = new Stream(xhr.mozResponseArrayBuffer || xhr.mozResponse ||
+                         xhr.responseArrayBuffer || xhr.response);
+
+  // Turn on this flag for additional debugging logs
+  var debug = false;
+
+  function dump(aStr) {
+    if (debug)
+      log(aStr);
+  };
+
+  function parseAsToken(aString, aMap) {
+    var decoded = readFontDictData(aString, aMap);
+
+    var stack = [];
+    var count = decoded.length;
+    for (var i = 0; i < count; i++) {
+      var token = decoded[i];
+      if (IsNum(token)) {
+        stack.push(token);
+      } else {
+        switch (token.operand) {
+          case "SID":
+            font.set(token.name, CFFStrings[stack.pop()]);
+            break;
+          case "number number":
+            font.set(token.name, {
+              offset: stack.pop(),
+              size: stack.pop()
+            });
+            break;
+          case "boolean":
+            font.set(token.name, stack.pop());
+            break;
+          case "delta":
+            font.set(token.name, stack.pop());
+            break;
+          default:
+            if (token.operand && token.operand.length) {
+              var array = [];
+              for (var j = 0; j < token.operand.length; j++)
+                array.push(stack.pop());
+              font.set(token.name, array);
+            } else {
+              font.set(token.name, stack.pop());
+            }
+            break;
+        }
+      }
+    }
+  };
+
+  this.parse = function(aStream) {
+    font.set("major", aStream.getByte());
+    font.set("minor", aStream.getByte());
+    font.set("hdrSize", aStream.getByte());
+    font.set("offsize", aStream.getByte());
+
+    // Move the cursor after the header
+    aStream.skip(font.get("hdrSize") - aStream.pos);
+
+    // Read the NAME Index
+    dump("Reading Index: Names");
+    font.set("Names", readFontIndexData(aStream));
+    log("Names: " + font.get("Names"));
+
+    // Read the Top Dict Index
+    dump("Reading Index: TopDict");
+    var topDict = readFontIndexData(aStream, true);
+    log("TopDict: " + topDict);
+
+    // Read the String Index
+    dump("Reading Index: Strings");
+    var strings = readFontIndexData(aStream);
+    log("strings: " + strings);
+
+    // Fill up the Strings dictionary with the new unique strings
+    for (var i = 0; i < strings.length; i++)
+      CFFStrings.push(strings[i].join(""));
+
+    // Parse the TopDict operator
+    var objects = [];
+    var count = topDict.length;
+    for (var i = 0; i < count; i++)
+      parseAsToken(topDict[i], CFFDictDataMap);
+
+    // Read the Global Subr Index that comes just after the Strings Index
+    // (cf. "The Compact Font Format Specification" Chapter 16)
+    dump("Reading Global Subr Index");
+    var subrs = readFontIndexData(aStream, true);
+    dump(subrs);
+
+    // Reading Private Dict
+    var private = font.get("Private");
+    log("Reading Private Dict (offset: " + private.offset + " size: " + private.size + ")");
+    aStream.pos = private.offset;
+
+    var privateDict = [];
+    for (var i = 0; i < private.size; i++)
+      privateDict.push(aStream.getByte());
+    dump("private:" + privateDict);
+    parseAsToken(privateDict, CFFDictPrivateDataMap);
+
+    for (var p in font.map)
+      dump(p + "::" + font.get(p));
+
+    // Read CharStrings Index
+    var charStringsOffset = font.get("CharStrings");
+    dump("Read CharStrings Index (offset: " + charStringsOffset + ")");
+    aStream.pos = charStringsOffset;
+    var charStrings = readFontIndexData(aStream, true);
+
+    // Read Charset
+    dump("Read Charset for " + charStrings.length + " glyphs");
+    var charsetEntry = font.get("charset");
+    if (charsetEntry == 0) {
+      error("Need to support CFFISOAdobeCharset");
+    } else if (charsetEntry == 1) {
+      error("Need to support CFFExpert");
+    } else if (charsetEntry == 2) {
+      error("Need to support CFFExpertSubsetCharset");
+    } else {
+      aStream.pos = charsetEntry;
+      var charset = readCharset(aStream, charStrings);
+    }
+  }
+};
+
+/*
+ * To try the Type2 decoder on a local file in the current directory:
+ *
+ *  var cff = new Type2Parser("file.cff");
+ *  cff.parse(this.data);
+ *
+ * To try the Type2 decoder on a custom built CFF array:
+ *
+ *  var file = new Uint8Array(cffFileArray, 0, cffFileSize);
+ *  var parser = new Type2Parser();
+ *  parser.parse(new Stream(file));
+ *
+ */
+
+
+/**
+ * Write to a file to the disk (works only on Firefox in privilege mode)
+ * but this is useful for dumping a font file to the disk and check with
+ * fontforge or the ots program what's wrong with the file.
+ *
+ * writeToFile(fontData, "/tmp/pdf.js." + fontCount + ".cff");
+ */
+function writeToFile(aBytes, aFilePath) {
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  var Cc = Components.classes,
+      Ci = Components.interfaces;
+  var file  = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
+  file.initWithPath(aFilePath);
+
+  var stream = Cc["@mozilla.org/network/file-output-stream;1"]
+                 .createInstance(Ci.nsIFileOutputStream);
+  stream.init(file, 0x04 | 0x08 | 0x20, 0600, 0);
+
+  var bos = Cc["@mozilla.org/binaryoutputstream;1"]
+              .createInstance(Ci.nsIBinaryOutputStream);
+  bos.setOutputStream(stream);
+  bos.writeByteArray(aBytes, aBytes.length);
+  stream.close();
+};
+
index 276ba30da4ef87fe8fd6a37911e2631866ca4dea..023cdeec2e14dbe7190a3bc09c3f38f4101085e0 100644 (file)
--- a/test.html
+++ b/test.html
@@ -1,15 +1,14 @@
 <html>
-<<<<<<< HEAD
     <head>
         <title>Simple pdf.js page viewer</title>
         <link rel="stylesheet" href="test.css"></link>
 
-        <script type="text/javascript" src="pdf.js"></script>
         <script type="text/javascript" src="test.js"></script>
+        <script type="text/javascript" src="pdf.js"></script>
+        <script type="text/javascript" src="fonts.js"></script>
         <script type="text/javascript" src="cffStandardStrings.js"></script>
-        <script type="text/javascript" src="glyphlist.js"></script>
         <script type="text/javascript" src="Encodings.js"></script>
-        <script type="text/javascript" src="PDFFont.js"></script>
+        <script type="text/javascript" src="glyphlist.js"></script>
   </head>
 
   <body onload="load();">