From: Vivien Nicolas <21@vingtetun.org> Date: Mon, 13 Jun 2011 00:30:16 +0000 (+0200) Subject: Beginning of the separatation of the Type1/CFF/OTF code X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=a8ce1d24e95e73ae4862ef7ae81d69240839adc2;p=pdf.js.git Beginning of the separatation of the Type1/CFF/OTF code --- diff --git a/PDFFont.js b/PDFFont.js index 15e5f2c..8cddb23 100644 --- a/PDFFont.js +++ b/PDFFont.js @@ -1,6 +1,12 @@ /* -*- 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; + + /** * Hold a map of decoded fonts and of the standard fourteen Type1 fonts and * their acronyms. @@ -32,20 +38,32 @@ var Font = function(aFontName, aFontFile, aFontType) { switch (aFontType) { case "Type1": + // All Type1 font program should begin with the comment %! + if (aFontFile.getByte() != 0x25 || aFontFile.getByte() != 0x21) + error("Invalid file header"); + + var cff = new CFF(aFontName, aFontFile); this.mimetype = "font/otf"; - this.font = new Type1(aFontName, aFontFile); + + // Wrap the CFF data inside an OTF font file + this.font = this.cover(cff); break; + case "TrueType": this.mimetype = "font/ttf"; - this.font = new TrueType(aFontName, aFontFile); + var ttf = new TrueType(aFontName, aFontFile); + this.font = ttf.data; break; + default: - error("Font " + aFontType + " is not supported"); + warn("Font " + aFontType + " is not supported"); break; } - Fonts[aFontName] = this.font; + // Attach the font to the document this.bind(); + + Fonts[aFontName] = this.font; }; Font.prototype = { @@ -53,8 +71,8 @@ Font.prototype = { font: null, mimetype: null, - bind: function() { - var data = this.font.data; + bind: function font_bind() { + var data = this.font; // Compute the binary data to base 64 var str = []; @@ -70,1443 +88,1423 @@ Font.prototype = { var rule = "@font-face { font-family:'" + this.name + "';src:" + url + "}"; var styleSheet = document.styleSheets[0]; styleSheet.insertRule(rule, styleSheet.length); - } -}; - -/** Implementation dirty logic starts here */ - -var kMaxFontFileSize = 100000; - -/** - * This dictionary holds decoded fonts data. - */ -var PSFonts = new Dict(); + }, + _createOpenTypeHeader: function font_createOpenTypeHeader(aNumTables) { + // sfnt version (4 bytes) + var version = [0x4F, 0x54, 0x54, 0X4F]; -var Stack = function() { - var innerStack = []; + // numTables (2 bytes) + var numTables = aNumTables; - this.push = function(aOperand) { - innerStack.push(aOperand); - }; + // searchRange (2 bytes) + var tablesMaxPower2 = FontsUtils.getMaxPower2(numTables); + var searchRange = tablesMaxPower2 * 16; - this.pop = function() { - if (!this.count()) - throw new Error("stackunderflow"); - return innerStack.pop(); - }; + // entrySelector (2 bytes) + var entrySelector = Math.log(tablesMaxPower2) / Math.log(2); - this.peek = function() { - if (!this.count()) - return null; - return innerStack[innerStack.length - 1]; - }; + // rangeShift (2 bytes) + var rangeShift = numTables * 16 - searchRange; - this.get = function(aIndex) { - return innerStack[aIndex]; - }; + return [].concat(version, + FontsUtils.integerToBytes(numTables, 2), + FontsUtils.integerToBytes(searchRange, 2), + FontsUtils.integerToBytes(entrySelector, 2), + FontsUtils.integerToBytes(rangeShift, 2)); + }, - this.clear = function() { - innerStack = []; - }; + _createTableEntry: function font_createTableEntry(aTag, aOffset, aData) { + // tag + var tag = [ + aTag.charCodeAt(0), + aTag.charCodeAt(1), + aTag.charCodeAt(2), + aTag.charCodeAt(3) + ]; - this.count = function() { - return innerStack.length; - }; + // offset + var offset = aOffset; - this.dump = function() { - for (var i = 0; i < this.length; i++) - log(innerStack[i]); - }; + // Per spec tables must be 4-bytes align so add some 0x00 if needed + while (aData.length & 3) + aData.push(0x00); - this.clone = function() { - return innerStack.slice(); - }; -}; + // length + var length = aData.length; -var TrueType = function(aFontName, aFontFile) { - var debug = true; - function dump(aMsg) { - if (debug) - log(aMsg); - } + // checksum + var checksum = FontsUtils.bytesToInteger(tag) + offset + length; - dump("Loading a TrueType font: " + aFontName); - this.data = aFontFile; -}; + return [].concat(tag, + FontsUtils.integerToBytes(checksum, 4), + FontsUtils.integerToBytes(offset, 4), + FontsUtils.integerToBytes(length, 4)); + }, -var Type1Parser = function(aAsciiStream, aBinaryStream) { - var lexer = aAsciiStream ? new Lexer(aAsciiStream) : null; + _createCMAPTable: function font_createCMAPTable(aGlyphs) { + var data = new Array(1000); + for (var i = 0; i < aGlyphs.length; i++) + data[aGlyphs[i].unicode] = i + 1; + + var ranges = []; + var range = []; + for (var i = 0; i < data.length; i++) { + var char = data[i]; + if (char) { + range.push(i); + } else if (range.length) { + if (0) { + log("create a new range of " + range.length + " chars width min: " + range[0] + " to max: " + range[range.length - 1]); + log("range content is: " + range); + } + ranges.push(range.slice()); + range = []; + } + } - // Turn on this flag for additional debugging logs - var debug = false; - var dump = function(aData) { - if (debug) - log(aData); - }; + var cmap = []; + var segCount = ranges.length + 1; - // Hold the fontName as declared inside the /FontName postscript directive - // XXX This is a hack but at the moment I need it to map the name declared - // in the PDF and the name in the PS code. - var fontName = ""; + var segCount2 = segCount * 2; + var searchRange = FontsUtils.getMaxPower2(segCount) * 2; + var searchEntry = Math.log(segCount) / Math.log(2); + var rangeShift = 2 * segCount - searchRange; + cmap = cmap.concat(FontsUtils.integerToBytes(segCount2, 2)); + cmap = cmap.concat(FontsUtils.integerToBytes(searchRange, 2)); + cmap = cmap.concat(FontsUtils.integerToBytes(searchEntry, 2)); + cmap = cmap.concat(FontsUtils.integerToBytes(rangeShift, 2)); - /* - * Parse a whole Type1 font stream (from the first segment to the last) - * assuming the 'eexec' block is binary data and fill up the 'Fonts' - * dictionary with the font informations. - */ - var self = this; - this.parse = function() { - if (!debug) { - while (!processNextToken()) {}; - return fontName; - } else { - // debug mode is used to debug postcript processing - setTimeout(function() { - if (!processNextToken()) - self.parse(); - }, 0); - } - }; + // End characters code with an additional 0xFFFF to finish the array + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + cmap = cmap.concat(FontsUtils.integerToBytes(range[range.length - 1], 2)); + }; + cmap = cmap.concat([0xFF, 0xFF]); - /* - * 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; + // reserved pad + cmap = cmap.concat([0x00, 0x00]); - function decrypt(aStream, aKey, aDiscardNumber) { - var start = Date.now(); - var r = aKey, c1 = 52845, c2 = 22719; - var decryptedString = []; + // Start characters code with an additional 0xFFFF to finish the array + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + cmap = cmap.concat(FontsUtils.integerToBytes(range[0], 2)); + }; + cmap = cmap.concat([0xFF, 0xFF]); + + // Fill idDelta + var delta = 0; + var p = 0; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + var start = range[0]; + var delta = ((start - 1) - p) % 65536; + + var value = FontsUtils.integerToBytes(delta, 2); + value[0] ^= 0xFF; + value[1] ^= 0xFF; + value[1] += 1; + cmap = cmap.concat([value[0], value[1]]); + + p += range.length; + }; + cmap = cmap.concat([0x00, 0x01]); - var value = ""; - var count = aStream.length; - for (var i = 0; i < count; i++) { - value = aStream.getByte(); - decryptedString[i] = String.fromCharCode(value ^ (r >> 8)); - r = ((value + r) * c1 + c2) & ((1 << 16) - 1); - } - var end = Date.now(); - dump("Time to decrypt string of length " + count + " is " + (end - start)); - return decryptedString.slice(aDiscardNumber); - }; - /* - * CharStrings are encoded following the the CharString Encoding sequence - * describe in Chapter 6 of the "Adobe Type1 Font Format" specification. - * The value in a byte indicates a command, a number, or subsequent bytes - * that are to be interpreted in a special way. - * - * CharString Number Encoding: - * A CharString byte containing the values from 32 through 255 inclusive - * indicate an integer. These values are decoded in four ranges. - * - * 1. A CharString byte containing a value, v, between 32 and 246 inclusive, - * indicate the integer v - 139. Thus, the integer values from -107 through - * 107 inclusive may be encoded in single byte. - * - * 2. A CharString byte containing a value, v, between 247 and 250 inclusive, - * indicates an integer involving the next byte, w, according to the formula: - * [(v - 247) x 256] + w + 108 - * - * 3. A CharString byte containing a value, v, between 251 and 254 inclusive, - * indicates an integer involving the next byte, w, according to the formula: - * -[(v - 251) * 256] - w - 108 - * - * 4. A CharString containing the value 255 indicates that the next 4 bytes - * are a two complement signed integer. The first of these bytes contains the - * highest order bits, the second byte contains the next higher order bits - * and the fourth byte contain the lowest order bits. - * - * - * CharString Command Encoding: - * CharStrings commands are encoded in 1 or 2 bytes. - * - * Single byte commands are encoded in 1 byte that contains a value between - * 0 and 31 inclusive. - * If a command byte contains the value 12, then the value in the next byte - * indicates a command. This "escape" mechanism allows many extra commands - * to be encoded and this encoding technique helps to minimize the length of - * the charStrings. - */ - var charStringDictionary = { - "1": "hstem", - "3": "vstem", - "4": "vmoveto", - "5": "rlineto", - "6": "hlineto", - "7": "vlineto", - "8": "rrcurveto", - "9": "closepath", - "10": "callsubr", - "11": "return", - "12": { - "0": "dotsection", - "1": "vstem3", - "3": "hstem3", - "6": "seac", - "7": "sbw", - "12": "div", - "16": "callothersubr", - "17": "pop", - "33": "setcurrentpoint" - }, - "13": "hsbw", - "14": "endchar", - "21": "rmoveto", - "22": "hmoveto", - "30": "vhcurveto", - "31": "hvcurveto" - }; + // Fill id Offsets with 0x00 + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + cmap = cmap.concat([0x00, 0x00]); + }; + cmap = cmap.concat([0x00, 0x00]); + + var cmapHeader = [ + 0x00, 0x00, // version + 0x00, 0x01, // numTables + 0x00, 0x03, // platformID + 0x00, 0x01, // encodingID + 0x00, 0x00, 0x00, 0x0C, // start of the table record + 0x00, 0x04 // format + ]; + cmapHeader = cmapHeader.concat(FontsUtils.integerToBytes(cmap.length + 6, 2)); // length + cmapHeader = cmapHeader.concat(0x00, 0x00); // language + + // Fill up data! + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + for (var j = 0; j < range.length; j++) + cmap = cmap.concat(range[j]); + }; + cmap = cmapHeader.concat(cmap); + return cmap; + }, - function decodeCharString(aStream) { - var start = Date.now(); - var charString = []; + cover: function font_cover(aFont) { + var otf = new Uint8Array(kMaxFontFileSize); + var aFontData = aFont.data; + var currentOffset = 0; - var value = ""; - var count = aStream.length; - for (var i = 0; i < count; i++) { - value = aStream.getByte(); + var numTables = 9; + //var tables = [OS2, cmap, head, hhea, hmtx, maxp, name, post]; + var header = this._createOpenTypeHeader(numTables); + otf.set(header, currentOffset); + currentOffset += header.length; - if (value < 32) { - if (value == 12) { - value = charStringDictionary["12"][aStream.getByte()]; - i++; - } else { - value = charStringDictionary[value]; - } - } else if (value <= 246) { - value = parseInt(value) - 139; - } else if (value <= 250) { - value = ((value - 247) * 256) + parseInt(aStream.getByte()) + 108; - i++; - } else if (value <= 254) { - value = -((value - 251) * 256) - parseInt(aStream.getByte()) - 108; - i++; - } else { - var byte = aStream.getByte(); - var high = (byte >> 1); - value = (byte - high) << 24 | aStream.getByte() << 16 | - aStream.getByte() << 8 | aStream.getByte(); - i += 4; - } + var baseOffset = numTables * (4 * 4) + currentOffset; + var virtualOffset = baseOffset; + var tableEntry = this._createTableEntry("CFF ", virtualOffset, aFontData); + otf.set(tableEntry, currentOffset); + currentOffset += tableEntry.length; + virtualOffset += aFontData.length; - charString.push(value); - } + var 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 + 0x00, 0x00, 0x00, 0x01, // ulUnicodeRange1 (Bits 0-31) + 0x00, 0x00, 0x00, 0x00, // ulUnicodeRange2 (Bits 32-63) + 0x00, 0x00, 0x00, 0x00, // ulUnicodeRange3 (Bits 64-95) + 0x00, 0x00, 0x00, 0x00, // ulUnicodeRange4 (Bits 96-127) + 0x47, 0x49, 0x60, 0x20, // 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 + ]; - var end = Date.now(); - dump("Time to decode charString of length " + count + " is " + (end - start)); - return charString; - } + var tableEntry = this._createTableEntry("OS/2", virtualOffset, OS2); + otf.set(tableEntry, currentOffset); + currentOffset += tableEntry.length; + virtualOffset += OS2.length; - /* - * The operand stack holds arbitrary PostScript objects that are the operands - * and results of PostScript operators being executed. The interpreter pushes - * objects on the operand stack when it encounters them as literal data in a - * program being executed. When an operator requires one or more operands, it - * obtains them by popping them off the top of the operand stack. When an - * operator returns one or more results, it does so by pushing them on the - * operand stack. - */ - var operandStack = new Stack(); + /** CMAP */ + var charstrings = aFont.getOrderedCharStrings(aFont.font); - // Flag indicating if the topmost operand of the operandStack is an array - var operandIsArray = 0; + var cmap = this._createCMAPTable(charstrings); + var tableEntry = this._createTableEntry("cmap", virtualOffset, cmap); + otf.set(tableEntry, currentOffset); + currentOffset += tableEntry.length; + virtualOffset += cmap.length; - /* - * The dictionary stack holds only dictionary objects. The current set of - * dictionaries on the dictionary stack defines the environment for all - * implicit name searches, such as those that occur when the interpreter - * encounters an executable name. The role of the dictionary stack is - * introduced in Section 3.3, “Data Types and Objects,” and is further - * explained in Section 3.5, “Execution.” of the PostScript Language - * Reference. - */ - var systemDict = new Dict(), - globalDict = new Dict(), - userDict = new Dict(); - var dictionaryStack = new Stack(); - dictionaryStack.push(systemDict); - dictionaryStack.push(globalDict); - dictionaryStack.push(userDict); + /** HEAD */ - /* - * The execution stack holds executable objects (mainly procedures and files) - * that are in intermediate stages of execution. At any point in the - * execution of a PostScript program, this stack represents the program’s - * call stack. Whenever the interpreter suspends execution of an object to - * execute some other object, it pushes the new object on the execution - * stack. When the interpreter finishes executing an object, it pops that - * object off the execution stack and resumes executing the suspended object - * beneath it. - */ - var executionStack = new Stack(); + var 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 (>= 16 && <=16384) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // created + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // modified + 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 + ]; + var tableEntry = this._createTableEntry("head", virtualOffset, head); + otf.set(tableEntry, currentOffset); + currentOffset += tableEntry.length; + virtualOffset += head.length; - /* - * Return the next token in the execution stack - */ - function nextInStack() { - var currentProcedure = executionStack.peek(); - if (currentProcedure) { - var command = currentProcedure.shift(); - if (!currentProcedure.length) - executionStack.pop(); - return command; + + /** HHEA */ + + var hhea = [ + 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 + ]; + hhea = hhea.concat(FontsUtils.integerToBytes(charstrings.length, 2)); // numberOfHMetrics + + var tableEntry = this._createTableEntry("hhea", virtualOffset, hhea); + otf.set(tableEntry, currentOffset); + currentOffset += tableEntry.length; + virtualOffset += hhea.length; + + /** HMTX */ + + var 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); } - return lexer.getObj(); - }; + var tableEntry = this._createTableEntry("hmtx", virtualOffset, hmtx); + otf.set(tableEntry, currentOffset); + currentOffset += tableEntry.length; + virtualOffset += hmtx.length; - /* - * Get the next token from the executionStack and process it. - * Actually the function does not process the third segment of a Type1 font - * and end on 'closefile'. - * - * The method thrown an error if it encounters an unknown token. - */ - function processNextToken() { - var obj = nextInStack(); - if (operandIsArray && !IsCmd(obj, "{") && !IsCmd(obj, "[") && - !IsCmd(obj, "]") && !IsCmd(obj, "}")) { - dump("Adding an object: " + obj +" to array " + operandIsArray); - var currentArray = operandStack.peek(); - for (var i = 1; i < operandIsArray; i++) - currentArray = currentArray[currentArray.length - 1]; - currentArray.push(obj); - } else if (IsBool(obj) || IsInt(obj) || IsNum(obj) || IsString(obj)) { - dump("Value: " + obj); - operandStack.push(obj); - } else if (IsName(obj)) { - dump("Name: " + obj.name); - operandStack.push(obj.name); - } else if (IsCmd(obj)) { - var command = obj.cmd; - dump(command); + /** MAXP */ - switch (command) { - case "[": - case "{": - dump("Start" + (command == "{" ? " Executable " : " ") + "Array"); - operandIsArray++; - var currentArray = operandStack; - for (var i = 1; i < operandIsArray; i++) - if (currentArray.peek) - currentArray = currentArray.peek(); - else - currentArray = currentArray[currentArray.length - 1]; - currentArray.push([]); - break; + var maxp = [ + 0x00, 0x00, 0x50, 0x00, // Version number + ].concat(FontsUtils.integerToBytes(charstrings.length + 1, 2)); // Num of glyphs (+1 to pass the sanitizer...) - case "]": - case "}": - var currentArray = operandStack.peek(); - for (var i = 1; i < operandIsArray; i++) - currentArray = currentArray[currentArray.length - 1]; - dump("End" + (command == "}" ? " Executable " : " ") + "Array: " + currentArray.join(" ")); - operandIsArray--; - break; + var tableEntry = this._createTableEntry("maxp", virtualOffset, maxp); + otf.set(tableEntry, currentOffset); + currentOffset += tableEntry.length; + virtualOffset += maxp.length; - case "if": - var procedure = operandStack.pop(); - var bool = operandStack.pop(); - if (!IsBool(bool)) { - dump("if: " + bool); - // we need to execute things, let be dirty - executionStack.push(bool); - } else { - dump("if ( " + bool + " ) { " + procedure + " }"); - if (bool) - executionStack.push(procedure); - } - break; - case "ifelse": - var procedure1 = operandStack.pop(); - var procedure2 = operandStack.pop(); - var bool = !!operandStack.pop(); - dump("if ( " + bool + " ) { " + procedure2 + " } else { " + procedure1 + " }"); - executionStack.push(bool ? procedure2 : procedure1); - break; + /** NAME */ - case "for": - var procedure = operandStack.pop(); - var limit = operandStack.pop(); - var increment = operandStack.pop(); - var initial = operandStack.pop(); - for (var i = 0; i < limit; i += increment) { - operandStack.push(i); - executionStack.push(procedure.slice()); - } - break; + var name = [ + 0x00, 0x00, // format + 0x00, 0x00, // Number of names Record + 0x00, 0x00 // Storage + ]; + var tableEntry = this._createTableEntry("name", virtualOffset, name); + otf.set(tableEntry, currentOffset); + currentOffset += tableEntry.length; + virtualOffset += name.length; - case "dup": - dump("duplicate: " + operandStack.peek()); - operandStack.push(operandStack.peek()); - break; - case "mark": - operandStack.push("mark"); - break; + /** POST */ - case "cleartomark": - var command = ""; - do { - command = operandStack.pop(); - } while (command != "mark"); - break; + // XXX get those info from the Font dict! + var post = [ + 0x00, 0x03, 0x00, 0x00, // Version number + 0x00, 0x00, 0x01, 0x00, // italicAngle + 0x00, 0x00, // underlinePosition + 0x00, 0x00, // underlineThickness + 0x00, 0x00, 0x00, 0x00, // isFixedPitch + 0x00, 0x00, 0x00, 0x00, // minMemType42 + 0x00, 0x00, 0x00, 0x00, // maxMemType42 + 0x00, 0x00, 0x00, 0x00, // minMemType1 + 0x00, 0x00, 0x00, 0x00 // maxMemType1 + ]; + var tableEntry = this._createTableEntry("post", virtualOffset, post); + otf.set(tableEntry, currentOffset); + currentOffset += tableEntry.length; + virtualOffset += post.length; - case "put": - var data = operandStack.pop(); - var indexOrKey = operandStack.pop(); - var object = operandStack.pop(); - dump("put " + data + " in " + object + "[" + indexOrKey + "]"); - object.set ? object.set(indexOrKey, data) - : object[indexOrKey] = data; - break; + // Set the CFF data + otf.set(aFontData, currentOffset); + currentOffset += aFontData.length; - case "pop": - operandStack.pop(); - break; + var tables = [OS2, cmap, head, hhea, hmtx, maxp, name, post]; + for (var i = 0; i < tables.length; i++) { + var table = tables[i]; + otf.set(table, currentOffset); + currentOffset += table.length; + } - case "exch": - var operand1 = operandStack.pop(); - var operand2 = operandStack.pop(); - operandStack.push(operand1); - operandStack.push(operand2); - break; + var fontData = []; + for (var i = 0; i < currentOffset; i++) + fontData.push(otf[i]); - case "get": - var indexOrKey = operandStack.pop(); - var object = operandStack.pop(); - var data = object.get ? object.get(indexOrKey) : object[indexOrKey]; - dump("get " + object + "[" + indexOrKey + "]: " + data); - operandStack.push(data); - break; + //writeToFile(fontData, "/tmp/pdf.js." + fontCount + ".otf"); + return fontData; + } +}; - case "currentdict": - var dict = dictionaryStack.peek(); - operandStack.push(dict); - break; - case "systemdict": - operandStack.push(systemDict); - break; +var FontsUtils = { + integerToBytes: function fu_integerToBytes(aValue, aBytesCount) { + var bytes = []; + for (var i = 0; i < aBytesCount; i++) + bytes[i] = 0x00; - case "readonly": - case "executeonly": - case "noaccess": - // Do nothing for the moment - break; + do { + bytes[--aBytesCount] = (aValue & 0xFF); + aValue = aValue >> 8; + } while (aBytesCount && aValue > 0); - case "currentfile": - operandStack.push("currentfile"); - break; + return bytes; + }, - case "array": - var size = operandStack.pop(); - var array = new Array(size); - operandStack.push(array); - break; + bytesToInteger: function(aBytesArray) { + var value = 0; + for (var i = 0; i < aBytesArray.length; i++) + value = (value << 8) + aBytesArray[i]; + return value; + }, - case "dict": - var size = operandStack.pop(); - var dict = new Dict(size); - operandStack.push(dict); - break; + 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; + } +}; - case "begin": - dictionaryStack.push(operandStack.pop()); - break; - case "end": - dictionaryStack.pop(); - break; +/** Implementation dirty logic starts here */ - case "def": - var value = operandStack.pop(); - var key = operandStack.pop(); - dump("def: " + key + " = " + value); - dictionaryStack.peek().set(key, value); - break; +/** + * At the moment TrueType is just a stub that does mostly nothing but in a + * (near?) future this class will rewrite the font to ensure it is well formed + * and valid in the point of view of the sanitizer. + */ +var TrueType = function(aFontName, aFontFile) { + this.data = aFontFile; +}; - case "definefont": - var font = operandStack.pop(); - var key = operandStack.pop(); - dump("definefont " + font + " with key: " + key); +/** + * This dictionary holds decoded fonts data. + */ +var PSFonts = new Dict(); - // The key will be the identifier to recognize this font - fontName = key; - PSFonts.set(key, font); - operandStack.push(font); - break; +var Stack = function() { + var innerStack = []; - case "known": - var name = operandStack.pop(); - var dict = operandStack.pop(); - var data = !!dict.get(name); - dump("known: " + data + " :: " + name + " in dict: " + dict); - operandStack.push(data); - break; + this.push = function(aOperand) { + innerStack.push(aOperand); + }; - case "exec": - executionStack.push(operandStack.pop()); - break; + this.pop = function() { + if (!this.count()) + throw new Error("stackunderflow"); + return innerStack.pop(); + }; - case "eexec": - // All the first segment data has been read, decrypt the second segment - // and start interpreting it in order to decode it - var file = operandStack.pop(); - var eexecString = decrypt(aBinaryStream, kEexecEncryptionKey, 4).join(""); - dump(eexecString); - lexer = new Lexer(new StringStream(eexecString)); - break; + this.peek = function() { + if (!this.count()) + return null; + return innerStack[innerStack.length - 1]; + }; - case "LenIV": - error("LenIV: argh! we need to modify the length of discard characters for charStrings"); - break; + this.get = function(aIndex) { + return innerStack[aIndex]; + }; - case "closefile": - var file = operandStack.pop(); - return true; - break; + this.clear = function() { + innerStack = []; + }; - case "index": - var operands = []; - var size = operandStack.pop(); - for (var i = 0; i < size; i++) - operands.push(operandStack.pop()); + this.count = function() { + return innerStack.length; + }; - var newOperand = operandStack.peek(); + this.dump = function() { + for (var i = 0; i < this.length; i++) + log(innerStack[i]); + }; - while (operands.length) - operandStack.push(operands.pop()); + this.clone = function() { + return innerStack.slice(); + }; +}; - operandStack.push(newOperand); - break; +var Type1Parser = function(aAsciiStream, aBinaryStream) { + var lexer = aAsciiStream ? new Lexer(aAsciiStream) : null; - case "string": - var size = operandStack.pop(); - var str = (new Array(size + 1)).join(" "); - operandStack.push(str); - break; + // Turn on this flag for additional debugging logs + var debug = false; - case "readstring": - var str = operandStack.pop(); - var size = str.length; + var dump = function(aData) { + if (debug) + log(aData); + }; - var file = operandStack.pop(); + // Hold the fontName as declared inside the /FontName postscript directive + // XXX This is a hack but at the moment I need it to map the name declared + // in the PDF and the name in the PS code. + var fontName = ""; - // Add '1' because of the space separator, this is dirty - var stream = lexer.stream.makeSubStream(lexer.stream.pos + 1, size); - lexer.stream.skip(size + 1); + /* + * Parse a whole Type1 font stream (from the first segment to the last) + * assuming the 'eexec' block is binary data and fill up the 'Fonts' + * dictionary with the font informations. + */ + var self = this; + this.parse = function() { + if (!debug) { + while (!processNextToken()) {}; + return fontName; + } else { + // debug mode is used to debug postcript processing + setTimeout(function() { + if (!processNextToken()) + self.parse(); + }, 0); + } + }; - var charString = decrypt(stream, kCharStringsEncryptionKey, 4).join(""); - var charStream = new StringStream(charString); - var decodedCharString = decodeCharString(charStream); - dump("decodedCharString: " + decodedCharString); - operandStack.push(decodedCharString); + /* + * 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; - // boolean indicating if the operation is a success or not - operandStack.push(true); - break; + function decrypt(aStream, aKey, aDiscardNumber) { + var start = Date.now(); + var r = aKey, c1 = 52845, c2 = 22719; + var decryptedString = []; - case "StandardEncoding": - // For some reason the value is considered as a command, maybe it is - // because of the uppercase 'S' - operandStack.push(obj.cmd); - break; + var value = ""; + var count = aStream.length; + for (var i = 0; i < count; i++) { + value = aStream.getByte(); + 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); + }; - default: - var command = null; - if (IsCmd(obj)) { - for (var i = 0; i < dictionaryStack.count(); i++) { - if (command = dictionaryStack.get(i).get(obj.cmd)) { - dump("found in dictionnary for " + obj.cmd + " command: " + command); - executionStack.push(command.slice()); - break; - } - } - } + /* + * CharStrings are encoded following the the CharString Encoding sequence + * describe in Chapter 6 of the "Adobe Type1 Font Format" specification. + * The value in a byte indicates a command, a number, or subsequent bytes + * that are to be interpreted in a special way. + * + * CharString Number Encoding: + * A CharString byte containing the values from 32 through 255 inclusive + * indicate an integer. These values are decoded in four ranges. + * + * 1. A CharString byte containing a value, v, between 32 and 246 inclusive, + * indicate the integer v - 139. Thus, the integer values from -107 through + * 107 inclusive may be encoded in single byte. + * + * 2. A CharString byte containing a value, v, between 247 and 250 inclusive, + * indicates an integer involving the next byte, w, according to the formula: + * [(v - 247) x 256] + w + 108 + * + * 3. A CharString byte containing a value, v, between 251 and 254 inclusive, + * indicates an integer involving the next byte, w, according to the formula: + * -[(v - 251) * 256] - w - 108 + * + * 4. A CharString containing the value 255 indicates that the next 4 bytes + * are a two complement signed integer. The first of these bytes contains the + * highest order bits, the second byte contains the next higher order bits + * and the fourth byte contain the lowest order bits. + * + * + * CharString Command Encoding: + * CharStrings commands are encoded in 1 or 2 bytes. + * + * Single byte commands are encoded in 1 byte that contains a value between + * 0 and 31 inclusive. + * If a command byte contains the value 12, then the value in the next byte + * indicates a command. This "escape" mechanism allows many extra commands + * to be encoded and this encoding technique helps to minimize the length of + * the charStrings. + */ + var charStringDictionary = { + "1": "hstem", + "3": "vstem", + "4": "vmoveto", + "5": "rlineto", + "6": "hlineto", + "7": "vlineto", + "8": "rrcurveto", + "9": "closepath", + "10": "callsubr", + "11": "return", + "12": { + "0": "dotsection", + "1": "vstem3", + "3": "hstem3", + "6": "seac", + "7": "sbw", + "12": "div", + "16": "callothersubr", + "17": "pop", + "33": "setcurrentpoint" + }, + "13": "hsbw", + "14": "endchar", + "21": "rmoveto", + "22": "hmoveto", + "30": "vhcurveto", + "31": "hvcurveto" + }; + + function decodeCharString(aStream) { + var start = Date.now(); + var charString = []; - if (!command) { - log("operandStack: " + operandStack); - log("dictionaryStack: " + dictionaryStack); - log(obj); - error("Unknow command while parsing font"); - } - break; + var value = ""; + var count = aStream.length; + for (var i = 0; i < count; i++) { + value = aStream.getByte(); + + if (value < 32) { + if (value == 12) { + value = charStringDictionary["12"][aStream.getByte()]; + i++; + } else { + value = charStringDictionary[value]; + } + } else if (value <= 246) { + value = parseInt(value) - 139; + } else if (value <= 250) { + value = ((value - 247) * 256) + parseInt(aStream.getByte()) + 108; + i++; + } else if (value <= 254) { + value = -((value - 251) * 256) - parseInt(aStream.getByte()) - 108; + i++; + } else { + var byte = aStream.getByte(); + var high = (byte >> 1); + value = (byte - high) << 24 | aStream.getByte() << 16 | + aStream.getByte() << 8 | aStream.getByte(); + i += 4; } - } else if (obj) { - dump("unknow: " + obj); - operandStack.push(obj); - } else { // The End! - operandStack.dump(); - return true; + + charString.push(value); } - return false; + var end = Date.now(); + dump("Time to decode charString of length " + count + " is " + (end - start)); + return charString; } /* - * Flatten the commands by interpreting the postscript code and replacing - * every 'callsubr', 'callothersubr' by the real commands. - * At the moment OtherSubrs are not fully supported and only otherSubrs 0-4 - * as descrived in 'Using Subroutines' of 'Adobe Type 1 Font Format', - * chapter 8. + * The operand stack holds arbitrary PostScript objects that are the operands + * and results of PostScript operators being executed. The interpreter pushes + * objects on the operand stack when it encounters them as literal data in a + * program being executed. When an operator requires one or more operands, it + * obtains them by popping them off the top of the operand stack. When an + * operator returns one or more results, it does so by pushing them on the + * operand stack. */ - this.flattenCharstring = function(aCharstring, aDefaultWidth, aSubrs) { - operandStack.clear(); - executionStack.clear(); - executionStack.push(aCharstring); - - var leftSidebearing = 0; - var lastPoint = 0; - while (true) { - var obj = nextInStack(); - if (IsBool(obj) || IsInt(obj) || IsNum(obj)) { - dump("Value: " + obj); - operandStack.push(obj); - } else if (IsString(obj)) { - dump("String: " + obj); - switch (obj) { - case "hsbw": - var charWidthVector = operandStack.pop(); - leftSidebearing = operandStack.pop(); - - if (charWidthVector != aDefaultWidth) - operandStack.push(charWidthVector - aDefaultWidth); - break; - - case "rmoveto": - var dy = operandStack.pop(); - var dx = operandStack.pop(); - - if (leftSidebearing) { - dx += leftSidebearing; - leftSidebearing = 0; - } - - operandStack.push(dx); - operandStack.push(dy); - operandStack.push("rmoveto"); - break; - - case "div": - var num2 = operandStack.pop(); - var num1 = operandStack.pop(); - operandStack.push(num2 / num1); - break; - - case "setcurrentpoint": - case "dotsection": - case "seac": - case "sbw": - error(obj + " parsing is not implemented (yet)"); - break; - - case "closepath": - case "return": - break; - - case "vstem3": - case "vstem": - operandStack.push("vstem"); - break; - - case "hstem": - case "hstem3": - operandStack.push("hstem"); - break; + var operandStack = new Stack(); - case "callsubr": - var index = operandStack.pop(); - executionStack.push(aSubrs[index].slice()); - break; + // Flag indicating if the topmost operand of the operandStack is an array + var operandIsArray = 0; - case "callothersubr": - // XXX need to be improved - var index = operandStack.pop(); - var count = operandStack.pop(); - var data = operandStack.pop(); - if (index != 3) - dump("callothersubr for index: " + index); - operandStack.push(3); - operandStack.push("callothersubr"); - break; + /* + * The dictionary stack holds only dictionary objects. The current set of + * dictionaries on the dictionary stack defines the environment for all + * implicit name searches, such as those that occur when the interpreter + * encounters an executable name. The role of the dictionary stack is + * introduced in Section 3.3, “Data Types and Objects,” and is further + * explained in Section 3.5, “Execution.” of the PostScript Language + * Reference. + */ + var systemDict = new Dict(), + globalDict = new Dict(), + userDict = new Dict(); - case "endchar": - operandStack.push("endchar"); - return operandStack.clone(); + var dictionaryStack = new Stack(); + dictionaryStack.push(systemDict); + dictionaryStack.push(globalDict); + dictionaryStack.push(userDict); - case "pop": - operandStack.pop(); - break; + /* + * The execution stack holds executable objects (mainly procedures and files) + * that are in intermediate stages of execution. At any point in the + * execution of a PostScript program, this stack represents the program’s + * call stack. Whenever the interpreter suspends execution of an object to + * execute some other object, it pushes the new object on the execution + * stack. When the interpreter finishes executing an object, it pops that + * object off the execution stack and resumes executing the suspended object + * beneath it. + */ + var executionStack = new Stack(); - default: - operandStack.push(obj); - break; - } - } + /* + * Return the next token in the execution stack + */ + function nextInStack() { + var currentProcedure = executionStack.peek(); + if (currentProcedure) { + var command = currentProcedure.shift(); + if (!currentProcedure.length) + executionStack.pop(); + return command; } - } -}; + return lexer.getObj(); + }; + + /* + * Get the next token from the executionStack and process it. + * Actually the function does not process the third segment of a Type1 font + * and end on 'closefile'. + * + * The method thrown an error if it encounters an unknown token. + */ + function processNextToken() { + var obj = nextInStack(); + if (operandIsArray && !IsCmd(obj, "{") && !IsCmd(obj, "[") && + !IsCmd(obj, "]") && !IsCmd(obj, "}")) { + dump("Adding an object: " + obj +" to array " + operandIsArray); + var currentArray = operandStack.peek(); + for (var i = 1; i < operandIsArray; i++) + currentArray = currentArray[currentArray.length - 1]; -var fontCount = 0; -var Type1 = function(aFontName, aFontFile) { - // All Type1 font program should begin with the comment %! - if (aFontFile.getByte() != 0x25 || aFontFile.getByte() != 0x21) - error("Invalid file header"); + currentArray.push(obj); + } else if (IsBool(obj) || IsInt(obj) || IsNum(obj) || IsString(obj)) { + dump("Value: " + obj); + operandStack.push(obj); + } else if (IsName(obj)) { + dump("Name: " + obj.name); + operandStack.push(obj.name); + } else if (IsCmd(obj)) { + var command = obj.cmd; + dump(command); - if (!fontCount || true) { - fontCount++; - var start = Date.now(); + switch (command) { + case "[": + case "{": + dump("Start" + (command == "{" ? " Executable " : " ") + "Array"); + operandIsArray++; + var currentArray = operandStack; + for (var i = 1; i < operandIsArray; i++) + if (currentArray.peek) + currentArray = currentArray.peek(); + else + currentArray = currentArray[currentArray.length - 1]; + currentArray.push([]); + break; - var ASCIIStream = aFontFile.makeSubStream(0, aFontFile.dict.get("Length1"), aFontFile.dict); - var binaryStream = aFontFile.makeSubStream(aFontFile.dict.get("Length1"), aFontFile.dict.get("Length2"), aFontFile.dict); + case "]": + case "}": + var currentArray = operandStack.peek(); + for (var i = 1; i < operandIsArray; i++) + currentArray = currentArray[currentArray.length - 1]; + dump("End" + (command == "}" ? " Executable " : " ") + "Array: " + currentArray.join(" ")); + operandIsArray--; + break; - this.parser = new Type1Parser(ASCIIStream, binaryStream); - var fontName = this.parser.parse(); - var font = PSFonts.get(fontName); - this.data = this.convertToOTF(this.convertToCFF(font), font); - var end = Date.now(); - log("Time to parse font is:" + (end - start)); - } -}; + case "if": + var procedure = operandStack.pop(); + var bool = operandStack.pop(); + if (!IsBool(bool)) { + dump("if: " + bool); + // we need to execute things, let be dirty + executionStack.push(bool); + } else { + dump("if ( " + bool + " ) { " + procedure + " }"); + if (bool) + executionStack.push(procedure); + } + break; -Type1.prototype = { - getDefaultWidth: function(aCharstrings) { - var defaultWidth = 0; - var defaultUsedCount = 0; + case "ifelse": + var procedure1 = operandStack.pop(); + var procedure2 = operandStack.pop(); + var bool = !!operandStack.pop(); + dump("if ( " + bool + " ) { " + procedure2 + " } else { " + procedure1 + " }"); + executionStack.push(bool ? procedure2 : procedure1); + break; - var widths = {}; - for (var i = 0; i < aCharstrings.length; i++) { - var width = aCharstrings[i].charstring[1]; - var usedCount = (widths[width] || 0) + 1; + case "for": + var procedure = operandStack.pop(); + var limit = operandStack.pop(); + var increment = operandStack.pop(); + var initial = operandStack.pop(); + for (var i = 0; i < limit; i += increment) { + operandStack.push(i); + executionStack.push(procedure.slice()); + } + break; - if (usedCount > defaultUsedCount) { - defaultUsedCount = usedCount; - defaultWidth = width; - } + case "dup": + dump("duplicate: " + operandStack.peek()); + operandStack.push(operandStack.peek()); + break; - widths[width] = usedCount; - } - return parseInt(defaultWidth); - }, + case "mark": + operandStack.push("mark"); + break; - createCFFIndexHeader: function(aObjects, aIsByte) { - var data = []; + case "cleartomark": + var command = ""; + do { + command = operandStack.pop(); + } while (command != "mark"); + break; - // First 2 bytes contains the number of objects contained into this index - var count = aObjects.length; - if (count ==0) - return [0x00, 0x00, 0x00]; + case "put": + var data = operandStack.pop(); + var indexOrKey = operandStack.pop(); + var object = operandStack.pop(); + dump("put " + data + " in " + object + "[" + indexOrKey + "]"); + object.set ? object.set(indexOrKey, data) + : object[indexOrKey] = data; + break; - var bytes = this.integerToBytes(count, 2); - for (var i = 0; i < bytes.length; i++) - data.push(bytes[i]); + case "pop": + operandStack.pop(); + break; - // 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); + case "exch": + var operand1 = operandStack.pop(); + var operand2 = operandStack.pop(); + operandStack.push(operand1); + operandStack.push(operand2); + break; - // 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 = this.integerToBytes(relativeOffset, 4); - for (var j = 0; j < bytes.length; j++) - data.push(bytes[j]); + case "get": + var indexOrKey = operandStack.pop(); + var object = operandStack.pop(); + var data = object.get ? object.get(indexOrKey) : object[indexOrKey]; + dump("get " + object + "[" + indexOrKey + "]: " + data); + operandStack.push(data); + break; - if (aObjects[i]) - relativeOffset += aObjects[i].length; - } + case "currentdict": + var dict = dictionaryStack.peek(); + operandStack.push(dict); + break; - 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; - }, + case "systemdict": + operandStack.push(systemDict); + break; - integerToBytes: function(aValue, aBytesCount) { - var bytes = []; - for (var i = 0; i < aBytesCount; i++) - bytes[i] = 0x00; + case "readonly": + case "executeonly": + case "noaccess": + // Do nothing for the moment + break; - do { - bytes[--aBytesCount] = (aValue & 0xFF); - aValue = aValue >> 8; - } while (aBytesCount && aValue > 0); + case "currentfile": + operandStack.push("currentfile"); + break; - return bytes; - }, + case "array": + var size = operandStack.pop(); + var array = new Array(size); + operandStack.push(array); + break; - bytesToInteger: function(aBytesArray) { - var value = 0; - for (var i = 0; i < aBytesArray.length; i++) - value = (value << 8) + aBytesArray[i]; - return value; - }, + case "dict": + var size = operandStack.pop(); + var dict = new Dict(size); + operandStack.push(dict); + break; - encodeNumber: function(aValue) { - var x = 0; - // XXX we don't really care about Type2 optimization here... - if (aValue >= -32768 && aValue <= 32767) { - return [ - 28, - this.integerToBytes(aValue >> 8, 1), - this.integerToBytes(aValue, 1) - ]; - } else if (aValue >= (-2147483647-1) && aValue <= 2147483647) { - return [ - 0xFF, - this.integerToBytes(aValue >> 24, 1), - this.integerToBytes(aValue >> 16, 1), - this.integerToBytes(aValue >> 8, 1), - this.integerToBytes(aValue, 1) - ]; - } else { - error("Value: " + aValue + " is not allowed"); - } - }, + case "begin": + dictionaryStack.push(operandStack.pop()); + break; - getOrderedCharStrings: function(aFont) { - var dict = aFont.get("CharStrings") - var charstrings = []; - for (var glyph in dict.map) { - var unicode = GlyphsUnicode[glyph]; - if (!unicode) { - if (glyph != ".notdef") - warn(glyph + " does not have an entry in the glyphs unicode dictionary"); - continue; - } + case "end": + dictionaryStack.pop(); + break; - var b1 = parseInt("0x" + unicode[0] + unicode[1]); - var b2 = parseInt("0x" + unicode[2] + unicode[3]); - unicode = this.bytesToInteger([b1, b2]); + case "def": + var value = operandStack.pop(); + var key = operandStack.pop(); + dump("def: " + key + " = " + value); + dictionaryStack.peek().set(key, value); + break; - charstrings.push({ - glyph: glyph, - unicode: unicode, - charstring: dict.map[glyph].slice() - }); - } + case "definefont": + var font = operandStack.pop(); + var key = operandStack.pop(); + dump("definefont " + font + " with key: " + key); - charstrings.sort(function(a, b) { - return a.unicode > b.unicode; - }); - return charstrings; - }, + // The key will be the identifier to recognize this font + fontName = key; + PSFonts.set(key, font); - convertToCFF: function(aFont) { - var debug = false; - function dump(aMsg) { - if (debug) - log(aMsg); - }; + operandStack.push(font); + break; - var charstrings = this.getOrderedCharStrings(aFont); - var defaultWidth = this.getDefaultWidth(charstrings); + case "known": + var name = operandStack.pop(); + var dict = operandStack.pop(); + var data = !!dict.get(name); + dump("known: " + data + " :: " + name + " in dict: " + dict); + operandStack.push(data); + break; - var charstringsCount = 0; - var charstringsDataLength = 0; - var glyphs = []; - var glyphsChecker = {}; - var subrs = aFont.get("Private").get("Subrs"); - var parser = new Type1Parser(); - for (var i = 0; i < charstrings.length; i++) { - var charstring = charstrings[i].charstring.slice(); - var glyph = charstrings[i].glyph; - if (glyphsChecker[glyph]) - error("glyphs already exists!"); - glyphsChecker[glyph] = true; + case "exec": + executionStack.push(operandStack.pop()); + break; - var flattened = parser.flattenCharstring(charstring, defaultWidth, subrs); - glyphs.push(flattened); - charstringsCount++; - charstringsDataLength += flattened.length; - } - dump("There is " + charstringsCount + " glyphs (size: " + charstringsDataLength + ")"); + case "eexec": + // All the first segment data has been read, decrypt the second segment + // and start interpreting it in order to decode it + var file = operandStack.pop(); + var eexecString = decrypt(aBinaryStream, kEexecEncryptionKey, 4).join(""); + dump(eexecString); + lexer = new Lexer(new StringStream(eexecString)); + break; - // Create a CFF font data - var cff = new Uint8Array(kMaxFontFileSize); - var currentOffset = 0; + case "LenIV": + error("LenIV: argh! we need to modify the length of discard characters for charStrings"); + break; - // Font header (major version, minor version, header size, offset size) - var header = [0x01, 0x00, 0x04, 0x04]; - currentOffset += header.length; - cff.set(header); + case "closefile": + var file = operandStack.pop(); + return true; + break; - // Names Index - var nameIndex = this.createCFFIndexHeader([aFont.get("FontName")]); - cff.set(nameIndex, currentOffset); - currentOffset += nameIndex.length; + case "index": + var operands = []; + var size = operandStack.pop(); + for (var i = 0; i < size; i++) + operands.push(operandStack.pop()); - // Calculate strings before writing the TopDICT index in order - // to calculate correct relative offsets for storing 'charset' - // and 'charstrings' data - var fontInfo = aFont.get("FontInfo"); - var version = fontInfo.get("version"); - var notice = fontInfo.get("Notice"); - var fullName = fontInfo.get("FullName"); - var familyName = fontInfo.get("FamilyName"); - var weight = fontInfo.get("Weight"); - var strings = [version, notice, fullName, - familyName, weight, "asteriskmath"]; - var stringsIndex = this.createCFFIndexHeader(strings); - var stringsDataLength = stringsIndex.length; + var newOperand = operandStack.peek(); - // Create the global subroutines index - var globalSubrsIndex = this.createCFFIndexHeader([]); + while (operands.length) + operandStack.push(operands.pop()); - // 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 = this.integerToBytes(index, 2); - charset.push(bytes[0]); - charset.push(bytes[1]); - } + operandStack.push(newOperand); + break; - // Convert charstrings - var getNumFor = { - "hstem": 1, - "vstem": 3, - "vmoveto": 4, - "rlineto": 5, - "hlineto": 6, - "vlineto": 7, - "rrcurveto": 8, - "endchar": 14, - "rmoveto": 21, - "hmoveto": 22, - "vhcurveto": 30, - "hvcurveto": 31, - }; + case "string": + var size = operandStack.pop(); + var str = (new Array(size + 1)).join(" "); + operandStack.push(str); + break; - // Encode the glyph and add it to the FUX - var r = [[0x40, 0x0E]]; - for (var i = 0; i < glyphs.length; i++) { - var data = glyphs[i].slice(); - var charstring = []; - for (var j = 0; j < data.length; j++) { - var c = data[j]; - if (!IsNum(c)) { - var token = getNumFor[c]; - if (!token) - error(c); - charstring.push(token); - } else { - try { - var bytes = this.encodeNumber(c); - } catch(e) { - log("Glyph " + i + " has a wrong value: " + c + " in charstring: " + data); - log("the default value is glyph " + charstrings[i].glyph + " and is supposed to be: " + charstrings[i].charstring); - } - for (var k = 0; k < bytes.length; k++) - charstring.push(bytes[k]); - } - } - r.push(charstring); - } + case "readstring": + var str = operandStack.pop(); + var size = str.length; - var charstringsIndex = this.createCFFIndexHeader(r, true); - charstringsIndex = charstringsIndex.join(" ").split(" "); // XXX why? + var file = operandStack.pop(); + // Add '1' because of the space separator, this is dirty + var stream = lexer.stream.makeSubStream(lexer.stream.pos + 1, size); + lexer.stream.skip(size + 1); - var fontBBox = aFont.get("FontBBox"); + var charString = decrypt(stream, kCharStringsEncryptionKey, 4).join(""); + var charStream = new StringStream(charString); + var decodedCharString = decodeCharString(charStream); + dump("decodedCharString: " + decodedCharString); + operandStack.push(decodedCharString); - //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 - ]; + // boolean indicating if the operation is a success or not + operandStack.push(true); + break; - for (var i = 0; i < fontBBox.length; i++) - topDictIndex = topDictIndex.concat(this.encodeNumber(fontBBox[i])); - topDictIndex.push(5) // FontBBox; + case "StandardEncoding": + // For some reason the value is considered as a command, maybe it is + // because of the uppercase 'S' + operandStack.push(obj.cmd); + break; - var charsetOffset = currentOffset + - (topDictIndex.length + (4 + 4 + 4 + 7)) + - stringsIndex.length + - globalSubrsIndex.length; - topDictIndex = topDictIndex.concat(this.encodeNumber(charsetOffset)); - topDictIndex.push(15); // charset + default: + var command = null; + if (IsCmd(obj)) { + for (var i = 0; i < dictionaryStack.count(); i++) { + if (command = dictionaryStack.get(i).get(obj.cmd)) { + dump("found in dictionnary for " + obj.cmd + " command: " + command); + executionStack.push(command.slice()); + break; + } + } + } - topDictIndex = topDictIndex.concat([28, 0, 0, 16]) // Encoding + if (!command) { + log("operandStack: " + operandStack); + log("dictionaryStack: " + dictionaryStack); + log(obj); + error("Unknow command while parsing font"); + } + break; + } + } else if (obj) { + dump("unknow: " + obj); + operandStack.push(obj); + } else { // The End! + operandStack.dump(); + return true; + } - var charstringsOffset = charsetOffset + (charstringsCount * 2) + 1; - topDictIndex = topDictIndex.concat(this.encodeNumber(charstringsOffset)); - topDictIndex.push(17); // charstrings + return false; + } - 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(" "); + /* + * Flatten the commands by interpreting the postscript code and replacing + * every 'callsubr', 'callothersubr' by the real commands. + * At the moment OtherSubrs are not fully supported and only otherSubrs 0-4 + * as descrived in 'Using Subroutines' of 'Adobe Type 1 Font Format', + * chapter 8. + */ + this.flattenCharstring = function(aCharstring, aDefaultWidth, aSubrs) { + operandStack.clear(); + executionStack.clear(); + executionStack.push(aCharstring); - // Top Dict Index - cff.set(topDictIndex, currentOffset); - currentOffset += topDictIndex.length; + var leftSidebearing = 0; + var lastPoint = 0; + while (true) { + var obj = nextInStack(); + if (IsBool(obj) || IsInt(obj) || IsNum(obj)) { + dump("Value: " + obj); + operandStack.push(obj); + } else if (IsString(obj)) { + dump("String: " + obj); + switch (obj) { + case "hsbw": + var charWidthVector = operandStack.pop(); + leftSidebearing = operandStack.pop(); - // Strings Index - cff.set(stringsIndex, currentOffset); - currentOffset += stringsIndex.length; + if (charWidthVector != aDefaultWidth) + operandStack.push(charWidthVector - aDefaultWidth); + break; - // Global Subrs Index - cff.set(globalSubrsIndex, currentOffset); - currentOffset += globalSubrsIndex.length; + case "rmoveto": + var dy = operandStack.pop(); + var dx = operandStack.pop(); - // Charset Index - cff.set(charset, currentOffset); - currentOffset += charset.length; + if (leftSidebearing) { + dx += leftSidebearing; + leftSidebearing = 0; + } - // Fill charstrings data - cff.set(charstringsIndex, currentOffset); - currentOffset += charstringsIndex.length; + operandStack.push(dx); + operandStack.push(dy); + operandStack.push("rmoveto"); + break; - // Private Data - var privateData = [ - 248, 136, 20, - 248, 136, 21, - 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 - ]; - cff.set(privateData, currentOffset); - currentOffset += privateData.length; + case "div": + var num2 = operandStack.pop(); + var num1 = operandStack.pop(); + operandStack.push(num2 / num1); + break; - // 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; + case "setcurrentpoint": + case "dotsection": + case "seac": + case "sbw": + error(obj + " parsing is not implemented (yet)"); + break; + case "closepath": + case "return": + break; - dump("==================== debug ===================="); - //var file = new Uint8Array(cff, 0, currentOffset); - //var parser = new Type2Parser(); - //parser.parse(new Stream(file)); + case "vstem3": + case "vstem": + operandStack.push("vstem"); + break; - var fontData = []; - for (var i = 0; i < currentOffset; i++) - fontData.push(cff[i]); + case "hstem": + case "hstem3": + operandStack.push("hstem"); + break; - //log("== write to file"); - //writeToFile(fontData, "/tmp/pdf.js." + fontCount + ".cff"); + case "callsubr": + var index = operandStack.pop(); + executionStack.push(aSubrs[index].slice()); + break; - return fontData; - }, + case "callothersubr": + // XXX need to be improved + var index = operandStack.pop(); + var count = operandStack.pop(); + var data = operandStack.pop(); + if (index != 3) + dump("callothersubr for index: " + index); + operandStack.push(3); + operandStack.push("callothersubr"); + break; - getMaxPower2: function(aNumber) { - var maxPower = 0; - var value = aNumber; - while (value >= 2) { - value /= 2; - maxPower++; + case "endchar": + operandStack.push("endchar"); + return operandStack.clone(); + + case "pop": + operandStack.pop(); + break; + + default: + operandStack.push(obj); + break; + } + } } + } +}; - value = 2; - for (var i = 1; i < maxPower; i++) - value *= 2; - return value; - }, - createOpenTypeHeader: function(aNumTables) { - // sfnt version (4 bytes) - var version = [0x4F, 0x54, 0x54, 0X4F]; +var fontCount = 0; +var CFF = function(aFontName, aFontFile) { + if (!fontCount || true) { + fontCount++; + var start = Date.now(); - // numTables (2 bytes) - var numTables = aNumTables; + var ASCIIStream = aFontFile.makeSubStream(0, aFontFile.dict.get("Length1"), aFontFile.dict); + var binaryStream = aFontFile.makeSubStream(aFontFile.dict.get("Length1"), aFontFile.dict.get("Length2"), aFontFile.dict); - // searchRange (2 bytes) - var searchRange = this.getMaxPower2(numTables) * 16; + this.parser = new Type1Parser(ASCIIStream, binaryStream); + var fontName = this.parser.parse(); + this.font = PSFonts.get(fontName); + this.data = this.convertToCFF(this.font); + var end = Date.now(); + log("Time to parse font is:" + (end - start)); + } +}; - // entrySelector (2 bytes) - var entrySelector = Math.log(this.getMaxPower2(numTables)) / Math.log(2); +CFF.prototype = { + getDefaultWidth: function(aCharstrings) { + var defaultWidth = 0; + var defaultUsedCount = 0; - // rangeShift (2 bytes) - var rangeShift = numTables * 16 - searchRange; + var widths = {}; + for (var i = 0; i < aCharstrings.length; i++) { + var width = aCharstrings[i].charstring[1]; + var usedCount = (widths[width] || 0) + 1; - return [].concat(version, - this.integerToBytes(numTables, 2), - this.integerToBytes(searchRange, 2), - this.integerToBytes(entrySelector, 2), - this.integerToBytes(rangeShift, 2)); + if (usedCount > defaultUsedCount) { + defaultUsedCount = usedCount; + defaultWidth = width; + } + + widths[width] = usedCount; + } + return parseInt(defaultWidth); }, - createTableEntry: function(aTag, aOffset, aData) { - // tag - var tag = [ - aTag.charCodeAt(0), - aTag.charCodeAt(1), - aTag.charCodeAt(2), - aTag.charCodeAt(3) - ]; + createCFFIndexHeader: function(aObjects, aIsByte) { + var data = []; - // offset - var offset = aOffset; + // First 2 bytes contains the number of objects contained into this index + var count = aObjects.length; + if (count ==0) + return [0x00, 0x00, 0x00]; - // length - // Per spec tables must be 4-bytes align so add some 0x00 if needed - while (aData.length & 3) - aData.push(0x00); + var bytes = FontsUtils.integerToBytes(count, 2); + for (var i = 0; i < bytes.length; i++) + data.push(bytes[i]); - var length = aData.length; + // 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); - // checksum - var checksum = this.bytesToInteger(tag) + offset + length; + // 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]); - return [].concat(tag, - this.integerToBytes(checksum, 4), - this.integerToBytes(offset, 4), - this.integerToBytes(length, 4)); + 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; }, - convertToOTF: function(aData, aFont) { - var otf = new Uint8Array(kMaxFontFileSize); - var currentOffset = 0; + encodeNumber: function(aValue) { + var x = 0; + // XXX we don't really care about Type2 optimization here... + if (aValue >= -32768 && aValue <= 32767) { + return [ + 28, + FontsUtils.integerToBytes(aValue >> 8, 1), + FontsUtils.integerToBytes(aValue, 1) + ]; + } else if (aValue >= (-2147483647-1) && aValue <= 2147483647) { + return [ + 0xFF, + FontsUtils.integerToBytes(aValue >> 24, 1), + FontsUtils.integerToBytes(aValue >> 16, 1), + FontsUtils.integerToBytes(aValue >> 8, 1), + FontsUtils.integerToBytes(aValue, 1) + ]; + } else { + error("Value: " + aValue + " is not allowed"); + } + }, - var numTables = 9; - var header = this.createOpenTypeHeader(numTables); - otf.set(header, currentOffset); - currentOffset += header.length; + getOrderedCharStrings: function(aFont) { + var dict = aFont.get("CharStrings") + var charstrings = []; + for (var glyph in dict.map) { + var unicode = GlyphsUnicode[glyph]; + if (!unicode) { + if (glyph != ".notdef") + warn(glyph + " does not have an entry in the glyphs unicode dictionary"); + continue; + } - var baseOffset = numTables * (4 * 4) + currentOffset; - var virtualOffset = baseOffset; - var tableEntry = this.createTableEntry("CFF ", virtualOffset, aData); - otf.set(tableEntry, currentOffset); - currentOffset += tableEntry.length; - virtualOffset += aData.length; + var b1 = parseInt("0x" + unicode[0] + unicode[1]); + var b2 = parseInt("0x" + unicode[2] + unicode[3]); + unicode = FontsUtils.bytesToInteger([b1, b2]); - var 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 - 0x00, 0x00, 0x00, 0x01, // ulUnicodeRange1 (Bits 0-31) - 0x00, 0x00, 0x00, 0x00, // ulUnicodeRange2 (Bits 32-63) - 0x00, 0x00, 0x00, 0x00, // ulUnicodeRange3 (Bits 64-95) - 0x00, 0x00, 0x00, 0x00, // ulUnicodeRange4 (Bits 96-127) - 0x47, 0x49, 0x60, 0x20, // 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 - ]; + charstrings.push({ + glyph: glyph, + unicode: unicode, + charstring: dict.map[glyph].slice() + }); + } - var tableEntry = this.createTableEntry("OS/2", virtualOffset, OS2); - otf.set(tableEntry, currentOffset); - currentOffset += tableEntry.length; - virtualOffset += OS2.length; + charstrings.sort(function(a, b) { + return a.unicode > b.unicode; + }); + return charstrings; + }, - /** CMAP */ - var charstrings = this.getOrderedCharStrings(aFont); + convertToCFF: function(aFont) { + var debug = false; + function dump(aMsg) { + if (debug) + log(aMsg); + }; - if (false) { - var cmap = [ - 0x00, 0x00, // version - 0x00, 0x01, // numTables - 0x00, 0x01, // platformID - 0x00, 0x00, // encodingID - 0x00, 0x00, 0x00, 0x0C, //offset - 0x00, 0x00, - 0x01, 0x06, - 0x00, 0x00 - ]; + var charstrings = this.getOrderedCharStrings(aFont); + var defaultWidth = this.getDefaultWidth(charstrings); - var data = []; - for (var i = 0; i < 262; i++) { - data.push(0x00); - } + var charstringsCount = 0; + var charstringsDataLength = 0; + var glyphs = []; + var glyphsChecker = {}; + var subrs = aFont.get("Private").get("Subrs"); + var parser = new Type1Parser(); + for (var i = 0; i < charstrings.length; i++) { + var charstring = charstrings[i].charstring.slice(); + var glyph = charstrings[i].glyph; + if (glyphsChecker[glyph]) + error("glyphs already exists!"); + glyphsChecker[glyph] = true; - for (var i = 0; i < charstrings.length; i++) - data[charstrings[i].unicode] = i + 1; - cmap = cmap.concat(data); + var flattened = parser.flattenCharstring(charstring, defaultWidth, subrs); + glyphs.push(flattened); + charstringsCount++; + charstringsDataLength += flattened.length; } - else { - var data = new Array(1000); - for (var i = 0; i < charstrings.length; i++) - data[charstrings[i].unicode] = i + 1; - - var ranges = []; - var range = []; - for (var i = 0; i < data.length; i++) { - var char = data[i]; - if (char) { - range.push(i); - } else if (range.length) { - if (0) { - log("create a new range of " + range.length + " chars width min: " + range[0] + " to max: " + range[range.length - 1]); - log("range content is: " + range); - } - ranges.push(range.slice()); - range = []; - } - } - + dump("There is " + charstringsCount + " glyphs (size: " + charstringsDataLength + ")"); - var cmap = []; - var segCount = ranges.length + 1; + // Create a CFF font data + var cff = new Uint8Array(kMaxFontFileSize); + var currentOffset = 0; - var segCount2 = segCount * 2; - var searchRange = this.getMaxPower2(segCount) * 2; - var searchEntry = Math.log(segCount) / Math.log(2); - var rangeShift = 2 * segCount - searchRange; - cmap = cmap.concat(this.integerToBytes(segCount2, 2)); - cmap = cmap.concat(this.integerToBytes(searchRange, 2)); - cmap = cmap.concat(this.integerToBytes(searchEntry, 2)); - cmap = cmap.concat(this.integerToBytes(rangeShift, 2)); + // Font header (major version, minor version, header size, offset size) + var header = [0x01, 0x00, 0x04, 0x04]; + currentOffset += header.length; + cff.set(header); - // End characters code with an additional 0xFFFF to finish the array - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - cmap = cmap.concat(this.integerToBytes(range[range.length - 1], 2)); - }; - cmap = cmap.concat([0xFF, 0xFF]); + // Names Index + var nameIndex = this.createCFFIndexHeader([aFont.get("FontName")]); + cff.set(nameIndex, currentOffset); + currentOffset += nameIndex.length; - // reserved pad - cmap = cmap.concat([0x00, 0x00]); + // Calculate strings before writing the TopDICT index in order + // to calculate correct relative offsets for storing 'charset' + // and 'charstrings' data + var fontInfo = aFont.get("FontInfo"); + var version = fontInfo.get("version"); + var notice = fontInfo.get("Notice"); + var fullName = fontInfo.get("FullName"); + var familyName = fontInfo.get("FamilyName"); + var weight = fontInfo.get("Weight"); + var strings = [version, notice, fullName, + familyName, weight, "asteriskmath"]; + var stringsIndex = this.createCFFIndexHeader(strings); + var stringsDataLength = stringsIndex.length; - // Start characters code with an additional 0xFFFF to finish the array - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - cmap = cmap.concat(this.integerToBytes(range[0], 2)); - }; - cmap = cmap.concat([0xFF, 0xFF]); - - // Fill idDelta - var delta = 0; - var p = 0; - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - var start = range[0]; - var delta = ((start - 1) - p) % 65536; - - var value = this.integerToBytes(delta, 2); - value[0] ^= 0xFF; - value[1] ^= 0xFF; - value[1] += 1; - cmap = cmap.concat([value[0], value[1]]); - - p += range.length; - }; - cmap = cmap.concat([0x00, 0x01]); - - - // Fill id Offsets with 0x00 - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - cmap = cmap.concat([0x00, 0x00]); - }; - cmap = cmap.concat([0x00, 0x00]); + // Create the global subroutines index + var globalSubrsIndex = this.createCFFIndexHeader([]); - var cmapHeader = [ - 0x00, 0x00, // version - 0x00, 0x01, // numTables - 0x00, 0x03, // platformID - 0x00, 0x01, // encodingID - 0x00, 0x00, 0x00, 0x0C, // start of the table record - 0x00, 0x04 // format - ]; - cmapHeader = cmapHeader.concat(this.integerToBytes(cmap.length + 6, 2)); // length - cmapHeader = cmapHeader.concat(0x00, 0x00); // language - - // Fill up data! - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - for (var j = 0; j < range.length; j++) - cmap = cmap.concat(range[j]); - }; - cmap = cmapHeader.concat(cmap); + // 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 tableEntry = this.createTableEntry("cmap", virtualOffset, cmap); - otf.set(tableEntry, currentOffset); - currentOffset += tableEntry.length; - virtualOffset += cmap.length; - + // Convert charstrings + var getNumFor = { + "hstem": 1, + "vstem": 3, + "vmoveto": 4, + "rlineto": 5, + "hlineto": 6, + "vlineto": 7, + "rrcurveto": 8, + "endchar": 14, + "rmoveto": 21, + "hmoveto": 22, + "vhcurveto": 30, + "hvcurveto": 31, + }; - /** HEAD */ + // Encode the glyph and add it to the FUX + var r = [[0x40, 0x0E]]; + for (var i = 0; i < glyphs.length; i++) { + var data = glyphs[i].slice(); + var charstring = []; + for (var j = 0; j < data.length; j++) { + var c = data[j]; + if (!IsNum(c)) { + var token = getNumFor[c]; + if (!token) + error(c); + charstring.push(token); + } else { + try { + var bytes = this.encodeNumber(c); + } catch(e) { + log("Glyph " + i + " has a wrong value: " + c + " in charstring: " + data); + log("the default value is glyph " + charstrings[i].glyph + " and is supposed to be: " + charstrings[i].charstring); + } + for (var k = 0; k < bytes.length; k++) + charstring.push(bytes[k]); + } + } + r.push(charstring); + } - var 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 (>= 16 && <=16384) - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // created - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // modified - 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 - ]; - var tableEntry = this.createTableEntry("head", virtualOffset, head); - otf.set(tableEntry, currentOffset); - currentOffset += tableEntry.length; - virtualOffset += head.length; + var charstringsIndex = this.createCFFIndexHeader(r, true); + charstringsIndex = charstringsIndex.join(" ").split(" "); // XXX why? - /** HHEA */ + var fontBBox = aFont.get("FontBBox"); - var hhea = [ - 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 + //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 ]; - hhea = hhea.concat(this.integerToBytes(charstrings.length, 2)); // numberOfHMetrics - var tableEntry = this.createTableEntry("hhea", virtualOffset, hhea); - otf.set(tableEntry, currentOffset); - currentOffset += tableEntry.length; - virtualOffset += hhea.length; + for (var i = 0; i < fontBBox.length; i++) + topDictIndex = topDictIndex.concat(this.encodeNumber(fontBBox[i])); + topDictIndex.push(5) // FontBBox; - /** HMTX */ + var charsetOffset = currentOffset + + (topDictIndex.length + (4 + 4 + 4 + 7)) + + stringsIndex.length + + globalSubrsIndex.length; + topDictIndex = topDictIndex.concat(this.encodeNumber(charsetOffset)); + topDictIndex.push(15); // charset - var hmtx = [0x01, 0xF4, 0x00, 0x00]; - for (var i = 0; i < charstrings.length; i++) { - var charstring = charstrings[i].charstring; - var width = this.integerToBytes(charstring[1], 2); - var lsb = this.integerToBytes(charstring[0], 2); - hmtx = hmtx.concat(width, lsb); - } + topDictIndex = topDictIndex.concat([28, 0, 0, 16]) // Encoding - var tableEntry = this.createTableEntry("hmtx", virtualOffset, hmtx); - otf.set(tableEntry, currentOffset); - currentOffset += tableEntry.length; - virtualOffset += hmtx.length; + 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(" "); - /** MAXP */ + // Top Dict Index + cff.set(topDictIndex, currentOffset); + currentOffset += topDictIndex.length; - var maxp = [ - 0x00, 0x00, 0x50, 0x00, // Version number - ].concat(this.integerToBytes(charstrings.length + 1, 2)); // Num of glyphs (+1 to pass the sanitizer...) + // Strings Index + cff.set(stringsIndex, currentOffset); + currentOffset += stringsIndex.length; - var tableEntry = this.createTableEntry("maxp", virtualOffset, maxp); - otf.set(tableEntry, currentOffset); - currentOffset += tableEntry.length; - virtualOffset += maxp.length; + // Global Subrs Index + cff.set(globalSubrsIndex, currentOffset); + currentOffset += globalSubrsIndex.length; + // Charset Index + cff.set(charset, currentOffset); + currentOffset += charset.length; - /** NAME */ + // Fill charstrings data + cff.set(charstringsIndex, currentOffset); + currentOffset += charstringsIndex.length; - var name = [ - 0x00, 0x00, // format - 0x00, 0x00, // Number of names Record - 0x00, 0x00 // Storage + // Private Data + var privateData = [ + 248, 136, 20, + 248, 136, 21, + 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 ]; - var tableEntry = this.createTableEntry("name", virtualOffset, name); - otf.set(tableEntry, currentOffset); - currentOffset += tableEntry.length; - virtualOffset += name.length; - - - /** POST */ + cff.set(privateData, currentOffset); + currentOffset += privateData.length; - // XXX get those info from the Font dict! - var post = [ - 0x00, 0x03, 0x00, 0x00, // Version number - 0x00, 0x00, 0x01, 0x00, // italicAngle - 0x00, 0x00, // underlinePosition - 0x00, 0x00, // underlineThickness - 0x00, 0x00, 0x00, 0x00, // isFixedPitch - 0x00, 0x00, 0x00, 0x00, // minMemType42 - 0x00, 0x00, 0x00, 0x00, // maxMemType42 - 0x00, 0x00, 0x00, 0x00, // minMemType1 - 0x00, 0x00, 0x00, 0x00 // maxMemType1 + // 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 ]; - var tableEntry = this.createTableEntry("post", virtualOffset, post); - otf.set(tableEntry, currentOffset); - currentOffset += tableEntry.length; - virtualOffset += post.length; + cff.set(shit, currentOffset); + currentOffset += shit.length; - // Set the CFF data - otf.set(aData, currentOffset); - currentOffset += aData.length; - var tables = [OS2, cmap, head, hhea, hmtx, maxp, name, post]; - for (var i = 0; i < tables.length; i++) { - var table = tables[i]; - otf.set(table, currentOffset); - currentOffset += table.length; - } + dump("==================== debug ===================="); + //var file = new Uint8Array(cff, 0, currentOffset); + //var parser = new Type2Parser(); + //parser.parse(new Stream(file)); var fontData = []; for (var i = 0; i < currentOffset; i++) - fontData.push(otf[i]); + fontData.push(cff[i]); + + //log("== write to file"); + //writeToFile(fontData, "/tmp/pdf.js." + fontCount + ".cff"); - //writeToFile(fontData, "/tmp/pdf.js." + fontCount + ".otf"); return fontData; } };