Merge branch 'master' into tiling

This commit is contained in:
sbarman 2011-06-17 17:38:29 -07:00
commit 2179d64593
3 changed files with 140 additions and 155 deletions

158
fonts.js
View File

@ -31,7 +31,7 @@ var fontCount = 0;
var Fonts = { var Fonts = {
_active: null, _active: null,
get active() { get active() {
return this._active || { encoding: [] }; return this._active;
}, },
set active(aName) { set active(aName) {
@ -39,8 +39,11 @@ var Fonts = {
}, },
unicodeFromCode: function fonts_unicodeFromCode(aCode) { unicodeFromCode: function fonts_unicodeFromCode(aCode) {
var unicode = GlyphsUnicode[this.active.encoding[aCode]]; var active = this._active;
return unicode ? unicode : aCode; if (!active || !active.properties.encoding)
return aCode;
return GlyphsUnicode[active.properties.encoding[aCode]];
} }
}; };
@ -49,16 +52,10 @@ var Fonts = {
* decoding logics whatever type it is (assuming the font type is supported). * decoding logics whatever type it is (assuming the font type is supported).
* *
* For example to read a Type1 font and to attach it to the document: * For example to read a Type1 font and to attach it to the document:
* var type1Font = new Font("MyFontName", binaryData, aFontEncoding, "Type1"); * var type1Font = new Font("MyFontName", binaryFile, propertiesObject);
* type1Font.bind(); * type1Font.bind();
*
* As an improvment the last parameter can be replaced by an automatic guess
* of the font type based on the first byte of the file.
*
* FIXME There is now too many parameters, this should be turned into an
* object containing all the required informations about the font
*/ */
var Font = function(aName, aFile, aEncoding, aCharset, aBBox, aType) { var Font = function(aName, aFile, aProperties) {
this.name = aName; this.name = aName;
// If the font has already been decoded simply return // If the font has already been decoded simply return
@ -68,41 +65,40 @@ var Font = function(aName, aFile, aEncoding, aCharset, aBBox, aType) {
} }
fontCount++; fontCount++;
var start = Date.now(); switch (aProperties.type) {
switch (aType) {
case "Type1": case "Type1":
var cff = new CFF(aName, aBBox, aFile); var cff = new CFF(aName, aFile, aProperties);
this.mimetype = "font/otf"; this.mimetype = "font/otf";
// Wrap the CFF data inside an OTF font file // Wrap the CFF data inside an OTF font file
this.font = this.cover(cff); this.font = this.cover(cff, aProperties);
break; break;
case "TrueType": case "TrueType":
// TrueType is disabled for the moment since the sanitizer prevent it
// from loading because of an overdated cmap table
return Fonts[aName] = { return Fonts[aName] = {
data: null, data: null,
encoding: {}, properties: {
charset: null, encoding: {},
charset: null
},
loading: false loading: false
}; };
// TrueType is disabled for the moment since the sanitizer prevent it
// from loading
this.mimetype = "font/ttf"; this.mimetype = "font/ttf";
var ttf = new TrueType(aFile); var ttf = new TrueType(aFile);
this.font = ttf.data; this.font = ttf.data;
break; break;
default: default:
warn("Font " + aType + " is not supported"); warn("Font " + aProperties.type + " is not supported");
break; break;
} }
var end = Date.now();
Fonts[aName] = { Fonts[aName] = {
data: this.font, data: this.font,
encoding: aEncoding, properties: aProperties,
charset: aCharset ? aCharset.slice() : null,
loading: true loading: true
} }
@ -204,7 +200,7 @@ Font.prototype = {
if (debug) if (debug)
ctx.fillText(testString, 20, 50); ctx.fillText(testString, 20, 50);
}, 20, this); }, 50, this);
/** Hack end */ /** Hack end */
@ -355,7 +351,7 @@ Font.prototype = {
idDeltas, idRangeOffsets, glyphsIdsArray); idDeltas, idRangeOffsets, glyphsIdsArray);
}, },
cover: function font_cover(aFont) { cover: function font_cover(aFont, aProperties) {
var otf = new Uint8Array(kMaxFontFileSize); var otf = new Uint8Array(kMaxFontFileSize);
// Required Tables // Required Tables
@ -431,7 +427,7 @@ Font.prototype = {
this._createTableEntry(otf, offsets, "OS/2", OS2); this._createTableEntry(otf, offsets, "OS/2", OS2);
//XXX Getting charstrings here seems wrong since this is another CFF glue //XXX Getting charstrings here seems wrong since this is another CFF glue
var charstrings = aFont.getOrderedCharStrings(aFont.glyphs); var charstrings = aFont.getOrderedCharStrings(aProperties.glyphs);
/** CMAP */ /** CMAP */
cmap = this._createCMAPTable(charstrings); cmap = this._createCMAPTable(charstrings);
@ -715,22 +711,11 @@ var TrueType = function(aFile) {
ttf.set(tableData, offsets.currentOffset); ttf.set(tableData, offsets.currentOffset);
offsets.currentOffset += tableData.length; offsets.currentOffset += tableData.length;
if (0) {
var data = [];
for (var j = 0; j < tableData.length; j++)
d.push(tableData[j]);
log("data for table: " + table.tag + ": " + data);
}
// 4-byte aligned data // 4-byte aligned data
while (offsets.currentOffset & 3) while (offsets.currentOffset & 3)
offsets.currentOffset++; offsets.currentOffset++;
} }
var fontData = [];
for (var i = 0; i < ttf.length; i++)
fontData.push(ttf[i]);
this.data = ttf; this.data = ttf;
return; return;
} else if (requiredTables.lenght) { } else if (requiredTables.lenght) {
@ -844,17 +829,11 @@ TrueType.prototype = {
/** /**
* This dictionary holds decoded fonts data. * Type1Parser encapsulate the needed code for parsing a Type1 font
* program.
* Some of its logic depends on the Type2 charstrings structure.
*/ */
var Type1Parser = function() { var Type1Parser = function() {
// Turn on this flag for additional debugging logs
var debug = false;
var dump = function(aData) {
if (debug)
log(aData);
};
/* /*
* Decrypt a Sequence of Ciphertext Bytes to Produce the Original Sequence * Decrypt a Sequence of Ciphertext Bytes to Produce the Original Sequence
* of Plaintext Bytes. The function took a key as a parameter which can be * of Plaintext Bytes. The function took a key as a parameter which can be
@ -863,8 +842,7 @@ var Type1Parser = function() {
var kEexecEncryptionKey = 55665; var kEexecEncryptionKey = 55665;
var kCharStringsEncryptionKey = 4330; var kCharStringsEncryptionKey = 4330;
function decrypt(aStream, aKey, aDiscardNumber, aByteArray) { function decrypt(aStream, aKey, aDiscardNumber) {
var start = Date.now();
var r = aKey, c1 = 52845, c2 = 22719; var r = aKey, c1 = 52845, c2 = 22719;
var decryptedString = []; var decryptedString = [];
@ -872,14 +850,9 @@ var Type1Parser = function() {
var count = aStream.length; var count = aStream.length;
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
value = aStream[i]; value = aStream[i];
if (aByteArray) decryptedString[i] = value ^ (r >> 8);
decryptedString[i] = value ^ (r >> 8);
else
decryptedString[i] = String.fromCharCode(value ^ (r >> 8));
r = ((value + r) * c1 + c2) & ((1 << 16) - 1); r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
} }
var end = Date.now();
dump("Time to decrypt string of length " + count + " is " + (end - start));
return decryptedString.slice(aDiscardNumber); return decryptedString.slice(aDiscardNumber);
}; };
@ -1014,8 +987,7 @@ var Type1Parser = function() {
} else if (!command) { } else if (!command) {
break; break;
} else if (command == -1) { } else if (command == -1) {
log("decodeCharstring: " + charString); error("Support for Type1 command " + value + " (" + escape + ") is not implemented in charstring: " + charString);
error("Support for Type1 command " + value + " (" + escape + ") is not implemented");
} }
value = command; value = command;
@ -1042,8 +1014,8 @@ var Type1Parser = function() {
* Returns an object containing a Subrs array and a CharStrings array * Returns an object containing a Subrs array and a CharStrings array
* extracted from and eexec encrypted block of data * extracted from and eexec encrypted block of data
*/ */
this.extractFontInfo = function(aStream) { this.extractFontProgram = function t1_extractFontProgram(aStream) {
var eexecString = decrypt(aStream, kEexecEncryptionKey, 4, true); var eexecString = decrypt(aStream, kEexecEncryptionKey, 4);
var subrs = [], glyphs = []; var subrs = [], glyphs = [];
var inSubrs = inGlyphs = false; var inSubrs = inGlyphs = false;
var glyph = ""; var glyph = "";
@ -1052,15 +1024,15 @@ var Type1Parser = function() {
var index = 0; var index = 0;
var length = 0; var length = 0;
var count = eexecString.length;
var c = ""; var c = "";
var count = eexecString.length;
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
var c = eexecString[i]; var c = eexecString[i];
if (inSubrs && c == 0x52) { if (inSubrs && c == 0x52) {
length = parseInt(length); length = parseInt(length);
var data = eexecString.slice(i + 3, i + 3 + length); var data = eexecString.slice(i + 3, i + 3 + length);
var encodedSubr = decrypt(data, kCharStringsEncryptionKey, 4, true); var encodedSubr = decrypt(data, kCharStringsEncryptionKey, 4);
var subr = decodeCharString(encodedSubr); var subr = decodeCharString(encodedSubr);
subrs.push(subr); subrs.push(subr);
@ -1068,7 +1040,7 @@ var Type1Parser = function() {
} else if (inGlyphs && c == 0x52) { } else if (inGlyphs && c == 0x52) {
length = parseInt(length); length = parseInt(length);
var data = eexecString.slice(i + 3, i + 3 + length); var data = eexecString.slice(i + 3, i + 3 + length);
var encodedCharstring = decrypt(data, kCharStringsEncryptionKey, 4, true); var encodedCharstring = decrypt(data, kCharStringsEncryptionKey, 4);
var subr = decodeCharString(encodedCharstring); var subr = decodeCharString(encodedCharstring);
glyphs.push({ glyphs.push({
@ -1104,36 +1076,37 @@ var Type1Parser = function() {
} }
}; };
var CFF = function(aFontName, aFontBBox, aFontFile) { /**
* Take a Type1 file as input and wrap it into a Compact Font Format (CFF)
* wrapping Type2 charstrings.
*/
var CFF = function(aName, aFile, aProperties) {
// Get the data block containing glyphs and subrs informations // Get the data block containing glyphs and subrs informations
var length1 = aFontFile.dict.get("Length1"); var length1 = aFile.dict.get("Length1");
var length2 = aFontFile.dict.get("Length2"); var length2 = aFile.dict.get("Length2");
aFontFile.skip(length1); aFile.skip(length1);
var eexecBlock = aFontFile.getBytes(length2); var eexecBlock = aFile.getBytes(length2);
// Extract informations from it // Decrypt the data blocks and retrieve the informations from it
var start = Date.now();
var parser = new Type1Parser(); var parser = new Type1Parser();
var fontInfo = parser.extractFontInfo(eexecBlock); var fontInfo = parser.extractFontProgram(eexecBlock);
fontInfo.name = aFontName;
fontInfo.bbox = aFontBBox;
// XXX This hold the glyph data as if, this should be improved aProperties.subrs = fontInfo.subrs;
this.glyphs = fontInfo.charstrings; aProperties.glyphs = fontInfo.charstrings;
this.data = this.wrap(aName, aProperties);
this.data = this.convertToCFF(fontInfo);
var end = Date.now();
}; };
CFF.prototype = { CFF.prototype = {
createCFFIndexHeader: function(aObjects, aIsByte) { createCFFIndexHeader: function(aObjects, aIsByte) {
var data = [];
// 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 = aObjects.length; var count = aObjects.length;
if (count ==0)
// If there is no object, just create an array saying that with another
// offset byte.
if (count == 0)
return [0x00, 0x00, 0x00]; return [0x00, 0x00, 0x00];
var data = [];
var bytes = FontsUtils.integerToBytes(count, 2); var bytes = FontsUtils.integerToBytes(count, 2);
for (var i = 0; i < bytes.length; i++) for (var i = 0; i < bytes.length; i++)
data.push(bytes[i]); data.push(bytes[i]);
@ -1164,10 +1137,9 @@ CFF.prototype = {
encodeNumber: function(aValue) { encodeNumber: function(aValue) {
var x = 0; var x = 0;
if (aValue >= -32768 && aValue <= 32767) { if (aValue >= -32768 && aValue <= 32767) {
return [ 28, aValue >> 8, aValue ]; return [ 28, aValue >> 8, aValue & 0xFF ];
} else if (aValue >= (-2147483647-1) && aValue <= 2147483647) { } else if (aValue >= (-2147483647-1) && aValue <= 2147483647) {
return [ return [ 0xFF, aValue >> 24, Value >> 16, aValue >> 8, aValue & 0xFF ];
0xFF, aValue >> 24, Value >> 16, aValue >> 8, aValue ];
} else { } else {
error("Value: " + aValue + " is not allowed"); error("Value: " + aValue + " is not allowed");
} }
@ -1220,7 +1192,6 @@ CFF.prototype = {
}, },
flattenCharstring: function(aGlyph, aCharstring, aSubrs) { flattenCharstring: function(aGlyph, aCharstring, aSubrs) {
var original = aCharstring.slice();
var i = 0; var i = 0;
while (true) { while (true) {
var obj = aCharstring[i]; var obj = aCharstring[i];
@ -1324,17 +1295,10 @@ CFF.prototype = {
error("failing with i = " + i + " in charstring:" + aCharstring + "(" + aCharstring.length + ")"); error("failing with i = " + i + " in charstring:" + aCharstring + "(" + aCharstring.length + ")");
}, },
convertToCFF: function(aFontInfo) { wrap: function(aName, aProperties) {
var debug = false; var charstrings = this.getOrderedCharStrings(aProperties.glyphs);
function dump(aMsg) {
if (debug)
log(aMsg);
};
var charstrings = this.getOrderedCharStrings(aFontInfo.charstrings);
// Starts the conversion of the Type1 charstrings to Type2 // Starts the conversion of the Type1 charstrings to Type2
var start = Date.now();
var charstringsCount = 0; var charstringsCount = 0;
var charstringsDataLength = 0; var charstringsDataLength = 0;
var glyphs = []; var glyphs = [];
@ -1342,16 +1306,12 @@ CFF.prototype = {
var charstring = charstrings[i].charstring.slice(); var charstring = charstrings[i].charstring.slice();
var glyph = charstrings[i].glyph; var glyph = charstrings[i].glyph;
var flattened = this.flattenCharstring(glyph, charstring, aFontInfo.subrs); var flattened = this.flattenCharstring(glyph, charstring, aProperties.subrs);
glyphs.push(flattened); glyphs.push(flattened);
charstringsCount++; charstringsCount++;
charstringsDataLength += flattened.length; charstringsDataLength += flattened.length;
} }
var end = Date.now();
dump("There is " + charstringsCount + " glyphs (size: " + charstringsDataLength + ")");
dump("Time to flatten the strings is : " + (end -start));
// Create a CFF font data // Create a CFF font data
var cff = new Uint8Array(kMaxFontFileSize); var cff = new Uint8Array(kMaxFontFileSize);
var currentOffset = 0; var currentOffset = 0;
@ -1362,7 +1322,7 @@ CFF.prototype = {
cff.set(header); cff.set(header);
// Names Index // Names Index
var nameIndex = this.createCFFIndexHeader([aFontInfo.name]); var nameIndex = this.createCFFIndexHeader([aName]);
cff.set(nameIndex, currentOffset); cff.set(nameIndex, currentOffset);
currentOffset += nameIndex.length; currentOffset += nameIndex.length;
@ -1406,7 +1366,7 @@ CFF.prototype = {
248, 31, 4 // Weight 248, 31, 4 // Weight
]; ];
var fontBBox = aFontInfo.bbox; var fontBBox = aProperties.bbox;
for (var i = 0; i < fontBBox.length; i++) for (var i = 0; i < fontBBox.length; i++)
topDictIndex = topDictIndex.concat(this.encodeNumber(fontBBox[i])); topDictIndex = topDictIndex.concat(this.encodeNumber(fontBBox[i]));
topDictIndex.push(5) // FontBBox; topDictIndex.push(5) // FontBBox;

82
pdf.js
View File

@ -1729,45 +1729,57 @@ var CanvasGraphics = (function() {
var fontName = descriptor.get("FontName").name; var fontName = descriptor.get("FontName").name;
fontName = fontName.replace("+", "_"); fontName = fontName.replace("+", "_");
var font = Fonts[fontName]; var fontFile = descriptor.get2("FontFile", "FontFile2");
if (!font) { if (!fontFile)
var fontFile = descriptor.get2("FontFile", "FontFile2"); errort("FontFile not found for font: " + fontName);
fontFile = xref.fetchIfRef(fontFile); fontFile = xref.fetchIfRef(fontFile);
// Generate the custom cmap of the font if needed // Generate the custom cmap of the font if needed
var encodingMap = {}; var encodingMap = {};
if (fontDict.has("Encoding")) { if (fontDict.has("Encoding")) {
var encoding = xref.fetchIfRef(fontDict.get("Encoding")); var encoding = xref.fetchIfRef(fontDict.get("Encoding"));
if (IsDict(encoding)) { if (IsDict(encoding)) {
// Build an map between codes and glyphs // Build an map between codes and glyphs
var differences = encoding.get("Differences"); var differences = encoding.get("Differences");
var index = 0; var index = 0;
for (var j = 0; j < differences.length; j++) { for (var j = 0; j < differences.length; j++) {
var data = differences[j]; var data = differences[j];
IsNum(data) ? index = data : encodingMap[index++] = data; IsNum(data) ? index = data : encodingMap[index++] = data;
} }
// Get the font charset // Get the font charset if any
var charset = descriptor.get("CharSet").split("/"); var charset = descriptor.get("CharSet");
} else if (IsName(encoding)) { if (charset)
var encoding = Encodings[encoding]; charset = charset.split("/");
var widths = xref.fetchIfRef(fontDict.get("Widths"));
var firstchar = xref.fetchIfRef(fontDict.get("FirstChar"));
var charset = []; } else if (IsName(encoding)) {
for (var j = 0; j < widths.length; j++) { var encoding = Encodings[encoding];
var index = widths[j]; var widths = xref.fetchIfRef(fontDict.get("Widths"));
if (index) var firstchar = xref.fetchIfRef(fontDict.get("FirstChar"));
charset.push(encoding[j + firstchar]);
}
}
}
var fontBBox = descriptor.get("FontBBox"); var charset = [];
var subtype = fontDict.get("Subtype").name; for (var j = 0; j < widths.length; j++) {
new Font(fontName, fontFile, encodingMap, charset, fontBBox, subtype); var index = widths[j];
} if (!index)
return Fonts[fontName]; continue;
charset.push(encoding[j + firstchar]);
}
}
}
var properties = {
type: fontDict.get("Subtype").name,
encoding: encodingMap,
charset: charset,
bbox: descriptor.get("FontBBox")
};
return {
name: fontName,
file: fontFile,
properties: properties
}
}, },
beginDrawing: function(mediaBox) { beginDrawing: function(mediaBox) {

55
test.js
View File

@ -74,30 +74,43 @@ function displayPage(num) {
page.compile(gfx, fonts); page.compile(gfx, fonts);
var t2 = Date.now(); var t2 = Date.now();
var interval = 0; var fontsReady = true;
for (var i = 0; i < fonts.length; i++) {
if (fonts[i].loading) { // Inspect fonts and translate the missing one
interval = 10; var count = fonts.length;
break; 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;
}
clearInterval(pageInterval);
var t3 = Date.now();
page.display(gfx);
var t4 = Date.now();
var infoDisplay = document.getElementById("info");
infoDisplay.innerHTML = "Time to load/compile/fonts/render: "+ (t1 - t0) + "/" + (t2 - t1) + "/" + (t3 - t2) + "/" + (t4 - t3) + " ms";
}; };
// FIXME This need to be replaced by an event if (fontsReady) {
pageInterval = setInterval(function() { delayLoadFont();
for (var i = 0; i < fonts.length; i++) { } else {
if (fonts[i].loading) pageInterval = setInterval(delayLoadFont, 10);
return; }
}
var t3 = Date.now();
clearInterval(pageInterval);
page.display(gfx);
var t4 = Date.now();
var infoDisplay = document.getElementById("info");
infoDisplay.innerHTML = "Time to load/compile/fonts/render: "+ (t1 - t0) + "/" + (t2 - t1) + "/" + (t3 - t2) + "/" + (t4 - t3) + " ms";
}, interval);
} }
function nextPage() { function nextPage() {