Complex text layouts progress report #2
By: Pāvels Nadtočajevs 10 November 2020
This article is from November 2020, some of its contents might be outdated and no longer accurate.
You can find up-to-date information about the engine in the official documentation.
This is the second part of my work on Complex Text Layouts for Godot 4.0, focusing on Fonts and UI mirroring.
See also the previous progress report for the
TextServer API implementation details.
Changes to the Godot Fonts
Since font handling was moved to
TextServer, some substantial changes were made to the Godot
Font and related classes:
DynamicFontData were removed and replaced with universal
FontData resources 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 changes, for example direct access to the system fonts.
Font class provides new functions to draw and measure multiline text, and apply alignment.
Font and outline size, that were properties 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
Functions for loading font data from the memory buffer are exposed to GDScript and GDNative.
Font substitution system for scripts and languages
A new, smarter font substitution system is added:
FontDatahas 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 the user.
- For each run of text with a specific script and language,
TextServerwill 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:
- 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
TextServerfrom mixing characters form different fonts.
- 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.
- 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 allows to seamlessly use the same font stack for the text in different languages and get the desired style.
Incorrect font used for “μ”:
Correct font used for “μ”:
language property set, using the same
Labels using same font instance:
Additionally, the new
TextServer’s BiDi and shaping features can work with variable fonts (see godot#43030 for the variable font support PR):
Functions to control the font size were added to the
set_font_sizeto control the font size in a similar manner to existing functions for
Font. The special value
-1can be used as unset/default (have the same effect as
nullfor the font).
To ensure that the content is easy to understand, user interfaces 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.
Right-to-left UI: Left-to-right UI:
In most cases, UI mirroring should happen automatically, and do not require any actions from the user or knowledge of the RTL writing systems.
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 the 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 uses
inherited (default setting) direction.
For RTL languages, Godot will automatically do the following changes to the UI:
- Mirror left/right anchors and margins.
- Swap left and right text alignment.
- Mirror horizontal order of the child controls in the containers, and items in
- Use mirrored order of the internal control elements (e.g.
OptionButtondropdown button, checkbox alignment,
Treeitem icons and connecting line alignment, etc.), in some cases mirrored controls use separate theme styles.
- The coordinate system is not mirrored, and non-UI nodes (sprites, etc.) are not affected.
BiDi override for 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 the
_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
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, applies BiDi to each part, and displays them in reversed order. The BiDi override can be used with any control, including input fields (
func _structured_text_parser(args, text): var ranges =  var offset = 0 for t in text.split(":"): var text_offset = offset + t.length() ranges.push_front(Vector2i(offset, text_offset) # Add text ranges.push_front(Vector2i(text_offset, text_offset + 1)) # Add ":" offset = text_offset + 1 return ranges
Other changes to the Godot controls
To control BiDi and font related features, all controls have the following properties added:
languageproperty (only for controls with text): Overrides the locale for that node: controls language specific line breaking rules, OpenType localized form, shaping, and font substitution.
English and Romanian rendering of the same string:
opentype_featuresproperty (only for controls with text): Controls OpenType font features for the node e.g. ligature types to use, number styles, small caps, etc. (Full list of standard tags, fonts can have custom features as well).
- UI improvements for implementing BiDi aware apps proposal: godot-proposals#1183.
- UI mirroring, Font, Theme, and control changes: godot#41100 (commits 5 to 8, the PR is regularly rebased to keep up with upstream changes, exact commit hashes may vary).
- Variable font support PR: godot#43030.
- Mirroring, BiDi layout and override demo projects: godot-demo-projects#538.
The next part will focus on the changes to the specific controls and