ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
61.49k stars 10.35k forks source link

Questions about usage of ImFont/ImFontConfig/ImFontAtlas #2346

Open ocornut opened 5 years ago

ocornut commented 5 years ago

(EDITED) Fixed typos.

Hello,

I've been looking at the font system lately (planning a long awaited refactor that may happen soonish).

Going to use this thread to ask questions/feedback, in particular trying to find users who use certain features to get details about their usage. I am aware I may not get enough feedback as the fields/features mentioned of those are rarely used, but let's try.

Q1. Have you ever used ImFontConfig::GlyphExtraSpacing ? If so, why and how? (which fonts, etc.) This was added in 1.43. While removing the extraneous spacing in ProggyClean, I added this field as a I thought it would be useful. I am highly enclined to obsolete it now.

Q2. Have you ever used ImFontAtlas::AddCustomRectFontGlyph() ? If so, why and how? This was primarily designed in 1.52 to be able to map color icons into codepoints of a font (by registering rectangles and copying graphics into them after packing). I suspect no-one has been using it (I added it to fullfil a specific private request but I don't think the team requesting it ended up using it). My problem is that in order to support a system for more naturally resizing fonts (e.g. 1 ImFont can map to different sizes transparently) this will need to be redesigned somehow.

Q3. Have you ever altered the ImFontConfig::GlyphOffset or ImFont::DisplayOffset field? If so why, for which fonts at which size? Are you using non-integer values for any of them? This is much easier to answer, I'm pretty sure lots of people are altering DisplayOffset.y but I'd be interested in getting specific examples (font/size). In #1619 stfx said "[...] used ImFontConfig.GlyphOffset.y to fix vertical font alignment when merging multiple fonts", that's one legit use case. (Edit) Likewise, the reason I am interested in this is because this field is often used a manual adjustment which may not scale linearly/properly. If we want to support a font-size cache how this field is handled needs to be addressed.

Thanks!

ocornut commented 5 years ago

Q4. Are you creating ImFontAtlas instance yourself? e.g. to share a font atlas between 2 imgui context, or another reason, please provide details!

ocornut commented 5 years ago

Q5. Are you using ImFont::SetFallbackChar()? This function was added in 1.35 but never advertised. I would like to move this information to ImFontConfig and have it be immutable.

floooh commented 5 years ago

No to all your specific questions, but I was recently looking for a way to add a simple raster font for using extracted old-school 8-bit home computer fonts without having to go through TTF, basically just a pre-baked font atlas texture, and an array of simplified glyph infos (only texture coords, or similar).

Totally low-prio though :)

ocornut commented 5 years ago

I was recently looking for a way to add a simple raster font for using extracted old-school 8-bit home computer fonts without having to go through TTF,

Was about to send this paragraph:

It is close to be possible or maybe already possible probably by twisting some code (or just bypassing Build/ImFontAtlasBuildWithStbTruetype, calling the ImFontAtlasBuild** helpers yourself and font->AddGlyph) but probably messy. This is probably should I could keep in the corner of my mind as I refactor the system, I agree if would be healthy.

Then I realized, it is already possible if you do the following:

Awkward but doable. That itself is a vote of confidence for AddCustomRectFontGlyph() :)

eclshunter commented 5 years ago

I've been looking at AddCustomRectFontGlyph for colorful icons just recently but haven't used it yet. The plan already was to use .svg icons so that they would work regardless of DPI scaling. Support for custom and colorful iconography is definitely something I would use.

jadwallis commented 5 years ago

We're not using / doing any of the things in questions 1-5.

However, we do use ImFontAtlas::TexGlyphPadding to facilitate a hack we have that adds an outline to the glyphs in the font texture. We do this to allow us to have windows with very translucent backgrounds (to avoid obscuring the game behind), while keeping the text legible regardless of what's going on in the background.

