Skip to main content

devpage_design/render/
layout.rs

1//! Layout: the scene as a taffy tree — flexbox restricted to Figma's auto
2//! layout, no-layout frames hugging their children — and every node's box.
3
4use crate::resolve::{self, Align, ImageFiles, Layout, Node, Scene, Size as DSize};
5use crate::tree::{Order, OrderKey};
6
7use super::*;
8
9mod run;
10pub(super) use run::*;
11impl Renderer {
12    /// Lays the scene out: every node's box, in tree order — as a drawing
13    /// lays it out, runs and all.
14    pub fn layout(&mut self, scene: &Scene) -> Vec<Frame> {
15        self.register(&scene.fonts);
16        let mut l = Laid::build(self, scene);
17        l.settle_top(self, &l.hugs.clone());
18        l.frames(self, &scene.root).into_values().collect()
19    }
20
21    pub(super) fn compute(
22        &mut self,
23        runs: &mut Runs,
24        tree: &mut TaffyTree<Option<Leaf>>,
25        root: NodeId,
26        available: Size<AvailableSpace>,
27    ) {
28        tree.compute_layout_with_measure(root, available, |input, _id, ctx, style| {
29            let known = input.known_dimensions;
30            let fixed = |d: Dimension| (!d.is_auto()).then(|| d.value());
31            let known = Size {
32                width: known.width.or(fixed(style.size.width)),
33                height: known.height.or(fixed(style.size.height)),
34            };
35            let size = match ctx {
36                Some(Some(Leaf::Intrinsic(w, h))) => {
37                    // A vector or an image hugging keeps its own aspect.
38                    let (w, h) = (*w, *h);
39                    match (known.width, known.height) {
40                        (Some(kw), None) => Size {
41                            width: kw,
42                            height: kw * h / w,
43                        },
44                        (None, Some(kh)) => Size {
45                            width: kh * w / h,
46                            height: kh,
47                        },
48                        (kw, kh) => Size {
49                            width: kw.unwrap_or(w),
50                            height: kh.unwrap_or(h),
51                        },
52                    }
53                }
54                Some(Some(Leaf::Run(id))) => {
55                    // A size the run's own style gives is never less than
56                    // its padding, as taffy floors a box's own size; a size
57                    // its parent gives — a stretch — is taken as it is.
58                    let [t, r, b, l] = runs.get(*id).padding;
59                    let own = Size {
60                        width: fixed(style.size.width).map(|v| v.max(l + r)),
61                        height: fixed(style.size.height).map(|v| v.max(t + b)),
62                    };
63                    let known = Size {
64                        width: input.known_dimensions.width.or(own.width),
65                        height: input.known_dimensions.height.or(own.height),
66                    };
67                    return taffy::LayoutOutput::from_outer_size(self.measure_run(
68                        runs,
69                        *id,
70                        known,
71                        input.available_space,
72                    ));
73                }
74                Some(Some(Leaf::Text(text, wraps))) => {
75                    let width = known.width.or(match input.available_space.width {
76                        AvailableSpace::Definite(w) if *wraps => Some(w),
77                        _ => None,
78                    });
79                    let layout = self.text_layout(text, width);
80                    Size {
81                        width: known.width.unwrap_or(layout.width().ceil()),
82                        height: known.height.unwrap_or(layout.height()),
83                    }
84                }
85                _ => Size {
86                    width: known.width.unwrap_or(0.0),
87                    height: known.height.unwrap_or(0.0),
88                },
89            };
90            taffy::LayoutOutput::from_outer_size(size)
91        })
92        .expect("layout");
93    }
94}
95
96impl Renderer {
97    /// Lays a tree out from `root` in `available`, hugging no-layout frames
98    /// to their children.
99    pub(super) fn settle_in(
100        &mut self,
101        runs: &mut Runs,
102        tree: &mut TaffyTree<Option<Leaf>>,
103        root: NodeId,
104        available: Size<AvailableSpace>,
105        hugs: &[(NodeId, bool, bool)],
106    ) {
107        for _ in 0..=hugs.len() {
108            self.compute(runs, tree, root, available);
109            let mut changed = false;
110            for &(id, w, h) in hugs {
111                let (mut ew, mut eh) = (0.0f32, 0.0f32);
112                for child in tree.children(id).expect("children") {
113                    let l = tree.unrounded_layout(child);
114                    ew = ew.max(l.location.x + l.size.width);
115                    eh = eh.max(l.location.y + l.size.height);
116                }
117                let mut style = tree.style(id).expect("style").clone();
118                let mut ch = false;
119                let mut set = |d: &mut Dimension, v: f32| {
120                    if *d != Dimension::length(v) {
121                        *d = Dimension::length(v);
122                        ch = true;
123                    }
124                };
125                if w {
126                    set(&mut style.size.width, ew);
127                }
128                if h {
129                    set(&mut style.size.height, eh);
130                }
131                if ch {
132                    tree.set_style(id, style).expect("style");
133                    changed = true;
134                }
135            }
136            if !changed {
137                break;
138            }
139        }
140    }
141}
142
143pub(super) fn find_node<'a>(root: &'a Node, order: &[OrderKey]) -> &'a Node {
144    let mut at = root;
145    for k in order {
146        at = at.children.get(k).expect("a node there");
147    }
148    at
149}
150
151/// Removes `id` and everything under it from the layout tree.
152pub(super) fn remove_subtree(tree: &mut TaffyTree<Option<Leaf>>, id: NodeId) {
153    for child in tree.children(id).unwrap_or_default() {
154        remove_subtree(tree, child);
155    }
156    let _ = tree.remove(id);
157}
158
159/// What a leaf of the layout tree measures by: a text, or a vector's or an
160/// image's own size.
161pub(super) enum Leaf {
162    /// A text, and whether it wraps at its width.
163    Text(Box<resolve::TextStyle>, bool),
164    Intrinsic(f32, f32),
165    /// A run, laid out by its own tree: [`Renderer::measure_run`].
166    Run(u32),
167}
168
169/// A scene's layout kept in pieces: the drawing's own layout tree, a tree
170/// for each run's children, and where each node is in them.
171pub(super) struct Laid {
172    pub(super) top: TaffyTree<Option<Leaf>>,
173    pub(super) root: NodeId,
174    pub(super) runs: Runs,
175    /// Each node's tree and layout node, by its order.
176    pub(super) nodes: BTreeMap<Order, (Owner, NodeId)>,
177    /// Each run's layout, by its node's order.
178    pub(super) run_of: BTreeMap<Order, u32>,
179    /// The frames that hug their children in the drawing's own tree, from
180    /// the build.
181    pub(super) hugs: Vec<(NodeId, bool, bool)>,
182}
183
184use std::collections::BTreeMap;
185
186impl Laid {
187    /// The layout trees of a scene, not yet laid out.
188    pub(super) fn build(_r: &mut Renderer, scene: &Scene) -> Laid {
189        let mut top = TaffyTree::new();
190        // Only the boxes read are rounded, by `placed`, as taffy would round
191        // them all.
192        top.disable_rounding();
193        let mut l = Laid {
194            top: TaffyTree::new(),
195            root: NodeId::from(0u64),
196            runs: Runs::default(),
197            nodes: BTreeMap::new(),
198            run_of: BTreeMap::new(),
199            hugs: Vec::new(),
200        };
201        let mut hugs = Vec::new();
202        let root = l.build_node(
203            &mut top,
204            Owner::Top,
205            None,
206            &scene.root,
207            None,
208            &mut hugs,
209            &scene.images,
210            Vec::new(),
211        );
212        l.top = top;
213        l.root = root;
214        l.hugs = hugs;
215        l
216    }
217
218    pub(super) fn tree(&self, owner: Owner) -> &TaffyTree<Option<Leaf>> {
219        match owner {
220            Owner::Top => &self.top,
221            Owner::Run(id) => &self.runs.get(id).tree,
222        }
223    }
224
225    pub(super) fn tree_mut(&mut self, owner: Owner) -> &mut TaffyTree<Option<Leaf>> {
226        match owner {
227            Owner::Top => &mut self.top,
228            Owner::Run(id) => &mut self.runs.get_mut(id).tree,
229        }
230    }
231
232    /// The layout tree of `node` — at `order`, in `tree`, which is `owner`'s
233    /// and, in a run's tree, inside that run's child `inside` — and
234    /// everything under it.
235    #[allow(clippy::too_many_arguments)]
236    pub(super) fn build_node(
237        &mut self,
238        tree: &mut TaffyTree<Option<Leaf>>,
239        owner: Owner,
240        inside: Option<OrderKey>,
241        node: &Node,
242        parent: Option<&Node>,
243        hugs: &mut Vec<(NodeId, bool, bool)>,
244        images: &ImageFiles,
245        order: Order,
246    ) -> NodeId {
247        let style = style_of(node, parent);
248        if let Some(leaf) = leaf_of(node, images) {
249            let id = tree.new_leaf_with_context(style, Some(leaf)).expect("leaf");
250            self.nodes.insert(order, (owner, id));
251            return id;
252        }
253        if is_run(node) {
254            let rid = self.runs.reserve();
255            // Its padding stays in its style: what a box is given is never
256            // less than its padding, and taffy floors it there.
257            let leaf = tree
258                .new_leaf_with_context(style, Some(Leaf::Run(rid)))
259                .expect("leaf");
260            let mut run = RunLayout::new(node, order.clone(), owner, leaf, inside);
261            for (k, c) in node.children.entries() {
262                self.add_child(&mut run, rid, k, c, node, images, &order);
263            }
264            self.runs.put(rid, run);
265            self.run_of.insert(order.clone(), rid);
266            self.nodes.insert(order, (owner, leaf));
267            return leaf;
268        }
269        let children: Vec<NodeId> = node
270            .children
271            .entries()
272            .map(|(k, c)| {
273                let mut o = order.clone();
274                o.push(k.clone());
275                self.build_node(tree, owner, inside.clone(), c, Some(node), hugs, images, o)
276            })
277            .collect();
278        let id = tree.new_with_children(style, &children).expect("node");
279        if let Some((w, h)) = hugs_of(node, parent) {
280            hugs.push((id, w, h));
281        }
282        self.nodes.insert(order, (owner, id));
283        id
284    }
285
286    /// Builds `child`, at key `k` under the run `node` — `run`, in slot
287    /// `rid`, at `order` — into the run's tree as a child of it.
288    #[allow(clippy::too_many_arguments)]
289    pub(super) fn add_child(
290        &mut self,
291        run: &mut RunLayout,
292        rid: u32,
293        k: &OrderKey,
294        child: &Node,
295        node: &Node,
296        images: &ImageFiles,
297        order: &Order,
298    ) {
299        let mut o = order.clone();
300        o.push(k.clone());
301        let mut hugs = Vec::new();
302        let mut tree = std::mem::replace(&mut run.tree, TaffyTree::new());
303        let id = self.build_node(
304            &mut tree,
305            Owner::Run(rid),
306            Some(k.clone()),
307            child,
308            Some(node),
309            &mut hugs,
310            images,
311            o,
312        );
313        run.tree = tree;
314        let across = if run.row { child.height } else { child.width };
315        run.add(k.clone(), id, across == DSize::Fill, hugs);
316    }
317
318    /// Lays the drawing's own tree out, and every run in it that changed;
319    /// `hugs` the frames in it to hug.
320    pub(super) fn settle_top(&mut self, r: &mut Renderer, hugs: &[(NodeId, bool, bool)]) {
321        let root = self.root;
322        r.settle_in(&mut self.runs, &mut self.top, root, Size::MAX_CONTENT, hugs);
323    }
324
325    /// Where the run at `order` places each child: the run's laid-out sizes
326    /// for its final breadth, and where the first child starts along it.
327    pub(super) fn run_places(
328        &self,
329        rid: u32,
330        size: Size<f32>,
331    ) -> Option<(&RunLayout, &Asked, f32)> {
332        let run = self.runs.get(rid);
333        let (pa0, pa1) = run.padding_along();
334        let (pc0, pc1) = run.padding_across();
335        let (along, across) = if run.row {
336            (size.width, size.height)
337        } else {
338            (size.height, size.width)
339        };
340        let line = (across - pc0 - pc1).max(0.0);
341        let asked = run.placed(line)?;
342        let free = along - pa0 - pa1 - RunLayout::length(asked, run.gap) as f32;
343        Some((run, asked, pa0 + run.lead(free)))
344    }
345
346    /// Every node's box, by order, in the design's coordinates.
347    pub(super) fn frames(&mut self, r: &mut Renderer, root: &Node) -> BTreeMap<Order, Frame> {
348        let mut out = BTreeMap::new();
349        let top = self.root;
350        self.frames_of(
351            r,
352            root,
353            Vec::new(),
354            Owner::Top,
355            top,
356            Place::default(),
357            true,
358            &mut out,
359        );
360        out
361    }
362
363    #[allow(clippy::too_many_arguments)]
364    fn frames_of(
365        &mut self,
366        r: &mut Renderer,
367        node: &Node,
368        order: Order,
369        owner: Owner,
370        id: NodeId,
371        parent: Place,
372        root: bool,
373        out: &mut BTreeMap<Order, Frame>,
374    ) {
375        let tree = self.tree(owner);
376        let (frame, place) = placed(tree, id, parent, root);
377        out.insert(order.clone(), frame);
378        if let Some(&rid) = self.run_of.get(&order) {
379            let size = tree.unrounded_layout(id).size;
380            let mut before = 0.0f64;
381            for (k, c) in node.children.entries() {
382                let Some((run, asked, lead)) = self.run_places(rid, size) else {
383                    return;
384                };
385                let line = asked.line;
386                let Some(s) = asked.sizes.get(k).copied() else {
387                    continue;
388                };
389                let (pc0, _) = run.padding_across();
390                let stretch = run.children[k].stretch;
391                let across = pc0 + run.across(line, &s, stretch);
392                let item = child_place(place, run.row, lead as f64 + before, across as f64);
393                let (gap, node_id) = (run.gap, run.children[k].node);
394                r.relay(&mut self.runs, rid, k, line);
395                let mut o = order.clone();
396                o.push(k.clone());
397                self.frames_of(r, c, o, Owner::Run(rid), node_id, item, false, out);
398                before += s.along as f64 + gap as f64;
399            }
400            return;
401        }
402        let kids = tree.children(id).unwrap_or_default();
403        for ((k, c), &child) in node.children.entries().zip(&kids) {
404            let mut o = order.clone();
405            o.push(k.clone());
406            self.frames_of(r, c, o, owner, child, place, false, out);
407        }
408    }
409}
410
411/// The place a run's child is laid out from: `along` and `across` from the
412/// run's node's unrounded top-left, rounded as taffy rounds a box in the
413/// flow — measured from the run's node — so that the child, at the origin of
414/// its own one-item tree, gets exactly the box taffy would give it.
415pub(super) fn child_place(run: Place, row: bool, along: f64, across: f64) -> Place {
416    // In single precision, as taffy places a box and its rounding pass
417    // rounds it: the child's place within the run, then the run's.
418    let (along, across) = (along as f32, across as f32);
419    let (dx, dy) = if row {
420        (along, across)
421    } else {
422        (across, along)
423    };
424    let raw = (run.raw.0 + dx, run.raw.1 + dy);
425    let at = (
426        run.at.0 + snap(raw.0) - snap(run.raw.0),
427        run.at.1 + snap(raw.1) - snap(run.raw.1),
428    );
429    Place { at, raw }
430}
431
432/// The layout tree of `node` and everything under it as one taffy tree,
433/// no run laid out by its own: what taffy alone makes of a scene, for the
434/// tests to hold the drawing's layout to.
435#[cfg(test)]
436pub(super) fn build_plain(
437    tree: &mut TaffyTree<Option<Leaf>>,
438    node: &Node,
439    parent: Option<&Node>,
440    hugs: &mut Vec<(NodeId, bool, bool)>,
441    images: &ImageFiles,
442) -> NodeId {
443    let style = style_of(node, parent);
444    if let Some(leaf) = leaf_of(node, images) {
445        return tree.new_leaf_with_context(style, Some(leaf)).expect("leaf");
446    }
447    let children: Vec<NodeId> = node
448        .children
449        .iter()
450        .map(|c| build_plain(tree, c, Some(node), hugs, images))
451        .collect();
452    let id = tree.new_with_children(style, &children).expect("node");
453    if let Some((w, h)) = hugs_of(node, parent) {
454        hugs.push((id, w, h));
455    }
456    id
457}
458
459/// Whether `node` hugs its children's extent, as a frame with no layout
460/// does — and along which axes.
461pub(super) fn hugs_of(node: &Node, parent: Option<&Node>) -> Option<(bool, bool)> {
462    if leafy(node) {
463        return None;
464    }
465    // A root has no parent space to fill: on its own, `"fill"` hugs.
466    let hugs_by = |s: DSize| s == DSize::Hug || (parent.is_none() && s == DSize::Fill);
467    let (w, h) = (hugs_by(node.width), hugs_by(node.height));
468    (node.layout == Layout::None && !node.children.is_empty() && (w || h)).then_some((w, h))
469}
470
471/// Whether `node` is a leaf of the layout tree: a text, a vector or an image.
472pub(super) fn leafy(node: &Node) -> bool {
473    node.text.is_some() || node.vector.is_some() || node.image.is_some()
474}
475
476/// What a leaf of the layout tree measures by, for a node that is one.
477pub(super) fn leaf_of(node: &Node, images: &ImageFiles) -> Option<Leaf> {
478    if let Some(text) = &node.text {
479        let wraps = !matches!(node.width, DSize::Hug);
480        return Some(Leaf::Text(Box::new(text.clone()), wraps));
481    }
482    let intrinsic = match (&node.vector, &node.image) {
483        (Some(v), _) => Some((v.view_box[2], v.view_box[3])),
484        (_, Some(i)) => images.get(&i.id).map(|f| (f.width as f32, f.height as f32)),
485        _ => None,
486    };
487    intrinsic.map(|(w, h)| Leaf::Intrinsic(w, h))
488}
489
490/// A node's flexbox style, which its parent's layout decides part of.
491pub(super) fn style_of(node: &Node, parent: Option<&Node>) -> Style {
492    let mut style = Style::default();
493    let dir = parent.map(|p| p.layout);
494    let dim = |s: DSize| match s {
495        DSize::Px(v) => Dimension::length(v),
496        _ => Dimension::auto(),
497    };
498    style.size = Size {
499        width: dim(node.width),
500        height: dim(node.height),
501    };
502    let (main, cross) = match dir {
503        Some(Layout::Row) => (node.width, node.height),
504        Some(Layout::Column) => (node.height, node.width),
505        _ => (DSize::Hug, DSize::Hug),
506    };
507    if main == DSize::Fill {
508        style.flex_grow = 1.0;
509        style.flex_basis = Dimension::length(0.0);
510    }
511    if cross == DSize::Fill {
512        style.align_self = Some(AlignSelf::STRETCH);
513    }
514    // Every frame is the containing block of the boxes placed absolutely in
515    // it, as in Figma: a child at `x` and `y` is placed, sized and rounded
516    // against its own parent, never a farther ancestor.
517    style.position = Position::Relative;
518    if dir == Some(Layout::None) {
519        style.position = Position::Absolute;
520        style.inset = taffy::Rect {
521            left: LengthPercentageAuto::length(node.x),
522            top: LengthPercentageAuto::length(node.y),
523            right: LengthPercentageAuto::auto(),
524            bottom: LengthPercentageAuto::auto(),
525        };
526    }
527    style.flex_shrink = 0.0;
528    match node.layout {
529        Layout::Row => style.flex_direction = FlexDirection::Row,
530        Layout::Column => style.flex_direction = FlexDirection::Column,
531        Layout::None => {}
532    }
533    style.gap = Size {
534        width: LengthPercentage::length(node.gap),
535        height: LengthPercentage::length(node.gap),
536    };
537    let [t, r, b, l] = node.padding;
538    style.padding = taffy::Rect {
539        top: LengthPercentage::length(t),
540        right: LengthPercentage::length(r),
541        bottom: LengthPercentage::length(b),
542        left: LengthPercentage::length(l),
543    };
544    style.align_items = match node.align {
545        Align::Center => AlignItems::CENTER,
546        Align::End => AlignItems::FLEX_END,
547        _ => AlignItems::FLEX_START,
548    };
549    style.justify_content = match node.justify {
550        Align::Center => JustifyContent::CENTER,
551        Align::End => JustifyContent::FLEX_END,
552        Align::SpaceBetween => JustifyContent::SPACE_BETWEEN,
553        Align::Start => JustifyContent::FLEX_START,
554    };
555    style
556}
557
558#[cfg(test)]
559mod tests;
560
561/// Rounds to the nearest whole pixel, a half up, as taffy rounds.
562pub(super) fn snap(v: f32) -> f32 {
563    (v + 0.5).floor()
564}
565
566/// Where a node's parent is, for placing the node: the parent's box's
567/// top-left in whole pixels, and its unrounded one, which taffy's rounding
568/// pass measures the node from — every frame is the containing block of the
569/// boxes placed absolutely in it, so a box is always measured from its
570/// parent.
571#[derive(Clone, Copy, Debug, Default)]
572pub(super) struct Place {
573    pub(super) at: (f32, f32),
574    pub(super) raw: (f32, f32),
575}
576
577/// A node's box in whole pixels, in the design's coordinates, and the place
578/// its children are measured from — given its parent's. The tree is laid
579/// out unrounded, and only the boxes read are rounded, exactly as taffy's
580/// own pass rounds every box: each edge at its unrounded place measured from
581/// its parent — so the boxes are the very ones a tree rounded whole has.
582pub(super) fn placed(
583    tree: &TaffyTree<Option<Leaf>>,
584    id: NodeId,
585    parent: Place,
586    _root: bool,
587) -> (Frame, Place) {
588    let l = tree.unrounded_layout(id);
589    let base = parent.raw;
590    let (x, y) = (base.0 + l.location.x, base.1 + l.location.y);
591    let frame = Frame {
592        x: parent.at.0 + snap(x) - snap(base.0),
593        y: parent.at.1 + snap(y) - snap(base.1),
594        width: snap(x + l.size.width) - snap(x),
595        height: snap(y + l.size.height) - snap(y),
596    };
597    let place = Place {
598        at: (frame.x, frame.y),
599        raw: (x, y),
600    };
601    (frame, place)
602}