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

Prevent unwanted font face changes #4882

Merged
merged 1 commit into from
May 20, 2020

Conversation

Gliese852
Copy link
Contributor

@Gliese852 Gliese852 commented May 18, 2020

Before PR -> After PR
Screenshot from 2020-05-18 13-37-17
Screenshot from 2020-05-18 13-36-36

When a character appears in the text, that is not in the default font face, it is looked up in the spare faces of the font. Moreover, the default range also began to be baked from the spare face. I made that only characters that are not in the default face are baked from the spare face.

To visually see what is this all about, start a new game and click on Toliman in the sector map. In it's description there is an "α" (alpha) symbol, this leads to a complete change of the font face. This PR fixes this issue.

Fixes #4807

@Gliese852
Copy link
Contributor Author

Gliese852 commented May 19, 2020

I think it's worth explaining in more detail the logic of what is happening there.

The font contains several faces:

pioneer/src/pigui/PiGui.cpp

Lines 131 to 135 in 97f0641

PiFont guifont("pionillium", {
PiFace("DejaVuSans.ttf", 13.0 / 14.0),
PiFace("wqy-microhei.ttc", 1.0),
PiFace("PionilliumText22L-Medium.ttf", 1.0)
});

it can be seen that the main face is at the end of the vector. How does it become main? A range is added to it (when a glyph is added, a range is created for it if it does not fall into an existing one, and in the beginning we don't have any ranges at all)
pifont.faces().back().addGlyph(0x20); // only add space

In the next function, the font texture is created, apparently

pioneer/src/pigui/PiGui.cpp

Lines 347 to 386 in 97f0641

void Instance::BakeFont(PiFont &font)
{
PROFILE_SCOPED()
ImGuiIO &io = ImGui::GetIO();
ImFont *imfont = nullptr;
for (PiFace &face : font.faces()) {
ImFontConfig config;
config.MergeMode = true;
float size = font.pixelsize() * face.sizefactor();
const std::string path = FileSystem::JoinPath(FileSystem::JoinPath(FileSystem::GetDataDir(), "fonts"), face.ttfname());
// Output("- baking face %s at size %f\n", path.c_str(), size);
face.sortUsedRanges();
if (face.used_ranges().size() > 0) {
face.m_imgui_ranges.clear();
ImFontAtlas::GlyphRangesBuilder gb;
// Always include the default range
gb.AddRanges(io.Fonts->GetGlyphRangesDefault());
ImWchar gr[3] = { 0, 0, 0 };
for (auto &range : face.used_ranges()) {
// Output("Used range: %x - %x", range.first, range.second);
gr[0] = range.first;
gr[1] = range.second;
gb.AddRanges(gr);
}
gb.BuildRanges(&face.m_imgui_ranges);
ImFont *f = io.Fonts->AddFontFromFileTTF(path.c_str(), size, imfont == nullptr ? nullptr : &config, face.m_imgui_ranges.Data);
assert(f);
if (imfont != nullptr)
assert(f == imfont);
imfont = f;
}
}
m_im_fonts[imfont] = std::make_pair(font.name(), font.pixelsize());
// Output("setting %s %i to %p\n", font.name(), font.pixelsize(), imfont);
m_fonts[std::make_pair(font.name(), font.pixelsize())] = imfont;
if (!imfont->MissingGlyphs.empty()) {
Output("WARNING: glyphs missing in shiny new font\n");
}
imfont->MissingGlyphs.clear();
}

It iterates over all faces of the font, in turn (note that the main face comes last)
for (PiFace &face : font.faces()) {

but it ignores faces without ranges, and in the beginning we have a range only for the main face.
if (face.used_ranges().size() > 0) {

А default range is added to each iterated face
gb.AddRanges(io.Fonts->GetGlyphRangesDefault());

that's what this range is
// Retrieve list of range (2 int per range, values are inclusive)
const ImWchar* ImFontAtlas::GetGlyphRangesDefault()
{
static const ImWchar ranges[] =
{
0x0020, 0x00FF, // Basic Latin + Latin Supplement
0,
};
return &ranges[0];
}

If after rendering, the dear ImGui lacks a glyph, this function is called (I omitted the details, there are a lot of calls, as I understand it)

pioneer/src/pigui/PiGui.cpp

Lines 161 to 184 in 97f0641

void Instance::AddGlyph(ImFont *font, unsigned short glyph)
{
PROFILE_SCOPED()
// range glyph..glyph
auto iter = m_im_fonts.find(font);
if (iter == m_im_fonts.end()) {
Error("Cannot find font instance for ImFont %p\n", font);
assert(false);
}
auto pifont_iter = m_pi_fonts.find(iter->second);
if (pifont_iter == m_pi_fonts.end()) {
Error("No registered PiFont for name %s size %i\n", iter->second.first.c_str(), iter->second.second);
assert(false);
}
PiFont &pifont = pifont_iter->second;
for (PiFace &face : pifont.faces()) {
if (face.isValidGlyph(glyph)) {
face.addGlyph(glyph);
m_should_bake_fonts = true;
return;
}
}
Error("No face in font %s handles glyph %i\n", pifont.name().c_str(), glyph);
}

It checks all faces of the font for the presence of a given glyph, and also sets the flag that fonts need to be rebuilt

pioneer/src/pigui/PiGui.cpp

Lines 176 to 181 in 97f0641

for (PiFace &face : pifont.faces()) {
if (face.isValidGlyph(glyph)) {
face.addGlyph(glyph);
m_should_bake_fonts = true;
return;
}

Аnd now we have a range not only in the main face, but also in some other. When baking, it will go before the main face, and now the default range will be added to it. When it comes to the main face, the range will already be occupied.

Therefore, I simply rearranged the main face to the top of the list, and the default range will always be taken from it, and everything that cannot be found in it will be taken from other faces.

@sturnclaw
Copy link
Member

@Gliese852 can I ask you to please transcribe that excellent write-up to function comments? The overall architecture is something I've had to reverse-engineer myself when doing imgui upgrades, and I'd really really appreciate having this overview as function comments so future maintainers (and future me!) don't have to recreate it.

When a character appears in the text, that is not in the default font
face, it is looked up in the spare faces of the font. Moreover, the
default range also began to be baked from the spare face. I made that
only characters that are not in the default face are baked from the
spare face.

Also renamed ImFontAtlas::GlyphRangesBuilder to ImFontGlyphRangesBuilder
because:
contrib/imgui/imgui.h:2181:
typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+
@Gliese852
Copy link
Contributor Author

@Web-eWorks Done, tried to adapt this information, because in this topic it is written how it works now, and in the comments in the code, I wrote how it will work after the patch.

@sturnclaw sturnclaw merged commit ec10d8c into pioneerspacesim:master May 20, 2020
@Gliese852 Gliese852 deleted the fix-font-faces branch December 15, 2020 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

UI font suddenly changes to smaller font size
2 participants