Introduction
This is the second part of my work on Complex Text Layouts for Godot 4.0, focusing on Fonts and UI mirroring.
See godot-proposals#1180, godot-proposals#1181, godot-proposals#1182, and godot-proposals#1183 on GitHub for detailed information on CTL proposals and feedback.
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:
BitmapFont
, DynamicFont
and DynamicFontData
were removed and replaced with universal Font
and 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.
The new 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 Font
instances.
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:
- Each
FontData
has 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,
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:
- 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. - 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 “μ”:
Label
s with language
property set, using the same Font
instance:
CJK variants, Label
s using same font instance:
Variable fonts
Additionally, the new Font
and 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 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 functions 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 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.
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 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
Tree
/ItemList
controls. - Use mirrored order of the internal control elements (e.g.
OptionButton
dropdown button, checkbox alignment, List
column order, Tree
item 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.
The 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 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, applies BiDi to each part, and displays 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(":"):
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:
language
property (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_features
property (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).
OpenType features:
Reference work
- 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.
Additional resources
Future
The next part will focus on the changes to the specific controls and RichTextLabel
BBCode.