gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1111HTML 1039Markdown 12TOML 94524 Sutichaykuna

⎇ main src/test.rs Rust · 174 siq'ikuna · 6.1 KB Ñawpa willay Pi imata llamkarqan ch'uya

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

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