adventuregamestudio / ags

AGS editor and engine source code
Other
703 stars 160 forks source link

Fonts: allow font instancing with different properties #996

Closed ghost closed 2 weeks ago

ghost commented 4 years ago

Fonts have attached properties that may be changed without altering the source font data itself. For instance: point size / scaling, or line spacing. But currently AGS requires you to create a new copy of a font file if you want to use it with different setting. While fonts files aren't large, this is still unnecessary copying of source data.

My proposal is to change this and treat AGS fonts as "instances" that may refer to same source file, and only separated in memory (even sharing same font data in memory when possible).

ericoporto commented 4 years ago

Maybe fontDef or fontConfig class where you add this Settings and pass along with the font itself to be consumed by the functions that draws text and objects (eg. Labels) .

ghost commented 4 years ago

Maybe fontDef or fontConfig class where you add this Settings and pass along with the font itself to be consumed by the functions that draws text and objects (eg. Labels) .

Um, no, there's no need for this, in API "font" number would mean font instance, and for end-user it will work as before. What changes is only how engine and editor handle source data internally. Support shared data instead of multiplying it between instances.

ivan-mogilko commented 1 year ago

I'd like to propose slightly different approach to this. Originally this ticket was suggesting "instancing fonts with the same source file". I want to suggest other way around: fonts freely link to the source file.

In other words: we have Font entity which is not strictly linked to the actual glyph data. Instead it has a "source file" property, which may be filled or not. Or changed at runtime.

This will allow to link multiple Fonts to the same asset, but also to not link a Font to any asset. The latter may make sense if you expect this font to be drawn by a plugin, or even have glyph data be created by a script.

What will this change immediately in engine and editor?

The engine should provide a dummy font renderer that does nothing, in case no source is linked.

The editor should change a way a font is "imported". When creating a new font it will be "empty". The user will have an option to link a source file to it, or unlink from a file. But then there's a question on how to keep importing font files convenient. Either there has to be an explicit command for that, or "font files" should have their own node in a project tree.

ericoporto commented 1 year ago

In the project tree, the font we have could have two subnodes, one for files and the other for the instances.

One thing that I want to ask is, does it make sense an instance be linked to two or more font files in an order? In web development, it's possible to pass a sequence of font family, this is usually done either for a multilingual web page or when using special fonts with symbols along with a different regular font for the letters and numbers, the way it works with web things, which are usually UTF-8, is if a symbol is not available in a font, it goes to the next one to fetch the symbol. The problem is this significantly complicates render code. So, I don't think we need/should support this, but I just wanted to mention because this was a thing I saw being repeatedly mentioned on SDL_TTF related questions - which also doesn't support this.

ivan-mogilko commented 1 year ago

One thing that I want to ask is, does it make sense an instance be linked to two or more font files in an order? In web development, it's possible to pass a sequence of font family, this is usually done either for a multilingual web page or when using special fonts with symbols along with a different regular font for the letters and numbers, the way it works with web things, which are usually UTF-8, is if a symbol is not available in a font, it goes to the next one to fetch the symbol. The problem is this significantly complicates render code. So, I don't think we need/should support this, but I just wanted to mention because this was a thing I saw being repeatedly mentioned on SDL_TTF related questions - which also doesn't support this.

In web design the problem AFAIK is that your webpage will be using fonts installed on user's system, something that you cannot control. This is why it makes sense to make a list of substitutes.

In game dev you are in full control of which fonts are packaged, you're the one responsible for the assets. If you support a language that cannot use one font, then you may just switch the font when the language is selected (or switch the font's source, in the new proposed system). So this auto fallback mechanism does not make much sense.

What could make sense is to automate font or font source switching along with the language, so that you won't have to script this by hand, but idk if that's ever an issue. Also for translation file to have an option that switches particular Font's "source" to another font file, which is distributed along with tra, for example.

messengerbag commented 1 year ago

I would like to second ghost's original request to be able to set e.g. point size and line spacing dynamically, rather than having to define separate Fonts at design-time. It's not clear to me that @ivan-mogilko's alternative proposal allows this—at least not without implementing the key elements in a plugin.

It would also be nice to be able to import multiple variations of a TrueType font (such as different weights, italics, etc.) "as one"; though this is less essential, and probably implies a bigger change to the AGS font model.

ivan-mogilko commented 1 year ago

I would like to second ghost's original request to be able to set e.g. point size and line spacing dynamically, rather than having to define separate Fonts at design-time.

The "ghost" is me, it's my old account that was deleted, then restored. Unfortunately a big number of my older posts were not connected back to the restored account.

It's not clear to me that @ivan-mogilko's alternative proposal allows this—at least not without implementing the key elements in a plugin.

There's no difference in that regard, you may implement dynamic change of point size and linespacing even now, within the current font system. I'm not sure how plugins are related to this.

