mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Math sub-, superscript and limit layout
This commit is contained in:
parent
f7b3b30ca1
commit
cfca115804
@ -3,9 +3,6 @@ use super::*;
|
|||||||
/// # Script
|
/// # Script
|
||||||
/// A mathematical sub- and/or superscript.
|
/// A mathematical sub- and/or superscript.
|
||||||
///
|
///
|
||||||
/// _Note:_ In the future, this might be unified with the [sub](@sub) and
|
|
||||||
/// [super](@super) functions that handle sub- and superscripts in text.
|
|
||||||
///
|
|
||||||
/// ## Syntax
|
/// ## Syntax
|
||||||
/// This function also has dedicated syntax: Use the underscore (`_`) to
|
/// This function also has dedicated syntax: Use the underscore (`_`) to
|
||||||
/// indicate a subscript and the circumflex (`^`) to indicate a superscript.
|
/// indicate a subscript and the circumflex (`^`) to indicate a superscript.
|
||||||
@ -28,7 +25,7 @@ use super::*;
|
|||||||
/// ## Category
|
/// ## Category
|
||||||
/// math
|
/// math
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(LayoutMath)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct ScriptNode {
|
pub struct ScriptNode {
|
||||||
/// The base.
|
/// The base.
|
||||||
@ -49,22 +46,139 @@ impl ScriptNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Texify for ScriptNode {
|
impl LayoutMath for ScriptNode {
|
||||||
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
self.base.texify(t)?;
|
let base = ctx.layout_fragment(&self.base)?;
|
||||||
|
|
||||||
if let Some(sub) = &self.sub {
|
let mut sub = Frame::new(Size::zero());
|
||||||
t.push_str("_{");
|
if let Some(node) = &self.sub {
|
||||||
sub.texify_unparen(t)?;
|
ctx.style(ctx.style.for_subscript());
|
||||||
t.push_str("}");
|
sub = ctx.layout_frame(node)?;
|
||||||
|
ctx.unstyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sup) = &self.sup {
|
let mut sup = Frame::new(Size::zero());
|
||||||
t.push_str("^{");
|
if let Some(node) = &self.sup {
|
||||||
sup.texify_unparen(t)?;
|
ctx.style(ctx.style.for_superscript());
|
||||||
t.push_str("}");
|
sup = ctx.layout_frame(node)?;
|
||||||
|
ctx.unstyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
let render_limits = ctx.style.size == MathSize::Display
|
||||||
|
&& base.class() == Some(MathClass::Large)
|
||||||
|
&& match &base {
|
||||||
|
MathFragment::Variant(variant) => LIMITS.contains(&variant.c),
|
||||||
|
MathFragment::Frame(fragment) => fragment.limits,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if render_limits {
|
||||||
|
limits(ctx, base, sub, sup)
|
||||||
|
} else {
|
||||||
|
scripts(ctx, base, sub, sup, self.sub.is_some() && self.sup.is_some())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Layout normal sub- and superscripts.
|
||||||
|
fn scripts(
|
||||||
|
ctx: &mut MathContext,
|
||||||
|
base: MathFragment,
|
||||||
|
sub: Frame,
|
||||||
|
sup: Frame,
|
||||||
|
both: bool,
|
||||||
|
) -> SourceResult<()> {
|
||||||
|
let sup_shift_up = if ctx.style.cramped {
|
||||||
|
scaled!(ctx, superscript_shift_up_cramped)
|
||||||
|
} else {
|
||||||
|
scaled!(ctx, superscript_shift_up)
|
||||||
|
};
|
||||||
|
let sup_bottom_min = scaled!(ctx, superscript_bottom_min);
|
||||||
|
let sup_bottom_max_with_sub = scaled!(ctx, superscript_bottom_max_with_subscript);
|
||||||
|
let sup_drop_max = scaled!(ctx, superscript_baseline_drop_max);
|
||||||
|
let gap_min = scaled!(ctx, sub_superscript_gap_min);
|
||||||
|
let sub_shift_down = scaled!(ctx, subscript_shift_down);
|
||||||
|
let sub_top_max = scaled!(ctx, subscript_top_max);
|
||||||
|
let sub_drop_min = scaled!(ctx, subscript_baseline_drop_min);
|
||||||
|
let space_after = scaled!(ctx, space_after_script);
|
||||||
|
|
||||||
|
let mut shift_up = sup_shift_up
|
||||||
|
.max(base.ascent() - sup_drop_max)
|
||||||
|
.max(sup_bottom_min + sup.descent());
|
||||||
|
|
||||||
|
let mut shift_down = sub_shift_down
|
||||||
|
.max(base.descent() + sub_drop_min)
|
||||||
|
.max(sub.ascent() - sub_top_max);
|
||||||
|
|
||||||
|
if both {
|
||||||
|
let sup_bottom = shift_up - sup.descent();
|
||||||
|
let sub_top = sub.ascent() - shift_down;
|
||||||
|
let gap = sup_bottom - sub_top;
|
||||||
|
if gap < gap_min {
|
||||||
|
let increase = gap_min - gap;
|
||||||
|
let sup_only =
|
||||||
|
(sup_bottom_max_with_sub - sup_bottom).clamp(Abs::zero(), increase);
|
||||||
|
let rest = (increase - sup_only) / 2.0;
|
||||||
|
shift_up += sup_only + rest;
|
||||||
|
shift_down += rest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let delta = base.italics_correction();
|
||||||
|
let ascent = shift_up + sup.ascent();
|
||||||
|
let descent = shift_down + sub.descent();
|
||||||
|
let height = ascent + descent;
|
||||||
|
let width = base.width() + sup.width().max(sub.width() - delta) + space_after;
|
||||||
|
let base_pos = Point::with_y(ascent - base.ascent());
|
||||||
|
let sup_pos = Point::with_x(base.width());
|
||||||
|
let sub_pos = Point::new(base.width() - delta, height - sub.height());
|
||||||
|
let class = base.class().unwrap_or(MathClass::Normal);
|
||||||
|
|
||||||
|
let mut frame = Frame::new(Size::new(width, height));
|
||||||
|
frame.set_baseline(ascent);
|
||||||
|
frame.push_frame(base_pos, base.to_frame(ctx));
|
||||||
|
frame.push_frame(sub_pos, sub);
|
||||||
|
frame.push_frame(sup_pos, sup);
|
||||||
|
ctx.push(FrameFragment { frame, class, limits: false });
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout limits.
|
||||||
|
fn limits(
|
||||||
|
ctx: &mut MathContext,
|
||||||
|
base: MathFragment,
|
||||||
|
sub: Frame,
|
||||||
|
sup: Frame,
|
||||||
|
) -> SourceResult<()> {
|
||||||
|
let upper_gap_min = scaled!(ctx, upper_limit_gap_min);
|
||||||
|
let upper_rise_min = scaled!(ctx, upper_limit_baseline_rise_min);
|
||||||
|
let lower_gap_min = scaled!(ctx, lower_limit_gap_min);
|
||||||
|
let lower_drop_min = scaled!(ctx, lower_limit_baseline_drop_min);
|
||||||
|
|
||||||
|
let sup_gap = upper_gap_min.max(upper_rise_min - sup.descent());
|
||||||
|
let sub_gap = lower_gap_min.max(lower_drop_min - sub.ascent());
|
||||||
|
|
||||||
|
let delta = base.italics_correction() / 2.0;
|
||||||
|
let width = base.width().max(sup.width()).max(sub.width());
|
||||||
|
let height = sup.height() + sup_gap + base.height() + sub_gap + sub.height();
|
||||||
|
let base_pos = Point::new((width - base.width()) / 2.0, sup.height() + sup_gap);
|
||||||
|
let sup_pos = Point::with_x((width - sup.width()) / 2.0 + delta);
|
||||||
|
let sub_pos = Point::new((width - sub.width()) / 2.0 - delta, height - sub.height());
|
||||||
|
let class = base.class().unwrap_or(MathClass::Normal);
|
||||||
|
|
||||||
|
let mut frame = Frame::new(Size::new(width, height));
|
||||||
|
frame.set_baseline(base_pos.y + base.ascent());
|
||||||
|
frame.push_frame(base_pos, base.to_frame(ctx));
|
||||||
|
frame.push_frame(sub_pos, sub);
|
||||||
|
frame.push_frame(sup_pos, sup);
|
||||||
|
ctx.push(FrameFragment { frame, class, limits: false });
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Codepoints that should have sub- and superscripts attached as limits.
|
||||||
|
const LIMITS: &[char] = &[
|
||||||
|
'\u{2210}', '\u{22C1}', '\u{22C0}', '\u{2A04}', '\u{22C2}', '\u{22C3}', '\u{220F}',
|
||||||
|
'\u{2211}', '\u{2A02}', '\u{2A01}', '\u{2A00}', '\u{2A06}',
|
||||||
|
];
|
||||||
|
@ -9,9 +9,6 @@ use crate::prelude::*;
|
|||||||
///
|
///
|
||||||
/// The text is rendered smaller and its baseline is lowered.
|
/// The text is rendered smaller and its baseline is lowered.
|
||||||
///
|
///
|
||||||
/// _Note:_ In the future, this might be unified with the [script](@script)
|
|
||||||
/// function that handles subscripts in math.
|
|
||||||
///
|
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```
|
/// ```
|
||||||
/// Revenue#sub[yearly]
|
/// Revenue#sub[yearly]
|
||||||
@ -93,9 +90,6 @@ impl Show for SubNode {
|
|||||||
///
|
///
|
||||||
/// The text is rendered smaller and its baseline is raised.
|
/// The text is rendered smaller and its baseline is raised.
|
||||||
///
|
///
|
||||||
/// _Note:_ In the future, this might be unified with the [script](@script)
|
|
||||||
/// function that handles superscripts in math.
|
|
||||||
///
|
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```
|
/// ```
|
||||||
/// 1#super[st] try!
|
/// 1#super[st] try!
|
||||||
|
Loading…
x
Reference in New Issue
Block a user