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:
commit
66422eb83e
@ -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,
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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.
|
||||
|
3
test/pdfs/.gitignore
vendored
3
test/pdfs/.gitignore
vendored
@ -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
|
||||
|
BIN
test/pdfs/PDFJS-9279-reduced.pdf
Normal file
BIN
test/pdfs/PDFJS-9279-reduced.pdf
Normal file
Binary file not shown.
1
test/pdfs/bug1425312.pdf.link
Normal file
1
test/pdfs/bug1425312.pdf.link
Normal file
@ -0,0 +1 @@
|
||||
https://web.archive.org/web/20171116124714/https://www.unicode.org/charts/PDF/U11A50.pdf
|
15887
test/pdfs/issue8795_reduced.pdf
Normal file
15887
test/pdfs/issue8795_reduced.pdf
Normal file
File diff suppressed because one or more lines are too long
BIN
test/pdfs/issue9278.pdf
Normal file
BIN
test/pdfs/issue9278.pdf
Normal file
Binary file not shown.
@ -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",
|
||||
|
@ -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
|
||||
});
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
@ -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',
|
||||
|
Loading…
x
Reference in New Issue
Block a user