The degree to which either of these use cases is advisable is certainly debatable (and something I'd like to revisit one day when I have time), but I bring it up as an information point since it seems relevant to the topic. :)

r-lyeh commented 5 years ago

I use tiny vertical positive/negative adjustments for GlyphOffset.y to align GoogleMaterialIcons with my main high-dpi fonts (for example, m-plus medium 18pt)

bsenftner commented 5 years ago

Q4. Are you creating ImFontAtlas instance yourself? e.g. to share a font atlas between 2 imgui context, or another reason, please provide details!

Yes, in a video/security app the main window creates an ImFontAtlas which is then shared with any Video Windows that may later be created. The font atlas itself is the Roboto-Medium loaded with 11 different point sizes, for the various sizes of overlay text in our video/security app.

Unit2Ed commented 5 years ago

No to all the other questions, though we have been thinking about using custom rect for colourful icons/emoji support.

Q4. Are you creating ImFontAtlas instance yourself? e.g. to share a font atlas between 2 imgui context, or another reason, please provide details!

I'll need to double-check, but I'm fairly sure the Segross Unreal 4 plugin shares a font atlas between PIE clients, where multiple game clients, each with their own ImGui context are started within the same process.

If you're looking at doing some font refactoring, it'd be great if we could have a more dynamic atlas that only fills in glyphs that are needed in the sizes rendered. We're looking to add support for more script types to our project, and some of the Asian languages include an awful lot of glyphs; multiplied by a couple of fonts (with some size, bold and italic variants), it's going to overflow a 4kx4k atlas quite easily. I expect the additional usage tracking would have a detrimental effect on the performance of CalcTextSize though.

Xeverous commented 4 years ago

As a new user of ImGui:

frink commented 4 years ago

The only one that applies to me:

Q3. Have you ever altered the ImFontConfig::GlyphOffset or ImFont::DisplayOffset field? If so why, for which fonts at which size? Are you using non-integer values for any of them?

I've used ImFontConfig::GlyphOffset (using floats for fine tuned alignment) to align an in-house custom icon font (generated by IcoMoon) and a general typography font (Sintony from Google) merged at a size of 16.

Also of note, I used the following general config to allow me to have different font sizes:

FontConf->RasterizeMultiply = 2
FontConf->OversampleH = 2
FontConf->OversampleV = 2

I can use ImGui::SetWindowFontScale() to size my fonts and give me different sizes depending on need. But the GlyphOffset was key in getting these two fonts to play nice together.

sturnclaw commented 1 year ago

If feedback on this point is still helpful, I've just finished implementing a code path for loading SVG icons into ImGui fonts:

Q2. Have you ever used ImFontAtlas::AddCustomRectFontGlyph() ? If so, why and how?

Yes. To break down our font handling in Pioneer:

This relies on using ImFontAtlas::AddCustomRectFontGlyph() to reserve space for the icon pixel data (at the rasterized resolution, which can be smaller than the font size), then running a separate pass after building the atlas to copy in the pixel data for each icon's rect. We also poke at the generated glyph after building and alter its dimensions if using a lower-resolution fallback texture, as AddCustomRectFontGlyph() assumes that the glyph will be the same size as the pixel data.

Currently, my only feedback on the problem is that it's very inconvenient to have to run two passes on either side of building the atlas texture. In our case, the pixel data used for the custom rects is already available when we add the rect, and it'd be very convenient (though not necessary) if ImGui could handle blitting from an A8 or RGBA32 array as part of "normal" font handling.

I could see this also being very useful for e.g. SDF fonts or other bitmap-based font loading if suitably generic, but a simple (ptr, buffer_pitch, bytes_per_pixel) tuple could fully describe the operation that the font builder code would need to do.

learn-more commented 9 months ago

Considering this is still open, I am assuming you are still looking for input.

2 + 4) Yes, for some simple toolbar icons. (See attached snippet / image) Having them in the main font simplifies displaying them.

ImFontAtlas / AddCustomRectFontGlyph usage:

