summaryrefslogtreecommitdiff
path: root/library
diff options
context:
space:
mode:
authorLeedehai <18319900+Leedehai@users.noreply.github.com>2023-04-25 05:24:07 -0400
committerGitHub <noreply@github.com>2023-04-25 11:24:07 +0200
commit62361b4127c39fc1b165b81f24f52b14ddaa41db (patch)
tree830f1ef69e77c9e9fbab2337f05136fa69ed0755 /library
parentfb99090208c9aface707f9d4526f411fcb67f705 (diff)
Support indices preceding the base symbol, revamping #699 (#825)
Breaking change: abbreviate attach() attachment params, top -> t, bottom -> b
Diffstat (limited to 'library')
-rw-r--r--library/src/lib.rs20
-rw-r--r--library/src/math/attach.rs315
2 files changed, 205 insertions, 130 deletions
diff --git a/library/src/lib.rs b/library/src/lib.rs
index 98ec121a..e7a23cd7 100644
--- a/library/src/lib.rs
+++ b/library/src/lib.rs
@@ -227,13 +227,25 @@ fn items() -> LangItems {
equation: |body, block| math::EquationElem::new(body).with_block(block).pack(),
math_align_point: || math::AlignPointElem::new().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);
+ if let Some(top) = top {
+ elem.push_t(Some(top));
+ }
if let Some(bottom) = bottom {
- elem.push_bottom(Some(bottom));
+ elem.push_b(Some(bottom));
}
- if let Some(top) = top {
- elem.push_top(Some(top));
+ 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()
},
diff --git a/library/src/math/attach.rs b/library/src/math/attach.rs
index dcf7fb78..34c24e17 100644
--- a/library/src/math/attach.rs
+++ b/library/src/math/attach.rs
@@ -3,8 +3,9 @@ use super::*;
/// A base with optional attachments.
///
/// ## Syntax
-/// This function also has dedicated syntax: Use the underscore (`_`) to
-/// indicate a bottom attachment and the hat (`^`) to indicate a top attachment.
+/// This function also has dedicated syntax for attachments after the base: Use the
+/// underscore (`_`) to indicate a subscript i.e. bottom attachment and the hat (`^`)
+/// to indicate a superscript i.e. top attachment.
///
/// ## Example
/// ```example
@@ -19,38 +20,56 @@ pub struct AttachElem {
#[required]
pub base: Content,
- /// The top attachment.
- pub top: Option<Content>,
+ /// The top attachment, smartly positioned at top-right or above the base.
+ /// Use limits() or scripts() on the base to override the smart positioning.
+ pub t: Option<Content>,
- /// The bottom attachment.
- pub bottom: Option<Content>,
+ /// The bottom attachment, smartly positioned at the bottom-right or below the base.
+ /// 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 {
#[tracing::instrument(skip(ctx))]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let base = self.base();
- let display_limits = base.is::<LimitsElem>();
- let display_scripts = base.is::<ScriptsElem>();
+ let base = ctx.layout_fragment(&self.base())?;
- 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());
- let top = self
- .top(ctx.styles())
- .map(|elem| ctx.layout_fragment(&elem))
- .transpose()?;
+ let arg_tl = getarg(ctx, Self::tl);
+ let arg_tr = getarg(ctx, Self::tr);
+ let arg_t = getarg(ctx, Self::t);
ctx.unstyle();
ctx.style(ctx.style.for_subscript());
- let bottom = self
- .bottom(ctx.styles())
- .map(|elem| ctx.layout_fragment(&elem))
- .transpose()?;
+ let arg_bl = getarg(ctx, Self::bl);
+ let arg_br = getarg(ctx, Self::br);
+ let arg_b = getarg(ctx, Self::b);
ctx.unstyle();
- let display_limits = display_limits
- || (!display_scripts
+ let as_limits = self.base().is::<LimitsElem>()
+ || (!self.base().is::<ScriptsElem>()
&& ctx.style.size == MathSize::Display
&& base.class() == Some(MathClass::Large)
&& match &base {
@@ -59,11 +78,12 @@ impl LayoutMath for AttachElem {
_ => false,
});
- if display_limits {
- limits(ctx, base, top, bottom)
- } else {
- scripts(ctx, base, top, bottom)
- }
+ let (t, tr) =
+ if as_limits || arg_tr.is_some() { (arg_t, arg_tr) } else { (None, arg_t) };
+ let (b, br) =
+ 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.
-fn scripts(
+/// Layout the attachments.
+fn layout_attachments(
ctx: &mut MathContext,
base: MathFragment,
- sup: Option<MathFragment>,
- sub: Option<MathFragment>,
+ [tl, t, tr, bl, b, br]: [Option<MathFragment>; 6],
) -> 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 = Abs::zero();
- let mut shift_down = Abs::zero();
+ let (shift_up, shift_down) =
+ compute_shifts_up_and_down(ctx, &base, [&tl, &tr, &bl, &br]);
- if let Some(sup) = &sup {
- let ascent = match &base {
- MathFragment::Frame(frame) => frame.base_ascent,
- _ => base.ascent(),
+ let sup_delta = Abs::zero();
+ 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);
+
+ macro_rules! measure {
+ ($e: ident, $attr: ident) => {
+ $e.as_ref().map(|e| e.$attr()).unwrap_or_default()
};
-
- 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 ascent = base_ascent
+ .max(shift_up + measure!(tr, ascent))
+ .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(());
}
- let italics = base.italics_correction();
- let sup_delta = Abs::zero();
- let sub_delta = -italics;
-
- let mut width = Abs::zero();
- let mut ascent = base.ascent();
- let mut descent = base.descent();
+ let mut frame = Frame::new(Size::new(
+ pre_width_max + base_width + post_max_width + scaled!(ctx, space_after_script),
+ ascent + descent,
+ ));
+ frame.set_baseline(ascent);
+ frame.push_frame(base_pos, center_frame);
- if let Some(sup) = &sup {
- ascent.set_max(shift_up + sup.ascent());
- width.set_max(sup_delta + sup.width());
+ if let Some(tl) = tl {
+ let pos =
+ Point::new(-pre_width_dif.min(Abs::zero()), ascent - shift_up - tl.ascent());
+ frame.push_frame(pos, tl.into_frame());
}
- if let Some(sub) = &sub {
- descent.set_max(shift_down + sub.descent());
- width.set_max(sub_delta + sub.width());
+ if let Some(bl) = bl {
+ let pos =
+ Point::new(pre_width_dif.max(Abs::zero()), ascent + shift_down - bl.ascent());
+ frame.push_frame(pos, bl.into_frame());
}
- width += base.width() + space_after;
-
- let base_pos = Point::with_y(ascent - base.ascent());
- 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.push_frame(base_pos, base.into_frame());
-
- if let Some(sup) = sup {
- let sup_pos =
- Point::new(sup_delta + base_width, ascent - shift_up - sup.ascent());
- frame.push_frame(sup_pos, sup.into_frame());
+ 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(sub) = sub {
- let sub_pos =
- Point::new(sub_delta + base_width, ascent + shift_down - sub.ascent());
- frame.push_frame(sub_pos, sub.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(class));
+ ctx.push(FrameFragment::new(ctx, frame).with_class(base_class));
Ok(())
}
-/// Layout limits.
-fn limits(
+fn attach_top_and_bottom(
ctx: &mut MathContext,
base: MathFragment,
- top: Option<MathFragment>,
- bottom: Option<MathFragment>,
-) -> SourceResult<()> {
+ t: Option<MathFragment>,
+ b: Option<MathFragment>,
+) -> (Frame, Abs) {
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);
@@ -229,41 +234,99 @@ fn limits(
let mut width = base.width();
let mut height = base.height();
- if let Some(top) = &top {
- let top_gap = upper_gap_min.max(upper_rise_min - top.descent());
- width.set_max(top.width());
- height += top.height() + top_gap;
- base_offset = top_gap + top.height();
+ if let Some(t) = &t {
+ let top_gap = upper_gap_min.max(upper_rise_min - t.descent());
+ width.set_max(t.width());
+ height += t.height() + top_gap;
+ base_offset = top_gap + t.height();
}
- if let Some(bottom) = &bottom {
- let bottom_gap = lower_gap_min.max(lower_drop_min - bottom.ascent());
- width.set_max(bottom.width());
- height += bottom.height() + bottom_gap;
+ if let Some(b) = &b {
+ let bottom_gap = lower_gap_min.max(lower_drop_min - b.ascent());
+ width.set_max(b.width());
+ height += b.height() + bottom_gap;
}
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 mut frame = Frame::new(Size::new(width, height));
frame.set_baseline(base_pos.y + base.ascent());
frame.push_frame(base_pos, base.into_frame());
- if let Some(top) = top {
- let top_pos = Point::with_x((width - top.width()) / 2.0 + delta);
- frame.push_frame(top_pos, top.into_frame());
+ if let Some(t) = t {
+ let top_pos = Point::with_x((width - t.width()) / 2.0 + delta);
+ frame.push_frame(top_pos, t.into_frame());
}
- if let Some(bottom) = bottom {
+ if let Some(b) = b {
let bottom_pos =
- Point::new((width - bottom.width()) / 2.0 - delta, height - bottom.height());
- frame.push_frame(bottom_pos, bottom.into_frame());
+ Point::new((width - b.width()) / 2.0 - delta, height - b.height());
+ 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.