1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
use toddle::query::{SharedFontLoader, FontQuery, FontIndex};
use toddle::tables::{CharMap, Header, HorizontalMetrics};
use crate::size::{Size, Size2D};
use crate::style::TextStyle;
use super::*;
/// 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 axes: LayoutAxes,
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 async fn layout_text(text: &str, ctx: TextContext<'_, '_>) -> LayoutResult<Layout> {
TextLayouter::new(text, ctx).layout().await
}
/// Layouts text into boxes.
struct TextLayouter<'a, 'p> {
ctx: TextContext<'a, 'p>,
text: &'a str,
actions: LayoutActions,
buffer: String,
active_font: FontIndex,
width: Size,
}
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: LayoutActions::new(),
buffer: String::new(),
active_font: FontIndex::MAX,
width: Size::ZERO,
}
}
/// Layout the text
async fn layout(mut self) -> LayoutResult<Layout> {
if self.ctx.axes.primary.is_positive() {
for c in self.text.chars() {
self.layout_char(c).await?;
}
} else {
for c in self.text.chars().rev() {
self.layout_char(c).await?;
}
}
if !self.buffer.is_empty() {
self.actions.add(LayoutAction::WriteText(self.buffer));
}
Ok(Layout {
dimensions: Size2D::new(self.width, self.ctx.style.font_size()),
alignment: self.ctx.alignment,
actions: self.actions.to_vec(),
})
}
/// Layout an individual character.
async fn layout_char(&mut self, c: char) -> LayoutResult<()> {
let (index, char_width) = self.select_font(c).await?;
self.width += char_width;
if self.active_font != index {
if !self.buffer.is_empty() {
let text = std::mem::replace(&mut self.buffer, String::new());
self.actions.add(LayoutAction::WriteText(text));
}
self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size()));
self.active_font = index;
}
self.buffer.push(c);
Ok(())
}
/// Select the best font for a character and return its index along with
/// the width of the char in the font.
async fn select_font(&mut self, c: char) -> LayoutResult<(FontIndex, Size)> {
let mut loader = self.ctx.loader.borrow_mut();
let query = FontQuery {
fallback: &self.ctx.style.fallback,
variant: self.ctx.style.variant,
c,
};
if let Some((font, index)) = loader.get(query).await {
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("select_font: font should have char");
let glyph_width = font
.read_table::<HorizontalMetrics>()?
.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));
}
error!("no suitable font for character `{}`", c);
}
}
|