tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 788HTML 740Markdown 12TOML 62536 Sutichaykuna

⎇ main tests/common/src/lib.rs Rust · 193 siq'ikuna · 6.7 KB Ñawpa willay Pi imata llamkarqan ch'uya

Represent generated tests as XML rather than Rust code (#926) 420184d71 · 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, 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: String, 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, 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, Default)]
108pub enum WritingMode {
109    /// Horizontal text
110    #[default]
111    Horizontal,
112    /// Vertical text
113    Vertical,
114}
115
116impl core::str::FromStr for WritingMode {
117    type Err = ();
118    fn from_str(s: &str) -> Result<Self, Self::Err> {
119        if s.contains("vertical") {
120            Ok(Self::Vertical)
121        } else if s.contains("horizontal") {
122            Ok(Self::Horizontal)
123        } else {
124            Err(())
125        }
126    }
127}
128
129/// Measure data for nodes contain text using the Ahem testing font
130#[derive(Debug, Clone)]
131pub struct AhemTextMeasureData {
132    /// The text string
133    pub text_content: String,
134    /// The writing mode
135    pub writing_mode: WritingMode,
136}
137impl AhemTextMeasureData {
138    fn measure(
139        &self,
140        known_dimensions: taffy::Size<Option<f32>>,
141        available_space: taffy::Size<taffy::AvailableSpace>,
142    ) -> taffy::Size<f32> {
143        use taffy::prelude::*;
144        use taffy::AbsoluteAxis;
145
146        const ZWS: char = '\u{200B}';
147        const H_WIDTH: f32 = 10.0;
148        const H_HEIGHT: f32 = 10.0;
149
150        let inline_axis = match self.writing_mode {
151            WritingMode::Horizontal => AbsoluteAxis::Horizontal,
152            WritingMode::Vertical => AbsoluteAxis::Vertical,
153        };
154        let block_axis = inline_axis.other_axis();
155        let lines: Vec<&str> = self.text_content.split(ZWS).collect();
156
157        if lines.is_empty() {
158            return Size::ZERO;
159        }
160
161        let min_line_length: usize = lines.iter().map(|line| line.len()).max().unwrap_or(0);
162        let max_line_length: usize = lines.iter().map(|line| line.len()).sum();
163        let inline_size = known_dimensions
164            .get_abs(inline_axis)
165            .unwrap_or_else(|| match available_space.get_abs(inline_axis) {
166                AvailableSpace::MinContent => min_line_length as f32 * H_WIDTH,
167                AvailableSpace::MaxContent => max_line_length as f32 * H_WIDTH,
168                AvailableSpace::Definite(inline_size) => inline_size.min(max_line_length as f32 * H_WIDTH),
169            })
170            .max(min_line_length as f32 * H_WIDTH);
171        let block_size = known_dimensions.get_abs(block_axis).unwrap_or_else(|| {
172            let inline_line_length = (inline_size / H_WIDTH).floor() as usize;
173            let mut line_count = 1;
174            let mut current_line_length = 0;
175            for line in &lines {
176                if current_line_length + line.len() > inline_line_length {
177                    if current_line_length > 0 {
178                        line_count += 1
179                    };
180                    current_line_length = line.len();
181                } else {
182                    current_line_length += line.len();
183                };
184            }
185            (line_count as f32) * H_HEIGHT
186        });
187
188        match self.writing_mode {
189            WritingMode::Horizontal => Size { width: inline_size, height: block_size },
190            WritingMode::Vertical => Size { width: block_size, height: inline_size },
191        }
192    }
193}