Add parley_core itemizer, refactor parley to use it - #670
Conversation
c64636c to
18b88f2
Compare
| let bidi_level = if self.bidi_levels.is_empty() { | ||
| self.paragraph_bidi_level | ||
| } else { | ||
| self.bidi_levels[0] | ||
| }; |
There was a problem hiding this comment.
Possibly we can start thinking about #649 (comment).
There was a problem hiding this comment.
To clarify, this review comment means that this 'consuming' side implements that, but not the creation side?
There was a problem hiding this comment.
That's right. I've not verified whether there are any other places still consuming it, but probably it should be relatively simple to also optimize/symmetrize the RTL case now.
18b88f2 to
94eff51
Compare
DJMcNab
left a comment
There was a problem hiding this comment.
This looks like a good split to me. None of the concerns are blocking, although I think a little bit of clarify improvement might help things here.
| // Inline boxes *before* this indexed are popped (this occurs if the itemizer | ||
| // split a run and we were not called). We push all boxes to the layout when we | ||
| // loop over the produced items. |
There was a problem hiding this comment.
What do you think of expanding the parenthetical, e.g. to this occurs if the itemiser split a run and we were not called, such as at a bidi boundary
The second sentence of this comment also probably could just be removed, but idk.
There was a problem hiding this comment.
Done, and removed the second line, too. I initially wrote it, as "popped" reads a bit as if they're ignored, but I suppose the code is clear enough that at this point it's just doing splitting (as opposed to pushing to layout).
| let bidi_level = if self.bidi_levels.is_empty() { | ||
| self.paragraph_bidi_level | ||
| } else { | ||
| self.bidi_levels[0] | ||
| }; |
There was a problem hiding this comment.
To clarify, this review comment means that this 'consuming' side implements that, but not the creation side?
| /// The per-char info, parallel to [`Self::char_indices`]. | ||
| char_info: &'a [CharInfo], | ||
| /// The per-char bidi level, parallel to [`Self::char_indices`]. | ||
| bidi_levels: &'a [BidiLevel], |
There was a problem hiding this comment.
Is there a particular reason for these not to use core::slice::Iter? I guess you need to peek into both, so it doesn't actually win anything?
There was a problem hiding this comment.
Yeah, I considered making them iterators, but they always need peeking, so it doesn't really matter which way you go (under the assumption Peekable<slice::Iter> is as efficient - I haven't measured!). Instead I wrote the code to reslice to the tail every time.
There was a problem hiding this comment.
Also Peekable of slice is meaningfully less efficient!
| self.current_script = script; | ||
| } | ||
|
|
||
| if bidi_level != item_bidi_level || real_script(script) && script != self.current_script |
There was a problem hiding this comment.
Can this real_script(script) here ever be false?
There was a problem hiding this comment.
Whoops, good catch (went through a few iterations of script handling!). I believe dropping this additional check is also valid if we ever default the text's initial script differently (i.e., if there's no "first real script", we currently set it to Latin somewhat arbitrarily, following what parley already did, and this still works if we set it to Common instead).
| const LATN: Script = Script::from_str_unchecked("Latn"); | ||
| const GREK: Script = Script::from_str_unchecked("Grek"); | ||
| const ARAB: Script = Script::from_str_unchecked("Arab"); |
There was a problem hiding this comment.
Have we checked whether:
const LATN: Script = Script::from_bytes(*b"Latn");
works? In that scenario, I might be in favour of removing from_str_unchecked entirely (obviously not in this PR)
There was a problem hiding this comment.
(removing from_str_unchecked doesn't sound half bad to me!)
| if level != item.level || script != item.script { | ||
| break_run = true; | ||
| let item_style_index = char_style_indices[item_range.char_range.start]; | ||
| let style_index = char_style_indices[item_range.char_range.end]; |
There was a problem hiding this comment.
I feel like this should be a char_range.end + 1, but the fact that none of our tests indicate that suggest I'm mistaken. The name split_after certainly indicates that it semantically means "do we need to split after this range"
There was a problem hiding this comment.
The range given is already decided to be an item. I started writing an explanation here, but wrote them as the following docs on itemize instead.
/// The predicate is given a range encoding the current item and considers whether to split
/// after that item based on the next character. Iff the predicate returns `true`, the text is
/// split after that item; i.e., given a range of `start..end`, the predicate controls whether
/// that item is now finished, or whether it is extended to include `end`, at which point it'll
/// be `start..end+1` (though note [`TextRange`] encodes both byte and character offsets).
There was a problem hiding this comment.
(I wouldn't be opposed to renaming the predicate to something wordier like extend_item_to_next_character and flipping the returned bool's semantics, but merging this for now.)
There was a problem hiding this comment.
Gotcha! It's an exclusive range is the key thing I was missing.
Which of course makes sense, as every other range is exclusive!
85e9cf6 to
95debf3
Compare
6114084 to
435ceea
Compare
This adds
Analysis::itemize, returning an iterator of items to be shaped individually.The itemizer takes care of splitting text on changing bidi levels and script (and more in the future, like glyph orientation). I've ported the heuristics
parleycurrently uses to fill inscriptfor characters without any particular script (i.e.,Common,Inherit, orUnknown). We may at some point want to perform bracket matching (see e.g. UAX 24 § 5.2), which will require some scratch space. At that point, an API likeItemizer::itemize(&mut self, analysis: &Analysis, split_after: ...) -> Items<...>might make sense instead.This benches neutral on my machine