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}