Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_font/src/font.rs

7.1 KiB, 57 runs

created by r1870400018:35696, 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//! The fallback chain: a face, and the faces to fall back to for what it cannot draw.
2//!
3//! See the crate note on why a font is a chain and not a file. This module is what keeps the face
4//! at the head of the chain swappable without the swap costing coverage.
5
6use crate::face::{
7 Face,
8 Metrics,
9};
10use crate::shape::{
11 Dir,
12 Feature,
13 Glyph,
14 Run,
15};
16
17use oxedyne_fe2o3_core::prelude::*;
18use oxedyne_fe2o3_graphics::prelude::*;
19use oxedyne_fe2o3_text::unicode::norm::combining_class;
20
21/// Whether a character takes the face of what surrounds it rather than choosing its own. A SPACE is
22/// in every face, so asking would cut every run at every word; a COMBINING MARK is positioned by the
23/// face that drew its base, so an accent drawn by a face that never saw that base floats. The mark
24/// test is the canonical combining class -- not every mark, but every one meant to be placed against
25/// something else.
26fn neutral(ch: char) -> bool {
27 ch.is_whitespace() || combining_class(ch) != 0
28}
29
30/// A stretch of a string that one face draws the whole of.
31#[derive(Clone, Copy, Debug)]
32struct Seg {
33 face: u8, // which face in the chain draws it
34 start: usize, // where it starts in the string, bytes
35 end: usize, // where it ends, bytes
36}
37
38/// What the engine draws with: a face chosen for how it reads, and the faces to fall back to for
39/// what it lacks. See the crate note on why a font is a chain, not a file.
40pub struct Font {
41 faces: Vec<Face>, // in the order they are tried; never empty
42}
43
44impl Font {
45
46 /// A font of one face, which falls back to nothing.
47 pub fn new(bytes: Vec<u8>) -> Outcome<Self> {
48 Ok(Self {
49 faces: vec![res!(Face::new(bytes))],
50 })
51 }
52
53 /// A font of a face and the faces behind it, in the order they are to be tried.
54 pub fn chain(faces: Vec<Face>) -> Outcome<Self> {
55 if faces.is_empty() {
56 return Err(err!(
57 "A font is a chain of at least one face, and this chain is empty.";
58 Invalid, Input, Missing));
59 }
60 if faces.len() > (u8::MAX as usize) + 1 {
61 return Err(err!(
62 "A chain of {} faces cannot be drawn: a glyph remembers which face drew it in one \
63 byte.", faces.len();
64 Invalid, Input, TooBig));
65 }
66 Ok(Self {
67 faces,
68 })
69 }
70
71 /// The face at the head of the chain: the one the reader actually reads.
72 fn first(&self) -> Outcome<&Face> {
73 match self.faces.first() {
74 Some(face) => Ok(face),
75 None => Err(err!("A font's chain of faces is empty, which its constructors refuse."; Bug)),
76 }
77 }
78
79 /// The face at a place in the chain, as a glyph's `face` names it.
80 pub fn face(&self, i: u8) -> Outcome<&Face> {
81 match self.faces.get(i as usize) {
82 Some(face) => Ok(face),
83 None => Err(err!(
84 "A glyph names face {} of a chain of {}.", i, self.faces.len(); Bug)),
85 }
86 }
87
88 /// The family, weight and slant of the face at the head of the chain -- the one the reader reads.
89 pub fn info(&self) -> Outcome<crate::face::FaceInfo> {
90 res!(self.first()).info()
91 }
92
93 /// The vertical metrics at a size, in pixels. They are the FIRST face's, never the tallest used:
94 /// a line's height must not change because one arrow in it came from further down the chain. The
95 /// faces behind the first are chosen to sit within its box.
96 pub fn metrics(&self, size: f32) -> Outcome<Metrics> {
97 res!(self.first()).metrics(size)
98 }
99
100 /// Which face draws a character: the first in the chain that can. One no face has is left with the
101 /// first, which draws its own "not defined" glyph -- the reader is told something is missing.
102 fn pick(&self, ch: char) -> u8 {
103 for (i, face) in self.faces.iter().enumerate() {
104 if face.covers(ch) {
105 return i as u8;
106 }
107 }
108 0
109 }
110
111 /// Cuts a string into the stretches each face draws. Nearly every character asks the chain who
112 /// draws it; the NEUTRAL ones (see [`neutral`]) take the face already in hand if it covers them at
113 /// all, so a space does not end the stretch either side of it and cut a line of Arabic into words.
114 /// Stickiness goes no further: a face kept for what it merely HAPPENS to cover would drag the rest
115 /// of a sentence into the fallback over one arrow, and the reader would see the typeface change
116 /// mid-line for no reason.
117 fn segment(&self, text: &str) -> Vec<Seg> {
118 let mut segs: Vec<Seg> = Vec::new();
119 let mut cur: Option<(u8, usize)> = None;
120 for (i, ch) in text.char_indices() {
121 let held = match cur {
122 Some((f, _)) => neutral(ch)
123 && self.faces.get(f as usize).map_or(false, |x| x.covers(ch)),
124 None => false,
125 };
126 let face = match cur {
127 Some((f, _)) if held => f,
128 _ => self.pick(ch),
129 };
130 match cur {
131 Some((f, _)) if f == face => {},
132 Some((f, s)) => {
133 segs.push(Seg { face: f, start: s, end: i });
134 cur = Some((face, i));
135 },
136 None => cur = Some((face, i)),
137 }
138 }
139 if let Some((f, s)) = cur {
140 segs.push(Seg { face: f, start: s, end: text.len() });
141 }
142 segs
143 }
144
145 /// Shapes a string, each face in the chain drawing what the one before it could not. The common
146 /// case by far is a string one face draws the whole of: one shaping call.
147 pub fn shape(&self, text: &str, size: f32, dir: Dir) -> Outcome<Run> {
148 self.shape_with(text, size, dir, &[])
149 }
150
151 /// As [`Font::shape`], with OpenType features applied across the whole string. A face down the chain
152 /// that lacks a feature simply draws without it, as every shaper does.
153 pub fn shape_with(&self, text: &str, size: f32, dir: Dir, features: &[Feature]) -> Outcome<Run> {
154 if text.is_empty() {
155 return Ok(Run {
156 glyphs: Vec::new(),
157 advance: 0.0,
158 size,
159 });
160 }
161 let segs = self.segment(text);
162 if let [seg] = segs[..] {
163 return res!(self.face(seg.face)).shape_with(text, size, dir, seg.face, 0, features);
164 }
165
166 // More than one face is needed, so each stretch is shaped by its own and the results are laid
167 // end to end. Shaping stops at the join -- a ligature cannot span two faces anyway, since the
168 // second face has never heard of the first's glyphs.
169 let mut runs: Vec<Run> = Vec::with_capacity(segs.len());
170 for seg in &segs {
171 let sub = match text.get(seg.start..seg.end) {
172 Some(s) => s,
173 None => return Err(err!(
174 "The stretch {}..{} is not a character boundary of the string being shaped.",
175 seg.start, seg.end;
176 Bug)),
177 };
178 runs.push(res!(res!(self.face(seg.face)).shape_with(sub, size, dir, seg.face, seg.start, features)));
179 }
180
181 // The stretches are laid out in VISUAL order, which for right-to-left text is the reverse of
182 // the order they were cut in: the first stretch of an Arabic line sits at its right-hand end.
183 // Within a stretch the shaper has already done this; across stretches it cannot, because it
184 // never saw them together.
185 let order: Vec<usize> = match dir {
186 Dir::Ltr => (0..runs.len()).collect(),
187 Dir::Rtl => (0..runs.len()).rev().collect(),
188 };
189 let mut glyphs = Vec::new();
190 let mut pen = 0.0f32;
191 for i in order {
192 for g in &runs[i].glyphs {
193 glyphs.push(Glyph {
194 x: g.x + pen,
195 ..*g
196 });
197 }
198 pen += runs[i].advance;
199 }
200 Ok(Run {
201 glyphs,
202 advance: pen,
203 size,
204 })
205 }
206
207 /// The outline of one glyph, drawn by the face that shaped it.
208 pub fn outline(&self, face: u8, id: u32, size: f32) -> Outcome<Path> {
209 res!(self.face(face)).outline(id, size)
210 }
211}