Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: load system fonts automatically #686

Open
std-microblock opened this issue Oct 4, 2024 · 5 comments
Open

feat: load system fonts automatically #686

std-microblock opened this issue Oct 4, 2024 · 5 comments
Labels
discussion Meta talk and feedback enhancement New feature or request

Comments

@std-microblock
Copy link
Contributor

Bringing fonts into the binary creates more distribution burdens and legal risks. I suppose exposing an interface on the system interface to make the process of searching and loading system fonts automatic. The CSS font-family fallback order can be used.

@std-microblock
Copy link
Contributor Author

I currently achieved this on Windows with this code.

std::vector<std::filesystem::path> getSystemFonts() {
  std::vector<std::filesystem::path> fonts;

  std::string windowsPath(MAX_PATH, '\0');
  GetWindowsDirectoryA(windowsPath.data(), windowsPath.size());
  windowsPath.resize(strlen(windowsPath.c_str()));

  HKEY hKey;
  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                   "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts", 0,
                   KEY_READ, &hKey) == ERROR_SUCCESS) {
    DWORD index = 0;
    char valueName[16383];
    DWORD valueNameSize = 16383;
    DWORD valueType;
    BYTE valueData[16383];
    DWORD valueDataSize = 16383;
    while (RegEnumValue(hKey, index, valueName, &valueNameSize, NULL,
                        &valueType, valueData,
                        &valueDataSize) == ERROR_SUCCESS) {
      fonts.push_back(std::filesystem::path(windowsPath) / "Fonts" /
                      std::string(valueData, valueData + valueDataSize));
      index++;
      valueNameSize = 16383;
      valueDataSize = 16383;
    }
    RegCloseKey(hKey);
  }

  if (RegOpenKeyEx(HKEY_CURRENT_USER,
                   "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts", 0,
                   KEY_READ, &hKey) == ERROR_SUCCESS) {
    DWORD index = 0;
    char valueName[16383];
    DWORD valueNameSize = 16383;
    DWORD valueType;
    BYTE valueData[16383];
    DWORD valueDataSize = 16383;
    while (RegEnumValue(hKey, index, valueName, &valueNameSize, NULL,
                        &valueType, valueData,
                        &valueDataSize) == ERROR_SUCCESS) {
      fonts.push_back(std::filesystem::path(
          std::string(valueData, valueData + valueDataSize)));
      index++;
      valueNameSize = 16383;
      valueDataSize = 16383;
    }
    RegCloseKey(hKey);
  }

  return fonts;
}

struct FontFace {
  std::string name;
  std::string style;
  int weight;
  std::string file;
};

std::vector<FontFace> getFontFaces() {
  auto fonts = getSystemFonts();
  std::vector<FontFace> fontFaces;
  FT_Library library;
  FT_Init_FreeType(&library);
  for (auto &font : fonts) {
    FT_Face face;
    if (!FT_New_Face(library, font.string().c_str(), 0, &face)) {

      TT_OS2 *font_table = (TT_OS2 *)FT_Get_Sfnt_Table(face, FT_SFNT_OS2);
      int weight;
      if (font_table && font_table->usWeightClass != 0)
        weight = font_table->usWeightClass;
      else
        weight = (face->style_flags & FT_STYLE_FLAG_BOLD) == FT_STYLE_FLAG_BOLD
                     ? 700
                     : 400;

      fontFaces.push_back({.name = face->family_name,
                           .style = face->style_name,
                           .weight = weight,
                           .file = font.string()});
      FT_Done_Face(face);
    }
  }

  return fontFaces;
}

// In main()

auto fonts = getFontFaces();
auto loadFonts = [&](std::initializer_list<std::string> preferedFonts,
                     bool fallback, bool onlyOne = true) {
  for (auto &preferedFont : preferedFonts) {
    auto font = std::find_if(fonts.begin(), fonts.end(), [&](auto &font) {
      return font.name == preferedFont && font.weight == 400;
    });

    if (font != fonts.end()) {
      Rml::LoadFontFace(font->file, fallback);
      if (onlyOne)
        return;
    }
  }
};

loadFonts(
  {
      "Arial",
  },
  false);

loadFonts(
  {
      "Noto Sans SC",
      "Microsoft YaHei",
      "Segoe UI",
  },
  true);

@std-microblock
Copy link
Contributor Author

This can also involve a load-when-used system for fonts and glyphs. Currently the memory consumption is significant for loading a font.

@mikke89 mikke89 added enhancement New feature or request discussion Meta talk and feedback labels Oct 6, 2024
@mikke89
Copy link
Owner

mikke89 commented Oct 6, 2024

We do have one example for loading system fonts: https://github.com/mikke89/RmlUi/blob/master/Samples/basic/ime/src/SystemFontWin32.cpp

I don't really think it's the right thing for our library to include this functionality in our core library. We want to give users full control over things like font resources. So any such platform-specific behavior should be located on the user side. We might later consider adding it to the platform layer,

Some kind of system for loading fonts as needed based on provided families and glyphs would indeed be nice. See in particular #500, I believe that issue should cover this feature already.

@std-microblock
Copy link
Contributor Author

The things is that it's extremely awful for users to implement this. Consider the following three points:

  1. CSS font family fallback cannot be used. This prevents softwares from providing compatibility.
  2. All the fonts have to be in memory to be used in html. This increases memory consumption greatly. If only one glyph is used, then we should only store one glyph in the memory, instead of everything about the font + the glyph. This is how real-world browsers works.
  3. Users have to implement their own system as there's no default one to use. This makes cross-platform more difficult, also highering the confusion of beginners.

@std-microblock
Copy link
Contributor Author

The thing can be optional, but we really should have something like that in the platform abstraction. if the user is not satisfied with the default implementation, they can use theit own's

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Meta talk and feedback enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants