Skip to main content

float_pigment_layout/
lib.rs

1//! Layout engine with common CSS block, flexbox, and grid support.
2//!
3//! Note: it is not a full web-compatible layout engine because it supports a subset of web layout algorithms.
4//!
5//! Supported layout strategies:
6//!
7//! * `display: block`
8//! * `display: flex`
9//! * `display: grid`
10//! * `display: none`
11//! * `position: absolute`
12//! * with an optional external *text layout engine*:
13//!   * `display: inline`
14//!   * `display: inline-block`
15//!   * `display: inline-flex`
16//!
17//! ### Basic Usages
18//!
19//! This crate does not construct node trees itself. You should firstly:
20//!
21//! * write a node tree struct that implements `LayoutTreeNode`, each node should owns a `LayoutNode` inside it;
22//! * or use the *float-pigment-forest* crate (the `float_pigment_forest::node::Node` is a good implementation).
23//!
24//! Each tree node has a corresponding `LayoutNode`.
25//! Calls the `Layoutnode::update` or `Layoutnode::update_with_containing_size` of tree **root** node every time you need a new layout result.
26//! (The all results in the tree will be updated when `Layoutnode::update*` is called on the tree root.)
27//! Then you can read any result in any node with `LayoutNode::result*` and `LayoutNode::computed_style`.
28//!
29//! When any property of any node has been updated, calls the `LayoutNode::mark_dirty`.
30//! The next `Layoutnode::update*` call on the tree root will carefully read the new properties and update the results.
31//!
32//! ### About Text Layout
33//!
34//! Text layout means to compose text glyphs and other structures (images, inline-blocks, etc.) in lines.
35//! It is a complex problem and deeply coupled with system environment interfaces.
36//!
37//! This crate does not solve text layout problems. Thus by default it does not support `display: inline` and similar features.
38//! However, you are informed the inline layout parts so that you can implement a text layout engine to handle them.
39
40#![warn(missing_docs)]
41#![no_std]
42
43#[macro_use]
44extern crate alloc;
45#[allow(unused_imports)]
46#[macro_use]
47extern crate log;
48
49#[allow(unused_imports)]
50use alloc::{boxed::Box, vec::Vec};
51use core::{
52    cell::{RefCell, RefMut},
53    fmt,
54};
55
56use float_pigment_css::typing::{
57    AlignContent, AlignItems, AlignSelf, BoxSizing, Direction, Display, FlexDirection, FlexWrap,
58    GridAutoFlow, JustifyContent, JustifyItems, JustifySelf, Position, TextAlign, WritingMode,
59};
60
61pub use unit::SizingMode;
62
63mod algo;
64mod cache;
65mod special_positioned;
66mod types;
67mod unit;
68
69pub(crate) use cache::*;
70pub use special_positioned::is_independent_positioning;
71pub(crate) use special_positioned::*;
72pub use types::*;
73pub(crate) use unit::*;
74
75/// A type to get screen size.
76#[allow(missing_docs)]
77pub trait ScreenQuery<L: LengthNum> {
78    fn screen_width(&self) -> L;
79    fn screen_height(&self) -> L;
80}
81
82/// The main tree node type.
83///
84/// It should be implemented by a tree implementation and then the layout algorithms can run on it.
85pub trait LayoutTreeNode: Sized {
86    /// The main length type.
87    ///
88    /// It can simply be an `f32`.
89    /// Sometimes float-point is not accurate enough. You can use the fixed-point types provided by the `fixed` crate.
90    type Length: LengthNum;
91
92    /// A custom length type used in `DefLength::Custom`.
93    ///
94    /// If you do not need it, simply use `i32`.
95    type LengthCustom: PartialEq + core::fmt::Debug + Clone;
96
97    /// A helper type for tree traversal.
98    ///
99    /// Hint: consider implement `LayoutTreeVisitor` for `Self` if the `Self` type can do tree traversal.
100    type TreeVisitor: LayoutTreeVisitor<Self>;
101
102    /// The layout styles of the current node.
103    type Style: LayoutStyle<Self::Length, Self::LengthCustom>;
104
105    /// A helper type to represent the current node as an inline node.
106    ///
107    /// This type is intended to be used in the text layout engine.
108    /// You can write a void implementation if you do not need inline layout.
109    type InlineUnit: InlineUnit<Self, Env = Self::Env>;
110
111    /// A helper type to measure inline node.
112    ///
113    /// This type is intended to be used in the text layout engine.
114    /// You can write a void implementation if you do not need inline layout.
115    type InlineMeasure: InlineMeasure<Self, InlineUnit = Self::InlineUnit, Env = Self::Env>;
116
117    /// Some custom environment data.
118    type Env: ScreenQuery<Self::Length>;
119
120    /// Get a reference to the `LayoutNode` of the current tree node.
121    fn layout_node(&self) -> &LayoutNode<Self>;
122
123    /// Get a helper for tree traversal.
124    fn tree_visitor(&self) -> &Self::TreeVisitor;
125
126    /// Get the styles of the current tree node.
127    fn style(&self) -> &Self::Style;
128
129    /// Resolve a `Length::Custom` value.
130    ///
131    /// If you do not use `Length::Custom` values, simply `unreachable!()`.
132    fn resolve_custom_length(
133        &self,
134        custom: &Self::LengthCustom,
135        owner: Self::Length,
136    ) -> Self::Length;
137
138    /// Returns if the node is a "measure" node.
139    ///
140    /// This means this node has a dedicated way to calculate its size (normally `true` for leaf nodes).
141    /// For example, images has a natural width/height -
142    /// its size should be calculated from the image natural size.
143    /// In this case, the `LayoutTreeNode::measure_block_size` will be called.
144    fn should_measure(&self, env: &mut Self::Env) -> bool;
145
146    /// Measure a size for the current tree node.
147    ///
148    /// Only be called if the node is a "measure" node, a.k.a. `LayoutTreeNode::should_measure` returns `true`.
149    /// The `env` is the one passed from the `Layoutnode::update*` call.
150    /// If `update_position` is set, then the returned result will be used as the new layout result.
151    #[allow(clippy::too_many_arguments)]
152    fn measure_block_size(
153        &self,
154        env: &mut Self::Env,
155        req_size: OptionSize<Self::Length>,
156        min: Size<Self::Length>,
157        max: Size<Self::Length>,
158        max_content: OptionSize<Self::Length>,
159        update_position: bool,
160        sizing_mode: SizingMode,
161    ) -> MeasureResult<Self::Length>;
162
163    /// Convert the current node to a `Self::InlineUnit`.
164    ///
165    /// The returned value will be passed to `InlineMeasure::block_size`.
166    /// This is intended for the text layout engine.
167    #[allow(clippy::too_many_arguments)]
168    fn measure_inline_unit(
169        &self,
170        env: &mut Self::Env,
171        req_size: OptionSize<Self::Length>,
172        min: Size<Self::Length>,
173        max: Size<Self::Length>,
174        max_content: OptionSize<Self::Length>,
175        sizing_mode: SizingMode,
176    ) -> MeasureResult<Self::Length>;
177
178    /// A notifier that the layout size of itself (or any node in the subtree) has been re-evaluated.
179    ///
180    /// Note that the position is the combination of size and origin.
181    /// This call indicates that the size may be changed and the origin is still undetermined.
182    fn size_updated(
183        &self,
184        _env: &mut Self::Env,
185        _size: Size<Self::Length>,
186        _computed_style: &ComputedStyle<Self::Length>,
187    ) {
188    }
189}
190
191/// A helper type for tree traversal.
192pub trait LayoutTreeVisitor<T: LayoutTreeNode> {
193    /// Get the parent node.
194    fn parent(&self) -> Option<&T>;
195
196    /// Get child nodes.
197    fn for_each_child<'a, 'b: 'a, F>(&'b self, f: F)
198    where
199        F: FnMut(&'a T, usize),
200        T: 'a;
201
202    /// Get the count of child nodes.
203    fn children_len(&self) -> usize;
204
205    /// Get the specified child.
206    fn child_at(&self, index: usize) -> Option<&T>;
207
208    /// A notifier that the node has been marked dirty.
209    ///
210    /// When `LayoutNode::mark_dirty` is called, some related nodes (e.g. the ancestors) are also marked dirty automatically.
211    /// These calls tells which nodes are marked dirty.
212    fn dirty_marked(&self) {}
213
214    ///  Get children iterator.
215    fn children_iter<'a, 'b: 'a>(&'b self) -> impl Iterator<Item = &'a T>
216    where
217        T: 'a;
218}
219
220/// The styles of a tree node.
221///
222/// The values are similar to corresponding CSS properties.
223#[allow(missing_docs)]
224pub trait LayoutStyle<L: LengthNum, T: PartialEq + Clone = i32> {
225    fn display(&self) -> Display;
226    fn position(&self) -> Position;
227    fn direction(&self) -> Direction;
228    fn writing_mode(&self) -> WritingMode;
229    fn flex_direction(&self) -> FlexDirection;
230    fn flex_wrap(&self) -> FlexWrap;
231    fn align_items(&self) -> AlignItems;
232    fn align_self(&self) -> AlignSelf;
233    fn align_content(&self) -> AlignContent;
234    fn justify_content(&self) -> JustifyContent;
235    fn left(&self) -> DefLength<L, T>;
236    fn right(&self) -> DefLength<L, T>;
237    fn top(&self) -> DefLength<L, T>;
238    fn bottom(&self) -> DefLength<L, T>;
239    fn border_left(&self) -> DefLength<L, T>;
240    fn border_right(&self) -> DefLength<L, T>;
241    fn border_top(&self) -> DefLength<L, T>;
242    fn border_bottom(&self) -> DefLength<L, T>;
243    fn margin_left(&self) -> DefLength<L, T>;
244    fn margin_right(&self) -> DefLength<L, T>;
245    fn margin_top(&self) -> DefLength<L, T>;
246    fn margin_bottom(&self) -> DefLength<L, T>;
247    fn padding_left(&self) -> DefLength<L, T>;
248    fn padding_right(&self) -> DefLength<L, T>;
249    fn padding_top(&self) -> DefLength<L, T>;
250    fn padding_bottom(&self) -> DefLength<L, T>;
251    fn flex_grow(&self) -> f32;
252    fn flex_shrink(&self) -> f32;
253    fn flex_basis(&self) -> DefLength<L, T>;
254    fn width(&self) -> DefLength<L, T>;
255    fn height(&self) -> DefLength<L, T>;
256    fn min_width(&self) -> DefLength<L, T>;
257    fn min_height(&self) -> DefLength<L, T>;
258    fn max_width(&self) -> DefLength<L, T>;
259    fn max_height(&self) -> DefLength<L, T>;
260    fn aspect_ratio(&self) -> Option<f32>;
261    fn box_sizing(&self) -> BoxSizing;
262    fn order(&self) -> i32;
263    fn text_align(&self) -> TextAlign {
264        TextAlign::Start
265    }
266    fn column_gap(&self) -> DefLength<L, T> {
267        DefLength::Undefined
268    }
269    fn row_gap(&self) -> DefLength<L, T> {
270        DefLength::Undefined
271    }
272    fn grid_template_rows(&self) -> LayoutGridTemplate<L, T> {
273        LayoutGridTemplate::None
274    }
275    fn grid_template_columns(&self) -> LayoutGridTemplate<L, T> {
276        LayoutGridTemplate::None
277    }
278    fn grid_auto_flow(&self) -> GridAutoFlow {
279        GridAutoFlow::Row
280    }
281    /// CSS Grid §7.6: grid-auto-rows
282    /// <https://www.w3.org/TR/css-grid-1/#auto-tracks>
283    fn grid_auto_rows(&self) -> LayoutGridAuto<L, T> {
284        LayoutGridAuto::default()
285    }
286    /// CSS Grid §7.6: grid-auto-columns
287    /// <https://www.w3.org/TR/css-grid-1/#auto-tracks>
288    fn grid_auto_columns(&self) -> LayoutGridAuto<L, T> {
289        LayoutGridAuto::default()
290    }
291    fn justify_items(&self) -> JustifyItems {
292        JustifyItems::Stretch
293    }
294    fn justify_self(&self) -> JustifySelf {
295        JustifySelf::Auto
296    }
297}
298
299/// The layout information of a tree node.
300pub struct LayoutNode<T: LayoutTreeNode> {
301    unit: RefCell<LayoutUnit<T>>,
302}
303
304impl<T: LayoutTreeNode> fmt::Debug for LayoutNode<T> {
305    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
306        Ok(())
307    }
308}
309
310impl<T: LayoutTreeNode> Default for LayoutNode<T> {
311    fn default() -> Self {
312        Self {
313            unit: RefCell::new(LayoutUnit::new()),
314        }
315    }
316}
317
318impl<T: LayoutTreeNode> LayoutNode<T> {
319    /// Create a new layout node.
320    #[inline]
321    pub fn new() -> Self {
322        Self::default()
323    }
324
325    /// Informs the node styles been changed.
326    #[inline]
327    pub fn mark_dirty(&self, node: &T::TreeVisitor) -> bool {
328        let ret = self.unit.borrow_mut().mark_dirty(node);
329        node.dirty_marked();
330        ret
331    }
332
333    /// Get the size and position results (border rect).
334    #[inline]
335    pub fn result(&self) -> Rect<T::Length> {
336        self.unit.borrow().result()
337    }
338
339    /// Get the result padding rect.
340    #[inline]
341    pub fn result_padding_rect(&self) -> Rect<T::Length> {
342        self.unit.borrow().result_padding_rect()
343    }
344
345    /// Get the result content rect.
346    #[inline]
347    pub fn result_content_rect(&self) -> Rect<T::Length> {
348        self.unit.borrow().result_content_rect()
349    }
350
351    /// Get the computed styles, such as margins, borders, and paddings.
352    #[inline]
353    pub fn computed_style(&self) -> ComputedStyle<T::Length> {
354        self.unit.borrow().computed_style()
355    }
356
357    /// Get the layout algorithm used for this node.
358    #[inline]
359    pub fn layout_algorithm(&self) -> LayoutAlgorithm {
360        self.unit.borrow().layout_algorithm
361    }
362
363    /// Check all nodes that has been `mark_dirty`, and update the layout results of the whole tree.
364    ///
365    /// Should only be called on the tree root node.
366    /// The `env` will be received in measure functions.
367    #[inline]
368    pub fn update(&self, env: &mut T::Env, node: &T, available_size: OptionSize<T::Length>) {
369        self.unit.borrow_mut().compute(env, node, available_size)
370    }
371
372    /// Check all nodes that has been `mark_dirty`, and update the layout results of the whole tree with container size given.
373    ///
374    /// Should only be called on the tree root node.
375    /// The `env` will be received in measure functions.
376    /// `available_size` is the available size for the root node.
377    /// `containing_size` is the size of the viewport.
378    #[inline]
379    pub fn update_with_containing_size(
380        &self,
381        env: &mut T::Env,
382        node: &T,
383        available_size: OptionSize<T::Length>,
384        containing_size: OptionSize<T::Length>,
385    ) {
386        self.unit.borrow_mut().compute_with_containing_size(
387            env,
388            node,
389            available_size,
390            containing_size,
391        )
392    }
393
394    #[inline]
395    pub(crate) fn unit(&self) -> RefMut<'_, LayoutUnit<T>> {
396        self.unit.borrow_mut()
397    }
398}
399
400/// A helper type to measure inline nodes.
401///
402/// This should be implemented by the text layout engine.
403pub trait InlineMeasure<T: LayoutTreeNode> {
404    /// A helper type to represent the current node as an inline node.
405    type InlineUnit: InlineUnit<T, Env = Self::Env>;
406
407    /// Some custom environment data.
408    type Env;
409
410    /// Measure a series of inline nodes.
411    ///
412    /// Continous inline nodes will be collected together, and treat as a whole block.
413    /// The text layout engine should returns:
414    /// * the total size;
415    /// * the position and detailed measure results for each inline node.
416    ///
417    /// The `env` will be received in measure functions.
418    /// The `block_node` is the parent of these inline nodes.
419    /// If `update_position` is set, then the returned result will be used as the new layout result.
420    #[allow(clippy::type_complexity)]
421    fn block_size(
422        env: &mut Self::Env,
423        block_node: &T,
424        inline_nodes: Vec<InlineUnitMetadata<T>>,
425        req_size: OptionSize<T::Length>,
426        max_content_with_max_size: OptionSize<T::Length>,
427        update_position: bool,
428        sizing_mode: SizingMode,
429    ) -> (
430        Size<T::Length>,
431        Vec<(Point<T::Length>, MeasureResult<T::Length>)>,
432    );
433}
434
435/// Inline unit with some metadata.
436#[allow(missing_docs)]
437#[derive(Debug)]
438pub struct InlineUnitMetadata<T: LayoutTreeNode> {
439    pub unit: T::InlineUnit,
440    pub margin: EdgeOption<T::Length>,
441}
442
443/// A helper type as the inline form of a tree node.
444pub trait InlineUnit<T: LayoutTreeNode> {
445    /// Some custom environment data.
446    type Env;
447
448    /// Construct from a tree node with specified size and baseline information.
449    fn new(env: &mut Self::Env, node: &T, result: MeasureResult<T::Length>) -> Self;
450}
451
452/// The result of the measure function.
453#[derive(Debug, Clone, Copy, PartialEq)]
454pub struct MeasureResult<L: LengthNum> {
455    /// The size that occupied.
456    pub size: Size<L>,
457
458    /// The first baseline position.
459    pub first_baseline_ascent: Vector<L>,
460
461    /// The last baseline position.
462    pub last_baseline_ascent: Vector<L>,
463}
464
465/// The computed `margin` `padding` `border` width.
466#[allow(missing_docs)]
467#[derive(Debug, Clone, Copy, PartialEq)]
468pub struct ComputedStyle<L: LengthNum> {
469    pub margin: Edge<L>,
470    pub padding: Edge<L>,
471    pub border: Edge<L>,
472}
473
474impl<L: LengthNum> Default for ComputedStyle<L> {
475    fn default() -> Self {
476        Self {
477            margin: Edge {
478                left: L::zero(),
479                right: L::zero(),
480                top: L::zero(),
481                bottom: L::zero(),
482            },
483            border: Edge {
484                left: L::zero(),
485                right: L::zero(),
486                top: L::zero(),
487                bottom: L::zero(),
488            },
489            padding: Edge {
490                left: L::zero(),
491                right: L::zero(),
492                top: L::zero(),
493                bottom: L::zero(),
494            },
495        }
496    }
497}
498
499/// The supported layout algorithms.
500#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
501pub enum LayoutAlgorithm {
502    /// Used when the node has no layout, e.g. `display: none`.
503    None,
504
505    /// Used when the node is an inline node.
506    ///
507    /// `display: inline` does not guarantee this.
508    /// For example, if the parent node has `display: flex`,
509    /// this node will not be an inline node.
510    Inline,
511
512    /// Used when the node measured as an inline node.
513    InlineMeasure,
514
515    /// Used when the node is a block node.
516    Block,
517
518    /// Used when the node is an inline-block node.
519    InlineBlock,
520
521    /// Used when the node measured as a block node.
522    BlockMeasure,
523
524    /// Used when the node is a flex container, e.g. `display: flex`.
525    Flex,
526
527    /// Used when the node is an inline-flex node.
528    InlineFlex,
529
530    /// Used when the node is a grid container, e.g. `display: grid`.
531    Grid,
532
533    /// Used when the node is an inline-grid node.
534    InlineGrid,
535}