mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Merge 080c25c3620356b85f9174a588a505a4f6070adb into 9b09146a6b5e936966ed7ee73bce9dd2df3810ae
This commit is contained in:
commit
a07ade7e01
@ -1,7 +1,7 @@
|
|||||||
use typst_library::diag::SourceResult;
|
use typst_library::diag::SourceResult;
|
||||||
use typst_library::foundations::{Packed, StyleChain};
|
use typst_library::foundations::{Packed, StyleChain};
|
||||||
use typst_library::layout::{Em, Frame, Point, Size};
|
use typst_library::layout::{Em, Frame, Point, Size};
|
||||||
use typst_library::math::{Accent, AccentElem};
|
use typst_library::math::AccentElem;
|
||||||
|
|
||||||
use super::{style_cramped, FrameFragment, GlyphFragment, MathContext, MathFragment};
|
use super::{style_cramped, FrameFragment, GlyphFragment, MathContext, MathFragment};
|
||||||
|
|
||||||
@ -18,8 +18,11 @@ pub fn layout_accent(
|
|||||||
let cramped = style_cramped();
|
let cramped = style_cramped();
|
||||||
let mut base = ctx.layout_into_fragment(&elem.base, styles.chain(&cramped))?;
|
let mut base = ctx.layout_into_fragment(&elem.base, styles.chain(&cramped))?;
|
||||||
|
|
||||||
// Try to replace a glyph with its dotless variant.
|
let accent = elem.accent;
|
||||||
if elem.dotless(styles) {
|
let top_accent = !accent.is_bottom();
|
||||||
|
|
||||||
|
// Try to replace base glyph with its dotless variant.
|
||||||
|
if top_accent && elem.dotless(styles) {
|
||||||
if let MathFragment::Glyph(glyph) = &mut base {
|
if let MathFragment::Glyph(glyph) = &mut base {
|
||||||
glyph.make_dotless_form(ctx);
|
glyph.make_dotless_form(ctx);
|
||||||
}
|
}
|
||||||
@ -29,41 +32,54 @@ pub fn layout_accent(
|
|||||||
let base_class = base.class();
|
let base_class = base.class();
|
||||||
let base_attach = base.accent_attach();
|
let base_attach = base.accent_attach();
|
||||||
|
|
||||||
let width = elem.size(styles).relative_to(base.width());
|
let mut glyph = GlyphFragment::new(ctx, styles, accent.0, elem.span());
|
||||||
|
|
||||||
let Accent(c) = elem.accent;
|
// Try to replace accent glyph with its flattened variant.
|
||||||
let mut glyph = GlyphFragment::new(ctx, styles, c, elem.span());
|
if top_accent {
|
||||||
|
let flattened_base_height = scaled!(ctx, styles, flattened_accent_base_height);
|
||||||
// Try to replace accent glyph with flattened variant.
|
if base.ascent() > flattened_base_height {
|
||||||
let flattened_base_height = scaled!(ctx, styles, flattened_accent_base_height);
|
glyph.make_flattened_accent_form(ctx);
|
||||||
if base.ascent() > flattened_base_height {
|
}
|
||||||
glyph.make_flattened_accent_form(ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forcing the accent to be at least as large as the base makes it too
|
// Forcing the accent to be at least as large as the base makes it too
|
||||||
// wide in many case.
|
// wide in many case.
|
||||||
|
let width = elem.size(styles).relative_to(base.width());
|
||||||
let short_fall = ACCENT_SHORT_FALL.at(glyph.font_size);
|
let short_fall = ACCENT_SHORT_FALL.at(glyph.font_size);
|
||||||
let variant = glyph.stretch_horizontal(ctx, width, short_fall);
|
let variant = glyph.stretch_horizontal(ctx, width, short_fall);
|
||||||
let accent = variant.frame;
|
let accent = variant.frame;
|
||||||
let accent_attach = variant.accent_attach;
|
let accent_attach = variant.accent_attach.0;
|
||||||
|
|
||||||
|
let (gap, accent_pos, base_pos) = if top_accent {
|
||||||
|
// Descent is negative because the accent's ink bottom is above the
|
||||||
|
// baseline. Therefore, the default gap is the accent's negated descent
|
||||||
|
// minus the accent base height. Only if the base is very small, we
|
||||||
|
// need a larger gap so that the accent doesn't move too low.
|
||||||
|
let accent_base_height = scaled!(ctx, styles, accent_base_height);
|
||||||
|
let gap = -accent.descent() - base.ascent().min(accent_base_height);
|
||||||
|
let accent_pos = Point::with_x(base_attach.0 - accent_attach);
|
||||||
|
let base_pos = Point::with_y(accent.height() + gap);
|
||||||
|
(gap, accent_pos, base_pos)
|
||||||
|
} else {
|
||||||
|
let gap = -accent.ascent();
|
||||||
|
let accent_pos = Point::new(base_attach.1 - accent_attach, base.height() + gap);
|
||||||
|
let base_pos = Point::zero();
|
||||||
|
(gap, accent_pos, base_pos)
|
||||||
|
};
|
||||||
|
|
||||||
// Descent is negative because the accent's ink bottom is above the
|
|
||||||
// baseline. Therefore, the default gap is the accent's negated descent
|
|
||||||
// minus the accent base height. Only if the base is very small, we need
|
|
||||||
// a larger gap so that the accent doesn't move too low.
|
|
||||||
let accent_base_height = scaled!(ctx, styles, accent_base_height);
|
|
||||||
let gap = -accent.descent() - base.ascent().min(accent_base_height);
|
|
||||||
let size = Size::new(base.width(), accent.height() + gap + base.height());
|
let size = Size::new(base.width(), accent.height() + gap + base.height());
|
||||||
let accent_pos = Point::with_x(base_attach - accent_attach);
|
|
||||||
let base_pos = Point::with_y(accent.height() + gap);
|
|
||||||
let baseline = base_pos.y + base.ascent();
|
let baseline = base_pos.y + base.ascent();
|
||||||
|
|
||||||
let base_italics_correction = base.italics_correction();
|
let base_italics_correction = base.italics_correction();
|
||||||
let base_text_like = base.is_text_like();
|
let base_text_like = base.is_text_like();
|
||||||
|
|
||||||
let base_ascent = match &base {
|
let base_ascent = match &base {
|
||||||
MathFragment::Frame(frame) => frame.base_ascent,
|
MathFragment::Frame(frame) => frame.base_ascent,
|
||||||
_ => base.ascent(),
|
_ => base.ascent(),
|
||||||
};
|
};
|
||||||
|
let base_descent = match &base {
|
||||||
|
MathFragment::Frame(frame) => frame.base_descent,
|
||||||
|
_ => base.descent(),
|
||||||
|
};
|
||||||
|
|
||||||
let mut frame = Frame::soft(size);
|
let mut frame = Frame::soft(size);
|
||||||
frame.set_baseline(baseline);
|
frame.set_baseline(baseline);
|
||||||
@ -73,6 +89,7 @@ pub fn layout_accent(
|
|||||||
FrameFragment::new(styles, frame)
|
FrameFragment::new(styles, frame)
|
||||||
.with_class(base_class)
|
.with_class(base_class)
|
||||||
.with_base_ascent(base_ascent)
|
.with_base_ascent(base_ascent)
|
||||||
|
.with_base_descent(base_descent)
|
||||||
.with_italics_correction(base_italics_correction)
|
.with_italics_correction(base_italics_correction)
|
||||||
.with_accent_attach(base_attach)
|
.with_accent_attach(base_attach)
|
||||||
.with_text_like(base_text_like),
|
.with_text_like(base_text_like),
|
||||||
|
@ -434,9 +434,13 @@ fn compute_script_shifts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if bl.is_some() || br.is_some() {
|
if bl.is_some() || br.is_some() {
|
||||||
|
let descent = match &base {
|
||||||
|
MathFragment::Frame(frame) => frame.base_descent,
|
||||||
|
_ => base.descent(),
|
||||||
|
};
|
||||||
shift_down = shift_down
|
shift_down = shift_down
|
||||||
.max(sub_shift_down)
|
.max(sub_shift_down)
|
||||||
.max(if is_text_like { Abs::zero() } else { base.descent() + sub_drop_min })
|
.max(if is_text_like { Abs::zero() } else { descent + sub_drop_min })
|
||||||
.max(measure!(bl, ascent) - sub_top_max)
|
.max(measure!(bl, ascent) - sub_top_max)
|
||||||
.max(measure!(br, ascent) - sub_top_max);
|
.max(measure!(br, ascent) - sub_top_max);
|
||||||
}
|
}
|
||||||
|
@ -164,12 +164,12 @@ impl MathFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn accent_attach(&self) -> Abs {
|
pub fn accent_attach(&self) -> (Abs, Abs) {
|
||||||
match self {
|
match self {
|
||||||
Self::Glyph(glyph) => glyph.accent_attach,
|
Self::Glyph(glyph) => glyph.accent_attach,
|
||||||
Self::Variant(variant) => variant.accent_attach,
|
Self::Variant(variant) => variant.accent_attach,
|
||||||
Self::Frame(fragment) => fragment.accent_attach,
|
Self::Frame(fragment) => fragment.accent_attach,
|
||||||
_ => self.width() / 2.0,
|
_ => (self.width() / 2.0, self.width() / 2.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,7 +240,7 @@ pub struct GlyphFragment {
|
|||||||
pub ascent: Abs,
|
pub ascent: Abs,
|
||||||
pub descent: Abs,
|
pub descent: Abs,
|
||||||
pub italics_correction: Abs,
|
pub italics_correction: Abs,
|
||||||
pub accent_attach: Abs,
|
pub accent_attach: (Abs, Abs),
|
||||||
pub font_size: Abs,
|
pub font_size: Abs,
|
||||||
pub class: MathClass,
|
pub class: MathClass,
|
||||||
pub math_size: MathSize,
|
pub math_size: MathSize,
|
||||||
@ -294,7 +294,7 @@ impl GlyphFragment {
|
|||||||
descent: Abs::zero(),
|
descent: Abs::zero(),
|
||||||
limits: Limits::for_char(c),
|
limits: Limits::for_char(c),
|
||||||
italics_correction: Abs::zero(),
|
italics_correction: Abs::zero(),
|
||||||
accent_attach: Abs::zero(),
|
accent_attach: (Abs::zero(), Abs::zero()),
|
||||||
class,
|
class,
|
||||||
span,
|
span,
|
||||||
modifiers: FrameModifiers::get_in(styles),
|
modifiers: FrameModifiers::get_in(styles),
|
||||||
@ -326,8 +326,14 @@ impl GlyphFragment {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let mut width = advance.scaled(ctx, self.font_size);
|
let mut width = advance.scaled(ctx, self.font_size);
|
||||||
let accent_attach =
|
|
||||||
|
// The fallback for accents is half the width plus or minus the italics
|
||||||
|
// correction. This is similar to how top and bottom attachments are
|
||||||
|
// shifted. For bottom accents we do not use the accent attach of the
|
||||||
|
// base as it is meant for top acccents.
|
||||||
|
let top_accent_attach =
|
||||||
accent_attach(ctx, id, self.font_size).unwrap_or((width + italics) / 2.0);
|
accent_attach(ctx, id, self.font_size).unwrap_or((width + italics) / 2.0);
|
||||||
|
let bottom_accent_attach = (width - italics) / 2.0;
|
||||||
|
|
||||||
let extended_shape = is_extended_shape(ctx, id);
|
let extended_shape = is_extended_shape(ctx, id);
|
||||||
if !extended_shape {
|
if !extended_shape {
|
||||||
@ -339,7 +345,7 @@ impl GlyphFragment {
|
|||||||
self.ascent = bbox.y_max.scaled(ctx, self.font_size);
|
self.ascent = bbox.y_max.scaled(ctx, self.font_size);
|
||||||
self.descent = -bbox.y_min.scaled(ctx, self.font_size);
|
self.descent = -bbox.y_min.scaled(ctx, self.font_size);
|
||||||
self.italics_correction = italics;
|
self.italics_correction = italics;
|
||||||
self.accent_attach = accent_attach;
|
self.accent_attach = (top_accent_attach, bottom_accent_attach);
|
||||||
self.extended_shape = extended_shape;
|
self.extended_shape = extended_shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,7 +463,7 @@ impl Debug for GlyphFragment {
|
|||||||
pub struct VariantFragment {
|
pub struct VariantFragment {
|
||||||
pub c: char,
|
pub c: char,
|
||||||
pub italics_correction: Abs,
|
pub italics_correction: Abs,
|
||||||
pub accent_attach: Abs,
|
pub accent_attach: (Abs, Abs),
|
||||||
pub frame: Frame,
|
pub frame: Frame,
|
||||||
pub font_size: Abs,
|
pub font_size: Abs,
|
||||||
pub class: MathClass,
|
pub class: MathClass,
|
||||||
@ -499,8 +505,9 @@ pub struct FrameFragment {
|
|||||||
pub limits: Limits,
|
pub limits: Limits,
|
||||||
pub spaced: bool,
|
pub spaced: bool,
|
||||||
pub base_ascent: Abs,
|
pub base_ascent: Abs,
|
||||||
|
pub base_descent: Abs,
|
||||||
pub italics_correction: Abs,
|
pub italics_correction: Abs,
|
||||||
pub accent_attach: Abs,
|
pub accent_attach: (Abs, Abs),
|
||||||
pub text_like: bool,
|
pub text_like: bool,
|
||||||
pub ignorant: bool,
|
pub ignorant: bool,
|
||||||
}
|
}
|
||||||
@ -508,6 +515,7 @@ pub struct FrameFragment {
|
|||||||
impl FrameFragment {
|
impl FrameFragment {
|
||||||
pub fn new(styles: StyleChain, frame: Frame) -> Self {
|
pub fn new(styles: StyleChain, frame: Frame) -> Self {
|
||||||
let base_ascent = frame.ascent();
|
let base_ascent = frame.ascent();
|
||||||
|
let base_descent = frame.descent();
|
||||||
let accent_attach = frame.width() / 2.0;
|
let accent_attach = frame.width() / 2.0;
|
||||||
Self {
|
Self {
|
||||||
frame: frame.modified(&FrameModifiers::get_in(styles)),
|
frame: frame.modified(&FrameModifiers::get_in(styles)),
|
||||||
@ -517,8 +525,9 @@ impl FrameFragment {
|
|||||||
limits: Limits::Never,
|
limits: Limits::Never,
|
||||||
spaced: false,
|
spaced: false,
|
||||||
base_ascent,
|
base_ascent,
|
||||||
|
base_descent,
|
||||||
italics_correction: Abs::zero(),
|
italics_correction: Abs::zero(),
|
||||||
accent_attach,
|
accent_attach: (accent_attach, accent_attach),
|
||||||
text_like: false,
|
text_like: false,
|
||||||
ignorant: false,
|
ignorant: false,
|
||||||
}
|
}
|
||||||
@ -540,11 +549,15 @@ impl FrameFragment {
|
|||||||
Self { base_ascent, ..self }
|
Self { base_ascent, ..self }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_base_descent(self, base_descent: Abs) -> Self {
|
||||||
|
Self { base_descent, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_italics_correction(self, italics_correction: Abs) -> Self {
|
pub fn with_italics_correction(self, italics_correction: Abs) -> Self {
|
||||||
Self { italics_correction, ..self }
|
Self { italics_correction, ..self }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_accent_attach(self, accent_attach: Abs) -> Self {
|
pub fn with_accent_attach(self, accent_attach: (Abs, Abs)) -> Self {
|
||||||
Self { accent_attach, ..self }
|
Self { accent_attach, ..self }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +278,7 @@ fn assemble(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let accent_attach = match axis {
|
let accent_attach = match axis {
|
||||||
Axis::X => frame.width() / 2.0,
|
Axis::X => (frame.width() / 2.0, frame.width() / 2.0),
|
||||||
Axis::Y => base.accent_attach,
|
Axis::Y => base.accent_attach,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -80,6 +80,19 @@ impl Accent {
|
|||||||
pub fn new(c: char) -> Self {
|
pub fn new(c: char) -> Self {
|
||||||
Self(Self::combine(c).unwrap_or(c))
|
Self(Self::combine(c).unwrap_or(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List of bottom accents. Currently just a list of ones included in the
|
||||||
|
/// Unicode math class document.
|
||||||
|
const BOTTOM: &[char] = &[
|
||||||
|
'\u{0323}', '\u{032C}', '\u{032D}', '\u{032E}', '\u{032F}', '\u{0330}',
|
||||||
|
'\u{0331}', '\u{0332}', '\u{0333}', '\u{033A}', '\u{20E8}', '\u{20EC}',
|
||||||
|
'\u{20ED}', '\u{20EE}', '\u{20EF}',
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Whether this accent is a bottom accent or not.
|
||||||
|
pub fn is_bottom(&self) -> bool {
|
||||||
|
Self::BOTTOM.contains(&self.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This macro generates accent-related functions.
|
/// This macro generates accent-related functions.
|
||||||
|
BIN
tests/ref/math-accent-bottom-high-base.png
Normal file
BIN
tests/ref/math-accent-bottom-high-base.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 572 B |
BIN
tests/ref/math-accent-bottom-sized.png
Normal file
BIN
tests/ref/math-accent-bottom-sized.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 382 B |
BIN
tests/ref/math-accent-bottom-subscript.png
Normal file
BIN
tests/ref/math-accent-bottom-subscript.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 417 B |
BIN
tests/ref/math-accent-bottom-wide-base.png
Normal file
BIN
tests/ref/math-accent-bottom-wide-base.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 359 B |
BIN
tests/ref/math-accent-bottom.png
Normal file
BIN
tests/ref/math-accent-bottom.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 622 B |
BIN
tests/ref/math-accent-nested.png
Normal file
BIN
tests/ref/math-accent-nested.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 537 B |
@ -58,3 +58,31 @@ $hat(a) hat(A)$
|
|||||||
$tilde(w) tilde(W)$
|
$tilde(w) tilde(W)$
|
||||||
$grave(i) grave(j)$
|
$grave(i) grave(j)$
|
||||||
$grave(I) grave(J)$
|
$grave(I) grave(J)$
|
||||||
|
|
||||||
|
--- math-accent-bottom ---
|
||||||
|
// Test bottom accents.
|
||||||
|
$accent(a, \u{20EE}), accent(T, \u{0323}), accent(xi, \u{0332}),
|
||||||
|
accent(f, \u{20ED}), accent(F, \u{20E8}), accent(y, \u{032E}),
|
||||||
|
accent(!, \u{032F}), accent(J, \u{0333}), accent(p, \u{0331})$
|
||||||
|
|
||||||
|
--- math-accent-bottom-wide-base ---
|
||||||
|
// Test wide base with bottom accents.
|
||||||
|
$accent(x + y, \u{20EF}), accent(sum, \u{032D})$
|
||||||
|
|
||||||
|
--- math-accent-bottom-subscript ---
|
||||||
|
// Test effect of bottom accent on subscript.
|
||||||
|
$q_x != accent(q, \u{032C})_x != accent(accent(q, \u{032C}), \u{032C})_x$
|
||||||
|
|
||||||
|
--- math-accent-bottom-high-base ---
|
||||||
|
// Test high base with bottom accents.
|
||||||
|
$ accent(integral, \u{20EC}), accent(integral, \u{20EC})_a^b, accent(integral_a^b, \u{20EC}) $
|
||||||
|
|
||||||
|
--- math-accent-bottom-sized ---
|
||||||
|
// Test bottom accent size.
|
||||||
|
$accent(sum, \u{0330}), accent(sum, \u{0330}, size: #50%), accent(H, \u{032D}, size: #200%)$
|
||||||
|
|
||||||
|
--- math-accent-nested ---
|
||||||
|
// Test nested top and bottom accents.
|
||||||
|
$hat(accent(L, \u{0330})), accent(circle(p), \u{0323}),
|
||||||
|
macron(accent(caron(accent(A, \u{20ED})), \u{0333})) \
|
||||||
|
breve(accent(eta, \u{032E})) = accent(breve(eta), \u{032E})$
|
||||||
|
Loading…
x
Reference in New Issue
Block a user