bmx-ng / text.mod

Text Utilities
0 stars 3 forks source link

Implemented harfbuzz based freetype font renderer. #30

Closed woollybah closed 3 months ago

woollybah commented 3 months ago

Introduces support for open type font features, such as kerning,

GWRon commented 3 months ago

Question regarding module scope: If this now is "text.hbfreetypefont", should we consider moving "brl.freetypefont" to "text.freetypefont" too (and keep some stub "brl.freetypefontdefault.mod" or so - similar to other "stub modules" for backwards compatibility?)

Or should the hbfreetypefont be moved to some other scope? Depends on what you expect to find in "text.mod" - I assume it is for working with "texts/documents"? I myself would have expected some kind of "font.mod"-module-scope (font.freetypefont.mod, font.hbfreetypefont.mod) but yeah, if the original fonttypefont module is to become deprecated then maybe an "extra main module scope" is not necessary.

GWRon commented 3 months ago

The above comment aside - just want to confirm that it works on linux (as almost expected):

image

GWRon commented 3 months ago

ttf font from there: https://fonts.google.com/specimen/Vollkorn

SuperStrict

Framework sdl.sdlrendermax2d
Import Text.HBFreeTypeFont
Import Brl.StandardIO

Graphics 1024, 768, 0

Local t:Int
Local c:Int = 1000000
Local fontOTF:TImageFont = LoadImageFont( "fonts/Vollkorn-Regular.otf", 16, SMOOTHFONT )
Local fontTTF:TImageFont = LoadImageFont( "fonts/Vollkorn-Regular.ttf", 16, SMOOTHFONT )

cls
SetImageFont fontOTF
t = Millisecs()
For local i:int = 0 until c
    DrawText "Hello World 0123456789", 100, 100
Next
Flip
print c+"x HBImageFont.DrawText took " + (Millisecs() -t) +"ms."

cls
SetImageFont fontTTF
t = Millisecs()
For local i:int = 0 until c
    DrawText "Hello World 0123456789", 100, 100
Next
Flip
print c+"x ImageFont.DrawText took " + (Millisecs() -t) +"ms."

for me it spit out:

1000000x HBImageFont.DrawText took 8696ms.
1000000x ImageFont.DrawText took 6309ms.

so yes, it seems to be slower a bit (but the features are of course... niceeeee)

Now I tried to run a debug build ...

small_caps: /home/ronny/Arbeit/Tools/BlitzMaxNG/mod/text.mod/hbfreetypefont.mod/harfbuzz/src/hb-buffer.hh:523: void hb_buffer_t::assert_unicode(): Assertion `(content_type == HB_BUFFER_CONTENT_TYPE_UNICODE) || (!len && (content_type == HB_BUFFER_CONTENT_TYPE_INVALID))' failed.`

Will raise an issue for it.

woollybah commented 3 months ago

Well, I had considered adding it to the core, but since harfbuzz is rather heavy, I decided not to.

I was only going to implement the bare minimum for small caps, but then realised it would be cool to have proper kerning available, so I added the methods to build the positional data based on full strings - which is required for this kind of thing - rather than passing single characters around.

I've optimised it a bit, trying to have it use the caches as much as possible, but it always needs to recalculate the shaping/positioning per string. I had considered doing some kind of string hash cache to speed it up even more, but all these things take up more memory. It's not too bad for a first pass.

I was always expecting it to be slower than a pure freetype implementation.

But then you are now getting proper font spacing and stuff.. so... Depends what you want to do. For my use case, I'm not bothered if it's a bit slower - I get the rendering that I want.

GWRon commented 3 months ago

In my bitmapfont-"functionality" I used a "cache-struct" (and an accompanying cache-struct-type to wrap it for easier "transportation"). That struct contains the calculated values and thus eg. could be useful if you render "the same text over and over" . So it is up to the "coder-user" if they want a recalculation each frame or use a "cache".

This "information cache" in my case is more used for "text block" information (where to wrap etc) which btw is I guess one of the "most useful" extensions to font rendering aside of "utf" (foreign languages...). I assume many people display "block text" somewhere ("mission text" etc). ... but I do not want to derail the issue/PR.

Of course this makes the code even "heavier" (more code ...).

Regarding performance itself: yeah, as said the features might outweigh it. And if you render your texts into some rendertarget, then this can be neglected at all (and it easily allows rotating "text blocks" more easily - else you need to do all the calculations as I did in the bitmapfont-files)