use toddle::query::{SharedFontLoader, FontQuery, FontClass}; use toddle::tables::{CharMap, Header, HorizontalMetrics}; use super::*; use crate::size::{Size, Size2D}; /// The context for text layouting. /// /// See [`LayoutContext`] for details about the fields. #[derive(Copy, Clone)] pub struct TextContext<'a, 'p> { pub loader: &'a SharedFontLoader<'p>, pub style: &'a TextStyle, pub alignment: LayoutAlignment, } /// Layouts text into a box. /// /// There is no complex layout involved. The text is simply laid out left- /// to-right using the correct font for each character. pub fn layout_text(text: &str, ctx: TextContext) -> LayoutResult { TextLayouter::new(text, ctx).layout() } /// Layouts text into boxes. struct TextLayouter<'a, 'p> { ctx: TextContext<'a, 'p>, text: &'a str, actions: LayoutActionList, buffer: String, active_font: usize, width: Size, classes: Vec, } impl<'a, 'p> TextLayouter<'a, 'p> { /// Create a new text layouter. fn new(text: &'a str, ctx: TextContext<'a, 'p>) -> TextLayouter<'a, 'p> { TextLayouter { ctx, text, actions: LayoutActionList::new(), buffer: String::new(), active_font: std::usize::MAX, width: Size::zero(), classes: ctx.style.classes.clone(), } } /// Layout the text fn layout(mut self) -> LayoutResult { for c in self.text.chars() { let (index, char_width) = self.select_font(c)?; self.width += char_width; if self.active_font != index { if !self.buffer.is_empty() { self.actions.add(LayoutAction::WriteText(self.buffer)); self.buffer = String::new(); } self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size)); self.active_font = index; } self.buffer.push(c); } if !self.buffer.is_empty() { self.actions.add(LayoutAction::WriteText(self.buffer)); } Ok(Layout { dimensions: Size2D::new(self.width, self.ctx.style.font_size), baseline: None, alignment: self.ctx.alignment, actions: self.actions.to_vec(), }) } /// Select the best font for a character and return its index along with /// the width of the char in the font. fn select_font(&mut self, c: char) -> LayoutResult<(usize, Size)> { let mut loader = self.ctx.loader.borrow_mut(); for class in &self.ctx.style.fallback { self.classes.push(class.clone()); let query = FontQuery { chars: &[c], classes: &self.classes, }; if let Some((font, index)) = loader.get(query) { let font_unit_ratio = 1.0 / (font.read_table::
()?.units_per_em as f32); let font_unit_to_size = |x| Size::pt(font_unit_ratio * x); let glyph = font .read_table::()? .get(c) .expect("select_font: font should have char"); let glyph_width = font .read_table::()? .get(glyph) .expect("select_font: font should have glyph") .advance_width as f32; let char_width = font_unit_to_size(glyph_width) * self.ctx.style.font_size.to_pt(); return Ok((index, char_width)); } self.classes.pop(); } error!("no suitable font for character `{}`", c); } }