HeapsIO / heaps

Heaps : Haxe Game Framework
http://heaps.io
MIT License
3.19k stars 338 forks source link

Font packing tool #709

Closed trethaller closed 4 years ago

trethaller commented 4 years ago

💰 Bounty - 250€

Note: currently being worked on (see comments)

We would like a command-line tool to automate the creation of Heaps fonts, supporting both Bitmap and SDF font types.

There are two stages in the generation process:

The program accepts a configuration file as argument (see below).

General requirements:

Configuration file

[
    {
        "input": "path/to/font.ttf",
        "output": "path/to/output_font.fnt",
        "charset": ["ansi", "latin1"],
        "fontSize": 12,
        "padding": {
            "top": 1,
            "bottom": 2,
            "left": 1,
            "right": 1
        },
        "dfSize": 6
    }
]

Rendering parameters

Most parameters seen in the configuration file should be self-explanatory.

charset

can be defined as:

dfSize

SDF only: size of the distance field gradient in pixels.

Note that depending on the method of calculation other parameters can be introduced.

Packing parameters

Ideally the packing tries to output with a global image ratio close to a square. If necessary a texWidth parameter could be used, as a max limit to the output texture width, in pixels.

For padding it is useful to separate sizes in 4 directions, for example to add effects such as drop shadows as a post-process step on the resulting texture.

Implementation

For SDF glyph rendering, an option would be to use https://github.com/Chlumsky/msdfgen, with parallel system calls to the msdfgen binary. The various SDF options (sdf, psdf, msdf) could be exposed directly in addition to bitmap.

Another option would be to use lime.graphics.cairo for glyph rendering.

For glyph packing, see this algorithm or the implementation of fontbuilder.

Yanrishatum commented 4 years ago

I've managed to get some progress on that front. Not usable yet, but major parts of the tool are operational.

Primary target is HL, but because I use ammel, it should also work with HXCPP and Eval (when it's fixed in ammel). It's partially done in C, but right now 14pt font generates in a matter of around 1 second with lots of debug print from haxe side. Dependencies:

What a surprise that I actually went and started doing it. image

trethaller commented 4 years ago

Haha I'm almost feel bad for "just letting you know". Awesome stuff though 👍

Yanrishatum commented 4 years ago

Okay, status update. MVP version of the tool is ready and can be found here: https://github.com/Yanrishatum/fontgen

Performance

Currently, when rasterizing test config, it takes less than 2 seconds to process 4 fonts at size 24 with different sdf method each, using charset slightly bigger than basic latin.

Supported targets

Tools uses ammer, hence allows for HL, HXCPP and interp (depending on state of ammer). Currently I only used HL bytecode, but I suppose HLC would work as well.

Multi-platform

As long as msdfgen can do that - that tool can too. But there is a need to fix makefiles in order to compile it for linux/mac.

Processing order

I pack glyphs first, only then render onto texture based on packing results. I use MaxRect algorithm with BestShortSideFit heuristic, and pack into bin with maximum size of 4k, then crop the size based on xMax/yMax before starting rasterization.

Design decisions

Multiple inputs

It is possible to specify multiple fonts per config.

Reason: fallback fonts. Since Heaps does not support multi-texture font files, it's preferable to bake all glyphs into one texture, which can pose a problem when dealing with localizations. Obviously, majority of fonts don't carry all possible characters inside, especially for asian languages. Adding fallback fonts it's possible to specify extra fonts for characters that aren't present in the primary font.

Possible improvement: Along manual fallback fonts it's possible to hook into OS on C side and fetch fonts from here. My knowledge of C/OS-level programming isn't that extensive to implement such thing, however.

Separate padding and spacing

Those are separate and act as follows:

raster mode is not an "honest" rasterization

I used generateMSDF -> renderSDF approach to rasterization of glyphs. Because of that - it is de-facto the slowest rasterization mode. Also see notes below.

Reason: Initally my focus was on SDF rasterizer, and regular pre-render was tacked on in "at least it works" manner. Yes, pretty lame reason. :)

Possible improvements:

Non-spec data in .fnt file

Because BMF format does not differentiate between SDF and non-SDF font, I decided to add extra line that describes SDF method and parameters. It's added at the end of file, because from what I've seen - some parser really depend on line order being info, common, page[], chars, char[], kernings, kerning[] and would panic if it would find some unaccounted line between any of those.

Present issues

.fnt file and integers

This is a pretty big problem for SDF fonts. BMF operates on integers everywhere, while for SDF all ofthe are preferable in either funits or ems to allow accurate scaling and alignment (especially vertical alignment). I would prefer to not modify format further and introduce custom font descriptor for SDF fonts. There are multiple possible implementations:

I will implement support for custom format in Heaps, obviously. It won't affect current font pipeline.

Current regular rasterizer is a hack-job

As mentioned previously, it's kinda tacked on, but on top of that it currently uses magic numbers in order to render itself. E.g. offsets glyphs with sort of arbitrary values during MSDF generation. Solutions were mentioned already.

Alpha channel always 1, even on raster

I most likely will add flag that allows to which channel sdf/psdf/raster modes will output (greyscale/alpha).

