I have a PR that allows the use of right-to-left and complex languages that require “text shaping”, like Arabic & Hebrew. And for other languages that are improved by it, like Indic, Thai, Lao, Khmer, Myanmar, Tibetan, Hangul, and others. Non-language benefits of this will (eventually) be support for ligatures, modern gpos kerning, and other text features.
Although this “mostly” works, I am needing some help from other developers who use one of these languages, ideally Arabic. In an ideal world they would be developers who are also familiar with mixed-direction complex text editing and/or direct experience with Harfbuzz. At this point I am not needing any testing or help from non-developers - that will come at a later point.
Compiling
The PR contains all the changes you need, assuming that you are able to build Blender. However, note that this requires two libraries that are included in our sources but are optional and disabled by default. So you would need to enable these by editing your build’s CMakeCache.txt file and ensuring that both WITH_FRIBIDI
and WITH_HARFBUZZ
are set to ON
. Then go to your build folder and run cmake .\
to update it with your new settings.
Once built, run Blender, go to Edit / Preferences / Interface and turn on “Developer Extras”. Then select the “Experimental” category and turn on “Complex Text Shaping”.
With this feature turned on you should be able to enter the following as an object name, “اَلْعَرَبِيَّةُ”, and see it displayed the same:
Yeah! But if you try inserting your mouse cursor, selecting text, etc it might sometimes behave oddly, which is what I need help with.
Design Requirements
It is a design requirement (of mine) that we support multiple languages of differing complexity and direction simultaneously, unrelated to OS input and Blender output settings. What this means is that your OS input language could be Russian, while having Blender set to display in English, but still enter and edit a single line of text that contains some Arabic, then English, and then Hebrew. So we want to do this well, not rely on any hacks or assumptions about the language expected. The (possibly aspirational) target is to do this as well as Chrome does.
This requirement is why we are using both FriBiDi and Harfbuzz. Although Harbuzz alone can reorder (some) RTL input text, it cannot properly reorder text containing multiple languages that differ in direction. So FriBiDi (an implementation of the Unicode Bidirectional Algorithm) is used to reorder and then we feed individual language fragments to Harbuzz for text shaping.
This means that the expected behavior might be unexpected to people unfamiliar with the editing of multi-directional text. Your web browser probably does this properly, so try selecting portions of text within the following string: “abc اَلْعَرَبِيَّةُ abc עִבְרִית abc”. Depending on where you start and end you might see more than one highlighted text section and sometimes they fill in the opposite direction you are dragging. That is normal and expected.
The PR Changes
This PR makes very minimal changes to the source file that is most important in regards to text display and editing: blf_font.cc
. Instead all the text shaping code is in a new file, blf_shaping.cc
(428 lines). Outside of this are just minor changes, mostly helper functions to determine whether a string contains any RTL codepoints or any belonging to a language that uses text shaping. blf_glyph.cc
has very minimal changes, mostly gaining a new function to find the ideal font in the stack for a requested language fragment.
How It Works
With western languages that use Latin text we have the luxury of just starting with the first letter in a string and then processing a single character at a time until we reach the end.
But with complex text we are starting with a fragment of input text (having the same language or direction) and processing it together to obtain an array of output glyphs that we then display. The glyph used to represent an input character might change depending on where it is in the word or based on some other relationship. The output array could have more or less glyphs than the input text. And might be in a different order.
In order to help with this complexity there is an structure called ShapingData
that contains all the arrays and data needed. An example of its use can be seen in blf_shaping_draw
. ShapingData
’s constructor takes a utf-8 string and length, so just defining…
ShapingData text(str, str_len);
… will initialize everything and have FriBiDi reorder it, so you end up with UTF-32 arrays of the logical string (input), the visual string (output), and arrays that map from the former to/from the latter.
blf_shaping_draw
then just calls text.process
in a loop until it returns false. This function isolates each language fragment, populating a segment
member that contains an array of glyphs and their positions. This processing is done by Harbuzz, with cluster_level set to HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS and with each cluster set to the character’s position in the string. This part allows us to know which input character is responsible for each output glyph.
What Doesn’t Work
The above works fine for displaying any text that contains a combination of any language and direction. The complexity comes when inserting your text cursor, moving that cursor, selecting text, etc.
When editing text it is important to keep in mind that we are editing the input string, not the string you see. Therefore we have functions that tell us which input character represents the glyph you just clicked on. And functions that tell us where to display the text cursor when the current position is at a particular place in the input, and that build highlight boxes from what you have selected. It is here that we have some left-to-right bias and off-by-one errors that I am finding hard to debug. Simple things like clicking to the right of the string should place the cursor at the end, but RTL ordering and a differing amount of output glyphs can complicate this beyond my unilingual brain.
What Is Needed
I am hoping that the code is in a shape that others can understand. Our text output code is complex but the code here and the issues involved are all within a small portion of it. I am really hoping to find someone who can take a look and say “yes, I can see why you are having this problem” and offer code help and advice. I am probably misunderstanding how Harfbuzz is meant to help with editing through its clustering.
EDIT: I’m fairly certain my last issues would be easy to fix if I were able to find a tutorial on how to do cursor positioning, selection highlighting, etc when using bidirectional text with Harfbuzz. Perhaps such tutorials exist but are only in Arabic? The closest I get to this is a document that explains that “a client program using HarfBuzz can utilize the cluster information to implement” these features, but not how to do so.
But I really don’t need non-development help though. User testing would come later.