constructor(name, file, properties, evaluatorOptions) {
    this.name = name;
    this.psName = null;
    this.mimetype = null;
    this.disableFontFace = evaluatorOptions.disableFontFace;
    this.fontExtraProperties = evaluatorOptions.fontExtraProperties;

    this.loadedName = properties.loadedName;
    this.isType3Font = properties.isType3Font;
    this.missingFile = false;
    this.cssFontInfo = properties.cssFontInfo;

    this._charsCache = Object.create(null);
    this._glyphCache = Object.create(null);

    let isSerifFont = !!(properties.flags & FontFlags.Serif);
    // Fallback to checking the font name, in order to improve text-selection,
    // since the /Flags-entry is often wrong (fixes issue13845.pdf).
    if (!isSerifFont && !properties.isSimulatedFlags) {
      const baseName = name.replaceAll(/[,_]/g, "-").split("-", 1)[0],
        serifFonts = getSerifFonts();
      for (const namePart of baseName.split("+")) {
        if (serifFonts[namePart]) {
          isSerifFont = true;
          break;
        }
      }
    }
    this.isSerifFont = isSerifFont;

    this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
    this.isMonospace = !!(properties.flags & FontFlags.FixedPitch);

    let { type, subtype } = properties;
    this.type = type;
    this.subtype = subtype;
    this.systemFontInfo = properties.systemFontInfo;

    const matches = name.match(/^InvalidPDFjsFont_(.*)_\d+$/);
    this.isInvalidPDFjsFont = !!matches;
    if (this.isInvalidPDFjsFont) {
      this.fallbackName = matches[1];
    } else if (this.isMonospace) {
      this.fallbackName = "monospace";
    } else if (this.isSerifFont) {
      this.fallbackName = "serif";
    } else {
      this.fallbackName = "sans-serif";
    }

    if (this.systemFontInfo?.guessFallback) {
      // Once the fallback name is guessed, we don't want to guess it again.
      this.systemFontInfo.guessFallback = false;
      this.systemFontInfo.css += `,${this.fallbackName}`;
    }

    this.differences = properties.differences;
    this.widths = properties.widths;
    this.defaultWidth = properties.defaultWidth;
    this.composite = properties.composite;
    this.cMap = properties.cMap;
    this.capHeight = properties.capHeight / PDF_GLYPH_SPACE_UNITS;
    this.ascent = properties.ascent / PDF_GLYPH_SPACE_UNITS;
    this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS;
    this.lineHeight = this.ascent - this.descent;
    this.fontMatrix = properties.fontMatrix;
    this.bbox = properties.bbox;
    this.defaultEncoding = properties.defaultEncoding;

    this.toUnicode = properties.toUnicode;
    this.toFontChar = [];

    if (properties.type === "Type3") {
      for (let charCode = 0; charCode < 256; charCode++) {
        this.toFontChar[charCode] =
          this.differences[charCode] || properties.defaultEncoding[charCode];
      }
      return;
    }

    this.cidEncoding = properties.cidEncoding || "";
    this.vertical = !!properties.vertical;
    if (this.vertical) {
      this.vmetrics = properties.vmetrics;
      this.defaultVMetrics = properties.defaultVMetrics;
    }

    if (!file || file.isEmpty) {
      if (file) {
        // Some bad PDF generators will include empty font files,
        // attempting to recover by assuming that no file exists.
        warn('Font file is empty in "' + name + '" (' + this.loadedName + ")");
      }
      this.fallbackToSystemFont(properties);
      return;
    }

    // Parse the font file to determine the correct type/subtype, rather than
    // relying on the (often incorrect) data in the font dictionary; (see e.g.
    //  issue6782.pdf, issue7598.pdf, and issue9949.pdf).
    [type, subtype] = getFontFileType(file, properties);

    if (type !== this.type || subtype !== this.subtype) {
      info(
        "Inconsistent font file Type/SubType, expected: " +
          `${this.type}/${this.subtype} but found: ${type}/${subtype}.`
      );
    }

    let data;
    try {
      switch (type) {
        case "MMType1":
          info("MMType1 font (" + name + "), falling back to Type1.");
        /* falls through */
        case "Type1":
        case "CIDFontType0":
          this.mimetype = "font/opentype";

          const cff =
            subtype === "Type1C" || subtype === "CIDFontType0C"
              ? new CFFFont(file, properties)
              : new Type1Font(name, file, properties);

          adjustWidths(properties);

          // Wrap the CFF data inside an OTF font file
          data = this.convert(name, cff, properties);
          break;

        case "OpenType":
        case "TrueType":
        case "CIDFontType2":
          this.mimetype = "font/opentype";

          // Repair the TrueType file. It is can be damaged in the point of
          // view of the sanitizer
          data = this.checkAndRepair(name, file, properties);

          adjustWidths(properties);

          if (this.isOpenType) {
            type = "OpenType";
          }
          break;

        default:
          throw new FormatError(`Font ${type} is not supported`);
      }
    } catch (e) {
      warn(e);
      this.fallbackToSystemFont(properties);
      return;
    }

    amendFallbackToUnicode(properties);
    this.data = data;

    // Transfer some properties again that could change during font conversion
    this.type = type;
    this.subtype = subtype;
    this.fontMatrix = properties.fontMatrix;
    this.widths = properties.widths;
    this.defaultWidth = properties.defaultWidth;
    this.toUnicode = properties.toUnicode;
    this.seacMap = properties.seacMap;
  }
'Font' is the class the outside world should use, it encapsulate all the font
decoding logics whatever type it is (assuming the font type is supported).