Add a basic support for TrueType (generate fonts with OS/2 table)

This commit is contained in:
Vivien Nicolas 2011-06-15 04:46:48 +02:00
parent c8c4326ca8
commit 1dcd42b66c
3 changed files with 246 additions and 9 deletions

View File

@ -581,11 +581,213 @@ var FontsUtils = {
*/
var TrueType = function(aFile) {
var header = this._readOpenTypeHeader(aFile);
this.data = 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.sort(function(a, b) {
return a.tag > b.tag;
});
// If any tables are still in the array this means some required tables are
// missing, which means that we need to rebuild the font in order to pass
// the sanitizer.
if (requiredTables.length && requiredTables[0] == "OS/2") {
OS2 = [
0x00, 0x03, // version
0x02, 0x24, // xAvgCharWidth
0x01, 0xF4, // usWeightClass
0x00, 0x05, // usWidthClass
0x00, 0x00, // fstype
0x02, 0x8A, // ySubscriptXSize
0x02, 0xBB, // ySubscriptYSize
0x00, 0x00, // ySubscriptXOffset
0x00, 0x8C, // ySubscriptYOffset
0x02, 0x8A, // ySuperScriptXSize
0x02, 0xBB, // ySuperScriptYSize
0x00, 0x00, // ySuperScriptXOffset
0x01, 0xDF, // ySuperScriptYOffset
0x00, 0x31, // yStrikeOutSize
0x01, 0x02, // yStrikeOutPosition
0x00, 0x00, // sFamilyClass
0x02, 0x00, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Panose
0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 0-31)
0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 32-63)
0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 64-95)
0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 96-127)
0x2A, 0x32, 0x31, 0x2A, // achVendID
0x00, 0x20, // fsSelection
0x00, 0x2D, // usFirstCharIndex
0x00, 0x7A, // usLastCharIndex
0x00, 0x03, // sTypoAscender
0x00, 0x20, // sTypeDescender
0x00, 0x38, // sTypoLineGap
0x00, 0x5A, // usWinAscent
0x02, 0xB4, // usWinDescent
0x00, 0xCE, 0x00, 0x00, // ulCodePageRange1 (Bits 0-31)
0x00, 0x01, 0x00, 0x00, // ulCodePageRange2 (Bits 32-63)
0x00, 0x00, // sxHeight
0x00, 0x00, // sCapHeight
0x00, 0x01, // usDefaultChar
0x00, 0xCD, // usBreakChar
0x00, 0x02 // usMaxContext
];
// Create a new file to hold the new version of our truetype with a new
// header and new offsets
var stream = aFile.stream || aFile;
var ttf = new Uint8Array(stream.length + 16 + OS2.length);
// The new numbers of tables will be the last one plus the num of missing
// tables
var numTables = header.numTables + 1;
// The offsets object holds at the same time a representation of where
// to write the table entry information about a table and another offset
// representing the offset where to draw the actual data of a particular
// table
var offsets = {
currentOffset: 0,
virtualOffset: numTables * (4 * 4)
};
// Write the sfnt header with one more table
this._createOpenTypeHeader(ttf, offsets, numTables);
// Insert the missing table
tables.unshift({
tag: "OS/2",
data: OS2
});
// rewrite the tables but tweak offsets
for (var i = 0; i < tables.length; i++) {
var table = tables[i];
var data = [];
var tableData = table.data;
for (var j = 0; j < tableData.length; j++)
data.push(tableData[j]);
this._createTableEntry(ttf, offsets, table.tag, data);
}
// Add the table datas
for (var i = 0; i < tables.length; i++) {
var table = tables[i];
var tableData = table.data;
ttf.set(tableData, offsets.currentOffset);
offsets.currentOffset += tableData.length;
if (0) {
var data = [];
for (var j = 0; j < tableData.length; j++)
d.push(tableData[j]);
log("data for table: " + table.tag + ": " + data);
}
// 4-byte aligned data
while (offsets.currentOffset & 3)
offsets.currentOffset++;
}
var fontData = [];
for (var i = 0; i < ttf.length; i++)
fontData.push(ttf[i]);
this.data = ttf;
//writeToFile(fontData, "/tmp/pdf.js." + fontCount + ".ttf");
return;
} else if (requiredTables.lenght) {
error("Table " + requiredTables[0] + " is missing from the TruType font");
} else {
this.data = aFile;
}
};
TrueType.prototype = {
_readOpenTypeHeader: function(aFile) {
_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)),
@ -593,6 +795,33 @@ TrueType.prototype = {
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
}
}
};

18
pdf.js
View File

@ -52,7 +52,7 @@ var Stream = (function() {
this.bytes = new Uint8Array(arrayBuffer);
this.start = start || 0;
this.pos = this.start;
this.end = (start + length) || arrayBuffer.byteLength;
this.end = (start + length) || this.bytes.byteLength;
this.dict = dict;
}
@ -1411,11 +1411,13 @@ var Page = (function() {
var resources = xref.fetchIfRef(this.resources);
var fontsDict = new Dict();
// Get the fonts use on the page
// Get the fonts use on the page if any
var fontResources = resources.get("Font");
fontResources.forEach(function(fontKey, fontData) {
fontsDict.set(fontKey, xref.fetch(fontData))
});
if (IsDict(fontResources)) {
fontResources.forEach(function(fontKey, fontData) {
fontsDict.set(fontKey, xref.fetch(fontData))
});
}
// Get the fonts use on xobjects of the page if any
var xobjs = xref.fetchIfRef(resources.get("XObject"));
@ -1864,7 +1866,11 @@ var CanvasGraphics = (function() {
this.current.leading = leading;
},
setFont: function(fontRef, size) {
var font = this.res.get("Font").get(fontRef.name);
var font = this.res.get("Font");
if (!IsDict(font))
return;
font = font.get(fontRef.name);
font = this.xref.fetchIfRef(font);
if (!font)
return;

View File

@ -85,7 +85,9 @@ function displayPage(num) {
// Check if the font has been loaded or is still loading
var font = Fonts[fontName];
if (!font) {
var fontFile = xref.fetchIfRef(descriptor.get2("FontFile", "FontFile2"));
var fontFile = descriptor.get2("FontFile", "FontFile2");
fontFile = xref.fetchIfRef(fontFile);
// Generate the custom cmap of the font if needed
var encodingMap = {};
if (fontDict.has("Encoding")) {