diff --git a/src/core/fonts.js b/src/core/fonts.js index d5f86bae6..936df4da9 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -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; @@ -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')); @@ -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; @@ -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 + ')'); } - - 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; } @@ -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'; + 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); + var cff = (subtype === 'Type1C' || subtype === 'CIDFontType0C') ? + new CFFFont(file, properties) : + new Type1Font(name, file, properties); - adjustWidths(properties); - - // 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'; - - // 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; + // Wrap the CFF data inside an OTF font file + data = this.convert(name, cff, properties); + break; - default: - throw new FormatError(`Font ${type} is not supported`); + 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); + + type = 'OpenType'; + } + break; + + 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; @@ -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. @@ -830,13 +773,15 @@ 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) { @@ -844,8 +789,7 @@ var Font = (function FontClosure() { nextAvailableFontCharCode = fontCharCode + 1; } - } while (usedFontCharCodes[fontCharCode] !== undefined && - nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END); + } while (usedFontCharCodes[fontCharCode] !== undefined); } newMap[fontCharCode] = glyphId; @@ -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; }); @@ -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)); @@ -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); } } } @@ -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 @@ -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.