struct IconSetMetrics
{
    const int width;
    const int height;
    const int output_top_padding;
};

struct Icon
{
    const int offset_x;
    const int width;
    const char id;
    const ImU32 color;
    int rect_id = 0;

    void add_rect(ImFontAtlas* atlas, ImFont* font, const IconSetMetrics* iconset)
    {
        rect_id = atlas->AddCustomRectFontGlyph(font, id, width, iconset->height + iconset->output_top_padding, float(width + 1));
    }

    void fill_rect(ImFontAtlas* atlas, const IconSetMetrics* iconset, const char* pixel_ascii_art) const
    {
        // Code based on ImFontAtlasBuildRender32bppRectFromString, but taking into account we want just one glyph from the iconset
        const ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(rect_id);
        IM_ASSERT(r->IsPacked());

        const int x = r->X;
        const int y = r->Y;
        IM_ASSERT(r->Width == width && r->Height == iconset->height + iconset->output_top_padding);
        IM_ASSERT(x >= 0 && x + width <= atlas->TexWidth);
        IM_ASSERT(y >= 0 && y + iconset->height <= atlas->TexHeight);
        // Take into account that we want to have some padding at the top (so the icons appear aligned with text)
        unsigned int* out_pixel = atlas->TexPixelsRGBA32 + x + ((y + iconset->output_top_padding) * atlas->TexWidth);
        for (int off_y = 0; off_y < iconset->height; off_y++, out_pixel += atlas->TexWidth)
        {
            const char* in_str = pixel_ascii_art + (off_y * iconset->width) + offset_x;
            for (int off_x = 0; off_x < width; off_x++)
            {
                out_pixel[off_x] = (in_str[off_x] == ' ') ? IM_COL32_BLACK_TRANS : color;
            }
        }
    }
};

constexpr IconSetMetrics Metrics = {27, 10, 2};

// Built with https://asciiflow.com/
static constexpr std::array<char, Metrics.width * Metrics.height + 1> icon_data = {
">                     iii  "
">>   |||  |||sssssss  iii  "
">>>  |||  |||sssssssiiiiiii"
">>>> |||  |||sssssss iiiii "
">>>>>|||  |||sssssss  iii  "
">>>> |||  |||sssssss   i   "
">>>  |||  |||sssssss       "
">>   |||  |||sssssss  iii  "
">                    iiiii "
"                      iii  "
};

Icon icon_play = { 0, 5, '\x14', IM_COL32(0, 155, 14, 255) };
Icon icon_pause = { 5, 8, '\x15', IM_COL32(155, 106, 0, 255) };
Icon icon_stop = { 13, 7, '\x16', IM_COL32(155, 0, 0, 255) };
Icon icon_step = { 20, 7, '\x17', IM_COL32(127, 106, 0, 255) };

void build_atlas(ImFontAtlas* atlas)
{
    ImFont* font = atlas->AddFontDefault();

    std::array<Icon*, 4> icons{ { &icon_play, &icon_pause, &icon_stop, &icon_step } };

    for (auto* icon : icons)
        icon->add_rect(atlas, font, &Metrics);

    // Force RGBA32 data generation (and trigger a build)
    unsigned char* pixels = nullptr;
    atlas->GetTexDataAsRGBA32(&pixels, nullptr, nullptr);
    IM_ASSERT(pixels);

    for (const auto* icon : icons)
        icon->fill_rect(atlas, &Metrics, icon_data.data());
}
//
void toolbar()
{
    ImGui::Dummy(ImVec2(40, 0));
    ImGui::Separator();

    ImGui::Button("\x14##Play");
    ImGui::SameLine();

    ImGui::Button("\x15##Pause");
    ImGui::SameLine();

    ImGui::Button("\x16##Stop");
    ImGui::SameLine();

    ImGui::Button("\x17##Step");
    ImGui::Separator();
}

And the resulting toolbar: image