1pub mod color;
4
5mod book;
6mod exceptions;
7mod variant;
8
9pub use self::book::{Coverage, FontBook, FontFlags, FontInfo};
10pub use self::variant::{FontStretch, FontStyle, FontVariant, FontWeight};
11
12use std::cell::OnceCell;
13use std::fmt::{self, Debug, Formatter};
14use std::hash::{Hash, Hasher};
15use std::sync::{Arc, OnceLock};
16
17use ttf_parser::{GlyphId, name_id};
18
19use self::book::find_name;
20use crate::foundations::{Bytes, Cast};
21use crate::layout::{Abs, Em, Frame};
22use crate::text::{
23 BottomEdge, DEFAULT_SUBSCRIPT_METRICS, DEFAULT_SUPERSCRIPT_METRICS, TopEdge,
24};
25
26#[derive(Clone)]
30pub struct Font(Arc<Repr>);
31
32struct Repr {
34 index: u32,
36 info: FontInfo,
38 metrics: FontMetrics,
40 ttf: ttf_parser::Face<'static>,
42 rusty: rustybuzz::Face<'static>,
44 data: Bytes,
52}
53
54impl Font {
55 pub fn new(data: Bytes, index: u32) -> Option<Self> {
57 let slice: &'static [u8] =
64 unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) };
65
66 let ttf = ttf_parser::Face::parse(slice, index).ok()?;
67 let rusty = rustybuzz::Face::from_slice(slice, index)?;
68 let metrics = FontMetrics::from_ttf(&ttf);
69 let info = FontInfo::from_ttf(&ttf)?;
70
71 Some(Self(Arc::new(Repr { data, index, info, metrics, ttf, rusty })))
72 }
73
74 pub fn iter(data: Bytes) -> impl Iterator<Item = Self> {
76 let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1);
77 (0..count).filter_map(move |index| Self::new(data.clone(), index))
78 }
79
80 pub fn data(&self) -> &Bytes {
82 &self.0.data
83 }
84
85 pub fn index(&self) -> u32 {
87 self.0.index
88 }
89
90 pub fn info(&self) -> &FontInfo {
92 &self.0.info
93 }
94
95 pub fn metrics(&self) -> &FontMetrics {
97 &self.0.metrics
98 }
99
100 #[inline]
102 pub fn math(&self) -> &MathConstants {
103 self.0.metrics.math.get_or_init(|| FontMetrics::init_math(self))
104 }
105
106 pub fn units_per_em(&self) -> f64 {
108 self.0.metrics.units_per_em
109 }
110
111 pub fn to_em(&self, units: impl Into<f64>) -> Em {
113 Em::from_units(units, self.units_per_em())
114 }
115
116 pub fn x_advance(&self, glyph: u16) -> Option<Em> {
118 self.0
119 .ttf
120 .glyph_hor_advance(GlyphId(glyph))
121 .map(|units| self.to_em(units))
122 }
123
124 pub fn y_advance(&self, glyph: u16) -> Option<Em> {
126 self.0
127 .ttf
128 .glyph_ver_advance(GlyphId(glyph))
129 .map(|units| self.to_em(units))
130 }
131
132 pub fn find_name(&self, id: u16) -> Option<String> {
134 find_name(&self.0.ttf, id)
135 }
136
137 pub fn ttf(&self) -> &ttf_parser::Face<'_> {
139 &self.0.ttf
142 }
143
144 pub fn rusty(&self) -> &rustybuzz::Face<'_> {
146 &self.0.rusty
149 }
150
151 pub fn edges(
153 &self,
154 top_edge: TopEdge,
155 bottom_edge: BottomEdge,
156 font_size: Abs,
157 bounds: TextEdgeBounds,
158 ) -> (Abs, Abs) {
159 let cell = OnceCell::new();
160 let bbox = |gid, f: fn(ttf_parser::Rect) -> i16| {
161 cell.get_or_init(|| self.ttf().glyph_bounding_box(GlyphId(gid)))
162 .map(|bbox| self.to_em(f(bbox)).at(font_size))
163 .unwrap_or_default()
164 };
165
166 let top = match top_edge {
167 TopEdge::Metric(metric) => match metric.try_into() {
168 Ok(metric) => self.metrics().vertical(metric).at(font_size),
169 Err(_) => match bounds {
170 TextEdgeBounds::Zero => Abs::zero(),
171 TextEdgeBounds::Frame(frame) => frame.ascent(),
172 TextEdgeBounds::Glyph(gid) => bbox(gid, |b| b.y_max),
173 },
174 },
175 TopEdge::Length(length) => length.at(font_size),
176 };
177
178 let bottom = match bottom_edge {
179 BottomEdge::Metric(metric) => match metric.try_into() {
180 Ok(metric) => -self.metrics().vertical(metric).at(font_size),
181 Err(_) => match bounds {
182 TextEdgeBounds::Zero => Abs::zero(),
183 TextEdgeBounds::Frame(frame) => frame.descent(),
184 TextEdgeBounds::Glyph(gid) => -bbox(gid, |b| b.y_min),
185 },
186 },
187 BottomEdge::Length(length) => -length.at(font_size),
188 };
189
190 (top, bottom)
191 }
192}
193
194impl Hash for Font {
195 fn hash<H: Hasher>(&self, state: &mut H) {
196 self.0.data.hash(state);
197 self.0.index.hash(state);
198 }
199}
200
201impl Debug for Font {
202 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
203 write!(f, "Font({}, {:?})", self.info().family, self.info().variant)
204 }
205}
206
207impl Eq for Font {}
208
209impl PartialEq for Font {
210 fn eq(&self, other: &Self) -> bool {
211 self.0.data == other.0.data && self.0.index == other.0.index
212 }
213}
214
215#[derive(Debug, Clone)]
217pub struct FontMetrics {
218 pub units_per_em: f64,
220 pub ascender: Em,
222 pub cap_height: Em,
224 pub x_height: Em,
226 pub descender: Em,
228 pub strikethrough: LineMetrics,
230 pub underline: LineMetrics,
232 pub overline: LineMetrics,
234 pub subscript: Option<ScriptMetrics>,
236 pub superscript: Option<ScriptMetrics>,
238 pub math: OnceLock<Box<MathConstants>>,
240}
241
242impl FontMetrics {
243 pub fn from_ttf(ttf: &ttf_parser::Face) -> Self {
245 let units_per_em = f64::from(ttf.units_per_em());
246 let to_em = |units| Em::from_units(units, units_per_em);
247
248 let ascender = to_em(ttf.typographic_ascender().unwrap_or(ttf.ascender()));
249 let cap_height = ttf.capital_height().filter(|&h| h > 0).map_or(ascender, to_em);
250 let x_height = ttf.x_height().filter(|&h| h > 0).map_or(ascender, to_em);
251 let descender = to_em(ttf.typographic_descender().unwrap_or(ttf.descender()));
252
253 let strikeout = ttf.strikeout_metrics();
254 let underline = ttf.underline_metrics();
255
256 let strikethrough = LineMetrics {
257 position: strikeout.map_or(Em::new(0.25), |s| to_em(s.position)),
258 thickness: strikeout
259 .or(underline)
260 .map_or(Em::new(0.06), |s| to_em(s.thickness)),
261 };
262
263 let underline = LineMetrics {
264 position: underline.map_or(Em::new(-0.2), |s| to_em(s.position)),
265 thickness: underline
266 .or(strikeout)
267 .map_or(Em::new(0.06), |s| to_em(s.thickness)),
268 };
269
270 let overline = LineMetrics {
271 position: cap_height + Em::new(0.1),
272 thickness: underline.thickness,
273 };
274
275 let subscript = ttf.subscript_metrics().map(|metrics| ScriptMetrics {
276 width: to_em(metrics.x_size),
277 height: to_em(metrics.y_size),
278 horizontal_offset: to_em(metrics.x_offset),
279 vertical_offset: -to_em(metrics.y_offset),
280 });
281
282 let superscript = ttf.superscript_metrics().map(|metrics| ScriptMetrics {
283 width: to_em(metrics.x_size),
284 height: to_em(metrics.y_size),
285 horizontal_offset: to_em(metrics.x_offset),
286 vertical_offset: to_em(metrics.y_offset),
287 });
288
289 Self {
290 units_per_em,
291 ascender,
292 cap_height,
293 x_height,
294 descender,
295 strikethrough,
296 underline,
297 overline,
298 superscript,
299 subscript,
300 math: OnceLock::new(),
301 }
302 }
303
304 fn init_math(font: &Font) -> Box<MathConstants> {
305 let ttf = font.ttf();
306 let metrics = font.metrics();
307
308 let space_width = ttf
309 .glyph_index(' ')
310 .and_then(|id| ttf.glyph_hor_advance(id).map(|units| font.to_em(units)))
311 .unwrap_or(typst_library::math::THICK);
312
313 let is_cambria = || {
314 font.find_name(name_id::POST_SCRIPT_NAME)
315 .is_some_and(|name| name == "CambriaMath")
316 };
317
318 Box::new(
319 ttf.tables()
320 .math
321 .and_then(|math| math.constants)
322 .map(|constants| MathConstants {
323 space_width,
324 script_percent_scale_down: constants.script_percent_scale_down(),
325 script_script_percent_scale_down: constants
326 .script_script_percent_scale_down(),
327 display_operator_min_height: font.to_em(if is_cambria() {
328 constants.delimited_sub_formula_min_height()
329 } else {
330 constants.display_operator_min_height()
331 }),
332 axis_height: font.to_em(constants.axis_height().value),
333 accent_base_height: font.to_em(constants.accent_base_height().value),
334 flattened_accent_base_height: font
335 .to_em(constants.flattened_accent_base_height().value),
336 subscript_shift_down: font
337 .to_em(constants.subscript_shift_down().value),
338 subscript_top_max: font.to_em(constants.subscript_top_max().value),
339 subscript_baseline_drop_min: font
340 .to_em(constants.subscript_baseline_drop_min().value),
341 superscript_shift_up: font
342 .to_em(constants.superscript_shift_up().value),
343 superscript_shift_up_cramped: font
344 .to_em(constants.superscript_shift_up_cramped().value),
345 superscript_bottom_min: font
346 .to_em(constants.superscript_bottom_min().value),
347 superscript_baseline_drop_max: font
348 .to_em(constants.superscript_baseline_drop_max().value),
349 sub_superscript_gap_min: font
350 .to_em(constants.sub_superscript_gap_min().value),
351 superscript_bottom_max_with_subscript: font
352 .to_em(constants.superscript_bottom_max_with_subscript().value),
353 space_after_script: font.to_em(constants.space_after_script().value),
354 upper_limit_gap_min: font
355 .to_em(constants.upper_limit_gap_min().value),
356 upper_limit_baseline_rise_min: font
357 .to_em(constants.upper_limit_baseline_rise_min().value),
358 lower_limit_gap_min: font
359 .to_em(constants.lower_limit_gap_min().value),
360 lower_limit_baseline_drop_min: font
361 .to_em(constants.lower_limit_baseline_drop_min().value),
362 fraction_numerator_shift_up: font
363 .to_em(constants.fraction_numerator_shift_up().value),
364 fraction_numerator_display_style_shift_up: font.to_em(
365 constants.fraction_numerator_display_style_shift_up().value,
366 ),
367 fraction_denominator_shift_down: font
368 .to_em(constants.fraction_denominator_shift_down().value),
369 fraction_denominator_display_style_shift_down: font.to_em(
370 constants.fraction_denominator_display_style_shift_down().value,
371 ),
372 fraction_numerator_gap_min: font
373 .to_em(constants.fraction_numerator_gap_min().value),
374 fraction_num_display_style_gap_min: font
375 .to_em(constants.fraction_num_display_style_gap_min().value),
376 fraction_rule_thickness: font
377 .to_em(constants.fraction_rule_thickness().value),
378 fraction_denominator_gap_min: font
379 .to_em(constants.fraction_denominator_gap_min().value),
380 fraction_denom_display_style_gap_min: font
381 .to_em(constants.fraction_denom_display_style_gap_min().value),
382 skewed_fraction_vertical_gap: font
383 .to_em(constants.skewed_fraction_vertical_gap().value),
384 skewed_fraction_horizontal_gap: font
385 .to_em(constants.skewed_fraction_horizontal_gap().value),
386 overbar_vertical_gap: font
387 .to_em(constants.overbar_vertical_gap().value),
388 overbar_rule_thickness: font
389 .to_em(constants.overbar_rule_thickness().value),
390 overbar_extra_ascender: font
391 .to_em(constants.overbar_extra_ascender().value),
392 underbar_vertical_gap: font
393 .to_em(constants.underbar_vertical_gap().value),
394 underbar_rule_thickness: font
395 .to_em(constants.underbar_rule_thickness().value),
396 underbar_extra_descender: font
397 .to_em(constants.underbar_extra_descender().value),
398 radical_vertical_gap: font
399 .to_em(constants.radical_vertical_gap().value),
400 radical_display_style_vertical_gap: font
401 .to_em(constants.radical_display_style_vertical_gap().value),
402 radical_rule_thickness: font
403 .to_em(constants.radical_rule_thickness().value),
404 radical_extra_ascender: font
405 .to_em(constants.radical_extra_ascender().value),
406 radical_kern_before_degree: font
407 .to_em(constants.radical_kern_before_degree().value),
408 radical_kern_after_degree: font
409 .to_em(constants.radical_kern_after_degree().value),
410 radical_degree_bottom_raise_percent: constants
411 .radical_degree_bottom_raise_percent()
412 as f64
413 / 100.0,
414 })
415 .unwrap_or(MathConstants {
430 space_width,
431 script_percent_scale_down: 70,
432 script_script_percent_scale_down: 50,
433 display_operator_min_height: Em::zero(),
434 axis_height: metrics.x_height / 2.0,
435 accent_base_height: metrics.x_height,
436 flattened_accent_base_height: metrics.cap_height,
437 subscript_shift_down: metrics
438 .subscript
439 .map(|metrics| metrics.vertical_offset)
440 .unwrap_or(DEFAULT_SUBSCRIPT_METRICS.vertical_offset),
441 subscript_top_max: 0.8 * metrics.x_height,
442 subscript_baseline_drop_min: Em::zero(),
443 superscript_shift_up: metrics
444 .superscript
445 .map(|metrics| metrics.vertical_offset)
446 .unwrap_or(DEFAULT_SUPERSCRIPT_METRICS.vertical_offset),
447 superscript_shift_up_cramped: Em::zero(),
448 superscript_bottom_min: 0.25 * metrics.x_height,
449 superscript_baseline_drop_max: Em::zero(),
450 sub_superscript_gap_min: 4.0 * metrics.underline.thickness,
451 superscript_bottom_max_with_subscript: 0.8 * metrics.x_height,
452 space_after_script: Em::new(1.0 / 24.0),
453 upper_limit_gap_min: Em::zero(),
454 upper_limit_baseline_rise_min: Em::zero(),
455 lower_limit_gap_min: Em::zero(),
456 lower_limit_baseline_drop_min: Em::zero(),
457 fraction_numerator_shift_up: Em::zero(),
458 fraction_numerator_display_style_shift_up: Em::zero(),
459 fraction_denominator_shift_down: Em::zero(),
460 fraction_denominator_display_style_shift_down: Em::zero(),
461 fraction_numerator_gap_min: metrics.underline.thickness,
462 fraction_num_display_style_gap_min: 3.0 * metrics.underline.thickness,
463 fraction_rule_thickness: metrics.underline.thickness,
464 fraction_denominator_gap_min: metrics.underline.thickness,
465 fraction_denom_display_style_gap_min: 3.0
466 * metrics.underline.thickness,
467 skewed_fraction_vertical_gap: Em::zero(),
468 skewed_fraction_horizontal_gap: Em::new(0.5),
469 overbar_vertical_gap: 3.0 * metrics.underline.thickness,
470 overbar_rule_thickness: metrics.underline.thickness,
471 overbar_extra_ascender: metrics.underline.thickness,
472 underbar_vertical_gap: 3.0 * metrics.underline.thickness,
473 underbar_rule_thickness: metrics.underline.thickness,
474 underbar_extra_descender: metrics.underline.thickness,
475 radical_vertical_gap: 1.25 * metrics.underline.thickness,
476 radical_display_style_vertical_gap: metrics.underline.thickness
477 + 0.25 * metrics.x_height,
478 radical_rule_thickness: metrics.underline.thickness,
479 radical_extra_ascender: metrics.underline.thickness,
480 radical_kern_before_degree: Em::new(5.0 / 18.0),
481 radical_kern_after_degree: Em::new(-10.0 / 18.0),
482 radical_degree_bottom_raise_percent: 0.6,
483 }),
484 )
485 }
486
487 pub fn vertical(&self, metric: VerticalFontMetric) -> Em {
489 match metric {
490 VerticalFontMetric::Ascender => self.ascender,
491 VerticalFontMetric::CapHeight => self.cap_height,
492 VerticalFontMetric::XHeight => self.x_height,
493 VerticalFontMetric::Baseline => Em::zero(),
494 VerticalFontMetric::Descender => self.descender,
495 }
496 }
497}
498
499#[derive(Debug, Copy, Clone)]
501pub struct LineMetrics {
502 pub position: Em,
505 pub thickness: Em,
507}
508
509#[derive(Debug, Copy, Clone)]
511pub struct ScriptMetrics {
512 pub width: Em,
514 pub height: Em,
516 pub horizontal_offset: Em,
521 pub vertical_offset: Em,
525}
526
527#[derive(Debug, Copy, Clone)]
531pub struct MathConstants {
532 pub space_width: Em,
534 pub script_percent_scale_down: i16,
536 pub script_script_percent_scale_down: i16,
537 pub display_operator_min_height: Em,
538 pub axis_height: Em,
539 pub accent_base_height: Em,
540 pub flattened_accent_base_height: Em,
541 pub subscript_shift_down: Em,
542 pub subscript_top_max: Em,
543 pub subscript_baseline_drop_min: Em,
544 pub superscript_shift_up: Em,
545 pub superscript_shift_up_cramped: Em,
546 pub superscript_bottom_min: Em,
547 pub superscript_baseline_drop_max: Em,
548 pub sub_superscript_gap_min: Em,
549 pub superscript_bottom_max_with_subscript: Em,
550 pub space_after_script: Em,
551 pub upper_limit_gap_min: Em,
552 pub upper_limit_baseline_rise_min: Em,
553 pub lower_limit_gap_min: Em,
554 pub lower_limit_baseline_drop_min: Em,
555 pub fraction_numerator_shift_up: Em,
556 pub fraction_numerator_display_style_shift_up: Em,
557 pub fraction_denominator_shift_down: Em,
558 pub fraction_denominator_display_style_shift_down: Em,
559 pub fraction_numerator_gap_min: Em,
560 pub fraction_num_display_style_gap_min: Em,
561 pub fraction_rule_thickness: Em,
562 pub fraction_denominator_gap_min: Em,
563 pub fraction_denom_display_style_gap_min: Em,
564 pub skewed_fraction_vertical_gap: Em,
565 pub skewed_fraction_horizontal_gap: Em,
566 pub overbar_vertical_gap: Em,
567 pub overbar_rule_thickness: Em,
568 pub overbar_extra_ascender: Em,
569 pub underbar_vertical_gap: Em,
570 pub underbar_rule_thickness: Em,
571 pub underbar_extra_descender: Em,
572 pub radical_vertical_gap: Em,
573 pub radical_display_style_vertical_gap: Em,
574 pub radical_rule_thickness: Em,
575 pub radical_extra_ascender: Em,
576 pub radical_kern_before_degree: Em,
577 pub radical_kern_after_degree: Em,
578 pub radical_degree_bottom_raise_percent: f64,
579}
580
581#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
583pub enum VerticalFontMetric {
584 Ascender,
586 CapHeight,
588 XHeight,
590 Baseline,
592 Descender,
594}
595
596#[derive(Debug, Copy, Clone)]
598pub enum TextEdgeBounds<'a> {
599 Zero,
601 Glyph(u16),
603 Frame(&'a Frame),
605}