mirror of
https://github.com/godotengine/godot-website.git
synced 2026-01-04 06:09:55 +03:00
29 lines
35 KiB
HTML
29 lines
35 KiB
HTML
<!doctype html><html lang=en><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=author content="Godot Engine"><meta name=description content="Report on the complex text layouts support implementation progress, including changes to RichTextEdit class, compatibility and custom control implementation details."><script defer data-domain=godotengine.org src=https://plausible.godot.foundation/js/script.file-downloads.outbound-links.js></script><meta property="og:site_name" content="Godot Engine"><meta property="og:url" content="https://godotengine.org/article/complex-text-layouts-progress-report-3/"><meta property="og:type" content="website"><meta property="og:description" content="Report on the complex text layouts support implementation progress, including changes to RichTextEdit class, compatibility and custom control implementation details."><meta property="og:image" content="https://godotengine.org/storage/app/uploads/public/5fe/1a5/089/5fe1a50899fe1978638754.png"><meta name=twitter:card content="summary_large_image"><meta property="twitter:domain" content="godotengine.org"><meta property="twitter:url" content="https://godotengine.org/article/complex-text-layouts-progress-report-3/"><meta property="og:title" content="Complex text layouts progress report #3 – Godot Engine"><title>Complex text layouts progress report #3 – Godot Engine</title>
|
||
<link rel=alternate type=application/rss+xml title="Godot News" href=/rss.xml><link rel=alternate type=application/json title="Godot News" href=/rss.json><link rel=alternate type=application/atom+xml title="Godot News" href=/atom.xml><link rel=icon href=/assets/favicon.png sizes=any><link rel=icon href=/assets/favicon.svg type=image/svg+xml><link rel=stylesheet href=/assets/css/main.css?121><link rel=stylesheet href=/assets/css/header.css?1><link rel=stylesheet href=/assets/css/tobii.min.css><link rel=preload as=font href=/assets/fonts/Montserrat-Italic-VariableFont_wght.woff2 crossorigin><link rel=preload as=font href=/assets/fonts/Montserrat-VariableFont_wght.woff2 crossorigin><link rel=me href=https://mastodon.gamedev.place/@godotengine><input type=checkbox id=nav_toggle_cb><header class="flex column"><div class="container flex align-center"><div id=nav_head><a href=/ id=logo-link><img class=nav-logo src=/assets/logo.svg width=136 height=48 alt="Godot Engine">
|
||
<img class="nav-logo dark-logo" src=/assets/logo_dark.svg width=136 height=48 alt="Godot Engine"></a><div class=mobile-links><span class="fund mobile"><a href=https://fund.godotengine.org><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="width:13px;fill:#fff;margin-right:4px"><path d="M47.6 300.4 228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6.0 115.2.0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg> Donate</a></span>
|
||
<label for=nav_toggle_cb id=nav_toggle_btn><img src=/assets/icons/hamburger.svg width=24 height=24 alt="Main menu"></label></div></div><nav id=nav><ul class=left><li><a href=/features/>Features</a><li><a href=/showcase/>Showcase</a><li><a href=/blog/>Blog</a><li><a href=/community/>Community</a><li><a href=https://godotengine.org/asset-library/asset>Assets</a></ul><ul class=right><li><a href=/download/windows/ class=set-os-download-url>Download</a><li><a href=https://docs.godotengine.org>Docs</a><li><a href=https://docs.godotengine.org/en/stable/contributing/how_to_contribute.html>Contribute</a><li class="fund desktop"><a href=https://fund.godotengine.org><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="width:13px;fill:#fff;margin-right:4px;top:1px;position:relative"><path d="M47.6 300.4 228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6.0 115.2.0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg> Donate</a></ul></nav></div></header><script>document.addEventListener("click",function(e){const t=document.querySelector(".language-selector");if(!t)return;t.contains(e.target)||t.classList.remove("open")});function setLanguagePreference(e,t){e.preventDefault();const s=t.getAttribute("data-lang-path"),o=t.getAttribute("data-lang"),n=new Date;n.setDate(n.getDate()+365),document.cookie=`preferred_language=${o}; expires=${n.toUTCString()}; path=/; SameSite=Lax`,window.location.href=s}</script><main><style>body{background-color:var(--background-color)}h1{margin-bottom:8px;margin-top:32px}:not(pre)>code{background:var(--code-background-color);padding:1px 4px;font-size:.95em;border-radius:3px}pre{background:var(--codeblock-background-color);color:var(--codeblock-color)}pre code{display:block;overflow-x:auto;padding:.5em}.date-big{line-height:2;margin-left:32px}article{background-color:var(--base-color);box-shadow:0 3px 2px rgba(0,0,0,.15)}figure{margin:0}figure img{margin:0}article img,article video{max-width:100%;height:auto;display:block;margin:auto;margin-top:16px;margin-bottom:16px}article h1{margin-top:64px}article h2,article h3,article h4{margin-top:42px}.article-info{display:flex;flex-direction:column;gap:8px}.article-metadata{display:flex;gap:24px;align-items:center;font-family:var(--header-font-family);margin-bottom:12px}@media(max-width:900px){.article-metadata{flex-direction:column;align-items:flex-start;gap:16px}}.article-author{color:var(--base-color-text-subtitle-date);font-weight:700;font-size:18px;flex-grow:1;display:flex;gap:12px;align-items:center}.article-author .avatar{border-radius:100%;margin:0;background:0 0}.article-author .by{color:var(--base-color-text-subtitle)}.article-metadata .date{color:var(--base-color-text-subtitle-date)}.article-metadata .date.post-recent-highlight{color:var(--post-recent-highlight-color);opacity:.8}.article-metadata .date.post-recent-highlight::after{font-size:80%;content:"NEW";border:2px solid var(--post-recent-highlight-color);padding:2px 3px;margin-left:8px}.tag.active{filter:saturate(.75)}@media screen and (min-width:900px){article .content{width:70%;margin:auto}}@media(max-width:900px){body{background-color:var(--base-color)}article{background-color:initial;box-shadow:none}article img:first-child,article video:first-child{max-width:100%}}.blog-navigation{display:grid;grid-template-columns:1fr 1fr;padding-top:30px;padding-bottom:60px}.blog-navigation .next{text-align:right}@media(max-width:900px){.blog-navigation{grid-template-columns:1fr;gap:20px;border-top:1px solid var(--code-background-color)}.blog-navigation .next{text-align:left}}.blog-navigation span{opacity:.6;font-weight:700;margin-bottom:5px;display:block}.blog-navigation a{display:inline-block;text-decoration:none;color:inherit;opacity:.6;transition:opacity .2s}.blog-navigation a:hover{opacity:1}</style><link rel=stylesheet href=/assets/css/highlight.obsidian.min.css><div class=container><article class=padded><div class="content article-container"><figure class=article-cover><img src=/storage/app/uploads/public/5fe/1a5/089/5fe1a50899fe1978638754.png title alt=" " class=rounded-lg style=width:100%;height:auto;background-color:initial></figure><div class=article-info><h1>Complex text layouts progress report #3</h1><div class=article-metadata><div class=article-author><span>By: </span><img class=avatar width=25 height=25 src=/assets/images/authors/default_avatar.svg alt="Pāvels Nadtočajevs" loading=lazy>
|
||
<span class=by>Pāvels Nadtočajevs</span></div><span class=date data-post-date="2020-12-29 09:00:00 +0000">29 December 2020</span></div><div class=tags><a href=/blog/progress-report><div class="tag active">Progress Report</div></a></div></div><div class="card card-warning"><p>This article is from <strong>December 2020</strong>, some of its contents might be outdated and no longer accurate.<br>You can find up-to-date information about the engine in the <a href=https://docs.godotengine.org/en/stable/>official documentation</a>.</div><div class=article-body><h1 id=introduction>Introduction</h1><p>This is the third part of my work on Complex Text Layouts for Godot 4.0.<p>See <a href=https://github.com/godotengine/godot-proposals/issues/1180>godot-proposals#1180</a>, <a href=https://github.com/godotengine/godot-proposals/issues/1181>godot-proposals#1181</a>, <a href=https://github.com/godotengine/godot-proposals/issues/1182>godot-proposals#1182</a>, and <a href=https://github.com/godotengine/godot-proposals/issues/1183>godot-proposals#1183</a> on GitHub for detailed information on <abbr title="Complex Text Layouts">CTL</abbr> proposals and feedback.<p>See also the <a href=https://godotengine.org/article/complex-text-layouts-progress-report-2>previous progress report</a> for UI mirroring details and the <a href=https://godotengine.org/article/complex-text-layouts-progress-report-1>first part</a> for the <code class="language-plaintext highlighter-rouge">TextServer</code> API implementation details.<h1 id=changes-to-the-richtextlabel-control>Changes to the RichTextLabel control</h1><h4 id=paragraph>Paragraph</h4><p>Existing alignment tags (<code class="language-plaintext highlighter-rouge">[left]</code>, <code class="language-plaintext highlighter-rouge">[center]</code>, <code class="language-plaintext highlighter-rouge">[right]</code>, <code class="language-plaintext highlighter-rouge">[fill]</code>), as well as new base direction, structured text override and language paragraph options are combined to the single <code class="language-plaintext highlighter-rouge">[p]</code> tag. Standalone alignment tags are still available for compatibility.<div class="language-plaintext highlighter-rouge"><div class=highlight><pre class=highlight><code>[p align=x dir=rtl]
|
||
</code></pre></div></div><table><thead><tr><th>Option<th>Values<th>Description<tbody><tr><td>align<td>left, center, right, fill<td>Text alignment.<tr><td>dir or direction<td>ltr, rtl or auto<td>Base text writing direction.<tr><td>lang or language<td>ISO language code<td>Text language.<tr><td>bidi_override<td>default, url, file, email, list, none, custom<td>Structured text BiDi override for the paragraph.</table><h4 id=tables>Tables</h4><p>Support for setting cell background and border colors, including alternating odd/even row background has been added to the table. Table horizontal layout is mirrored for RTL paragraphs.<div class="language-plaintext highlighter-rouge"><div class=highlight><pre class=highlight><code>[cell bg=odd_color,even_color border=color]AAA[/cell]
|
||
</code></pre></div></div><p>Additionally, tables can be directly included into the text flow. Table opening and closing tags won’t start a new line anymore.<p><img src=/storage/app/uploads/public/5fe/1a5/51c/5fe1a551c4971868617968.png alt="Example of RichTextLabel table"><h4 id=lists>Lists</h4><p>Support for numbered and unnumbered lists (list tags existed before but were non-functional) has been added.<div class="language-plaintext highlighter-rouge"><div class=highlight><pre class=highlight><code>[ol type=1]Item 1
|
||
Item 2
|
||
Item 3[/ol]
|
||
</code></pre></div></div><p>The following list types are supported:<ul><li>Unnumbered lists ( <code class="language-plaintext highlighter-rouge">[ul]</code> ).<li>Numbered lists ( <code class="language-plaintext highlighter-rouge">[ol]</code> ) using numbers: <code class="language-plaintext highlighter-rouge">1</code> type (supports localized numbering systems, e.g. east Arabic numerals, based on paragraph language).<li>Numbered lists ( <code class="language-plaintext highlighter-rouge">[ol]</code> ) using Roman numerals: <code class="language-plaintext highlighter-rouge">i</code> and <code class="language-plaintext highlighter-rouge">I</code> type.<li>Numbered lists ( <code class="language-plaintext highlighter-rouge">[ol]</code> ) using Latin letters: <code class="language-plaintext highlighter-rouge">a</code> and <code class="language-plaintext highlighter-rouge">A</code> type.</ul><p><img src=/storage/app/uploads/public/5fe/1a5/614/5fe1a56147259830483005.png alt="Unordered and ordered lists"><h4 id=images>Images</h4><p>Added support for image inline vertical alignment:<div class="language-plaintext highlighter-rouge"><div class=highlight><pre class=highlight><code>[image align=top]
|
||
</code></pre></div></div><p><img src=/storage/app/uploads/public/5fe/1a5/6e9/5fe1a56e97fcd500275634.png alt="Images with inline vertical alignment"><h4 id=font-and-outline>Font and outline</h4><p>Added tags to control font size, OpenType features and font outline parameters:<div class="language-plaintext highlighter-rouge"><div class=highlight><pre class=highlight><code>[font_size=36]
|
||
[opentype_features=scap]
|
||
[outline_color=red][outline_size=2]
|
||
</code></pre></div></div><p>or combined in the single tag:<div class="language-plaintext highlighter-rouge"><div class=highlight><pre class=highlight><code>[font name=res://font.tres size=36 features=scap]
|
||
</code></pre></div></div><p><img src=/storage/app/uploads/public/5fe/1a5/7e1/5fe1a57e1bc07058215711.png alt="Examples of varying font parameters"><h4 id=control-characters>Control characters</h4><p>Unicode control characters can be inserted to the text by inputting them directly (e.g. copy-pasting or using Godot input control context menu), or by using special tags (e.g. <code class="language-plaintext highlighter-rouge">[rlm]</code>, <code class="language-plaintext highlighter-rouge">[lrm]</code>, <code class="language-plaintext highlighter-rouge">[zwnj]</code>).<h4 id=dropcaps>Dropcaps</h4><p>Additionally, dropped capitals support is added to the <code class="language-plaintext highlighter-rouge">RichTextLabel</code>. Dropcaps support the same formatting options as the main text, and margins for precise placement control.<p><img src=/storage/app/uploads/public/5fe/1a5/8b1/5fe1a58b1a6d5796589718.png alt="Drop cap margins">
|
||
<img src=/storage/app/uploads/public/5fe/1a5/905/5fe1a59051f29443168025.png alt="Example of Latin and Hebrew text using dropcaps"><h1 id=designing-custom-controls>Designing custom controls</h1><p>In addition to the text server interface and draw function in the <code class="language-plaintext highlighter-rouge">Font</code> class, <code class="language-plaintext highlighter-rouge">TextLine</code> and <code class="language-plaintext highlighter-rouge">TextParagraph</code> helper classes, which serve as the base for most Godot controls, are available for more convenient single and multiline text rendering and designing custom controls.<p><code class="language-plaintext highlighter-rouge">TextLine</code> and <code class="language-plaintext highlighter-rouge">TextParagraph</code> classes support the following features:<ul><li>Multiple fonts and font options, and multiple languages in the single line or paragraph. Spans of text with designated font and properties can be sequentially pushed to the text buffer.<div class="language-python highlighter-rouge"><div class=highlight><pre class=highlight><code><span class=n>add_string</span><span class=p>(</span><span class=n>text</span><span class=p>,</span> <span class=n>font</span><span class=p>,</span> <span class=n>font_size</span><span class=p>,</span> <span class=n>opentype_features</span><span class=p>,</span> <span class=n>language</span><span class=p>)</span>
|
||
</code></pre></div></div><li>Text buffers can embed user defined custom objects (e.g. images and tables, like <code class="language-plaintext highlighter-rouge">RichTextLabel</code> does) into the text flow. In the text, such objects are represented as <code class="language-plaintext highlighter-rouge">length</code> object replacement characters, and follow all <abbr title=Bi-Directional>BiDi</abbr> reordering rules. Text buffers handle object size, alignment and placement in the text, drawing and interaction should be implemented separately.<div class="language-python highlighter-rouge"><div class=highlight><pre class=highlight><code><span class=n>add_object</span><span class=p>(</span><span class=n>key</span><span class=p>,</span> <span class=n>size</span><span class=p>,</span> <span class=n>inline_align</span><span class=p>,</span> <span class=n>length</span><span class=p>)</span>
|
||
</code></pre></div></div><p>The final object position in the text layout can be retrieved by using <code class="language-plaintext highlighter-rouge">get_object_rect(key)</code> function.</ul><p>Both <code class="language-plaintext highlighter-rouge">TextLine</code> and <code class="language-plaintext highlighter-rouge">TextParagraph</code> provide direct access to the text server buffer <code class="language-plaintext highlighter-rouge">RID</code>s to mix their usage with direct text server API calls (e.g. accessing individual glyphs).<p>Text server buffers, <code class="language-plaintext highlighter-rouge">TextLine</code> and <code class="language-plaintext highlighter-rouge">TextParagraph</code> use a lazy calculation model. No BiDi reordering, shaping, line breaking or any other kind of text processing is done until it’s necessary to do it for the <code class="language-plaintext highlighter-rouge">get_*</code> function call or buffer rendering. While layout text is immutable and can’t be changed without redoing BiDi reordering and reshaping, layout size and alignment as well as embedded object sizes can be changed dynamically. Doing so is much faster than new buffer creation.<h1 id=limited-vertical-text-support>Limited vertical text support</h1><p>While Godot controls are designed to work with horizontal text only, text server and <code class="language-plaintext highlighter-rouge">TextLine</code>/<code class="language-plaintext highlighter-rouge">TextParagraph</code> classes have limited vertical text support as well. Vertical layouts can be enabled by setting the <code class="language-plaintext highlighter-rouge">orientation</code> property of the text buffer or helper class.<p><img src=/storage/app/uploads/public/5fe/1a5/a77/5fe1a5a7744fe943517369.png alt="Example of vertical text orientation"><h1 id=input>Input</h1><p>Godot’s BiDi caret movement follows the logical order of characters (right arrow key always acts as movement forward). If the caret is located on the edge of RTL and LTR text segments, two carets are displayed. Each one indicating a position where newly inputted RTL and LTR characters will be displayed. The primary caret, which controls scrolling, is determined based on the direction of the last inputted character (or can be switched manually, by using <code class="language-plaintext highlighter-rouge">Ctrl/Cmd + ~</code>).<p><img src=/storage/app/uploads/public/5fe/1a5/ebc/5fe1a5ebc206a227345391.png alt="Dual caret at the intersection between LTR and RTL text segments"><p>By default, all input keys except <kbd>Backspace</kbd>, which always deletes a single character, move the caret over the whole grapheme (i.e. base character and combining diacritics):<p><img src=/storage/app/uploads/public/5fe/1a5/ee7/5fe1a5ee7ee8c255544417.png alt="Demonstration of caret movement"><p>This behavior can be changed by setting <code class="language-plaintext highlighter-rouge">mid_grapheme_caret</code> control property:<p><img src=/storage/app/uploads/public/5fe/1a5/f0c/5fe1a5f0c2620352609364.png alt="Demonstration of mid-grapheme caret positioning"><p><em>Note: depending on the OS and language and keyboard layout, some characters can be entered both as combining characters and single characters. In case of single character input, both backspace and movement will affect the whole character.</em><p>Input controls have various BiDi options available for the user via the context menu:<ul><li>Base direction.<li>Inserting control characters.<li>Enabling and disabling display of control characters.</ul><p><img src=/storage/app/uploads/public/5fe/1a5/f2d/5fe1a5f2d7c69213554132.png alt="BiDi options in context menu"><p>For the custom controls, the text server provides functions to determine caret positions, selection bounding rectangles and hit testing:<div class="language-cpp highlighter-rouge"><div class=highlight><pre class=highlight><code><span class=n>shaped_text_get_carets</span><span class=p>(</span><span class=n>RID</span> <span class=n>p_text_buffer</span><span class=p>,</span> <span class=kt>int</span> <span class=n>p_position</span><span class=p>)</span>
|
||
<span class=n>shaped_text_get_selection</span><span class=p>(</span><span class=n>RID</span> <span class=n>p_text_buffer</span><span class=p>,</span> <span class=kt>int</span> <span class=n>p_start</span><span class=p>,</span> <span class=kt>int</span> <span class=n>p_end</span><span class=p>)</span>
|
||
<span class=n>shaped_text_hit_test_position</span><span class=p>(</span><span class=n>RID</span> <span class=n>p_text_buffer</span><span class=p>,</span> <span class=kt>float</span> <span class=n>p_coord</span><span class=p>)</span>
|
||
</code></pre></div></div><p>All input controls fully support structured text BiDi overrides, which do not interfere with caret movement, do not affect contents of the underlying raw string and unlike Unicode control characters can’t be accidentally modified by the user.<h1 id=compatibility>Compatibility</h1><p>While there are breaking changes in the <code class="language-plaintext highlighter-rouge">Font</code> and <code class="language-plaintext highlighter-rouge">Theme</code>, existing 3.2 scenes can be loaded directly, some of the conversions will be done automatically:<p><code class="language-plaintext highlighter-rouge">DynamicFont</code> and <code class="language-plaintext highlighter-rouge">BitmapFont</code> resources are converted to the new unified <code class="language-plaintext highlighter-rouge">Font</code> resources, but font size will be lost, and should be re-added in the theme properties of the controls.<p>Font loading and text drawing GDScript code should be changed manually:<ul><li>Font loading: Change <code class="language-plaintext highlighter-rouge">DynamicFont</code> to <code class="language-plaintext highlighter-rouge">Font</code>, <code class="language-plaintext highlighter-rouge">DynamicFontData</code> and <code class="language-plaintext highlighter-rouge">BitmapFont</code> to <code class="language-plaintext highlighter-rouge">FontData</code>, use single list of data sources.<li>Text / character drawing and size measurement functions: new arguments for font size, outline and text alignment were added, as well as new function for drawing multiline text.</ul><h1 id=icu-data-build-options-and-export-templates>ICU data, build options and export templates</h1><p>Full-blown CTL support is slower and results in a bigger binary size. Since some game project do not need CTL at all, or text usage is very limited, multiple compile options are available to control text server building.<p>The following build options are available:<p><code class="language-plaintext highlighter-rouge">builtin_graphite</code>, <code class="language-plaintext highlighter-rouge">builtin_harfbuzz</code> and <code class="language-plaintext highlighter-rouge">builtin_icu</code> (default value: <code class="language-plaintext highlighter-rouge">True</code>) - If true, uses built-in version of the libraries instead of system copies (can be disabled for Linux distribution packages).<p>There are two text server implementations available by default:<p>ICU, HarfBuzz and SIL Graphite 2 based text server with the full complex text layout support (<code class="language-plaintext highlighter-rouge">adv</code>).
|
||
Fallback text server without CTL support, it uses same text server API and provides roughly the same text rendering capabilities as in Godot 3.2 (<code class="language-plaintext highlighter-rouge">fb</code>).<p>Text servers can be enabled/disabled by using following build options: <code class="language-plaintext highlighter-rouge">module_text_server_adv_enabled</code>, <code class="language-plaintext highlighter-rouge">module_text_server_fb_enabled</code>.<p>If an editor or export template is built with the multiple text servers enabled, the default one can be selected in Project Settings or by using the <code class="language-plaintext highlighter-rouge">--text-driver Name</code> command line argument.<p>At runtime, the text server can be changed by using the <code class="language-plaintext highlighter-rouge">TextServerManager</code> class, but doing so will cause all existing fonts and text buffers to become invalid. It’s the user’s responsibility to free all such resources before switching text server.<p>Additional text servers can be implemented as C++ modules or GDNative libraries, and loaded / used in the same way default servers are.<p>Some languages do not use spaces between words, and dictionaries (or other type of rule-based word breaking data) are required for better line breaking. ICU provides such data, but its size can be substantial for smaller projects. To reduce export size, ICU data is not included by default, but it can be added at export and stored in the PCK archive. ICU data is always embedded in the editor binaries. Also, you can avoid the necessity for ICU data at all by manually adding valid breaking points (by adding zero-width breaking spaces, which can be inserted from Godot input controls’ context menu).<h1 id=demos-and-docs>Demos and docs</h1><ul><li>CTL documentation and tutorials PR - https://github.com/godotengine/godot-docs/pull/4319<li>CTL demos PR - https://github.com/godotengine/godot-demo-projects/pull/538</ul><h1 id=reference-work>Reference work</h1><ul><li>UI improvements for implementing BiDi aware apps proposal - https://github.com/godotengine/godot-proposals/issues/1183<li>CTL implementation main PR (merged) - https://github.com/godotengine/godot/pull/41100<li>RichTextLabel control refactoring PR (merged) - https://github.com/godotengine/godot/pull/42595<li>Variable font support PR (merged) - https://github.com/godotengine/godot/pull/43030<li>Compatibility support for the legacy Font resources PR (merged) - https://github.com/godotengine/godot/pull/43931<li>Drop cap support PR (merged) - https://github.com/godotengine/godot/pull/43691</ul></div></div></article><div class=blog-navigation><div class=previous><span>Previous</span>
|
||
<a rel=prev href=/article/godot-web-export-progress-report-4/>Web Editor beta, AudioWorklet, GDNative and more!</a></div><div class=next><span>Next</span>
|
||
<a rel=next href=/article/godot-2020-year-in-review-video/>Godot 2020 Year in Review video</a></div></div></div><link rel=stylesheet href=/assets/css/anchor-link.css?1><link rel=stylesheet href=/assets/css/article-cards.css?3><script src=/assets/js/anchor-link.js></script><script>document.addEventListener("DOMContentLoaded",()=>{window.applyAnchorLinks(".article-body"),document.querySelectorAll(".article-cover img, .article-body img").forEach(e=>{if(e.classList.contains("lightbox-ignore"))return;const t=document.createElement("a");t.href=e.src,t.classList.add("lightbox"),t.dataset.group="article",e.parentNode.appendChild(t),t.appendChild(e)})})</script></main><footer class=footer-global><div class=wrapper><div class=columns><div class=col><h2>Godot Engine</h2><ul><li><a class=set-os-download-url href=/download>Download</a><li><a href=https://docs.godotengine.org>Documentation</a><li><a href=/features/>Features</a><li><a href=https://editor.godotengine.org/releases/latest/>Web editor</a><li><a href=/download/archive/>Release archive</a><li><a href=https://github.com/godotengine>Source code</a></ul></div><div class=col><h2>Project</h2><ul><li><a href=/blog/>Blog</a><li><a href=/code-of-conduct/>Code of conduct</a><li><a href=/governance/>Governance</a><li><a href=/teams/>Teams</a><li><a href=/priorities/>Priorities</a><li><a href=/community/>Communities</a></ul></div><div class=col><h2>Resources</h2><ul><li><a href=https://godotengine.org/asset-library/asset>Asset library</a><li><a href=/press/>Press kit</a><li><a href=/showcase/>Showcase</a><li><a href=/education/>Education</a></ul></div><div class=col><h2>Foundation</h2><ul><li><a href=https://godot.foundation/>About</a><li><a href=https://fund.godotengine.org>Donate</a><li><a href=/license/>License</a><li><a href=/privacy-policy/>Privacy policy</a><li><a href=/contact/>Contact us</a></ul></div></div><hr><div class=credits-and-socials><p>© 2007-2025 Juan Linietsky, Ariel Manzur and <a href=https://github.com/godotengine/godot/blob/master/AUTHORS.md target=_blank rel=noopener>contributors</a>. Hosted by the <a href=https://godot.foundation/ target=_blank rel=noopener>Godot Foundation</a>. Website <a href=https://github.com/godotengine/godot-website target=_blank rel=noopener>source code on GitHub</a>.<div class=social><a href=https://github.com/godotengine target=_blank rel=noopener title=GitHub><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6.0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6.0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3.0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1.0-6.2-.3-40.4-.3-61.4.0.0-70 15-84.7-29.8.0.0-11.4-29.1-27.8-36.6.0.0-22.9-15.7 1.6-15.4.0.0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5.0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9.0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4.0 33.7-.3 75.4-.3 83.6.0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6.0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9.0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
|
||
</a><a href=https://bsky.app/profile/godotengine.org target=_blank rel=noopener title=Bluesky><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M407.8 294.7c-3.3-.4-6.7-.8-10-1.3 3.4.4 6.7.9 10 1.3zM288 227.1C261.9 176.4 190.9 81.9 124.9 35.3 61.6-9.4 37.5-1.7 21.6 5.5 3.3 13.8.0 41.9.0 58.4S9.1 194 15 213.9c19.5 65.7 89.1 87.9 153.2 80.7 3.3-.5 6.6-.9 10-1.4-3.3.5-6.6 1-10 1.4-93.9 14-177.3 48.2-67.9 169.9C220.6 589.1 265.1 437.8 288 361.1c22.9 76.7 49.2 222.5 185.6 103.4 102.4-103.4 28.1-156-65.8-169.9-3.3-.4-6.7-.8-10-1.3 3.4.4 6.7.9 10 1.3 64.1 7.1 133.6-15.1 153.2-80.7C566.9 194 576 75 576 58.4s-3.3-44.7-21.6-52.9c-15.8-7.1-40-14.9-103.2 29.8C385.1 81.9 314.1 176.4 288 227.1z"/></svg>
|
||
</a><a href=https://mastodon.gamedev.place/@godotengine target=_blank rel=noopener title=Mastodon><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M433 179.1c0-97.2-63.7-125.7-63.7-125.7-62.5-28.7-228.6-28.4-290.5.0.0.0-63.7 28.5-63.7 125.7.0 115.7-6.6 259.4 105.6 289.1 40.5 10.7 75.3 13 103.3 11.4 50.8-2.8 79.3-18.1 79.3-18.1l-1.7-36.9s-36.3 11.4-77.1 10.1c-40.4-1.4-83-4.4-89.6-54a102.5 102.5.0 01-.9-13.9c85.6 20.9 158.7 9.1 178.8 6.7 56.1-6.7 105-41.3 111.2-72.9 9.8-49.8 9-121.5 9-121.5zm-75.1 125.2h-46.6V190.1c0-49.7-64-51.6-64 6.9v62.5H201V197c0-58.5-64-56.6-64-6.9v114.2H90.2c0-122.1-5.2-147.9 18.4-175 25.9-28.9 79.8-30.8 103.8 6.1l11.6 19.5 11.6-19.5c24.1-37.1 78.1-34.8 103.8-6.1 23.7 27.3 18.4 53 18.4 175z"/></svg>
|
||
</a><a href=https://discord.gg/godotengine target=_blank rel=noopener title=Discord><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M524.5 69.8a1.5 1.5.0 00-.8-.7A485.1 485.1.0 00404.1 32a1.8 1.8.0 00-1.9.9 337.5 337.5.0 00-14.9 30.6 447.8 447.8.0 00-134.4.0 309.5 309.5.0 00-15.1-30.6 1.9 1.9.0 00-1.9-.9A483.7 483.7.0 00116.1 69.1a1.7 1.7.0 00-.8.7C39.1 183.7 18.2 294.7 28.4 404.4a2 2 0 00.8 1.4A487.7 487.7.0 00176 479.9a1.9 1.9.0 002.1-.7 348.2 348.2.0 0030-48.8 1.9 1.9.0 00-1-2.6 321.2 321.2.0 01-45.9-21.9 1.9 1.9.0 01-.2-3.1c3.1-2.3 6.2-4.7 9.1-7.1a1.8 1.8.0 011.9-.3c96.2 43.9 200.4 43.9 295.5.0a1.8 1.8.0 011.9.2c2.9 2.4 6 4.9 9.1 7.2a1.9 1.9.0 01-.2 3.1 301.4 301.4.0 01-45.9 21.8 1.9 1.9.0 00-1 2.6 391.1 391.1.0 0030 48.8 1.9 1.9.0 002.1.7 486 486 0 00147.2-74.1 1.9 1.9.0 00.8-1.4c12.2-126.7-20.6-236.8-87-334.5zm-302 267.8c-29 0-52.8-26.6-52.8-59.2s23.4-59.3 52.8-59.3c29.7.0 53.3 26.8 52.8 59.2.0 32.7-23.4 59.3-52.8 59.3zm195.4.0c-29 0-52.8-26.6-52.8-59.2s23.3-59.3 52.8-59.3c29.7.0 53.3 26.8 52.8 59.2.0 32.7-23.2 59.3-52.8 59.3z"/></svg>
|
||
</a><a href=https://www.reddit.com/r/godot title=Reddit target=_blank rel=noopener><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 256C0 114.6 114.6.0 256 0S512 114.6 512 256 397.4 512 256 512H37.1c-13.7.0-20.5-16.5-10.9-26.2L75 437C28.7 390.7.0 326.7.0 256zM349.6 153.6c23.6.0 42.7-19.1 42.7-42.7s-19.1-42.7-42.7-42.7c-20.6.0-37.8 14.6-41.8 34-34.5 3.7-61.4 33-61.4 68.4v.2c-37.5 1.6-71.8 12.3-99 29.1-10.1-7.8-22.8-12.5-36.5-12.5-33 0-59.8 26.8-59.8 59.8.0 24 14.1 44.6 34.4 54.1 2 69.4 77.6 125.2 170.6 125.2s168.7-55.9 170.6-125.3c20.2-9.6 34.1-30.2 34.1-54 0-33-26.8-59.8-59.8-59.8-13.7.0-26.3 4.6-36.4 12.4-27.4-17-62.1-27.7-1e2-29.1v-.2c0-25.4 18.9-46.5 43.4-49.9 4.4 18.8 21.3 32.8 41.5 32.8zM177.1 246.9c16.7.0 29.5 17.6 28.5 39.3s-13.5 29.6-30.3 29.6-31.4-8.8-30.4-30.5S160.3 247 177 247zm190.1 38.3c1 21.7-13.7 30.5-30.4 30.5s-29.3-7.9-30.3-29.6c-1-21.7 11.8-39.3 28.5-39.3s31.2 16.6 32.1 38.3zm-48.1 56.7c-10.3 24.6-34.6 41.9-63 41.9s-52.7-17.3-63-41.9c-1.2-2.9.8-6.2 3.9-6.5 18.4-1.9 38.3-2.9 59.1-2.9s40.7 1 59.1 2.9c3.1.3 5.1 3.6 3.9 6.5z"/></svg>
|
||
</a><a href=/rss.xml title=RSS target=_blank rel=noopener><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M64 32C28.7 32 0 60.7.0 96V416c0 35.3 28.7 64 64 64h320c35.3.0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM96 136c0-13.3 10.7-24 24-24 137 0 248 111 248 248 0 13.3-10.7 24-24 24s-24-10.7-24-24c0-110.5-89.5-2e2-2e2-2e2-13.3.0-24-10.7-24-24zm0 96c0-13.3 10.7-24 24-24 83.9.0 152 68.1 152 152 0 13.3-10.7 24-24 24s-24-10.7-24-24c0-57.4-46.6-104-104-104-13.3.0-24-10.7-24-24zm0 120a32 32 0 1164 0 32 32 0 11-64 0z"/></svg></a></div></div></div></footer><script defer src=/assets/js/localize.js?5></script><script defer src=/assets/js/tobii.min.js></script><script defer src=/assets/js/highlight.min.js?1></script><script defer src=/assets/js/highlight.gdscript.min.js?1></script><script>document.addEventListener("DOMContentLoaded",()=>{document.querySelectorAll("pre code").forEach(e=>{hljs.highlightBlock(e)}),document.querySelectorAll("[data-post-date]").forEach(e=>{Date.parse(e.dataset.postDate)>Date.now()-1e3*60*60*48&&e.classList.add("post-recent-highlight")}),new Tobii({zoom:!1});const e=document.querySelectorAll(".set-os-download-url");for(let n=0;n<e.length;n++){const s=e[n];let o="download";"version"in s.dataset&&s.dataset.version==="3"&&(o="download/3.x");let t="windows";navigator.platform.indexOf("Mac")!==-1?t="macos":navigator.userAgent.indexOf("Android")!==-1?t="android":navigator.platform.indexOf("Linux")!==-1&&(t="linux"),s.href=`/${o}/${t}/`}})</script> |