#![allow(dead_code)] use taffy::{AvailableSpace, NodeId, Size, Style}; /// A shared measure function for tests which means that tests compiled with separate crates /// and using different styles of measure function. This saves on compile time when running tests. #[derive(Debug, Copy, Clone)] pub struct TestNodeContext { /// How many times the measure function has been called pub count: usize, /// The measurement data to compute the intrinsic size from pub measure_data: TestMeasureData, } impl TestNodeContext { /// Create a new `TestNodeContext` from `TestMeasureData` pub const fn new(measure_data: TestMeasureData) -> Self { Self { count: 0, measure_data } } /// Create a `TestNodeContext` for a zero-sized node pub const fn zero() -> Self { Self::new(TestMeasureData::Zero) } /// Create a `TestNodeContext` for a fixed-sized node pub const fn fixed(size: Size) -> Self { Self::new(TestMeasureData::Fixed(size)) } /// Create a `TestNodeContext` for a node with a width and aspect-ratio pub const fn aspect_ratio(width: f32, height_ratio: f32) -> Self { let data = AspectRatioMeasureData { width, height_ratio }; Self::new(TestMeasureData::AspectRatio(data)) } /// Create a `TestNodeContext` for a node with text using the Ahem font pub const fn ahem_text(text_content: &'static str, writing_mode: WritingMode) -> Self { let data = AhemTextMeasureData { text_content, writing_mode }; Self::new(TestMeasureData::AhemText(data)) } } /// The measurement data for the node #[derive(Debug, Copy, Clone)] pub enum TestMeasureData { /// A zero-sized node Zero, /// A node with a fixed size Fixed(Size), /// A node with a fixed size AspectRatio(AspectRatioMeasureData), /// A node with text using the Ahem font AhemText(AhemTextMeasureData), } /// A measure function for tests that works with `TestNodeContext` pub fn test_measure_function( known_dimensions: Size>, available_space: Size, _node_id: NodeId, context: Option<&mut TestNodeContext>, _style: &Style, ) -> Size { if let Size { width: Some(width), height: Some(height) } = known_dimensions { return Size { width, height }; } let Some(context) = context else { return known_dimensions.map(|d| d.unwrap_or(0.0)) }; // Increment count context.count += 1; let compute_size = match &context.measure_data { TestMeasureData::Zero => Size::ZERO, TestMeasureData::Fixed(size) => *size, TestMeasureData::AspectRatio(data) => data.measure(known_dimensions), TestMeasureData::AhemText(data) => data.measure(known_dimensions, available_space), }; Size { width: known_dimensions.width.unwrap_or(compute_size.width), height: known_dimensions.height.unwrap_or(compute_size.height), } } /// Measure data for nodes that returns results based on an intrinsic aspect ratio #[derive(Debug, Copy, Clone)] pub struct AspectRatioMeasureData { width: f32, height_ratio: f32, } impl AspectRatioMeasureData { fn measure(&self, known_dimensions: Size>) -> Size { let width = known_dimensions.width.unwrap_or(self.width); let height = known_dimensions.height.unwrap_or(width * self.height_ratio); Size { width, height } } } /// Whether text is horizontal or vertical #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum WritingMode { /// Horizontal text Horizontal, /// Vertical text Vertical, } /// Measure data for nodes contain text using the Ahem testing font #[derive(Debug, Clone, Copy)] pub struct AhemTextMeasureData { /// The text string pub text_content: &'static str, /// The writing mode pub writing_mode: WritingMode, } impl AhemTextMeasureData { fn measure( &self, known_dimensions: taffy::Size>, available_space: taffy::Size, ) -> taffy::Size { use taffy::prelude::*; use taffy::AbsoluteAxis; const ZWS: char = '\u{200B}'; const H_WIDTH: f32 = 10.0; const H_HEIGHT: f32 = 10.0; let inline_axis = match self.writing_mode { WritingMode::Horizontal => AbsoluteAxis::Horizontal, WritingMode::Vertical => AbsoluteAxis::Vertical, }; let block_axis = inline_axis.other_axis(); let lines: Vec<&str> = self.text_content.split(ZWS).collect(); if lines.is_empty() { return Size::ZERO; } let min_line_length: usize = lines.iter().map(|line| line.len()).max().unwrap_or(0); let max_line_length: usize = lines.iter().map(|line| line.len()).sum(); let inline_size = known_dimensions .get_abs(inline_axis) .unwrap_or_else(|| match available_space.get_abs(inline_axis) { AvailableSpace::MinContent => min_line_length as f32 * H_WIDTH, AvailableSpace::MaxContent => max_line_length as f32 * H_WIDTH, AvailableSpace::Definite(inline_size) => inline_size.min(max_line_length as f32 * H_WIDTH), }) .max(min_line_length as f32 * H_WIDTH); let block_size = known_dimensions.get_abs(block_axis).unwrap_or_else(|| { let inline_line_length = (inline_size / H_WIDTH).floor() as usize; let mut line_count = 1; let mut current_line_length = 0; for line in &lines { if current_line_length + line.len() > inline_line_length { if current_line_length > 0 { line_count += 1 }; current_line_length = line.len(); } else { current_line_length += line.len(); }; } (line_count as f32) * H_HEIGHT }); match self.writing_mode { WritingMode::Horizontal => Size { width: inline_size, height: block_size }, WritingMode::Vertical => Size { width: block_size, height: inline_size }, } } }