messengerbag commented 1 year ago

(I thought the original post was by former AGSer Ghost, though possibly the timing doesn't fit.)

you may implement dynamic change of point size and linespacing even now

How? AFAIK you need to choose the point size when you import the font at design time. Is this a 4.0 thing?

messengerbag commented 1 year ago

If instead of "Fonts" you had two types of entities, static FontFiles defined in the project and FontStyles that could be dynamically instantiated and modified with data about things like point size, line spacing, outline, etc., and linked to specific FontFiles, wouldn't that meet both of your proposals (and ericoporto's too)?

(To jump topic a little, one use-case for @ericoporto's suggestion of fallback fonts is if the renderer comes across a character/codepoint that isn't present in the primary font.)

ivan-mogilko commented 1 year ago

you may implement dynamic change of point size and linespacing even now

How? AFAIK you need to choose the point size when you import the font at design time. Is this a 4.0 thing?

I mean that you may implement a script function in the engine that changes a point size, and recreates the internal data. This may be done even now, without any additional modifications to the font system.

If instead of "Fonts" you had two types of entities, static FontFiles defined in the project and FontStyles that could be dynamically instantiated and modified with data about things like point size, line spacing, outline, etc., and linked to specific FontFiles, wouldn't that meet both of your proposals (and ericoporto's too)?

I guess, except my proposal was to keep both entities in the project too, as that might be easier for some, and also let see the result in the Editor.

messengerbag commented 1 year ago

I mean that you may implement a script function in the engine that changes a point size, and recreates the internal data. This may be done even now, without any additional modifications to the font system.

Right, that's more or less the original proposal, which is what I seconded. I was thrown because you introduced the second version as an alternative approach, but without mentioning any way it would actually allow you to do this (e.g. change the point size), instead focusing on the possibility of not having it linked to a specific font file. But I now suppose that you considered it to be implicit that this "Font" entity (equivalent to what I call FontStyle) would allow you to set those properties dynamically.

I guess, except my proposal was to keep both entities in the project too

Yes, I agree that this would be useful, and necessary in order to preview text in the editor. Are there any examples already in 4.0 of entities that can be created both in the editor and dynamically? Otherwise I suppose you could always create a bunch of them as placeholders (as long as the properties can be edited dynamically), but it gets tedious.

ericoporto commented 1 year ago

Just a note, setting properties dynamically may not be interesting if you want to cache the glyphs and benefit from it, as they need to be regenerated for each point size or other property change - there is a glyph cache in alfont and I believe SDL_ttf has it too.

ivan-mogilko commented 1 year ago

Are there any examples already in 4.0 of entities that can be created both in the editor and dynamically?

I think only sprites. They are separated on "static" sprites and dynamic sprites. In the past we discussed changing AGS to have everything created same way (whether loaded from game files or ordered in script), and let delete anything at runtime, but we are currently not close to that.

Just a note, setting properties dynamically may not be interesting if you want to cache the glyphs and benefit from it, as they need to be regenerated for each point size or other property change

That depends on how often do you change the properties, and for which purpose; and it's the same problem as with anything else that requires reloading or regenerating data. If this is done only on language change, that may be tolerated. A warning to users in documentation may be enough to help avoid misuse.

ericoporto commented 1 year ago

Just a note, setting properties dynamically may not be interesting if you want to cache the glyphs

Other approach to speed up is not only cache the glyph but use a font atlas - this means like pushing a font atlas to the GPU, and then rendering from it. Of course there are the ranges of characters that one wants to do this. And about font size, there are SDF fonts, which, although not perfectly, may enable font atlas with variable font size.

This comment is less about implementing this and more about not blocking being able to implement this in the future.

ivan-mogilko commented 1 month ago

After reviewing this today again, I think that it's better to separate a task of font source separation and dynamic font modification or creation in script. These two tasks may be accomplished on their own, perhaps accomplishing the first will make second easier to make, or easier to make convenient.

In regards to the latter, I wonder if it may be better to support dynamic font creation, but make them immutable, or mostly immutable (with an exception of properties that do not require glyph regeneration). There should be a separate ticket for this. But I think that we may have a Font struct in script, that makes it easier to work with both preloaded and dynamically created fonts.

ivan-mogilko commented 1 month ago

