-
Notifications
You must be signed in to change notification settings - Fork 15
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
Support inline box layout #25
Comments
Seems like a good start, but also affects Cursor. I think it would be wise to have a text-only interface (maybe keep the current API there) that handles the case of mere text; and implement that on top of the more general interface. |
This looks like the exact right approach for adding inline objects to the current API. The only potential issue I see is bidi handling-- objects would essentially be ignored. Before we get started, I'd like to propose a different approach... something I've been hacking away on when time permits for a few months now. I think we should drop the "attributed text" API, at least as the core layout primitive, and replace it with a tree-based API. The current proof of concept looks like this: /// User defined identifier for a text element.
pub type TextId = u64;
/// User defined identifier for an inline object element.
pub type ObjectId = u64;
/// User defined identifier for a span element.
pub type SpanId = u64;
impl<'a> Builder<'a> {
/// Pushes a new span with the given identifier and style.
///
/// Any undefined properties in the style will be inherited from the
/// parent (or default) span.
pub fn push_span(&mut self, id: SpanId, style: &Style) {}
/// Pops the current span.
pub fn pop_span(&mut self) {}
/// Appends a text fragment.
///
/// The `id` parameter allows tracking the source of the fragment and
/// the `start` parameter can be used to track the offset where this text
/// begins in that source.
pub fn text(&mut self, id: TextId, start: usize, text: &str) {}
/// Appends an inline object.
///
/// The `id` parameter allows tracking the source of the object. The
/// dimensions are used during line layout and breaking.
pub fn object(&mut self, id: ObjectId, width: f32, height: f32) {}
/// Consumes the builder and flushes any pending operations.
pub fn finish(mut self) {}
} IMO, this is more generally useful than attributed text (which can be built atop this) and goes a long way toward supporting real inline flow layout. The current state does track The complexity of the cursor type, and text selection/navigation more generally, will likely grow in response to this but I think that's a worthwhile trade-off. We can provide a simplified API mode if necessary. |
|
RE: 1. Might be reasonable to somehow make the object aware of the writing direction (and mode, if we implement it). RE: 4. I don't think there is much planned to change right this moment outside of font libraries/fallback (fount/fontique) but I might be wrong about that. The rest is working fine for current use cases. |
Seems like we're all on the same page wrt object bidi. The current code just adds an object replacement character so it's boundary neutral but we can easily use strong a LTR/RTL character instead, or surround the BN character with LRI/RLI+PDI controls depending on what works best. We could expose this with a writing direction property as Aaron mentioned. I'm going to assume further discussion is based on some sort of tree structure because that's the general case and the more difficult one to solve. Both cosmic-text and parley already do a pretty good job with attributed text. So given a tree structure, we can define two processing stages that need to occur in the "front end" phase of layout:
The API I posted above handles both of these simultaneously but I don't see why the functionality couldn't be exposed separately.
I'm a slightly hesitant +1 on spinning out the style components. I'd like to see this happen but my preference is to get a solid concrete implementation working first.
I'm not opposed to adding a pull-based API but I think that can be built on top of the push API. We need to flatten the text nodes and track references through shaping anyway so having access to the client's tree doesn't save us much.
Apologies for this. On zulip, I requested two weeks to make some potentially sweeping changes. I'll see what I can get done in that timeframe. After that, I'll just do a handoff to linebender regardless of the current state and we can address any further changes through the normal PR/review process. |
Just wanted to add: doing incremental updates to layout in general and inline flow in particular is a known hard problem. If we can nail it, that's great but I don't think we should spend a great deal of time on it as a first pass. I'd rather focus on making the full rebuild case as fast as possible and then tackle the incremental case later if necessary. |
I've done a lot of thinking (and a little bit of coding) on this so I thought I'd write down a few more thoughts. If we're considering real CSS typesetting, the main sources of complexity are tracking tree structure and handling merging and splitting of text fragments. The combination of these two drives up the difficulty level fairly quickly. I've tried to identify the points in the pipeline where these come into play:
My goal over the next week is to experiment with these things and see how parley can be modified to accommodate them. I can't allocate any actual work hours to this right now so my time is limited. Regardless, I've set a target of April 8 to do a transfer to linebender so progress is not blocked directly on me. We can move forward from there. |
Really awesome to see that you're thinking about this stuff. I am definitely interested in implementing fully-compliant CSS typesetting. Having said that, as things stand I would happily make do with something close-but-not-quite. And would like to put out there that (as someone building a web renderer) my main priorities are:
Even with just 1, I feel like that could take me from "most of the web is pretty broken" to "most of the web is somewhat resembling what it's supposed to look like" and for text layout to no longer be such a critical blocker. Things like whitespace collapsing and text-transform will be important long-term but seem like they could be emulated relatively well by pre-processing content before passing it into parley. Merging fragments for shaping seems like it is needed for completeness but also something that is likely to be relatively rare in real-world content (surely nobody would generate content like that on purpose - it seems likely that it would mostly be the output of badly behaving WYSIWYG tools?). |
Regarding floats: I have thought about this a little, so I thought I'd do a brain dump of my own. My understanding thus far (with the caveat that there may be edge cases I haven't come across yet) is that text layout can primarily think of floats simply as excluded regions (specifically rectangles) that text should not be laid out into. The annoying float-specific complexity being that those regions are not known ahead of time and are generated/placed when the floated box is encountered in the text stream. And that this can require glyphs in the current line to be retroactively moved out of the floated region. The good news being that:
So float layout basically involves laying out around excluded areas + sometimes shifting all glyphs in the current line to towards the end of the line. The actual placement of the floated box is quite complicated, but AFAIK that doesn't depend on text layout or glyph positions other than "what is the Y offset of the current line" (it also depends on "block formatting context" container size and the position of other floats) so that could potentially live outside of parley (IMO that might make quite a nice standalone micro-library). |
@dfrg Having come to implementing web inline layout in Blitz using Parley, I'm definitely seeing the benefit of the tree-based model. Indeed I suspect it probably be easier to just go ahead and implement this in Parley than try to "lower" a DOM tree down to Parley's existing API (seems like you might already have been of this opinion). Your comment above makes it sound like you might actually have some (unfinished) code implementing this? I'm probably going to look into implementing this tomorrow. I have a pretty good idea of how I might do that (and your notes above are making a lot more sense now I'm actually working with tree-shaped input data), but a partial implementation might well be a useful reference if you have one sitting around. |
Motivation
One may wish to mix textual and non-textual content and have the non-textual content laid out in flow with the text. For example, in order to display images or even whole widgets within paragraphs of text. This is necessary in order to implement web-style "inline/flow" layout, but it's use is not limited to web layout contexts: it is a feature that is more generally useful to anyone wishing to layout mixed content.
Notes
The functionality required from the text layout system in order to implement "inline layout" is laying out fixed size boxes, possibly with some kind of supplementary baseline alignment / vertical alignment information. There is no need for the text layout system to size such boxes.
Proposed Implementation
I think we can avoid involving inline boxes in ranged style resolution. Based on that assumption, my proposal is as follows:
inline_boxes: Vec<InlineBox>
property toLayoutContext
push_inline_box(box: InlineBox)
methodRangedBuilder
which pushes to theinline_boxes
property in the layout contextRangedBuilder::finish
shape::shape_text
to break text runs at text indexes where an inline box is present (in addition to all of the places where it already does so).LayoutData.runs
toLayoutData.runs_or_boxes
using the new enumshape::shape_text
to push inline boxes toLayoutData.runs_or_boxes
LayoutData.line_runs
toLayoutData.line_runs_or_boxes
using the new enumBreakLines::break_next
to account for inline boxes when line breaking. This should:(x, y)
location for the box (either global or line-relative)BreakLines::finish
to account for boxes when performing alignmentThe text was updated successfully, but these errors were encountered: