Add a basic support for TrueType (generate fonts with OS/2 table)
This commit is contained in:
parent
bdf8577c48
commit
32118c5650
233
PDFFont.js
233
PDFFont.js
@ -581,11 +581,213 @@ var FontsUtils = {
|
|||||||
*/
|
*/
|
||||||
var TrueType = function(aFile) {
|
var TrueType = function(aFile) {
|
||||||
var header = this._readOpenTypeHeader(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 = {
|
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 {
|
return {
|
||||||
version: aFile.getBytes(4),
|
version: aFile.getBytes(4),
|
||||||
numTables: FontsUtils.bytesToInteger(aFile.getBytes(2)),
|
numTables: FontsUtils.bytesToInteger(aFile.getBytes(2)),
|
||||||
@ -593,6 +795,33 @@ TrueType.prototype = {
|
|||||||
entrySelector: FontsUtils.bytesToInteger(aFile.getBytes(2)),
|
entrySelector: FontsUtils.bytesToInteger(aFile.getBytes(2)),
|
||||||
rangeShift: 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
18
pdf.js
@ -52,7 +52,7 @@ var Stream = (function() {
|
|||||||
this.bytes = new Uint8Array(arrayBuffer);
|
this.bytes = new Uint8Array(arrayBuffer);
|
||||||
this.start = start || 0;
|
this.start = start || 0;
|
||||||
this.pos = this.start;
|
this.pos = this.start;
|
||||||
this.end = (start + length) || arrayBuffer.byteLength;
|
this.end = (start + length) || this.bytes.byteLength;
|
||||||
this.dict = dict;
|
this.dict = dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1411,11 +1411,13 @@ var Page = (function() {
|
|||||||
var resources = xref.fetchIfRef(this.resources);
|
var resources = xref.fetchIfRef(this.resources);
|
||||||
var fontsDict = new Dict();
|
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");
|
var fontResources = resources.get("Font");
|
||||||
fontResources.forEach(function(fontKey, fontData) {
|
if (IsDict(fontResources)) {
|
||||||
fontsDict.set(fontKey, xref.fetch(fontData))
|
fontResources.forEach(function(fontKey, fontData) {
|
||||||
});
|
fontsDict.set(fontKey, xref.fetch(fontData))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Get the fonts use on xobjects of the page if any
|
// Get the fonts use on xobjects of the page if any
|
||||||
var xobjs = xref.fetchIfRef(resources.get("XObject"));
|
var xobjs = xref.fetchIfRef(resources.get("XObject"));
|
||||||
@ -1864,7 +1866,11 @@ var CanvasGraphics = (function() {
|
|||||||
this.current.leading = leading;
|
this.current.leading = leading;
|
||||||
},
|
},
|
||||||
setFont: function(fontRef, size) {
|
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);
|
font = this.xref.fetchIfRef(font);
|
||||||
if (!font)
|
if (!font)
|
||||||
return;
|
return;
|
||||||
|
4
test.js
4
test.js
@ -85,7 +85,9 @@ function displayPage(num) {
|
|||||||
// Check if the font has been loaded or is still loading
|
// Check if the font has been loaded or is still loading
|
||||||
var font = Fonts[fontName];
|
var font = Fonts[fontName];
|
||||||
if (!font) {
|
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
|
// Generate the custom cmap of the font if needed
|
||||||
var encodingMap = {};
|
var encodingMap = {};
|
||||||
if (fontDict.has("Encoding")) {
|
if (fontDict.has("Encoding")) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user