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 ) -> MeasureResult<Self::Length>;
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 /// A notifier that the node has been marked dirty.
204 ///
205 /// When `LayoutNode::mark_dirty` is called, some related nodes (e.g. the ancestors) are also marked dirty automatically.
206 /// These calls tells which nodes are marked dirty.
207 fn dirty_marked(&self) {}
208}
209
210/// The styles of a tree node.
211///
212/// The values are similar to corresponding CSS properties.
213#[allow(missing_docs)]
214pub trait LayoutStyle<L: LengthNum, T: PartialEq = i32> {
215 fn display(&self) -> Display;
216 fn position(&self) -> Position;
217 fn direction(&self) -> Direction;
218 fn writing_mode(&self) -> WritingMode;
219 fn flex_direction(&self) -> FlexDirection;
220 fn flex_wrap(&self) -> FlexWrap;
221 fn align_items(&self) -> AlignItems;
222 fn align_self(&self) -> AlignSelf;
223 fn align_content(&self) -> AlignContent;
224 fn justify_content(&self) -> JustifyContent;
225 fn left(&self) -> DefLength<L, T>;
226 fn right(&self) -> DefLength<L, T>;
227 fn top(&self) -> DefLength<L, T>;
228 fn bottom(&self) -> DefLength<L, T>;
229 fn border_left(&self) -> DefLength<L, T>;
230 fn border_right(&self) -> DefLength<L, T>;
231 fn border_top(&self) -> DefLength<L, T>;
232 fn border_bottom(&self) -> DefLength<L, T>;
233 fn margin_left(&self) -> DefLength<L, T>;
234 fn margin_right(&self) -> DefLength<L, T>;
235 fn margin_top(&self) -> DefLength<L, T>;
236 fn margin_bottom(&self) -> DefLength<L, T>;
237 fn padding_left(&self) -> DefLength<L, T>;
238 fn padding_right(&self) -> DefLength<L, T>;
239 fn padding_top(&self) -> DefLength<L, T>;
240 fn padding_bottom(&self) -> DefLength<L, T>;
241 fn flex_grow(&self) -> f32;
242 fn flex_shrink(&self) -> f32;
243 fn flex_basis(&self) -> DefLength<L, T>;
244 fn width(&self) -> DefLength<L, T>;
245 fn height(&self) -> DefLength<L, T>;
246 fn min_width(&self) -> DefLength<L, T>;
247 fn min_height(&self) -> DefLength<L, T>;
248 fn max_width(&self) -> DefLength<L, T>;
249 fn max_height(&self) -> DefLength<L, T>;
250 fn aspect_ratio(&self) -> Option<f32>;
251 fn box_sizing(&self) -> BoxSizing;
252 fn order(&self) -> i32;
253 fn text_align(&self) -> TextAlign {
254 TextAlign::Start
255 }
256 fn column_gap(&self) -> DefLength<L, T> {
257 DefLength::Undefined
258 }
259 fn row_gap(&self) -> DefLength<L, T> {
260 DefLength::Undefined
261 }
262}
263
264/// The layout information of a tree node.
265pub struct LayoutNode<T: LayoutTreeNode> {
266 unit: RefCell<LayoutUnit<T>>,
267}
268
269impl<T: LayoutTreeNode> fmt::Debug for LayoutNode<T> {
270 fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
271 Ok(())
272 }
273}
274
275impl<T: LayoutTreeNode> Default for LayoutNode<T> {
276 fn default() -> Self {
277 Self {
278 unit: RefCell::new(LayoutUnit::new()),
279 }
280 }
281}
282
283impl<T: LayoutTreeNode> LayoutNode<T> {
284 /// Create a new layout node.
285 #[inline]
286 pub fn new() -> Self {
287 Self::default()
288 }
289
290 /// Informs the node styles been changed.
291 #[inline]
292 pub fn mark_dirty(&self, node: &T::TreeVisitor) -> bool {
293 let ret = self.unit.borrow_mut().mark_dirty(node);
294 node.dirty_marked();
295 ret
296 }
297
298 /// Get the size and position results (border rect).
299 #[inline]
300 pub fn result(&self) -> Rect<T::Length> {
301 self.unit.borrow().result()
302 }
303
304 /// Get the result padding rect.
305 #[inline]
306 pub fn result_padding_rect(&self) -> Rect<T::Length> {
307 self.unit.borrow().result_padding_rect()
308 }
309
310 /// Get the result content rect.
311 #[inline]
312 pub fn result_content_rect(&self) -> Rect<T::Length> {
313 self.unit.borrow().result_content_rect()
314 }
315
316 /// Get the computed styles, such as margins, borders, and paddings.
317 #[inline]
318 pub fn computed_style(&self) -> ComputedStyle<T::Length> {
319 self.unit.borrow().computed_style()
320 }
321
322 /// Check all nodes that has been `mark_dirty`, and update the layout results of the whole tree.
323 ///
324 /// Should only be called on the tree root node.
325 /// The `env` will be received in measure functions.
326 #[inline]
327 pub fn update(&self, env: &mut T::Env, node: &T, available_size: OptionSize<T::Length>) {
328 self.unit.borrow_mut().compute(env, node, available_size)
329 }
330
331 /// Check all nodes that has been `mark_dirty`, and update the layout results of the whole tree with container size given.
332 ///
333 /// Should only be called on the tree root node.
334 /// The `env` will be received in measure functions.
335 /// `available_size` is the available size for the root node.
336 /// `containing_size` is the size of the viewport.
337 #[inline]
338 pub fn update_with_containing_size(
339 &self,
340 env: &mut T::Env,
341 node: &T,
342 available_size: OptionSize<T::Length>,
343 containing_size: OptionSize<T::Length>,
344 ) {
345 self.unit.borrow_mut().compute_with_containing_size(
346 env,
347 node,
348 available_size,
349 containing_size,
350 )
351 }
352
353 #[inline]
354 pub(crate) fn unit(&self) -> RefMut<LayoutUnit<T>> {
355 self.unit.borrow_mut()
356 }
357}
358
359/// A helper type to measure inline nodes.
360///
361/// This should be implemented by the text layout engine.
362pub trait InlineMeasure<T: LayoutTreeNode> {
363 /// A helper type to represent the current node as an inline node.
364 type InlineUnit: InlineUnit<T, Env = Self::Env>;
365
366 /// Some custom environment data.
367 type Env;
368
369 /// Measure a series of inline nodes.
370 ///
371 /// Continous inline nodes will be collected together, and treat as a whole block.
372 /// The text layout engine should returns:
373 /// * the total size;
374 /// * the position and detailed measure results for each inline node.
375 ///
376 /// The `env` will be received in measure functions.
377 /// The `block_node` is the parent of these inline nodes.
378 /// If `update_position` is set, then the returned result will be used as the new layout result.
379 #[allow(clippy::type_complexity)]
380 fn block_size(
381 env: &mut Self::Env,
382 block_node: &T,
383 inline_nodes: Vec<InlineUnitMetadata<T>>,
384 req_size: OptionSize<T::Length>,
385 max_content_with_max_size: OptionSize<T::Length>,
386 update_position: bool,
387 ) -> (
388 Size<T::Length>,
389 Vec<(Point<T::Length>, MeasureResult<T::Length>)>,
390 );
391}
392
393/// Inline unit with some metadata.
394#[allow(missing_docs)]
395#[derive(Debug)]
396pub struct InlineUnitMetadata<T: LayoutTreeNode> {
397 pub unit: T::InlineUnit,
398 pub margin: EdgeOption<T::Length>,
399}
400
401/// A helper type as the inline form of a tree node.
402pub trait InlineUnit<T: LayoutTreeNode> {
403 /// Some custom environment data.
404 type Env;
405
406 /// Construct from a tree node with specified size and baseline information.
407 fn new(env: &mut Self::Env, node: &T, result: MeasureResult<T::Length>) -> Self;
408}
409
410/// The result of the measure function.
411#[derive(Debug, Clone, Copy, PartialEq)]
412pub struct MeasureResult<L: LengthNum> {
413 /// The size that occupied.
414 pub size: Size<L>,
415
416 /// The first baseline position.
417 pub first_baseline_ascent: Vector<L>,
418
419 /// The last baseline position.
420 pub last_baseline_ascent: Vector<L>,
421}
422
423/// The computed `margin` `padding` `border` width.
424#[allow(missing_docs)]
425#[derive(Debug, Clone, Copy, PartialEq)]
426pub struct ComputedStyle<L: LengthNum> {
427 pub margin: Edge<L>,
428 pub padding: Edge<L>,
429 pub border: Edge<L>,
430}
431
432impl<L: LengthNum> Default for ComputedStyle<L> {
433 fn default() -> Self {
434 Self {
435 margin: Edge {
436 left: L::zero(),
437 right: L::zero(),
438 top: L::zero(),
439 bottom: L::zero(),
440 },
441 border: Edge {
442 left: L::zero(),
443 right: L::zero(),
444 top: L::zero(),
445 bottom: L::zero(),
446 },
447 padding: Edge {
448 left: L::zero(),
449 right: L::zero(),
450 top: L::zero(),
451 bottom: L::zero(),
452 },
453 }
454 }
455}