How to use adjustToUnicode method in wpt

Best JavaScript code snippet using wpt

fonts.js

Source:fonts.js Github

copy

Full Screen

...125 glyphsWidths[glyph] *= scale;126 }127 properties.defaultWidth *= scale;128}129function adjustToUnicode(properties, builtInEncoding) {130 if (properties.isInternalFont) {131 return;132 }133 if (builtInEncoding === properties.defaultEncoding) {134 return; // No point in trying to adjust `toUnicode` if the encodings match.135 }136 if (properties.toUnicode instanceof IdentityToUnicodeMap) {137 return;138 }139 const toUnicode = [],140 glyphsUnicodeMap = getGlyphsUnicode();141 for (const charCode in builtInEncoding) {142 if (properties.hasIncludedToUnicodeMap) {143 if (properties.toUnicode.has(charCode)) {144 continue; // The font dictionary has a `ToUnicode` entry.145 }146 } else if (properties.hasEncoding) {147 if (148 properties.differences.length === 0 ||149 properties.differences[charCode] !== undefined150 ) {151 continue; // The font dictionary has an `Encoding`/`Differences` entry.152 }153 }154 const glyphName = builtInEncoding[charCode];155 const unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);156 if (unicode !== -1) {157 toUnicode[charCode] = String.fromCharCode(unicode);158 }159 }160 if (toUnicode.length > 0) {161 properties.toUnicode.amend(toUnicode);162 }163}164/**165 * NOTE: This function should only be called at the *end* of font-parsing,166 * after e.g. `adjustToUnicode` has run, to prevent any issues.167 */168function amendFallbackToUnicode(properties) {169 if (!properties.fallbackToUnicode) {170 return;171 }172 if (properties.toUnicode instanceof IdentityToUnicodeMap) {173 return;174 }175 const toUnicode = [];176 for (const charCode in properties.fallbackToUnicode) {177 if (properties.toUnicode.has(charCode)) {178 continue; // The font dictionary has a `ToUnicode` entry.179 }180 toUnicode[charCode] = properties.fallbackToUnicode[charCode];181 }182 if (toUnicode.length > 0) {183 properties.toUnicode.amend(toUnicode);184 }185}186class Glyph {187 constructor(188 originalCharCode,189 fontChar,190 unicode,191 accent,192 width,193 vmetric,194 operatorListId,195 isSpace,196 isInFont197 ) {198 this.originalCharCode = originalCharCode;199 this.fontChar = fontChar;200 this.unicode = unicode;201 this.accent = accent;202 this.width = width;203 this.vmetric = vmetric;204 this.operatorListId = operatorListId;205 this.isSpace = isSpace;206 this.isInFont = isInFont;207 const category = getCharUnicodeCategory(unicode);208 this.isWhitespace = category.isWhitespace;209 this.isZeroWidthDiacritic = category.isZeroWidthDiacritic;210 this.isInvisibleFormatMark = category.isInvisibleFormatMark;211 }212 matchesForCache(213 originalCharCode,214 fontChar,215 unicode,216 accent,217 width,218 vmetric,219 operatorListId,220 isSpace,221 isInFont222 ) {223 return (224 this.originalCharCode === originalCharCode &&225 this.fontChar === fontChar &&226 this.unicode === unicode &&227 this.accent === accent &&228 this.width === width &&229 this.vmetric === vmetric &&230 this.operatorListId === operatorListId &&231 this.isSpace === isSpace &&232 this.isInFont === isInFont233 );234 }235}236function int16(b0, b1) {237 return (b0 << 8) + b1;238}239function writeSignedInt16(bytes, index, value) {240 bytes[index + 1] = value;241 bytes[index] = value >>> 8;242}243function signedInt16(b0, b1) {244 const value = (b0 << 8) + b1;245 return value & (1 << 15) ? value - 0x10000 : value;246}247function int32(b0, b1, b2, b3) {248 return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;249}250function string16(value) {251 if (252 typeof PDFJSDev === "undefined" ||253 PDFJSDev.test("!PRODUCTION || TESTING")254 ) {255 assert(256 typeof value === "number" && Math.abs(value) < 2 ** 16,257 `string16: Unexpected input "${value}".`258 );259 }260 return String.fromCharCode((value >> 8) & 0xff, value & 0xff);261}262function safeString16(value) {263 if (264 typeof PDFJSDev === "undefined" ||265 PDFJSDev.test("!PRODUCTION || TESTING")266 ) {267 assert(268 typeof value === "number" && !Number.isNaN(value),269 `safeString16: Unexpected input "${value}".`270 );271 }272 // clamp value to the 16-bit int range273 if (value > 0x7fff) {274 value = 0x7fff;275 } else if (value < -0x8000) {276 value = -0x8000;277 }278 return String.fromCharCode((value >> 8) & 0xff, value & 0xff);279}280function isTrueTypeFile(file) {281 const header = file.peekBytes(4);282 return (283 readUint32(header, 0) === 0x00010000 || bytesToString(header) === "true"284 );285}286function isTrueTypeCollectionFile(file) {287 const header = file.peekBytes(4);288 return bytesToString(header) === "ttcf";289}290function isOpenTypeFile(file) {291 const header = file.peekBytes(4);292 return bytesToString(header) === "OTTO";293}294function isType1File(file) {295 const header = file.peekBytes(2);296 // All Type1 font programs must begin with the comment '%!' (0x25 + 0x21).297 if (header[0] === 0x25 && header[1] === 0x21) {298 return true;299 }300 // ... obviously some fonts violate that part of the specification,301 // please refer to the comment in |Type1Font| below (pfb file header).302 if (header[0] === 0x80 && header[1] === 0x01) {303 return true;304 }305 return false;306}307/**308 * Compared to other font formats, the header in CFF files is not constant309 * but contains version numbers. To reduce the possibility of misclassifying310 * font files as CFF, it's recommended to check for other font formats first.311 */312function isCFFFile(file) {313 const header = file.peekBytes(4);314 if (315 /* major version, [1, 255] */ header[0] >= 1 &&316 /* minor version, [0, 255]; header[1] */317 /* header size, [0, 255]; header[2] */318 /* offset(0) size, [1, 4] */ header[3] >= 1 &&319 header[3] <= 4320 ) {321 return true;322 }323 return false;324}325function getFontFileType(file, { type, subtype, composite }) {326 let fileType, fileSubtype;327 if (isTrueTypeFile(file) || isTrueTypeCollectionFile(file)) {328 if (composite) {329 fileType = "CIDFontType2";330 } else {331 fileType = "TrueType";332 }333 } else if (isOpenTypeFile(file)) {334 if (composite) {335 fileType = "CIDFontType2";336 } else {337 fileType = "OpenType";338 }339 } else if (isType1File(file)) {340 if (composite) {341 fileType = "CIDFontType0";342 } else {343 fileType = type === "MMType1" ? "MMType1" : "Type1";344 }345 } else if (isCFFFile(file)) {346 if (composite) {347 fileType = "CIDFontType0";348 fileSubtype = "CIDFontType0C";349 } else {350 fileType = type === "MMType1" ? "MMType1" : "Type1";351 fileSubtype = "Type1C";352 }353 } else {354 warn("getFontFileType: Unable to detect correct font file Type/Subtype.");355 fileType = type;356 fileSubtype = subtype;357 }358 return [fileType, fileSubtype];359}360function applyStandardFontGlyphMap(map, glyphMap) {361 for (const charCode in glyphMap) {362 map[+charCode] = glyphMap[charCode];363 }364}365function buildToFontChar(encoding, glyphsUnicodeMap, differences) {366 const toFontChar = [];367 let unicode;368 for (let i = 0, ii = encoding.length; i < ii; i++) {369 unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap);370 if (unicode !== -1) {371 toFontChar[i] = unicode;372 }373 }374 for (const charCode in differences) {375 unicode = getUnicodeForGlyph(differences[charCode], glyphsUnicodeMap);376 if (unicode !== -1) {377 toFontChar[+charCode] = unicode;378 }379 }380 return toFontChar;381}382function convertCidString(charCode, cid, shouldThrow = false) {383 switch (cid.length) {384 case 1:385 return cid.charCodeAt(0);386 case 2:387 return (cid.charCodeAt(0) << 8) | cid.charCodeAt(1);388 }389 const msg = `Unsupported CID string (charCode ${charCode}): "${cid}".`;390 if (shouldThrow) {391 throw new FormatError(msg);392 }393 warn(msg);394 return cid;395}396/**397 * Rebuilds the char code to glyph ID map by moving all char codes to the398 * private use area. This is done to avoid issues with various problematic399 * unicode areas where either a glyph won't be drawn or is deformed by a400 * shaper.401 * @returns {Object} Two properties:402 * 'toFontChar' - maps original char codes(the value that will be read403 * from commands such as show text) to the char codes that will be used in the404 * font that we build405 * 'charCodeToGlyphId' - maps the new font char codes to glyph ids406 */407function adjustMapping(charCodeToGlyphId, hasGlyph, newGlyphZeroId) {408 const newMap = Object.create(null);409 const toFontChar = [];410 let privateUseAreaIndex = 0;411 let nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0];412 let privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1];413 for (let originalCharCode in charCodeToGlyphId) {414 originalCharCode |= 0;415 let glyphId = charCodeToGlyphId[originalCharCode];416 // For missing glyphs don't create the mappings so the glyph isn't417 // drawn.418 if (!hasGlyph(glyphId)) {419 continue;420 }421 if (nextAvailableFontCharCode > privateUseOffetEnd) {422 privateUseAreaIndex++;423 if (privateUseAreaIndex >= PRIVATE_USE_AREAS.length) {424 warn("Ran out of space in font private use area.");425 break;426 }427 nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0];428 privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1];429 }430 const fontCharCode = nextAvailableFontCharCode++;431 if (glyphId === 0) {432 glyphId = newGlyphZeroId;433 }434 newMap[fontCharCode] = glyphId;435 toFontChar[originalCharCode] = fontCharCode;436 }437 return {438 toFontChar,439 charCodeToGlyphId: newMap,440 nextAvailableFontCharCode,441 };442}443function getRanges(glyphs, numGlyphs) {444 // Array.sort() sorts by characters, not numerically, so convert to an445 // array of characters.446 const codes = [];447 for (const charCode in glyphs) {448 // Remove an invalid glyph ID mappings to make OTS happy.449 if (glyphs[charCode] >= numGlyphs) {450 continue;451 }452 codes.push({ fontCharCode: charCode | 0, glyphId: glyphs[charCode] });453 }454 // Some fonts have zero glyphs and are used only for text selection, but455 // there needs to be at least one to build a valid cmap table.456 if (codes.length === 0) {457 codes.push({ fontCharCode: 0, glyphId: 0 });458 }459 codes.sort(function fontGetRangesSort(a, b) {460 return a.fontCharCode - b.fontCharCode;461 });462 // Split the sorted codes into ranges.463 const ranges = [];464 const length = codes.length;465 for (let n = 0; n < length; ) {466 const start = codes[n].fontCharCode;467 const codeIndices = [codes[n].glyphId];468 ++n;469 let end = start;470 while (n < length && end + 1 === codes[n].fontCharCode) {471 codeIndices.push(codes[n].glyphId);472 ++end;473 ++n;474 if (end === 0xffff) {475 break;476 }477 }478 ranges.push([start, end, codeIndices]);479 }480 return ranges;481}482function createCmapTable(glyphs, numGlyphs) {483 const ranges = getRanges(glyphs, numGlyphs);484 const numTables = ranges[ranges.length - 1][1] > 0xffff ? 2 : 1;485 let cmap =486 "\x00\x00" + // version487 string16(numTables) + // numTables488 "\x00\x03" + // platformID489 "\x00\x01" + // encodingID490 string32(4 + numTables * 8); // start of the table record491 let i, ii, j, jj;492 for (i = ranges.length - 1; i >= 0; --i) {493 if (ranges[i][0] <= 0xffff) {494 break;495 }496 }497 const bmpLength = i + 1;498 if (ranges[i][0] < 0xffff && ranges[i][1] === 0xffff) {499 ranges[i][1] = 0xfffe;500 }501 const trailingRangesCount = ranges[i][1] < 0xffff ? 1 : 0;502 const segCount = bmpLength + trailingRangesCount;503 const searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2);504 // Fill up the 4 parallel arrays describing the segments.505 let startCount = "";506 let endCount = "";507 let idDeltas = "";508 let idRangeOffsets = "";509 let glyphsIds = "";510 let bias = 0;511 let range, start, end, codes;512 for (i = 0, ii = bmpLength; i < ii; i++) {513 range = ranges[i];514 start = range[0];515 end = range[1];516 startCount += string16(start);517 endCount += string16(end);518 codes = range[2];519 let contiguous = true;520 for (j = 1, jj = codes.length; j < jj; ++j) {521 if (codes[j] !== codes[j - 1] + 1) {522 contiguous = false;523 break;524 }525 }526 if (!contiguous) {527 const offset = (segCount - i) * 2 + bias * 2;528 bias += end - start + 1;529 idDeltas += string16(0);530 idRangeOffsets += string16(offset);531 for (j = 0, jj = codes.length; j < jj; ++j) {532 glyphsIds += string16(codes[j]);533 }534 } else {535 const startCode = codes[0];536 idDeltas += string16((startCode - start) & 0xffff);537 idRangeOffsets += string16(0);538 }539 }540 if (trailingRangesCount > 0) {541 endCount += "\xFF\xFF";542 startCount += "\xFF\xFF";543 idDeltas += "\x00\x01";544 idRangeOffsets += "\x00\x00";545 }546 const format314 =547 "\x00\x00" + // language548 string16(2 * segCount) +549 string16(searchParams.range) +550 string16(searchParams.entry) +551 string16(searchParams.rangeShift) +552 endCount +553 "\x00\x00" +554 startCount +555 idDeltas +556 idRangeOffsets +557 glyphsIds;558 let format31012 = "";559 let header31012 = "";560 if (numTables > 1) {561 cmap +=562 "\x00\x03" + // platformID563 "\x00\x0A" + // encodingID564 string32(4 + numTables * 8 + 4 + format314.length); // start of the table record565 format31012 = "";566 for (i = 0, ii = ranges.length; i < ii; i++) {567 range = ranges[i];568 start = range[0];569 codes = range[2];570 let code = codes[0];571 for (j = 1, jj = codes.length; j < jj; ++j) {572 if (codes[j] !== codes[j - 1] + 1) {573 end = range[0] + j - 1;574 format31012 +=575 string32(start) + // startCharCode576 string32(end) + // endCharCode577 string32(code); // startGlyphID578 start = end + 1;579 code = codes[j];580 }581 }582 format31012 +=583 string32(start) + // startCharCode584 string32(range[1]) + // endCharCode585 string32(code); // startGlyphID586 }587 header31012 =588 "\x00\x0C" + // format589 "\x00\x00" + // reserved590 string32(format31012.length + 16) + // length591 "\x00\x00\x00\x00" + // language592 string32(format31012.length / 12); // nGroups593 }594 return (595 cmap +596 "\x00\x04" + // format597 string16(format314.length + 4) + // length598 format314 +599 header31012 +600 format31012601 );602}603function validateOS2Table(os2, file) {604 file.pos = (file.start || 0) + os2.offset;605 const version = file.getUint16();606 // TODO verify all OS/2 tables fields, but currently we validate only those607 // that give us issues608 file.skip(60); // skipping type, misc sizes, panose, unicode ranges609 const selection = file.getUint16();610 if (version < 4 && selection & 0x0300) {611 return false;612 }613 const firstChar = file.getUint16();614 const lastChar = file.getUint16();615 if (firstChar > lastChar) {616 return false;617 }618 file.skip(6); // skipping sTypoAscender/Descender/LineGap619 const usWinAscent = file.getUint16();620 if (usWinAscent === 0) {621 // makes font unreadable by windows622 return false;623 }624 // OS/2 appears to be valid, resetting some fields625 os2.data[8] = os2.data[9] = 0; // IE rejects fonts if fsType != 0626 return true;627}628function createOS2Table(properties, charstrings, override) {629 override = override || {630 unitsPerEm: 0,631 yMax: 0,632 yMin: 0,633 ascent: 0,634 descent: 0,635 };636 let ulUnicodeRange1 = 0;637 let ulUnicodeRange2 = 0;638 let ulUnicodeRange3 = 0;639 let ulUnicodeRange4 = 0;640 let firstCharIndex = null;641 let lastCharIndex = 0;642 if (charstrings) {643 for (let code in charstrings) {644 code |= 0;645 if (firstCharIndex > code || !firstCharIndex) {646 firstCharIndex = code;647 }648 if (lastCharIndex < code) {649 lastCharIndex = code;650 }651 const position = getUnicodeRangeFor(code);652 if (position < 32) {653 ulUnicodeRange1 |= 1 << position;654 } else if (position < 64) {655 ulUnicodeRange2 |= 1 << (position - 32);656 } else if (position < 96) {657 ulUnicodeRange3 |= 1 << (position - 64);658 } else if (position < 123) {659 ulUnicodeRange4 |= 1 << (position - 96);660 } else {661 throw new FormatError(662 "Unicode ranges Bits > 123 are reserved for internal usage"663 );664 }665 }666 if (lastCharIndex > 0xffff) {667 // OS2 only supports a 16 bit int. The spec says if supplementary668 // characters are used the field should just be set to 0xFFFF.669 lastCharIndex = 0xffff;670 }671 } else {672 // TODO673 firstCharIndex = 0;674 lastCharIndex = 255;675 }676 const bbox = properties.bbox || [0, 0, 0, 0];677 const unitsPerEm =678 override.unitsPerEm ||679 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0];680 // if the font units differ to the PDF glyph space units681 // then scale up the values682 const scale = properties.ascentScaled683 ? 1.0684 : unitsPerEm / PDF_GLYPH_SPACE_UNITS;685 const typoAscent =686 override.ascent || Math.round(scale * (properties.ascent || bbox[3]));687 let typoDescent =688 override.descent || Math.round(scale * (properties.descent || bbox[1]));689 if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) {690 typoDescent = -typoDescent; // fixing incorrect descent691 }692 const winAscent = override.yMax || typoAscent;693 const winDescent = -override.yMin || -typoDescent;694 return (695 "\x00\x03" + // version696 "\x02\x24" + // xAvgCharWidth697 "\x01\xF4" + // usWeightClass698 "\x00\x05" + // usWidthClass699 "\x00\x00" + // fstype (0 to let the font loads via font-face on IE)700 "\x02\x8A" + // ySubscriptXSize701 "\x02\xBB" + // ySubscriptYSize702 "\x00\x00" + // ySubscriptXOffset703 "\x00\x8C" + // ySubscriptYOffset704 "\x02\x8A" + // ySuperScriptXSize705 "\x02\xBB" + // ySuperScriptYSize706 "\x00\x00" + // ySuperScriptXOffset707 "\x01\xDF" + // ySuperScriptYOffset708 "\x00\x31" + // yStrikeOutSize709 "\x01\x02" + // yStrikeOutPosition710 "\x00\x00" + // sFamilyClass711 "\x00\x00\x06" +712 String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) +713 "\x00\x00\x00\x00\x00\x00" + // Panose714 string32(ulUnicodeRange1) + // ulUnicodeRange1 (Bits 0-31)715 string32(ulUnicodeRange2) + // ulUnicodeRange2 (Bits 32-63)716 string32(ulUnicodeRange3) + // ulUnicodeRange3 (Bits 64-95)717 string32(ulUnicodeRange4) + // ulUnicodeRange4 (Bits 96-127)718 "\x2A\x32\x31\x2A" + // achVendID719 string16(properties.italicAngle ? 1 : 0) + // fsSelection720 string16(firstCharIndex || properties.firstChar) + // usFirstCharIndex721 string16(lastCharIndex || properties.lastChar) + // usLastCharIndex722 string16(typoAscent) + // sTypoAscender723 string16(typoDescent) + // sTypoDescender724 "\x00\x64" + // sTypoLineGap (7%-10% of the unitsPerEM value)725 string16(winAscent) + // usWinAscent726 string16(winDescent) + // usWinDescent727 "\x00\x00\x00\x00" + // ulCodePageRange1 (Bits 0-31)728 "\x00\x00\x00\x00" + // ulCodePageRange2 (Bits 32-63)729 string16(properties.xHeight) + // sxHeight730 string16(properties.capHeight) + // sCapHeight731 string16(0) + // usDefaultChar732 string16(firstCharIndex || properties.firstChar) + // usBreakChar733 "\x00\x03"734 ); // usMaxContext735}736function createPostTable(properties) {737 const angle = Math.floor(properties.italicAngle * 2 ** 16);738 return (739 "\x00\x03\x00\x00" + // Version number740 string32(angle) + // italicAngle741 "\x00\x00" + // underlinePosition742 "\x00\x00" + // underlineThickness743 string32(properties.fixedPitch ? 1 : 0) + // isFixedPitch744 "\x00\x00\x00\x00" + // minMemType42745 "\x00\x00\x00\x00" + // maxMemType42746 "\x00\x00\x00\x00" + // minMemType1747 "\x00\x00\x00\x00"748 ); // maxMemType1749}750function createPostscriptName(name) {751 // See https://docs.microsoft.com/en-us/typography/opentype/spec/recom#name.752 return name.replace(/[^\x21-\x7E]|[[\](){}<>/%]/g, "").slice(0, 63);753}754function createNameTable(name, proto) {755 if (!proto) {756 proto = [[], []]; // no strings and unicode strings757 }758 const strings = [759 proto[0][0] || "Original licence", // 0.Copyright760 proto[0][1] || name, // 1.Font family761 proto[0][2] || "Unknown", // 2.Font subfamily (font weight)762 proto[0][3] || "uniqueID", // 3.Unique ID763 proto[0][4] || name, // 4.Full font name764 proto[0][5] || "Version 0.11", // 5.Version765 proto[0][6] || createPostscriptName(name), // 6.Postscript name766 proto[0][7] || "Unknown", // 7.Trademark767 proto[0][8] || "Unknown", // 8.Manufacturer768 proto[0][9] || "Unknown", // 9.Designer769 ];770 // Mac want 1-byte per character strings while Windows want771 // 2-bytes per character, so duplicate the names table772 const stringsUnicode = [];773 let i, ii, j, jj, str;774 for (i = 0, ii = strings.length; i < ii; i++) {775 str = proto[1][i] || strings[i];776 const strBufUnicode = [];777 for (j = 0, jj = str.length; j < jj; j++) {778 strBufUnicode.push(string16(str.charCodeAt(j)));779 }780 stringsUnicode.push(strBufUnicode.join(""));781 }782 const names = [strings, stringsUnicode];783 const platforms = ["\x00\x01", "\x00\x03"];784 const encodings = ["\x00\x00", "\x00\x01"];785 const languages = ["\x00\x00", "\x04\x09"];786 const namesRecordCount = strings.length * platforms.length;787 let nameTable =788 "\x00\x00" + // format789 string16(namesRecordCount) + // Number of names Record790 string16(namesRecordCount * 12 + 6); // Storage791 // Build the name records field792 let strOffset = 0;793 for (i = 0, ii = platforms.length; i < ii; i++) {794 const strs = names[i];795 for (j = 0, jj = strs.length; j < jj; j++) {796 str = strs[j];797 const nameRecord =798 platforms[i] + // platform ID799 encodings[i] + // encoding ID800 languages[i] + // language ID801 string16(j) + // name ID802 string16(str.length) +803 string16(strOffset);804 nameTable += nameRecord;805 strOffset += str.length;806 }807 }808 nameTable += strings.join("") + stringsUnicode.join("");809 return nameTable;810}811/**812 * 'Font' is the class the outside world should use, it encapsulate all the font813 * decoding logics whatever type it is (assuming the font type is supported).814 */815class Font {816 constructor(name, file, properties) {817 this.name = name;818 this.psName = null;819 this.mimetype = null;820 this.disableFontFace = false;821 this.loadedName = properties.loadedName;822 this.isType3Font = properties.isType3Font;823 this.missingFile = false;824 this.cssFontInfo = properties.cssFontInfo;825 this._charsCache = Object.create(null);826 this._glyphCache = Object.create(null);827 let isSerifFont = !!(properties.flags & FontFlags.Serif);828 // Fallback to checking the font name, in order to improve text-selection,829 // since the /Flags-entry is often wrong (fixes issue13845.pdf).830 if (!isSerifFont && !properties.isSimulatedFlags) {831 const baseName = name.replace(/[,_]/g, "-").split("-")[0],832 serifFonts = getSerifFonts();833 for (const namePart of baseName.split("+")) {834 if (serifFonts[namePart]) {835 isSerifFont = true;836 break;837 }838 }839 }840 this.isSerifFont = isSerifFont;841 this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);842 this.isMonospace = !!(properties.flags & FontFlags.FixedPitch);843 let type = properties.type;844 let subtype = properties.subtype;845 this.type = type;846 this.subtype = subtype;847 let fallbackName = "sans-serif";848 if (this.isMonospace) {849 fallbackName = "monospace";850 } else if (this.isSerifFont) {851 fallbackName = "serif";852 }853 this.fallbackName = fallbackName;854 this.differences = properties.differences;855 this.widths = properties.widths;856 this.defaultWidth = properties.defaultWidth;857 this.composite = properties.composite;858 this.cMap = properties.cMap;859 this.capHeight = properties.capHeight / PDF_GLYPH_SPACE_UNITS;860 this.ascent = properties.ascent / PDF_GLYPH_SPACE_UNITS;861 this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS;862 this.lineHeight = this.ascent - this.descent;863 this.fontMatrix = properties.fontMatrix;864 this.bbox = properties.bbox;865 this.defaultEncoding = properties.defaultEncoding;866 this.toUnicode = properties.toUnicode;867 this.toFontChar = [];868 if (properties.type === "Type3") {869 for (let charCode = 0; charCode < 256; charCode++) {870 this.toFontChar[charCode] =871 this.differences[charCode] || properties.defaultEncoding[charCode];872 }873 this.fontType = FontType.TYPE3;874 return;875 }876 this.cidEncoding = properties.cidEncoding || "";877 this.vertical = !!properties.vertical;878 if (this.vertical) {879 this.vmetrics = properties.vmetrics;880 this.defaultVMetrics = properties.defaultVMetrics;881 }882 if (!file || file.isEmpty) {883 if (file) {884 // Some bad PDF generators will include empty font files,885 // attempting to recover by assuming that no file exists.886 warn('Font file is empty in "' + name + '" (' + this.loadedName + ")");887 }888 this.fallbackToSystemFont(properties);889 return;890 }891 // Parse the font file to determine the correct type/subtype, rather than892 // relying on the (often incorrect) data in the font dictionary; (see e.g.893 // issue6782.pdf, issue7598.pdf, and issue9949.pdf).894 [type, subtype] = getFontFileType(file, properties);895 if (type !== this.type || subtype !== this.subtype) {896 info(897 "Inconsistent font file Type/SubType, expected: " +898 `${this.type}/${this.subtype} but found: ${type}/${subtype}.`899 );900 }901 let data;902 try {903 switch (type) {904 case "MMType1":905 info("MMType1 font (" + name + "), falling back to Type1.");906 /* falls through */907 case "Type1":908 case "CIDFontType0":909 this.mimetype = "font/opentype";910 const cff =911 subtype === "Type1C" || subtype === "CIDFontType0C"912 ? new CFFFont(file, properties)913 : new Type1Font(name, file, properties);914 adjustWidths(properties);915 // Wrap the CFF data inside an OTF font file916 data = this.convert(name, cff, properties);917 break;918 case "OpenType":919 case "TrueType":920 case "CIDFontType2":921 this.mimetype = "font/opentype";922 // Repair the TrueType file. It is can be damaged in the point of923 // view of the sanitizer924 data = this.checkAndRepair(name, file, properties);925 if (this.isOpenType) {926 adjustWidths(properties);927 type = "OpenType";928 }929 break;930 default:931 throw new FormatError(`Font ${type} is not supported`);932 }933 } catch (e) {934 warn(e);935 this.fallbackToSystemFont(properties);936 return;937 }938 amendFallbackToUnicode(properties);939 this.data = data;940 this.fontType = getFontType(type, subtype, properties.isStandardFont);941 // Transfer some properties again that could change during font conversion942 this.fontMatrix = properties.fontMatrix;943 this.widths = properties.widths;944 this.defaultWidth = properties.defaultWidth;945 this.toUnicode = properties.toUnicode;946 this.seacMap = properties.seacMap;947 }948 get renderer() {949 const renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED);950 return shadow(this, "renderer", renderer);951 }952 exportData(extraProperties = false) {953 const exportDataProperties = extraProperties954 ? [...EXPORT_DATA_PROPERTIES, ...EXPORT_DATA_EXTRA_PROPERTIES]955 : EXPORT_DATA_PROPERTIES;956 const data = Object.create(null);957 let property, value;958 for (property of exportDataProperties) {959 value = this[property];960 // Ignore properties that haven't been explicitly set.961 if (value !== undefined) {962 data[property] = value;963 }964 }965 return data;966 }967 fallbackToSystemFont(properties) {968 this.missingFile = true;969 // The file data is not specified. Trying to fix the font name970 // to be used with the canvas.font.971 const name = this.name;972 const type = this.type;973 const subtype = this.subtype;974 let fontName = normalizeFontName(name);975 const stdFontMap = getStdFontMap(),976 nonStdFontMap = getNonStdFontMap();977 const isStandardFont = !!stdFontMap[fontName];978 const isMappedToStandardFont = !!(979 nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]980 );981 fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;982 const fontBasicMetricsMap = getFontBasicMetrics();983 const metrics = fontBasicMetricsMap[fontName];984 if (metrics) {985 if (isNaN(this.ascent)) {986 this.ascent = metrics.ascent / PDF_GLYPH_SPACE_UNITS;987 }988 if (isNaN(this.descent)) {989 this.descent = metrics.descent / PDF_GLYPH_SPACE_UNITS;990 }991 if (isNaN(this.capHeight)) {992 this.capHeight = metrics.capHeight / PDF_GLYPH_SPACE_UNITS;993 }994 }995 this.bold = fontName.search(/bold/gi) !== -1;996 this.italic =997 fontName.search(/oblique/gi) !== -1 || fontName.search(/italic/gi) !== -1;998 // Use 'name' instead of 'fontName' here because the original999 // name ArialBlack for example will be replaced by Helvetica.1000 this.black = name.search(/Black/g) !== -1;1001 // Use 'name' instead of 'fontName' here because the original1002 // name ArialNarrow for example will be replaced by Helvetica.1003 const isNarrow = name.search(/Narrow/g) !== -1;1004 // if at least one width is present, remeasure all chars when exists1005 this.remeasure =1006 (!isStandardFont || isNarrow) && Object.keys(this.widths).length > 0;1007 if (1008 (isStandardFont || isMappedToStandardFont) &&1009 type === "CIDFontType2" &&1010 this.cidEncoding.startsWith("Identity-")1011 ) {1012 const cidToGidMap = properties.cidToGidMap;1013 // Standard fonts might be embedded as CID font without glyph mapping.1014 // Building one based on GlyphMapForStandardFonts.1015 const map = [];1016 applyStandardFontGlyphMap(map, getGlyphMapForStandardFonts());1017 if (/Arial-?Black/i.test(name)) {1018 applyStandardFontGlyphMap(map, getSupplementalGlyphMapForArialBlack());1019 } else if (/Calibri/i.test(name)) {1020 applyStandardFontGlyphMap(map, getSupplementalGlyphMapForCalibri());1021 }1022 // Always update the glyph mapping with the `cidToGidMap` when it exists1023 // (fixes issue12418_reduced.pdf).1024 if (cidToGidMap) {1025 for (const charCode in map) {1026 const cid = map[charCode];1027 if (cidToGidMap[cid] !== undefined) {1028 map[+charCode] = cidToGidMap[cid];1029 }1030 }1031 // When the /CIDToGIDMap is "incomplete", fallback to the included1032 // /ToUnicode-map regardless of its encoding (fixes issue11915.pdf).1033 if (1034 cidToGidMap.length !== this.toUnicode.length &&1035 properties.hasIncludedToUnicodeMap &&1036 this.toUnicode instanceof IdentityToUnicodeMap1037 ) {1038 this.toUnicode.forEach(function (charCode, unicodeCharCode) {1039 const cid = map[charCode];1040 if (cidToGidMap[cid] === undefined) {1041 map[+charCode] = unicodeCharCode;1042 }1043 });1044 }1045 }1046 if (!(this.toUnicode instanceof IdentityToUnicodeMap)) {1047 this.toUnicode.forEach(function (charCode, unicodeCharCode) {1048 map[+charCode] = unicodeCharCode;1049 });1050 }1051 this.toFontChar = map;1052 this.toUnicode = new ToUnicodeMap(map);1053 } else if (/Symbol/i.test(fontName)) {1054 this.toFontChar = buildToFontChar(1055 SymbolSetEncoding,1056 getGlyphsUnicode(),1057 this.differences1058 );1059 } else if (/Dingbats/i.test(fontName)) {1060 if (/Wingdings/i.test(name)) {1061 warn("Non-embedded Wingdings font, falling back to ZapfDingbats.");1062 }1063 this.toFontChar = buildToFontChar(1064 ZapfDingbatsEncoding,1065 getDingbatsGlyphsUnicode(),1066 this.differences1067 );1068 } else if (isStandardFont) {1069 const map = buildToFontChar(1070 this.defaultEncoding,1071 getGlyphsUnicode(),1072 this.differences1073 );1074 if (1075 type === "CIDFontType2" &&1076 !this.cidEncoding.startsWith("Identity-") &&1077 !(this.toUnicode instanceof IdentityToUnicodeMap)1078 ) {1079 this.toUnicode.forEach(function (charCode, unicodeCharCode) {1080 map[+charCode] = unicodeCharCode;1081 });1082 }1083 this.toFontChar = map;1084 } else {1085 const glyphsUnicodeMap = getGlyphsUnicode();1086 const map = [];1087 this.toUnicode.forEach((charCode, unicodeCharCode) => {1088 if (!this.composite) {1089 const glyphName =1090 this.differences[charCode] || this.defaultEncoding[charCode];1091 const unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);1092 if (unicode !== -1) {1093 unicodeCharCode = unicode;1094 }1095 }1096 map[+charCode] = unicodeCharCode;1097 });1098 // Attempt to improve the glyph mapping for (some) composite fonts that1099 // appear to lack meaningful ToUnicode data.1100 if (this.composite && this.toUnicode instanceof IdentityToUnicodeMap) {1101 if (/Verdana/i.test(name)) {1102 // Fixes issue11242_reduced.pdf1103 applyStandardFontGlyphMap(map, getGlyphMapForStandardFonts());1104 }1105 }1106 this.toFontChar = map;1107 }1108 amendFallbackToUnicode(properties);1109 this.loadedName = fontName.split("-")[0];1110 this.fontType = getFontType(type, subtype, properties.isStandardFont);1111 }1112 checkAndRepair(name, font, properties) {1113 const VALID_TABLES = [1114 "OS/2",1115 "cmap",1116 "head",1117 "hhea",1118 "hmtx",1119 "maxp",1120 "name",1121 "post",1122 "loca",1123 "glyf",1124 "fpgm",1125 "prep",1126 "cvt ",1127 "CFF ",1128 ];1129 function readTables(file, numTables) {1130 const tables = Object.create(null);1131 tables["OS/2"] = null;1132 tables.cmap = null;1133 tables.head = null;1134 tables.hhea = null;1135 tables.hmtx = null;1136 tables.maxp = null;1137 tables.name = null;1138 tables.post = null;1139 for (let i = 0; i < numTables; i++) {1140 const table = readTableEntry(file);1141 if (!VALID_TABLES.includes(table.tag)) {1142 continue; // skipping table if it's not a required or optional table1143 }1144 if (table.length === 0) {1145 continue; // skipping empty tables1146 }1147 tables[table.tag] = table;1148 }1149 return tables;1150 }1151 function readTableEntry(file) {1152 const tag = file.getString(4);1153 const checksum = file.getInt32() >>> 0;1154 const offset = file.getInt32() >>> 0;1155 const length = file.getInt32() >>> 0;1156 // Read the table associated data1157 const previousPosition = file.pos;1158 file.pos = file.start ? file.start : 0;1159 file.skip(offset);1160 const data = file.getBytes(length);1161 file.pos = previousPosition;1162 if (tag === "head") {1163 // clearing checksum adjustment1164 data[8] = data[9] = data[10] = data[11] = 0;1165 data[17] |= 0x20; // Set font optimized for cleartype flag.1166 }1167 return {1168 tag,1169 checksum,1170 length,1171 offset,1172 data,1173 };1174 }1175 function readOpenTypeHeader(ttf) {1176 return {1177 version: ttf.getString(4),1178 numTables: ttf.getUint16(),1179 searchRange: ttf.getUint16(),1180 entrySelector: ttf.getUint16(),1181 rangeShift: ttf.getUint16(),1182 };1183 }1184 function readTrueTypeCollectionHeader(ttc) {1185 const ttcTag = ttc.getString(4);1186 assert(ttcTag === "ttcf", "Must be a TrueType Collection font.");1187 const majorVersion = ttc.getUint16();1188 const minorVersion = ttc.getUint16();1189 const numFonts = ttc.getInt32() >>> 0;1190 const offsetTable = [];1191 for (let i = 0; i < numFonts; i++) {1192 offsetTable.push(ttc.getInt32() >>> 0);1193 }1194 const header = {1195 ttcTag,1196 majorVersion,1197 minorVersion,1198 numFonts,1199 offsetTable,1200 };1201 switch (majorVersion) {1202 case 1:1203 return header;1204 case 2:1205 header.dsigTag = ttc.getInt32() >>> 0;1206 header.dsigLength = ttc.getInt32() >>> 0;1207 header.dsigOffset = ttc.getInt32() >>> 0;1208 return header;1209 }1210 throw new FormatError(1211 `Invalid TrueType Collection majorVersion: ${majorVersion}.`1212 );1213 }1214 function readTrueTypeCollectionData(ttc, fontName) {1215 const { numFonts, offsetTable } = readTrueTypeCollectionHeader(ttc);1216 const fontNameParts = fontName.split("+");1217 let fallbackData;1218 for (let i = 0; i < numFonts; i++) {1219 ttc.pos = (ttc.start || 0) + offsetTable[i];1220 const potentialHeader = readOpenTypeHeader(ttc);1221 const potentialTables = readTables(ttc, potentialHeader.numTables);1222 if (!potentialTables.name) {1223 throw new FormatError(1224 'TrueType Collection font must contain a "name" table.'1225 );1226 }1227 const nameTable = readNameTable(potentialTables.name);1228 for (let j = 0, jj = nameTable.length; j < jj; j++) {1229 for (let k = 0, kk = nameTable[j].length; k < kk; k++) {1230 const nameEntry =1231 nameTable[j][k] && nameTable[j][k].replace(/\s/g, "");1232 if (!nameEntry) {1233 continue;1234 }1235 if (nameEntry === fontName) {1236 return {1237 header: potentialHeader,1238 tables: potentialTables,1239 };1240 }1241 if (fontNameParts.length < 2) {1242 continue;1243 }1244 for (const part of fontNameParts) {1245 if (nameEntry === part) {1246 fallbackData = {1247 name: part,1248 header: potentialHeader,1249 tables: potentialTables,1250 };1251 }1252 }1253 }1254 }1255 }1256 if (fallbackData) {1257 warn(1258 `TrueType Collection does not contain "${fontName}" font, ` +1259 `falling back to "${fallbackData.name}" font instead.`1260 );1261 return {1262 header: fallbackData.header,1263 tables: fallbackData.tables,1264 };1265 }1266 throw new FormatError(1267 `TrueType Collection does not contain "${fontName}" font.`1268 );1269 }1270 /**1271 * Read the appropriate subtable from the cmap according to 9.6.6.4 from1272 * PDF spec1273 */1274 function readCmapTable(cmap, file, isSymbolicFont, hasEncoding) {1275 if (!cmap) {1276 warn("No cmap table available.");1277 return {1278 platformId: -1,1279 encodingId: -1,1280 mappings: [],1281 hasShortCmap: false,1282 };1283 }1284 let segment;1285 let start = (file.start ? file.start : 0) + cmap.offset;1286 file.pos = start;1287 file.skip(2); // version1288 const numTables = file.getUint16();1289 let potentialTable;1290 let canBreak = false;1291 // There's an order of preference in terms of which cmap subtable to1292 // use:1293 // - non-symbolic fonts the preference is a 3,1 table then a 1,0 table1294 // - symbolic fonts the preference is a 3,0 table then a 1,0 table1295 // The following takes advantage of the fact that the tables are sorted1296 // to work.1297 for (let i = 0; i < numTables; i++) {1298 const platformId = file.getUint16();1299 const encodingId = file.getUint16();1300 const offset = file.getInt32() >>> 0;1301 let useTable = false;1302 // Sometimes there are multiple of the same type of table. Default1303 // to choosing the first table and skip the rest.1304 if (1305 potentialTable &&1306 potentialTable.platformId === platformId &&1307 potentialTable.encodingId === encodingId1308 ) {1309 continue;1310 }1311 if (1312 platformId === 0 &&1313 (encodingId === /* Unicode Default */ 0 ||1314 encodingId === /* Unicode 1.1 */ 1 ||1315 encodingId === /* Unicode BMP */ 3)1316 ) {1317 useTable = true;1318 // Continue the loop since there still may be a higher priority1319 // table.1320 } else if (platformId === 1 && encodingId === 0) {1321 useTable = true;1322 // Continue the loop since there still may be a higher priority1323 // table.1324 } else if (1325 platformId === 3 &&1326 encodingId === 1 &&1327 (hasEncoding || !potentialTable)1328 ) {1329 useTable = true;1330 if (!isSymbolicFont) {1331 canBreak = true;1332 }1333 } else if (isSymbolicFont && platformId === 3 && encodingId === 0) {1334 useTable = true;1335 let correctlySorted = true;1336 if (i < numTables - 1) {1337 const nextBytes = file.peekBytes(2),1338 nextPlatformId = int16(nextBytes[0], nextBytes[1]);1339 if (nextPlatformId < platformId) {1340 correctlySorted = false;1341 }1342 }1343 if (correctlySorted) {1344 canBreak = true;1345 }1346 }1347 if (useTable) {1348 potentialTable = {1349 platformId,1350 encodingId,1351 offset,1352 };1353 }1354 if (canBreak) {1355 break;1356 }1357 }1358 if (potentialTable) {1359 file.pos = start + potentialTable.offset;1360 }1361 if (!potentialTable || file.peekByte() === -1) {1362 warn("Could not find a preferred cmap table.");1363 return {1364 platformId: -1,1365 encodingId: -1,1366 mappings: [],1367 hasShortCmap: false,1368 };1369 }1370 const format = file.getUint16();1371 file.skip(2 + 2); // length + language1372 let hasShortCmap = false;1373 const mappings = [];1374 let j, glyphId;1375 // TODO(mack): refactor this cmap subtable reading logic out1376 if (format === 0) {1377 for (j = 0; j < 256; j++) {1378 const index = file.getByte();1379 if (!index) {1380 continue;1381 }1382 mappings.push({1383 charCode: j,1384 glyphId: index,1385 });1386 }1387 hasShortCmap = true;1388 } else if (format === 2) {1389 const subHeaderKeys = [];1390 let maxSubHeaderKey = 0;1391 // Read subHeaderKeys. If subHeaderKeys[i] === 0, then i is a1392 // single-byte character. Otherwise, i is the first byte of a1393 // multi-byte character, and the value is 8*index into1394 // subHeaders.1395 for (let i = 0; i < 256; i++) {1396 const subHeaderKey = file.getUint16() >> 3;1397 subHeaderKeys.push(subHeaderKey);1398 maxSubHeaderKey = Math.max(subHeaderKey, maxSubHeaderKey);1399 }1400 // Read subHeaders. The number of entries is determined1401 // dynamically based on the subHeaderKeys found above.1402 const subHeaders = [];1403 for (let i = 0; i <= maxSubHeaderKey; i++) {1404 subHeaders.push({1405 firstCode: file.getUint16(),1406 entryCount: file.getUint16(),1407 idDelta: signedInt16(file.getByte(), file.getByte()),1408 idRangePos: file.pos + file.getUint16(),1409 });1410 }1411 for (let i = 0; i < 256; i++) {1412 if (subHeaderKeys[i] === 0) {1413 // i is a single-byte code.1414 file.pos = subHeaders[0].idRangePos + 2 * i;1415 glyphId = file.getUint16();1416 mappings.push({1417 charCode: i,1418 glyphId,1419 });1420 } else {1421 // i is the first byte of a two-byte code.1422 const s = subHeaders[subHeaderKeys[i]];1423 for (j = 0; j < s.entryCount; j++) {1424 const charCode = (i << 8) + j + s.firstCode;1425 file.pos = s.idRangePos + 2 * j;1426 glyphId = file.getUint16();1427 if (glyphId !== 0) {1428 glyphId = (glyphId + s.idDelta) % 65536;1429 }1430 mappings.push({1431 charCode,1432 glyphId,1433 });1434 }1435 }1436 }1437 } else if (format === 4) {1438 // re-creating the table in format 4 since the encoding1439 // might be changed1440 const segCount = file.getUint16() >> 1;1441 file.skip(6); // skipping range fields1442 const segments = [];1443 let segIndex;1444 for (segIndex = 0; segIndex < segCount; segIndex++) {1445 segments.push({ end: file.getUint16() });1446 }1447 file.skip(2);1448 for (segIndex = 0; segIndex < segCount; segIndex++) {1449 segments[segIndex].start = file.getUint16();1450 }1451 for (segIndex = 0; segIndex < segCount; segIndex++) {1452 segments[segIndex].delta = file.getUint16();1453 }1454 let offsetsCount = 0,1455 offsetIndex;1456 for (segIndex = 0; segIndex < segCount; segIndex++) {1457 segment = segments[segIndex];1458 const rangeOffset = file.getUint16();1459 if (!rangeOffset) {1460 segment.offsetIndex = -1;1461 continue;1462 }1463 offsetIndex = (rangeOffset >> 1) - (segCount - segIndex);1464 segment.offsetIndex = offsetIndex;1465 offsetsCount = Math.max(1466 offsetsCount,1467 offsetIndex + segment.end - segment.start + 11468 );1469 }1470 const offsets = [];1471 for (j = 0; j < offsetsCount; j++) {1472 offsets.push(file.getUint16());1473 }1474 for (segIndex = 0; segIndex < segCount; segIndex++) {1475 segment = segments[segIndex];1476 start = segment.start;1477 const end = segment.end;1478 const delta = segment.delta;1479 offsetIndex = segment.offsetIndex;1480 for (j = start; j <= end; j++) {1481 if (j === 0xffff) {1482 continue;1483 }1484 glyphId = offsetIndex < 0 ? j : offsets[offsetIndex + j - start];1485 glyphId = (glyphId + delta) & 0xffff;1486 mappings.push({1487 charCode: j,1488 glyphId,1489 });1490 }1491 }1492 } else if (format === 6) {1493 // Format 6 is a 2-bytes dense mapping, which means the font data1494 // lives glue together even if they are pretty far in the unicode1495 // table. (This looks weird, so I can have missed something), this1496 // works on Linux but seems to fails on Mac so let's rewrite the1497 // cmap table to a 3-1-4 style1498 const firstCode = file.getUint16();1499 const entryCount = file.getUint16();1500 for (j = 0; j < entryCount; j++) {1501 glyphId = file.getUint16();1502 const charCode = firstCode + j;1503 mappings.push({1504 charCode,1505 glyphId,1506 });1507 }1508 } else {1509 warn("cmap table has unsupported format: " + format);1510 return {1511 platformId: -1,1512 encodingId: -1,1513 mappings: [],1514 hasShortCmap: false,1515 };1516 }1517 // removing duplicate entries1518 mappings.sort(function (a, b) {1519 return a.charCode - b.charCode;1520 });1521 for (let i = 1; i < mappings.length; i++) {1522 if (mappings[i - 1].charCode === mappings[i].charCode) {1523 mappings.splice(i, 1);1524 i--;1525 }1526 }1527 return {1528 platformId: potentialTable.platformId,1529 encodingId: potentialTable.encodingId,1530 mappings,1531 hasShortCmap,1532 };1533 }1534 function sanitizeMetrics(1535 file,1536 header,1537 metrics,1538 headTable,1539 numGlyphs,1540 dupFirstEntry1541 ) {1542 if (!header) {1543 if (metrics) {1544 metrics.data = null;1545 }1546 return;1547 }1548 file.pos = (file.start ? file.start : 0) + header.offset;1549 file.pos += 4; // version1550 file.pos += 2; // ascent1551 file.pos += 2; // descent1552 file.pos += 2; // linegap1553 file.pos += 2; // adv_width_max1554 file.pos += 2; // min_sb11555 file.pos += 2; // min_sb21556 file.pos += 2; // max_extent1557 file.pos += 2; // caret_slope_rise1558 file.pos += 2; // caret_slope_run1559 const caretOffset = file.getUint16();1560 file.pos += 8; // reserved1561 file.pos += 2; // format1562 let numOfMetrics = file.getUint16();1563 if (caretOffset !== 0) {1564 const macStyle = int16(headTable.data[44], headTable.data[45]);1565 if (!(macStyle & 2)) {1566 // Suppress OTS warnings about the `caretOffset` in the hhea-table.1567 header.data[22] = 0;1568 header.data[23] = 0;1569 }1570 }1571 if (numOfMetrics > numGlyphs) {1572 info(1573 `The numOfMetrics (${numOfMetrics}) should not be ` +1574 `greater than the numGlyphs (${numGlyphs}).`1575 );1576 // Reduce numOfMetrics if it is greater than numGlyphs1577 numOfMetrics = numGlyphs;1578 header.data[34] = (numOfMetrics & 0xff00) >> 8;1579 header.data[35] = numOfMetrics & 0x00ff;1580 }1581 const numOfSidebearings = numGlyphs - numOfMetrics;1582 const numMissing =1583 numOfSidebearings - ((metrics.length - numOfMetrics * 4) >> 1);1584 if (numMissing > 0) {1585 // For each missing glyph, we set both the width and lsb to 0 (zero).1586 // Since we need to add two properties for each glyph, this explains1587 // the use of |numMissing * 2| when initializing the typed array.1588 const entries = new Uint8Array(metrics.length + numMissing * 2);1589 entries.set(metrics.data);1590 if (dupFirstEntry) {1591 // Set the sidebearing value of the duplicated glyph.1592 entries[metrics.length] = metrics.data[2];1593 entries[metrics.length + 1] = metrics.data[3];1594 }1595 metrics.data = entries;1596 }1597 }1598 function sanitizeGlyph(1599 source,1600 sourceStart,1601 sourceEnd,1602 dest,1603 destStart,1604 hintsValid1605 ) {1606 const glyphProfile = {1607 length: 0,1608 sizeOfInstructions: 0,1609 };1610 if (sourceEnd - sourceStart <= 12) {1611 // glyph with data less than 12 is invalid one1612 return glyphProfile;1613 }1614 const glyf = source.subarray(sourceStart, sourceEnd);1615 let contoursCount = signedInt16(glyf[0], glyf[1]);1616 if (contoursCount < 0) {1617 // OTS doesn't like contour count to be less than -1.1618 contoursCount = -1;1619 writeSignedInt16(glyf, 0, contoursCount);1620 // complex glyph, writing as is1621 dest.set(glyf, destStart);1622 glyphProfile.length = glyf.length;1623 return glyphProfile;1624 }1625 let i,1626 j = 10,1627 flagsCount = 0;1628 for (i = 0; i < contoursCount; i++) {1629 const endPoint = (glyf[j] << 8) | glyf[j + 1];1630 flagsCount = endPoint + 1;1631 j += 2;1632 }1633 // skipping instructions1634 const instructionsStart = j;1635 const instructionsLength = (glyf[j] << 8) | glyf[j + 1];1636 glyphProfile.sizeOfInstructions = instructionsLength;1637 j += 2 + instructionsLength;1638 const instructionsEnd = j;1639 // validating flags1640 let coordinatesLength = 0;1641 for (i = 0; i < flagsCount; i++) {1642 const flag = glyf[j++];1643 if (flag & 0xc0) {1644 // reserved flags must be zero, cleaning up1645 glyf[j - 1] = flag & 0x3f;1646 }1647 let xLength = 2;1648 if (flag & 2) {1649 xLength = 1;1650 } else if (flag & 16) {1651 xLength = 0;1652 }1653 let yLength = 2;1654 if (flag & 4) {1655 yLength = 1;1656 } else if (flag & 32) {1657 yLength = 0;1658 }1659 const xyLength = xLength + yLength;1660 coordinatesLength += xyLength;1661 if (flag & 8) {1662 const repeat = glyf[j++];1663 i += repeat;1664 coordinatesLength += repeat * xyLength;1665 }1666 }1667 // glyph without coordinates will be rejected1668 if (coordinatesLength === 0) {1669 return glyphProfile;1670 }1671 let glyphDataLength = j + coordinatesLength;1672 if (glyphDataLength > glyf.length) {1673 // not enough data for coordinates1674 return glyphProfile;1675 }1676 if (!hintsValid && instructionsLength > 0) {1677 dest.set(glyf.subarray(0, instructionsStart), destStart);1678 dest.set([0, 0], destStart + instructionsStart);1679 dest.set(1680 glyf.subarray(instructionsEnd, glyphDataLength),1681 destStart + instructionsStart + 21682 );1683 glyphDataLength -= instructionsLength;1684 if (glyf.length - glyphDataLength > 3) {1685 glyphDataLength = (glyphDataLength + 3) & ~3;1686 }1687 glyphProfile.length = glyphDataLength;1688 return glyphProfile;1689 }1690 if (glyf.length - glyphDataLength > 3) {1691 // truncating and aligning to 4 bytes the long glyph data1692 glyphDataLength = (glyphDataLength + 3) & ~3;1693 dest.set(glyf.subarray(0, glyphDataLength), destStart);1694 glyphProfile.length = glyphDataLength;1695 return glyphProfile;1696 }1697 // glyph data is fine1698 dest.set(glyf, destStart);1699 glyphProfile.length = glyf.length;1700 return glyphProfile;1701 }1702 function sanitizeHead(head, numGlyphs, locaLength) {1703 const data = head.data;1704 // Validate version:1705 // Should always be 0x000100001706 const version = int32(data[0], data[1], data[2], data[3]);1707 if (version >> 16 !== 1) {1708 info("Attempting to fix invalid version in head table: " + version);1709 data[0] = 0;1710 data[1] = 1;1711 data[2] = 0;1712 data[3] = 0;1713 }1714 const indexToLocFormat = int16(data[50], data[51]);1715 if (indexToLocFormat < 0 || indexToLocFormat > 1) {1716 info(1717 "Attempting to fix invalid indexToLocFormat in head table: " +1718 indexToLocFormat1719 );1720 // The value of indexToLocFormat should be 0 if the loca table1721 // consists of short offsets, and should be 1 if the loca table1722 // consists of long offsets.1723 //1724 // The number of entries in the loca table should be numGlyphs + 1.1725 //1726 // Using this information, we can work backwards to deduce if the1727 // size of each offset in the loca table, and thus figure out the1728 // appropriate value for indexToLocFormat.1729 const numGlyphsPlusOne = numGlyphs + 1;1730 if (locaLength === numGlyphsPlusOne << 1) {1731 // 0x0000 indicates the loca table consists of short offsets1732 data[50] = 0;1733 data[51] = 0;1734 } else if (locaLength === numGlyphsPlusOne << 2) {1735 // 0x0001 indicates the loca table consists of long offsets1736 data[50] = 0;1737 data[51] = 1;1738 } else {1739 throw new FormatError(1740 "Could not fix indexToLocFormat: " + indexToLocFormat1741 );1742 }1743 }1744 }1745 function sanitizeGlyphLocations(1746 loca,1747 glyf,1748 numGlyphs,1749 isGlyphLocationsLong,1750 hintsValid,1751 dupFirstEntry,1752 maxSizeOfInstructions1753 ) {1754 let itemSize, itemDecode, itemEncode;1755 if (isGlyphLocationsLong) {1756 itemSize = 4;1757 itemDecode = function fontItemDecodeLong(data, offset) {1758 return (1759 (data[offset] << 24) |1760 (data[offset + 1] << 16) |1761 (data[offset + 2] << 8) |1762 data[offset + 3]1763 );1764 };1765 itemEncode = function fontItemEncodeLong(data, offset, value) {1766 data[offset] = (value >>> 24) & 0xff;1767 data[offset + 1] = (value >> 16) & 0xff;1768 data[offset + 2] = (value >> 8) & 0xff;1769 data[offset + 3] = value & 0xff;1770 };1771 } else {1772 itemSize = 2;1773 itemDecode = function fontItemDecode(data, offset) {1774 return (data[offset] << 9) | (data[offset + 1] << 1);1775 };1776 itemEncode = function fontItemEncode(data, offset, value) {1777 data[offset] = (value >> 9) & 0xff;1778 data[offset + 1] = (value >> 1) & 0xff;1779 };1780 }1781 // The first glyph is duplicated.1782 const numGlyphsOut = dupFirstEntry ? numGlyphs + 1 : numGlyphs;1783 const locaDataSize = itemSize * (1 + numGlyphsOut);1784 // Resize loca table to account for duplicated glyph.1785 const locaData = new Uint8Array(locaDataSize);1786 locaData.set(loca.data.subarray(0, locaDataSize));1787 loca.data = locaData;1788 // removing the invalid glyphs1789 const oldGlyfData = glyf.data;1790 const oldGlyfDataLength = oldGlyfData.length;1791 const newGlyfData = new Uint8Array(oldGlyfDataLength);1792 // The spec says the offsets should be in ascending order, however1793 // this is not true for some fonts or they use the offset of 0 to mark a1794 // glyph as missing. OTS requires the offsets to be in order and not to1795 // be zero, so we must sort and rebuild the loca table and potentially1796 // re-arrange the glyf data.1797 let i, j;1798 const locaEntries = [];1799 // There are numGlyphs + 1 loca table entries.1800 for (i = 0, j = 0; i < numGlyphs + 1; i++, j += itemSize) {1801 let offset = itemDecode(locaData, j);1802 if (offset > oldGlyfDataLength) {1803 offset = oldGlyfDataLength;1804 }1805 locaEntries.push({1806 index: i,1807 offset,1808 endOffset: 0,1809 });1810 }1811 locaEntries.sort((a, b) => {1812 return a.offset - b.offset;1813 });1814 // Now the offsets are sorted, calculate the end offset of each glyph.1815 // The last loca entry's endOffset is not calculated since it's the end1816 // of the data and will be stored on the previous entry's endOffset.1817 for (i = 0; i < numGlyphs; i++) {1818 locaEntries[i].endOffset = locaEntries[i + 1].offset;1819 }1820 // Re-sort so glyphs aren't out of order.1821 locaEntries.sort((a, b) => {1822 return a.index - b.index;1823 });1824 // Calculate the endOffset of the "first" glyph correctly when there are1825 // *multiple* empty ones at the start of the data (fixes issue14618.pdf).1826 for (i = 0; i < numGlyphs; i++) {1827 const { offset, endOffset } = locaEntries[i];1828 if (offset !== 0 || endOffset !== 0) {1829 break;1830 }1831 const nextOffset = locaEntries[i + 1].offset;1832 if (nextOffset === 0) {1833 continue;1834 }1835 locaEntries[i].endOffset = nextOffset;1836 break;1837 }1838 const missingGlyphs = Object.create(null);1839 let writeOffset = 0;1840 itemEncode(locaData, 0, writeOffset);1841 for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {1842 const glyphProfile = sanitizeGlyph(1843 oldGlyfData,1844 locaEntries[i].offset,1845 locaEntries[i].endOffset,1846 newGlyfData,1847 writeOffset,1848 hintsValid1849 );1850 const newLength = glyphProfile.length;1851 if (newLength === 0) {1852 missingGlyphs[i] = true;1853 }1854 if (glyphProfile.sizeOfInstructions > maxSizeOfInstructions) {1855 maxSizeOfInstructions = glyphProfile.sizeOfInstructions;1856 }1857 writeOffset += newLength;1858 itemEncode(locaData, j, writeOffset);1859 }1860 if (writeOffset === 0) {1861 // glyf table cannot be empty -- redoing the glyf and loca tables1862 // to have single glyph with one point1863 const simpleGlyph = new Uint8Array([1864 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0,1865 ]);1866 for (i = 0, j = itemSize; i < numGlyphsOut; i++, j += itemSize) {1867 itemEncode(locaData, j, simpleGlyph.length);1868 }1869 glyf.data = simpleGlyph;1870 } else if (dupFirstEntry) {1871 // Browsers will not display a glyph at position 0. Typically glyph 01872 // is notdef, but a number of fonts put a valid glyph there so it must1873 // be duplicated and appended.1874 const firstEntryLength = itemDecode(locaData, itemSize);1875 if (newGlyfData.length > firstEntryLength + writeOffset) {1876 glyf.data = newGlyfData.subarray(0, firstEntryLength + writeOffset);1877 } else {1878 glyf.data = new Uint8Array(firstEntryLength + writeOffset);1879 glyf.data.set(newGlyfData.subarray(0, writeOffset));1880 }1881 glyf.data.set(newGlyfData.subarray(0, firstEntryLength), writeOffset);1882 itemEncode(1883 loca.data,1884 locaData.length - itemSize,1885 writeOffset + firstEntryLength1886 );1887 } else {1888 glyf.data = newGlyfData.subarray(0, writeOffset);1889 }1890 return {1891 missingGlyphs,1892 maxSizeOfInstructions,1893 };1894 }1895 function readPostScriptTable(post, propertiesObj, maxpNumGlyphs) {1896 const start = (font.start ? font.start : 0) + post.offset;1897 font.pos = start;1898 const length = post.length,1899 end = start + length;1900 const version = font.getInt32();1901 // skip rest to the tables1902 font.skip(28);1903 let glyphNames;1904 let valid = true;1905 let i;1906 switch (version) {1907 case 0x00010000:1908 glyphNames = MacStandardGlyphOrdering;1909 break;1910 case 0x00020000:1911 const numGlyphs = font.getUint16();1912 if (numGlyphs !== maxpNumGlyphs) {1913 valid = false;1914 break;1915 }1916 const glyphNameIndexes = [];1917 for (i = 0; i < numGlyphs; ++i) {1918 const index = font.getUint16();1919 if (index >= 32768) {1920 valid = false;1921 break;1922 }1923 glyphNameIndexes.push(index);1924 }1925 if (!valid) {1926 break;1927 }1928 const customNames = [],1929 strBuf = [];1930 while (font.pos < end) {1931 const stringLength = font.getByte();1932 strBuf.length = stringLength;1933 for (i = 0; i < stringLength; ++i) {1934 strBuf[i] = String.fromCharCode(font.getByte());1935 }1936 customNames.push(strBuf.join(""));1937 }1938 glyphNames = [];1939 for (i = 0; i < numGlyphs; ++i) {1940 const j = glyphNameIndexes[i];1941 if (j < 258) {1942 glyphNames.push(MacStandardGlyphOrdering[j]);1943 continue;1944 }1945 glyphNames.push(customNames[j - 258]);1946 }1947 break;1948 case 0x00030000:1949 break;1950 default:1951 warn("Unknown/unsupported post table version " + version);1952 valid = false;1953 if (propertiesObj.defaultEncoding) {1954 glyphNames = propertiesObj.defaultEncoding;1955 }1956 break;1957 }1958 propertiesObj.glyphNames = glyphNames;1959 return valid;1960 }1961 function readNameTable(nameTable) {1962 const start = (font.start ? font.start : 0) + nameTable.offset;1963 font.pos = start;1964 const names = [[], []];1965 const length = nameTable.length,1966 end = start + length;1967 const format = font.getUint16();1968 const FORMAT_0_HEADER_LENGTH = 6;1969 if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) {1970 // unsupported name table format or table "too" small1971 return names;1972 }1973 const numRecords = font.getUint16();1974 const stringsStart = font.getUint16();1975 const records = [];1976 const NAME_RECORD_LENGTH = 12;1977 let i, ii;1978 for (i = 0; i < numRecords && font.pos + NAME_RECORD_LENGTH <= end; i++) {1979 const r = {1980 platform: font.getUint16(),1981 encoding: font.getUint16(),1982 language: font.getUint16(),1983 name: font.getUint16(),1984 length: font.getUint16(),1985 offset: font.getUint16(),1986 };1987 // using only Macintosh and Windows platform/encoding names1988 if (1989 (r.platform === 1 && r.encoding === 0 && r.language === 0) ||1990 (r.platform === 3 && r.encoding === 1 && r.language === 0x409)1991 ) {1992 records.push(r);1993 }1994 }1995 for (i = 0, ii = records.length; i < ii; i++) {1996 const record = records[i];1997 if (record.length <= 0) {1998 continue; // Nothing to process, ignoring.1999 }2000 const pos = start + stringsStart + record.offset;2001 if (pos + record.length > end) {2002 continue; // outside of name table, ignoring2003 }2004 font.pos = pos;2005 const nameIndex = record.name;2006 if (record.encoding) {2007 // unicode2008 let str = "";2009 for (let j = 0, jj = record.length; j < jj; j += 2) {2010 str += String.fromCharCode(font.getUint16());2011 }2012 names[1][nameIndex] = str;2013 } else {2014 names[0][nameIndex] = font.getString(record.length);2015 }2016 }2017 return names;2018 }2019 // prettier-ignore2020 const TTOpsStackDeltas = [2021 0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5,2022 -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1,2023 1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1,2024 0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -999, 0, -2, -2,2025 0, 0, -2, 0, -2, 0, 0, 0, -2, -1, -1, 1, 1, 0, 0, -1,2026 -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, 0, -999, -1, -1,2027 -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,2028 -2, -999, -999, -999, -999, -999, -1, -1, -2, -2, 0, 0, 0, 0, -1, -1,2029 -999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2];2030 // 0xC0-DF == -1 and 0xE0-FF == -22031 function sanitizeTTProgram(table, ttContext) {2032 let data = table.data;2033 let i = 0,2034 j,2035 n,2036 b,2037 funcId,2038 pc,2039 lastEndf = 0,2040 lastDeff = 0;2041 const stack = [];2042 const callstack = [];2043 const functionsCalled = [];2044 let tooComplexToFollowFunctions = ttContext.tooComplexToFollowFunctions;2045 let inFDEF = false,2046 ifLevel = 0,2047 inELSE = 0;2048 for (let ii = data.length; i < ii; ) {2049 const op = data[i++];2050 // The TrueType instruction set docs can be found at2051 // https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html2052 if (op === 0x40) {2053 // NPUSHB - pushes n bytes2054 n = data[i++];2055 if (inFDEF || inELSE) {2056 i += n;2057 } else {2058 for (j = 0; j < n; j++) {2059 stack.push(data[i++]);2060 }2061 }2062 } else if (op === 0x41) {2063 // NPUSHW - pushes n words2064 n = data[i++];2065 if (inFDEF || inELSE) {2066 i += n * 2;2067 } else {2068 for (j = 0; j < n; j++) {2069 b = data[i++];2070 stack.push((b << 8) | data[i++]);2071 }2072 }2073 } else if ((op & 0xf8) === 0xb0) {2074 // PUSHB - pushes bytes2075 n = op - 0xb0 + 1;2076 if (inFDEF || inELSE) {2077 i += n;2078 } else {2079 for (j = 0; j < n; j++) {2080 stack.push(data[i++]);2081 }2082 }2083 } else if ((op & 0xf8) === 0xb8) {2084 // PUSHW - pushes words2085 n = op - 0xb8 + 1;2086 if (inFDEF || inELSE) {2087 i += n * 2;2088 } else {2089 for (j = 0; j < n; j++) {2090 b = data[i++];2091 stack.push((b << 8) | data[i++]);2092 }2093 }2094 } else if (op === 0x2b && !tooComplexToFollowFunctions) {2095 // CALL2096 if (!inFDEF && !inELSE) {2097 // collecting information about which functions are used2098 funcId = stack[stack.length - 1];2099 if (isNaN(funcId)) {2100 info("TT: CALL empty stack (or invalid entry).");2101 } else {2102 ttContext.functionsUsed[funcId] = true;2103 if (funcId in ttContext.functionsStackDeltas) {2104 const newStackLength =2105 stack.length + ttContext.functionsStackDeltas[funcId];2106 if (newStackLength < 0) {2107 warn("TT: CALL invalid functions stack delta.");2108 ttContext.hintsValid = false;2109 return;2110 }2111 stack.length = newStackLength;2112 } else if (2113 funcId in ttContext.functionsDefined &&2114 !functionsCalled.includes(funcId)2115 ) {2116 callstack.push({ data, i, stackTop: stack.length - 1 });2117 functionsCalled.push(funcId);2118 pc = ttContext.functionsDefined[funcId];2119 if (!pc) {2120 warn("TT: CALL non-existent function");2121 ttContext.hintsValid = false;2122 return;2123 }2124 data = pc.data;2125 i = pc.i;2126 }2127 }2128 }2129 } else if (op === 0x2c && !tooComplexToFollowFunctions) {2130 // FDEF2131 if (inFDEF || inELSE) {2132 warn("TT: nested FDEFs not allowed");2133 tooComplexToFollowFunctions = true;2134 }2135 inFDEF = true;2136 // collecting information about which functions are defined2137 lastDeff = i;2138 funcId = stack.pop();2139 ttContext.functionsDefined[funcId] = { data, i };2140 } else if (op === 0x2d) {2141 // ENDF - end of function2142 if (inFDEF) {2143 inFDEF = false;2144 lastEndf = i;2145 } else {2146 pc = callstack.pop();2147 if (!pc) {2148 warn("TT: ENDF bad stack");2149 ttContext.hintsValid = false;2150 return;2151 }2152 funcId = functionsCalled.pop();2153 data = pc.data;2154 i = pc.i;2155 ttContext.functionsStackDeltas[funcId] = stack.length - pc.stackTop;2156 }2157 } else if (op === 0x89) {2158 // IDEF - instruction definition2159 if (inFDEF || inELSE) {2160 warn("TT: nested IDEFs not allowed");2161 tooComplexToFollowFunctions = true;2162 }2163 inFDEF = true;2164 // recording it as a function to track ENDF2165 lastDeff = i;2166 } else if (op === 0x58) {2167 // IF2168 ++ifLevel;2169 } else if (op === 0x1b) {2170 // ELSE2171 inELSE = ifLevel;2172 } else if (op === 0x59) {2173 // EIF2174 if (inELSE === ifLevel) {2175 inELSE = 0;2176 }2177 --ifLevel;2178 } else if (op === 0x1c) {2179 // JMPR2180 if (!inFDEF && !inELSE) {2181 const offset = stack[stack.length - 1];2182 // only jumping forward to prevent infinite loop2183 if (offset > 0) {2184 i += offset - 1;2185 }2186 }2187 }2188 // Adjusting stack not extactly, but just enough to get function id2189 if (!inFDEF && !inELSE) {2190 let stackDelta = 0;2191 if (op <= 0x8e) {2192 stackDelta = TTOpsStackDeltas[op];2193 } else if (op >= 0xc0 && op <= 0xdf) {2194 stackDelta = -1;2195 } else if (op >= 0xe0) {2196 stackDelta = -2;2197 }2198 if (op >= 0x71 && op <= 0x75) {2199 n = stack.pop();2200 if (!isNaN(n)) {2201 stackDelta = -n * 2;2202 }2203 }2204 while (stackDelta < 0 && stack.length > 0) {2205 stack.pop();2206 stackDelta++;2207 }2208 while (stackDelta > 0) {2209 stack.push(NaN); // pushing any number into stack2210 stackDelta--;2211 }2212 }2213 }2214 ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions;2215 const content = [data];2216 if (i > data.length) {2217 content.push(new Uint8Array(i - data.length));2218 }2219 if (lastDeff > lastEndf) {2220 warn("TT: complementing a missing function tail");2221 // new function definition started, but not finished2222 // complete function by [CLEAR, ENDF]2223 content.push(new Uint8Array([0x22, 0x2d]));2224 }2225 foldTTTable(table, content);2226 }2227 function checkInvalidFunctions(ttContext, maxFunctionDefs) {2228 if (ttContext.tooComplexToFollowFunctions) {2229 return;2230 }2231 if (ttContext.functionsDefined.length > maxFunctionDefs) {2232 warn("TT: more functions defined than expected");2233 ttContext.hintsValid = false;2234 return;2235 }2236 for (let j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {2237 if (j > maxFunctionDefs) {2238 warn("TT: invalid function id: " + j);2239 ttContext.hintsValid = false;2240 return;2241 }2242 if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) {2243 warn("TT: undefined function: " + j);2244 ttContext.hintsValid = false;2245 return;2246 }2247 }2248 }2249 function foldTTTable(table, content) {2250 if (content.length > 1) {2251 // concatenating the content items2252 let newLength = 0;2253 let j, jj;2254 for (j = 0, jj = content.length; j < jj; j++) {2255 newLength += content[j].length;2256 }2257 newLength = (newLength + 3) & ~3;2258 const result = new Uint8Array(newLength);2259 let pos = 0;2260 for (j = 0, jj = content.length; j < jj; j++) {2261 result.set(content[j], pos);2262 pos += content[j].length;2263 }2264 table.data = result;2265 table.length = newLength;2266 }2267 }2268 function sanitizeTTPrograms(fpgm, prep, cvt, maxFunctionDefs) {2269 const ttContext = {2270 functionsDefined: [],2271 functionsUsed: [],2272 functionsStackDeltas: [],2273 tooComplexToFollowFunctions: false,2274 hintsValid: true,2275 };2276 if (fpgm) {2277 sanitizeTTProgram(fpgm, ttContext);2278 }2279 if (prep) {2280 sanitizeTTProgram(prep, ttContext);2281 }2282 if (fpgm) {2283 checkInvalidFunctions(ttContext, maxFunctionDefs);2284 }2285 if (cvt && cvt.length & 1) {2286 const cvtData = new Uint8Array(cvt.length + 1);2287 cvtData.set(cvt.data);2288 cvt.data = cvtData;2289 }2290 return ttContext.hintsValid;2291 }2292 // The following steps modify the original font data, making copy2293 font = new Stream(new Uint8Array(font.getBytes()));2294 let header, tables;2295 if (isTrueTypeCollectionFile(font)) {2296 const ttcData = readTrueTypeCollectionData(font, this.name);2297 header = ttcData.header;2298 tables = ttcData.tables;2299 } else {2300 header = readOpenTypeHeader(font);2301 tables = readTables(font, header.numTables);2302 }2303 let cff, cffFile;2304 const isTrueType = !tables["CFF "];2305 if (!isTrueType) {2306 const isComposite =2307 properties.composite &&2308 ((properties.cidToGidMap || []).length > 0 ||2309 !(properties.cMap instanceof IdentityCMap));2310 // OpenType font (skip composite fonts with non-default glyph mapping).2311 if (2312 (header.version === "OTTO" && !isComposite) ||2313 !tables.head ||2314 !tables.hhea ||2315 !tables.maxp ||2316 !tables.post2317 ) {2318 // No major tables: throwing everything at `CFFFont`.2319 cffFile = new Stream(tables["CFF "].data);2320 cff = new CFFFont(cffFile, properties);2321 adjustWidths(properties);2322 return this.convert(name, cff, properties);2323 }2324 delete tables.glyf;2325 delete tables.loca;2326 delete tables.fpgm;2327 delete tables.prep;2328 delete tables["cvt "];2329 this.isOpenType = true;2330 } else {2331 if (!tables.loca) {2332 throw new FormatError('Required "loca" table is not found');2333 }2334 if (!tables.glyf) {2335 warn('Required "glyf" table is not found -- trying to recover.');2336 // Note: We use `sanitizeGlyphLocations` to add dummy glyf data below.2337 tables.glyf = {2338 tag: "glyf",2339 data: new Uint8Array(0),2340 };2341 }2342 this.isOpenType = false;2343 }2344 if (!tables.maxp) {2345 throw new FormatError('Required "maxp" table is not found');2346 }2347 font.pos = (font.start || 0) + tables.maxp.offset;2348 const version = font.getInt32();2349 const numGlyphs = font.getUint16();2350 if (2351 properties.scaleFactors &&2352 properties.scaleFactors.length === numGlyphs &&2353 isTrueType2354 ) {2355 const { scaleFactors } = properties;2356 const isGlyphLocationsLong = int16(2357 tables.head.data[50],2358 tables.head.data[51]2359 );2360 const glyphs = new GlyfTable({2361 glyfTable: tables.glyf.data,2362 isGlyphLocationsLong,2363 locaTable: tables.loca.data,2364 numGlyphs,2365 });2366 glyphs.scale(scaleFactors);2367 const { glyf, loca, isLocationLong } = glyphs.write();2368 tables.glyf.data = glyf;2369 tables.loca.data = loca;2370 if (isLocationLong !== !!isGlyphLocationsLong) {2371 tables.head.data[50] = 0;2372 tables.head.data[51] = isLocationLong ? 1 : 0;2373 }2374 const metrics = tables.hmtx.data;2375 for (let i = 0; i < numGlyphs; i++) {2376 const j = 4 * i;2377 const advanceWidth = Math.round(2378 scaleFactors[i] * int16(metrics[j], metrics[j + 1])2379 );2380 metrics[j] = (advanceWidth >> 8) & 0xff;2381 metrics[j + 1] = advanceWidth & 0xff;2382 const lsb = Math.round(2383 scaleFactors[i] * signedInt16(metrics[j + 2], metrics[j + 3])2384 );2385 writeSignedInt16(metrics, j + 2, lsb);2386 }2387 }2388 // Glyph 0 is duplicated and appended.2389 let numGlyphsOut = numGlyphs + 1;2390 let dupFirstEntry = true;2391 if (numGlyphsOut > 0xffff) {2392 dupFirstEntry = false;2393 numGlyphsOut = numGlyphs;2394 warn("Not enough space in glyfs to duplicate first glyph.");2395 }2396 let maxFunctionDefs = 0;2397 let maxSizeOfInstructions = 0;2398 if (version >= 0x00010000 && tables.maxp.length >= 22) {2399 // maxZones can be invalid2400 font.pos += 8;2401 const maxZones = font.getUint16();2402 if (maxZones > 2) {2403 // reset to 2 if font has invalid maxZones2404 tables.maxp.data[14] = 0;2405 tables.maxp.data[15] = 2;2406 }2407 font.pos += 4;2408 maxFunctionDefs = font.getUint16();2409 font.pos += 4;2410 maxSizeOfInstructions = font.getUint16();2411 }2412 tables.maxp.data[4] = numGlyphsOut >> 8;2413 tables.maxp.data[5] = numGlyphsOut & 255;2414 const hintsValid = sanitizeTTPrograms(2415 tables.fpgm,2416 tables.prep,2417 tables["cvt "],2418 maxFunctionDefs2419 );2420 if (!hintsValid) {2421 delete tables.fpgm;2422 delete tables.prep;2423 delete tables["cvt "];2424 }2425 // Ensure the hmtx table contains the advance width and2426 // sidebearings information for numGlyphs in the maxp table2427 sanitizeMetrics(2428 font,2429 tables.hhea,2430 tables.hmtx,2431 tables.head,2432 numGlyphsOut,2433 dupFirstEntry2434 );2435 if (!tables.head) {2436 throw new FormatError('Required "head" table is not found');2437 }2438 sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0);2439 let missingGlyphs = Object.create(null);2440 if (isTrueType) {2441 const isGlyphLocationsLong = int16(2442 tables.head.data[50],2443 tables.head.data[51]2444 );2445 const glyphsInfo = sanitizeGlyphLocations(2446 tables.loca,2447 tables.glyf,2448 numGlyphs,2449 isGlyphLocationsLong,2450 hintsValid,2451 dupFirstEntry,2452 maxSizeOfInstructions2453 );2454 missingGlyphs = glyphsInfo.missingGlyphs;2455 // Some fonts have incorrect maxSizeOfInstructions values, so we use2456 // the computed value instead.2457 if (version >= 0x00010000 && tables.maxp.length >= 22) {2458 tables.maxp.data[26] = glyphsInfo.maxSizeOfInstructions >> 8;2459 tables.maxp.data[27] = glyphsInfo.maxSizeOfInstructions & 255;2460 }2461 }2462 if (!tables.hhea) {2463 throw new FormatError('Required "hhea" table is not found');2464 }2465 // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth2466 // Sometimes it's 0. That needs to be fixed2467 if (tables.hhea.data[10] === 0 && tables.hhea.data[11] === 0) {2468 tables.hhea.data[10] = 0xff;2469 tables.hhea.data[11] = 0xff;2470 }2471 // Extract some more font properties from the OpenType head and2472 // hhea tables; yMin and descent value are always negative.2473 const metricsOverride = {2474 unitsPerEm: int16(tables.head.data[18], tables.head.data[19]),2475 yMax: int16(tables.head.data[42], tables.head.data[43]),2476 yMin: signedInt16(tables.head.data[38], tables.head.data[39]),2477 ascent: signedInt16(tables.hhea.data[4], tables.hhea.data[5]),2478 descent: signedInt16(tables.hhea.data[6], tables.hhea.data[7]),2479 lineGap: signedInt16(tables.hhea.data[8], tables.hhea.data[9]),2480 };2481 // PDF FontDescriptor metrics lie -- using data from actual font.2482 this.ascent = metricsOverride.ascent / metricsOverride.unitsPerEm;2483 this.descent = metricsOverride.descent / metricsOverride.unitsPerEm;2484 this.lineGap = metricsOverride.lineGap / metricsOverride.unitsPerEm;2485 if (this.cssFontInfo && this.cssFontInfo.lineHeight) {2486 this.lineHeight = this.cssFontInfo.metrics.lineHeight;2487 this.lineGap = this.cssFontInfo.metrics.lineGap;2488 } else {2489 this.lineHeight = this.ascent - this.descent + this.lineGap;2490 }2491 // The 'post' table has glyphs names.2492 if (tables.post) {2493 readPostScriptTable(tables.post, properties, numGlyphs);2494 }2495 // The original 'post' table is not needed, replace it.2496 tables.post = {2497 tag: "post",2498 data: createPostTable(properties),2499 };2500 const charCodeToGlyphId = [];2501 // Helper function to try to skip mapping of empty glyphs.2502 function hasGlyph(glyphId) {2503 return !missingGlyphs[glyphId];2504 }2505 if (properties.composite) {2506 const cidToGidMap = properties.cidToGidMap || [];2507 const isCidToGidMapEmpty = cidToGidMap.length === 0;2508 properties.cMap.forEach(function (charCode, cid) {2509 if (typeof cid === "string") {2510 cid = convertCidString(charCode, cid, /* shouldThrow = */ true);2511 }2512 if (cid > 0xffff) {2513 throw new FormatError("Max size of CID is 65,535");2514 }2515 let glyphId = -1;2516 if (isCidToGidMapEmpty) {2517 glyphId = cid;2518 } else if (cidToGidMap[cid] !== undefined) {2519 glyphId = cidToGidMap[cid];2520 }2521 if (glyphId >= 0 && glyphId < numGlyphs && hasGlyph(glyphId)) {2522 charCodeToGlyphId[charCode] = glyphId;2523 }2524 });2525 } else {2526 // Most of the following logic in this code branch is based on the2527 // 9.6.6.4 of the PDF spec.2528 const cmapTable = readCmapTable(2529 tables.cmap,2530 font,2531 this.isSymbolicFont,2532 properties.hasEncoding2533 );2534 const cmapPlatformId = cmapTable.platformId;2535 const cmapEncodingId = cmapTable.encodingId;2536 const cmapMappings = cmapTable.mappings;2537 const cmapMappingsLength = cmapMappings.length;2538 let baseEncoding = [],2539 forcePostTable = false;2540 if (2541 properties.hasEncoding &&2542 (properties.baseEncodingName === "MacRomanEncoding" ||2543 properties.baseEncodingName === "WinAnsiEncoding")2544 ) {2545 baseEncoding = getEncoding(properties.baseEncodingName);2546 }2547 // If the font has an encoding and is not symbolic then follow the rules2548 // in section 9.6.6.4 of the spec on how to map 3,1 and 1,0 cmaps.2549 if (2550 properties.hasEncoding &&2551 !this.isSymbolicFont &&2552 ((cmapPlatformId === 3 && cmapEncodingId === 1) ||2553 (cmapPlatformId === 1 && cmapEncodingId === 0))2554 ) {2555 const glyphsUnicodeMap = getGlyphsUnicode();2556 for (let charCode = 0; charCode < 256; charCode++) {2557 let glyphName;2558 if (this.differences[charCode] !== undefined) {2559 glyphName = this.differences[charCode];2560 } else if (baseEncoding.length && baseEncoding[charCode] !== "") {2561 glyphName = baseEncoding[charCode];2562 } else {2563 glyphName = StandardEncoding[charCode];2564 }2565 if (!glyphName) {2566 continue;2567 }2568 // Ensure that non-standard glyph names are resolved to valid ones.2569 const standardGlyphName = recoverGlyphName(2570 glyphName,2571 glyphsUnicodeMap2572 );2573 let unicodeOrCharCode;2574 if (cmapPlatformId === 3 && cmapEncodingId === 1) {2575 unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName];2576 } else if (cmapPlatformId === 1 && cmapEncodingId === 0) {2577 // TODO: the encoding needs to be updated with mac os table.2578 unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName);2579 }2580 if (unicodeOrCharCode === undefined) {2581 // Not a valid glyph name, fallback to using the /ToUnicode map2582 // when no post-table exists (fixes issue13316_reduced.pdf).2583 if (2584 !properties.glyphNames &&2585 properties.hasIncludedToUnicodeMap &&2586 !(this.toUnicode instanceof IdentityToUnicodeMap)2587 ) {2588 const unicode = this.toUnicode.get(charCode);2589 if (unicode) {2590 unicodeOrCharCode = unicode.codePointAt(0);2591 }2592 }2593 if (unicodeOrCharCode === undefined) {2594 continue; // No valid glyph mapping found.2595 }2596 }2597 for (let i = 0; i < cmapMappingsLength; ++i) {2598 if (cmapMappings[i].charCode !== unicodeOrCharCode) {2599 continue;2600 }2601 charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;2602 break;2603 }2604 }2605 } else if (cmapPlatformId === 0) {2606 // Default Unicode semantics, use the charcodes as is.2607 for (let i = 0; i < cmapMappingsLength; ++i) {2608 charCodeToGlyphId[cmapMappings[i].charCode] = cmapMappings[i].glyphId;2609 }2610 // Always prefer the BaseEncoding/Differences arrays, when they exist2611 // (fixes issue13433.pdf).2612 forcePostTable = true;2613 } else {2614 // When there is only a (1, 0) cmap table, the char code is a single2615 // byte and it is used directly as the char code.2616 // When a (3, 0) cmap table is present, it is used instead but the2617 // spec has special rules for char codes in the range of 0xF000 to2618 // 0xF0FF and it says the (3, 0) table should map the values from2619 // the (1, 0) table by prepending 0xF0 to the char codes. To reverse2620 // this, the upper bits of the char code are cleared, but only for the2621 // special range since some PDFs have char codes outside of this range2622 // (e.g. 0x2013) which when masked would overwrite other values in the2623 // cmap.2624 for (let i = 0; i < cmapMappingsLength; ++i) {2625 let charCode = cmapMappings[i].charCode;2626 if (2627 cmapPlatformId === 3 &&2628 charCode >= 0xf000 &&2629 charCode <= 0xf0ff2630 ) {2631 charCode &= 0xff;2632 }2633 charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;2634 }2635 }2636 // Last, try to map any missing charcodes using the post table.2637 if (2638 properties.glyphNames &&2639 (baseEncoding.length || this.differences.length)2640 ) {2641 for (let i = 0; i < 256; ++i) {2642 if (!forcePostTable && charCodeToGlyphId[i] !== undefined) {2643 continue;2644 }2645 const glyphName = this.differences[i] || baseEncoding[i];2646 if (!glyphName) {2647 continue;2648 }2649 const glyphId = properties.glyphNames.indexOf(glyphName);2650 if (glyphId > 0 && hasGlyph(glyphId)) {2651 charCodeToGlyphId[i] = glyphId;2652 }2653 }2654 }2655 }2656 if (charCodeToGlyphId.length === 0) {2657 // defines at least one glyph2658 charCodeToGlyphId[0] = 0;2659 }2660 // Typically glyph 0 is duplicated and the mapping must be updated, but if2661 // there isn't enough room to duplicate, the glyph id is left the same. In2662 // this case, glyph 0 may not work correctly, but that is better than2663 // having the whole font fail.2664 let glyphZeroId = numGlyphsOut - 1;2665 if (!dupFirstEntry) {2666 glyphZeroId = 0;2667 }2668 // When `cssFontInfo` is set, the font is used to render text in the HTML2669 // view (e.g. with Xfa) so nothing must be moved in the private use area.2670 if (!properties.cssFontInfo) {2671 // Converting glyphs and ids into font's cmap table2672 const newMapping = adjustMapping(2673 charCodeToGlyphId,2674 hasGlyph,2675 glyphZeroId2676 );2677 this.toFontChar = newMapping.toFontChar;2678 tables.cmap = {2679 tag: "cmap",2680 data: createCmapTable(newMapping.charCodeToGlyphId, numGlyphsOut),2681 };2682 if (!tables["OS/2"] || !validateOS2Table(tables["OS/2"], font)) {2683 tables["OS/2"] = {2684 tag: "OS/2",2685 data: createOS2Table(2686 properties,2687 newMapping.charCodeToGlyphId,2688 metricsOverride2689 ),2690 };2691 }2692 }2693 if (!isTrueType) {2694 try {2695 // Trying to repair CFF file2696 cffFile = new Stream(tables["CFF "].data);2697 const parser = new CFFParser(2698 cffFile,2699 properties,2700 SEAC_ANALYSIS_ENABLED2701 );2702 cff = parser.parse();2703 cff.duplicateFirstGlyph();2704 const compiler = new CFFCompiler(cff);2705 tables["CFF "].data = compiler.compile();2706 } catch (e) {2707 warn("Failed to compile font " + properties.loadedName);2708 }2709 }2710 // Re-creating 'name' table2711 if (!tables.name) {2712 tables.name = {2713 tag: "name",2714 data: createNameTable(this.name),2715 };2716 } else {2717 // ... using existing 'name' table as prototype2718 const namePrototype = readNameTable(tables.name);2719 tables.name.data = createNameTable(name, namePrototype);2720 this.psName = namePrototype[0][6] || null;2721 }2722 const builder = new OpenTypeFileBuilder(header.version);2723 for (const tableTag in tables) {2724 builder.addTable(tableTag, tables[tableTag].data);2725 }2726 return builder.toArray();2727 }2728 convert(fontName, font, properties) {2729 // TODO: Check the charstring widths to determine this.2730 properties.fixedPitch = false;2731 if (properties.builtInEncoding) {2732 // For Type1 fonts that do not include either `ToUnicode` or `Encoding`2733 // data, attempt to use the `builtInEncoding` to improve text selection.2734 adjustToUnicode(properties, properties.builtInEncoding);2735 }2736 // Type 1 fonts have a notdef inserted at the beginning, so glyph 02737 // becomes glyph 1. In a CFF font glyph 0 is appended to the end of the2738 // char strings.2739 let glyphZeroId = 1;2740 if (font instanceof CFFFont) {2741 glyphZeroId = font.numGlyphs - 1;2742 }2743 const mapping = font.getGlyphMapping(properties);2744 let newMapping = null;2745 let newCharCodeToGlyphId = mapping;2746 // When `cssFontInfo` is set, the font is used to render text in the HTML2747 // view (e.g. with Xfa) so nothing must be moved in the private use area.2748 if (!properties.cssFontInfo) {...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1var wptexturize = require('wptexturize');2var adjustToUnicode = wptexturize.adjustToUnicode;3console.log(adjustToUnicode('\'"'));4var wptexturize = require('wptexturize');5var adjustToHtmlEntities = wptexturize.adjustToHtmlEntities;6console.log(adjustToHtmlEntities('\'"'));7var wptexturize = require('wptexturize');8var adjustToHtmlEntitiesExcludingQuotes = wptexturize.adjustToHtmlEntitiesExcludingQuotes;9console.log(adjustToHtmlEntitiesExcludingQuotes('\'"'));10[MIT](

Full Screen

Using AI Code Generation

copy

Full Screen

1var texturize = require('wptexturize');2var text = 'To quote someone, use the "quotation" mark.';3var text1 = 'To quote someone, use the \'quotation\' mark.';4var text2 = 'To quote someone, use the &quot;quotation&quot; mark.';5var text3 = 'To quote someone, use the &#8220;quotation&#8221; mark.';6var text4 = 'To quote someone, use the &#x201C;quotation&#x201D; mark.';7console.log(texturize.adjustToUnicode(text));8console.log(texturize.adjustToUnicode(text1));9console.log(texturize.adjustToUnicode(text2));10console.log(texturize.adjustToUnicode(text3));11console.log(texturize.adjustToUnicode(text4));12MIT © [Kunal Kushwaha](

Full Screen

Using AI Code Generation

copy

Full Screen

1var wp = require('wptexturize');2var text = 'This is a test string. It has "quotes" and \'quotes\' and (parentheses) and {brackets} and [brackets] and --em-dashes-- and ---em-dashes--- and ...ellipses... and ...ellipses. It has a single quote in it\'s and a double quote in it" and a single quote in it’s and a double quote in it".';3console.log(wp.adjustToUnicode(text));4var wp = require('wptexturize');5var text = 'This is a test string. It has "quotes" and \'quotes\' and (parentheses) and {brackets} and [brackets] and --em-dashes-- and ---em-dashes--- and ...ellipses... and ...ellipses. It has a single quote in it\'s and a double quote in it" and a single quote in it’s and a double quote in it".';6console.log(wp.convertHighAscii(text));7var wp = require('wpt

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run wpt automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful