Browse Source

Support font with characters below 0x20 declared in format 1 cmap and be more general about the TrueType rewritting cases

Vivien Nicolas 14 years ago
parent
commit
10e9694135
  1. 277
      fonts.js
  2. 1
      web/viewer.html

277
fonts.js

@ -372,7 +372,7 @@ function getUnicodeRangeFor(value) {
* var type1Font = new Font("MyFontName", binaryFile, propertiesObject); * var type1Font = new Font("MyFontName", binaryFile, propertiesObject);
* type1Font.bind(); * type1Font.bind();
*/ */
var Font = (function() { var Font = (function Font() {
var constructor = function font_constructor(name, file, properties) { var constructor = function font_constructor(name, file, properties) {
this.name = name; this.name = name;
this.textMatrix = properties.textMatrix || IDENTITY_MATRIX; this.textMatrix = properties.textMatrix || IDENTITY_MATRIX;
@ -531,6 +531,19 @@ var Font = (function() {
} }
ranges.push([start, end]); ranges.push([start, end]);
} }
// Removes duplicate ranges
for (var i = ranges.length - 1; i > 0; i--) {
var range = [];
var range1 = ranges[i];
var range2 = ranges[i - 1];
if (range1[0] == range2[1]) {
range2[0] = range2[0] - 1;
range2[1] = range1[1];
ranges.splice(i, 1);
}
}
return ranges; return ranges;
}; };
@ -790,21 +803,57 @@ var Font = (function() {
}; };
function replaceCMapTable(cmap, font, properties) { function replaceCMapTable(cmap, font, properties) {
font.pos = (font.start ? font.start : 0) + cmap.length; var start = (font.start ? font.start : 0) + cmap.length;
font.pos = start;
var version = int16(font.getBytes(2)); var version = int16(font.getBytes(2));
var numTables = int16(font.getBytes(2)); var numRecords = int16(font.getBytes(2));
var records = [];
for (var i = 0; i < numRecords; i++) {
records.push({
platformID: int16(font.getBytes(2)),
encodingID: int16(font.getBytes(2)),
offset: int32(font.getBytes(4))
});
};
for (var i = 0; i < numTables; i++) {
var platformID = int16(font.getBytes(2)); for (var i = 0; i < numRecords; i++) {
var encodingID = int16(font.getBytes(2)); var table = records[i];
var offset = int32(font.getBytes(4)); font.pos = start + table.offset;
var format = int16(font.getBytes(2)); var format = int16(font.getBytes(2));
var length = int16(font.getBytes(2)); var length = int16(font.getBytes(2));
var language = int16(font.getBytes(2)); var language = int16(font.getBytes(2));
if ((format == 0 && numTables == 1) || if (format == 0 && numRecords > 1) {
(format == 6 && numTables == 1 && !properties.encoding.empty)) { // Characters below 0x20 are controls characters that are hardcoded
// into the platform so if some characters in the font are assigned
// under this limit they will not be displayed so let's rewrite the
// CMap.
var map = [];
var rewrite = false;
for (var j = 0; j < 256; j++) {
var index = font.getByte();
if (index != 0) {
map.push(index);
if (j < 0x20)
rewrite = true;
}
}
if (rewrite) {
var glyphs = [];
for (var j = 0x20; j < 256; j++) {
// TODO do not hardcode WinAnsiEncoding
var unicode = GlyphsUnicode[Encodings["WinAnsiEncoding"][j]];
glyphs.push({ unicode: unicode });
}
cmap.data = createCMapTable(glyphs, true);
}
} else if ((format == 0 && numRecords == 1) ||
(format == 6 && numRecords == 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 ' + TODO('Use an other source of informations than ' +
@ -818,7 +867,7 @@ var Font = (function() {
} }
cmap.data = createCMapTable(glyphs); cmap.data = createCMapTable(glyphs);
} else if (format == 6 && numTables == 1) { } else if (format == 6 && numRecords == 1) {
// Format 6 is a 2-bytes dense mapping, which means the font data // Format 6 is a 2-bytes dense mapping, which means the font data
// lives glue together even if they are pretty far in the unicode // lives glue together even if they are pretty far in the unicode
// table. (This looks weird, so I can have missed something), this // table. (This looks weird, so I can have missed something), this
@ -871,10 +920,7 @@ var Font = (function() {
var header = readOpenTypeHeader(font); var header = readOpenTypeHeader(font);
var numTables = header.numTables; var numTables = header.numTables;
// This keep a reference to the CMap and the post tables since they can var cmap, maxp;
// be rewritted
var cmap, post, nameTable, maxp;
var tables = []; var tables = [];
for (var i = 0; i < numTables; i++) { for (var i = 0; i < numTables; i++) {
var table = readTableEntry(font); var table = readTableEntry(font);
@ -882,10 +928,6 @@ var Font = (function() {
if (index != -1) { if (index != -1) {
if (table.tag == 'cmap') if (table.tag == 'cmap')
cmap = table; cmap = table;
else if (table.tag == 'post')
post = table;
else if (table.tag == 'name')
nameTable = table;
else if (table.tag == 'maxp') else if (table.tag == 'maxp')
maxp = table; maxp = table;
@ -894,136 +936,127 @@ var Font = (function() {
tables.push(table); tables.push(table);
} }
// If any tables are still in the array this means some required // Create a new file to hold the new version of our truetype with a new
// tables are missing, which means that we need to rebuild the // header and new offsets
// font in order to pass the sanitizer. var ttf = new Uint8Array(kMaxFontFileSize);
if (requiredTables.length && requiredTables[0] == 'OS/2') {
// Create a new file to hold the new version of our truetype with a new // The offsets object holds at the same time a representation of where
// header and new offsets // to write the table entry information about a table and another offset
var ttf = new Uint8Array(kMaxFontFileSize); // representing the offset where to put the actual data of a particular
// table
// The offsets object holds at the same time a representation of where var numTables = header.numTables + requiredTables.length;
// to write the table entry information about a table and another offset var offsets = {
// representing the offset where to put the actual data of a particular currentOffset: 0,
// table virtualOffset: numTables * (4 * 4)
var numTables = header.numTables + requiredTables.length; };
var offsets = {
currentOffset: 0,
virtualOffset: numTables * (4 * 4)
};
// The new numbers of tables will be the last one plus the num // The new numbers of tables will be the last one plus the num
// of missing tables // of missing tables
createOpenTypeHeader('\x00\x01\x00\x00', ttf, offsets, numTables); createOpenTypeHeader('\x00\x01\x00\x00', ttf, offsets, numTables);
// Insert the missing table if (requiredTables.indexOf('OS/2') != -1) {
tables.push({ tables.push({
tag: 'OS/2', tag: 'OS/2',
data: stringToArray(createOS2Table(properties)) data: stringToArray(createOS2Table(properties))
}); });
}
// Replace the old CMAP table with a shiny new one // Replace the old CMAP table with a shiny new one
if (properties.type == 'CIDFontType2') { if (properties.type == 'CIDFontType2') {
// Type2 composite fonts map characters directly to glyphs so the cmap // Type2 composite fonts map characters directly to glyphs so the cmap
// table must be replaced. // table must be replaced.
var glyphs = []; var glyphs = [];
var charset = properties.charset; var charset = properties.charset;
if (!charset.length) { if (!charset.length) {
// PDF did not contain a GIDMap for the font so create an identity cmap // PDF did not contain a GIDMap for the font so create an identity cmap
// First get the number of glyphs from the maxp table // First get the number of glyphs from the maxp table
font.pos = (font.start ? font.start : 0) + maxp.length; font.pos = (font.start ? font.start : 0) + maxp.length;
var version = int16(font.getBytes(4)); var version = int16(font.getBytes(4));
var numGlyphs = int16(font.getBytes(2)); var numGlyphs = int16(font.getBytes(2));
// Now create an identity mapping // Now create an identity mapping
for (var i = 1; i < numGlyphs; i++) { for (var i = 1; i < numGlyphs; i++) {
glyphs.push({
unicode: i
});
}
} else {
for (var i = 1; i < charset.length; i++) {
var index = charset.indexOf(i);
if (index != -1) {
glyphs.push({ glyphs.push({
unicode: i unicode: index
}); });
} } else {
} else { break;
for (var i = 1; i < charset.length; i++) {
var index = charset.indexOf(i);
if (index != -1) {
glyphs.push({
unicode: index
});
} else {
break;
}
} }
} }
if (!cmap) {
// Font did not contain a cmap
tables.push({
tag: 'cmap',
data: createCMapTable(glyphs)
})
} else {
cmap.data = createCMapTable(glyphs);
}
} else {
replaceCMapTable(cmap, font, properties);
}
// Rewrite the 'post' table if needed
if (!post) {
tables.push({
tag: 'post',
data: stringToArray(createPostTable(properties))
});
} }
// Rewrite the 'name' table if needed if (!cmap) {
if (!nameTable) { // Font did not contain a cmap
tables.push({ tables.push({
tag: 'name', tag: 'cmap',
data: stringToArray(createNameTable(this.name)) data: createCMapTable(glyphs)
}); })
} else {
cmap.data = createCMapTable(glyphs);
} }
} else {
replaceCMapTable(cmap, font, properties);
}
// Tables needs to be written by ascendant alphabetic order // Rewrite the 'post' table if needed
tables.sort(function tables_sort(a, b) { if (requiredTables.indexOf('post') != -1) {
return (a.tag > b.tag) - (a.tag < b.tag); tables.push({
tag: 'post',
data: stringToArray(createPostTable(properties))
}); });
}
// rewrite the tables but tweak offsets // Rewrite the 'name' table if needed
for (var i = 0; i < tables.length; i++) { if (requiredTables.indexOf('name') != -1) {
var table = tables[i]; tables.push({
var data = []; tag: 'name',
data: stringToArray(createNameTable(this.name))
});
}
var tableData = table.data; // Tables needs to be written by ascendant alphabetic order
for (var j = 0; j < tableData.length; j++) tables.sort(function tables_sort(a, b) {
data.push(tableData[j]); return (a.tag > b.tag) - (a.tag < b.tag);
createTableEntry(ttf, offsets, table.tag, data); });
}
// Add the table datas // rewrite the tables but tweak offsets
for (var i = 0; i < tables.length; i++) { for (var i = 0; i < tables.length; i++) {
var table = tables[i]; var table = tables[i];
var tableData = table.data; var data = [];
ttf.set(tableData, offsets.currentOffset);
offsets.currentOffset += tableData.length;
// 4-byte aligned data var tableData = table.data;
while (offsets.currentOffset & 3) for (var j = 0; j < tableData.length; j++)
offsets.currentOffset++; data.push(tableData[j]);
} createTableEntry(ttf, offsets, table.tag, data);
}
var fontData = []; // Add the table datas
for (var i = 0; i < offsets.currentOffset; i++) for (var i = 0; i < tables.length; i++) {
fontData.push(ttf[i]); var table = tables[i];
var tableData = table.data;
ttf.set(tableData, offsets.currentOffset);
offsets.currentOffset += tableData.length;
return fontData; // 4-byte aligned data
} else if (requiredTables.length) { while (offsets.currentOffset & 3)
error('Table ' + requiredTables[0] + offsets.currentOffset++;
' is missing from the TrueType font');
} }
return font.getBytes(); var fontData = [];
for (var i = 0; i < offsets.currentOffset; i++)
fontData.push(ttf[i]);
return fontData;
}, },
convert: function font_convert(fontName, font, properties) { convert: function font_convert(fontName, font, properties) {

1
web/viewer.html

@ -8,6 +8,7 @@
<script type="text/javascript" src="viewer.js"></script> <script type="text/javascript" src="viewer.js"></script>
<script type="text/javascript" src="../pdf.js"></script> <script type="text/javascript" src="../pdf.js"></script>
<script type="text/javascript" src="../fonts.js"></script> <script type="text/javascript" src="../fonts.js"></script>
<script type="text/javascript" src="../utils/fonts_utils.js"></script>
<script type="text/javascript" src="../crypto.js"></script> <script type="text/javascript" src="../crypto.js"></script>
<script type="text/javascript" src="../glyphlist.js"></script> <script type="text/javascript" src="../glyphlist.js"></script>
</head> </head>

Loading…
Cancel
Save