Math behind calculation of xadvance, xoffset and yoffset is sketchy

What the title says. :) Currently it renders "okay-ish", but it's very apparent with yoffset - letters sometimes don't sit on the baseline. I need to revise the rasterizer parameters (specifically - translate offsets) as well as fnt generator in order to ensure they are rendered at baseline with integer values. Even if we'll use custom SDF descriptor, I still need to do that, because tool will have to support output in both .fnt and custom format. And I don't like magic numbers in the code.

Output texture is roughly square, but isn't POT

Most likely will add a flag to enforce POT texture. (Or enforce it by default and flag to disable it)

No CLI

See repo readme, it's very barebones right now, and there isn't even a help print.

Other TODOs

Note: I'm not doing support for colored glyphs (unicode emojis for example). Just no.

trethaller commented 4 years ago

Looks great!

.fnt file and integers

I understand now why it's not just a matter of replacing some ints by floats. But this seems out of the scope of the current issue (we have used SDF with the current font format and are reasonably happy with it). You're welcome to do these changes, but this wasn't part of the requirements

Sketchy maths / hacks

Not too concerning to me as a product user :-D as long as the results are consistent across fonts and sizes

Output texture is roughly square, but isn't POT

We discussed this with @ncannasse, we don't consider this a problem

Other TODOs

Nice to haves but not blocking

Alpha channel always 1, even on raster

Bitmap (raster) mode: this mode is important for us because we have many bitmap fonts we need to support on our current projects, even if, with this new tools we will probably favor SDF in the future. For me this is the only blocker.

One last thing: a sample ready-to-use config file and windows binary in the heaps/tools (with the output font in heaps/samples/res) would be nice (I'm a bit concerned with including a commercial font like Londinia in there, most likely some people will use it without reading the license)

trethaller commented 4 years ago

Also I admit we had not really considered the annoyance of reading and interpreting ttf files to generate the glyph coordinates, so Nicolas suggested we add an extra 50€ to the bounty

Yanrishatum commented 4 years ago

Quick update: Blocking issues were resolved. I'm getting rid of HxTrueType library and instead completely rely on Freetype library, as it provides way more accurate hinting. It also being used to rasterize glyphs in raster mode, resulting in more high-quality output compared to SDF->Raster approach. And since Freetype handles all TTF processing, pretty much all CMap tables should work perfectly fine. Cleaning up code and doing some finishing touches, so it should be live today or tomorrow.

Yanrishatum commented 4 years ago

v1 is essentially ready. It still have room for improvement and optimization, but right now it does what it supposed to do. Going from previous (long) update post:

Freetype library for font file handling

Positive of hxTrueType was that it's in Haxe. Negative... It's not fully implemented and performing things like type-hinting would would be pretty hard. Freetype on other hand provides very accurate type-hinting, saving a lot of headache in regard of getting glyphs where they are supposed to be. Hence hxTrueType had to go and tool no longer depends on it. As a consequence, redesigned major parts of native side to work with Freetype more effectively.

raster mode fixes

This mode now uses Freetype, which yields good results comparable in quality to other tools that rasterize font files.

PNG files

This version always produces RGBA image, but in future I most likely improve it to save in appropriate format. For sdf, psdf and msdf all pixels are non-transparent, SDF uses other channels to render. For raster it uses white pixels and glyphs control the transparency. They are calculated by Freetype, and while testing it looks pretty consistent. (Hail type-hinting)

Other stuff

One last thing: a sample ready-to-use config file and windows binary in the heaps/tools (with the output font in heaps/samples/res) would be nice (I'm a bit concerned with including a commercial font like Londinia in there, most likely some people will use it without reading the license)

I will remove Londinia from repo soon. And I think this part needs separate discussion, as to how it should be integrated. (Ping me up in Discord)

Sample render via Heaps (with 1.1.0, I fixed a few issues, I'll be publishing it tomorrow): img

trethaller commented 4 years ago

Looks awesome, really happy you ended-up with good results on bitmap fonts as well, switch to freetype was worth it! Still need to try it but from what I see I'm good to close the issue. Regarding integration into Heaps, I'm now thinking maybe a link to the fontgen repo is enough, or else we could add it as sub-repo under the tools folder actually under heaps/tools we'd most likely only want ready-to-use binaries

Yanrishatum commented 4 years ago

v1.1.0 up and running: https://github.com/Yanrishatum/fontgen/releases/tag/1.1.0 The winding fix is opt-in because solution msdfgen uses is pretty slow, adding a good chunk of time to processing. So I would enable it only if there are glyphs that are inverted on SDF methods. Also excluded non-printing character range from rasterization by default, because usually they aren't present in the ttf file, produce missing char warning and shouldn't be rendered in the first place. And also implemented support for 1-bit bitmap output from freetype, enabling pure B&W fonts support in raster mode. zip file should bundle self-contained HL/C exe + .hl file with corresponding hdll/dll (Including libhl).

trethaller commented 4 years ago

Great, have been using the windows release without a problem so far, I think we can close this and continue the discussion on the fontgen repo if needed. Thanks!