Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_font/src/face.rs

11.0 KiB, 99 runs

created by r1870400018:35694, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! One typeface: parse, coverage, metrics, shaping and glyph outlines.
2//!
3//! Where `harfrust` shapes and `skrifa` draws, both turned back into this crate's own types at once.
4//! A face is rarely used alone; what a caller draws with is a [`Font`](crate::font::Font), a chain of
5//! these.
6
7use crate::shape::{
8 Dir,
9 Feature,
10 Glyph,
11 Run,
12};
13
14use oxedyne_fe2o3_core::prelude::*;
15use oxedyne_fe2o3_graphics::prelude::*;
16use oxedyne_fe2o3_graphics::pdf_font::FontProgram;
17
18use harfrust::{
19 Feature as ShapeFeature,
20 FontRef as ShapeFont,
21 ShapeOptions,
22 ShaperData,
23 UnicodeBuffer,
24};
25
26use skrifa::{
27 instance::{
28 LocationRef,
29 Size,
30 },
31 outline::{
32 DrawSettings,
33 OutlinePen,
34 },
35 attribute::Style,
36 string::StringId,
37 FontRef as OutlineFont,
38 GlyphId,
39 MetadataProvider,
40};
41
42use std::collections::HashMap;
43use std::collections::HashSet;
44use std::sync::{
45 Arc,
46 RwLock,
47};
48
49/// The part a font plays. A document names a role; the reader's font set decides what it looks like.
50#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
51pub enum Role {
52 #[default]
53 Body, // running text
54 Bold, // running text, emphasised strongly
55 Italic, // running text, emphasised
56 BoldItalic, // running text, emphasised, and strongly
57 Mono, // preserved source, where the columns must line up
58}
59
60/// The vertical metrics of a font at a size, in pixels.
61#[derive(Clone, Copy, Debug, PartialEq)]
62pub struct Metrics {
63 pub ascent: f32, // how far the tallest letters rise above the baseline
64 pub descent: f32, // how far the deepest fall below it, as a positive number
65 pub leading: f32, // the gap the designer asks between one line's descent and the next's ascent
66}
67
68impl Metrics {
69
70 /// The distance from one baseline to the next.
71 pub fn line_height(&self) -> f32 {
72 self.ascent + self.descent + self.leading
73 }
74}
75
76/// What a font file says about itself: the family it belongs to and where in that family it sits. This
77/// is what a document's `font: "Name"` is matched against, so a face is found by the name its designer
78/// gave it rather than by whatever its file happens to be called.
79#[derive(Clone, Debug, PartialEq, Eq)]
80pub struct FaceInfo {
81 pub family: String, // the typographic family (name ID 16), else the legacy family (name ID 1)
82 pub weight: u16, // OS/2 weight class, 100-900; 400 regular, 700 bold
83 pub italic: bool, // italic or oblique
84}
85
86impl FaceInfo {
87
88 /// Reads a font file's family, weight and slant without building a shaper, so a directory of fonts
89 /// can be indexed cheaply before any of them is needed.
90 pub fn read(bytes: &[u8]) -> Outcome<Self> {
91 let of = match OutlineFont::new(bytes) {
92 Ok(f) => f,
93 Err(e) => return Err(err!(
94 "The {} bytes given are not a font whose names can be read: {:?}.", bytes.len(), e;
95 Invalid, Input)),
96 };
97 // The typographic family groups every weight and width under one name ("Noto Sans"), where the
98 // legacy family splits them four to a family ("Noto Sans SemiBold"); prefer it where present.
99 let family = of.localized_strings(StringId::TYPOGRAPHIC_FAMILY_NAME).english_or_first()
100 .or_else(|| of.localized_strings(StringId::FAMILY_NAME).english_or_first())
101 .map(|s| s.to_string());
102 let family = match family {
103 Some(f) if !f.trim().is_empty() => f.trim().to_string(),
104 _ => return Err(err!(
105 "The font of {} bytes names no family in its name table.", bytes.len();
106 Invalid, Input, Missing)),
107 };
108 let attrs = of.attributes();
109 Ok(Self {
110 family,
111 weight: attrs.weight.value().round().clamp(1.0, 1000.0) as u16,
112 italic: !matches!(attrs.style, Style::Normal),
113 })
114 }
115}
116
117/// One typeface, at any size: a single font file. Its bytes are owned and lent to both third-party
118/// parsers when needed; the shaper's tables, the costly part to build, are cached.
119pub struct Face {
120 bytes: Arc<Vec<u8>>, // the font file, shared with its embeddable program
121 program: Option<Arc<FontProgram>>, // the file as a PDF embeds it; `None` when it cannot be
122 shaper: ShaperData, // the shaper's cached view, built once
123 upem: f32, // font units per em, what every measurement in the file is in terms of
124 covers: HashSet<u32>, // every character the face can draw, read once (asked per character)
125 // Drawn glyph outlines, memoised by (glyph id, size in its raw bits). A book draws the same few
126 // hundred glyphs at the same few sizes hundreds of thousands of times; re-reading the font and
127 // redrawing each outline every time was the whole cost of emit. The outline is a pure function of
128 // its key, so the cache changes nothing in the bytes drawn -- only how many times they are computed.
129 outlines: RwLock<HashMap<(u32, u32), Path>>,
130}
131
132impl Face {
133
134 pub fn new(bytes: Vec<u8>) -> Outcome<Self> {
135 let sf = match ShapeFont::new(&bytes) {
136 Ok(f) => f,
137 Err(e) => return Err(err!(
138 "The {} bytes given are not a font a shaper can read: {:?}.", bytes.len(), e;
139 Invalid, Input)),
140 };
141 let shaper = ShaperData::new(&sf);
142 let of = match OutlineFont::new(&bytes) {
143 Ok(f) => f,
144 Err(e) => return Err(err!(
145 "The {} bytes given are not a font an outline reader can read: {:?}.",
146 bytes.len(), e;
147 Invalid, Input)),
148 };
149 let upem = of.metrics(Size::unscaled(), LocationRef::default()).units_per_em as f32;
150 if upem <= 0.0 {
151 return Err(err!(
152 "The font declares {} units per em, which cannot be scaled by.", upem;
153 Invalid, Input));
154 }
155 let covers: HashSet<u32> = of.charmap().mappings().map(|(c, _)| c).collect();
156 drop(of);
157 drop(sf);
158 let bytes = Arc::new(bytes);
159 // A file the embedding reader cannot follow is still a face to shape and outline; it is drawn as
160 // outlines in a PDF rather than embedded, so the failure is not the caller's.
161 let program = FontProgram::parse(bytes.clone()).ok().flatten().map(Arc::new);
162 Ok(Self {
163 bytes,
164 program,
165 shaper,
166 upem,
167 covers,
168 outlines: RwLock::new(HashMap::new()),
169 })
170 }
171
172 /// The face's file as a PDF embeds it, or `None` for a face that cannot be embedded -- a variable
173 /// `CFF2` face, or one whose licence forbids it -- and must be drawn as outlines.
174 pub fn program(&self) -> Option<&Arc<FontProgram>> {
175 self.program.as_ref()
176 }
177
178 /// The family, weight and slant the file declares.
179 pub fn info(&self) -> Outcome<FaceInfo> {
180 FaceInfo::read(&self.bytes)
181 }
182
183 /// Can the face draw this character?
184 pub fn covers(&self, ch: char) -> bool {
185 self.covers.contains(&(ch as u32))
186 }
187
188 /// The font as the shaper reads it.
189 fn shape_font(&self) -> Outcome<ShapeFont<'_>> {
190 match ShapeFont::new(&self.bytes[..]) {
191 Ok(f) => Ok(f),
192 Err(e) => Err(err!("The font could not be re-read for shaping: {:?}.", e; Bug)),
193 }
194 }
195
196 /// The font as the outline reader reads it.
197 fn outline_font(&self) -> Outcome<OutlineFont<'_>> {
198 match OutlineFont::new(&self.bytes[..]) {
199 Ok(f) => Ok(f),
200 Err(e) => Err(err!("The font could not be re-read for outlines: {:?}.", e; Bug)),
201 }
202 }
203
204 /// The vertical metrics at a size, in pixels.
205 pub fn metrics(&self, size: f32) -> Outcome<Metrics> {
206 let of = res!(self.outline_font());
207 let m = of.metrics(Size::new(size), LocationRef::default());
208 Ok(Metrics {
209 ascent: m.ascent,
210 descent: m.descent.abs(),
211 leading: m.leading.max(0.0),
212 })
213 }
214
215 /// Shapes a string this face can draw the whole of: the glyphs it becomes, and where each sits.
216 /// `face` is which face in the chain this is, carried on every glyph so painting knows whose
217 /// outline to ask for; `at` is the string's byte offset in the one it was cut from, added to each
218 /// cluster so a caret reads offsets into the original text rather than into this fragment.
219 pub fn shape(&self, text: &str, size: f32, dir: Dir, face: u8, at: usize) -> Outcome<Run> {
220 self.shape_with(text, size, dir, face, at, &[])
221 }
222
223 /// As [`Face::shape`], with OpenType features applied across the whole string.
224 pub fn shape_with(
225 &self,
226 text: &str,
227 size: f32,
228 dir: Dir,
229 face: u8,
230 at: usize,
231 features: &[Feature],
232 )
233 -> Outcome<Run>
234 {
235 if text.is_empty() {
236 return Ok(Run {
237 glyphs: Vec::new(),
238 advance: 0.0,
239 size,
240 });
241 }
242 let sf = res!(self.shape_font());
243 let shaper = self.shaper.shaper(&sf).build();
244
245 let mut buf = UnicodeBuffer::new();
246 buf.push_str(text);
247 buf.set_direction(match dir {
248 Dir::Ltr => harfrust::Direction::LeftToRight,
249 Dir::Rtl => harfrust::Direction::RightToLeft,
250 });
251 buf.guess_segment_properties();
252
253 let feats: Vec<ShapeFeature> = features.iter()
254 .map(|f| ShapeFeature::new(harfrust::Tag::new(&f.tag), f.value, ..))
255 .collect();
256 let out = shaper.shape(buf, ShapeOptions::new().features(&feats));
257 let infos = out.glyph_infos();
258 let posns = out.glyph_positions();
259
260 // Font units become pixels here, and nowhere else.
261 let scale = size / self.upem;
262 let mut glyphs = Vec::with_capacity(infos.len());
263 let mut pen = 0.0f32;
264 for (i, p) in infos.iter().zip(posns.iter()) {
265 let adv = (p.x_advance as f32) * scale;
266 glyphs.push(Glyph {
267 id: i.glyph_id,
268 face,
269 x: pen + (p.x_offset as f32) * scale,
270 y: (p.y_offset as f32) * scale,
271 adv,
272 cluster: (i.cluster as usize) + at,
273 });
274 pen += adv;
275 }
276 Ok(Run {
277 glyphs,
278 advance: pen,
279 size,
280 })
281 }
282
283 /// The outline of one glyph at a size, in the font's frame: origin the glyph's own, y up. Painting
284 /// flips it onto the page.
285 pub fn outline(&self, id: u32, size: f32) -> Outcome<Path> {
286 // The same glyph at the same size is drawn again and again across a book; memoise it. The key is
287 // the size's raw bits, so two calls at the identical `f32` share an entry and a re-shaped run at a
288 // new size (a heading, a footnote) gets its own -- no float is compared for near-equality.
289 let key = (id, size.to_bits());
290 {
291 let cache = lock_read!(self.outlines);
292 if let Some(path) = cache.get(&key) {
293 return Ok(path.clone());
294 }
295 }
296
297 let of = res!(self.outline_font());
298 let glyphs = of.outline_glyphs();
299 let glyph = match glyphs.get(GlyphId::new(id)) {
300 Some(g) => g,
301 None => return Err(err!(
302 "The font holds no glyph {}, which shaping asked for.", id; Invalid, Input)),
303 };
304 let mut pen = Pen::new();
305 let settings = DrawSettings::unhinted(Size::new(size), LocationRef::default());
306 if let Err(e) = glyph.draw(settings, &mut pen) {
307 return Err(err!("The outline of glyph {} could not be drawn: {:?}.", id, e; Invalid));
308 }
309 let path = res!(pen.finish());
310 let mut cache = lock_write!(self.outlines);
311 cache.insert(key, path.clone());
312 Ok(path)
313 }
314}
315
316/// Turns the outline reader's calls into one of our paths.
317struct Pen {
318 pb: PathBuilder,
319}
320
321impl Pen {
322
323 fn new() -> Self {
324 Self {
325 pb: PathBuilder::new(),
326 }
327 }
328
329 fn finish(self) -> Outcome<Path> {
330 self.pb.finish()
331 }
332}
333
334impl OutlinePen for Pen {
335
336 fn move_to(&mut self, x: f32, y: f32) {
337 self.pb.move_to(Pt::new(x, y));
338 }
339
340 fn line_to(&mut self, x: f32, y: f32) {
341 self.pb.line_to(Pt::new(x, y));
342 }
343
344 fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
345 self.pb.quad_to(Pt::new(cx, cy), Pt::new(x, y));
346 }
347
348 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
349 self.pb.cubic_to(Pt::new(cx0, cy0), Pt::new(cx1, cy1), Pt::new(x, y));
350 }
351
352 fn close(&mut self) {
353 self.pb.close();
354 }
355}