extractFontProgram()

in src/core/type1_parser.js [553:715]


  extractFontProgram(properties) {
    const stream = this.stream;

    const subrs = [],
      charstrings = [];
    const privateData = Object.create(null);
    privateData.lenIV = 4;
    const program = {
      subrs: [],
      charstrings: [],
      properties: {
        privateData,
      },
    };
    let token, length, data, lenIV;
    while ((token = this.getToken()) !== null) {
      if (token !== "/") {
        continue;
      }
      token = this.getToken();
      switch (token) {
        case "CharStrings":
          // The number immediately following CharStrings must be greater or
          // equal to the number of CharStrings.
          this.getToken();
          this.getToken(); // read in 'dict'
          this.getToken(); // read in 'dup'
          this.getToken(); // read in 'begin'
          while (true) {
            token = this.getToken();
            if (token === null || token === "end") {
              break;
            }

            if (token !== "/") {
              continue;
            }
            const glyph = this.getToken();
            length = this.readInt();
            this.getToken(); // read in 'RD' or '-|'
            data = length > 0 ? stream.getBytes(length) : new Uint8Array(0);
            lenIV = program.properties.privateData.lenIV;
            const encoded = this.readCharStrings(data, lenIV);
            this.nextChar();
            token = this.getToken(); // read in 'ND' or '|-'
            if (token === "noaccess") {
              this.getToken(); // read in 'def'
            } else if (token === "/") {
              // The expected 'ND' or '|-' token is missing, avoid swallowing
              // the start of the next glyph (fixes issue14462_reduced.pdf).
              this.prevChar();
            }
            charstrings.push({
              glyph,
              encoded,
            });
          }
          break;
        case "Subrs":
          this.readInt(); // num
          this.getToken(); // read in 'array'
          while (this.getToken() === "dup") {
            const index = this.readInt();
            length = this.readInt();
            this.getToken(); // read in 'RD' or '-|'
            data = length > 0 ? stream.getBytes(length) : new Uint8Array(0);
            lenIV = program.properties.privateData.lenIV;
            const encoded = this.readCharStrings(data, lenIV);
            this.nextChar();
            token = this.getToken(); // read in 'NP' or '|'
            if (token === "noaccess") {
              this.getToken(); // read in 'put'
            }
            subrs[index] = encoded;
          }
          break;
        case "BlueValues":
        case "OtherBlues":
        case "FamilyBlues":
        case "FamilyOtherBlues":
          const blueArray = this.readNumberArray();
          // *Blue* values may contain invalid data: disables reading of
          // those values when hinting is disabled.
          if (
            blueArray.length > 0 &&
            blueArray.length % 2 === 0 &&
            HINTING_ENABLED
          ) {
            program.properties.privateData[token] = blueArray;
          }
          break;
        case "StemSnapH":
        case "StemSnapV":
          program.properties.privateData[token] = this.readNumberArray();
          break;
        case "StdHW":
        case "StdVW":
          program.properties.privateData[token] = this.readNumberArray()[0];
          break;
        case "BlueShift":
        case "lenIV":
        case "BlueFuzz":
        case "BlueScale":
        case "LanguageGroup":
          program.properties.privateData[token] = this.readNumber();
          break;
        case "ExpansionFactor":
          // Firefox doesn't render correctly a font with a null factor on
          // Windows (see issue 15289), hence we just reset it to its default
          // value (0.06).
          program.properties.privateData[token] = this.readNumber() || 0.06;
          break;
        case "ForceBold":
          program.properties.privateData[token] = this.readBoolean();
          break;
      }
    }

    for (const { encoded, glyph } of charstrings) {
      const charString = new Type1CharString();
      const error = charString.convert(
        encoded,
        subrs,
        this.seacAnalysisEnabled
      );
      let output = charString.output;
      if (error) {
        // It seems when FreeType encounters an error while evaluating a glyph
        // that it completely ignores the glyph so we'll mimic that behaviour
        // here and put an endchar to make the validator happy.
        output = [14];
      }
      const charStringObject = {
        glyphName: glyph,
        charstring: output,
        width: charString.width,
        lsb: charString.lsb,
        seac: charString.seac,
      };
      if (glyph === ".notdef") {
        // Make sure .notdef is at index zero (issue #11477).
        program.charstrings.unshift(charStringObject);
      } else {
        program.charstrings.push(charStringObject);
      }

      // Attempt to replace missing widths, from the font dictionary /Widths
      // entry, with ones from the font data (fixes issue11150_reduced.pdf).
      if (properties.builtInEncoding) {
        const index = properties.builtInEncoding.indexOf(glyph);
        if (
          index > -1 &&
          properties.widths[index] === undefined &&
          index >= properties.firstChar &&
          index <= properties.lastChar
        ) {
          properties.widths[index] = charString.width;
        }
      }
    }

    return program;
  }