-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Design requirements #1
Comments
This is topical: https://users.rust-lang.org/t/the-state-of-fonts-parsers-glyph-shaping-and-text-layout-in-rust/32064
|
Apparently I also overlooked this: iced-rs/iced#33
Also CC @SimonSapin |
Yes, I also want to write a simple text layout library, which could be used by |
As for the design document, you do not mention:
Basically, it's a nightmare.
TrueType defines at least 9 variants.
I'm not sure this is true. |
Thanks for taking a look @RazrFalcon.
Yes, it is. Lets stick to the idiom of divide-and-conquer as much as we can — although as I've already discovered, the "simple" job of line-wrapping seems to interact with nearly everything else. I really don't want to deal with fonts here. In the short term I intend to do the simplest thing and store a list of loaded fonts over the Drawing fonts and decorations such as underline is also somewhat beyond the scope of this library. Some type of API is needed (draw this list of positioned glyphs; draw a line here); but IMO starting with something simple and improving it later makes more sense than worrying about underline gaps now. Vertical text is another layout, and not one widely used (in Western languages). Also something I think we can leave until later. Hyphenation is a variation of line-wrapping and would of course be good to support (later). The same is true for justified alignment. Okay, I'm after a rich-text handling library which can take care of the basics now, and either be expanded or replaced later. I am not competing with web browsers either. |
Correct rendering of international "plain" text is already rather involved, as RazrFalcon mentioned. Additionally, for "rich" text:
Implementing full CSS layout is not just a matter of length units, it’s a multi-year project. I imagine it is well out of scope for this library. Therefore support for HTML+CSS input only makes sense if you define a very limited subset. But even this can be very tricky to get right, so I would recommend either:
|
Good points @SimonSapin. Initially I plan to support CommonMark via Tables are very likely beyond my interest in the project. As for scope creep, generally I ask other people to do the heavy lifting and provide a rationale. Hopefully eventually someone will either take over this project or make it obsolete. |
Actually I don’t know what you intend to be in scope or not. For general-purpose text layout libraries I think a number of different levels of abstractions make sense:
I imagine that |
@SimonSapin Here are some examples of what resvg is capable of. |
Using CSS for layout control is definitely out-of-scope. Using it for text styles, possibly. There is a potential conflict here: for a GUI there may be many "small documents", some of them simply a few words; on top of this there is already an external "theme" controlling widget rendering, default text size and colours. Possibly allowing optional master CSS and per-document CSS sheets over the basic styles provided by the theme would be the best option (eventually). Regarding segmentation, this is beyond the detail I want to go to. Building on top of Skribo may make a lot of sense. Initially I simply won't support this. @RazrFalcon |
@dhardy Yes, I do plan to extract the text layout bit by bit. But the first step is to port harfbuzz to Rust. |
@RazrFalcon say a shaped Arabic word has formatting applied to make (only) a portion of the word italic: this is only possible if the shaper is formatting-aware, right? I don't see how to do this with HarfBuzz. (As an experiment, I tested and found that LibreOffice can do this — though without knowing Arabic I have no idea whether this would be perceived as being correct.) On the subject of correct shaping of directional Arabic, I found this message with some examples (from before the Unicode BIDI specification) which still behave quite differently today (both between FF and Chromium, and from what the authors state is expected), thus I conclude that no one cares much. (The only reason I investigated is because simply understanding how line-wrapping is supposed to work is devilishly complicated given that it interacts with both BIDI and shaping.) |
Not sure what is the correct method, but resvg will shape the word twice. Once with regular font face, and once with italic. And then combine the result. |
This is just over a month old and HarfBuzz integration was recently merged, so it's a nice time to reflect on progress (taking bullet points from the top of the design document):
|
Haven't checked the API you have yet, but from the discussion:
|
Heh, thanks for the comments. A lot of that touches on things I haven't even planned yet (e.g. I don't plan on touching emojis myself and I'm not sure I'll bother getting style changes within words done "right" — if, as you suggest, there even is such a thing).
For editing, there's potentially a more important question: how to handle large texts and provide performant updates. The lazy option may be to use separate |
A notable nav weirdness is that at least at some point macOS supported visually contiguous selection ranges in bidi text, I can't test if that's still true after they've dumped UI toolkits at least twice, but most of the other weirdness I've heard of is state based (eg up then down on the first line), so stateless sounds fine. Maybe navigation by Unicode property if that's something you will readily have? If you're ok with non-contiguous runs, eg user provides an array of (style, start, end) tuples and a backing buffer, you are punting storage to them, but it sounds like the shaping libs won't like that. |
Thanks for explaining mid-glyph style changes — I certainly don't intend to worry about how exactly that's rendered (in the short term). It's the same with combining diacritics and even multi-byte code-points: valid indices can occur within a glyph (cluster), and it's not worth (in my opinion) throwing an error because of that: better just to assume the index was at the start/end. Later on we should formally document this behaviour but not in the short term. It's also questionable how we should handle navigation over ligatures: it would be valid to decide to always advance by a whole glyph, for example (preventing selection of half a ligature). And in the short term, that may be the easiest option, though arguably not correct. (Backspace can work by removing the last code-point, and in e.g. Qt it's already the case that backspace removes only the last combining diacritic while left/right arrow move over the whole cluster.) |
Likely we will provide both interfaces: we use the version with contiguous storage for |
I would be careful to avoid conflating glyphs and grapheme clusters, "é" is one grapheme cluster with two glyphs (in some fonts) (also, apparently, "ch" in Slovak), while a "ffi" ligature is three grapheme clusters with one glyph (IIRC, Unicode is confusing!) https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
But then:
So glyph is defined by the font, and affects shaping / rendering, while grapheme cluster is defined by Unicode and affects navigation / editing. That said, clusters seem to be pretty complex to do well fast from that TR? Not sure, never had to actually implement them. Probably the right thing to do with multi-cluster glyphs is to divide in the text flow direction by the cluster count, eg. "ffi" may be a 12 pixel wide glyph, and navigation moves through by 4 pixels each time.
That would be a problem if it's being done for the purpose of supporting very large content, at least unless there's some smarts going on that seem like they could be more complex than a rope implementation. |
Hah, just reading a bit further:
Unicode in a nutshell. What a nightmare! |
So many corner cases! Frankly I don't have the bandwidth now to go over the Unicode TRs, so I'm just going to try and get things roughly right and hope I get some correction PRs later. E.g. 'ä' and 'ä' should appear identical (depending on the font and shaping, they don't always), but the first is two code-points while the latter is one. Both are one "grapheme cluster". If navigation is implemented over the text, then left/right arrows should skip combining diacritics; if it is implemented from the generated glyphs, then the result actually depends on the shaping (since it appears HarfBuzz uses a single glyph in both cases, but it can be drawn from multiple, and with Zalgo text it must use separate glyphs). Ligatures are defined only by the font (avoiding that other definition, where e.g. ß is a ligature), thus navigation based on the code-points can advance one "character" at a time through a ligature correctly. Placing an edit marker / selection end within a ligature: as you say, probably the correct approach is dividing the width by the number of chars. Drawing a selection background over half a ligature should then be easy enough, but changing font colour part-way: lets not worry about that yet.
Even editing bidi text under a standard-compliant editor is a nightmare IMO. For word/line breaks I'm currently using |
Probably? It seems to be confusing to a lot of people: w3c/csswg-drafts#3861 Also, not all line break opportunities are word boundaries, thanks to soft-hyphen at least, and probably others, not sure if that was clear. Probably safest to assume everything is an exception in Unicode! |
Actually, seems like ZWSP is supposed to continue word shaping even across actual line breaks! Eg. Arabic would use medial forms on either side. |
Well... it seems to me that the "latin" alphabet most European languages are based on was adapted heavily to facilitate typesetting, while Unicode is a complex attempt to adapt typesetting to handwritten alphabets. I suspect many of these corner-cases are unclear even to native users of the languages in question and more about hacks for machine-generated text. Anyway... for now I'm going to take the policy of lets not care about corner cases and focus on getting the basics right (otherwise I fear this library will never get anywhere). |
@dhardy This may not be your intention but your last comment comes across as incredibly dismissive and ignorant. Printed media and typesetting are much older than computers and Unicode. Each region of the world has had centuries to develop conventions and rules for how to do it in their respective writing systems. Most of those writing systems in turn are much older than typesetting. Now, implementing fully correct layout of international text is hard. Only you get to decide how you spend your time and energy, and it’s fine if you decide to only care in your code about handling a simplified model of Latin text. There is no need to disregard everything else as corner cases. |
Depends on what was being referred to as corner cases: mid-word line breaking is reasonably advanced, from a shaping perspective it certainly is enough of an edge case for it to be a legitimately open question even for experts if it should work. I'm very against editing and navigating by glyph though: that's a work in progress hack implementation even for plain English text, let alone Latin scripts, let alone the rest of the wacky stuff Unicode gets up to. I think editing by code point is very bizarre (backspace on "á" gives you "a" when it is using a combining diacratic but deleting the whole character if it's in the combined normalized form, can you navigate between combining characters, if so what does backspace do, etc) but some editors do do this intentionally, as pointed out. So the right thing here is know where you can't make simplifying assumptions, especially implicit assumptions, so as you work through the backlog you aren't boxing yourself in. Otherwise you aren't releasing anything for decades with how big and complex Unicode is. I'd rather have something a little broken than nothing after all. Did you know that Unicode supports left to right text embedded within a top to bottom line? Guess how many implementations get that right. |
@SimonSapin the statement I made was an overly crude approximation. Even so, when I mentioned European languages being adapted to facilitate typesetting, I was not referring to modern digital typesetting. Regarding the history of non-European typesetting, I admit that I am incredibly ignorant on this subject. This article would appear to be a decent starting point to educate myself on this, but it does rather seem to reinforce my point: European scripts have been adapted to better enable typesetting, while this approach hasn't really worked for Arabic (or rather, has had less influence on the letter forms). Regarding corner-cases, I was also refering to this message (linked above), where Arab authors suggest four methods of shaping a sequence of glyphs without apparently pointing out the correctness of any — presumably because the logical character sequence has no semantic meaning anyway. There are lots of corner cases in Unicode which would appear to be semantically meaningless.
You're right, this is a short-sighted hack. As for backspace and left-arrow behaving differently, Qt and GTK both do this. Probably though this library should not be opinionated on the matter and just offer both variants. Vertical text is another thing I know exists, but would prefer to ignore for now. |
Rich-text is now supported (though not as originally expected). A parser must implement the This still leaves some gaps in functionality:
|
This is to collect feedback on the initial design document. This is more a plan-of-scope than an actual design, but does give me some idea where to start.
@hecrj and @alexheretic are invited to comment (especially Héctor), as is anyone else with useful insight into this topic. (I may also consider renaming to something more neutral if there are good suggestions; it is not intended to be tied to KAS in any way.)
Major inspirations: Modern text rendering with Linux,
glyph-brush-layout
, Unicode Bidirectional Algorithm, HarfBuzz and harfbuzz_rs.Tracker:
String
repr, BIDI and run-breaking parses whole text at once)The text was updated successfully, but these errors were encountered: