tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 707HTML 660Markdown 12TOML 62344 Sutichaykuna

⎇ float-layout tests/common/src/lib.rs Rust · 178 siq'ikuna · 6.4 KB Ñawpa willay Pi imata llamkarqan ch'uya

Add `taffy_test_helpers` crate (#755) 2a3e8476b · Nico Burns ·

1use taffy::{AvailableSpace, NodeId, Size, Style, TaffyTree};
2
3/// Creates a `TaffyTree` that uses `TestNodeContext`. The purpose of this function is
4/// to allow `TaffyTree` to be monomophised once in this crate rather than separately for
5/// each test crate.
6pub fn new_test_tree() -> TaffyTree<TestNodeContext> {
7    TaffyTree::new()
8}
9
10/// A sharednode context for tests which means that tests compiled with separate crates
11/// and using different styles of measure function. This saves on compile time when running tests.
12#[derive(Debug, Copy, Clone)]
13pub struct TestNodeContext {
14    /// How many times the measure function has been called
15    pub count: usize,
16    /// The measurement data to compute the intrinsic size from
17    pub measure_data: TestMeasureData,
18}
19
20impl TestNodeContext {
21    /// Create a new `TestNodeContext` from `TestMeasureData`
22    pub const fn new(measure_data: TestMeasureData) -> Self {
23        Self { count: 0, measure_data }
24    }
25
26    /// Create a `TestNodeContext` for a zero-sized node
27    pub const fn zero() -> Self {
28        Self::new(TestMeasureData::Zero)
29    }
30
31    /// Create a `TestNodeContext` for a fixed-sized node
32    pub const fn fixed(width: f32, height: f32) -> Self {
33        Self::new(TestMeasureData::Fixed(Size { width, height }))
34    }
35
36    /// Create a `TestNodeContext` for a node with a width and aspect-ratio
37    pub const fn aspect_ratio(width: f32, height_ratio: f32) -> Self {
38        let data = AspectRatioMeasureData { width, height_ratio };
39        Self::new(TestMeasureData::AspectRatio(data))
40    }
41
42    /// Create a `TestNodeContext` for a node with text using the Ahem font
43    pub const fn ahem_text(text_content: &'static str, writing_mode: WritingMode) -> Self {
44        let data = AhemTextMeasureData { text_content, writing_mode };
45        Self::new(TestMeasureData::AhemText(data))
46    }
47}
48
49/// The measurement data for the node
50#[derive(Debug, Copy, Clone)]
51pub enum TestMeasureData {
52    /// A zero-sized node
53    Zero,
54    /// A node with a fixed size
55    Fixed(Size<f32>),
56    /// A node with a fixed size
57    AspectRatio(AspectRatioMeasureData),
58    /// A node with text using the Ahem font
59    AhemText(AhemTextMeasureData),
60}
61
62/// A measure function for tests that works with `TestNodeContext`
63pub fn test_measure_function(
64    known_dimensions: Size<Option<f32>>,
65    available_space: Size<AvailableSpace>,
66    _node_id: NodeId,
67    context: Option<&mut TestNodeContext>,
68    _style: &Style,
69) -> Size<f32> {
70    if let Size { width: Some(width), height: Some(height) } = known_dimensions {
71        return Size { width, height };
72    }
73
74    let Some(context) = context else { return known_dimensions.map(|d| d.unwrap_or(0.0)) };
75
76    // Increment count
77    context.count += 1;
78
79    let compute_size = match &context.measure_data {
80        TestMeasureData::Zero => Size::ZERO,
81        TestMeasureData::Fixed(size) => *size,
82        TestMeasureData::AspectRatio(data) => data.measure(known_dimensions),
83        TestMeasureData::AhemText(data) => data.measure(known_dimensions, available_space),
84    };
85
86    Size {
87        width: known_dimensions.width.unwrap_or(compute_size.width),
88        height: known_dimensions.height.unwrap_or(compute_size.height),
89    }
90}
91
92/// Measure data for nodes that returns results based on an intrinsic aspect ratio
93#[derive(Debug, Copy, Clone)]
94pub struct AspectRatioMeasureData {
95    width: f32,
96    height_ratio: f32,
97}
98impl AspectRatioMeasureData {
99    fn measure(&self, known_dimensions: Size<Option<f32>>) -> Size<f32> {
100        let width = known_dimensions.width.unwrap_or(self.width);
101        let height = known_dimensions.height.unwrap_or(width * self.height_ratio);
102        Size { width, height }
103    }
104}
105
106/// Whether text is horizontal or vertical
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum WritingMode {
109    /// Horizontal text
110    Horizontal,
111    /// Vertical text
112    Vertical,
113}
114/// Measure data for nodes contain text using the Ahem testing font
115#[derive(Debug, Clone, Copy)]
116pub struct AhemTextMeasureData {
117    /// The text string
118    pub text_content: &'static str,
119    /// The writing mode
120    pub writing_mode: WritingMode,
121}
122impl AhemTextMeasureData {
123    fn measure(
124        &self,
125        known_dimensions: taffy::Size<Option<f32>>,
126        available_space: taffy::Size<taffy::AvailableSpace>,
127    ) -> taffy::Size<f32> {
128        use taffy::prelude::*;
129        use taffy::AbsoluteAxis;
130
131        const ZWS: char = '\u{200B}';
132        const H_WIDTH: f32 = 10.0;
133        const H_HEIGHT: f32 = 10.0;
134
135        let inline_axis = match self.writing_mode {
136            WritingMode::Horizontal => AbsoluteAxis::Horizontal,
137            WritingMode::Vertical => AbsoluteAxis::Vertical,
138        };
139        let block_axis = inline_axis.other_axis();
140        let lines: Vec<&str> = self.text_content.split(ZWS).collect();
141
142        if lines.is_empty() {
143            return Size::ZERO;
144        }
145
146        let min_line_length: usize = lines.iter().map(|line| line.len()).max().unwrap_or(0);
147        let max_line_length: usize = lines.iter().map(|line| line.len()).sum();
148        let inline_size = known_dimensions
149            .get_abs(inline_axis)
150            .unwrap_or_else(|| match available_space.get_abs(inline_axis) {
151                AvailableSpace::MinContent => min_line_length as f32 * H_WIDTH,
152                AvailableSpace::MaxContent => max_line_length as f32 * H_WIDTH,
153                AvailableSpace::Definite(inline_size) => inline_size.min(max_line_length as f32 * H_WIDTH),
154            })
155            .max(min_line_length as f32 * H_WIDTH);
156        let block_size = known_dimensions.get_abs(block_axis).unwrap_or_else(|| {
157            let inline_line_length = (inline_size / H_WIDTH).floor() as usize;
158            let mut line_count = 1;
159            let mut current_line_length = 0;
160            for line in &lines {
161                if current_line_length + line.len() > inline_line_length {
162                    if current_line_length > 0 {
163                        line_count += 1
164                    };
165                    current_line_length = line.len();
166                } else {
167                    current_line_length += line.len();
168                };
169            }
170            (line_count as f32) * H_HEIGHT
171        });
172
173        match self.writing_mode {
174            WritingMode::Horizontal => Size { width: inline_size, height: block_size },
175            WritingMode::Vertical => Size { width: block_size, height: inline_size },
176        }
177    }
178}