use crate::gpu::{FontFamily, ShapedText, TextRun, TextStyle, TextSystem, TextTraits};
use crate::view::context::FrameState;
use rustc_hash::{FxHashMap, FxHasher};
use std::hash::{Hash, Hasher};
use std::rc::Rc;
use taffy::{AvailableSpace, NodeId};
#[derive(Clone, Debug)]
pub struct TextLeaf {
pub content: Rc<str>,
pub runs: Rc<[TextRun]>,
pub font_size: f32,
pub line_height: f32,
pub monospace: bool,
pub family: Option<FontFamily>,
pub wrap: bool,
pub traits: TextTraits,
}
#[derive(Clone, PartialEq, Eq, Hash)]
struct ShapeKey {
content: Rc<str>,
style_hash: u64,
width: Option<u32>,
}
impl TextLeaf {
fn style_hash(&self) -> u64 {
let mut hasher = FxHasher::default();
self.runs.hash(&mut hasher);
self.font_size.to_bits().hash(&mut hasher);
self.line_height.to_bits().hash(&mut hasher);
self.monospace.hash(&mut hasher);
self.family.hash(&mut hasher);
self.traits.hash(&mut hasher);
hasher.finish()
}
}
#[derive(Default)]
pub struct ShapeCache {
current: FxHashMap<ShapeKey, Rc<ShapedText>>,
previous: FxHashMap<ShapeKey, Rc<ShapedText>>,
}
impl ShapeCache {
pub fn begin_frame(&mut self) {
self.previous = std::mem::take(&mut self.current);
}
pub fn clear(&mut self) {
self.current = FxHashMap::default();
self.previous = FxHashMap::default();
}
pub fn shape(&mut self, text: &mut TextSystem, leaf: &TextLeaf, max_width: Option<f32>) -> Rc<ShapedText> {
let width = max_width.filter(|_| leaf.wrap).map(|width| width.max(0.0).ceil());
let key =
ShapeKey { content: leaf.content.clone(), style_hash: leaf.style_hash(), width: width.map(f32::to_bits) };
if let Some(shaped) = self.current.get(&key) {
return shaped.clone();
}
if let Some(shaped) = self.previous.remove(&key) {
self.current.insert(key, shaped.clone());
return shaped;
}
let style = TextStyle {
font_size: leaf.font_size,
line_height: leaf.line_height,
monospace: leaf.monospace,
family: leaf.family,
max_width: width,
traits: leaf.traits,
};
let shaped = Rc::new(text.shape_rich(&leaf.content, &leaf.runs, &style));
self.current.insert(key, shaped.clone());
shaped
}
}
fn measure(
text: &mut TextSystem,
cache: &mut ShapeCache,
leaf: &TextLeaf,
known: taffy::Size<Option<f32>>,
available: taffy::Size<AvailableSpace>,
) -> taffy::Size<f32> {
let max_width = known.width.or(match available.width {
AvailableSpace::Definite(width) => Some(width),
AvailableSpace::MinContent => Some(0.0),
AvailableSpace::MaxContent => None,
});
let shaped = cache.shape(text, leaf, max_width);
taffy::Size {
width: known.width.unwrap_or(shaped.size.width.0.ceil()),
height: known.height.unwrap_or(shaped.size.height.0),
}
}
pub fn compute_layout(
frame: &mut FrameState,
text: &mut TextSystem,
cache: &mut ShapeCache,
root: NodeId,
available: taffy::Size<AvailableSpace>,
) -> Result<(), taffy::TaffyError> {
frame.taffy.compute_layout_with_measure(root, available, |inputs, _node, context, style| {
taffy::compute_leaf_layout(
inputs,
style,
|_, _| 0.0,
|known, available| match context.as_deref() {
Some(leaf) => measure(text, cache, leaf, known, available),
None => taffy::Size::ZERO,
},
)
})
}