mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Refactor and fix bounds metric (#5186)
This commit is contained in:
parent
bb0e089474
commit
61d461f080
@ -16,7 +16,7 @@ use crate::foundations::{Smart, StyleChain};
|
|||||||
use crate::layout::{Abs, Dir, Em, Frame, FrameItem, Point, Size};
|
use crate::layout::{Abs, Dir, Em, Frame, FrameItem, Point, Size};
|
||||||
use crate::text::{
|
use crate::text::{
|
||||||
decorate, families, features, is_default_ignorable, variant, Font, FontVariant,
|
decorate, families, features, is_default_ignorable, variant, Font, FontVariant,
|
||||||
Glyph, Lang, Region, TextElem, TextItem,
|
Glyph, Lang, Region, TextEdgeBounds, TextElem, TextItem,
|
||||||
};
|
};
|
||||||
use crate::utils::SliceExt;
|
use crate::utils::SliceExt;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
@ -338,9 +338,10 @@ impl<'a> ShapedText<'a> {
|
|||||||
let bottom_edge = TextElem::bottom_edge_in(self.styles);
|
let bottom_edge = TextElem::bottom_edge_in(self.styles);
|
||||||
|
|
||||||
// Expand top and bottom by reading the font's vertical metrics.
|
// Expand top and bottom by reading the font's vertical metrics.
|
||||||
let mut expand = |font: &Font, bbox: Option<ttf_parser::Rect>| {
|
let mut expand = |font: &Font, bounds: TextEdgeBounds| {
|
||||||
top.set_max(top_edge.resolve(self.size, font, bbox));
|
let (t, b) = font.edges(top_edge, bottom_edge, self.size, bounds);
|
||||||
bottom.set_max(-bottom_edge.resolve(self.size, font, bbox));
|
top.set_max(t);
|
||||||
|
bottom.set_max(b);
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.glyphs.is_empty() {
|
if self.glyphs.is_empty() {
|
||||||
@ -353,18 +354,13 @@ impl<'a> ShapedText<'a> {
|
|||||||
.select(family, self.variant)
|
.select(family, self.variant)
|
||||||
.and_then(|id| world.font(id))
|
.and_then(|id| world.font(id))
|
||||||
{
|
{
|
||||||
expand(&font, None);
|
expand(&font, TextEdgeBounds::Zero);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for g in self.glyphs.iter() {
|
for g in self.glyphs.iter() {
|
||||||
let bbox = if top_edge.is_bounds() || bottom_edge.is_bounds() {
|
expand(&g.font, TextEdgeBounds::Glyph(g.glyph_id));
|
||||||
g.font.ttf().glyph_bounding_box(ttf_parser::GlyphId(g.glyph_id))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
expand(&g.font, bbox);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,8 @@ use crate::math::{
|
|||||||
use crate::model::{Numbering, Outlinable, ParElem, ParLine, Refable, Supplement};
|
use crate::model::{Numbering, Outlinable, ParElem, ParLine, Refable, Supplement};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::text::{
|
use crate::text::{
|
||||||
families, variant, Font, FontFamily, FontList, FontWeight, LocalName, TextElem,
|
families, variant, Font, FontFamily, FontList, FontWeight, LocalName, TextEdgeBounds,
|
||||||
|
TextElem,
|
||||||
};
|
};
|
||||||
use crate::utils::{NonZeroExt, Numeric};
|
use crate::utils::{NonZeroExt, Numeric};
|
||||||
use crate::World;
|
use crate::World;
|
||||||
@ -290,12 +291,16 @@ fn layout_equation_inline(
|
|||||||
|
|
||||||
let font_size = scaled_font_size(&ctx, styles);
|
let font_size = scaled_font_size(&ctx, styles);
|
||||||
let slack = ParElem::leading_in(styles) * 0.7;
|
let slack = ParElem::leading_in(styles) * 0.7;
|
||||||
let top_edge = TextElem::top_edge_in(styles).resolve(font_size, &font, None);
|
|
||||||
let bottom_edge =
|
|
||||||
-TextElem::bottom_edge_in(styles).resolve(font_size, &font, None);
|
|
||||||
|
|
||||||
let ascent = top_edge.max(frame.ascent() - slack);
|
let (t, b) = font.edges(
|
||||||
let descent = bottom_edge.max(frame.descent() - slack);
|
TextElem::top_edge_in(styles),
|
||||||
|
TextElem::bottom_edge_in(styles),
|
||||||
|
font_size,
|
||||||
|
TextEdgeBounds::Frame(frame),
|
||||||
|
);
|
||||||
|
|
||||||
|
let ascent = t.max(frame.ascent() - slack);
|
||||||
|
let descent = b.max(frame.descent() - slack);
|
||||||
frame.translate(Point::with_y(ascent - frame.baseline()));
|
frame.translate(Point::with_y(ascent - frame.baseline()));
|
||||||
frame.size_mut().y = ascent + descent;
|
frame.size_mut().y = ascent + descent;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,8 @@ use crate::layout::{
|
|||||||
};
|
};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::text::{
|
use crate::text::{
|
||||||
BottomEdge, BottomEdgeMetric, TextElem, TextItem, TopEdge, TopEdgeMetric,
|
BottomEdge, BottomEdgeMetric, TextEdgeBounds, TextElem, TextItem, TopEdge,
|
||||||
|
TopEdgeMetric,
|
||||||
};
|
};
|
||||||
use crate::visualize::{styled_rect, Color, FixedStroke, Geometry, Paint, Stroke};
|
use crate::visualize::{styled_rect, Color, FixedStroke, Geometry, Paint, Stroke};
|
||||||
|
|
||||||
@ -422,7 +423,7 @@ pub(crate) fn decorate(
|
|||||||
&deco.line
|
&deco.line
|
||||||
{
|
{
|
||||||
let (top, bottom) = determine_edges(text, *top_edge, *bottom_edge);
|
let (top, bottom) = determine_edges(text, *top_edge, *bottom_edge);
|
||||||
let size = Size::new(width + 2.0 * deco.extent, top - bottom);
|
let size = Size::new(width + 2.0 * deco.extent, top + bottom);
|
||||||
let rects = styled_rect(size, radius, fill.clone(), stroke);
|
let rects = styled_rect(size, radius, fill.clone(), stroke);
|
||||||
let origin = Point::new(pos.x - deco.extent, pos.y - top - shift);
|
let origin = Point::new(pos.x - deco.extent, pos.y - top - shift);
|
||||||
frame.prepend_multiple(
|
frame.prepend_multiple(
|
||||||
@ -540,22 +541,20 @@ fn determine_edges(
|
|||||||
top_edge: TopEdge,
|
top_edge: TopEdge,
|
||||||
bottom_edge: BottomEdge,
|
bottom_edge: BottomEdge,
|
||||||
) -> (Abs, Abs) {
|
) -> (Abs, Abs) {
|
||||||
let mut bbox = None;
|
let mut top = Abs::zero();
|
||||||
if top_edge.is_bounds() || bottom_edge.is_bounds() {
|
let mut bottom = Abs::zero();
|
||||||
let ttf = text.font.ttf();
|
|
||||||
bbox = text
|
for g in text.glyphs.iter() {
|
||||||
.glyphs
|
let (t, b) = text.font.edges(
|
||||||
.iter()
|
top_edge,
|
||||||
.filter_map(|g| ttf.glyph_bounding_box(ttf_parser::GlyphId(g.id)))
|
bottom_edge,
|
||||||
.reduce(|a, b| ttf_parser::Rect {
|
text.size,
|
||||||
y_max: a.y_max.max(b.y_max),
|
TextEdgeBounds::Glyph(g.id),
|
||||||
y_min: a.y_min.min(b.y_min),
|
);
|
||||||
..a
|
top.set_max(t);
|
||||||
});
|
bottom.set_max(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
let top = top_edge.resolve(text.size, &text.font, bbox);
|
|
||||||
let bottom = bottom_edge.resolve(text.size, &text.font, bbox);
|
|
||||||
(top, bottom)
|
(top, bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ mod variant;
|
|||||||
pub use self::book::{Coverage, FontBook, FontFlags, FontInfo};
|
pub use self::book::{Coverage, FontBook, FontFlags, FontInfo};
|
||||||
pub use self::variant::{FontStretch, FontStyle, FontVariant, FontWeight};
|
pub use self::variant::{FontStretch, FontStyle, FontVariant, FontWeight};
|
||||||
|
|
||||||
|
use std::cell::OnceCell;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -17,7 +18,8 @@ use ttf_parser::GlyphId;
|
|||||||
|
|
||||||
use self::book::find_name;
|
use self::book::find_name;
|
||||||
use crate::foundations::{Bytes, Cast};
|
use crate::foundations::{Bytes, Cast};
|
||||||
use crate::layout::Em;
|
use crate::layout::{Abs, Em, Frame};
|
||||||
|
use crate::text::{BottomEdge, TopEdge};
|
||||||
|
|
||||||
/// An OpenType font.
|
/// An OpenType font.
|
||||||
///
|
///
|
||||||
@ -125,6 +127,48 @@ impl Font {
|
|||||||
// internal 'static lifetime.
|
// internal 'static lifetime.
|
||||||
&self.0.rusty
|
&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 {
|
impl Hash for Font {
|
||||||
@ -249,3 +293,14 @@ pub enum VerticalFontMetric {
|
|||||||
/// The font's ascender, which typically exceeds the depth of all glyphs.
|
/// The font's ascender, which typically exceeds the depth of all glyphs.
|
||||||
Descender,
|
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),
|
||||||
|
}
|
||||||
|
@ -37,7 +37,7 @@ use icu_provider_blob::BlobDataProvider;
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use rustybuzz::Feature;
|
use rustybuzz::Feature;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use ttf_parser::{Rect, Tag};
|
use ttf_parser::Tag;
|
||||||
|
|
||||||
use crate::diag::{bail, warning, HintedStrResult, SourceResult};
|
use crate::diag::{bail, warning, HintedStrResult, SourceResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
@ -891,28 +891,6 @@ pub enum TopEdge {
|
|||||||
Length(Length),
|
Length(Length),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TopEdge {
|
|
||||||
/// Determine if the edge is specified from bounding box info.
|
|
||||||
pub fn is_bounds(&self) -> bool {
|
|
||||||
matches!(self, Self::Metric(TopEdgeMetric::Bounds))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolve the value of the text edge given a font's metrics.
|
|
||||||
pub fn resolve(self, font_size: Abs, font: &Font, bbox: Option<Rect>) -> Abs {
|
|
||||||
match self {
|
|
||||||
TopEdge::Metric(metric) => {
|
|
||||||
if let Ok(metric) = metric.try_into() {
|
|
||||||
font.metrics().vertical(metric).at(font_size)
|
|
||||||
} else {
|
|
||||||
bbox.map(|bbox| (font.to_em(bbox.y_max)).at(font_size))
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TopEdge::Length(length) => length.at(font_size),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cast! {
|
cast! {
|
||||||
TopEdge,
|
TopEdge,
|
||||||
self => match self {
|
self => match self {
|
||||||
@ -961,28 +939,6 @@ pub enum BottomEdge {
|
|||||||
Length(Length),
|
Length(Length),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BottomEdge {
|
|
||||||
/// Determine if the edge is specified from bounding box info.
|
|
||||||
pub fn is_bounds(&self) -> bool {
|
|
||||||
matches!(self, Self::Metric(BottomEdgeMetric::Bounds))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolve the value of the text edge given a font's metrics.
|
|
||||||
pub fn resolve(self, font_size: Abs, font: &Font, bbox: Option<Rect>) -> Abs {
|
|
||||||
match self {
|
|
||||||
BottomEdge::Metric(metric) => {
|
|
||||||
if let Ok(metric) = metric.try_into() {
|
|
||||||
font.metrics().vertical(metric).at(font_size)
|
|
||||||
} else {
|
|
||||||
bbox.map(|bbox| (font.to_em(bbox.y_min)).at(font_size))
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BottomEdge::Length(length) => length.at(font_size),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cast! {
|
cast! {
|
||||||
BottomEdge,
|
BottomEdge,
|
||||||
self => match self {
|
self => match self {
|
||||||
|
@ -92,3 +92,10 @@
|
|||||||
table(columns: 5, u(17), it, u(1), it, u(5))
|
table(columns: 5, u(17), it, u(1), it, u(5))
|
||||||
[#size.width] // 17pt
|
[#size.width] // 17pt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--- issue-5180-measure-inline-math-bounds ---
|
||||||
|
#context {
|
||||||
|
let height = measure(text(top-edge: "bounds", $x$)).height
|
||||||
|
assert(height > 4pt)
|
||||||
|
assert(height < 5pt)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user