Following is the plan that I'm considering now:

  1. In the Project Tree, under Fonts node, have 2 folders: FontFiles and Fonts.
  2. Adding items under FontFiles will import font files into the project, by copying the file if it's elsewhere or keeping existing file if it's inside project folder. The file's name is retained, it is not longer required to be called AGSFNTX.*, name may be anything. FontFiles don't have any modifiable properties, only readonly information such as font format, font family, etc.
  3. Adding items under Fonts will add Font object, which contains a SourceFile property linking to one of the FontFiles in the project, and any other properties. For convenience we may have commands that add bare Font with no source linked, and command that lets select an existing FontFile in a dialog. In any case, Font item lets to change SourceFile anytime, the property may be made a dropdown with an updated FontFiles list in it.
  4. Upgrading old projects: simply generate FontFile items from Fonts, and link Fonts to them.
  5. Question of a FontFile preview: probably will leave same preview for the time being, but since FontFile does not have a size property, TTFs may display some default size.
  6. When compiling the game the Editor only gathers font files mentioned in the project tree.
  7. Compiled game data adds "source filename" to each font. When engine loads fonts it uses this filename instead of historical "AGSFNTX.*" name.

Other things:

  1. Deleting a FontFile removes SourceFile link from all connected Fonts, but these Fonts remain.
  2. A font with no SourceFile may still be used, but has no renderer connected, so will not draw any text (it practically becomes a "null font"). Unless something connects a renderer to it (something like SpriteFont plugin). In the future we may have other options.
ericoporto commented 1 month ago

Adding items under FontFiles will import font files into the project, by copying the file if it's elsewhere or keeping existing file if it's inside project folder. The file's name is retained, it is not longer required to be called AGSFNTX.*, name may be anything

If we had a dedicated directory for font files (say Fonts) it would be more predictable where to look for fonts, which would be nice since we still have to remap these to the root of the game package when packaging. If we accept any directory we could have conflict with files that are custom packaged through the config in general settings.

messengerbag commented 1 month ago
  1. In the Project Tree, under Fonts node, have 2 folders: FontFiles and Fonts.

Instead of separate nodes, would it makes sense to make Fonts sub-nodes of FontFiles? And maybe call them something like FontStyle instead?

ivan-mogilko commented 1 month ago

Instead of separate nodes, would it makes sense to make Fonts sub-nodes of FontFiles?

No, because they are not going to be sub-type of FontFiles. I've already mentioned in my previous comments, that I changed my mind about approach for this from "multiple instances of files" to "independent entities that may or not be connected with a file".

And maybe call them something like FontStyle instead?

I doubt if that is going to be a correct name for what I had in mind. I was thinking about an entity that is not directly related to a file, but rather is a setup over any kind of glyph data. In theory these items may represent self-contained sprite fonts, for example. Also, changing a term might be misleading, since they are called as "font" everywhere else, like in API.

EDIT: correction: the arguments are called "font", but the fonts are called "FontType" (that's the generated enum).

messengerbag commented 1 month ago

No, because they are not going to be sub-type of FontFiles.

No, but how you described them, as "attached properties that may be changed without altering the source font data itself. For instance: point size / scaling, or line spacing," they are additional formatting/styling specifications applied to a font. I think it would be pretty confusing for users to have completely different nodes for it in the project tree. If there is a need for "Fonts" not linked to a particular font file (I have my doubts), you could just allow the parent node to be empty.

Also, rereading the comment thread I see that I already proposed "FontStyle" as a better name over a year ago. And I still think that would be better: use Font for what you call "FontFile" and FontStyle for what you call "Font." That way things start to appear more consistent with how the term "font" is used in modern computers (unlike in old-fashioned physical typesetting), where it's the same font whether it's at 8 pts or 12 pts.

ivan-mogilko commented 1 month ago

The way I thought this will work is that Font item combines a data source with properties, where data source can be anything, including something that is only created in script, e.g. using a plugin. If I make it a sub-node of a file, then how would I make a Font that is not connected to a file? What kind of node would that be?

Another thing, right now users are probably naming Fonts with their purpose. "Speech font", "menu font" etc. How would they seek for a font if they are subnodes of a file? Will they have to remember which file that is?

What if users will want to organize fonts in folders as in categories? If fonts are sub-nodes of their files, then they will only be able to group fonts (or "font styles") belonging to the same file, but never fonts belonging to different files.

ivan-mogilko commented 1 month ago

Another opportunity is to do both kinds of lists.

My assumption is that data will not depend on how items are arranged in the tree. So it should be possible to have same items duplicated in two lists, where one is a plain list of "fonts" and another is "fonts" arranged as subitems of "font files". Then try it out and decide whether to keep both or leave one of them.

ericoporto commented 1 month ago

Uhm, I can see an approach where you have font files that are specific for different languages and have they use same properties when instancing

ivan-mogilko commented 1 month ago

Uhm, I can see an approach where you have font files that are specific for different languages and have they use same properties when instancing

It's difficult to tell whether using exact same properties with different font files is a common enough case to justify relying on it. Even fonts of about similar look may require slight adjustments to fit into the same place in game.

I suppose that's more a question of how to make font replacement on translation change convenient.

ivan-mogilko commented 1 month ago

Opened a draft for preview: #2545

ivan-mogilko commented 2 weeks ago

Resolved by #2545

Further changes to fonts management and editor UI may be discussed and applied if necessary.