Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partial ligature selection with DirectWrite

Using HitTestTextPosition style API from IDWriteTextLayout I did not managed to handle properly text positions inside "ti", "ffi" or other ligatures with fonts like Calibri. It always returns position after or before ligature not inside like t|i or f|f|i.

What is the recommended way to do a caret movement inside ligatures with DirectWrite API?

like image 536
Filip Kunc Avatar asked Jan 26 '18 13:01

Filip Kunc


2 Answers

The text editors I know of use the simple hack of (1) dividing the width of the glyph cluster by the number of code points within the cluster (excluding any zero width combining marks), rather than use the GDEF caret positioning information. This includes even Word, which you can tell if you look closely enough below. It's not precise, but since it's simple and close enough at ordinary reading sizes, it's what many do:

Word displaying fi ligature in Calibri

(2) I've heard that some may (but don't know which) also use the original glyph advances of the unshaped characters (pre-ligation) and scale them proportionally to the ligature cluster width.

(3) Some text editors may use the GDEF table, but I never knew of any for sure (possibly Adobe In-Design?).

The most challenging aspect of using methods 2 or 3 with IDWriteTextLayout is that accessing the corresponding IDWriteFontFace in that run requires quite the indirection because the specific IDWriteFontFace used (after resolving font family name+WWS+variable font axes) is stored in the layout but not publicly accessible via any "getter" API. The only way you can extract them is by "drawing" the glyph runs via IDWriteTextLayout::Draw into a user-defined IDWriteTextRenderer interface to record all the DWRITE_GLYPH_RUN::fontFace's. Then you could call IDWriteFontFace::GetDesignGlyphAdvances on the code points or IDWriteFontFace::TryGetFontTable to read the OpenType GDEF table (which is complex to read). It's a lot of work, and that it's because...

The official PadWrite example has the same issue

IDWriteTextLayout was designed for displaying text rather than editing it. It has some functionality for hit-testing which is useful if you want to display an underlined link in a paragraph and test for it being clicked (in which case the ligature would be whole anyway within a word), or if you want to draw some decorations around some text, but it wasn't really intended for the full editing experience, which includes caret navigation. It was always intended that actual text editing engines (e.g. those used in Word, PowerPoint, OpenOffice, ...) would call the lower level API's, which they do.

The PadWrite sample I wrote is a little misleading because although it basic editing, that was just so you can play around with the formatting and see how things worked. It had a long way to go before it could really be an interactive editor. For one (the big one), it completely recreated the IDWriteTextLayout each edit, which is why the sample only presented a few paragraphs of text, because a full editor with several pages of text would want to incrementally update the text. I don't work on that team anymore, but I've thought of creating a DWrite helper library on GitHub to fill in some hindsight gaps, and if I ever did, I'd probably ... just use method 1 :b.

like image 78
Dwayne Robinson Avatar answered Jan 03 '23 15:01

Dwayne Robinson


There... is no "inside" position if you have GSUB replacements turned on?

Opentype GSUB ligatures are single glyph replacements for codepoint sequences, rather than being "several glyphs, smushed together". They are literally distinct, single glyphs, with single bounding boxes, and a single left and right side bearing for cursor placement/alignment. If you have the text A + E and the font has a ligature replacement that turns it into Ӕ then with ligatures enabled there really are only two cursor positions in that code sequence: and Ӕ|. You can't place the cursor "in the middle", because there is no "middle"; it's a single, atomic, indivisible element.

The same goes for f. ligatures like , , , , , or : these are single glyphs once shaped with GSUB turned on. This is in fact what's supposed to happen: having GSUB ligatures enabled means you expressly want text to be presented—for all intents and purposes—as having atomic glyphs for many-to-one substitutions, like turning the full phrase "صلى الله عليه وعلى آله وسلم‎", as well as variations of that, into the single glyph ﷺ.

If you want to work with the base codepoint sequences (so that if you have a text with f + f + i it doesn't turn that into ) you will need to load the font with the liga OpenType feature disabled.

like image 28
Mike 'Pomax' Kamermans Avatar answered Jan 03 '23 16:01

Mike 'Pomax' Kamermans