Merge pull request #76 from vingtetun/master

Minor changes
This commit is contained in:
Andreas Gal 2011-06-24 14:10:14 -07:00
commit b6c92f674b
4 changed files with 212 additions and 325 deletions

341
fonts.js
View File

@ -80,6 +80,36 @@ var Fonts = {
} }
}; };
var FontsLoader = {
bind: function(fonts) {
var worker = (typeof window == "undefined");
var ready = true;
for (var i = 0; i < fonts.length; i++) {
var font = fonts[i];
if (Fonts[font.name]) {
ready = ready && !Fonts[font.name].loading;
continue;
} else {
ready = false;
}
var obj = new Font(font.name, font.file, font.properties);
var str = "";
var data = Fonts[font.name].data;
var length = data.length;
for (var j = 0; j < length; j++)
str += String.fromCharCode(data[j]);
worker ? obj.bindWorker(str) : obj.bindDOM(str);
}
return ready;
}
};
/** /**
* 'Font' is the class the outside world should use, it encapsulate all the font * 'Font' is the class the outside world should use, it encapsulate all the font
* decoding logics whatever type it is (assuming the font type is supported). * decoding logics whatever type it is (assuming the font type is supported).
@ -113,13 +143,14 @@ var Font = (function () {
return; return;
} }
var data;
switch (properties.type) { switch (properties.type) {
case "Type1": case "Type1":
var cff = new CFF(name, file, properties); var cff = new CFF(name, file, properties);
this.mimetype = "font/opentype"; this.mimetype = "font/opentype";
// Wrap the CFF data inside an OTF font file // Wrap the CFF data inside an OTF font file
this.font = this.convert(name, cff, properties); data = this.convert(name, cff, properties);
break; break;
case "TrueType": case "TrueType":
@ -127,7 +158,7 @@ var Font = (function () {
// Repair the TrueType file if it is can be damaged in the point of // Repair the TrueType file if it is can be damaged in the point of
// view of the sanitizer // view of the sanitizer
this.font = this.checkAndRepair(name, file, properties); data = this.checkAndRepair(name, file, properties);
break; break;
default: default:
@ -135,28 +166,12 @@ var Font = (function () {
break; break;
} }
var data = this.font;
Fonts[name] = { Fonts[name] = {
data: data, data: data,
properties: properties, properties: properties,
loading: true, loading: true,
cache: Object.create(null) cache: Object.create(null)
} };
// Convert data to a string.
var dataStr = "";
var length = data.length;
for (var i = 0; i < length; ++i)
dataStr += String.fromCharCode(data[i]);
// Attach the font to the document. If this script is runnig in a worker,
// call `bindWorker`, which sends stuff over to the main thread.
if (typeof window != "undefined") {
this.bindDOM(dataStr);
} else {
this.bindWorker(dataStr);
}
}; };
function stringToArray(str) { function stringToArray(str) {
@ -310,57 +325,60 @@ var Font = (function () {
idDeltas + idRangeOffsets + glyphsIds); idDeltas + idRangeOffsets + glyphsIds);
}; };
function createOS2Table() { function createOS2Table(properties) {
var OS2 = stringToArray( return "\x00\x03" + // version
"\x00\x03" + // version "\x02\x24" + // xAvgCharWidth
"\x02\x24" + // xAvgCharWidth "\x01\xF4" + // usWeightClass
"\x01\xF4" + // usWeightClass "\x00\x05" + // usWidthClass
"\x00\x05" + // usWidthClass "\x00\x00" + // fstype
"\x00\x00" + // fstype "\x02\x8A" + // ySubscriptXSize
"\x02\x8A" + // ySubscriptXSize "\x02\xBB" + // ySubscriptYSize
"\x02\xBB" + // ySubscriptYSize "\x00\x00" + // ySubscriptXOffset
"\x00\x00" + // ySubscriptXOffset "\x00\x8C" + // ySubscriptYOffset
"\x00\x8C" + // ySubscriptYOffset "\x02\x8A" + // ySuperScriptXSize
"\x02\x8A" + // ySuperScriptXSize "\x02\xBB" + // ySuperScriptYSize
"\x02\xBB" + // ySuperScriptYSize "\x00\x00" + // ySuperScriptXOffset
"\x00\x00" + // ySuperScriptXOffset "\x01\xDF" + // ySuperScriptYOffset
"\x01\xDF" + // ySuperScriptYOffset "\x00\x31" + // yStrikeOutSize
"\x00\x31" + // yStrikeOutSize "\x01\x02" + // yStrikeOutPosition
"\x01\x02" + // yStrikeOutPosition "\x00\x00" + // sFamilyClass
"\x00\x00" + // sFamilyClass "\x02\x00\x06\x03\x00\x00\x00\x00\x00\x00" + // Panose
"\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 0-31) "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63) "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95) "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127) "\x2A\x32\x31\x2A" + // achVendID
"\x2A\x32\x31\x2A" + // achVendID "\x00\x20" + // fsSelection
"\x00\x20" + // fsSelection "\x00\x2D" + // usFirstCharIndex
"\x00\x2D" + // usFirstCharIndex "\x00\x7A" + // usLastCharIndex
"\x00\x7A" + // usLastCharIndex "\x00\x03" + // sTypoAscender
"\x00\x03" + // sTypoAscender "\x00\x20" + // sTypeDescender
"\x00\x20" + // sTypeDescender "\x00\x38" + // sTypoLineGap
"\x00\x38" + // sTypoLineGap string16(properties.ascent) + // usWinAscent
"\x00\x5A" + // usWinAscent string16(properties.descent) + // usWinDescent
"\x02\xB4" + // usWinDescent "\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31)
"\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31) "\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63)
"\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63) string16(properties.xHeight) + // sxHeight
"\x00\x00" + // sxHeight string16(properties.capHeight) + // sCapHeight
"\x00\x00" + // sCapHeight "\x00\x01" + // usDefaultChar
"\x00\x01" + // usDefaultChar "\x00\xCD" + // usBreakChar
"\x00\xCD" + // usBreakChar "\x00\x02"; // usMaxContext
"\x00\x02" // usMaxContext };
);
return OS2; function createPostTable(properties) {
TODO("Fill with real values from the font dict");
return "\x00\x03\x00\x00" + // Version number
string32(properties.italicAngle) + // 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
}; };
/**
* A bunch of the OpenType code is duplicate between this class and the
* TrueType code, this is intentional and will merge in a future version
* where all the code relative to OpenType will probably have its own
* class and will take decision without the Fonts consent.
* But at the moment it allows to develop around the TrueType rewriting
* on the fly without messing up with the 'regular' Type1 to OTF conversion.
*/
constructor.prototype = { constructor.prototype = {
name: null, name: null,
font: null, font: null,
@ -418,10 +436,11 @@ var Font = (function () {
var length = FontsUtils.bytesToInteger(font.getBytes(2)); var length = FontsUtils.bytesToInteger(font.getBytes(2));
var language = FontsUtils.bytesToInteger(font.getBytes(2)); var language = FontsUtils.bytesToInteger(font.getBytes(2));
if ((format == 0 && numTables == 1) || if ((format == 0 && numTables == 1) ||
(format == 6 && numTables == 1 && !properties.encoding.empty)) { (format == 6 && numTables == 1 && !properties.encoding.empty)) {
// Format 0 alone is not allowed by the sanitizer so let's rewrite // Format 0 alone is not allowed by the sanitizer so let's rewrite
// that to a 3-1-4 Unicode BMP table // that to a 3-1-4 Unicode BMP table
TODO("Use an other source of informations than charset here, it is not reliable");
var charset = properties.charset; var charset = properties.charset;
var glyphs = []; var glyphs = [];
for (var j = 0; j < charset.length; j++) { for (var j = 0; j < charset.length; j++) {
@ -525,10 +544,9 @@ var Font = (function () {
createOpenTypeHeader("\x00\x01\x00\x00", ttf, offsets, numTables); createOpenTypeHeader("\x00\x01\x00\x00", ttf, offsets, numTables);
// Insert the missing table // Insert the missing table
var OS2 = createOS2Table();
tables.push({ tables.push({
tag: "OS/2", tag: "OS/2",
data: OS2 data: stringToArray(createOS2Table(properties))
}); });
// Replace the old CMAP table with a shiny new one // Replace the old CMAP table with a shiny new one
@ -536,20 +554,9 @@ var Font = (function () {
// Rewrite the 'post' table if needed // Rewrite the 'post' table if needed
if (!post) { if (!post) {
post = tables.push({
"\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", tag: "post",
data: stringToArray(post) data: stringToArray(createPostTable(properties))
}); });
} }
@ -593,16 +600,16 @@ var Font = (function () {
return font.getBytes(); return font.getBytes();
}, },
convert: function font_convert(name, font, properties) { convert: function font_convert(fontName, font, properties) {
var otf = new Uint8Array(kMaxFontFileSize); var otf = new Uint8Array(kMaxFontFileSize);
function createNameTable(name) { function createNameTable(name) {
var names = [ var names = [
"See original licence", // Copyright "See original licence", // Copyright
name, // Font family fontName, // Font family
"undefined", // Font subfamily (font weight) "undefined", // Font subfamily (font weight)
"uniqueID", // Unique ID "uniqueID", // Unique ID
name, // Full font name fontName, // Full font name
"0.1", // Version "0.1", // Version
"undefined", // Postscript name "undefined", // Postscript name
"undefined", // Trademark "undefined", // Trademark
@ -610,7 +617,7 @@ var Font = (function () {
"undefined" // Designer "undefined" // Designer
]; ];
var name = var nameTable =
"\x00\x00" + // format "\x00\x00" + // format
"\x00\x0A" + // Number of names Record "\x00\x0A" + // Number of names Record
"\x00\x7E"; // Storage "\x00\x7E"; // Storage
@ -627,21 +634,21 @@ var Font = (function () {
"\x00\x00" + // name ID "\x00\x00" + // name ID
string16(str.length) + string16(str.length) +
string16(strOffset); string16(strOffset);
name += nameRecord; nameTable += nameRecord;
strOffset += str.length; strOffset += str.length;
} }
name += names.join(""); nameTable += names.join("");
return name; return nameTable;
} }
// Required Tables // Required Tables
var CFF = var CFF =
font.data, // PostScript Font Program font.data, // PostScript Font Program
OS2, // OS/2 and Windows Specific metrics OS2, // OS/2 and Windows Specific metrics
cmap, // Character to glyphs mapping cmap, // Character to glyphs mapping
head, // Font eader head, // Font header
hhea, // Horizontal header hhea, // Horizontal header
hmtx, // Horizontal metrics hmtx, // Horizontal metrics
maxp, // Maximum profile maxp, // Maximum profile
@ -665,13 +672,11 @@ var Font = (function () {
createTableEntry(otf, offsets, "CFF ", CFF); createTableEntry(otf, offsets, "CFF ", CFF);
/** OS/2 */ /** OS/2 */
OS2 = createOS2Table(); OS2 = stringToArray(createOS2Table(properties));
createTableEntry(otf, offsets, "OS/2", OS2); createTableEntry(otf, offsets, "OS/2", OS2);
//XXX Getting charstrings here seems wrong since this is another CFF glue
var charstrings = font.getOrderedCharStrings(properties.glyphs);
/** CMAP */ /** CMAP */
var charstrings = font.charstrings;
cmap = createCMapTable(charstrings); cmap = createCMapTable(charstrings);
createTableEntry(otf, offsets, "cmap", cmap); createTableEntry(otf, offsets, "cmap", cmap);
@ -720,11 +725,15 @@ var Font = (function () {
createTableEntry(otf, offsets, "hhea", hhea); createTableEntry(otf, offsets, "hhea", hhea);
/** HMTX */ /** HMTX */
hmtx = "\x01\xF4\x00\x00"; /* For some reasons, probably related to how the backend handle fonts,
* Linux seems to ignore this file and prefer the data from the CFF itself
* while Windows use this data. So be careful if you hack on Linux and
* have to touch the 'hmtx' table
*/
hmtx = "\x01\xF4\x00\x00"; // Fake .notdef
var width = 0, lsb = 0;
for (var i = 0; i < charstrings.length; i++) { for (var i = 0; i < charstrings.length; i++) {
var charstring = charstrings[i].charstring; width = charstrings[i].charstring[1];
var width = charstring[1];
var lsb = charstring[0];
hmtx += string16(width) + string16(lsb); hmtx += string16(width) + string16(lsb);
} }
hmtx = stringToArray(hmtx); hmtx = stringToArray(hmtx);
@ -741,17 +750,7 @@ var Font = (function () {
createTableEntry(otf, offsets, "name", name); createTableEntry(otf, offsets, "name", name);
/** POST */ /** POST */
// TODO: get those informations from the FontInfo structure post = stringToArray(createPostTable(properties));
post = "\x00\x03\x00\x00" + // Version number
"\x00\x00\x01\x00" + // italicAngle
"\x00\x00" + // underlinePosition
"\x00\x00" + // underlineThickness
"\x00\x00\x00\x00" + // isFixedPitch
"\x00\x00\x00\x00" + // minMemType42
"\x00\x00\x00\x00" + // maxMemType42
"\x00\x00\x00\x00" + // minMemType1
"\x00\x00\x00\x00"; // maxMemType1
post = stringToArray(post);
createTableEntry(otf, offsets, "post", post); createTableEntry(otf, offsets, "post", post);
// Once all the table entries header are written, dump the data! // Once all the table entries header are written, dump the data!
@ -768,96 +767,55 @@ var Font = (function () {
return fontData; return fontData;
}, },
bindWorker: function font_bind_worker(dataStr) { bindWorker: function font_bindWorker(data) {
postMessage({ postMessage({
action: "font", action: "font",
data: { data: {
raw: dataStr, raw: data,
fontName: this.name, fontName: this.name,
mimetype: this.mimetype mimetype: this.mimetype
} }
}); });
}, },
bindDOM: function font_bind_dom(dataStr) { bindDOM: function font_bindDom(data) {
var fontName = this.name; var fontName = this.name;
/** Hack begin */ /** Hack begin */
// Actually there is not event when a font has finished downloading so // 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 // the following code are a dirty hack to 'guess' when a font is ready
// This code could go away when bug 471915 has landed
var canvas = document.createElement("canvas"); 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"); var ctx = canvas.getContext("2d");
ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; ctx.font = "bold italic 20px " + fontName + ", Symbol";
var testString = " "; 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 // Periodicaly check for the width of the testString, it will be
// different once the real font has loaded // different once the real font has loaded
var textWidth = ctx.measureText(testString).width; var textWidth = ctx.measureText(testString).width;
var interval = window.setInterval(function canvasInterval(self) { var interval = window.setInterval(function canvasInterval(self) {
this.start = this.start || Date.now(); this.start = this.start || Date.now();
ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; ctx.font = "bold italic 20px " + fontName + ", Symbol";
// For some reasons the font has not loaded, so mark it loaded for the // For some reasons the font has not loaded, so mark it loaded for the
// page to proceed but cry // page to proceed but cry
if ((Date.now() - this.start) >= kMaxWaitForFontFace) { if ((Date.now() - this.start) >= kMaxWaitForFontFace) {
window.clearInterval(interval); window.clearInterval(interval);
Fonts[fontName].loading = false; Fonts[fontName].loading = false;
warn("Is " + fontName + " for charset: " + charset + " loaded?"); warn("Is " + fontName + " loaded?");
this.start = 0; this.start = 0;
} else if (textWidth != ctx.measureText(testString).width) { } else if (textWidth != ctx.measureText(testString).width) {
window.clearInterval(interval); window.clearInterval(interval);
Fonts[fontName].loading = false; Fonts[fontName].loading = false;
this.start = 0; this.start = 0;
} }
if (debug)
ctx.fillText(testString, 20, 50);
}, 30, this); }, 30, this);
/** Hack end */ /** Hack end */
// Convert the data string and add it to the page.
var base64 = window.btoa(dataStr);
// Add the @font-face rule to the document // Add the @font-face rule to the document
var url = "url(data:" + this.mimetype + ";base64," + base64 + ");"; var url = "url(data:" + this.mimetype + ";base64," + window.btoa(data) + ");";
var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}";
var styleSheet = document.styleSheets[0]; var styleSheet = document.styleSheets[0];
styleSheet.insertRule(rule, styleSheet.length); styleSheet.insertRule(rule, styleSheet.length);
@ -887,6 +845,9 @@ var FontsUtils = {
bytes.set([value >> 24, value >> 16, value >> 8, value]); bytes.set([value >> 24, value >> 16, value >> 8, value]);
return [bytes[0], bytes[1], bytes[2], bytes[3]]; return [bytes[0], bytes[1], bytes[2], bytes[3]];
} }
error("This number of bytes " + bytesCount + " is not supported");
return null;
}, },
bytesToInteger: function fu_bytesToInteger(bytesArray) { bytesToInteger: function fu_bytesToInteger(bytesArray) {
@ -1223,6 +1184,8 @@ var CFFStrings = [
"001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold" "001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold"
]; ];
var type1Parser = new Type1Parser();
var CFF = function(name, file, properties) { var CFF = function(name, file, properties) {
// Get the data block containing glyphs and subrs informations // Get the data block containing glyphs and subrs informations
var length1 = file.dict.get("Length1"); var length1 = file.dict.get("Length1");
@ -1230,17 +1193,15 @@ var CFF = function(name, file, properties) {
file.skip(length1); file.skip(length1);
var eexecBlock = file.getBytes(length2); var eexecBlock = file.getBytes(length2);
// Decrypt the data blocks and retrieve the informations from it // Decrypt the data blocks and retrieve it's content
var parser = new Type1Parser(); var data = type1Parser.extractFontProgram(eexecBlock);
var fontInfo = parser.extractFontProgram(eexecBlock);
properties.subrs = fontInfo.subrs; this.charstrings = this.getOrderedCharStrings(data.charstrings);
properties.glyphs = fontInfo.charstrings; this.data = this.wrap(name, this.charstrings, data.subrs, properties);
this.data = this.wrap(name, properties);
}; };
CFF.prototype = { CFF.prototype = {
createCFFIndexHeader: function(objects, isByte) { createCFFIndexHeader: function cff_createCFFIndexHeader(objects, isByte) {
// First 2 bytes contains the number of objects contained into this index // First 2 bytes contains the number of objects contained into this index
var count = objects.length; var count = objects.length;
@ -1277,18 +1238,18 @@ CFF.prototype = {
return data; return data;
}, },
encodeNumber: function(value) { encodeNumber: function cff_encodeNumber(value) {
var x = 0; var x = 0;
if (value >= -32768 && value <= 32767) { if (value >= -32768 && value <= 32767) {
return [ 28, value >> 8, value & 0xFF ]; return [ 28, value >> 8, value & 0xFF ];
} else if (value >= (-2147483647-1) && value <= 2147483647) { } else if (value >= (-2147483647-1) && value <= 2147483647) {
return [ 0xFF, value >> 24, Value >> 16, value >> 8, value & 0xFF ]; return [ 0xFF, value >> 24, Value >> 16, value >> 8, value & 0xFF ];
} else {
error("Value: " + value + " is not allowed");
} }
error("Value: " + value + " is not allowed");
return null;
}, },
getOrderedCharStrings: function(glyphs) { getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs) {
var charstrings = []; var charstrings = [];
for (var i = 0; i < glyphs.length; i++) { for (var i = 0; i < glyphs.length; i++) {
@ -1301,7 +1262,7 @@ CFF.prototype = {
charstrings.push({ charstrings.push({
glyph: glyph, glyph: glyph,
unicode: unicode, unicode: unicode,
charstring: glyphs[i].data.slice() charstring: glyphs[i].data
}); });
} }
}; };
@ -1334,7 +1295,7 @@ CFF.prototype = {
"hvcurveto": 31, "hvcurveto": 31,
}, },
flattenCharstring: function flattenCharstring(glyph, charstring, subrs) { flattenCharstring: function flattenCharstring(charstring, subrs) {
var i = 0; var i = 0;
while (true) { while (true) {
var obj = charstring[i]; var obj = charstring[i];
@ -1344,9 +1305,9 @@ CFF.prototype = {
if (obj.charAt) { if (obj.charAt) {
switch (obj) { switch (obj) {
case "callsubr": case "callsubr":
var subr = subrs[charstring[i - 1]].slice(); var subr = subrs[charstring[i - 1]];
if (subr.length > 1) { if (subr.length > 1) {
subr = this.flattenCharstring(glyph, subr, subrs); subr = this.flattenCharstring(subr, subrs);
subr.pop(); subr.pop();
charstring.splice(i - 1, 2, subr); charstring.splice(i - 1, 2, subr);
} else { } else {
@ -1436,23 +1397,16 @@ CFF.prototype = {
i++; i++;
} }
error("failing with i = " + i + " in charstring:" + charstring + "(" + charstring.length + ")"); error("failing with i = " + i + " in charstring:" + charstring + "(" + charstring.length + ")");
return [];
}, },
wrap: function wrap(name, properties) { wrap: function wrap(name, charstrings, subrs, properties) {
var charstrings = this.getOrderedCharStrings(properties.glyphs);
// Starts the conversion of the Type1 charstrings to Type2 // Starts the conversion of the Type1 charstrings to Type2
var charstringsCount = 0;
var charstringsDataLength = 0;
var glyphs = []; var glyphs = [];
for (var i = 0; i < charstrings.length; i++) { var glyphsCount = charstrings.length;
var charstring = charstrings[i].charstring.slice(); for (var i = 0; i < glyphsCount; i++) {
var glyph = charstrings[i].glyph; var charstring = charstrings[i].charstring;
glyphs.push(this.flattenCharstring(charstring.slice(), subrs));
var flattened = this.flattenCharstring(glyph, charstring, properties.subrs);
glyphs.push(flattened);
charstringsCount++;
charstringsDataLength += flattened.length;
} }
// Create a CFF font data // Create a CFF font data
@ -1487,17 +1441,16 @@ CFF.prototype = {
// Fill the charset header (first byte is the encoding) // Fill the charset header (first byte is the encoding)
var charset = [0x00]; var charset = [0x00];
for (var i = 0; i < glyphs.length; i++) { for (var i = 0; i < glyphsCount; i++) {
var index = CFFStrings.indexOf(charstrings[i].glyph); var index = CFFStrings.indexOf(charstrings[i].glyph);
if (index == -1) if (index == -1)
index = CFFStrings.length + strings.indexOf(glyph); index = CFFStrings.length + strings.indexOf(charstrings[i].glyph);
var bytes = FontsUtils.integerToBytes(index, 2); var bytes = FontsUtils.integerToBytes(index, 2);
charset.push(bytes[0]); charset.push(bytes[0]);
charset.push(bytes[1]); charset.push(bytes[1]);
} }
var charstringsIndex = this.createCFFIndexHeader([[0x40, 0x0E]].concat(glyphs), true); var charstringsIndex = this.createCFFIndexHeader([[0x40, 0x0E]].concat(glyphs), true);
charstringsIndex = charstringsIndex.join(" ").split(" "); // XXX why?
//Top Dict Index //Top Dict Index
var topDictIndex = [ var topDictIndex = [
@ -1523,7 +1476,7 @@ CFF.prototype = {
topDictIndex = topDictIndex.concat([28, 0, 0, 16]) // Encoding topDictIndex = topDictIndex.concat([28, 0, 0, 16]) // Encoding
var charstringsOffset = charsetOffset + (charstringsCount * 2) + 1; var charstringsOffset = charsetOffset + (glyphsCount * 2) + 1;
topDictIndex = topDictIndex.concat(this.encodeNumber(charstringsOffset)); topDictIndex = topDictIndex.concat(this.encodeNumber(charstringsOffset));
topDictIndex.push(17); // charstrings topDictIndex.push(17); // charstrings
@ -1531,7 +1484,6 @@ CFF.prototype = {
var privateOffset = charstringsOffset + charstringsIndex.length; var privateOffset = charstringsOffset + charstringsIndex.length;
topDictIndex = topDictIndex.concat(this.encodeNumber(privateOffset)); topDictIndex = topDictIndex.concat(this.encodeNumber(privateOffset));
topDictIndex.push(18); // Private topDictIndex.push(18); // Private
topDictIndex = topDictIndex.join(" ").split(" ");
var indexes = [ var indexes = [
topDictIndex, stringsIndex, topDictIndex, stringsIndex,
@ -1561,7 +1513,6 @@ CFF.prototype = {
139, 12, 14, 139, 12, 14,
28, 0, 55, 19 28, 0, 55, 19
]); ]);
privateData = privateData.join(" ").split(" ");
cff.set(privateData, currentOffset); cff.set(privateData, currentOffset);
currentOffset += privateData.length; currentOffset += privateData.length;

View File

@ -3,6 +3,8 @@
"use strict"; "use strict";
var pageTimeout;
var PDFViewer = { var PDFViewer = {
queryParams: {}, queryParams: {},
@ -75,81 +77,45 @@ var PDFViewer = {
}, },
drawPage: function(num) { drawPage: function(num) {
if (!PDFViewer.pdf) { if (!PDFViewer.pdf)
return; return;
}
var div = document.getElementById('pageContainer' + num); var div = document.getElementById('pageContainer' + num);
var canvas = document.createElement('canvas'); var canvas = document.createElement('canvas');
if (div && !div.hasChildNodes()) { if (div && !div.hasChildNodes()) {
div.appendChild(canvas);
var page = PDFViewer.pdf.getPage(num); var page = PDFViewer.pdf.getPage(num);
canvas.id = 'page' + num; canvas.id = 'page' + num;
canvas.mozOpaque = true; canvas.mozOpaque = true;
// Canvas dimensions must be specified in CSS pixels. CSS pixels // Canvas dimensions must be specified in CSS pixels. CSS pixels
// are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
canvas.width = PDFViewer.pageWidth(); canvas.width = PDFViewer.pageWidth();
canvas.height = PDFViewer.pageHeight(); canvas.height = PDFViewer.pageHeight();
div.appendChild(canvas);
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
ctx.save(); ctx.save();
ctx.fillStyle = 'rgb(255, 255, 255)'; ctx.fillStyle = 'rgb(255, 255, 255)';
ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore(); ctx.restore();
var gfx = new CanvasGraphics(ctx); var gfx = new CanvasGraphics(ctx);
var fonts = [];
// page.compile will collect all fonts for us, once we have loaded them // page.compile will collect all fonts for us, once we have loaded them
// we can trigger the actual page rendering with page.display // we can trigger the actual page rendering with page.display
var fonts = [];
page.compile(gfx, fonts); page.compile(gfx, fonts);
var areFontsReady = true; var loadFont = function() {
if (!FontsLoader.bind(fonts)) {
// Inspect fonts and translate the missing one pageTimeout = window.setTimeout(loadFont, 10);
var fontCount = fonts.length; return;
for (var i = 0; i < fontCount; i++) {
var font = fonts[i];
if (Fonts[font.name]) {
areFontsReady = areFontsReady && !Fonts[font.name].loading;
continue;
} }
page.display(gfx);
new Font(font.name, font.file, font.properties);
areFontsReady = false;
} }
loadFont();
var pageInterval;
var delayLoadFont = function() {
for (var i = 0; i < fontCount; i++) {
if (Fonts[font.name].loading) {
return;
}
}
clearInterval(pageInterval);
while (div.hasChildNodes()) {
div.removeChild(div.firstChild);
}
PDFViewer.drawPage(num);
}
if (!areFontsReady) {
pageInterval = setInterval(delayLoadFont, 10);
return;
}
page.display(gfx);
} }
}, },
@ -258,7 +224,6 @@ var PDFViewer = {
}; };
window.onload = function() { window.onload = function() {
// Parse the URL query parameters into a cached object. // Parse the URL query parameters into a cached object.
PDFViewer.queryParams = function() { PDFViewer.queryParams = function() {
var qs = window.location.search.substring(1); var qs = window.location.search.substring(1);

88
pdf.js
View File

@ -71,14 +71,14 @@ var Stream = (function() {
get length() { get length() {
return this.end - this.start; return this.end - this.start;
}, },
getByte: function() { getByte: function stream_getByte() {
if (this.pos >= this.end) if (this.pos >= this.end)
return; return null;
return this.bytes[this.pos++]; return this.bytes[this.pos++];
}, },
// returns subarray of original buffer // returns subarray of original buffer
// should only be read // should only be read
getBytes: function(length) { getBytes: function stream_getBytes(length) {
var bytes = this.bytes; var bytes = this.bytes;
var pos = this.pos; var pos = this.pos;
var strEnd = this.end; var strEnd = this.end;
@ -93,28 +93,28 @@ var Stream = (function() {
this.pos = end; this.pos = end;
return bytes.subarray(pos, end); return bytes.subarray(pos, end);
}, },
lookChar: function() { lookChar: function stream_lookChar() {
if (this.pos >= this.end) if (this.pos >= this.end)
return; return null;
return String.fromCharCode(this.bytes[this.pos]); return String.fromCharCode(this.bytes[this.pos]);
}, },
getChar: function() { getChar: function stream_getChar() {
if (this.pos >= this.end) if (this.pos >= this.end)
return; return null;
return String.fromCharCode(this.bytes[this.pos++]); return String.fromCharCode(this.bytes[this.pos++]);
}, },
skip: function(n) { skip: function stream_skip(n) {
if (!n) if (!n)
n = 1; n = 1;
this.pos += n; this.pos += n;
}, },
reset: function() { reset: function stream_reset() {
this.pos = this.start; this.pos = this.start;
}, },
moveStart: function() { moveStart: function stream_moveStart() {
this.start = this.pos; this.start = this.pos;
}, },
makeSubStream: function(start, length, dict) { makeSubStream: function stream_makeSubstream(start, length, dict) {
return new Stream(this.bytes.buffer, start, length, dict); return new Stream(this.bytes.buffer, start, length, dict);
} }
}; };
@ -146,7 +146,7 @@ var DecodeStream = (function() {
} }
constructor.prototype = { constructor.prototype = {
ensureBuffer: function(requested) { ensureBuffer: function decodestream_ensureBuffer(requested) {
var buffer = this.buffer; var buffer = this.buffer;
var current = buffer ? buffer.byteLength : 0; var current = buffer ? buffer.byteLength : 0;
if (requested < current) if (requested < current)
@ -159,16 +159,16 @@ var DecodeStream = (function() {
buffer2[i] = buffer[i]; buffer2[i] = buffer[i];
return this.buffer = buffer2; return this.buffer = buffer2;
}, },
getByte: function() { getByte: function decodestream_getByte() {
var pos = this.pos; var pos = this.pos;
while (this.bufferLength <= pos) { while (this.bufferLength <= pos) {
if (this.eof) if (this.eof)
return; return null;
this.readBlock(); this.readBlock();
} }
return this.buffer[this.pos++]; return this.buffer[this.pos++];
}, },
getBytes: function(length) { getBytes: function decodestream_getBytes(length) {
var pos = this.pos; var pos = this.pos;
if (length) { if (length) {
@ -191,25 +191,25 @@ var DecodeStream = (function() {
this.pos = end; this.pos = end;
return this.buffer.subarray(pos, end) return this.buffer.subarray(pos, end)
}, },
lookChar: function() { lookChar: function decodestream_lookChar() {
var pos = this.pos; var pos = this.pos;
while (this.bufferLength <= pos) { while (this.bufferLength <= pos) {
if (this.eof) if (this.eof)
return; return null;
this.readBlock(); this.readBlock();
} }
return String.fromCharCode(this.buffer[this.pos]); return String.fromCharCode(this.buffer[this.pos]);
}, },
getChar: function() { getChar: function decodestream_getChar() {
var pos = this.pos; var pos = this.pos;
while (this.bufferLength <= pos) { while (this.bufferLength <= pos) {
if (this.eof) if (this.eof)
return; return null;
this.readBlock(); this.readBlock();
} }
return String.fromCharCode(this.buffer[this.pos++]); return String.fromCharCode(this.buffer[this.pos++]);
}, },
skip: function(n) { skip: function decodestream_skip(n) {
if (!n) if (!n)
n = 1; n = 1;
this.pos += n; this.pos += n;
@ -635,6 +635,7 @@ var PredictorStream = (function() {
var rowBytes = this.rowBytes = (columns * colors * bits + 7) >> 3; var rowBytes = this.rowBytes = (columns * colors * bits + 7) >> 3;
DecodeStream.call(this); DecodeStream.call(this);
return this;
} }
constructor.prototype = Object.create(DecodeStream.prototype); constructor.prototype = Object.create(DecodeStream.prototype);
@ -905,7 +906,9 @@ var Dict = (function() {
constructor.prototype = { constructor.prototype = {
get: function(key) { get: function(key) {
return this.map[key]; if (key in this.map)
return this.map[key];
return null;
}, },
get2: function(key1, key2) { get2: function(key1, key2) {
return this.get(key1) || this.get(key2); return this.get(key1) || this.get(key2);
@ -1590,7 +1593,7 @@ var XRef = (function() {
} }
constructor.prototype = { constructor.prototype = {
readXRefTable: function(parser) { readXRefTable: function readXRefTable(parser) {
var obj; var obj;
while (true) { while (true) {
if (IsCmd(obj = parser.getObj(), "trailer")) if (IsCmd(obj = parser.getObj(), "trailer"))
@ -1661,7 +1664,7 @@ var XRef = (function() {
return dict; return dict;
}, },
readXRefStream: function(stream) { readXRefStream: function readXRefStream(stream) {
var streamParameters = stream.parameters; var streamParameters = stream.parameters;
var length = streamParameters.get("Length"); var length = streamParameters.get("Length");
var byteWidths = streamParameters.get("W"); var byteWidths = streamParameters.get("W");
@ -1713,7 +1716,7 @@ var XRef = (function() {
this.readXRef(prev); this.readXRef(prev);
return streamParameters; return streamParameters;
}, },
readXRef: function(startXRef) { readXRef: function readXref(startXRef) {
var stream = this.stream; var stream = this.stream;
stream.pos = startXRef; stream.pos = startXRef;
var parser = new Parser(new Lexer(stream), true); var parser = new Parser(new Lexer(stream), true);
@ -1731,6 +1734,7 @@ var XRef = (function() {
return this.readXRefStream(obj); return this.readXRefStream(obj);
} }
error("Invalid XRef"); error("Invalid XRef");
return null;
}, },
getEntry: function(i) { getEntry: function(i) {
var e = this.entries[i]; var e = this.entries[i];
@ -2396,7 +2400,7 @@ var CanvasGraphics = (function() {
if (!fd) if (!fd)
// XXX deprecated "special treatment" for standard // XXX deprecated "special treatment" for standard
// fonts? What do we need to do here? // fonts? What do we need to do here?
return; return null;
var descriptor = xref.fetch(fd); var descriptor = xref.fetch(fd);
var fontName = descriptor.get("FontName"); var fontName = descriptor.get("FontName");
@ -2408,16 +2412,9 @@ var CanvasGraphics = (function() {
error("FontFile not found for font: " + fontName); error("FontFile not found for font: " + fontName);
fontFile = xref.fetchIfRef(fontFile); fontFile = xref.fetchIfRef(fontFile);
// Fonts with an embedded cmap but without any assignment in
// it are not yet supported, so ask the fonts loader to ignore
// them to not pay a stupid one sec latence.
var ignoreFont = false;
var encodingMap = {}; var encodingMap = {};
var charset = []; var charset = [];
if (fontDict.has("Encoding")) { if (fontDict.has("Encoding")) {
ignoreFont = false;
var encoding = xref.fetchIfRef(fontDict.get("Encoding")); var encoding = xref.fetchIfRef(fontDict.get("Encoding"));
if (IsDict(encoding)) { if (IsDict(encoding)) {
// Build a map between codes and glyphs // Build a map between codes and glyphs
@ -2440,9 +2437,8 @@ var CanvasGraphics = (function() {
error("Unknown font encoding"); error("Unknown font encoding");
var index = 0; var index = 0;
for (var j = 0; j < encoding.length; j++) { for (var j = 0; j < encoding.length; j++)
encodingMap[index++] = GlyphsUnicode[encoding[j]]; encodingMap[index++] = GlyphsUnicode[encoding[j]];
}
var firstChar = xref.fetchIfRef(fontDict.get("FirstChar")); var firstChar = xref.fetchIfRef(fontDict.get("FirstChar"));
var widths = xref.fetchIfRef(fontDict.get("Widths")); var widths = xref.fetchIfRef(fontDict.get("Widths"));
@ -2466,13 +2462,7 @@ var CanvasGraphics = (function() {
var tokens = []; var tokens = [];
var token = ""; var token = "";
var length = cmapObj.length; var cmap = cmapObj.getBytes(cmapObj.length);
if (cmapObj instanceof FlateStream) {
cmapObj.readBlock();
length = cmapObj.bufferLength;
}
var cmap = cmapObj.getBytes(length);
for (var i =0; i < cmap.length; i++) { for (var i =0; i < cmap.length; i++) {
var byte = cmap[i]; var byte = cmap[i];
if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) { if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) {
@ -2482,7 +2472,6 @@ var CanvasGraphics = (function() {
break; break;
case "beginbfrange": case "beginbfrange":
ignoreFont = false;
case "begincodespacerange": case "begincodespacerange":
token = ""; token = "";
tokens = []; tokens = [];
@ -2529,16 +2518,19 @@ var CanvasGraphics = (function() {
} }
var subType = fontDict.get("Subtype"); var subType = fontDict.get("Subtype");
var bbox = descriptor.get("FontBBox"); assertWellFormed(IsName(subType), "invalid font Subtype");
assertWellFormed(IsName(subType) && IsArray(bbox),
"invalid font Subtype or FontBBox");
var properties = { var properties = {
type: subType.name, type: subType.name,
encoding: encodingMap, encoding: encodingMap,
charset: charset, charset: charset,
bbox: bbox, bbox: descriptor.get("FontBBox"),
ignore: ignoreFont ascent: descriptor.get("Ascent"),
descent: descriptor.get("Descent"),
xHeight: descriptor.get("XHeight"),
capHeight: descriptor.get("CapHeight"),
flags: descriptor.get("Flags"),
italicAngle: descriptor.get("ItalicAngle")
}; };
return { return {
@ -3049,7 +3041,7 @@ var CanvasGraphics = (function() {
this.restore(); this.restore();
TODO("Inverse pattern is painted"); TODO("Inverse pattern is painted");
var pattern = this.ctx.createPattern(tmpCanvas, "repeat"); pattern = this.ctx.createPattern(tmpCanvas, "repeat");
this.ctx.fillStyle = pattern; this.ctx.fillStyle = pattern;
}, },
setStrokeGray: function(gray) { setStrokeGray: function(gray) {

View File

@ -3,7 +3,7 @@
"use strict"; "use strict";
var pdfDocument, canvas, pageDisplay, pageNum, numPages, pageInterval; var pdfDocument, canvas, pageDisplay, pageNum, numPages, pageTimeout;
function load(userInput) { function load(userInput) {
canvas = document.getElementById("canvas"); canvas = document.getElementById("canvas");
canvas.mozOpaque = true; canvas.mozOpaque = true;
@ -52,7 +52,7 @@ function gotoPage(num) {
} }
function displayPage(num) { function displayPage(num) {
window.clearInterval(pageInterval); window.clearTimeout(pageTimeout);
document.getElementById("pageNumber").value = num; document.getElementById("pageNumber").value = num;
@ -75,28 +75,12 @@ function displayPage(num) {
page.compile(gfx, fonts); page.compile(gfx, fonts);
var t2 = Date.now(); var t2 = Date.now();
var fontsReady = true; function loadFont() {
if (!FontsLoader.bind(fonts)) {
// Inspect fonts and translate the missing one pageTimeout = window.setTimeout(loadFont, 10);
var count = fonts.length; return;
for (var i = 0; i < count; i++) {
var font = fonts[i];
if (Fonts[font.name]) {
fontsReady = fontsReady && !Fonts[font.name].loading;
continue;
} }
new Font(font.name, font.file, font.properties);
fontsReady = false;
}
function delayLoadFont() {
for (var i = 0; i < count; i++) {
if (Fonts[font.name].loading)
return;
}
window.clearInterval(pageInterval);
var t3 = Date.now(); var t3 = Date.now();
page.display(gfx); page.display(gfx);
@ -106,12 +90,7 @@ function displayPage(num) {
var infoDisplay = document.getElementById("info"); var infoDisplay = document.getElementById("info");
infoDisplay.innerHTML = "Time to load/compile/fonts/render: "+ (t1 - t0) + "/" + (t2 - t1) + "/" + (t3 - t2) + "/" + (t4 - t3) + " ms"; infoDisplay.innerHTML = "Time to load/compile/fonts/render: "+ (t1 - t0) + "/" + (t2 - t1) + "/" + (t3 - t2) + "/" + (t4 - t3) + " ms";
}; };
loadFont();
if (fontsReady) {
delayLoadFont();
} else {
pageInterval = setInterval(delayLoadFont, 10);
}
} }
function nextPage() { function nextPage() {