Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix several issues with glyph id mappings (issue 8668, bug 1383504) #8681

Merged
merged 1 commit into from
Aug 3, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
263 changes: 144 additions & 119 deletions src/core/fonts.js
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ var ProblematicCharRanges = new Int32Array([
*/
var Font = (function FontClosure() {
function Font(name, file, properties) {
var charCode, glyphName, unicode;
var charCode;

this.name = name;
this.loadedName = properties.loadedName;
Expand All @@ -498,6 +498,7 @@ var Font = (function FontClosure() {
var type = properties.type;
var subtype = properties.subtype;
this.type = type;
this.subtype = subtype;

this.fallbackName = (this.isMonospace ? 'monospace' :
(this.isSerifFont ? 'serif' : 'sans-serif'));
Expand All @@ -512,6 +513,7 @@ var Font = (function FontClosure() {
this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS;
this.fontMatrix = properties.fontMatrix;
this.bbox = properties.bbox;
this.defaultEncoding = properties.defaultEncoding;

this.toUnicode = properties.toUnicode;

Expand All @@ -532,88 +534,14 @@ var Font = (function FontClosure() {
this.vmetrics = properties.vmetrics;
this.defaultVMetrics = properties.defaultVMetrics;
}
var glyphsUnicodeMap;

if (!file || file.isEmpty) {
if (file) {
// Some bad PDF generators will include empty font files,
// attempting to recover by assuming that no file exists.
warn('Font file is empty in "' + name + '" (' + this.loadedName + ')');
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this is extracted into fallbackToSystemFont().

this.missingFile = true;
// The file data is not specified. Trying to fix the font name
// to be used with the canvas.font.
var fontName = name.replace(/[,_]/g, '-');
var stdFontMap = getStdFontMap(), nonStdFontMap = getNonStdFontMap();
var isStandardFont = !!stdFontMap[fontName] ||
!!(nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]);
fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;

this.bold = (fontName.search(/bold/gi) !== -1);
this.italic = ((fontName.search(/oblique/gi) !== -1) ||
(fontName.search(/italic/gi) !== -1));

// Use 'name' instead of 'fontName' here because the original
// name ArialBlack for example will be replaced by Helvetica.
this.black = (name.search(/Black/g) !== -1);

// if at least one width is present, remeasure all chars when exists
this.remeasure = Object.keys(this.widths).length > 0;
if (isStandardFont && type === 'CIDFontType2' &&
properties.cidEncoding.indexOf('Identity-') === 0) {
var GlyphMapForStandardFonts = getGlyphMapForStandardFonts();
// Standard fonts might be embedded as CID font without glyph mapping.
// Building one based on GlyphMapForStandardFonts.
var map = [];
for (charCode in GlyphMapForStandardFonts) {
map[+charCode] = GlyphMapForStandardFonts[charCode];
}
if (/Arial-?Black/i.test(name)) {
var SupplementalGlyphMapForArialBlack =
getSupplementalGlyphMapForArialBlack();
for (charCode in SupplementalGlyphMapForArialBlack) {
map[+charCode] = SupplementalGlyphMapForArialBlack[charCode];
}
}
var isIdentityUnicode = this.toUnicode instanceof IdentityToUnicodeMap;
if (!isIdentityUnicode) {
this.toUnicode.forEach(function(charCode, unicodeCharCode) {
map[+charCode] = unicodeCharCode;
});
}
this.toFontChar = map;
this.toUnicode = new ToUnicodeMap(map);
} else if (/Symbol/i.test(fontName)) {
this.toFontChar = buildToFontChar(SymbolSetEncoding, getGlyphsUnicode(),
properties.differences);
} else if (/Dingbats/i.test(fontName)) {
if (/Wingdings/i.test(name)) {
warn('Non-embedded Wingdings font, falling back to ZapfDingbats.');
}
this.toFontChar = buildToFontChar(ZapfDingbatsEncoding,
getDingbatsGlyphsUnicode(),
properties.differences);
} else if (isStandardFont) {
this.toFontChar = buildToFontChar(properties.defaultEncoding,
getGlyphsUnicode(),
properties.differences);
} else {
glyphsUnicodeMap = getGlyphsUnicode();
this.toUnicode.forEach((charCode, unicodeCharCode) => {
if (!this.composite) {
glyphName = (properties.differences[charCode] ||
properties.defaultEncoding[charCode]);
unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
if (unicode !== -1) {
unicodeCharCode = unicode;
}
}
this.toFontChar[charCode] = unicodeCharCode;
});
}
this.loadedName = fontName.split('-')[0];
this.loading = false;
this.fontType = getFontType(type, subtype);
this.fallbackToSystemFont();
return;
}

Expand Down Expand Up @@ -649,41 +577,51 @@ var Font = (function FontClosure() {
type = 'OpenType';
}

var data;
switch (type) {
case 'MMType1':
info('MMType1 font (' + name + '), falling back to Type1.');
/* falls through */
case 'Type1':
case 'CIDFontType0':
this.mimetype = 'font/opentype';

var cff = (subtype === 'Type1C' || subtype === 'CIDFontType0C') ?
new CFFFont(file, properties) : new Type1Font(name, file, properties);
try {
var data;
switch (type) {
case 'MMType1':
info('MMType1 font (' + name + '), falling back to Type1.');
/* falls through */
case 'Type1':
case 'CIDFontType0':
this.mimetype = 'font/opentype';

var cff = (subtype === 'Type1C' || subtype === 'CIDFontType0C') ?
new CFFFont(file, properties) :
new Type1Font(name, file, properties);

adjustWidths(properties);
adjustWidths(properties);

// Wrap the CFF data inside an OTF font file
data = this.convert(name, cff, properties);
break;
// Wrap the CFF data inside an OTF font file
data = this.convert(name, cff, properties);
break;

case 'OpenType':
case 'TrueType':
case 'CIDFontType2':
this.mimetype = 'font/opentype';
case 'OpenType':
case 'TrueType':
case 'CIDFontType2':
this.mimetype = 'font/opentype';

// Repair the TrueType file. It is can be damaged in the point of
// view of the sanitizer
data = this.checkAndRepair(name, file, properties);
if (this.isOpenType) {
adjustWidths(properties);
// Repair the TrueType file. It is can be damaged in the point of
// view of the sanitizer
data = this.checkAndRepair(name, file, properties);
if (this.isOpenType) {
adjustWidths(properties);

type = 'OpenType';
}
break;
type = 'OpenType';
}
break;

default:
throw new FormatError(`Font ${type} is not supported`);
default:
throw new FormatError(`Font ${type} is not supported`);
}
} catch (e) {
if (!(e instanceof FormatError)) {
throw e;
}
warn(e);
this.fallbackToSystemFont();
return;
}

this.data = data;
Expand Down Expand Up @@ -812,6 +750,11 @@ var Font = (function FontClosure() {
for (var originalCharCode in charCodeToGlyphId) {
originalCharCode |= 0;
var glyphId = charCodeToGlyphId[originalCharCode];
// For missing glyphs don't create the mappings so the glyph isn't
// drawn.
if (missingGlyphs[glyphId]) {
continue;
}
var fontCharCode = originalCharCode;
// First try to map the value to a unicode position if a non identity map
// was created.
Expand All @@ -830,22 +773,23 @@ var Font = (function FontClosure() {
// font was symbolic and there is only an identity unicode map since the
// characters probably aren't in the correct position (fixes an issue
// with firefox and thuluthfont).
if (!missingGlyphs[glyphId] &&
(usedFontCharCodes[fontCharCode] !== undefined ||
if ((usedFontCharCodes[fontCharCode] !== undefined ||
isProblematicUnicodeLocation(fontCharCode) ||
(isSymbolic && !hasUnicodeValue)) &&
nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END) { // Room left.
(isSymbolic && !hasUnicodeValue))) {
// Loop to try and find a free spot in the private use area.
do {
if (nextAvailableFontCharCode > PRIVATE_USE_OFFSET_END) {
warn('Ran out of space in font private use area.');
break;
}
fontCharCode = nextAvailableFontCharCode++;

if (SKIP_PRIVATE_USE_RANGE_F000_TO_F01F && fontCharCode === 0xF000) {
fontCharCode = 0xF020;
nextAvailableFontCharCode = fontCharCode + 1;
}

} while (usedFontCharCodes[fontCharCode] !== undefined &&
nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END);
} while (usedFontCharCodes[fontCharCode] !== undefined);
}

newMap[fontCharCode] = glyphId;
Expand All @@ -870,6 +814,11 @@ var Font = (function FontClosure() {
}
codes.push({ fontCharCode: charCode | 0, glyphId: glyphs[charCode], });
}
// Some fonts have zero glyphs and are used only for text selection, but
// there needs to be at least one to build a valid cmap table.
if (codes.length === 0) {
codes.push({ fontCharCode: 0, glyphId: 0, });
}
codes.sort(function fontGetRangesSort(a, b) {
return a.fontCharCode - b.fontCharCode;
});
Expand Down Expand Up @@ -1248,6 +1197,87 @@ var Font = (function FontClosure() {
return data;
},

fallbackToSystemFont: function Font_fallbackToSystemFont() {
this.missingFile = true;
var charCode, unicode;
// The file data is not specified. Trying to fix the font name
// to be used with the canvas.font.
var name = this.name;
var type = this.type;
var subtype = this.subtype;
var fontName = name.replace(/[,_]/g, '-');
var stdFontMap = getStdFontMap(), nonStdFontMap = getNonStdFontMap();
var isStandardFont = !!stdFontMap[fontName] ||
!!(nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]);
fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;

this.bold = (fontName.search(/bold/gi) !== -1);
this.italic = ((fontName.search(/oblique/gi) !== -1) ||
(fontName.search(/italic/gi) !== -1));

// Use 'name' instead of 'fontName' here because the original
// name ArialBlack for example will be replaced by Helvetica.
this.black = (name.search(/Black/g) !== -1);

// if at least one width is present, remeasure all chars when exists
this.remeasure = Object.keys(this.widths).length > 0;
if (isStandardFont && type === 'CIDFontType2' &&
this.cidEncoding.indexOf('Identity-') === 0) {
var GlyphMapForStandardFonts = getGlyphMapForStandardFonts();
// Standard fonts might be embedded as CID font without glyph mapping.
// Building one based on GlyphMapForStandardFonts.
var map = [];
for (charCode in GlyphMapForStandardFonts) {
map[+charCode] = GlyphMapForStandardFonts[charCode];
}
if (/Arial-?Black/i.test(name)) {
var SupplementalGlyphMapForArialBlack =
getSupplementalGlyphMapForArialBlack();
for (charCode in SupplementalGlyphMapForArialBlack) {
map[+charCode] = SupplementalGlyphMapForArialBlack[charCode];
}
}
var isIdentityUnicode = this.toUnicode instanceof IdentityToUnicodeMap;
if (!isIdentityUnicode) {
this.toUnicode.forEach(function(charCode, unicodeCharCode) {
map[+charCode] = unicodeCharCode;
});
}
this.toFontChar = map;
this.toUnicode = new ToUnicodeMap(map);
} else if (/Symbol/i.test(fontName)) {
this.toFontChar = buildToFontChar(SymbolSetEncoding, getGlyphsUnicode(),
this.differences);
} else if (/Dingbats/i.test(fontName)) {
if (/Wingdings/i.test(name)) {
warn('Non-embedded Wingdings font, falling back to ZapfDingbats.');
}
this.toFontChar = buildToFontChar(ZapfDingbatsEncoding,
getDingbatsGlyphsUnicode(),
this.differences);
} else if (isStandardFont) {
this.toFontChar = buildToFontChar(this.defaultEncoding,
getGlyphsUnicode(),
this.differences);
} else {
var glyphsUnicodeMap = getGlyphsUnicode();
this.toUnicode.forEach((charCode, unicodeCharCode) => {
if (!this.composite) {
var glyphName = (this.differences[charCode] ||
this.defaultEncoding[charCode]);
unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
if (unicode !== -1) {
unicodeCharCode = unicode;
}
}
this.toFontChar[charCode] = unicodeCharCode;
});
}
this.loadedName = fontName.split('-')[0];
this.loading = false;
this.fontType = getFontType(type, subtype);
},

checkAndRepair: function Font_checkAndRepair(name, font, properties) {
function readTableEntry(file) {
var tag = bytesToString(file.getBytes(4));
Expand Down Expand Up @@ -1641,7 +1671,8 @@ var Font = (function FontClosure() {
data[50] = 0;
data[51] = 1;
} else {
warn('Could not fix indexToLocFormat: ' + indexToLocFormat);
throw new FormatError('Could not fix indexToLocFormat: ' +
indexToLocFormat);
}
}
}
Expand Down Expand Up @@ -1687,9 +1718,6 @@ var Font = (function FontClosure() {
var startOffset = itemDecode(locaData, 0);
var writeOffset = 0;
var missingGlyphData = Object.create(null);
// Glyph zero should be notdef which isn't drawn. Sometimes this is a
// valid glyph but, then it is duplicated.
missingGlyphData[0] = true;
itemEncode(locaData, 0, writeOffset);
var i, j;
// When called with dupFirstEntry the number of glyphs has already been
Expand Down Expand Up @@ -2382,9 +2410,6 @@ var Font = (function FontClosure() {
found = true;
}
}
if (!found) {
charCodeToGlyphId[charCode] = 0; // notdef
}
}
} else if (cmapPlatformId === 0 && cmapEncodingId === 0) {
// Default Unicode semantics, use the charcodes as is.
Expand Down