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

Example for using fonts. #303

Closed
Omustardo opened this issue Sep 15, 2024 · 5 comments
Closed

Example for using fonts. #303

Omustardo opened this issue Sep 15, 2024 · 5 comments
Labels
documentation Improvements or additions to documentation

Comments

@Omustardo
Copy link

Omustardo commented Sep 15, 2024

I've spent a good bit of time trying to work with fonts recently. First to just trying to load them, and then using them at different sizes and scales. I wanted to add to the examples/ directory, but as I was writing the example I realized that I don't actually understand some of the FontConfig options, and there are a few remaining issues. Instead I'll include what I've done here as a reference for others.

Example code

EDIT 2024-09-25: This example works, but the way it scales text isn't ideal - it ends up being very blurry. Examples in later comments are better, but this shows the basics of working with fonts so I'm leaving it as-is.

https://gist.github.com/Omustardo/79db81d8fddf91fa22d8caa4b94bd1ac

References

Golang <-> C issues

When loading a font, the functions (e.g. AddFontFromMemoryTTF) require a uintptr, but it wasn't clear what this should refer to. Claude eventually got me to a working solution:

var defaultFontTTF []byte // load the content of a .ttf file into this slice
fontDataPtr := uintptr(unsafe.Pointer(&defaultFontTTF[0]))
fontDataLen := int32(len(defaultFontTTF))
imgui.CurrentIO().Fonts().AddFontFromMemoryTTF(fontDataPtr, fontDataLen, float32(DefaultFontSize))

If you crash with the message: "munmap_chunk(): invalid pointer", it's because C code is trying to free memory that's managed by golang. To avoid this, I loaded fonts using FontConfig structs, which let me mark the uintptr as not being owned by imgui's font atlas:

fontDataPtr := uintptr(unsafe.Pointer(&defaultFontTTF[0]))
fontDataLen := int32(len(defaultFontTTF))

cfg := imgui.NewFontConfig()
// This option lets golang manage the memory of the provided data.
// If this is true, imgui will crash when trying to clean up with the error: `munmap_chunk(): invalid pointer`
cfg.SetFontDataOwnedByAtlas(false)

cfg.SetSizePixels(pixelSize)
cfg.SetFontData(fontDataPtr)
cfg.SetFontDataSize(fontDataLen)
cfg.SetOversampleV(1)
cfg.SetOversampleV(1)
cfg.SetPixelSnapH(true)
return imgui.CurrentIO().Fonts().AddFont(cfg)

Compressed fonts

There's also a way to compress fonts before loading them, like imgui.CurrentIO().Fonts().AddFontFromMemoryCompressedBase85TTF()

I didn't try this out myself, but the compressed input can be created with https://github.com/ocornut/imgui/blob/master/misc/fonts/binary_to_compressed_c.cpp

This shouldn't ever be needed in Go, because it's easier to just embed TTF files. See the example section below for an example of this.

When to load

imgui has some information and documentation about loading fonts, but strangely I didn't find anything about how to load them while using an imgui backend.

If I put the font loading within my loop, I got:

panic: Assertion failed!
        File: /home/runner/work/cimgui-go/cimgui-go/cimgui/imgui/imgui_draw.cpp, Line 2177
        
        Expression: !Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"

That was a bit surprising because I never call NewFrame() anywhere - it's handled by the backend.

I tried loading from the main method, before the main loop and got:

panic: Assertion failed!
        File: /home/runner/work/cimgui-go/cimgui-go/cimgui/imgui/imgui.cpp, Line 4386
        
        Expression: GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?"

Instead, load fonts within the backend.SetBeforeRenderHook callback. Note that this function is called every frame so you should guard the loading of fonts with a boolean so it isn't done more than once.

Remaining issues

In the example (https://gist.github.com/Omustardo/79db81d8fddf91fa22d8caa4b94bd1ac), I tried to set the current font size and scale:

	oldSize := imgui.CurrentFont().FontSize()
	imgui.CurrentFont().SetFontSize(userConfigurableFontSize)
	imgui.Text(fmt.Sprintf("5. Font with user-configurabled size: %.2f", userConfigurableFontSize))
	imgui.CurrentFont().SetFontSize(oldSize)

	oldScale := imgui.CurrentFont().Scale()
	imgui.CurrentFont().SetScale(userConfigurableFontScale)
	imgui.Text(fmt.Sprintf("6. Font with user-configurabled scale: %.2f", userConfigurableFontScale))
	imgui.CurrentFont().SetScale(oldScale)

I'm almost certainly mis-using these, but I don't think they work in an intuitive manner.

  • SetFontSize: If you create a font with pixelSize 25 and then SetFontSize(25), it looks the same as if you didn't SetFontSize. If you call SetFontSize(50), the text shows up as half of its original size. Similarly, if you use a smaller font size than the initial size, the font actually looks larger! It seems like this sets a ratio that's based on the size that the text was originally loaded at. I'm not sure why it's like this, or what the expected usage is.

  • With SetFontSize, widgets don't adjust around the resized text. This causes larger text to overlaps other content. I'd probably stay away from this option and just load fonts at the desired sizes in the first place, but there may be something that I'm missing that would make this work.

  • Setting the font scale doesn't seem to do anything at all.

@gucio321
Copy link
Collaborator

When loading a font, the functions (e.g. AddFontFromMemoryTTF) require a uintptr, but it wasn't clear what this should refer to. Claude eventually got me to a working solution:

That's because it is void* in C so this was only way to wrap it

Instead, load fonts within the backend.SetBeforeRenderHook callback. Note that this function is called every frame so you should guard the loading of fonts with a boolean so it isn't done more than once.

There is AfterCreateContext so you don't need boolean

@Omustardo
Copy link
Author

Nice, thanks. That's a bit cleaner. Here's an example of it for future readers: https://gist.github.com/Omustardo/e5cbbe57f6bd82bc0788d83c360ae493

@gucio321 gucio321 added the documentation Improvements or additions to documentation label Sep 19, 2024
@Omustardo
Copy link
Author

Another font related demo. This one shows loading a font at many different sizes, and switching between them. This avoids issues with loading a single font and then scaling it with (imgui.CurrentFont().SetScale(x) or imgui.CurrentIO().SetFontGlobalScale()), which results in blurry or pixelated text. I'm not sure when those are meant to be used.

https://gist.github.com/Omustardo/b15eedaacabdf4d6a6579f762a8a05a0

@gucio321
Copy link
Collaborator

@Omustardo can we close this and just pin and mark with documentation label a sthis isn't actually an issue - its just Dear ImGui design 😄

@gucio321 gucio321 pinned this issue Sep 25, 2024
@gucio321 gucio321 changed the title Add example for using fonts Example for using fonts. Sep 25, 2024
@Omustardo
Copy link
Author

Sounds good, I'll add more font related examples here as I learn more.

My next goal is to implement scaling for different DPI's based on https://github.com/ocornut/imgui/blob/d0107f5da2343004cd0a9b23402d8620b8387e03/docs/FAQ.md?plain=1#L539

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

2 participants