typst_library/text/
item.rs1use std::fmt::{self, Debug, Formatter};
2use std::ops::Range;
3
4use ecow::EcoString;
5use typst_syntax::Span;
6
7use crate::layout::{Abs, Em, Point, Rect};
8use crate::text::{Font, Lang, Region, is_default_ignorable};
9use crate::visualize::{FixedStroke, Paint};
10
11#[derive(Clone, Eq, PartialEq, Hash)]
13pub struct TextItem {
14 pub font: Font,
16 pub size: Abs,
18 pub fill: Paint,
20 pub stroke: Option<FixedStroke>,
22 pub lang: Lang,
24 pub region: Option<Region>,
26 pub text: EcoString,
28 pub glyphs: Vec<Glyph>,
31}
32
33impl TextItem {
34 pub fn width(&self) -> Abs {
36 self.glyphs.iter().map(|g| g.x_advance).sum::<Em>().at(self.size)
37 }
38
39 pub fn height(&self) -> Abs {
41 self.glyphs.iter().map(|g| g.y_advance).sum::<Em>().at(self.size)
42 }
43
44 #[comemo::memoize]
46 pub fn bbox(&self) -> Rect {
47 let mut min = Point::splat(Abs::inf());
48 let mut max = Point::splat(-Abs::inf());
49 let mut cursor = Point::zero();
50
51 for glyph in self.glyphs.iter() {
52 let advance =
53 Point::new(glyph.x_advance.at(self.size), glyph.y_advance.at(self.size));
54 let offset =
55 Point::new(glyph.x_offset.at(self.size), glyph.y_offset.at(self.size));
56 if let Some(rect) =
57 self.font.ttf().glyph_bounding_box(ttf_parser::GlyphId(glyph.id))
58 {
59 let pos = cursor + offset;
60 let a = pos
61 + Point::new(
62 self.font.to_em(rect.x_min).at(self.size),
63 self.font.to_em(rect.y_min).at(self.size),
64 );
65 let b = pos
66 + Point::new(
67 self.font.to_em(rect.x_max).at(self.size),
68 self.font.to_em(rect.y_max).at(self.size),
69 );
70 min = min.min(a).min(b);
71 max = max.max(a).max(b);
72 }
73 cursor += advance;
74 }
75
76 min.y *= -1.0;
79 max.y *= -1.0;
80 Rect::new(min, max)
81 }
82}
83
84impl Debug for TextItem {
85 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
86 f.write_str("Text(")?;
87 self.text.fmt(f)?;
88 f.write_str(")")
89 }
90}
91
92#[derive(Debug, Clone, Eq, PartialEq, Hash)]
94pub struct Glyph {
95 pub id: u16,
97 pub x_advance: Em,
99 pub x_offset: Em,
101 pub y_advance: Em,
103 pub y_offset: Em,
105 pub range: Range<u16>,
108 pub span: (Span, u16),
110}
111
112impl Glyph {
113 pub fn range(&self) -> Range<usize> {
115 usize::from(self.range.start)..usize::from(self.range.end)
116 }
117}
118
119pub struct TextItemView<'a> {
121 pub item: &'a TextItem,
123 pub glyph_range: Range<usize>,
125}
126
127impl<'a> TextItemView<'a> {
128 pub fn full(text: &'a TextItem) -> Self {
130 Self::from_glyph_range(text, 0..text.glyphs.len())
131 }
132
133 pub fn from_glyph_range(text: &'a TextItem, glyph_range: Range<usize>) -> Self {
135 TextItemView { item: text, glyph_range }
136 }
137
138 pub fn glyphs(&self) -> &[Glyph] {
143 &self.item.glyphs[self.glyph_range.clone()]
144 }
145
146 pub fn glyph_text(&self, glyph: &Glyph) -> EcoString {
149 self.item.text[glyph.range()]
154 .trim_matches(is_default_ignorable)
155 .into()
156 }
157
158 pub fn width(&self) -> Abs {
160 self.glyphs()
161 .iter()
162 .map(|g| g.x_advance)
163 .sum::<Em>()
164 .at(self.item.size)
165 }
166
167 pub fn height(&self) -> Abs {
169 self.glyphs()
170 .iter()
171 .map(|g| g.y_advance)
172 .sum::<Em>()
173 .at(self.item.size)
174 }
175}