use std::{rc::Rc, sync::Arc};
use cranpose_ui_graphics::{
DrawTextMeasurer, DrawTextStyle, FontStyle as DrawFontStyle, MeasuredText, TextMeasurement,
estimate_text_measurement,
};
use super::{
font::{FontFamily, FontStyle, FontWeight},
line_box::LineBox,
recent_values::RecentValues,
style::{SpanStyle, TextStyle},
unit::TextUnit,
};
pub fn text_style_for_draw_style(style: &DrawTextStyle) -> TextStyle {
let mut span_style = SpanStyle {
font_size: TextUnit::Sp(style.resolved_font_size()),
font_weight: Some(FontWeight::new(style.font_weight.value())),
font_style: Some(match style.font_style {
DrawFontStyle::Normal => FontStyle::Normal,
DrawFontStyle::Italic | DrawFontStyle::Oblique => FontStyle::Italic,
}),
..SpanStyle::default()
};
if let Some(family) = &style.font_family {
span_style.font_family = Some(FontFamily::from_name(family));
}
let letter_spacing = style.resolved_letter_spacing();
if letter_spacing != 0.0 {
span_style.letter_spacing = TextUnit::Sp(letter_spacing);
}
let mut text_style = TextStyle::from_span_style(span_style);
if let Some(line_height) = style.line_height
&& line_height.is_finite()
&& line_height > 0.0
{
text_style.paragraph_style.line_height = TextUnit::Sp(line_height);
}
text_style.paragraph_style.line_height_style = style.line_height_style;
text_style
}
const DRAWN_TEXT_STYLES: usize = 8;
pub(crate) struct DrawnStyle {
draw: DrawTextStyle,
style: Arc<TextStyle>,
hash: u64,
}
pub(crate) type DrawnTextStyles = RecentValues<DrawnStyle, DRAWN_TEXT_STYLES>;
pub fn drawn_text_style(style: &DrawTextStyle) -> (Arc<TextStyle>, u64) {
crate::thread_state::with(|state| {
let mut drawn = state.drawn_text_styles.borrow_mut();
let kept = match drawn.find(|kept| kept.draw == *style) {
Some(kept) => kept,
None => drawn.keep(drawn_style(style)),
};
(Arc::clone(&kept.style), kept.hash)
})
}
#[cold]
#[inline(never)]
fn drawn_style(style: &DrawTextStyle) -> DrawnStyle {
let text_style = text_style_for_draw_style(style);
DrawnStyle {
draw: style.clone(),
hash: text_style.render_hash(),
style: Arc::new(text_style),
}
}
pub fn draw_style_line_box(style: &DrawTextStyle) -> Option<LineBox> {
super::measure::resolved_line_box(&text_style_for_draw_style(style))
}
#[derive(Clone, Copy, Debug, Default)]
pub struct AppContextTextMeasurer;
impl AppContextTextMeasurer {
pub fn shared() -> Rc<dyn DrawTextMeasurer> {
crate::thread_state::with(|state| {
Rc::clone(state.draw_text_measurer.get_or_init(|| Rc::new(Self)))
})
}
}
impl DrawTextMeasurer for AppContextTextMeasurer {
fn measure_text(&self, text: &str, style: &DrawTextStyle) -> MeasuredText {
if crate::render_state::current_app_context().is_none() {
return MeasuredText {
measurement: estimate_text_measurement(text, style),
layout: None,
};
}
let prepared = prepare_draw_text(text, style);
let layout = &prepared.layout;
let line_height = layout.line_advance();
let first_baseline = layout.line_baseline(0);
let measurement = if text.is_empty() {
TextMeasurement::empty(line_height, first_baseline)
} else {
TextMeasurement {
size: prepared.size(),
line_height,
first_baseline,
line_count: prepared.metrics().line_count,
}
};
MeasuredText {
measurement,
layout: Some(prepared.layout_handle()),
}
}
}
fn prepare_draw_text(text: &str, style: &DrawTextStyle) -> Rc<super::PreparedTextLayout> {
let (style, style_hash) = drawn_text_style(style);
let text = super::shared_plain_annotated_string(text);
super::measure::prepare_resolved(&super::measure::SharedText {
text: &text,
style: &style,
style_hash,
})
}
#[cfg(test)]
#[path = "tests/draw_scope_text_tests.rs"]
mod tests;