diff options
| author | Leedehai <18319900+Leedehai@users.noreply.github.com> | 2023-04-25 05:24:07 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-04-25 11:24:07 +0200 |
| commit | 62361b4127c39fc1b165b81f24f52b14ddaa41db (patch) | |
| tree | 830f1ef69e77c9e9fbab2337f05136fa69ed0755 /library | |
| parent | fb99090208c9aface707f9d4526f411fcb67f705 (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.rs | 20 | ||||
| -rw-r--r-- | library/src/math/attach.rs | 315 |
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. |
