Merge pull request #9340 from brendandahl/private-use

Map all glyphs to the private use area and duplicate the first glyph.
This commit is contained in:
Tim van der Meij 2018-09-08 17:51:04 +02:00 committed by GitHub
commit 66422eb83e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 16188 additions and 304 deletions

View File

@ -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,
};

View File

@ -122,7 +122,7 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
}
function lookupCmap(ranges, unicode) {
var code = unicode.charCodeAt(0), gid = 0;
var code = unicode.codePointAt(0), gid = 0;
var l = 0, r = ranges.length - 1;
while (l < r) {
var c = (l + r + 1) >> 1;

View File

@ -39,18 +39,24 @@ import { IdentityCMap } from './cmap';
import { Stream } from './stream';
import { Type1Parser } from './type1_parser';
// Unicode Private Use Area
var PRIVATE_USE_OFFSET_START = 0xE000;
var PRIVATE_USE_OFFSET_END = 0xF8FF;
var SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = false;
// Unicode Private Use Areas:
const PRIVATE_USE_AREAS = [
[0xE000, 0xF8FF], // BMP (0)
[0x100000, 0x10FFFD], // PUP (16)
];
// PDF Glyph Space Units are one Thousandth of a TextSpace Unit
// except for Type 3 fonts
var PDF_GLYPH_SPACE_UNITS = 1000;
// Accented characters are not displayed properly on Windows, using this flag
// to control analysis of seac charstrings.
var SEAC_ANALYSIS_ENABLED = false;
// Accented characters have issues on Windows and Linux. When this flag is
// enabled glyphs that use seac and seac style endchar operators are truncated
// and we instead just store the glyph id's of the base glyph and its accent to
// be drawn individually.
// Linux (freetype) requires that when a seac style endchar is used
// that the charset must be a predefined one, however we build a
// custom one. Windows just refuses to draw glyphs with seac operators.
var SEAC_ANALYSIS_ENABLED = true;
var FontFlags = {
FixedPitch: 1,
@ -444,37 +450,6 @@ var OpenTypeFileBuilder = (function OpenTypeFileBuilderClosure() {
return OpenTypeFileBuilder;
})();
// Problematic Unicode characters in the fonts that needs to be moved to avoid
// issues when they are painted on the canvas, e.g. complex-script shaping or
// control/whitespace characters. The ranges are listed in pairs: the first item
// is a code of the first problematic code, the second one is the next
// non-problematic code. The ranges must be in sorted order.
var ProblematicCharRanges = new Int32Array([
// Control characters.
0x0000, 0x0020,
0x007F, 0x00A1,
0x00AD, 0x00AE,
// Chars that is used in complex-script shaping.
0x0600, 0x0780,
0x08A0, 0x10A0,
0x1780, 0x1800,
0x1C00, 0x1C50,
// General punctuation chars.
0x2000, 0x2010,
0x2011, 0x2012,
0x2028, 0x2030,
0x205F, 0x2070,
0x25CC, 0x25CD,
0x3000, 0x3001,
0x3164, 0x3165,
// Chars that is used in complex-script shaping.
0xAA60, 0xAA80,
// Unicode high surrogates.
0xD800, 0xE000,
// Specials Unicode block.
0xFFF0, 0x10000
]);
/**
* 'Font' is the class the outside world should use, it encapsulate all the font
* decoding logics whatever type it is (assuming the font type is supported).
@ -755,91 +730,46 @@ var Font = (function FontClosure() {
}
/**
* Helper function for `adjustMapping`.
* @return {boolean}
*/
function isProblematicUnicodeLocation(code) {
// Using binary search to find a range start.
var i = 0, j = ProblematicCharRanges.length - 1;
while (i < j) {
var c = (i + j + 1) >> 1;
if (code < ProblematicCharRanges[c]) {
j = c - 1;
} else {
i = c;
}
}
// Even index means code in problematic range.
return !(i & 1);
}
/**
* Rebuilds the char code to glyph ID map by trying to replace the char codes
* with their unicode value. It also moves char codes that are in known
* problematic locations.
* Rebuilds the char code to glyph ID map by moving all char codes to the
* private use area. This is done to avoid issues with various problematic
* unicode areas where either a glyph won't be drawn or is deformed by a
* shaper.
* @return {Object} Two properties:
* 'toFontChar' - maps original char codes(the value that will be read
* from commands such as show text) to the char codes that will be used in the
* font that we build
* 'charCodeToGlyphId' - maps the new font char codes to glyph ids
*/
function adjustMapping(charCodeToGlyphId, properties, missingGlyphs) {
var toUnicode = properties.toUnicode;
var isSymbolic = !!(properties.flags & FontFlags.Symbolic);
var isIdentityUnicode =
properties.toUnicode instanceof IdentityToUnicodeMap;
function adjustMapping(charCodeToGlyphId, hasGlyph, newGlyphZeroId) {
var newMap = Object.create(null);
var toFontChar = [];
var usedFontCharCodes = [];
var nextAvailableFontCharCode = PRIVATE_USE_OFFSET_START;
var privateUseAreaIndex = 0;
var nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0];
var privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1];
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]) {
if (!hasGlyph(glyphId)) {
continue;
}
var fontCharCode = originalCharCode;
// First try to map the value to a unicode position if a non identity map
// was created.
var hasUnicodeValue = false;
if (!isIdentityUnicode && toUnicode.has(originalCharCode)) {
hasUnicodeValue = true;
var unicode = toUnicode.get(fontCharCode);
// TODO: Try to map ligatures to the correct spot.
if (unicode.length === 1) {
fontCharCode = unicode.charCodeAt(0);
if (nextAvailableFontCharCode > privateUseOffetEnd) {
privateUseAreaIndex++;
if (privateUseAreaIndex >= PRIVATE_USE_AREAS.length) {
warn('Ran out of space in font private use area.');
break;
}
nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0];
privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1];
}
// Try to move control characters, special characters and already mapped
// characters to the private use area since they will not be drawn by
// canvas if left in their current position. Also, move characters if the
// 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 ((usedFontCharCodes[fontCharCode] !== undefined ||
isProblematicUnicodeLocation(fontCharCode) ||
(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);
var fontCharCode = nextAvailableFontCharCode++;
if (glyphId === 0) {
glyphId = newGlyphZeroId;
}
newMap[fontCharCode] = glyphId;
toFontChar[originalCharCode] = fontCharCode;
usedFontCharCodes[fontCharCode] = true;
}
return {
toFontChar,
@ -1076,6 +1006,11 @@ var Font = (function FontClosure() {
'Unicode ranges Bits > 123 are reserved for internal usage');
}
}
if (lastCharIndex > 0xFFFF) {
// OS2 only supports a 16 bit int. The spec says if supplementary
// characters are used the field should just be set to 0xFFFF.
lastCharIndex = 0xFFFF;
}
} else {
// TODO
firstCharIndex = 0;
@ -1863,14 +1798,14 @@ var Font = (function FontClosure() {
data[offset + 1] = (value >> 1) & 0xFF;
};
}
// The first glyph is duplicated.
var numGlyphsOut = dupFirstEntry ? numGlyphs + 1 : numGlyphs;
var locaData = loca.data;
var locaDataSize = itemSize * (1 + numGlyphs);
// is loca.data too short or long?
if (locaData.length !== locaDataSize) {
locaData = new Uint8Array(locaDataSize);
locaData.set(loca.data.subarray(0, locaDataSize));
loca.data = locaData;
}
var locaDataSize = itemSize * (1 + numGlyphsOut);
// Resize loca table to account for duplicated glyph.
locaData = new Uint8Array(locaDataSize);
locaData.set(loca.data.subarray(0, locaDataSize));
loca.data = locaData;
// removing the invalid glyphs
var oldGlyfData = glyf.data;
var oldGlyfDataLength = oldGlyfData.length;
@ -1880,10 +1815,7 @@ var Font = (function FontClosure() {
var missingGlyphs = Object.create(null);
itemEncode(locaData, 0, writeOffset);
var i, j;
// When called with dupFirstEntry the number of glyphs has already been
// increased but there isn't data yet for the duplicated glyph.
var locaCount = dupFirstEntry ? numGlyphs - 1 : numGlyphs;
for (i = 0, j = itemSize; i < locaCount; i++, j += itemSize) {
for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
var endOffset = itemDecode(locaData, j);
// The spec says the offsets should be in ascending order, however
// some fonts use the offset of 0 to mark a glyph as missing.
@ -1921,11 +1853,14 @@ var Font = (function FontClosure() {
// to have single glyph with one point
var simpleGlyph = new Uint8Array(
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]);
for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
for (i = 0, j = itemSize; i < numGlyphsOut; i++, j += itemSize) {
itemEncode(locaData, j, simpleGlyph.length);
}
glyf.data = simpleGlyph;
} else if (dupFirstEntry) {
// 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.
var firstEntryLength = itemDecode(locaData, itemSize);
if (newGlyfData.length > firstEntryLength + writeOffset) {
glyf.data = newGlyfData.subarray(0, firstEntryLength + writeOffset);
@ -2382,7 +2317,15 @@ var Font = (function FontClosure() {
font.pos = (font.start || 0) + tables['maxp'].offset;
var version = font.getInt32();
var numGlyphs = font.getUint16();
const numGlyphs = font.getUint16();
// Glyph 0 is duplicated and appended.
let numGlyphsOut = numGlyphs + 1;
let dupFirstEntry = true;
if (numGlyphsOut > 0xFFFF) {
dupFirstEntry = false;
numGlyphsOut = numGlyphs;
warn('Not enough space in glyfs to duplicate first glyph.');
}
var maxFunctionDefs = 0;
var maxSizeOfInstructions = 0;
if (version >= 0x00010000 && tables['maxp'].length >= 22) {
@ -2399,15 +2342,8 @@ var Font = (function FontClosure() {
maxSizeOfInstructions = font.getUint16();
}
var dupFirstEntry = false;
if (properties.type === 'CIDFontType2' && properties.toUnicode &&
properties.toUnicode.get(0) > '\u0000') {
// oracle's defect (see 3427), duplicating first entry
dupFirstEntry = true;
numGlyphs++;
tables['maxp'].data[4] = numGlyphs >> 8;
tables['maxp'].data[5] = numGlyphs & 255;
}
tables['maxp'].data[4] = numGlyphsOut >> 8;
tables['maxp'].data[5] = numGlyphsOut & 255;
var hintsValid = sanitizeTTPrograms(tables['fpgm'], tables['prep'],
tables['cvt '], maxFunctionDefs);
@ -2419,7 +2355,7 @@ var Font = (function FontClosure() {
// Ensure the hmtx table contains the advance width and
// sidebearings information for numGlyphs in the maxp table
sanitizeMetrics(font, tables['hhea'], tables['hmtx'], numGlyphs);
sanitizeMetrics(font, tables['hhea'], tables['hmtx'], numGlyphsOut);
if (!tables['head']) {
throw new FormatError('Required "head" table is not found');
@ -2472,12 +2408,15 @@ var Font = (function FontClosure() {
// The 'post' table has glyphs names.
if (tables['post']) {
var valid = readPostScriptTable(tables['post'], properties, numGlyphs);
if (!valid) {
tables['post'] = null;
}
readPostScriptTable(tables['post'], properties, numGlyphs);
}
// The original 'post' table is not needed, replace it.
tables['post'] = {
tag: 'post',
data: createPostTable(properties),
};
var charCodeToGlyphId = [], charCode;
// Helper function to try to skip mapping of empty glyphs.
@ -2504,12 +2443,6 @@ var Font = (function FontClosure() {
charCodeToGlyphId[charCode] = glyphId;
}
});
if (dupFirstEntry && (isCidToGidMapEmpty || !charCodeToGlyphId[0])) {
// We don't duplicate the first entry in the `charCodeToGlyphId` map
// if the font has a `CIDToGIDMap` which has already mapped the first
// entry to a non-zero `glyphId` (fixes issue7544.pdf).
charCodeToGlyphId[0] = numGlyphs - 1;
}
} else {
// Most of the following logic in this code branch is based on the
// 9.6.6.4 of the PDF spec.
@ -2620,13 +2553,21 @@ var Font = (function FontClosure() {
charCodeToGlyphId[0] = 0;
}
// Typically glyph 0 is duplicated and the mapping must be updated, but if
// there isn't enough room to duplicate, the glyph id is left the same. In
// this case, glyph 0 may not work correctly, but that is better than
// having the whole font fail.
let glyphZeroId = numGlyphsOut - 1;
if (!dupFirstEntry) {
glyphZeroId = 0;
}
// Converting glyphs and ids into font's cmap table
var newMapping = adjustMapping(charCodeToGlyphId, properties,
missingGlyphs);
var newMapping = adjustMapping(charCodeToGlyphId, hasGlyph, glyphZeroId);
this.toFontChar = newMapping.toFontChar;
tables['cmap'] = {
tag: 'cmap',
data: createCmapTable(newMapping.charCodeToGlyphId, numGlyphs),
data: createCmapTable(newMapping.charCodeToGlyphId, numGlyphsOut),
};
if (!tables['OS/2'] || !validateOS2Table(tables['OS/2'])) {
@ -2637,14 +2578,6 @@ var Font = (function FontClosure() {
};
}
// Rewrite the 'post' table if needed
if (!tables['post']) {
tables['post'] = {
tag: 'post',
data: createPostTable(properties),
};
}
if (!isTrueType) {
try {
// Trying to repair CFF file
@ -2652,6 +2585,7 @@ var Font = (function FontClosure() {
var parser = new CFFParser(cffFile, properties,
SEAC_ANALYSIS_ENABLED);
cff = parser.parse();
cff.duplicateFirstGlyph();
var compiler = new CFFCompiler(cff);
tables['CFF '].data = compiler.compile();
} catch (e) {
@ -2688,8 +2622,16 @@ var Font = (function FontClosure() {
adjustToUnicode(properties, properties.builtInEncoding);
}
// Type 1 fonts have a notdef inserted at the beginning, so glyph 0
// becomes glyph 1. In a CFF font glyph 0 is appended to the end of the
// char strings.
let glyphZeroId = 1;
if (font instanceof CFFFont) {
glyphZeroId = font.numGlyphs - 1;
}
var mapping = font.getGlyphMapping(properties);
var newMapping = adjustMapping(mapping, properties, Object.create(null));
var newMapping = adjustMapping(mapping, font.hasGlyphId.bind(font),
glyphZeroId);
this.toFontChar = newMapping.toFontChar;
var numGlyphs = font.numGlyphs;
@ -2927,12 +2869,14 @@ var Font = (function FontClosure() {
var seac = this.seacMap[charcode];
fontCharCode = seac.baseFontCharCode;
accent = {
fontChar: String.fromCharCode(seac.accentFontCharCode),
fontChar: String.fromCodePoint(seac.accentFontCharCode),
offset: seac.accentOffset,
};
}
var fontChar = String.fromCharCode(fontCharCode);
var fontChar = typeof fontCharCode === 'number' ?
String.fromCodePoint(fontCharCode) :
'';
var glyph = this.glyphCache[charcode];
if (!glyph ||
@ -3283,6 +3227,18 @@ var Type1Font = (function Type1FontClosure() {
return type1FontGlyphMapping(properties, builtInEncoding, glyphNames);
},
hasGlyphId: function Type1Font_hasGlyphID(id) {
if (id < 0 || id >= this.numGlyphs) {
return false;
}
if (id === 0) {
// notdef is always defined.
return true;
}
var glyph = this.charstrings[id - 1];
return glyph.charstring.length > 0;
},
getSeacs: function Type1Font_getSeacs(charstrings) {
var i, ii;
var seacMap = [];
@ -3382,14 +3338,7 @@ var Type1Font = (function Type1FontClosure() {
var charStringsIndex = new CFFIndex();
charStringsIndex.add([0x8B, 0x0E]); // .notdef
for (i = 0; i < count; i++) {
var glyph = glyphs[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([0x8B, 0x0E]); // .notdef
continue;
}
charStringsIndex.add(glyph);
charStringsIndex.add(glyphs[i]);
}
cff.charStrings = charStringsIndex;
@ -3448,6 +3397,7 @@ var CFFFont = (function CFFFontClosure() {
var parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED);
this.cff = parser.parse();
this.cff.duplicateFirstGlyph();
var compiler = new CFFCompiler(this.cff);
this.seacs = this.cff.seacs;
try {
@ -3498,37 +3448,20 @@ var CFFFont = (function CFFFontClosure() {
charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets);
return charCodeToGlyphId;
},
hasGlyphId: function CFFFont_hasGlyphID(id) {
return this.cff.hasGlyphId(id);
},
};
return CFFFont;
})();
// Workaround for seac on Windows.
(function checkSeacSupport() {
if (typeof navigator !== 'undefined' && /Windows/.test(navigator.userAgent)) {
SEAC_ANALYSIS_ENABLED = true;
}
})();
// Workaround for Private Use Area characters in Chrome on Windows
// http://code.google.com/p/chromium/issues/detail?id=122465
// https://github.com/mozilla/pdf.js/issues/1689
(function checkChromeWindows() {
if (typeof navigator !== 'undefined' &&
/Windows.*Chrome/.test(navigator.userAgent)) {
SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = true;
}
})();
export {
SEAC_ANALYSIS_ENABLED,
PRIVATE_USE_OFFSET_START,
PRIVATE_USE_OFFSET_END,
ErrorFont,
Font,
FontFlags,
ToUnicodeMap,
IdentityToUnicodeMap,
ProblematicCharRanges,
getFontType,
};

View File

@ -165,6 +165,24 @@ const hasDOM = typeof window === 'object' && typeof document === 'object';
globalScope.WeakMap = require('core-js/fn/weak-map');
})();
// Provides support for String.codePointAt in legacy browsers.
// Support: IE11.
(function checkStringCodePointAt() {
if (String.codePointAt) {
return;
}
String.codePointAt = require('core-js/fn/string/code-point-at');
})();
// Provides support for String.fromCodePoint in legacy browsers.
// Support: IE11.
(function checkStringFromCodePoint() {
if (String.fromCodePoint) {
return;
}
String.fromCodePoint = require('core-js/fn/string/from-code-point');
})();
} // End of !PDFJSDev.test('CHROME')
// Provides support for Object.values in legacy browsers.

View File

@ -114,6 +114,7 @@
!issue5686.pdf
!issue3928.pdf
!clippath.pdf
!issue8795_reduced.pdf
!close-path-bug.pdf
!issue6019.pdf
!issue6621.pdf
@ -281,6 +282,7 @@
!issue5475.pdf
!annotation-border-styles.pdf
!IdentityToUnicodeMap_charCodeOf.pdf
!PDFJS-9279-reduced.pdf
!issue5481.pdf
!issue5567.pdf
!issue5701.pdf
@ -303,6 +305,7 @@
!issue7014.pdf
!issue8187.pdf
!annotation-link-text-popup.pdf
!issue9278.pdf
!annotation-text-without-popup.pdf
!annotation-underline.pdf
!annotation-strikeout.pdf

Binary file not shown.

View File

@ -0,0 +1 @@
https://web.archive.org/web/20171116124714/https://www.unicode.org/charts/PDF/U11A50.pdf

File diff suppressed because one or more lines are too long

BIN
test/pdfs/issue9278.pdf Normal file

Binary file not shown.

View File

@ -122,6 +122,12 @@
"lastPage": 1,
"type": "eq"
},
{ "id": "issue8795_reduced",
"file": "pdfs/issue8795_reduced.pdf",
"md5": "3ce58fa4aff351d46c42e0677d582099",
"rounds": 1,
"type": "eq"
},
{ "id": "issue2391-1",
"file": "pdfs/issue2391-1.pdf",
"md5": "25ae9cb959612e7b343b55da63af2716",
@ -2443,6 +2449,12 @@
"link": false,
"type": "text"
},
{ "id": "issue9278",
"file": "pdfs/issue9278.pdf",
"md5": "9819c3a5715c1a46ea5a6740f9ead3da",
"rounds": 1,
"type": "eq"
},
{ "id": "bug894572",
"file": "pdfs/bug894572.pdf",
"md5": "e54a6b0451939f685ed37e3d46e16158",
@ -3467,6 +3479,15 @@
"rounds": 1,
"type": "eq"
},
{ "id": "bug1425312",
"file": "pdfs/bug1425312.pdf",
"md5": "5b1e7d3e4ba7792fab2b69d1836df5a9",
"link": true,
"rounds": 1,
"firstPage": 2,
"lastPage": 2,
"type": "eq"
},
{ "id": "issue3438",
"file": "pdfs/issue3438.pdf",
"md5": "5aa3340b0920b65a377f697587668f89",
@ -3706,6 +3727,12 @@
"type": "eq",
"about": "Image mask in higher resolution than the image itself"
},
{ "id": "PDFJS-9279-reduced",
"file": "pdfs/PDFJS-9279-reduced.pdf",
"md5": "a562a25596e9fe571ac6fb5b9f561974",
"rounds": 1,
"type": "eq"
},
{ "id": "issue4436",
"file": "pdfs/issue4436r.pdf",
"md5": "4e43d692d213f56674fcac92110c7364",

View File

@ -14,7 +14,7 @@
*/
import {
CFFCompiler, CFFParser, CFFStrings
CFFCompiler, CFFFDSelect, CFFParser, CFFStrings
} from '../../src/core/cff_parser';
import { SEAC_ANALYSIS_ENABLED } from '../../src/core/fonts';
import { Stream } from '../../src/core/stream';
@ -311,7 +311,7 @@ describe('CFFParser', function() {
var fdSelect = parser.parseFDSelect(0, 2);
expect(fdSelect.fdSelect).toEqual([0, 1]);
expect(fdSelect.raw).toEqual(bytes);
expect(fdSelect.format).toEqual(0);
});
it('parses fdselect format 3', function() {
@ -327,7 +327,7 @@ describe('CFFParser', function() {
var fdSelect = parser.parseFDSelect(0, 4);
expect(fdSelect.fdSelect).toEqual([9, 9, 0xa, 0xa]);
expect(fdSelect.raw).toEqual(bytes);
expect(fdSelect.format).toEqual(3);
});
it('parses invalid fdselect format 3 (bug 1146106)', function() {
@ -343,8 +343,7 @@ describe('CFFParser', function() {
var fdSelect = parser.parseFDSelect(0, 4);
expect(fdSelect.fdSelect).toEqual([9, 9, 0xa, 0xa]);
bytes[3] = bytes[4] = 0x00; // The adjusted first range, first gid.
expect(fdSelect.raw).toEqual(bytes);
expect(fdSelect.format).toEqual(3);
});
// TODO fdArray
@ -400,5 +399,52 @@ describe('CFFCompiler', function() {
expect(names[0].length).toEqual(127);
});
it('compiles fdselect format 0', function() {
var fdSelect = new CFFFDSelect(0, [3, 2, 1]);
var c = new CFFCompiler();
var out = c.compileFDSelect(fdSelect);
expect(out).toEqual([
0, // format
3, // gid: 0 fd 3
2, // gid: 1 fd 3
1, // gid: 2 fd 3
]);
});
it('compiles fdselect format 3', function() {
var fdSelect = new CFFFDSelect(3, [0, 0, 1, 1]);
var c = new CFFCompiler();
var out = c.compileFDSelect(fdSelect);
expect(out).toEqual([
3, // format
0, // nRanges (high)
2, // nRanges (low)
0, // range struct 0 - first (high)
0, // range struct 0 - first (low)
0, // range struct 0 - fd
0, // range struct 0 - first (high)
2, // range struct 0 - first (low)
1, // range struct 0 - fd
0, // sentinel (high)
4, // sentinel (low)
]);
});
it('compiles fdselect format 3, single range', function() {
var fdSelect = new CFFFDSelect(3, [0, 0]);
var c = new CFFCompiler();
var out = c.compileFDSelect(fdSelect);
expect(out).toEqual([
3, // format
0, // nRanges (high)
1, // nRanges (low)
0, // range struct 0 - first (high)
0, // range struct 0 - first (low)
0, // range struct 0 - fd
0, // sentinel (high)
2, // sentinel (low)
]);
});
// TODO a lot more compiler tests
});

View File

@ -18,7 +18,6 @@
"dom_utils_spec.js",
"encodings_spec.js",
"evaluator_spec.js",
"fonts_spec.js",
"function_spec.js",
"message_handler_spec.js",
"metadata_spec.js",

View File

@ -1,91 +0,0 @@
/* Copyright 2017 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
PRIVATE_USE_OFFSET_END, PRIVATE_USE_OFFSET_START, ProblematicCharRanges
} from '../../src/core/fonts';
/**
* Used to validate the entries in `ProblematicCharRanges`, and to ensure that
* its total number of characters does not exceed the PUA (Private Use Area)
* length.
* @returns {Object} An object with {number} `numChars`, {number} `puaLength`,
* and {number} `percentage` parameters.
*/
var checkProblematicCharRanges = function checkProblematicCharRanges() {
function printRange(limits) {
return '[' + limits.lower.toString('16').toUpperCase() + ', ' +
limits.upper.toString('16').toUpperCase() + ')';
}
var numRanges = ProblematicCharRanges.length;
if (numRanges % 2 !== 0) {
throw new Error('Char ranges must contain an even number of elements.');
}
var prevLimits, numChars = 0;
for (var i = 0; i < numRanges; i += 2) {
var limits = {
lower: ProblematicCharRanges[i],
upper: ProblematicCharRanges[i + 1],
};
if (!Number.isInteger(limits.lower) || !Number.isInteger(limits.upper)) {
throw new Error('Range endpoints must be integers: ' +
printRange(limits));
}
if (limits.lower < 0 || limits.upper < 0) {
throw new Error('Range endpoints must be non-negative: ' +
printRange(limits));
}
var range = limits.upper - limits.lower;
if (range < 1) {
throw new Error('Range must contain at least one element: ' +
printRange(limits));
}
if (prevLimits) {
if (limits.lower < prevLimits.lower) {
throw new Error('Ranges must be sorted in ascending order: ' +
printRange(limits) + ', ' + printRange(prevLimits));
}
if (limits.lower < prevLimits.upper) {
throw new Error('Ranges must not overlap: ' +
printRange(limits) + ', ' + printRange(prevLimits));
}
}
prevLimits = {
lower: limits.lower,
upper: limits.upper,
};
// The current range is OK.
numChars += range;
}
var puaLength = (PRIVATE_USE_OFFSET_END + 1) - PRIVATE_USE_OFFSET_START;
if (numChars > puaLength) {
throw new Error('Total number of chars must not exceed the PUA length.');
}
return {
numChars,
puaLength,
percentage: 100 * (numChars / puaLength),
};
};
describe('Fonts', function() {
it('checkProblematicCharRanges', function() {
var EXPECTED_PERCENTAGE = 100;
var result = checkProblematicCharRanges();
expect(result.percentage).toBeLessThan(EXPECTED_PERCENTAGE);
});
});

View File

@ -60,7 +60,6 @@ function initializePDFJS(callback) {
'pdfjs-test/unit/dom_utils_spec',
'pdfjs-test/unit/encodings_spec',
'pdfjs-test/unit/evaluator_spec',
'pdfjs-test/unit/fonts_spec',
'pdfjs-test/unit/function_spec',
'pdfjs-test/unit/message_handler_spec',
'pdfjs-test/unit/metadata_spec',