use std::rc::Rc;
use crate::{
Brush, DrawPrimitive, Point, Rect,
typography::{
DrawTextMeasurer, DrawTextStyle, MeasuredText, TextAlign, TextMeasurement,
TextVerticalAlign, estimate_text_measurement,
},
};
#[derive(Clone, Debug, PartialEq)]
pub struct TextPrimitive {
pub rect: Rect,
pub text: Rc<str>,
pub style: DrawTextStyle,
pub brush: Brush,
pub layout: Option<crate::TextLayoutHandle>,
}
pub(crate) fn shared_text_str(text: &str) -> Rc<str> {
use std::hash::{Hash, Hasher};
let mut hasher = crate::FxHasher::default();
text.hash(&mut hasher);
crate::thread_state::with(|state| {
state.shared_texts.borrow_mut().with(
hasher.finish(),
|shared| &**shared == text,
|| Rc::from(text),
Rc::clone,
)
})
}
pub fn align_text_block(rect: Rect, measurement: TextMeasurement, style: &DrawTextStyle) -> Point {
let x = match style.align {
TextAlign::Left => rect.x,
TextAlign::Center => rect.x + (rect.width - measurement.size.width) * 0.5,
TextAlign::Right => rect.x + rect.width - measurement.size.width,
};
let y = match style.vertical_align {
TextVerticalAlign::Top => rect.y,
TextVerticalAlign::Center => rect.y + (rect.height - measurement.size.height) * 0.5,
TextVerticalAlign::Bottom => rect.y + rect.height - measurement.size.height,
TextVerticalAlign::Baseline => rect.y - measurement.first_baseline,
};
Point::new(x, y)
}
#[derive(Clone, Copy)]
pub(crate) enum TextBlockAt {
Aligned(Rect),
From(Point),
}
pub(crate) fn measured_text(
measurer: Option<&dyn DrawTextMeasurer>,
text: &str,
style: &DrawTextStyle,
) -> MeasuredText {
match measurer {
Some(measurer) => measurer.measure_text(text, style),
None => MeasuredText {
measurement: estimate_text_measurement(text, style),
layout: None,
},
}
}
pub(crate) fn text_primitive(
measurer: Option<&dyn DrawTextMeasurer>,
at: TextBlockAt,
brush: Brush,
text: &str,
style: &DrawTextStyle,
) -> Option<DrawPrimitive> {
if text.is_empty() || !brush.paints() {
return None;
}
let measured = measured_text(measurer, text, style);
let size = measured.measurement.size;
if !(size.width > 0.0 && size.height > 0.0) {
return None;
}
let (origin, style) = match at {
TextBlockAt::Aligned(rect) => (
align_text_block(rect, measured.measurement, style),
style.clone(),
),
TextBlockAt::From(top_left) => (
top_left,
DrawTextStyle {
align: TextAlign::Left,
vertical_align: TextVerticalAlign::Top,
..style.clone()
},
),
};
if !origin.x.is_finite() || !origin.y.is_finite() {
return None;
}
Some(DrawPrimitive::Text(Box::new(TextPrimitive {
rect: Rect::from_origin_size(origin, size),
text: shared_text_str(text),
style,
brush,
layout: measured.layout,
})))
}