diff --git a/fonts.js b/fonts.js index b8a490369..ad3d4fd35 100644 --- a/fonts.js +++ b/fonts.js @@ -18,6 +18,13 @@ var kMaxWaitForFontFace = 1000; * many fonts are loaded. */ var fontCount = 0; +var fontName = ""; + +/** + * If for some reason one want to debug without fonts activated, it just need + * to turn this pref to true/false. + */ +var kDisableFonts = false; /** * Hold a map of decoded fonts and of the standard fourteen Type1 fonts and @@ -32,42 +39,44 @@ var Fonts = { return this._active; }, - set active(aName) { - this._active = this[aName]; + set active(name) { + this._active = this[name]; }, - chars2Unicode: function(chars) { + charsToUnicode: function fonts_chars2Unicode(chars) { var active = this._active; if (!active) return chars; // if we translated this string before, just grab it from the cache - var ret = active.cache[chars]; - if (ret) - return ret; + var str = active.cache[chars]; + if (str) + return str; // translate the string using the font's encoding var encoding = active.properties.encoding; if (!encoding) return chars; - var ret = ""; + str = ""; for (var i = 0; i < chars.length; ++i) { - var ch = chars.charCodeAt(i); - var uc = encoding[ch]; - if (uc instanceof Name) // we didn't convert the glyph yet - uc = encoding[ch] = GlyphsUnicode[uc.name]; - if (uc > 0xffff) { // handle surrogate pairs - ret += String.fromCharCode(uc & 0xffff); - uc >>= 16; + var charcode = chars.charCodeAt(i); + var unicode = encoding[charcode]; + + // Check if the glyph has already been converted + if (unicode instanceof Name) + unicode = encoding[unicode] = GlyphsUnicode[unicode.name]; + + // Handle surrogate pairs + if (unicode > 0xFFFF) { + str += String.fromCharCode(unicode & 0xFFFF); + unicode >>= 16; } - ret += String.fromCharCode(uc); + str += String.fromCharCode(unicode); } - // enter the translated string into the cache - active.cache[chars] = ret; - - return ret; + // Enter the translated string into the cache + return active.cache[chars] = str; } }; @@ -80,51 +89,55 @@ var Fonts = { * type1Font.bind(); */ var Font = (function () { - var constructor = function(aName, aFile, aProperties) { - this.name = aName; + var constructor = function font_constructor(name, file, properties) { + this.name = name; + this.encoding = properties.encoding; // If the font has already been decoded simply return it - if (Fonts[aName]) { - this.font = Fonts[aName].data; + if (Fonts[name]) { + this.font = Fonts[name].data; return; } fontCount++; + fontName = name; - switch (aProperties.type) { - case "Type1": - var cff = new CFF(aName, aFile, aProperties); - this.mimetype = "font/otf"; - - // Wrap the CFF data inside an OTF font file - this.font = this.convert(cff, aProperties); - break; - - case "TrueType": - // TrueType is disabled for the moment since the sanitizer prevent it - // from loading because of an overdated cmap table - return Fonts[aName] = { - data: null, - properties: { - encoding: {}, - charset: null - }, + // If the font is to be ignored, register it like an already loaded font + // to avoid the cost of waiting for it be be loaded by the platform. + if (properties.ignore || properties.type == "TrueType" || kDisableFonts) { + Fonts[name] = { + data: file, loading: false, + properties: {}, cache: Object.create(null) - }; - - this.mimetype = "font/ttf"; - var ttf = new TrueType(aFile); - this.font = ttf.data; - break; - - default: - warn("Font " + aProperties.type + " is not supported"); - break; + } + return; } - Fonts[aName] = { + switch (properties.type) { + case "Type1": + var cff = new CFF(name, file, properties); + this.mimetype = "font/opentype"; + + // Wrap the CFF data inside an OTF font file + this.font = this.convert(name, cff, properties); + break; + + case "TrueType": + this.mimetype = "font/opentype"; + + // Repair the TrueType file if it is can be damaged in the point of + // view of the sanitizer + this.font = this.checkAndRepair(name, file, properties); + break; + + default: + warn("Font " + properties.type + " is not supported"); + break; + } + + Fonts[name] = { data: this.font, - properties: aProperties, + properties: properties, loading: true, cache: Object.create(null) } @@ -133,6 +146,200 @@ var Font = (function () { this.bind(); }; + function stringToArray(str) { + var array = []; + for (var i = 0; i < str.length; ++i) + array[i] = str.charCodeAt(i); + + return array; + }; + + function string16(value) { + return String.fromCharCode((value >> 8) & 0xff) + + String.fromCharCode(value & 0xff); + }; + + function string32(value) { + return String.fromCharCode((value >> 24) & 0xff) + + String.fromCharCode((value >> 16) & 0xff) + + String.fromCharCode((value >> 8) & 0xff) + + String.fromCharCode(value & 0xff); + }; + + function createOpenTypeHeader(sfnt, file, offsets, numTables) { + // sfnt version (4 bytes) + var header = sfnt; + + // numTables (2 bytes) + header += string16(numTables); + + // searchRange (2 bytes) + var tablesMaxPower2 = FontsUtils.getMaxPower2(numTables); + var searchRange = tablesMaxPower2 * 16; + header += string16(searchRange); + + // entrySelector (2 bytes) + header += string16(Math.log(tablesMaxPower2) / Math.log(2)); + + // rangeShift (2 bytes) + header += string16(numTables * 16 - searchRange); + + file.set(stringToArray(header), offsets.currentOffset); + offsets.currentOffset += header.length; + offsets.virtualOffset += header.length; + }; + + function createTableEntry(file, offsets, tag, data) { + // offset + var offset = offsets.virtualOffset; + + // Per spec tables must be 4-bytes align so add padding as needed + while (data.length & 3) + data.push(0x00); + + while (offsets.virtualOffset & 3) + offsets.virtualOffset++; + + // length + var length = data.length; + + // checksum + var checksum = tag.charCodeAt(0) + + tag.charCodeAt(1) + + tag.charCodeAt(2) + + tag.charCodeAt(3) + + offset + + length; + + var tableEntry = tag + string32(checksum) + string32(offset) + string32(length); + tableEntry = stringToArray(tableEntry); + file.set(tableEntry, offsets.currentOffset); + + offsets.currentOffset += tableEntry.length; + offsets.virtualOffset += data.length; + }; + + function getRanges(glyphs) { + // Array.sort() sorts by characters, not numerically, so convert to an + // array of characters. + var codes = []; + var length = glyphs.length; + for (var n = 0; n < length; ++n) + codes.push(String.fromCharCode(glyphs[n].unicode)) + codes.sort(); + + // Split the sorted codes into ranges. + var ranges = []; + for (var n = 0; n < length; ) { + var start = codes[n++].charCodeAt(0); + var end = start; + while (n < length && end + 1 == codes[n].charCodeAt(0)) { + ++end; + ++n; + } + ranges.push([start, end]); + } + return ranges; + }; + + function createCMAPTable(glyphs) { + var ranges = getRanges(glyphs); + + var headerSize = (12 * 2 + (ranges.length * 4 * 2)); + var segCount = ranges.length + 1; + var segCount2 = segCount * 2; + var searchRange = FontsUtils.getMaxPower2(segCount) * 2; + var searchEntry = Math.log(segCount) / Math.log(2); + var rangeShift = 2 * segCount - searchRange; + + var cmap = "\x00\x00" + // version + "\x00\x01" + // numTables + "\x00\x03" + // platformID + "\x00\x01" + // encodingID + "\x00\x00\x00\x0C" + // start of the table record + "\x00\x04" + // format + string16(headerSize) + // length + "\x00\x00" + // languages + string16(segCount2) + + string16(searchRange) + + string16(searchEntry) + + string16(rangeShift); + + // Fill up the 4 parallel arrays describing the segments. + var startCount = ""; + var endCount = ""; + var idDeltas = ""; + var idRangeOffsets = ""; + var glyphsIds = ""; + var bias = 0; + for (var i = 0; i < segCount - 1; i++) { + var range = ranges[i]; + var start = range[0]; + var end = range[1]; + var delta = (((start - 1) - bias) ^ 0xffff) + 1; + bias += (end - start + 1); + + startCount += string16(start); + endCount += string16(end); + idDeltas += string16(delta); + idRangeOffsets += string16(0); + + for (var j = start; j <= end; j++) + glyphsIds += String.fromCharCode(j); + } + + startCount += "\xFF\xFF"; + endCount += "\xFF\xFF"; + idDeltas += "\x00\x01"; + idRangeOffsets += "\x00\x00"; + + return stringToArray(cmap + endCount + "\x00\x00" + startCount + + idDeltas + idRangeOffsets + glyphsIds); + }; + + function createOS2Table() { + var OS2 = stringToArray( + "\x00\x03" + // version + "\x02\x24" + // xAvgCharWidth + "\x01\xF4" + // usWeightClass + "\x00\x05" + // usWidthClass + "\x00\x00" + // fstype + "\x02\x8A" + // ySubscriptXSize + "\x02\xBB" + // ySubscriptYSize + "\x00\x00" + // ySubscriptXOffset + "\x00\x8C" + // ySubscriptYOffset + "\x02\x8A" + // ySuperScriptXSize + "\x02\xBB" + // ySuperScriptYSize + "\x00\x00" + // ySuperScriptXOffset + "\x01\xDF" + // ySuperScriptYOffset + "\x00\x31" + // yStrikeOutSize + "\x01\x02" + // yStrikeOutPosition + "\x00\x00" + // sFamilyClass + "\x02\x00\x06\x03\x00\x00\x00\x00\x00\x00" + // Panose + "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 0-31) + "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63) + "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95) + "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127) + "\x2A\x32\x31\x2A" + // achVendID + "\x00\x20" + // fsSelection + "\x00\x2D" + // usFirstCharIndex + "\x00\x7A" + // usLastCharIndex + "\x00\x03" + // sTypoAscender + "\x00\x20" + // sTypeDescender + "\x00\x38" + // sTypoLineGap + "\x00\x5A" + // usWinAscent + "\x02\xB4" + // usWinDescent + "\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31) + "\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63) + "\x00\x00" + // sxHeight + "\x00\x00" + // sCapHeight + "\x00\x01" + // usDefaultChar + "\x00\xCD" + // usBreakChar + "\x00\x02" // usMaxContext + ); + return OS2; + }; + /** * 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 @@ -145,255 +352,228 @@ var Font = (function () { name: null, font: null, mimetype: null, + encoding: null, - bind: function font_bind() { - var data = this.font; + checkAndRepair: function font_checkAndRepair(name, font, properties) { + function readTableEntry(file) { + // tag + var tag = file.getBytes(4); + tag = String.fromCharCode(tag[0]) + + String.fromCharCode(tag[1]) + + String.fromCharCode(tag[2]) + + String.fromCharCode(tag[3]); - // Get the base64 encoding of the binary font data - var str = ""; - var length = data.length; - for (var i = 0; i < length; ++i) - str += String.fromCharCode(data[i]); + var checksum = FontsUtils.bytesToInteger(file.getBytes(4)); + var offset = FontsUtils.bytesToInteger(file.getBytes(4)); + var length = FontsUtils.bytesToInteger(file.getBytes(4)); - var dataBase64 = window.btoa(str); - var fontName = this.name; + // Read the table associated data + var currentPosition = file.pos; + file.pos = file.start + offset; - /** Hack begin */ + var data = file.getBytes(length); + file.pos = currentPosition; - // 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 = GlyphsUnicode[charset[i]]; - if (!unicode) - error("Unicode for " + charset[i] + " is has not been found in the glyphs list"); - testString += String.fromCharCode(unicode); + return { + tag: tag, + checksum: checksum, + length: offset, + offset: length, + data: data } + }; + + function readOpenTypeHeader(ttf) { + return { + version: ttf.getBytes(4), + numTables: FontsUtils.bytesToInteger(ttf.getBytes(2)), + searchRange: FontsUtils.bytesToInteger(ttf.getBytes(2)), + entrySelector: FontsUtils.bytesToInteger(ttf.getBytes(2)), + rangeShift: FontsUtils.bytesToInteger(ttf.getBytes(2)) + } + }; + + // Check that required tables are present + var requiredTables = [ "OS/2", "cmap", "head", "hhea", + "hmtx", "maxp", "name", "post" ]; + + var header = readOpenTypeHeader(font); + var numTables = header.numTables; + + // This keep a reference to the CMap and the post tables since they can + // be rewritted + var cmap, post; + + var tables = []; + for (var i = 0; i < numTables; i++) { + var table = readTableEntry(font); + var index = requiredTables.indexOf(table.tag); + if (index != -1) { + if (table.tag == "cmap") + cmap = table; + else if (table.tag == "post") + post = table; + + requiredTables.splice(index, 1); + } + tables.push(table); } - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; - var textWidth = ctx.measureText(testString).width; - if (debug) - ctx.fillText(testString, 20, 20); + // 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") { + // Create a new file to hold the new version of our truetype with a new + // header and new offsets + var ttf = Uint8Array(kMaxFontFileSize); - var start = Date.now(); - var interval = window.setInterval(function canvasInterval(self) { - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; + // 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 put the actual data of a particular + // table + var numTables = header.numTables + requiredTables.length; + var offsets = { + currentOffset: 0, + virtualOffset: numTables * (4 * 4) + }; - // For some reasons the font has not loaded, so mark it loaded for the - // page to proceed but cry - if ((Date.now() - start) >= kMaxWaitForFontFace) { - window.clearInterval(interval); - Fonts[fontName].loading = false; - warn("Is " + fontName + " for charset: " + charset + " loaded?"); - } else if (textWidth != ctx.measureText(testString).width) { - window.clearInterval(interval); - Fonts[fontName].loading = false; - } + // The new numbers of tables will be the last one plus the num of missing + // tables + createOpenTypeHeader("\x00\x01\x00\x00", ttf, offsets, numTables); - if (debug) - ctx.fillText(testString, 20, 50); - }, 50, this); + // Insert the missing table + var OS2 = createOS2Table(); + tables.push({ + tag: "OS/2", + data: OS2 + }); - /** Hack end */ + // If the font is missing a OS/2 table it's could be an old mac font + // without a 3-1-4 Unicode BMP table, so let's rewrite it. + var charset = properties.charset; + var glyphs = []; + for (var i = 0; i < charset.length; i++) { + glyphs.push({ + unicode: GlyphsUnicode[charset[i]] + }); + } - // 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); + // Replace the old CMAP table with a shiny new one + cmap.data = createCMAPTable(glyphs); + + // Rewrite the 'post' table if needed + if (!post) { + post = + "\x00\x03\x00\x00" + // Version number + "\x00\x00\x01\x00" + // italicAngle + "\x00\x00" + // underlinePosition + "\x00\x00" + // underlineThickness + "\x00\x00\x00\x00" + // isFixedPitch + "\x00\x00\x00\x00" + // minMemType42 + "\x00\x00\x00\x00" + // maxMemType42 + "\x00\x00\x00\x00" + // minMemType1 + "\x00\x00\x00\x00"; // maxMemType1 + + tables.unshift({ + tag: "post", + data: stringToArray(post) + }); + } + + // Tables needs to be written by ascendant alphabetic order + tables.sort(function tables_sort(a, b) { + return a.tag > b.tag; + }); + + // rewrite the tables but tweak offsets + for (var i = 0; i < tables.length; i++) { + var table = tables[i]; + var data = []; + + var tableData = table.data; + for (var j = 0; j < tableData.length; j++) + data.push(tableData[j]); + 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; + + // 4-byte aligned data + while (offsets.currentOffset & 3) + offsets.currentOffset++; + } + + var fontData = []; + for (var i = 0; i < offsets.currentOffset; i++) + fontData.push(ttf[i]); + + return fontData; + } else if (requiredTables.length) { + error("Table " + requiredTables[0] + " is missing from the TrueType font"); + } + + return font.getBytes(); }, - convert: function font_convert(aFont, aProperties) { - var otf = new Uint8Array(kMaxFontFileSize); + convert: function font_convert(name, font, properties) { + var otf = Uint8Array(kMaxFontFileSize); - function s2a(s) { - var a = []; - for (var i = 0; i < s.length; ++i) - a[i] = s.charCodeAt(i); - return a; - } + function createNameTable(name) { + var names = [ + "See original licence", // Copyright + name, // Font family + "undefined", // Font subfamily (font weight) + "uniqueID", // Unique ID + name, // Full font name + "0.1", // Version + "undefined", // Postscript name + "undefined", // Trademark + "undefined", // Manufacturer + "undefined" // Designer + ]; - function s16(value) { - return String.fromCharCode((value >> 8) & 0xff) + String.fromCharCode(value & 0xff); - } + var name = + "\x00\x00" + // format + "\x00\x0A" + // Number of names Record + "\x00\x7E"; // Storage - function s32(value) { - return String.fromCharCode((value >> 24) & 0xff) + String.fromCharCode((value >> 16) & 0xff) + - String.fromCharCode((value >> 8) & 0xff) + String.fromCharCode(value & 0xff); - } + // Build the name records field + var strOffset = 0; + for (var i = 0; i < names.length; i++) { + var str = names[i]; - function createOpenTypeHeader(aFile, aOffsets, numTables) { - var header = ""; + var nameRecord = + "\x00\x01" + // platform ID + "\x00\x00" + // encoding ID + "\x00\x00" + // language ID + "\x00\x00" + // name ID + string16(str.length) + + string16(strOffset); + name += nameRecord; - // sfnt version (4 bytes) - header += "\x4F\x54\x54\x4F"; - - // numTables (2 bytes) - header += s16(numTables); - - // searchRange (2 bytes) - var tablesMaxPower2 = FontsUtils.getMaxPower2(numTables); - var searchRange = tablesMaxPower2 * 16; - header += s16(searchRange); - - // entrySelector (2 bytes) - header += s16(Math.log(tablesMaxPower2) / Math.log(2)); - - // rangeShift (2 bytes) - header += s16(numTables * 16 - searchRange); - - aFile.set(s2a(header), aOffsets.currentOffset); - aOffsets.currentOffset += header.length; - aOffsets.virtualOffset += header.length; - } - - function createTableEntry(aFile, aOffsets, aTag, aData) { - // offset - var offset = aOffsets.virtualOffset; - - // Per spec tables must be 4-bytes align so add padding as needed - while (aData.length & 3) - aData.push(0x00); - - // length - var length = aData.length; - - // checksum - var checksum = aTag.charCodeAt(0) + - aTag.charCodeAt(1) + - aTag.charCodeAt(2) + - aTag.charCodeAt(3) + - offset + - length; - - var tableEntry = aTag + s32(checksum) + s32(offset) + s32(length); - tableEntry = s2a(tableEntry); - aFile.set(tableEntry, aOffsets.currentOffset); - aOffsets.currentOffset += tableEntry.length; - aOffsets.virtualOffset += aData.length; - } - - function getRanges(glyphs) { - // Array.sort() sorts by characters, not numerically, so convert to an - // array of characters. - var codes = []; - var length = glyphs.length; - for (var n = 0; n < length; ++n) - codes.push(String.fromCharCode(glyphs[n].unicode)) - codes.sort(); - - // Split the sorted codes into ranges. - var ranges = []; - for (var n = 0; n < length; ) { - var start = codes[n++].charCodeAt(0); - var end = start; - while (n < length && end + 1 == codes[n].charCodeAt(0)) { - ++end; - ++n; - } - ranges.push([start, end]); - } - return ranges; - } - - function createCMAPTable(aGlyphs) { - var ranges = getRanges(aGlyphs); - - var headerSize = (12 * 2 + (ranges.length * 4 * 2)); - var segCount = ranges.length + 1; - var segCount2 = segCount * 2; - var searchRange = FontsUtils.getMaxPower2(segCount) * 2; - var searchEntry = Math.log(segCount) / Math.log(2); - var rangeShift = 2 * segCount - searchRange; - - var cmap = "\x00\x00" + // version - "\x00\x01" + // numTables - "\x00\x03" + // platformID - "\x00\x01" + // encodingID - "\x00\x00\x00\x0C" + // start of the table record - "\x00\x04" + // format - s16(headerSize) + // length - "\x00\x00" + // languages - s16(segCount2) + - s16(searchRange) + - s16(searchEntry) + - s16(rangeShift); - - // Fill up the 4 parallel arrays describing the segments. - var startCount = ""; - var endCount = ""; - var idDeltas = ""; - var idRangeOffsets = ""; - var glyphsIds = ""; - var bias = 0; - for (var i = 0; i < segCount - 1; i++) { - var range = ranges[i]; - var start = range[0]; - var end = range[1]; - var delta = (((start - 1) - bias) ^ 0xffff) + 1; - bias += (end - start + 1); - - startCount += s16(start); - endCount += s16(end); - idDeltas += s16(delta); - idRangeOffsets += s16(0); - - for (var j = start; j <= end; j++) - glyphsIds += String.fromCharCode(j); + strOffset += str.length; } - startCount += "\xFF\xFF"; - endCount += "\xFF\xFF"; - idDeltas += "\x00\x01"; - idRangeOffsets += "\x00\x00"; - - return s2a(cmap + endCount + "\x00\x00" + startCount + - idDeltas + idRangeOffsets + glyphsIds); + name += names.join(""); + return name; } // 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 CFF = + font.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 @@ -405,66 +585,25 @@ var Font = (function () { virtualOffset: tables.length * (4 * 4) }; - // For files with only one font the offset table is the first thing of the - // file - createOpenTypeHeader(otf, offsets, tables.length); + // It there is only one font, offset table is the first bytes of the file + createOpenTypeHeader("\x4F\x54\x54\x4F", otf, offsets, tables.length); - // TODO: It is probable that in a future we want to get rid of this glue - // between the CFF and the OTF format in order to be able to embed TrueType - // data. + /** CFF */ createTableEntry(otf, offsets, "CFF ", CFF); /** OS/2 */ - OS2 = s2a( - "\x00\x03" + // version - "\x02\x24" + // xAvgCharWidth - "\x01\xF4" + // usWeightClass - "\x00\x05" + // usWidthClass - "\x00\x00" + // fstype - "\x02\x8A" + // ySubscriptXSize - "\x02\xBB" + // ySubscriptYSize - "\x00\x00" + // ySubscriptXOffset - "\x00\x8C" + // ySubscriptYOffset - "\x02\x8A" + // ySuperScriptXSize - "\x02\xBB" + // ySuperScriptYSize - "\x00\x00" + // ySuperScriptXOffset - "\x01\xDF" + // ySuperScriptYOffset - "\x00\x31" + // yStrikeOutSize - "\x01\x02" + // yStrikeOutPosition - "\x00\x00" + // sFamilyClass - "\x02\x00\x06\x03\x00\x00\x00\x00\x00\x00" + // Panose - "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 0-31) - "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63) - "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95) - "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127) - "\x2A\x32\x31\x2A" + // achVendID - "\x00\x20" + // fsSelection - "\x00\x2D" + // usFirstCharIndex - "\x00\x7A" + // usLastCharIndex - "\x00\x03" + // sTypoAscender - "\x00\x20" + // sTypeDescender - "\x00\x38" + // sTypoLineGap - "\x00\x5A" + // usWinAscent - "\x02\xB4" + // usWinDescent - "\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31) - "\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63) - "\x00\x00" + // sxHeight - "\x00\x00" + // sCapHeight - "\x00\x01" + // usDefaultChar - "\x00\xCD" + // usBreakChar - "\x00\x02" // usMaxContext - ); + OS2 = createOS2Table(); createTableEntry(otf, offsets, "OS/2", OS2); //XXX Getting charstrings here seems wrong since this is another CFF glue - var charstrings = aFont.getOrderedCharStrings(aProperties.glyphs); + var charstrings = font.getOrderedCharStrings(properties.glyphs); /** CMAP */ cmap = createCMAPTable(charstrings); createTableEntry(otf, offsets, "cmap", cmap); /** HEAD */ - head = s2a( + head = stringToArray( "\x00\x01\x00\x00" + // Version number "\x00\x00\x50\x00" + // fontRevision "\x00\x00\x00\x00" + // checksumAdjustement @@ -486,7 +625,7 @@ var Font = (function () { createTableEntry(otf, offsets, "head", head); /** HHEA */ - hhea = s2a( + hhea = stringToArray( "\x00\x01\x00\x00" + // Version number "\x00\x00" + // Typographic Ascent "\x00\x00" + // Typographic Descent @@ -503,7 +642,7 @@ var Font = (function () { "\x00\x00" + // -reserved- "\x00\x00" + // -reserved- "\x00\x00" + // metricDataFormat - s16(charstrings.length) + string16(charstrings.length) ); createTableEntry(otf, offsets, "hhea", hhea); @@ -513,22 +652,19 @@ var Font = (function () { var charstring = charstrings[i].charstring; var width = charstring[1]; var lsb = charstring[0]; - hmtx += s16(width) + s16(lsb); + hmtx += string16(width) + string16(lsb); } - hmtx = s2a(hmtx); + hmtx = stringToArray(hmtx); createTableEntry(otf, offsets, "hmtx", hmtx); /** MAXP */ maxp = "\x00\x00\x50\x00" + // Version number - s16(charstrings.length + 1); // Num of glyphs (+1 to pass the sanitizer...) - maxp = s2a(maxp); + string16(charstrings.length + 1); // Num of glyphs (+1 to pass the sanitizer...) + maxp = stringToArray(maxp); createTableEntry(otf, offsets, "maxp", maxp); /** NAME */ - name = "\x00\x00" + // Format - "\x00\x00" + // Number of name records - "\x00\x00"; // Storage - name = s2a(name); + name = stringToArray(createNameTable(name)); createTableEntry(otf, offsets, "name", name); /** POST */ @@ -542,7 +678,7 @@ var Font = (function () { "\x00\x00\x00\x00" + // maxMemType42 "\x00\x00\x00\x00" + // minMemType1 "\x00\x00\x00\x00"; // maxMemType1 - post = s2a(post); + post = stringToArray(post); createTableEntry(otf, offsets, "post", post); // Once all the table entries header are written, dump the data! @@ -557,43 +693,135 @@ var Font = (function () { for (var i = 0; i < offsets.currentOffset; i++) fontData.push(otf[i]); return fontData; + }, + + bind: function font_bind() { + var data = this.font; + var fontName = this.name; + + /** Hack begin */ + + // Actually there is not event when a font has finished downloading so + // the following code are a dirty hack to 'guess' when a font is ready + 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); + + // Get the font size canvas think it will be for 'spaces' + var ctx = canvas.getContext("2d"); + ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; + var testString = " "; + + // When debugging use the characters provided by the charsets to visually + // see what's happening instead of 'spaces' + 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); + + // Retrieve font charset + var charset = Fonts[fontName].properties.charset || []; + + // if the charset is too small make it repeat a few times + var count = 30; + while (count-- && charset.length <= 30) + charset = charset.concat(charset.slice()); + + for (var i = 0; i < charset.length; i++) { + var unicode = GlyphsUnicode[charset[i]]; + if (!unicode) + continue; + testString += String.fromCharCode(unicode); + } + + ctx.fillText(testString, 20, 20); + } + + // Periodicaly check for the width of the testString, it will be + // different once the real font has loaded + var textWidth = ctx.measureText(testString).width; + + var interval = window.setInterval(function canvasInterval(self) { + this.start = this.start || Date.now(); + ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; + + // For some reasons the font has not loaded, so mark it loaded for the + // page to proceed but cry + if ((Date.now() - this.start) >= kMaxWaitForFontFace) { + window.clearInterval(interval); + Fonts[fontName].loading = false; + warn("Is " + fontName + " for charset: " + charset + " loaded?"); + this.start = 0; + } else if (textWidth != ctx.measureText(testString).width) { + window.clearInterval(interval); + Fonts[fontName].loading = false; + this.start = 0; + } + + if (debug) + ctx.fillText(testString, 20, 50); + }, 30, this); + + /** Hack end */ + + // Get the base64 encoding of the binary font data + var str = ""; + var length = data.length; + for (var i = 0; i < length; ++i) + str += String.fromCharCode(data[i]); + + var base64 = window.btoa(str); + + // Add the @font-face rule to the document + var url = "url(data:" + this.mimetype + ";base64," + base64 + ");"; + var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; + var styleSheet = document.styleSheets[0]; + styleSheet.insertRule(rule, styleSheet.length); } }; return constructor; })(); + /** * 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) { + integerToBytes: function fu_integerToBytes(value, bytesCount) { var bytes = this._bytesArray; - if (aBytesCount == 1) { - bytes.set([aValue]); + if (bytesCount == 1) { + bytes.set([value]); return bytes[0]; - } else if (aBytesCount == 2) { - bytes.set([aValue >> 8, aValue]); + } else if (bytesCount == 2) { + bytes.set([value >> 8, value]); return [bytes[0], bytes[1]]; - } else if (aBytesCount == 4) { - bytes.set([aValue >> 24, aValue >> 16, aValue >> 8, aValue]); + } else if (bytesCount == 4) { + bytes.set([value >> 24, value >> 16, value >> 8, value]); return [bytes[0], bytes[1], bytes[2], bytes[3]]; } }, - bytesToInteger: function fu_bytesToInteger(aBytesArray) { + bytesToInteger: function fu_bytesToInteger(bytesArray) { var value = 0; - for (var i = 0; i < aBytesArray.length; i++) - value = (value << 8) + aBytesArray[i]; + for (var i = 0; i < bytesArray.length; i++) + value = (value << 8) + bytesArray[i]; return value; }, - getMaxPower2: function fu_getMaxPower2(aNumber) { + getMaxPower2: function fu_getMaxPower2(number) { var maxPower = 0; - var value = aNumber; + var value = number; while (value >= 2) { value /= 2; maxPower++; @@ -607,253 +835,6 @@ var FontsUtils = { }; -/** - * 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; - - // 4-byte aligned data - while (offsets.currentOffset & 3) - offsets.currentOffset++; - } - - this.data = ttf; - return; - } else if (requiredTables.lenght) { - error("Table " + requiredTables[0] + " is missing from the TruType font"); - } else { - this.data = aFile.getBytes(); - } -}; - -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 - } - } -}; - - /** * Type1Parser encapsulate the needed code for parsing a Type1 font * program. @@ -868,18 +849,18 @@ var Type1Parser = function() { var kEexecEncryptionKey = 55665; var kCharStringsEncryptionKey = 4330; - function decrypt(aStream, aKey, aDiscardNumber) { - var r = aKey, c1 = 52845, c2 = 22719; + function decrypt(stream, key, discardNumber) { + var r = key, c1 = 52845, c2 = 22719; var decryptedString = []; var value = ""; - var count = aStream.length; + var count = stream.length; for (var i = 0; i < count; i++) { - value = aStream[i]; + value = stream[i]; decryptedString[i] = value ^ (r >> 8); r = ((value + r) * c1 + c2) & ((1 << 16) - 1); } - return decryptedString.slice(aDiscardNumber); + return decryptedString.slice(discardNumber); }; /* @@ -989,18 +970,18 @@ var Type1Parser = function() { "31": "hvcurveto" }; - function decodeCharString(aArray) { + function decodeCharString(array) { var charString = []; var value = ""; - var count = aArray.length; + var count = array.length; for (var i = 0; i < count; i++) { - value = parseInt(aArray[i]); + value = parseInt(array[i]); if (value < 32) { var command = null; if (value == 12) { - var escape = aArray[++i]; + var escape = array[++i]; command = charStringDictionary["12"][escape]; } else { command = charStringDictionary[value]; @@ -1020,14 +1001,14 @@ var Type1Parser = function() { } else if (value <= 246) { value = parseInt(value) - 139; } else if (value <= 250) { - value = ((value - 247) * 256) + parseInt(aArray[++i]) + 108; + value = ((value - 247) * 256) + parseInt(array[++i]) + 108; } else if (value <= 254) { - value = -((value - 251) * 256) - parseInt(aArray[++i]) - 108; + value = -((value - 251) * 256) - parseInt(array[++i]) - 108; } else { - var byte = aArray[++i]; + var byte = array[++i]; var high = (byte >> 1); - value = (byte - high) << 24 | aArray[++i] << 16 | - aArray[++i] << 8 | aArray[++i]; + value = (byte - high) << 24 | array[++i] << 16 | + array[++i] << 8 | array[++i]; } charString.push(value); @@ -1040,8 +1021,8 @@ var Type1Parser = function() { * Returns an object containing a Subrs array and a CharStrings array * extracted from and eexec encrypted block of data */ - this.extractFontProgram = function t1_extractFontProgram(aStream) { - var eexecString = decrypt(aStream, kEexecEncryptionKey, 4); + this.extractFontProgram = function t1_extractFontProgram(stream) { + var eexecString = decrypt(stream, kEexecEncryptionKey, 4); var subrs = [], glyphs = []; var inGlyphs = false; var inSubrs = false; @@ -1103,6 +1084,10 @@ var Type1Parser = function() { } }; +/** + * The CFF class takes a Type1 file and wrap it into a 'Compact Font Format', + * which itself embed Type2 charstrings. + */ const CFFStrings = [ ".notdef","space","exclam","quotedbl","numbersign","dollar","percent","ampersand", "quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period", @@ -1161,30 +1146,26 @@ const CFFStrings = [ "001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold" ]; -/** - * Take a Type1 file as input and wrap it into a Compact Font Format (CFF) - * wrapping Type2 charstrings. - */ -var CFF = function(aName, aFile, aProperties) { +var CFF = function(name, file, properties) { // Get the data block containing glyphs and subrs informations - var length1 = aFile.dict.get("Length1"); - var length2 = aFile.dict.get("Length2"); - aFile.skip(length1); - var eexecBlock = aFile.getBytes(length2); + var length1 = file.dict.get("Length1"); + var length2 = file.dict.get("Length2"); + file.skip(length1); + var eexecBlock = file.getBytes(length2); // Decrypt the data blocks and retrieve the informations from it var parser = new Type1Parser(); var fontInfo = parser.extractFontProgram(eexecBlock); - aProperties.subrs = fontInfo.subrs; - aProperties.glyphs = fontInfo.charstrings; - this.data = this.wrap(aName, aProperties); + properties.subrs = fontInfo.subrs; + properties.glyphs = fontInfo.charstrings; + this.data = this.wrap(name, properties); }; CFF.prototype = { - createCFFIndexHeader: function(aObjects, aIsByte) { + createCFFIndexHeader: function(objects, isByte) { // First 2 bytes contains the number of objects contained into this index - var count = aObjects.length; + var count = objects.length; // If there is no object, just create an array saying that with another // offset byte. @@ -1208,33 +1189,33 @@ CFF.prototype = { for (var j = 0; j < bytes.length; j++) data.push(bytes[j]); - if (aObjects[i]) - relativeOffset += aObjects[i].length; + if (objects[i]) + relativeOffset += objects[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)); + for (var j = 0; j < objects[i].length; j++) + data.push(isByte ? objects[i][j] : objects[i].charCodeAt(j)); } return data; }, - encodeNumber: function(aValue) { + encodeNumber: function(value) { var x = 0; - if (aValue >= -32768 && aValue <= 32767) { - return [ 28, aValue >> 8, aValue & 0xFF ]; - } else if (aValue >= (-2147483647-1) && aValue <= 2147483647) { - return [ 0xFF, aValue >> 24, Value >> 16, aValue >> 8, aValue & 0xFF ]; + if (value >= -32768 && value <= 32767) { + return [ 28, value >> 8, value & 0xFF ]; + } else if (value >= (-2147483647-1) && value <= 2147483647) { + return [ 0xFF, value >> 24, Value >> 16, value >> 8, value & 0xFF ]; } else { - error("Value: " + aValue + " is not allowed"); + error("Value: " + value + " is not allowed"); } }, - getOrderedCharStrings: function(aGlyphs) { + getOrderedCharStrings: function(glyphs) { var charstrings = []; - for (var i = 0; i < aGlyphs.length; i++) { - var glyph = aGlyphs[i].glyph; + for (var i = 0; i < glyphs.length; i++) { + var glyph = glyphs[i].glyph; var unicode = GlyphsUnicode[glyph]; if (!unicode) { if (glyph != ".notdef") @@ -1243,12 +1224,12 @@ CFF.prototype = { charstrings.push({ glyph: glyph, unicode: unicode, - charstring: aGlyphs[i].data.slice() + charstring: glyphs[i].data.slice() }); } }; - charstrings.sort(function(a, b) { + charstrings.sort(function charstrings_sort(a, b) { return a.unicode > b.unicode; }); return charstrings; @@ -1276,100 +1257,100 @@ CFF.prototype = { "hvcurveto": 31, }, - flattenCharstring: function(aGlyph, aCharstring, aSubrs) { + flattenCharstring: function flattenCharstring(glyph, charstring, subrs) { var i = 0; while (true) { - var obj = aCharstring[i]; + var obj = charstring[i]; if (obj == null) return []; if (obj.charAt) { switch (obj) { case "callsubr": - var subr = aSubrs[aCharstring[i - 1]].slice(); + var subr = subrs[charstring[i - 1]].slice(); if (subr.length > 1) { - subr = this.flattenCharstring(aGlyph, subr, aSubrs); + subr = this.flattenCharstring(glyph, subr, subrs); subr.pop(); - aCharstring.splice(i - 1, 2, subr); + charstring.splice(i - 1, 2, subr); } else { - aCharstring.splice(i - 1, 2); + charstring.splice(i - 1, 2); } i -= 1; break; case "callothersubr": - var index = aCharstring[i - 1]; - var count = aCharstring[i - 2]; - var data = aCharstring[i - 3]; + var index = charstring[i - 1]; + var count = charstring[i - 2]; + var data = charstring[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 + ")"); + error("callothersubr for index: " + index + " (" + charstring + ")"); if (!data) { - aCharstring.splice(i - 2, 4, "pop", 3); + charstring.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); + charstring.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); + var num2 = charstring[i - 1]; + var num1 = charstring[i - 2]; + charstring.splice(i - 2, 3, num1 / num2); i -= 2; break; case "pop": if (i) - aCharstring.splice(i - 2, 2); + charstring.splice(i - 2, 2); else - aCharstring.splice(i - 1, 1); + charstring.splice(i - 1, 1); i -= 1; break; case "hsbw": - var charWidthVector = aCharstring[i - 1]; - var leftSidebearing = aCharstring[i - 2]; + var charWidthVector = charstring[i - 1]; + var leftSidebearing = charstring[i - 2]; if (leftSidebearing) - aCharstring.splice(i - 2, 3, charWidthVector, leftSidebearing, "hmoveto"); + charstring.splice(i - 2, 3, charWidthVector, leftSidebearing, "hmoveto"); else - aCharstring.splice(i - 2, 3, charWidthVector); + charstring.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]; + for (var j = 0; j < charstring.length; j++) { + var command = charstring[j]; if (parseFloat(command) == command) { - aCharstring.splice(j, 1, 28, command >> 8, command); + charstring.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]); + charstring.splice(j - 1, 1, command[0], command[1]); j += 1; } else { - aCharstring[j] = command; + charstring[j] = command; } } else { - aCharstring.splice(j, 1); + charstring.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]); + charstring.splice(j + k, 0, command[k]); j+= command.length - 1; } } - return aCharstring; + return charstring; default: break; @@ -1377,11 +1358,11 @@ CFF.prototype = { } i++; } - error("failing with i = " + i + " in charstring:" + aCharstring + "(" + aCharstring.length + ")"); + error("failing with i = " + i + " in charstring:" + charstring + "(" + charstring.length + ")"); }, - wrap: function(aName, aProperties) { - var charstrings = this.getOrderedCharStrings(aProperties.glyphs); + wrap: function wrap(name, properties) { + var charstrings = this.getOrderedCharStrings(properties.glyphs); // Starts the conversion of the Type1 charstrings to Type2 var charstringsCount = 0; @@ -1391,7 +1372,7 @@ CFF.prototype = { var charstring = charstrings[i].charstring.slice(); var glyph = charstrings[i].glyph; - var flattened = this.flattenCharstring(glyph, charstring, aProperties.subrs); + var flattened = this.flattenCharstring(glyph, charstring, properties.subrs); glyphs.push(flattened); charstringsCount++; charstringsDataLength += flattened.length; @@ -1407,7 +1388,7 @@ CFF.prototype = { cff.set(header); // Names Index - var nameIndex = this.createCFFIndexHeader([aName]); + var nameIndex = this.createCFFIndexHeader([name]); cff.set(nameIndex, currentOffset); currentOffset += nameIndex.length; @@ -1451,7 +1432,7 @@ CFF.prototype = { 248, 31, 4 // Weight ]; - var fontBBox = aProperties.bbox; + var fontBBox = properties.bbox; for (var i = 0; i < fontBBox.length; i++) topDictIndex = topDictIndex.concat(this.encodeNumber(fontBBox[i])); topDictIndex.push(5) // FontBBox; diff --git a/images/combobox.png b/images/combobox.png deleted file mode 100644 index f1527d6a2..000000000 Binary files a/images/combobox.png and /dev/null differ diff --git a/images/source/ComboBox.psd.zip b/images/source/ComboBox.psd.zip deleted file mode 100644 index 232604c75..000000000 Binary files a/images/source/ComboBox.psd.zip and /dev/null differ diff --git a/multi-page-viewer.css b/multi-page-viewer.css index c96567465..f9a7837b1 100644 --- a/multi-page-viewer.css +++ b/multi-page-viewer.css @@ -27,7 +27,30 @@ span { .control > input { float: left; + border: 1px solid #4d4d4d; + height: 20px; + padding: 0px; margin: 0px 2px 0px 0px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); +} + +.control > select { + float: left; + border: 1px solid #4d4d4d; + height: 22px; + padding: 2px 0px 0px; + margin: 0px 0px 1px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); } .control > span { @@ -79,7 +102,7 @@ span { #previousPageButton { background: url('images/buttons.png') no-repeat 0px -23px; - cursor: pointer; + cursor: default; display: inline-block; float: left; margin: 0px; @@ -97,7 +120,7 @@ span { #nextPageButton { background: url('images/buttons.png') no-repeat -28px -23px; - cursor: pointer; + cursor: default; display: inline-block; float: left; margin: 0px; @@ -113,68 +136,7 @@ span { background: url('images/buttons.png') no-repeat -28px 0px; } -#scaleComboBoxInput { - background: url('images/combobox.png') no-repeat 0px -23px; - display: inline-block; - float: left; - margin: 0px; - width: 35px; - height: 23px; -} - -#scaleComboBoxInput input { - background: none; - border: 0px; - margin: 3px 2px 0px; - width: 31px; -} - -#scaleComboBoxButton { - background: url('images/combobox.png') no-repeat -41px -23px; - cursor: pointer; - display: inline-block; - float: left; - margin: 0px; - width: 21px; - height: 23px; -} - -#scaleComboBoxButton.down { - background: url('images/combobox.png') no-repeat -41px -46px; -} - -#scaleComboBoxButton.disabled { - background: url('images/combobox.png') no-repeat -41px 0px; -} - -#scaleComboBoxList { - background-color: #fff; - border: 1px solid #666; - clear: both; - position: relative; - display: none; - top: -20px; - width: 48px; -} - -#scaleComboBoxList > ul { - list-style: none; - padding: 0px; - margin: 0px; -} - -#scaleComboBoxList > ul > li { - display: inline-block; - cursor: pointer; - width: 100%; -} - -#scaleComboBoxList > ul > li:hover { - background-color: #09f; - color: #fff; -} - -#pageNumber, #scale { +#pageNumber { text-align: right; } diff --git a/multi-page-viewer.html b/multi-page-viewer.html index 692cfb1c4..4e15cf4f8 100644 --- a/multi-page-viewer.html +++ b/multi-page-viewer.html @@ -2,7 +2,8 @@