openframeworks / openFrameworks

openFrameworks is a community-developed cross platform toolkit for creative coding in C++.
http://openframeworks.cc
Other
9.92k stars 2.55k forks source link

add functionality to ofTrueTypeFont #8133

Open chilina opened 2 days ago

chilina commented 2 days ago

Related forum thread: https://forum.openframeworks.cc/t/oftruetypefont-and-font-collection-ttc-file/43895

I'd like to propose some additional functionality for ofTrueTypeFont. I'm happy to submit a pull request based on any feedback on the following.

Background: A common True Type Font file format is a True Type Collection, with a file extension of .ttc. There are often several font styles stored in a collection, which can be accessed when loading by ofTrueTypeFontSettings::index. ofTrueTypeFont has a private member std::shared_ptr<struct FT_FaceRec_> face. This struct has a member 'num_faces' which stores the number of styles in the file. This value doesn't currently have any getters, and can't be accessed in a child class because it's private.

Having access to the value in 'num_faces' would be helpful. There are maybe 4 different ways to do this; 3 involve modifying OF and 1 is a quick workaround. Any thoughts on the following would be helpful for a pull request.

Option 1: add a public getter ofTrueTypeFont::getIndexSize() to return the value of face->num_faces. The return value would be 0 for nullptr.

Option 2: make std::shared_ptr<struct FT_FaceRec_> face a protected member. This requires the Freetype2 lib includes to use it in a child class, but would allow full access to all of the members of the FTFaceRec struct. Maybe too much access?

Option 3: add a member ofTrueTypeSettings::indexSize to store the value, and add a getter to ofTrueTypeSettings::getSettings() to get it. I haven't tested this one yet. Maybe too much access?

Option 4 (the workaround): no changes to OF. The number of styles in a .ttc file can be found with a do-while loop that loads each one of them with an incremented value for ofTrueTypeSettings::index until one of them fails to load.

artificiel commented 2 days ago

very interesting! nailing the API would be the best way to determine the requirements and decide between your 4 options.

what is the correlation between the current OF "load operation" vs getting the variants info? on the forum you mention OF loads index 0 by default, but do we need to load a font to get access to the variants?

is this feasible?

static std::vector<std::string> ofTrueTypeFont::getVariantsName(of::filesystem::path path_to_ttc);

to inspect and perhaps dynamically choose variants by name? (I presume the variants names are simply text labels, that aim to be coherent by convention, as opposed to a strict enum defined somewhere?)

and for the Settings, we don't want the user to have load twice; more like an interface like this:

ofTrueTypeFontSettings settings("Helvetica", 24);
settings.variant = "Bold";
ttf_.load(settings);

as for sure we want to go by name. so within load(), a loop iterates and hopes to find "Bold" somewhere and load that or default to 0 with a log message?

(and maybe setup() itself can also be adapted for another string param)

would the above cover your use cases? maybe you have other requirements, or maybe there's additional juicy stuff to extract from the FTFaceRec? it could also be exposed in something like ofTrueTypeFontVariantInfo:

static std::vector<ofTrueTypeFontVariantInfo> ofTrueTypeFont::getVariantsInfo(of::filesystem::path path_to_ttc);

in all cases I would not worry about details and access within ofTrueTypeFont itself, as long as the user-facing API is slick (the implementation can change; the API is forever)

chilina commented 1 day ago

or maybe there's additional juicy stuff to extract from the FTFaceRec?

Yes there are a few members in this struct that would be useful. Their names and descriptions are here: https://freetype.org/freetype2/docs/reference/ft2-face_creation.html. One of them is style_name, which is a string for the style (like "Italic", "Bold", etc), which would facilitate searching with a string(s).

and for the Settings, we don't want the user to have load twice

In OF currently, the font does fully load before its FTFaceRec is populated, which involves doing everything needed to use it with .drawString(). There is quite a bit involved. So I do like the idea of this:

it could also be exposed in something like ofTrueTypeFontVariantInfo

Maybe ofTrueTypeFontMetadata? As a struct that mirrors FTFaceRec ? Which populates with .getMetadata() or similar and doesn't require fully loading the font for use.

Maybe a slow-baked rewrite of some aspects of ofTrueTypeFont would be helpful, to add these ideas like "search for style by string" or "what index is currently loaded" or "how many indexes does this font file have" or "find all the fonts in a folder that belong to a particular font family". But that said, moving the struct to protected from private, or mirroring its values somewhere in the class, would allow people to design their own interfaces as needed.

Then, there is also this forum post regarding methods like .drawStringToFit() that could be added to the class at some point: https://forum.openframeworks.cc/t/calculate-letter-spacing-for-oftruetypefont-setletterspacing/42897/4 . I just used this again recently and it works great! And with access to the metadata, we might (maybe) also do things like '.findFontSizeToFit()', which would find the font size with a suitable value for its "char advance" value to fit a width for a given string.

in all cases I would not worry about details and access within ofTrueTypeFont itself

I find this quite helpful and will keep it in mind. I think my plan is to play around with some of these ideas for a while and see if I can take it someplace, and also get better acquainted with the Freetype2 lib. And maybe people will post more suggestions and ideas here in the meantime.

I do sometimes wonder why some things like struct FTFaceRec are private in OF, instead of protected. There may be some great reasons for not exposing it. Its so much easier to derive child classes with only protected members in the parent.

dimitre commented 1 day ago

I personally think they can be changed from private to protected. maybe this is like this just because it was unneeded until now.

artificiel commented 1 day ago

yes for sure, if going protected allows you to experiment in userland, let's do that! my response above is discussing changes/additions to ofTrueTypeFont's API, which can come once you've figured out a good set of tools to integrate.

(drawStringToFit()/drawStringToWidth() plus a couple other methods are in a branch, but a lot of micro-changes broke auto-merge, and also some ambiguities linger on some aspects of the implementation (notable the Settings/setup are messy) so it's sort of in limbo at the moment.)