Skip to content

Commit 2a956bb

Browse files
committed
Refactor and fix bounds metric (#5186)
1 parent 4e46fb1 commit 2a956bb

File tree

6 files changed

+97
-79
lines changed

6 files changed

+97
-79
lines changed

crates/typst/src/layout/inline/shaping.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::foundations::{Smart, StyleChain};
1616
use crate::layout::{Abs, Dir, Em, Frame, FrameItem, Point, Size};
1717
use crate::text::{
1818
decorate, families, features, is_default_ignorable, variant, Font, FontVariant,
19-
Glyph, Lang, Region, TextElem, TextItem,
19+
Glyph, Lang, Region, TextEdgeBounds, TextElem, TextItem,
2020
};
2121
use crate::utils::SliceExt;
2222
use crate::World;
@@ -338,9 +338,10 @@ impl<'a> ShapedText<'a> {
338338
let bottom_edge = TextElem::bottom_edge_in(self.styles);
339339

340340
// Expand top and bottom by reading the font's vertical metrics.
341-
let mut expand = |font: &Font, bbox: Option<ttf_parser::Rect>| {
342-
top.set_max(top_edge.resolve(self.size, font, bbox));
343-
bottom.set_max(-bottom_edge.resolve(self.size, font, bbox));
341+
let mut expand = |font: &Font, bounds: TextEdgeBounds| {
342+
let (t, b) = font.edges(top_edge, bottom_edge, self.size, bounds);
343+
top.set_max(t);
344+
bottom.set_max(b);
344345
};
345346

346347
if self.glyphs.is_empty() {
@@ -353,18 +354,13 @@ impl<'a> ShapedText<'a> {
353354
.select(family, self.variant)
354355
.and_then(|id| world.font(id))
355356
{
356-
expand(&font, None);
357+
expand(&font, TextEdgeBounds::Zero);
357358
break;
358359
}
359360
}
360361
} else {
361362
for g in self.glyphs.iter() {
362-
let bbox = if top_edge.is_bounds() || bottom_edge.is_bounds() {
363-
g.font.ttf().glyph_bounding_box(ttf_parser::GlyphId(g.glyph_id))
364-
} else {
365-
None
366-
};
367-
expand(&g.font, bbox);
363+
expand(&g.font, TextEdgeBounds::Glyph(g.glyph_id));
368364
}
369365
}
370366

crates/typst/src/math/equation.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ use crate::math::{
2020
use crate::model::{Numbering, Outlinable, ParElem, ParLine, Refable, Supplement};
2121
use crate::syntax::Span;
2222
use crate::text::{
23-
families, variant, Font, FontFamily, FontList, FontWeight, LocalName, TextElem,
23+
families, variant, Font, FontFamily, FontList, FontWeight, LocalName, TextEdgeBounds,
24+
TextElem,
2425
};
2526
use crate::utils::{NonZeroExt, Numeric};
2627
use crate::World;
@@ -290,12 +291,16 @@ fn layout_equation_inline(
290291

291292
let font_size = scaled_font_size(&ctx, styles);
292293
let slack = ParElem::leading_in(styles) * 0.7;
293-
let top_edge = TextElem::top_edge_in(styles).resolve(font_size, &font, None);
294-
let bottom_edge =
295-
-TextElem::bottom_edge_in(styles).resolve(font_size, &font, None);
296294

297-
let ascent = top_edge.max(frame.ascent() - slack);
298-
let descent = bottom_edge.max(frame.descent() - slack);
295+
let (t, b) = font.edges(
296+
TextElem::top_edge_in(styles),
297+
TextElem::bottom_edge_in(styles),
298+
font_size,
299+
TextEdgeBounds::Frame(frame),
300+
);
301+
302+
let ascent = t.max(frame.ascent() - slack);
303+
let descent = b.max(frame.descent() - slack);
299304
frame.translate(Point::with_y(ascent - frame.baseline()));
300305
frame.size_mut().y = ascent + descent;
301306
}

crates/typst/src/text/deco.rs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use crate::layout::{
1010
};
1111
use crate::syntax::Span;
1212
use crate::text::{
13-
BottomEdge, BottomEdgeMetric, TextElem, TextItem, TopEdge, TopEdgeMetric,
13+
BottomEdge, BottomEdgeMetric, TextEdgeBounds, TextElem, TextItem, TopEdge,
14+
TopEdgeMetric,
1415
};
1516
use crate::visualize::{styled_rect, Color, FixedStroke, Geometry, Paint, Stroke};
1617

@@ -422,7 +423,7 @@ pub(crate) fn decorate(
422423
&deco.line
423424
{
424425
let (top, bottom) = determine_edges(text, *top_edge, *bottom_edge);
425-
let size = Size::new(width + 2.0 * deco.extent, top - bottom);
426+
let size = Size::new(width + 2.0 * deco.extent, top + bottom);
426427
let rects = styled_rect(size, radius, fill.clone(), stroke);
427428
let origin = Point::new(pos.x - deco.extent, pos.y - top - shift);
428429
frame.prepend_multiple(
@@ -540,22 +541,20 @@ fn determine_edges(
540541
top_edge: TopEdge,
541542
bottom_edge: BottomEdge,
542543
) -> (Abs, Abs) {
543-
let mut bbox = None;
544-
if top_edge.is_bounds() || bottom_edge.is_bounds() {
545-
let ttf = text.font.ttf();
546-
bbox = text
547-
.glyphs
548-
.iter()
549-
.filter_map(|g| ttf.glyph_bounding_box(ttf_parser::GlyphId(g.id)))
550-
.reduce(|a, b| ttf_parser::Rect {
551-
y_max: a.y_max.max(b.y_max),
552-
y_min: a.y_min.min(b.y_min),
553-
..a
554-
});
544+
let mut top = Abs::zero();
545+
let mut bottom = Abs::zero();
546+
547+
for g in text.glyphs.iter() {
548+
let (t, b) = text.font.edges(
549+
top_edge,
550+
bottom_edge,
551+
text.size,
552+
TextEdgeBounds::Glyph(g.id),
553+
);
554+
top.set_max(t);
555+
bottom.set_max(b);
555556
}
556557

557-
let top = top_edge.resolve(text.size, &text.font, bbox);
558-
let bottom = bottom_edge.resolve(text.size, &text.font, bbox);
559558
(top, bottom)
560559
}
561560

crates/typst/src/text/font/mod.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod variant;
99
pub use self::book::{Coverage, FontBook, FontFlags, FontInfo};
1010
pub use self::variant::{FontStretch, FontStyle, FontVariant, FontWeight};
1111

12+
use std::cell::OnceCell;
1213
use std::fmt::{self, Debug, Formatter};
1314
use std::hash::{Hash, Hasher};
1415
use std::sync::Arc;
@@ -17,7 +18,8 @@ use ttf_parser::GlyphId;
1718

1819
use self::book::find_name;
1920
use crate::foundations::{Bytes, Cast};
20-
use crate::layout::Em;
21+
use crate::layout::{Abs, Em, Frame};
22+
use crate::text::{BottomEdge, TopEdge};
2123

2224
/// An OpenType font.
2325
///
@@ -125,6 +127,48 @@ impl Font {
125127
// internal 'static lifetime.
126128
&self.0.rusty
127129
}
130+
131+
/// Resolve the top and bottom edges of text.
132+
pub fn edges(
133+
&self,
134+
top_edge: TopEdge,
135+
bottom_edge: BottomEdge,
136+
font_size: Abs,
137+
bounds: TextEdgeBounds,
138+
) -> (Abs, Abs) {
139+
let cell = OnceCell::new();
140+
let bbox = |gid, f: fn(ttf_parser::Rect) -> i16| {
141+
cell.get_or_init(|| self.ttf().glyph_bounding_box(GlyphId(gid)))
142+
.map(|bbox| self.to_em(f(bbox)).at(font_size))
143+
.unwrap_or_default()
144+
};
145+
146+
let top = match top_edge {
147+
TopEdge::Metric(metric) => match metric.try_into() {
148+
Ok(metric) => self.metrics().vertical(metric).at(font_size),
149+
Err(_) => match bounds {
150+
TextEdgeBounds::Zero => Abs::zero(),
151+
TextEdgeBounds::Frame(frame) => frame.ascent(),
152+
TextEdgeBounds::Glyph(gid) => bbox(gid, |b| b.y_max),
153+
},
154+
},
155+
TopEdge::Length(length) => length.at(font_size),
156+
};
157+
158+
let bottom = match bottom_edge {
159+
BottomEdge::Metric(metric) => match metric.try_into() {
160+
Ok(metric) => -self.metrics().vertical(metric).at(font_size),
161+
Err(_) => match bounds {
162+
TextEdgeBounds::Zero => Abs::zero(),
163+
TextEdgeBounds::Frame(frame) => frame.descent(),
164+
TextEdgeBounds::Glyph(gid) => -bbox(gid, |b| b.y_min),
165+
},
166+
},
167+
BottomEdge::Length(length) => -length.at(font_size),
168+
};
169+
170+
(top, bottom)
171+
}
128172
}
129173

130174
impl Hash for Font {
@@ -249,3 +293,14 @@ pub enum VerticalFontMetric {
249293
/// The font's ascender, which typically exceeds the depth of all glyphs.
250294
Descender,
251295
}
296+
297+
/// Defines how to resolve a `Bounds` text edge.
298+
#[derive(Debug, Copy, Clone)]
299+
pub enum TextEdgeBounds<'a> {
300+
/// Set the bounds to zero.
301+
Zero,
302+
/// Use the bounding box of the given glyph for the bounds.
303+
Glyph(u16),
304+
/// Use the dimension of the given frame for the bounds.
305+
Frame(&'a Frame),
306+
}

crates/typst/src/text/mod.rs

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ use icu_provider_blob::BlobDataProvider;
3737
use once_cell::sync::Lazy;
3838
use rustybuzz::Feature;
3939
use smallvec::SmallVec;
40-
use ttf_parser::{Rect, Tag};
40+
use ttf_parser::Tag;
4141

4242
use crate::diag::{bail, warning, HintedStrResult, SourceResult};
4343
use crate::engine::Engine;
@@ -891,28 +891,6 @@ pub enum TopEdge {
891891
Length(Length),
892892
}
893893

894-
impl TopEdge {
895-
/// Determine if the edge is specified from bounding box info.
896-
pub fn is_bounds(&self) -> bool {
897-
matches!(self, Self::Metric(TopEdgeMetric::Bounds))
898-
}
899-
900-
/// Resolve the value of the text edge given a font's metrics.
901-
pub fn resolve(self, font_size: Abs, font: &Font, bbox: Option<Rect>) -> Abs {
902-
match self {
903-
TopEdge::Metric(metric) => {
904-
if let Ok(metric) = metric.try_into() {
905-
font.metrics().vertical(metric).at(font_size)
906-
} else {
907-
bbox.map(|bbox| (font.to_em(bbox.y_max)).at(font_size))
908-
.unwrap_or_default()
909-
}
910-
}
911-
TopEdge::Length(length) => length.at(font_size),
912-
}
913-
}
914-
}
915-
916894
cast! {
917895
TopEdge,
918896
self => match self {
@@ -961,28 +939,6 @@ pub enum BottomEdge {
961939
Length(Length),
962940
}
963941

964-
impl BottomEdge {
965-
/// Determine if the edge is specified from bounding box info.
966-
pub fn is_bounds(&self) -> bool {
967-
matches!(self, Self::Metric(BottomEdgeMetric::Bounds))
968-
}
969-
970-
/// Resolve the value of the text edge given a font's metrics.
971-
pub fn resolve(self, font_size: Abs, font: &Font, bbox: Option<Rect>) -> Abs {
972-
match self {
973-
BottomEdge::Metric(metric) => {
974-
if let Ok(metric) = metric.try_into() {
975-
font.metrics().vertical(metric).at(font_size)
976-
} else {
977-
bbox.map(|bbox| (font.to_em(bbox.y_min)).at(font_size))
978-
.unwrap_or_default()
979-
}
980-
}
981-
BottomEdge::Length(length) => length.at(font_size),
982-
}
983-
}
984-
}
985-
986942
cast! {
987943
BottomEdge,
988944
self => match self {

tests/suite/layout/measure.typ

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,10 @@
9292
table(columns: 5, u(17), it, u(1), it, u(5))
9393
[#size.width] // 17pt
9494
}
95+
96+
--- issue-5180-measure-inline-math-bounds ---
97+
#context {
98+
let height = measure(text(top-edge: "bounds", $x$)).height
99+
assert(height > 4pt)
100+
assert(height < 5pt)
101+
}

0 commit comments

Comments
 (0)