Refactor the text layouting ♻

This commit is contained in:
Laurenz 2019-10-14 23:33:29 +02:00
parent c768b8b61f
commit 5473e3903a
2 changed files with 103 additions and 78 deletions

View File

@ -14,6 +14,7 @@ pub struct StackLayouter {
/// The context for the [`StackLayouter`]. /// The context for the [`StackLayouter`].
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct StackContext { pub struct StackContext {
/// The space to layout the boxes in.
pub space: LayoutSpace, pub space: LayoutSpace,
} }

View File

@ -1,4 +1,4 @@
use toddle::query::{FontQuery, SharedFontLoader}; use toddle::query::{SharedFontLoader, FontQuery, FontClass};
use toddle::tables::{CharMap, Header, HorizontalMetrics}; use toddle::tables::{CharMap, Header, HorizontalMetrics};
use super::*; use super::*;
@ -13,82 +13,106 @@ pub struct TextContext<'a, 'p> {
pub style: &'a TextStyle, pub style: &'a TextStyle,
} }
/// Layout one piece of text without any breaks as one continous box. /// 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<Layout> { pub fn layout_text(text: &str, ctx: TextContext) -> LayoutResult<Layout> {
let mut loader = ctx.loader.borrow_mut(); TextLayouter::new(text, ctx).layout()
let mut actions = Vec::new();
let mut active_font = std::usize::MAX;
let mut buffer = String::new();
let mut width = Size::zero();
// Walk the characters.
for character in text.chars() {
// Retrieve the best font for this character.
let mut font = None;
let mut classes = ctx.style.classes.clone();
for class in &ctx.style.fallback {
classes.push(class.clone());
font = loader.get(FontQuery {
chars: &[character],
classes: &classes,
});
if font.is_some() {
break;
} }
classes.pop(); /// 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<FontClass>,
} }
let (font, index) = match font { impl<'a, 'p> TextLayouter<'a, 'p> {
Some(f) => f, /// Create a new text layouter.
None => return Err(LayoutError::NoSuitableFont(character)), fn new(text: &'a str, ctx: TextContext<'a, 'p>) -> TextLayouter<'a, 'p> {
}; TextLayouter {
ctx,
// Create a conversion function between font units and sizes. text,
let font_unit_ratio = 1.0 / (font.read_table::<Header>()?.units_per_em as f32); actions: LayoutActionList::new(),
let font_unit_to_size = |x| Size::pt(font_unit_ratio * x); buffer: String::new(),
active_font: std::usize::MAX,
// Add the char width to the total box width. width: Size::zero(),
let glyph = font classes: ctx.style.classes.clone(),
.read_table::<CharMap>()? }
.get(character)
.expect("layout text: font should have char");
let glyph_width = font_unit_to_size(
font.read_table::<HorizontalMetrics>()?
.get(glyph)
.expect("layout text: font should have glyph")
.advance_width as f32,
);
let char_width = glyph_width * ctx.style.font_size;
width += char_width;
// Change the font if necessary.
if active_font != index {
if !buffer.is_empty() {
actions.push(LayoutAction::WriteText(buffer));
buffer = String::new();
} }
actions.push(LayoutAction::SetFont(index, ctx.style.font_size)); /// Layout the text
active_font = index; fn layout(mut self) -> LayoutResult<Layout> {
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();
} }
buffer.push(character); self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size));
self.active_font = index;
} }
// Write the remaining characters. self.buffer.push(c);
if !buffer.is_empty() { }
actions.push(LayoutAction::WriteText(buffer));
if !self.buffer.is_empty() {
self.actions.add(LayoutAction::WriteText(self.buffer));
} }
Ok(Layout { Ok(Layout {
dimensions: Size2D::new(width, Size::pt(ctx.style.font_size)), dimensions: Size2D::new(self.width, Size::pt(self.ctx.style.font_size)),
actions, actions: self.actions.into_vec(),
debug_render: false, debug_render: false,
}) })
} }
/// 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::<Header>()?.units_per_em as f32);
let font_unit_to_size = |x| Size::pt(font_unit_ratio * x);
let glyph = font
.read_table::<CharMap>()?
.get(c)
.expect("layout text: font should have char");
let glyph_width = font
.read_table::<HorizontalMetrics>()?
.get(glyph)
.expect("layout text: font should have glyph")
.advance_width as f32;
let char_width = font_unit_to_size(glyph_width) * self.ctx.style.font_size;
return Ok((index, char_width));
}
self.classes.pop();
}
Err(LayoutError::NoSuitableFont(c))
}
}