convert()

in src/core/fonts.js [3116:3314]


  convert(fontName, font, properties) {
    // TODO: Check the charstring widths to determine this.
    properties.fixedPitch = false;

    if (properties.builtInEncoding) {
      // For Type1 fonts that do not include either `ToUnicode` or `Encoding`
      // data, attempt to use the `builtInEncoding` to improve text selection.
      adjustType1ToUnicode(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;
    }
    const mapping = font.getGlyphMapping(properties);
    let newMapping = null;
    let newCharCodeToGlyphId = mapping;
    let toUnicodeExtraMap = null;

    // When `cssFontInfo` is set, the font is used to render text in the HTML
    // view (e.g. with Xfa) so nothing must be moved in the private use area.
    if (!properties.cssFontInfo) {
      newMapping = adjustMapping(
        mapping,
        font.hasGlyphId.bind(font),
        glyphZeroId,
        this.toUnicode
      );
      this.toFontChar = newMapping.toFontChar;
      newCharCodeToGlyphId = newMapping.charCodeToGlyphId;
      toUnicodeExtraMap = newMapping.toUnicodeExtraMap;
    }
    const numGlyphs = font.numGlyphs;

    function getCharCodes(charCodeToGlyphId, glyphId) {
      let charCodes = null;
      for (const charCode in charCodeToGlyphId) {
        if (glyphId === charCodeToGlyphId[charCode]) {
          (charCodes ||= []).push(charCode | 0);
        }
      }
      return charCodes;
    }

    function createCharCode(charCodeToGlyphId, glyphId) {
      for (const charCode in charCodeToGlyphId) {
        if (glyphId === charCodeToGlyphId[charCode]) {
          return charCode | 0;
        }
      }
      newMapping.charCodeToGlyphId[newMapping.nextAvailableFontCharCode] =
        glyphId;
      return newMapping.nextAvailableFontCharCode++;
    }

    const seacs = font.seacs;
    if (newMapping && SEAC_ANALYSIS_ENABLED && seacs?.length) {
      const matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX;
      const charset = font.getCharset();
      const seacMap = Object.create(null);
      for (let glyphId in seacs) {
        glyphId |= 0;
        const seac = seacs[glyphId];
        const baseGlyphName = StandardEncoding[seac[2]];
        const accentGlyphName = StandardEncoding[seac[3]];
        const baseGlyphId = charset.indexOf(baseGlyphName);
        const accentGlyphId = charset.indexOf(accentGlyphName);
        if (baseGlyphId < 0 || accentGlyphId < 0) {
          continue;
        }
        const accentOffset = {
          x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4],
          y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5],
        };

        const charCodes = getCharCodes(mapping, glyphId);
        if (!charCodes) {
          // There's no point in mapping it if the char code was never mapped
          // to begin with.
          continue;
        }
        for (const charCode of charCodes) {
          // Find a fontCharCode that maps to the base and accent glyphs.
          // If one doesn't exists, create it.
          const charCodeToGlyphId = newMapping.charCodeToGlyphId;
          const baseFontCharCode = createCharCode(
            charCodeToGlyphId,
            baseGlyphId
          );
          const accentFontCharCode = createCharCode(
            charCodeToGlyphId,
            accentGlyphId
          );
          seacMap[charCode] = {
            baseFontCharCode,
            accentFontCharCode,
            accentOffset,
          };
        }
      }
      properties.seacMap = seacMap;
    }

    const unitsPerEm = properties.fontMatrix
      ? 1 / Math.max(...properties.fontMatrix.slice(0, 4).map(Math.abs))
      : 1000;

    const builder = new OpenTypeFileBuilder("\x4F\x54\x54\x4F");
    // PostScript Font Program
    builder.addTable("CFF ", font.data);
    // OS/2 and Windows Specific metrics
    builder.addTable("OS/2", createOS2Table(properties, newCharCodeToGlyphId));
    // Character to glyphs mapping
    builder.addTable(
      "cmap",
      createCmapTable(newCharCodeToGlyphId, toUnicodeExtraMap, numGlyphs)
    );
    // Font header
    builder.addTable(
      "head",
      "\x00\x01\x00\x00" + // Version number
        "\x00\x00\x10\x00" + // fontRevision
        "\x00\x00\x00\x00" + // checksumAdjustement
        "\x5F\x0F\x3C\xF5" + // magicNumber
        "\x00\x00" + // Flags
        safeString16(unitsPerEm) + // unitsPerEM
        "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // creation date
        "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // modifification date
        "\x00\x00" + // xMin
        safeString16(properties.descent) + // yMin
        "\x0F\xFF" + // xMax
        safeString16(properties.ascent) + // yMax
        string16(properties.italicAngle ? 2 : 0) + // macStyle
        "\x00\x11" + // lowestRecPPEM
        "\x00\x00" + // fontDirectionHint
        "\x00\x00" + // indexToLocFormat
        "\x00\x00"
    ); // glyphDataFormat

    // Horizontal header
    builder.addTable(
      "hhea",
      "\x00\x01\x00\x00" + // Version number
        safeString16(properties.ascent) + // Typographic Ascent
        safeString16(properties.descent) + // Typographic Descent
        "\x00\x00" + // Line Gap
        "\xFF\xFF" + // advanceWidthMax
        "\x00\x00" + // minLeftSidebearing
        "\x00\x00" + // minRightSidebearing
        "\x00\x00" + // xMaxExtent
        safeString16(properties.capHeight) + // caretSlopeRise
        safeString16(Math.tan(properties.italicAngle) * properties.xHeight) + // caretSlopeRun
        "\x00\x00" + // caretOffset
        "\x00\x00" + // -reserved-
        "\x00\x00" + // -reserved-
        "\x00\x00" + // -reserved-
        "\x00\x00" + // -reserved-
        "\x00\x00" + // metricDataFormat
        string16(numGlyphs)
    ); // Number of HMetrics

    // Horizontal metrics
    builder.addTable(
      "hmtx",
      (function fontFieldsHmtx() {
        const charstrings = font.charstrings;
        const cffWidths = font.cff ? font.cff.widths : null;
        let hmtx = "\x00\x00\x00\x00"; // Fake .notdef
        for (let i = 1, ii = numGlyphs; i < ii; i++) {
          let width = 0;
          if (charstrings) {
            const charstring = charstrings[i - 1];
            width = "width" in charstring ? charstring.width : 0;
          } else if (cffWidths) {
            width = Math.ceil(cffWidths[i] || 0);
          }
          hmtx += string16(width) + string16(0);
        }
        return hmtx;
      })()
    );

    // Maximum profile
    builder.addTable(
      "maxp",
      "\x00\x00\x50\x00" + string16(numGlyphs) // Version number
    ); // Num of glyphs

    // Naming tables
    builder.addTable("name", createNameTable(fontName));

    // PostScript information
    builder.addTable("post", createPostTable(properties));

    return builder.toArray();
  }