Complex text layouts progress report #2:

Changes to the Godot Font:

Since font handling was moved to TextServer, some substantial changes were made to the Godot Font and related classes:

BitmapFont, DynamicFont and DynacmicFontData were removed and replaced with universal Font and FontData resource which are backed by TextServer. This provides cleaner font fallback/substitution and possibility for custom text servers to expose different types of fonts without interface changers, for example direct access to the system fonts.

New Font class provides new functions to draw and measure multiline text, and apply alignment.

Font and outline size, that were property of the Font instance, are moved to the arguments of the draw functions and theme constants. This allows changing font size for individual draw calls, controls, or spans of text in the RichTextLabel control without creating new Font instances.

Functions for loading font data from the memory buffer are exposed to GDScript and GDNative.

Font substitution system for scripts and languages:

New, smarter font substitution system is added:

  • Each font data have an associated list of supported scripts (writing systems) and languages. For a TrueType/OpenType fonts, a script list is populated automatically from the OS2 table, but it's not always precise.
  • Script and language support can be overridden by user.
  • For each run of text with a specific script and language, TextServer will try to use fonts in the following order:
    • Fonts with both script and language supported.
    • Fonts with script supported.
    • Rest of the fonts.

Here're a few cases of manual override:

  1.  Many Latin fonts have a limited set of Greek characters for use in scientific texts (and such fonts usually have Greek script support flag set in the OS2 table), but it's not always enough to display Greek text. Adding a separate font with full Greek support, and disabling Greek support in the main font will prevent TextServer from mixing characters form different fonts.
  2. TrueType/OpenType font tables do not have flags for rare/ancient scripts (e.g. Egyptian hieroglyphs), enabling script support manually will speed up font substitution.
  3. Some languages use the same script, but different font styles (e.g. Kufic, Naskh and Nastaʼlīq writing styles preferred for writing different Arabic languages; or the use of traditional Chinese characters in different regions and CJK variants - Traditional Chinese, Simplified Chinese, Japanese, and Korean). Setting language overrides allow to seamlessly use the same font stack for the text in different languages and get desired style.
    Labels with "language" property set using the same "Font" instance.
    "Label"s with "language" property set, using the same "Font" instance.
    CJK variants, "Label" using same font instance.
    CJK variants, "Label"s using same font instance.

Variable fonts:

Additional, the new Font and TextServer's BiDi and shaping features can work with variable fonts (see https://github.com/godotengine/godot/pull/43030 for variable font support PR):

Functions to control the font size were added to the Theme, Control, and Window classes:

  • Control and Window functions add_theme_font_size_override, get_theme_font_size, has_theme_font_size, has_theme_font_size_override.
  • Theme functions: clear_font_size, get_font_size, get_font_size_list, has_font_size, set_font_size to control the font size in a similar manner to existing function for Font. The special value -1 can be used as unset/default (have the same effect as null for the font).

UI mirroring:

To ensure that the content is easy to understand, user interface for the right-to-left written languages should flow from right-to-left. UI "mirroring" provides a convenient way to achieve this without designing separate interfaces for the RTL and LTR languages.

In most cases, UI mirroring should happen automatically, and do not require any actions from the user or knowledge of the RTL writing systems.

Each Control has a "layout_direction" property to control mirroring. It can be set to "inherited" (use the same direction as the parent control, same as "auto" for the root control), "auto" (direction is determined based on current locale) or forced to RTL or LTR.

On the above screenshots, the green and blue "compass" controls are forced to LTR and RTL layout respectively, the red container is set to "auto" and the rest of the UI use "inherited" (default setting) direction.

For RTL languages, Godot will automatically do the following changes to the UI:

  • Mirrors left/right anchors and margins.
  • Swaps left and right text alignment.
  • Mirrors horizontal order of the child controls in the containers, and items in Tree/ItemList controls.
  • Uses mirrored order of the internal control elements (e.g. OptionButton dropdown button, checkbox alignment, List column order, Tree item icons and connecting line alignment, e.t.c.), in some cases mirrored controls use separate theme styles.
  • Coordinate system is not mirrored, and non-UI nodes (sprites, e.t.c) are not affected.

BiDi override for the structured text:

The Unicode BiDi algorithm is designed to work with natural text and it's incapable of handling text with a higher level order, like file names, URIs, email addresses, regular expressions or source code.

structured_text_bidi_override property and _structured_text_parser callback are added to the all text controls to handle this.

File path display

For example, the path for the directory structure in the above screenshot will be displayed incorrectly (top "LineEdit" control). The "File" type structured text override splits text into segments, then the BiDi algorithm is applied to each of them individually to correctly display directory names in any language and preserve correct order of the folders (bottom "LineEdit" control).

Custom callbacks provide a way to override BiDi for the other types of structured text. For example, the following code splits a text using ":" as separator, apply BiDi to each part, and display them in reversed order. The BiDi override can be used with any control, including input fields (LineEdit, TextEdit):

func _structured_text_parser(args, text):
    var ranges = []
    var offset = 0
    for t in text.split(":"):
         ranges.push_front(Vector2i(offset, offset + t.length())) # Add text
         ranges.push_front(Vector2i(offset + t.length(), offset + t.length() + 1)) # Add ":"
         offset = offset + t.length() + 1
    return ranges

Other changes to the Godot controls:

To control BiDi and font related features, all controls have the following properties added:

  • language property (only for controls with text):
    Overrides the locale for the node: controls language specific line breaking rules, OpenType localized form, shaping, and font substitution.
English and Romanian rendering of the same string.
OpenType features.

Reference work:

Additional resources:

 

This article was updated on November 10, 2020