Partial ligature selection with DirectWrite
Asked Answered
C

2

7

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?

Cleptomania answered 26/1, 2018 at 13:17 Comment(4)
Does DirectWrite have a pointer to the OpenType Ligature Caret Positioning Table?Yeseniayeshiva
I am not sure. I could not find it in the docs for DirectWrite. The docs you mention have a section for older Uniscribe, but no mention of newer DirectWrite. On the other hand I believe that UWP TextEdit has this solved and it uses DirectWrite under the hood. So how they do it? The official PadWrite example has the same issue: github.com/Microsoft/Windows-classic-samples/tree/master/…Cleptomania
Can you expand on what it is you are concretely trying to achieve? (e.g. describe what you'd see as a user, and why you'd expect that to be a thing you want the user to experience?)Vitavitaceous
@Mike'Pomax'Kamermans: I recommend article from MS Word developer: blogs.msdn.microsoft.com/murrays/2012/06/29/… where he tries to explain what was needed to handle ligatures from user perspective. The HitTestSomething API in DirectWrite explicitly mentions caret position for GUI automation, I mean you have to be able to go inside ti or ffi ligature otherwise the user would feel it is broken.Cleptomania
M
2

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.

Merilee answered 2/2, 2018 at 4:44 Comment(4)
Viewed from a glyph point of view, you are (of course) correct, but from a text editor's perspective, that is what the Caret Positioning Table is for: "The Ligature Caret List table (LigCaretList) defines caret positions for all the ligatures in a font." This table is to provide exactly that information needed in your second paragraph. It is a subset, though, and not meant for all possible ligatures (per your Arabic example). Another example would be where :) automatically displays as 😀, in which case it'd also be useless.Yeseniayeshiva
Ah, I had not dug deep enough into GDEF (or: at all). I'll have to dig through this a bit, although we're still left with Filip's phrasing, which feels like he's trying to put a cursor where it shouldn't be (i.e. a presentation context is so different from an edit context I struggle to understand why you'd try to accommodate for this. Doesn't invalidate the question of course, but still adds counter-questionmarks)Vitavitaceous
The positioning inside ligature must be possible, see similar thing for TrueType: developer.apple.com/fonts/TrueType-Reference-Manual/RM06/…Cleptomania
You might need to read through the source for harfbuzz here, to see how it gets DirectWrite to do this.Vitavitaceous
H
2

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'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 supports 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.

Haulage answered 19/3, 2022 at 1:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.