summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/text/font/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library/src/text/font/mod.rs')
-rw-r--r--crates/typst-library/src/text/font/mod.rs306
1 files changed, 306 insertions, 0 deletions
diff --git a/crates/typst-library/src/text/font/mod.rs b/crates/typst-library/src/text/font/mod.rs
new file mode 100644
index 00000000..09837312
--- /dev/null
+++ b/crates/typst-library/src/text/font/mod.rs
@@ -0,0 +1,306 @@
+//! Font handling.
+
+pub mod color;
+
+mod book;
+mod exceptions;
+mod variant;
+
+pub use self::book::{Coverage, FontBook, FontFlags, FontInfo};
+pub use self::variant::{FontStretch, FontStyle, FontVariant, FontWeight};
+
+use std::cell::OnceCell;
+use std::fmt::{self, Debug, Formatter};
+use std::hash::{Hash, Hasher};
+use std::sync::Arc;
+
+use ttf_parser::GlyphId;
+
+use self::book::find_name;
+use crate::foundations::{Bytes, Cast};
+use crate::layout::{Abs, Em, Frame};
+use crate::text::{BottomEdge, TopEdge};
+
+/// An OpenType font.
+///
+/// Values of this type are cheap to clone and hash.
+#[derive(Clone)]
+pub struct Font(Arc<Repr>);
+
+/// The internal representation of a font.
+struct Repr {
+ /// The raw font data, possibly shared with other fonts from the same
+ /// collection. The vector's allocation must not move, because `ttf` points
+ /// into it using unsafe code.
+ data: Bytes,
+ /// The font's index in the buffer.
+ index: u32,
+ /// Metadata about the font.
+ info: FontInfo,
+ /// The font's metrics.
+ metrics: FontMetrics,
+ /// The underlying ttf-parser face.
+ ttf: ttf_parser::Face<'static>,
+ /// The underlying rustybuzz face.
+ rusty: rustybuzz::Face<'static>,
+}
+
+impl Font {
+ /// Parse a font from data and collection index.
+ pub fn new(data: Bytes, index: u32) -> Option<Self> {
+ // Safety:
+ // - The slices's location is stable in memory:
+ // - We don't move the underlying vector
+ // - Nobody else can move it since we have a strong ref to the `Arc`.
+ // - The internal 'static lifetime is not leaked because its rewritten
+ // to the self-lifetime in `ttf()`.
+ let slice: &'static [u8] =
+ unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) };
+
+ let ttf = ttf_parser::Face::parse(slice, index).ok()?;
+ let rusty = rustybuzz::Face::from_slice(slice, index)?;
+ let metrics = FontMetrics::from_ttf(&ttf);
+ let info = FontInfo::from_ttf(&ttf)?;
+
+ Some(Self(Arc::new(Repr { data, index, info, metrics, ttf, rusty })))
+ }
+
+ /// Parse all fonts in the given data.
+ pub fn iter(data: Bytes) -> impl Iterator<Item = Self> {
+ let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1);
+ (0..count).filter_map(move |index| Self::new(data.clone(), index))
+ }
+
+ /// The underlying buffer.
+ pub fn data(&self) -> &Bytes {
+ &self.0.data
+ }
+
+ /// The font's index in the buffer.
+ pub fn index(&self) -> u32 {
+ self.0.index
+ }
+
+ /// The font's metadata.
+ pub fn info(&self) -> &FontInfo {
+ &self.0.info
+ }
+
+ /// The font's metrics.
+ pub fn metrics(&self) -> &FontMetrics {
+ &self.0.metrics
+ }
+
+ /// The number of font units per one em.
+ pub fn units_per_em(&self) -> f64 {
+ self.0.metrics.units_per_em
+ }
+
+ /// Convert from font units to an em length.
+ pub fn to_em(&self, units: impl Into<f64>) -> Em {
+ Em::from_units(units, self.units_per_em())
+ }
+
+ /// Look up the horizontal advance width of a glyph.
+ pub fn advance(&self, glyph: u16) -> Option<Em> {
+ self.0
+ .ttf
+ .glyph_hor_advance(GlyphId(glyph))
+ .map(|units| self.to_em(units))
+ }
+
+ /// Lookup a name by id.
+ pub fn find_name(&self, id: u16) -> Option<String> {
+ find_name(&self.0.ttf, id)
+ }
+
+ /// A reference to the underlying `ttf-parser` face.
+ pub fn ttf(&self) -> &ttf_parser::Face<'_> {
+ // We can't implement Deref because that would leak the
+ // internal 'static lifetime.
+ &self.0.ttf
+ }
+
+ /// A reference to the underlying `rustybuzz` face.
+ pub fn rusty(&self) -> &rustybuzz::Face<'_> {
+ // We can't implement Deref because that would leak the
+ // internal 'static lifetime.
+ &self.0.rusty
+ }
+
+ /// Resolve the top and bottom edges of text.
+ pub fn edges(
+ &self,
+ top_edge: TopEdge,
+ bottom_edge: BottomEdge,
+ font_size: Abs,
+ bounds: TextEdgeBounds,
+ ) -> (Abs, Abs) {
+ let cell = OnceCell::new();
+ let bbox = |gid, f: fn(ttf_parser::Rect) -> i16| {
+ cell.get_or_init(|| self.ttf().glyph_bounding_box(GlyphId(gid)))
+ .map(|bbox| self.to_em(f(bbox)).at(font_size))
+ .unwrap_or_default()
+ };
+
+ let top = match top_edge {
+ TopEdge::Metric(metric) => match metric.try_into() {
+ Ok(metric) => self.metrics().vertical(metric).at(font_size),
+ Err(_) => match bounds {
+ TextEdgeBounds::Zero => Abs::zero(),
+ TextEdgeBounds::Frame(frame) => frame.ascent(),
+ TextEdgeBounds::Glyph(gid) => bbox(gid, |b| b.y_max),
+ },
+ },
+ TopEdge::Length(length) => length.at(font_size),
+ };
+
+ let bottom = match bottom_edge {
+ BottomEdge::Metric(metric) => match metric.try_into() {
+ Ok(metric) => -self.metrics().vertical(metric).at(font_size),
+ Err(_) => match bounds {
+ TextEdgeBounds::Zero => Abs::zero(),
+ TextEdgeBounds::Frame(frame) => frame.descent(),
+ TextEdgeBounds::Glyph(gid) => -bbox(gid, |b| b.y_min),
+ },
+ },
+ BottomEdge::Length(length) => -length.at(font_size),
+ };
+
+ (top, bottom)
+ }
+}
+
+impl Hash for Font {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.0.data.hash(state);
+ self.0.index.hash(state);
+ }
+}
+
+impl Debug for Font {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Font({}, {:?})", self.info().family, self.info().variant)
+ }
+}
+
+impl Eq for Font {}
+
+impl PartialEq for Font {
+ fn eq(&self, other: &Self) -> bool {
+ self.0.data == other.0.data && self.0.index == other.0.index
+ }
+}
+
+/// Metrics of a font.
+#[derive(Debug, Copy, Clone)]
+pub struct FontMetrics {
+ /// How many font units represent one em unit.
+ pub units_per_em: f64,
+ /// The distance from the baseline to the typographic ascender.
+ pub ascender: Em,
+ /// The approximate height of uppercase letters.
+ pub cap_height: Em,
+ /// The approximate height of non-ascending lowercase letters.
+ pub x_height: Em,
+ /// The distance from the baseline to the typographic descender.
+ pub descender: Em,
+ /// Recommended metrics for a strikethrough line.
+ pub strikethrough: LineMetrics,
+ /// Recommended metrics for an underline.
+ pub underline: LineMetrics,
+ /// Recommended metrics for an overline.
+ pub overline: LineMetrics,
+}
+
+impl FontMetrics {
+ /// Extract the font's metrics.
+ pub fn from_ttf(ttf: &ttf_parser::Face) -> Self {
+ let units_per_em = f64::from(ttf.units_per_em());
+ let to_em = |units| Em::from_units(units, units_per_em);
+
+ let ascender = to_em(ttf.typographic_ascender().unwrap_or(ttf.ascender()));
+ let cap_height = ttf.capital_height().filter(|&h| h > 0).map_or(ascender, to_em);
+ let x_height = ttf.x_height().filter(|&h| h > 0).map_or(ascender, to_em);
+ let descender = to_em(ttf.typographic_descender().unwrap_or(ttf.descender()));
+ let strikeout = ttf.strikeout_metrics();
+ let underline = ttf.underline_metrics();
+
+ let strikethrough = LineMetrics {
+ position: strikeout.map_or(Em::new(0.25), |s| to_em(s.position)),
+ thickness: strikeout
+ .or(underline)
+ .map_or(Em::new(0.06), |s| to_em(s.thickness)),
+ };
+
+ let underline = LineMetrics {
+ position: underline.map_or(Em::new(-0.2), |s| to_em(s.position)),
+ thickness: underline
+ .or(strikeout)
+ .map_or(Em::new(0.06), |s| to_em(s.thickness)),
+ };
+
+ let overline = LineMetrics {
+ position: cap_height + Em::new(0.1),
+ thickness: underline.thickness,
+ };
+
+ Self {
+ units_per_em,
+ ascender,
+ cap_height,
+ x_height,
+ descender,
+ strikethrough,
+ underline,
+ overline,
+ }
+ }
+
+ /// Look up a vertical metric.
+ pub fn vertical(&self, metric: VerticalFontMetric) -> Em {
+ match metric {
+ VerticalFontMetric::Ascender => self.ascender,
+ VerticalFontMetric::CapHeight => self.cap_height,
+ VerticalFontMetric::XHeight => self.x_height,
+ VerticalFontMetric::Baseline => Em::zero(),
+ VerticalFontMetric::Descender => self.descender,
+ }
+ }
+}
+
+/// Metrics for a decorative line.
+#[derive(Debug, Copy, Clone)]
+pub struct LineMetrics {
+ /// The vertical offset of the line from the baseline. Positive goes
+ /// upwards, negative downwards.
+ pub position: Em,
+ /// The thickness of the line.
+ pub thickness: Em,
+}
+
+/// Identifies a vertical metric of a font.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
+pub enum VerticalFontMetric {
+ /// The font's ascender, which typically exceeds the height of all glyphs.
+ Ascender,
+ /// The approximate height of uppercase letters.
+ CapHeight,
+ /// The approximate height of non-ascending lowercase letters.
+ XHeight,
+ /// The baseline on which the letters rest.
+ Baseline,
+ /// The font's ascender, which typically exceeds the depth of all glyphs.
+ Descender,
+}
+
+/// Defines how to resolve a `Bounds` text edge.
+#[derive(Debug, Copy, Clone)]
+pub enum TextEdgeBounds<'a> {
+ /// Set the bounds to zero.
+ Zero,
+ /// Use the bounding box of the given glyph for the bounds.
+ Glyph(u16),
+ /// Use the dimension of the given frame for the bounds.
+ Frame(&'a Frame),
+}