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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
//! Layouting of continous pieces of text into boxes.
//!
//! The layouter picks the most suitable font for each individual character.
//! When the primary layouting axis horizontally inversed, the word is spelled
//! backwards. Vertical word layout is not yet supported.
use fontdock::{FaceId, FaceQuery, FontStyle};
use ttf_parser::GlyphId;
use crate::font::SharedFontLoader;
use crate::geom::Size;
use crate::style::TextStyle;
use super::elements::{LayoutElement, Shaped};
use super::*;
/// Layouts text into a box.
pub async fn layout_text(text: &str, ctx: TextContext<'_>) -> BoxLayout {
TextLayouter::new(text, ctx).layout().await
}
/// Performs the text layouting.
#[derive(Debug)]
struct TextLayouter<'a> {
ctx: TextContext<'a>,
text: &'a str,
shaped: Shaped,
elements: LayoutElements,
start: f64,
width: f64,
}
/// The context for text layouting.
#[derive(Debug, Copy, Clone)]
pub struct TextContext<'a> {
/// The font loader to retrieve fonts from when typesetting text with
/// `layout_text`.
pub loader: &'a SharedFontLoader,
/// The style for text: Font selection with classes, weights and variants,
/// font sizes, spacing and so on.
pub style: &'a TextStyle,
/// The direction into which the word is laid out. For now, only horizontal
/// directions are supported.
pub dir: Dir,
/// The alignment of the _resulting_ layout. This does not effect the line
/// layouting itself, but rather how the finished layout will be positioned
/// in a parent layout.
pub align: LayoutAlign,
}
impl<'a> TextLayouter<'a> {
fn new(text: &'a str, ctx: TextContext<'a>) -> Self {
Self {
ctx,
text,
shaped: Shaped::new(FaceId::MAX, ctx.style.font_size()),
elements: LayoutElements::new(),
start: 0.0,
width: 0.0,
}
}
async fn layout(mut self) -> BoxLayout {
// If the primary axis is negative, we layout the characters reversed.
if self.ctx.dir.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;
}
}
// Flush the last buffered parts of the word.
if !self.shaped.text.is_empty() {
let pos = Size::new(self.start, 0.0);
self.elements.push(pos, LayoutElement::Text(self.shaped));
}
BoxLayout {
size: Size::new(self.width, self.ctx.style.font_size()),
align: self.ctx.align,
elements: self.elements,
}
}
async fn layout_char(&mut self, c: char) {
let (index, glyph, char_width) = match self.select_font(c).await {
Some(selected) => selected,
// TODO: Issue warning about missing character.
None => return,
};
// Flush the buffer and issue a font setting action if the font differs
// from the last character's one.
if self.shaped.face != index {
if !self.shaped.text.is_empty() {
let pos = Size::new(self.start, 0.0);
let shaped = std::mem::replace(
&mut self.shaped,
Shaped::new(FaceId::MAX, self.ctx.style.font_size()),
);
self.elements.push(pos, LayoutElement::Text(shaped));
self.start = self.width;
}
self.shaped.face = index;
}
self.shaped.text.push(c);
self.shaped.glyphs.push(glyph);
self.shaped.offsets.push(self.width);
self.width += char_width;
}
async fn select_font(&mut self, c: char) -> Option<(FaceId, GlyphId, f64)> {
let mut loader = self.ctx.loader.borrow_mut();
let mut variant = self.ctx.style.variant;
if self.ctx.style.bolder {
variant.weight.0 += 300;
}
if self.ctx.style.italic {
variant.style = match variant.style {
FontStyle::Normal => FontStyle::Italic,
FontStyle::Italic => FontStyle::Normal,
FontStyle::Oblique => FontStyle::Normal,
}
}
let query = FaceQuery {
fallback: self.ctx.style.fallback.iter(),
variant,
c,
};
if let Some((id, face)) = loader.query(query).await {
// Determine the width of the char.
let units_per_em = face.units_per_em().unwrap_or(1000) as f64;
let ratio = 1.0 / units_per_em;
let to_raw = |x| ratio * x as f64;
let glyph = face.glyph_index(c)?;
let glyph_width = face.glyph_hor_advance(glyph)?;
let char_width = to_raw(glyph_width) * self.ctx.style.font_size();
Some((id, glyph, char_width))
} else {
None
}
}
}
|