mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Breaking change: abbreviate attach() attachment params, top -> t, bottom -> b
This commit is contained in:
parent
fb99090208
commit
62361b4127
@ -227,13 +227,25 @@ fn items() -> LangItems {
|
|||||||
equation: |body, block| math::EquationElem::new(body).with_block(block).pack(),
|
equation: |body, block| math::EquationElem::new(body).with_block(block).pack(),
|
||||||
math_align_point: || math::AlignPointElem::new().pack(),
|
math_align_point: || math::AlignPointElem::new().pack(),
|
||||||
math_delimited: |open, body, close| math::LrElem::new(open + body + close).pack(),
|
math_delimited: |open, body, close| math::LrElem::new(open + body + close).pack(),
|
||||||
math_attach: |base, bottom, top| {
|
math_attach: |base, top, bottom, topleft, bottomleft, topright, bottomright| {
|
||||||
let mut elem = math::AttachElem::new(base);
|
let mut elem = math::AttachElem::new(base);
|
||||||
if let Some(bottom) = bottom {
|
|
||||||
elem.push_bottom(Some(bottom));
|
|
||||||
}
|
|
||||||
if let Some(top) = top {
|
if let Some(top) = top {
|
||||||
elem.push_top(Some(top));
|
elem.push_t(Some(top));
|
||||||
|
}
|
||||||
|
if let Some(bottom) = bottom {
|
||||||
|
elem.push_b(Some(bottom));
|
||||||
|
}
|
||||||
|
if let Some(topleft) = topleft {
|
||||||
|
elem.push_tl(Some(topleft));
|
||||||
|
}
|
||||||
|
if let Some(bottomleft) = bottomleft {
|
||||||
|
elem.push_bl(Some(bottomleft));
|
||||||
|
}
|
||||||
|
if let Some(topright) = topright {
|
||||||
|
elem.push_tr(Some(topright));
|
||||||
|
}
|
||||||
|
if let Some(bottomright) = bottomright {
|
||||||
|
elem.push_br(Some(bottomright));
|
||||||
}
|
}
|
||||||
elem.pack()
|
elem.pack()
|
||||||
},
|
},
|
||||||
|
@ -3,8 +3,9 @@ use super::*;
|
|||||||
/// A base with optional attachments.
|
/// A base with optional attachments.
|
||||||
///
|
///
|
||||||
/// ## Syntax
|
/// ## Syntax
|
||||||
/// This function also has dedicated syntax: Use the underscore (`_`) to
|
/// This function also has dedicated syntax for attachments after the base: Use the
|
||||||
/// indicate a bottom attachment and the hat (`^`) to indicate a top attachment.
|
/// underscore (`_`) to indicate a subscript i.e. bottom attachment and the hat (`^`)
|
||||||
|
/// to indicate a superscript i.e. top attachment.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```example
|
/// ```example
|
||||||
@ -19,38 +20,56 @@ pub struct AttachElem {
|
|||||||
#[required]
|
#[required]
|
||||||
pub base: Content,
|
pub base: Content,
|
||||||
|
|
||||||
/// The top attachment.
|
/// The top attachment, smartly positioned at top-right or above the base.
|
||||||
pub top: Option<Content>,
|
/// Use limits() or scripts() on the base to override the smart positioning.
|
||||||
|
pub t: Option<Content>,
|
||||||
|
|
||||||
/// The bottom attachment.
|
/// The bottom attachment, smartly positioned at the bottom-right or below the base.
|
||||||
pub bottom: Option<Content>,
|
/// Use limits() or scripts() on the base to override the smart positioning.
|
||||||
|
pub b: Option<Content>,
|
||||||
|
|
||||||
|
/// The top-left attachment before the base.
|
||||||
|
pub tl: Option<Content>,
|
||||||
|
|
||||||
|
/// The bottom-left attachment before base.
|
||||||
|
pub bl: Option<Content>,
|
||||||
|
|
||||||
|
/// The top-right attachment after the base.
|
||||||
|
pub tr: Option<Content>,
|
||||||
|
|
||||||
|
/// The bottom-right attachment after the base.
|
||||||
|
pub br: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetAttachmentContent =
|
||||||
|
fn(&AttachElem, styles: ::typst::model::StyleChain) -> Option<Content>;
|
||||||
|
|
||||||
impl LayoutMath for AttachElem {
|
impl LayoutMath for AttachElem {
|
||||||
#[tracing::instrument(skip(ctx))]
|
#[tracing::instrument(skip(ctx))]
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
let base = self.base();
|
let base = ctx.layout_fragment(&self.base())?;
|
||||||
let display_limits = base.is::<LimitsElem>();
|
|
||||||
let display_scripts = base.is::<ScriptsElem>();
|
|
||||||
|
|
||||||
let base = ctx.layout_fragment(&base)?;
|
let getarg = |ctx: &mut MathContext, getter: GetAttachmentContent| {
|
||||||
|
getter(self, ctx.styles())
|
||||||
|
.map(|elem| ctx.layout_fragment(&elem))
|
||||||
|
.transpose()
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
ctx.style(ctx.style.for_superscript());
|
ctx.style(ctx.style.for_superscript());
|
||||||
let top = self
|
let arg_tl = getarg(ctx, Self::tl);
|
||||||
.top(ctx.styles())
|
let arg_tr = getarg(ctx, Self::tr);
|
||||||
.map(|elem| ctx.layout_fragment(&elem))
|
let arg_t = getarg(ctx, Self::t);
|
||||||
.transpose()?;
|
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
|
|
||||||
ctx.style(ctx.style.for_subscript());
|
ctx.style(ctx.style.for_subscript());
|
||||||
let bottom = self
|
let arg_bl = getarg(ctx, Self::bl);
|
||||||
.bottom(ctx.styles())
|
let arg_br = getarg(ctx, Self::br);
|
||||||
.map(|elem| ctx.layout_fragment(&elem))
|
let arg_b = getarg(ctx, Self::b);
|
||||||
.transpose()?;
|
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
|
|
||||||
let display_limits = display_limits
|
let as_limits = self.base().is::<LimitsElem>()
|
||||||
|| (!display_scripts
|
|| (!self.base().is::<ScriptsElem>()
|
||||||
&& ctx.style.size == MathSize::Display
|
&& ctx.style.size == MathSize::Display
|
||||||
&& base.class() == Some(MathClass::Large)
|
&& base.class() == Some(MathClass::Large)
|
||||||
&& match &base {
|
&& match &base {
|
||||||
@ -59,11 +78,12 @@ impl LayoutMath for AttachElem {
|
|||||||
_ => false,
|
_ => false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if display_limits {
|
let (t, tr) =
|
||||||
limits(ctx, base, top, bottom)
|
if as_limits || arg_tr.is_some() { (arg_t, arg_tr) } else { (None, arg_t) };
|
||||||
} else {
|
let (b, br) =
|
||||||
scripts(ctx, base, top, bottom)
|
if as_limits || arg_br.is_some() { (arg_b, arg_br) } else { (None, arg_b) };
|
||||||
}
|
|
||||||
|
layout_attachments(ctx, base, [arg_tl, t, tr, arg_bl, b, br])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,113 +133,98 @@ impl LayoutMath for LimitsElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout sub- and superscripts.
|
/// Layout the attachments.
|
||||||
fn scripts(
|
fn layout_attachments(
|
||||||
ctx: &mut MathContext,
|
ctx: &mut MathContext,
|
||||||
base: MathFragment,
|
base: MathFragment,
|
||||||
sup: Option<MathFragment>,
|
[tl, t, tr, bl, b, br]: [Option<MathFragment>; 6],
|
||||||
sub: Option<MathFragment>,
|
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let sup_shift_up = if ctx.style.cramped {
|
let (shift_up, shift_down) =
|
||||||
scaled!(ctx, superscript_shift_up_cramped)
|
compute_shifts_up_and_down(ctx, &base, [&tl, &tr, &bl, &br]);
|
||||||
} 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 = Abs::zero();
|
|
||||||
let mut shift_down = Abs::zero();
|
|
||||||
|
|
||||||
if let Some(sup) = &sup {
|
|
||||||
let ascent = match &base {
|
|
||||||
MathFragment::Frame(frame) => frame.base_ascent,
|
|
||||||
_ => base.ascent(),
|
|
||||||
};
|
|
||||||
|
|
||||||
shift_up = sup_shift_up
|
|
||||||
.max(ascent - sup_drop_max)
|
|
||||||
.max(sup_bottom_min + sup.descent());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(sub) = &sub {
|
|
||||||
shift_down = sub_shift_down
|
|
||||||
.max(base.descent() + sub_drop_min)
|
|
||||||
.max(sub.ascent() - sub_top_max);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let (Some(sup), Some(sub)) = (&sup, &sub) {
|
|
||||||
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 italics = base.italics_correction();
|
|
||||||
let sup_delta = Abs::zero();
|
let sup_delta = Abs::zero();
|
||||||
let sub_delta = -italics;
|
let sub_delta = -base.italics_correction();
|
||||||
|
let (base_width, base_ascent, base_descent) =
|
||||||
|
(base.width(), base.ascent(), base.descent());
|
||||||
|
let base_class = base.class().unwrap_or(MathClass::Normal);
|
||||||
|
|
||||||
let mut width = Abs::zero();
|
macro_rules! measure {
|
||||||
let mut ascent = base.ascent();
|
($e: ident, $attr: ident) => {
|
||||||
let mut descent = base.descent();
|
$e.as_ref().map(|e| e.$attr()).unwrap_or_default()
|
||||||
|
};
|
||||||
if let Some(sup) = &sup {
|
|
||||||
ascent.set_max(shift_up + sup.ascent());
|
|
||||||
width.set_max(sup_delta + sup.width());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sub) = &sub {
|
let ascent = base_ascent
|
||||||
descent.set_max(shift_down + sub.descent());
|
.max(shift_up + measure!(tr, ascent))
|
||||||
width.set_max(sub_delta + sub.width());
|
.max(shift_up + measure!(tl, ascent))
|
||||||
|
.max(shift_up + measure!(t, height));
|
||||||
|
|
||||||
|
let descent = base_descent
|
||||||
|
.max(shift_down + measure!(br, descent))
|
||||||
|
.max(shift_down + measure!(bl, descent))
|
||||||
|
.max(shift_down + measure!(b, height));
|
||||||
|
|
||||||
|
let pre_sup_width = measure!(tl, width);
|
||||||
|
let pre_sub_width = measure!(bl, width);
|
||||||
|
let pre_width_dif = pre_sup_width - pre_sub_width; // Could be negative.
|
||||||
|
let pre_width_max = pre_sup_width.max(pre_sub_width);
|
||||||
|
let post_max_width =
|
||||||
|
(sup_delta + measure!(tr, width)).max(sub_delta + measure!(br, width));
|
||||||
|
|
||||||
|
let (center_frame, base_offset) = attach_top_and_bottom(ctx, base, t, b);
|
||||||
|
let base_pos =
|
||||||
|
Point::new(sup_delta + pre_width_max, ascent - base_ascent - base_offset);
|
||||||
|
if [&tl, &bl, &tr, &br].iter().all(|&e| e.is_none()) {
|
||||||
|
ctx.push(FrameFragment::new(ctx, center_frame).with_class(base_class));
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
width += base.width() + space_after;
|
let mut frame = Frame::new(Size::new(
|
||||||
|
pre_width_max + base_width + post_max_width + scaled!(ctx, space_after_script),
|
||||||
let base_pos = Point::with_y(ascent - base.ascent());
|
ascent + descent,
|
||||||
let base_width = base.width();
|
));
|
||||||
let class = base.class().unwrap_or(MathClass::Normal);
|
|
||||||
|
|
||||||
let mut frame = Frame::new(Size::new(width, ascent + descent));
|
|
||||||
frame.set_baseline(ascent);
|
frame.set_baseline(ascent);
|
||||||
frame.push_frame(base_pos, base.into_frame());
|
frame.push_frame(base_pos, center_frame);
|
||||||
|
|
||||||
if let Some(sup) = sup {
|
if let Some(tl) = tl {
|
||||||
let sup_pos =
|
let pos =
|
||||||
Point::new(sup_delta + base_width, ascent - shift_up - sup.ascent());
|
Point::new(-pre_width_dif.min(Abs::zero()), ascent - shift_up - tl.ascent());
|
||||||
frame.push_frame(sup_pos, sup.into_frame());
|
frame.push_frame(pos, tl.into_frame());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sub) = sub {
|
if let Some(bl) = bl {
|
||||||
let sub_pos =
|
let pos =
|
||||||
Point::new(sub_delta + base_width, ascent + shift_down - sub.ascent());
|
Point::new(pre_width_dif.max(Abs::zero()), ascent + shift_down - bl.ascent());
|
||||||
frame.push_frame(sub_pos, sub.into_frame());
|
frame.push_frame(pos, bl.into_frame());
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.push(FrameFragment::new(ctx, frame).with_class(class));
|
if let Some(tr) = tr {
|
||||||
|
let pos = Point::new(
|
||||||
|
sup_delta + pre_width_max + base_width,
|
||||||
|
ascent - shift_up - tr.ascent(),
|
||||||
|
);
|
||||||
|
frame.push_frame(pos, tr.into_frame());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(br) = br {
|
||||||
|
let pos = Point::new(
|
||||||
|
sub_delta + pre_width_max + base_width,
|
||||||
|
ascent + shift_down - br.ascent(),
|
||||||
|
);
|
||||||
|
frame.push_frame(pos, br.into_frame());
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.push(FrameFragment::new(ctx, frame).with_class(base_class));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout limits.
|
fn attach_top_and_bottom(
|
||||||
fn limits(
|
|
||||||
ctx: &mut MathContext,
|
ctx: &mut MathContext,
|
||||||
base: MathFragment,
|
base: MathFragment,
|
||||||
top: Option<MathFragment>,
|
t: Option<MathFragment>,
|
||||||
bottom: Option<MathFragment>,
|
b: Option<MathFragment>,
|
||||||
) -> SourceResult<()> {
|
) -> (Frame, Abs) {
|
||||||
let upper_gap_min = scaled!(ctx, upper_limit_gap_min);
|
let upper_gap_min = scaled!(ctx, upper_limit_gap_min);
|
||||||
let upper_rise_min = scaled!(ctx, upper_limit_baseline_rise_min);
|
let upper_rise_min = scaled!(ctx, upper_limit_baseline_rise_min);
|
||||||
let lower_gap_min = scaled!(ctx, lower_limit_gap_min);
|
let lower_gap_min = scaled!(ctx, lower_limit_gap_min);
|
||||||
@ -229,41 +234,99 @@ fn limits(
|
|||||||
let mut width = base.width();
|
let mut width = base.width();
|
||||||
let mut height = base.height();
|
let mut height = base.height();
|
||||||
|
|
||||||
if let Some(top) = &top {
|
if let Some(t) = &t {
|
||||||
let top_gap = upper_gap_min.max(upper_rise_min - top.descent());
|
let top_gap = upper_gap_min.max(upper_rise_min - t.descent());
|
||||||
width.set_max(top.width());
|
width.set_max(t.width());
|
||||||
height += top.height() + top_gap;
|
height += t.height() + top_gap;
|
||||||
base_offset = top_gap + top.height();
|
base_offset = top_gap + t.height();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(bottom) = &bottom {
|
if let Some(b) = &b {
|
||||||
let bottom_gap = lower_gap_min.max(lower_drop_min - bottom.ascent());
|
let bottom_gap = lower_gap_min.max(lower_drop_min - b.ascent());
|
||||||
width.set_max(bottom.width());
|
width.set_max(b.width());
|
||||||
height += bottom.height() + bottom_gap;
|
height += b.height() + bottom_gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
let base_pos = Point::new((width - base.width()) / 2.0, base_offset);
|
let base_pos = Point::new((width - base.width()) / 2.0, base_offset);
|
||||||
let class = base.class().unwrap_or(MathClass::Normal);
|
|
||||||
let delta = base.italics_correction() / 2.0;
|
let delta = base.italics_correction() / 2.0;
|
||||||
|
|
||||||
let mut frame = Frame::new(Size::new(width, height));
|
let mut frame = Frame::new(Size::new(width, height));
|
||||||
frame.set_baseline(base_pos.y + base.ascent());
|
frame.set_baseline(base_pos.y + base.ascent());
|
||||||
frame.push_frame(base_pos, base.into_frame());
|
frame.push_frame(base_pos, base.into_frame());
|
||||||
|
|
||||||
if let Some(top) = top {
|
if let Some(t) = t {
|
||||||
let top_pos = Point::with_x((width - top.width()) / 2.0 + delta);
|
let top_pos = Point::with_x((width - t.width()) / 2.0 + delta);
|
||||||
frame.push_frame(top_pos, top.into_frame());
|
frame.push_frame(top_pos, t.into_frame());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(bottom) = bottom {
|
if let Some(b) = b {
|
||||||
let bottom_pos =
|
let bottom_pos =
|
||||||
Point::new((width - bottom.width()) / 2.0 - delta, height - bottom.height());
|
Point::new((width - b.width()) / 2.0 - delta, height - b.height());
|
||||||
frame.push_frame(bottom_pos, bottom.into_frame());
|
frame.push_frame(bottom_pos, b.into_frame());
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.push(FrameFragment::new(ctx, frame).with_class(class));
|
(frame, base_offset)
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
fn compute_shifts_up_and_down(
|
||||||
|
ctx: &MathContext,
|
||||||
|
base: &MathFragment,
|
||||||
|
[tl, tr, bl, br]: [&Option<MathFragment>; 4],
|
||||||
|
) -> (Abs, Abs) {
|
||||||
|
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 mut shift_up = Abs::zero();
|
||||||
|
let mut shift_down = Abs::zero();
|
||||||
|
|
||||||
|
for e in [tl, tr].into_iter().flatten() {
|
||||||
|
let ascent = match &base {
|
||||||
|
MathFragment::Frame(frame) => frame.base_ascent,
|
||||||
|
_ => base.ascent(),
|
||||||
|
};
|
||||||
|
|
||||||
|
shift_up = shift_up
|
||||||
|
.max(sup_shift_up)
|
||||||
|
.max(ascent - sup_drop_max)
|
||||||
|
.max(sup_bottom_min + e.descent());
|
||||||
|
}
|
||||||
|
for e in [bl, br].into_iter().flatten() {
|
||||||
|
shift_down = shift_down
|
||||||
|
.max(sub_shift_down)
|
||||||
|
.max(base.descent() + sub_drop_min)
|
||||||
|
.max(e.ascent() - sub_top_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (sup, sub) in [(tl, bl), (tr, br)] {
|
||||||
|
if let (Some(sup), Some(sub)) = (&sup, &sub) {
|
||||||
|
let sup_bottom = shift_up - sup.descent();
|
||||||
|
let sub_top = sub.ascent() - shift_down;
|
||||||
|
let gap = sup_bottom - sub_top;
|
||||||
|
if gap >= gap_min {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(shift_up, shift_down)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Codepoints that should have sub- and superscripts attached as limits.
|
/// Codepoints that should have sub- and superscripts attached as limits.
|
||||||
|
@ -87,8 +87,18 @@ pub struct LangItems {
|
|||||||
/// Matched delimiters in math: `[x + y]`.
|
/// Matched delimiters in math: `[x + y]`.
|
||||||
pub math_delimited: fn(open: Content, body: Content, close: Content) -> Content,
|
pub math_delimited: fn(open: Content, body: Content, close: Content) -> Content,
|
||||||
/// A base with optional attachments in math: `a_1^2`.
|
/// A base with optional attachments in math: `a_1^2`.
|
||||||
pub math_attach:
|
#[allow(clippy::type_complexity)]
|
||||||
fn(base: Content, bottom: Option<Content>, top: Option<Content>) -> Content,
|
pub math_attach: fn(
|
||||||
|
base: Content,
|
||||||
|
// Positioned smartly.
|
||||||
|
top: Option<Content>,
|
||||||
|
bottom: Option<Content>,
|
||||||
|
// Fixed positions.
|
||||||
|
topleft: Option<Content>,
|
||||||
|
bottomleft: Option<Content>,
|
||||||
|
topright: Option<Content>,
|
||||||
|
bottomright: Option<Content>,
|
||||||
|
) -> Content,
|
||||||
/// A base with an accent: `arrow(x)`.
|
/// A base with an accent: `arrow(x)`.
|
||||||
pub math_accent: fn(base: Content, accent: char) -> Content,
|
pub math_accent: fn(base: Content, accent: char) -> Content,
|
||||||
/// A fraction in math: `x/2`.
|
/// A fraction in math: `x/2`.
|
||||||
|
@ -708,9 +708,9 @@ impl Eval for ast::MathAttach {
|
|||||||
#[tracing::instrument(name = "MathAttach::eval", skip_all)]
|
#[tracing::instrument(name = "MathAttach::eval", skip_all)]
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
let base = self.base().eval_display(vm)?;
|
let base = self.base().eval_display(vm)?;
|
||||||
let bottom = self.bottom().map(|expr| expr.eval_display(vm)).transpose()?;
|
|
||||||
let top = self.top().map(|expr| expr.eval_display(vm)).transpose()?;
|
let top = self.top().map(|expr| expr.eval_display(vm)).transpose()?;
|
||||||
Ok((vm.items.math_attach)(base, bottom, top))
|
let bottom = self.bottom().map(|expr| expr.eval_display(vm)).transpose()?;
|
||||||
|
Ok((vm.items.math_attach)(base, top, bottom, None, None, None, None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 21 KiB |
@ -15,8 +15,8 @@
|
|||||||
}
|
}
|
||||||
} else if func == math.attach {
|
} else if func == math.attach {
|
||||||
let value = f(elem.base)
|
let value = f(elem.base)
|
||||||
if elem.has("top") {
|
if elem.has("t") {
|
||||||
value = calc.pow(value, f(elem.top))
|
value = calc.pow(value, f(elem.t))
|
||||||
}
|
}
|
||||||
value
|
value
|
||||||
} else if elem.has("children") {
|
} else if elem.has("children") {
|
||||||
|
@ -1,9 +1,45 @@
|
|||||||
// Test top and bottom attachments.
|
// Test t and b attachments.
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test basics.
|
// Test basics, postscripts.
|
||||||
$f_x + t^b + V_1^2
|
$f_x + t^b + V_1^2 + attach(A, t: alpha, b: beta)$
|
||||||
+ attach(A, top: alpha, bottom: beta)$
|
|
||||||
|
---
|
||||||
|
// Test basics, prescripts. Notably, the upper and lower prescripts' content need to be
|
||||||
|
// aligned on the right edge of their bounding boxes, not on the left as in postscripts.
|
||||||
|
$
|
||||||
|
attach(upright(O), bl: 8, tl: 16, br: 2, tr: 2-),
|
||||||
|
attach("Pb", bl: 82, tl: 207) + attach(upright(e), bl: -1, tl: 0) + macron(v)_e \
|
||||||
|
$
|
||||||
|
|
||||||
|
---
|
||||||
|
// A mixture of attachment positioning schemes.
|
||||||
|
$
|
||||||
|
attach(a, tl: u), attach(a, tr: v), attach(a, bl: x),
|
||||||
|
attach(a, br: y), limits(a)^t, limits(a)_b \
|
||||||
|
|
||||||
|
attach(a, tr: v, t: t),
|
||||||
|
attach(a, tr: v, br: y),
|
||||||
|
attach(a, br: y, b: b),
|
||||||
|
attach(limits(a), b: b, bl: x),
|
||||||
|
attach(a, tl: u, bl: x),
|
||||||
|
attach(limits(a), t: t, tl: u) \
|
||||||
|
|
||||||
|
attach(a, tl: u, tr: v),
|
||||||
|
attach(limits(a), t: t, br: y),
|
||||||
|
attach(limits(a), b: b, tr: v),
|
||||||
|
attach(a, bl: x, br: y),
|
||||||
|
attach(limits(a), b: b, tl: u),
|
||||||
|
attach(limits(a), t: t, bl: u),
|
||||||
|
limits(a)^t_b \
|
||||||
|
|
||||||
|
attach(a, tl: u, tr: v, bl: x, br: y),
|
||||||
|
attach(limits(a), t: t, bl: x, br: y, b: b),
|
||||||
|
attach(limits(a), t: t, tl: u, tr: v, b: b),
|
||||||
|
attach(limits(a), tl: u, bl: x, t: t, b: b),
|
||||||
|
attach(limits(a), t: t, b: b, tr: v, br: y),
|
||||||
|
attach(a, tl: u, t: t, tr: v, bl: x, b: b, br: y)
|
||||||
|
$
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test function call after subscript.
|
// Test function call after subscript.
|
||||||
@ -12,13 +48,21 @@ $pi_1(Y), a_f(x), a^zeta(x) \
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test associativity and scaling.
|
// Test associativity and scaling.
|
||||||
$ 1/(V^2^3^4^5) $
|
$ 1/(V^2^3^4^5),
|
||||||
|
1/attach(V, tl: attach(2, tl: attach(3, tl: attach(4, tl: 5)))),
|
||||||
|
attach(Omega,
|
||||||
|
tl: attach(2, tl: attach(3, tl: attach(4, tl: 5))),
|
||||||
|
tr: attach(2, tr: attach(3, tr: attach(4, tr: 5))),
|
||||||
|
bl: attach(2, bl: attach(3, bl: attach(4, bl: 5))),
|
||||||
|
br: attach(2, br: attach(3, br: attach(4, br: 5))),
|
||||||
|
)
|
||||||
|
$
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test high subscript and superscript.
|
// Test high subscript and superscript.
|
||||||
$sqrt(a_(1/2)^zeta)$
|
$ sqrt(a_(1/2)^zeta), sqrt(a_alpha^(1/2)), sqrt(a_(1/2)^(3/4)) \
|
||||||
$sqrt(a_alpha^(1/2))$
|
sqrt(attach(a, tl: 1/2, bl: 3/4)),
|
||||||
$sqrt(a_(1/2)^(3/4))$
|
sqrt(attach(a, tl: 1/2, bl: 3/4, tr: 1/2, br: 3/4)) $
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test frame base.
|
// Test frame base.
|
||||||
|
@ -11,7 +11,7 @@ $ x := #table(columns: 2)[x][y]/mat(1, 2, 3)
|
|||||||
= #table[A][B][C] $
|
= #table[A][B][C] $
|
||||||
---
|
---
|
||||||
// Test non-equation math directly in content.
|
// Test non-equation math directly in content.
|
||||||
#math.attach($a$, top: [b])
|
#math.attach($a$, t: [b])
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test font switch.
|
// Test font switch.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user