async translateFont()

in src/core/evaluator.js [4301:4594]


  async translateFont({
    descriptor,
    dict,
    baseDict,
    composite,
    type,
    firstChar,
    lastChar,
    toUnicode,
    cssFontInfo,
  }) {
    const isType3Font = type === "Type3";

    if (!descriptor) {
      if (isType3Font) {
        const bbox = lookupNormalRect(dict.getArray("FontBBox"), [0, 0, 0, 0]);
        // FontDescriptor is only required for Type3 fonts when the document
        // is a tagged pdf. Create a barbebones one to get by.
        descriptor = new Dict(null);
        descriptor.set("FontName", Name.get(type));
        descriptor.set("FontBBox", bbox);
      } else {
        // Before PDF 1.5 if the font was one of the base 14 fonts, having a
        // FontDescriptor was not required.
        // This case is here for compatibility.
        let baseFontName = dict.get("BaseFont");
        if (!(baseFontName instanceof Name)) {
          throw new FormatError("Base font is not specified");
        }

        // Using base font name as a font name.
        baseFontName = baseFontName.name.replaceAll(/[,_]/g, "-");
        const metrics = this.getBaseFontMetrics(baseFontName);

        // Simulating descriptor flags attribute
        const fontNameWoStyle = baseFontName.split("-", 1)[0];
        const flags =
          (this.isSerifFont(fontNameWoStyle) ? FontFlags.Serif : 0) |
          (metrics.monospace ? FontFlags.FixedPitch : 0) |
          (getSymbolsFonts()[fontNameWoStyle]
            ? FontFlags.Symbolic
            : FontFlags.Nonsymbolic);

        const properties = {
          type,
          name: baseFontName,
          loadedName: baseDict.loadedName,
          systemFontInfo: null,
          widths: metrics.widths,
          defaultWidth: metrics.defaultWidth,
          isSimulatedFlags: true,
          flags,
          firstChar,
          lastChar,
          toUnicode,
          xHeight: 0,
          capHeight: 0,
          italicAngle: 0,
          isType3Font,
        };
        const widths = dict.get("Widths");

        const standardFontName = getStandardFontName(baseFontName);
        let file = null;
        if (standardFontName) {
          file = await this.fetchStandardFontData(standardFontName);
          properties.isInternalFont = !!file;
        }
        if (!properties.isInternalFont && this.options.useSystemFonts) {
          properties.systemFontInfo = getFontSubstitution(
            this.systemFontCache,
            this.idFactory,
            this.options.standardFontDataUrl,
            baseFontName,
            standardFontName,
            type
          );
        }

        const newProperties = await this.extractDataStructures(
          dict,
          properties
        );
        if (Array.isArray(widths)) {
          const glyphWidths = [];
          let j = firstChar;
          for (const w of widths) {
            const width = this.xref.fetchIfRef(w);
            if (typeof width === "number") {
              glyphWidths[j] = width;
            }
            j++;
          }
          newProperties.widths = glyphWidths;
        } else {
          newProperties.widths = this.buildCharCodeToWidth(
            metrics.widths,
            newProperties
          );
        }
        return new Font(baseFontName, file, newProperties, this.options);
      }
    }

    // According to the spec if 'FontDescriptor' is declared, 'FirstChar',
    // 'LastChar' and 'Widths' should exist too, but some PDF encoders seem
    // to ignore this rule when a variant of a standard font is used.
    // TODO Fill the width array depending on which of the base font this is
    // a variant.

    let fontName = descriptor.get("FontName");
    let baseFont = dict.get("BaseFont");
    // Some bad PDFs have a string as the font name.
    if (typeof fontName === "string") {
      fontName = Name.get(fontName);
    }
    if (typeof baseFont === "string") {
      baseFont = Name.get(baseFont);
    }

    const fontNameStr = fontName?.name;
    const baseFontStr = baseFont?.name;
    if (!isType3Font && fontNameStr !== baseFontStr) {
      info(
        `The FontDescriptor's FontName is "${fontNameStr}" but ` +
          `should be the same as the Font's BaseFont "${baseFontStr}".`
      );
      // - Workaround for cases where e.g. fontNameStr = 'Arial' and
      //   baseFontStr = 'Arial,Bold' (needed when no font file is embedded).
      //
      // - Workaround for cases where e.g. fontNameStr = 'wg09np' and
      //   baseFontStr = 'Wingdings-Regular' (fixes issue7454.pdf).
      if (
        fontNameStr &&
        baseFontStr &&
        (baseFontStr.startsWith(fontNameStr) ||
          (!isKnownFontName(fontNameStr) && isKnownFontName(baseFontStr)))
      ) {
        fontName = null;
      }
    }
    fontName ||= baseFont;

    if (!(fontName instanceof Name)) {
      throw new FormatError("invalid font name");
    }

    let fontFile, subtype, length1, length2, length3;
    try {
      fontFile = descriptor.get("FontFile", "FontFile2", "FontFile3");

      if (fontFile) {
        if (!(fontFile instanceof BaseStream)) {
          throw new FormatError("FontFile should be a stream");
        } else if (fontFile.isEmpty) {
          throw new FormatError("FontFile is empty");
        }
      }
    } catch (ex) {
      if (!this.options.ignoreErrors) {
        throw ex;
      }
      warn(`translateFont - fetching "${fontName.name}" font file: "${ex}".`);
      fontFile = null;
    }
    let isInternalFont = false;
    let glyphScaleFactors = null;
    let systemFontInfo = null;
    if (fontFile) {
      if (fontFile.dict) {
        const subtypeEntry = fontFile.dict.get("Subtype");
        if (subtypeEntry instanceof Name) {
          subtype = subtypeEntry.name;
        }
        length1 = fontFile.dict.get("Length1");
        length2 = fontFile.dict.get("Length2");
        length3 = fontFile.dict.get("Length3");
      }
    } else if (cssFontInfo) {
      // We've a missing XFA font.
      const standardFontName = getXfaFontName(fontName.name);
      if (standardFontName) {
        cssFontInfo.fontFamily = `${cssFontInfo.fontFamily}-PdfJS-XFA`;
        cssFontInfo.metrics = standardFontName.metrics || null;
        glyphScaleFactors = standardFontName.factors || null;
        fontFile = await this.fetchStandardFontData(standardFontName.name);
        isInternalFont = !!fontFile;

        // We're using a substitution font but for example widths (if any)
        // are related to the glyph positions in the font.
        // So we overwrite everything here to be sure that widths are
        // correct.
        baseDict = dict = getXfaFontDict(fontName.name);
        composite = true;
      }
    } else if (!isType3Font) {
      const standardFontName = getStandardFontName(fontName.name);
      if (standardFontName) {
        fontFile = await this.fetchStandardFontData(standardFontName);
        isInternalFont = !!fontFile;
      }
      if (!isInternalFont && this.options.useSystemFonts) {
        systemFontInfo = getFontSubstitution(
          this.systemFontCache,
          this.idFactory,
          this.options.standardFontDataUrl,
          fontName.name,
          standardFontName,
          type
        );
      }
    }

    const fontMatrix = lookupMatrix(
      dict.getArray("FontMatrix"),
      FONT_IDENTITY_MATRIX
    );
    const bbox = lookupNormalRect(
      descriptor.getArray("FontBBox") || dict.getArray("FontBBox"),
      undefined
    );
    let ascent = descriptor.get("Ascent");
    if (typeof ascent !== "number") {
      ascent = undefined;
    }
    let descent = descriptor.get("Descent");
    if (typeof descent !== "number") {
      descent = undefined;
    }
    let xHeight = descriptor.get("XHeight");
    if (typeof xHeight !== "number") {
      xHeight = 0;
    }
    let capHeight = descriptor.get("CapHeight");
    if (typeof capHeight !== "number") {
      capHeight = 0;
    }
    let flags = descriptor.get("Flags");
    if (!Number.isInteger(flags)) {
      flags = 0;
    }
    let italicAngle = descriptor.get("ItalicAngle");
    if (typeof italicAngle !== "number") {
      italicAngle = 0;
    }

    const properties = {
      type,
      name: fontName.name,
      subtype,
      file: fontFile,
      length1,
      length2,
      length3,
      isInternalFont,
      loadedName: baseDict.loadedName,
      composite,
      fixedPitch: false,
      fontMatrix,
      firstChar,
      lastChar,
      toUnicode,
      bbox,
      ascent,
      descent,
      xHeight,
      capHeight,
      flags,
      italicAngle,
      isType3Font,
      cssFontInfo,
      scaleFactors: glyphScaleFactors,
      systemFontInfo,
    };

    if (composite) {
      const cidEncoding = baseDict.get("Encoding");
      if (cidEncoding instanceof Name) {
        properties.cidEncoding = cidEncoding.name;
      }
      const cMap = await CMapFactory.create({
        encoding: cidEncoding,
        fetchBuiltInCMap: this._fetchBuiltInCMapBound,
        useCMap: null,
      });
      properties.cMap = cMap;
      properties.vertical = properties.cMap.vertical;
    }

    const newProperties = await this.extractDataStructures(dict, properties);
    this.extractWidths(dict, descriptor, newProperties);

    return new Font(fontName.name, fontFile, newProperties, this.options);
  }