mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-25 17:48:07 +02:00
Map all glyphs to the private use area and duplicate the first glyph.
There have been lots of problems with trying to map glyphs to their unicode values. It's more reliable to just use the private use areas so the browser's font renderer doesn't mess with the glyphs. Using the private use area for all glyphs did highlight other issues that this patch also had to fix: * small private use area - Previously, only the BMP private use area was used which can't map many glyphs. Now, the (much bigger) PUP 16 area can also be used. * glyph zero not shown - Browsers will not use the glyph from a font if it is glyph id = 0. This issue was less prevalent when we mapped to unicode values since the fallback font would be used. However, when using the private use area, the glyph would not be drawn at all. This is illustrated in one of the current test cases (issue #8234) where there's an "ä" glyph at position zero. The PDF looked like it rendered correctly, but it was actually not using the glyph from the font. To properly show the first glyph it is always duplicated and appended to the glyphs and the maps are adjusted. * supplementary characters - The private use area PUP 16 is 4 bytes, so String.fromCodePoint must be used where we previously used String.fromCharCode. This is actually an issue that should have been fixed regardless of this patch. * charset - Freetype fails to load fonts when the charset size doesn't match number of glyphs in the font. We now write out a fake charset with the correct length. This also brought up the issue that glyphs with seac/endchar should only ever write a standard charset, but we now write a custom one. To get around this the seac analysis is permanently enabled so those glyphs are instead always drawn as two glyphs.
This commit is contained in:
parent
20cd1b354b
commit
b76cf665ec
14 changed files with 16188 additions and 304 deletions
|
@ -815,11 +815,10 @@ var CFFParser = (function CFFParserClosure() {
|
|||
return new CFFEncoding(predefined, format, encoding, raw);
|
||||
},
|
||||
parseFDSelect: function CFFParser_parseFDSelect(pos, length) {
|
||||
var start = pos;
|
||||
var bytes = this.bytes;
|
||||
var format = bytes[pos++];
|
||||
var fdSelect = [], rawBytes;
|
||||
var i, invalidFirstGID = false;
|
||||
var fdSelect = [];
|
||||
var i;
|
||||
|
||||
switch (format) {
|
||||
case 0:
|
||||
|
@ -827,7 +826,6 @@ var CFFParser = (function CFFParserClosure() {
|
|||
var id = bytes[pos++];
|
||||
fdSelect.push(id);
|
||||
}
|
||||
rawBytes = bytes.subarray(start, pos);
|
||||
break;
|
||||
case 3:
|
||||
var rangesCount = (bytes[pos++] << 8) | bytes[pos++];
|
||||
|
@ -836,7 +834,6 @@ var CFFParser = (function CFFParserClosure() {
|
|||
if (i === 0 && first !== 0) {
|
||||
warn('parseFDSelect: The first range must have a first GID of 0' +
|
||||
' -- trying to recover.');
|
||||
invalidFirstGID = true;
|
||||
first = 0;
|
||||
}
|
||||
var fdIndex = bytes[pos++];
|
||||
|
@ -847,11 +844,6 @@ var CFFParser = (function CFFParserClosure() {
|
|||
}
|
||||
// Advance past the sentinel(next).
|
||||
pos += 2;
|
||||
rawBytes = bytes.subarray(start, pos);
|
||||
|
||||
if (invalidFirstGID) {
|
||||
rawBytes[3] = rawBytes[4] = 0; // Adjust the first range, first GID.
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new FormatError(`parseFDSelect: Unknown format "${format}".`);
|
||||
|
@ -860,7 +852,7 @@ var CFFParser = (function CFFParserClosure() {
|
|||
throw new FormatError('parseFDSelect: Invalid font data.');
|
||||
}
|
||||
|
||||
return new CFFFDSelect(fdSelect, rawBytes);
|
||||
return new CFFFDSelect(format, fdSelect);
|
||||
},
|
||||
};
|
||||
return CFFParser;
|
||||
|
@ -885,6 +877,30 @@ var CFF = (function CFFClosure() {
|
|||
|
||||
this.isCIDFont = false;
|
||||
}
|
||||
CFF.prototype = {
|
||||
duplicateFirstGlyph: function CFF_duplicateFirstGlyph() {
|
||||
// Browsers will not display a glyph at position 0. Typically glyph 0 is
|
||||
// notdef, but a number of fonts put a valid glyph there so it must be
|
||||
// duplicated and appended.
|
||||
if (this.charStrings.count >= 65535) {
|
||||
warn('Not enough space in charstrings to duplicate first glyph.');
|
||||
return;
|
||||
}
|
||||
var glyphZero = this.charStrings.get(0);
|
||||
this.charStrings.add(glyphZero);
|
||||
if (this.isCIDFont) {
|
||||
this.fdSelect.fdSelect.push(this.fdSelect.fdSelect[0]);
|
||||
}
|
||||
},
|
||||
hasGlyphId: function CFF_hasGlyphID(id) {
|
||||
if (id < 0 || id >= this.charStrings.count) {
|
||||
return false;
|
||||
}
|
||||
var glyph = this.charStrings.get(id);
|
||||
return glyph.length > 0;
|
||||
},
|
||||
};
|
||||
|
||||
return CFF;
|
||||
})();
|
||||
|
||||
|
@ -1142,9 +1158,9 @@ var CFFEncoding = (function CFFEncodingClosure() {
|
|||
})();
|
||||
|
||||
var CFFFDSelect = (function CFFFDSelectClosure() {
|
||||
function CFFFDSelect(fdSelect, raw) {
|
||||
function CFFFDSelect(format, fdSelect) {
|
||||
this.format = format;
|
||||
this.fdSelect = fdSelect;
|
||||
this.raw = raw;
|
||||
}
|
||||
CFFFDSelect.prototype = {
|
||||
getFDIndex: function CFFFDSelect_get(glyphIndex) {
|
||||
|
@ -1261,6 +1277,7 @@ var CFFCompiler = (function CFFCompilerClosure() {
|
|||
}
|
||||
}
|
||||
|
||||
cff.topDict.setByName('charset', 0);
|
||||
var compiled = this.compileTopDicts([cff.topDict],
|
||||
output.length,
|
||||
cff.isCIDFont);
|
||||
|
@ -1284,17 +1301,9 @@ var CFFCompiler = (function CFFCompilerClosure() {
|
|||
output.add(encoding);
|
||||
}
|
||||
}
|
||||
|
||||
if (cff.charset && cff.topDict.hasName('charset')) {
|
||||
if (cff.charset.predefined) {
|
||||
topDictTracker.setEntryLocation('charset', [cff.charset.format],
|
||||
output);
|
||||
} else {
|
||||
var charset = this.compileCharset(cff.charset);
|
||||
topDictTracker.setEntryLocation('charset', [output.length], output);
|
||||
output.add(charset);
|
||||
}
|
||||
}
|
||||
var charset = this.compileCharset(cff.charset);
|
||||
topDictTracker.setEntryLocation('charset', [output.length], output);
|
||||
output.add(charset);
|
||||
|
||||
var charStrings = this.compileCharStrings(cff.charStrings);
|
||||
topDictTracker.setEntryLocation('CharStrings', [output.length], output);
|
||||
|
@ -1304,7 +1313,7 @@ var CFFCompiler = (function CFFCompilerClosure() {
|
|||
// For some reason FDSelect must be in front of FDArray on windows. OSX
|
||||
// and linux don't seem to care.
|
||||
topDictTracker.setEntryLocation('FDSelect', [output.length], output);
|
||||
var fdSelect = this.compileFDSelect(cff.fdSelect.raw);
|
||||
var fdSelect = this.compileFDSelect(cff.fdSelect);
|
||||
output.add(fdSelect);
|
||||
// It is unclear if the sub font dictionary can have CID related
|
||||
// dictionary keys, but the sanitizer doesn't like them so remove them.
|
||||
|
@ -1547,16 +1556,68 @@ var CFFCompiler = (function CFFCompilerClosure() {
|
|||
this.out.writeByteArray(this.compileIndex(globalSubrIndex));
|
||||
},
|
||||
compileCharStrings: function CFFCompiler_compileCharStrings(charStrings) {
|
||||
return this.compileIndex(charStrings);
|
||||
var charStringsIndex = new CFFIndex();
|
||||
for (var i = 0; i < charStrings.count; i++) {
|
||||
var glyph = charStrings.get(i);
|
||||
// If the CharString outline is empty, replace it with .notdef to
|
||||
// prevent OTS from rejecting the font (fixes bug1252420.pdf).
|
||||
if (glyph.length === 0) {
|
||||
charStringsIndex.add(new Uint8Array([0x8B, 0x0E]));
|
||||
continue;
|
||||
}
|
||||
charStringsIndex.add(glyph);
|
||||
}
|
||||
return this.compileIndex(charStringsIndex);
|
||||
},
|
||||
compileCharset: function CFFCompiler_compileCharset(charset) {
|
||||
return this.compileTypedArray(charset.raw);
|
||||
let length = 1 + (this.cff.charStrings.count - 1) * 2;
|
||||
// The contents of the charset doesn't matter, it's just there to make
|
||||
// freetype happy.
|
||||
let out = new Uint8Array(length);
|
||||
return this.compileTypedArray(out);
|
||||
},
|
||||
compileEncoding: function CFFCompiler_compileEncoding(encoding) {
|
||||
return this.compileTypedArray(encoding.raw);
|
||||
},
|
||||
compileFDSelect: function CFFCompiler_compileFDSelect(fdSelect) {
|
||||
return this.compileTypedArray(fdSelect);
|
||||
let format = fdSelect.format;
|
||||
let out, i;
|
||||
switch (format) {
|
||||
case 0:
|
||||
out = new Uint8Array(1 + fdSelect.fdSelect.length);
|
||||
out[0] = format;
|
||||
for (i = 0; i < fdSelect.fdSelect.length; i++) {
|
||||
out[i + 1] = fdSelect.fdSelect[i];
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
let start = 0;
|
||||
let lastFD = fdSelect.fdSelect[0];
|
||||
let ranges = [
|
||||
format,
|
||||
0, // nRanges place holder
|
||||
0, // nRanges place holder
|
||||
(start >> 8) & 0xFF,
|
||||
start & 0xFF,
|
||||
lastFD
|
||||
];
|
||||
for (i = 1; i < fdSelect.fdSelect.length; i++) {
|
||||
let currentFD = fdSelect.fdSelect[i];
|
||||
if (currentFD !== lastFD) {
|
||||
ranges.push((i >> 8) & 0xFF, i & 0xFF, currentFD);
|
||||
lastFD = currentFD;
|
||||
}
|
||||
}
|
||||
// 3 bytes are pushed for every range and there are 3 header bytes.
|
||||
let numRanges = (ranges.length - 3) / 3;
|
||||
ranges[1] = (numRanges >> 8) & 0xFF;
|
||||
ranges[2] = numRanges & 0xFF;
|
||||
// sentinel
|
||||
ranges.push((i >> 8) & 0xFF, i & 0xFF);
|
||||
out = new Uint8Array(ranges);
|
||||
break;
|
||||
}
|
||||
return this.compileTypedArray(out);
|
||||
},
|
||||
compileTypedArray: function CFFCompiler_compileTypedArray(data) {
|
||||
var out = [];
|
||||
|
@ -1648,4 +1709,5 @@ export {
|
|||
CFFTopDict,
|
||||
CFFPrivateDict,
|
||||
CFFCompiler,
|
||||
CFFFDSelect,
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue