float_pigment_layout/
lib.rs

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