Merge pull request #262 from ironymark/CIDFont
First Pass CIDFont support
This commit is contained in:
commit
bbc940724f
215
fonts.js
215
fonts.js
@ -7,7 +7,7 @@ var isWorker = (typeof window == 'undefined');
|
|||||||
/**
|
/**
|
||||||
* Maximum file size of the font.
|
* Maximum file size of the font.
|
||||||
*/
|
*/
|
||||||
var kMaxFontFileSize = 40000;
|
var kMaxFontFileSize = 200000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum time to wait for a font to be loaded by font-face rules.
|
* Maximum time to wait for a font to be loaded by font-face rules.
|
||||||
@ -389,6 +389,7 @@ var Font = (function() {
|
|||||||
var data;
|
var data;
|
||||||
switch (properties.type) {
|
switch (properties.type) {
|
||||||
case 'Type1':
|
case 'Type1':
|
||||||
|
case 'CIDFontType0':
|
||||||
var cff = new CFF(name, file, properties);
|
var cff = new CFF(name, file, properties);
|
||||||
this.mimetype = 'font/opentype';
|
this.mimetype = 'font/opentype';
|
||||||
|
|
||||||
@ -397,6 +398,7 @@ var Font = (function() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'TrueType':
|
case 'TrueType':
|
||||||
|
case 'CIDFontType2':
|
||||||
this.mimetype = 'font/opentype';
|
this.mimetype = 'font/opentype';
|
||||||
|
|
||||||
// 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
|
||||||
@ -409,9 +411,10 @@ var Font = (function() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
this.type = properties.type;
|
||||||
this.id = Fonts.registerFont(name, data, properties);
|
this.id = Fonts.registerFont(name, data, properties);
|
||||||
this.loadedName = 'pdfFont' + this.id;
|
this.loadedName = 'pdfFont' + this.id;
|
||||||
|
this.compositeFont = properties.compositeFont;
|
||||||
};
|
};
|
||||||
|
|
||||||
function stringToArray(str) {
|
function stringToArray(str) {
|
||||||
@ -679,6 +682,65 @@ var Font = (function() {
|
|||||||
'\x00\x00\x00\x00'; // maxMemType1
|
'\x00\x00\x00\x00'; // maxMemType1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function createNameTable(name) {
|
||||||
|
var strings = [
|
||||||
|
'Original licence', // 0.Copyright
|
||||||
|
name, // 1.Font family
|
||||||
|
'Unknown', // 2.Font subfamily (font weight)
|
||||||
|
'uniqueID', // 3.Unique ID
|
||||||
|
name, // 4.Full font name
|
||||||
|
'Version 0.11', // 5.Version
|
||||||
|
'', // 6.Postscript name
|
||||||
|
'Unknown', // 7.Trademark
|
||||||
|
'Unknown', // 8.Manufacturer
|
||||||
|
'Unknown' // 9.Designer
|
||||||
|
];
|
||||||
|
|
||||||
|
// Mac want 1-byte per character strings while Windows want
|
||||||
|
// 2-bytes per character, so duplicate the names table
|
||||||
|
var stringsUnicode = [];
|
||||||
|
for (var i = 0; i < strings.length; i++) {
|
||||||
|
var str = strings[i];
|
||||||
|
|
||||||
|
var strUnicode = '';
|
||||||
|
for (var j = 0; j < str.length; j++)
|
||||||
|
strUnicode += string16(str.charCodeAt(j));
|
||||||
|
stringsUnicode.push(strUnicode);
|
||||||
|
}
|
||||||
|
|
||||||
|
var names = [strings, stringsUnicode];
|
||||||
|
var platforms = ['\x00\x01', '\x00\x03'];
|
||||||
|
var encodings = ['\x00\x00', '\x00\x01'];
|
||||||
|
var languages = ['\x00\x00', '\x04\x09'];
|
||||||
|
|
||||||
|
var namesRecordCount = strings.length * platforms.length;
|
||||||
|
var nameTable =
|
||||||
|
'\x00\x00' + // format
|
||||||
|
string16(namesRecordCount) + // Number of names Record
|
||||||
|
string16(namesRecordCount * 12 + 6); // Storage
|
||||||
|
|
||||||
|
// Build the name records field
|
||||||
|
var strOffset = 0;
|
||||||
|
for (var i = 0; i < platforms.length; i++) {
|
||||||
|
var strs = names[i];
|
||||||
|
for (var j = 0; j < strs.length; j++) {
|
||||||
|
var str = strs[j];
|
||||||
|
var nameRecord =
|
||||||
|
platforms[i] + // platform ID
|
||||||
|
encodings[i] + // encoding ID
|
||||||
|
languages[i] + // language ID
|
||||||
|
string16(j) + // name ID
|
||||||
|
string16(str.length) +
|
||||||
|
string16(strOffset);
|
||||||
|
nameTable += nameRecord;
|
||||||
|
strOffset += str.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nameTable += strings.join('') + stringsUnicode.join('');
|
||||||
|
return nameTable;
|
||||||
|
}
|
||||||
|
|
||||||
constructor.prototype = {
|
constructor.prototype = {
|
||||||
name: null,
|
name: null,
|
||||||
font: null,
|
font: null,
|
||||||
@ -811,7 +873,7 @@ var Font = (function() {
|
|||||||
|
|
||||||
// This keep a reference to the CMap and the post tables since they can
|
// This keep a reference to the CMap and the post tables since they can
|
||||||
// be rewritted
|
// be rewritted
|
||||||
var cmap, post;
|
var cmap, post, nameTable, maxp;
|
||||||
|
|
||||||
var tables = [];
|
var tables = [];
|
||||||
for (var i = 0; i < numTables; i++) {
|
for (var i = 0; i < numTables; i++) {
|
||||||
@ -822,6 +884,10 @@ var Font = (function() {
|
|||||||
cmap = table;
|
cmap = table;
|
||||||
else if (table.tag == 'post')
|
else if (table.tag == 'post')
|
||||||
post = table;
|
post = table;
|
||||||
|
else if (table.tag == 'name')
|
||||||
|
nameTable = table;
|
||||||
|
else if (table.tag == 'maxp')
|
||||||
|
maxp = table;
|
||||||
|
|
||||||
requiredTables.splice(index, 1);
|
requiredTables.splice(index, 1);
|
||||||
}
|
}
|
||||||
@ -857,7 +923,51 @@ var Font = (function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Replace the old CMAP table with a shiny new one
|
// Replace the old CMAP table with a shiny new one
|
||||||
|
if (properties.type == 'CIDFontType2') {
|
||||||
|
// Type2 composite fonts map characters directly to glyphs so the cmap
|
||||||
|
// table must be replaced.
|
||||||
|
|
||||||
|
var glyphs = [];
|
||||||
|
var charset = properties.charset;
|
||||||
|
if (!charset.length) {
|
||||||
|
// PDF did not contain a GIDMap for the font so create an identity cmap
|
||||||
|
|
||||||
|
// First get the number of glyphs from the maxp table
|
||||||
|
font.pos = (font.start ? font.start : 0) + maxp.length;
|
||||||
|
var version = int16(font.getBytes(4));
|
||||||
|
var numGlyphs = int16(font.getBytes(2));
|
||||||
|
|
||||||
|
// Now create an identity mapping
|
||||||
|
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({
|
||||||
|
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);
|
replaceCMapTable(cmap, font, properties);
|
||||||
|
}
|
||||||
|
|
||||||
// Rewrite the 'post' table if needed
|
// Rewrite the 'post' table if needed
|
||||||
if (!post) {
|
if (!post) {
|
||||||
@ -867,6 +977,14 @@ var Font = (function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rewrite the 'name' table if needed
|
||||||
|
if (!nameTable) {
|
||||||
|
tables.push({
|
||||||
|
tag: 'name',
|
||||||
|
data: stringToArray(createNameTable(this.name))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Tables needs to be written by ascendant alphabetic order
|
// Tables needs to be written by ascendant alphabetic order
|
||||||
tables.sort(function tables_sort(a, b) {
|
tables.sort(function tables_sort(a, b) {
|
||||||
return (a.tag > b.tag) - (a.tag < b.tag);
|
return (a.tag > b.tag) - (a.tag < b.tag);
|
||||||
@ -909,65 +1027,6 @@ var Font = (function() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
convert: function font_convert(fontName, font, properties) {
|
convert: function font_convert(fontName, font, properties) {
|
||||||
function createNameTable(name) {
|
|
||||||
var strings = [
|
|
||||||
'Original licence', // 0.Copyright
|
|
||||||
name, // 1.Font family
|
|
||||||
'Unknown', // 2.Font subfamily (font weight)
|
|
||||||
'uniqueID', // 3.Unique ID
|
|
||||||
name, // 4.Full font name
|
|
||||||
'Version 0.11', // 5.Version
|
|
||||||
'', // 6.Postscript name
|
|
||||||
'Unknown', // 7.Trademark
|
|
||||||
'Unknown', // 8.Manufacturer
|
|
||||||
'Unknown' // 9.Designer
|
|
||||||
];
|
|
||||||
|
|
||||||
// Mac want 1-byte per character strings while Windows want
|
|
||||||
// 2-bytes per character, so duplicate the names table
|
|
||||||
var stringsUnicode = [];
|
|
||||||
for (var i = 0; i < strings.length; i++) {
|
|
||||||
var str = strings[i];
|
|
||||||
|
|
||||||
var strUnicode = '';
|
|
||||||
for (var j = 0; j < str.length; j++)
|
|
||||||
strUnicode += string16(str.charCodeAt(j));
|
|
||||||
stringsUnicode.push(strUnicode);
|
|
||||||
}
|
|
||||||
|
|
||||||
var names = [strings, stringsUnicode];
|
|
||||||
var platforms = ['\x00\x01', '\x00\x03'];
|
|
||||||
var encodings = ['\x00\x00', '\x00\x01'];
|
|
||||||
var languages = ['\x00\x00', '\x04\x09'];
|
|
||||||
|
|
||||||
var namesRecordCount = strings.length * platforms.length;
|
|
||||||
var nameTable =
|
|
||||||
'\x00\x00' + // format
|
|
||||||
string16(namesRecordCount) + // Number of names Record
|
|
||||||
string16(namesRecordCount * 12 + 6); // Storage
|
|
||||||
|
|
||||||
// Build the name records field
|
|
||||||
var strOffset = 0;
|
|
||||||
for (var i = 0; i < platforms.length; i++) {
|
|
||||||
var strs = names[i];
|
|
||||||
for (var j = 0; j < strs.length; j++) {
|
|
||||||
var str = strs[j];
|
|
||||||
var nameRecord =
|
|
||||||
platforms[i] + // platform ID
|
|
||||||
encodings[i] + // encoding ID
|
|
||||||
languages[i] + // language ID
|
|
||||||
string16(j) + // name ID
|
|
||||||
string16(str.length) +
|
|
||||||
string16(strOffset);
|
|
||||||
nameTable += nameRecord;
|
|
||||||
strOffset += str.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nameTable += strings.join('') + stringsUnicode.join('');
|
|
||||||
return nameTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFixedPitch(glyphs) {
|
function isFixedPitch(glyphs) {
|
||||||
for (var i = 0; i < glyphs.length - 1; i++) {
|
for (var i = 0; i < glyphs.length - 1; i++) {
|
||||||
if (glyphs[i] != glyphs[i + 1])
|
if (glyphs[i] != glyphs[i + 1])
|
||||||
@ -1110,23 +1169,44 @@ var Font = (function() {
|
|||||||
|
|
||||||
charsToUnicode: function fonts_chars2Unicode(chars) {
|
charsToUnicode: function fonts_chars2Unicode(chars) {
|
||||||
var charsCache = this.charsCache;
|
var charsCache = this.charsCache;
|
||||||
|
var str;
|
||||||
|
|
||||||
// if we translated this string before, just grab it from the cache
|
// if we translated this string before, just grab it from the cache
|
||||||
if (charsCache) {
|
if (charsCache) {
|
||||||
var str = charsCache[chars];
|
str = charsCache[chars];
|
||||||
if (str)
|
if (str)
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lazily create the translation cache
|
||||||
|
if (!charsCache)
|
||||||
|
charsCache = this.charsCache = Object.create(null);
|
||||||
|
|
||||||
|
if (this.compositeFont) {
|
||||||
|
// composite fonts have multi-byte strings
|
||||||
|
// convert the string from single-byte to multi-byte
|
||||||
|
// XXX assuming CIDFonts are two-byte - later need to extract the correct byte encoding
|
||||||
|
// according to the PDF spec
|
||||||
|
str = '';
|
||||||
|
var multiByteStr = "";
|
||||||
|
var length = chars.length;
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
var byte1 = chars.charCodeAt(i++) & 0xFF;
|
||||||
|
var byte2;
|
||||||
|
if (i == length)
|
||||||
|
byte2 = 0;
|
||||||
|
else
|
||||||
|
byte2 = chars.charCodeAt(i) & 0xFF;
|
||||||
|
multiByteStr += String.fromCharCode((byte1 << 8) | byte2);
|
||||||
|
}
|
||||||
|
str = multiByteStr;
|
||||||
|
}
|
||||||
|
else {
|
||||||
// translate the string using the font's encoding
|
// translate the string using the font's encoding
|
||||||
var encoding = this.encoding;
|
var encoding = this.encoding;
|
||||||
if (!encoding)
|
if (!encoding)
|
||||||
return chars;
|
return chars;
|
||||||
|
|
||||||
// lazily create the translation cache
|
|
||||||
if (!charsCache)
|
|
||||||
charsCache = this.charsCache = Object.create(null);
|
|
||||||
|
|
||||||
str = '';
|
str = '';
|
||||||
for (var i = 0; i < chars.length; ++i) {
|
for (var i = 0; i < chars.length; ++i) {
|
||||||
var charcode = chars.charCodeAt(i);
|
var charcode = chars.charCodeAt(i);
|
||||||
@ -1149,6 +1229,7 @@ var Font = (function() {
|
|||||||
}
|
}
|
||||||
str += String.fromCharCode(unicode);
|
str += String.fromCharCode(unicode);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Enter the translated string into the cache
|
// Enter the translated string into the cache
|
||||||
return charsCache[chars] = str;
|
return charsCache[chars] = str;
|
||||||
|
67
pdf.js
67
pdf.js
@ -3624,11 +3624,31 @@ var PartialEvaluator = (function() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
translateFont: function(fontDict, xref, resources) {
|
translateFont: function(fontDict, xref, resources) {
|
||||||
var fd = fontDict.get('FontDescriptor');
|
var fd;
|
||||||
if (!fd)
|
var descendant = [];
|
||||||
// XXX deprecated "special treatment" for standard
|
var subType = fontDict.get('Subtype');
|
||||||
// fonts? What do we need to do here?
|
var compositeFont = false;
|
||||||
|
assertWellFormed(IsName(subType), 'invalid font Subtype');
|
||||||
|
|
||||||
|
// If font is a composite
|
||||||
|
// - get the descendant font
|
||||||
|
// - set the type according to the descendant font
|
||||||
|
// - get the FontDescriptor from the descendant font
|
||||||
|
if (subType.name == 'Type0') {
|
||||||
|
var df = fontDict.get('DescendantFonts');
|
||||||
|
if (!df)
|
||||||
return null;
|
return null;
|
||||||
|
compositeFont = true;
|
||||||
|
descendant = xref.fetch(df[0]);
|
||||||
|
subType = descendant.get('Subtype');
|
||||||
|
fd = descendant.get('FontDescriptor');
|
||||||
|
} else {
|
||||||
|
fd = fontDict.get('FontDescriptor');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fd)
|
||||||
|
return null;
|
||||||
|
|
||||||
var descriptor = xref.fetch(fd);
|
var descriptor = xref.fetch(fd);
|
||||||
|
|
||||||
var fontName = descriptor.get('FontName');
|
var fontName = descriptor.get('FontName');
|
||||||
@ -3642,7 +3662,38 @@ var PartialEvaluator = (function() {
|
|||||||
|
|
||||||
var encodingMap = {};
|
var encodingMap = {};
|
||||||
var charset = [];
|
var charset = [];
|
||||||
if (fontDict.has('Encoding')) {
|
if (compositeFont) {
|
||||||
|
// Special CIDFont support
|
||||||
|
// XXX only CIDFontType2 supported for now
|
||||||
|
if (subType.name == 'CIDFontType2') {
|
||||||
|
var cidToGidMap = descendant.get('CIDToGIDMap');
|
||||||
|
if (cidToGidMap && IsRef(cidToGidMap)) {
|
||||||
|
// Extract the charset from the CIDToGIDMap
|
||||||
|
var glyphsStream = xref.fetchIfRef(cidToGidMap);
|
||||||
|
var glyphsData = glyphsStream.getBytes(0);
|
||||||
|
var i = 0;
|
||||||
|
// Glyph ids are big-endian 2-byte values
|
||||||
|
for (var j=0; j<glyphsData.length; j++) {
|
||||||
|
var glyphID = (glyphsData[j++] << 8) | glyphsData[j];
|
||||||
|
charset.push(glyphID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// XXX This is a placeholder for handling of the encoding of CIDFontType0 fonts
|
||||||
|
var encoding = xref.fetchIfRef(fontDict.get('Encoding'));
|
||||||
|
if (IsName(encoding)) {
|
||||||
|
// Encoding is a predefined CMap
|
||||||
|
if (encoding.name == 'Identity-H') {
|
||||||
|
TODO ('Need to create an identity cmap')
|
||||||
|
} else {
|
||||||
|
TODO ('Need to support predefined CMaps see PDF 32000-1:2008 9.7.5.2 Predefined CMaps')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TODO ('Need to support encoding streams see PDF 32000-1:2008 9.7.5.3');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else 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 a map of between codes and glyphs
|
// Build a map of between codes and glyphs
|
||||||
@ -3758,9 +3809,6 @@ var PartialEvaluator = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var subType = fontDict.get('Subtype');
|
|
||||||
assertWellFormed(IsName(subType), 'invalid font Subtype');
|
|
||||||
|
|
||||||
var properties = {
|
var properties = {
|
||||||
type: subType.name,
|
type: subType.name,
|
||||||
encoding: encodingMap,
|
encoding: encodingMap,
|
||||||
@ -3775,7 +3823,8 @@ var PartialEvaluator = (function() {
|
|||||||
flags: descriptor.get('Flags'),
|
flags: descriptor.get('Flags'),
|
||||||
italicAngle: descriptor.get('ItalicAngle'),
|
italicAngle: descriptor.get('ItalicAngle'),
|
||||||
fixedPitch: false,
|
fixedPitch: false,
|
||||||
textMatrix: IDENTITY_MATRIX
|
textMatrix: IDENTITY_MATRIX,
|
||||||
|
compositeFont: compositeFont
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
BIN
test/pdfs/ArabicCIDTrueType.pdf
Normal file
BIN
test/pdfs/ArabicCIDTrueType.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/arial_unicode_ab_cidfont.pdf
Normal file
BIN
test/pdfs/arial_unicode_ab_cidfont.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/arial_unicode_en_cidfont.pdf
Normal file
BIN
test/pdfs/arial_unicode_en_cidfont.pdf
Normal file
Binary file not shown.
@ -48,5 +48,20 @@
|
|||||||
"link": true,
|
"link": true,
|
||||||
"rounds": 1,
|
"rounds": 1,
|
||||||
"type": "load"
|
"type": "load"
|
||||||
|
},
|
||||||
|
{ "id": "openofficecidtruetype-pdf",
|
||||||
|
"file": "pdfs/arial_unicode_en_cidfont.pdf",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "load"
|
||||||
|
},
|
||||||
|
{ "id": "openofficearabiccidtruetype-pdf",
|
||||||
|
"file": "pdfs/arial_unicode_ab_cidfont.pdf",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "load"
|
||||||
|
},
|
||||||
|
{ "id": "arabiccidtruetype-pdf",
|
||||||
|
"file": "pdfs/ArabicCIDTrueType.pdf",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "load"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user