From 373163dba4b4a4d9186b96a671af64d435d0eda3 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 26 Aug 2024 17:04:02 +0000 Subject: [PATCH] Implement math kerning and fix various `math.attach` bugs (#4762) --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/typst-utils/src/lib.rs | 21 ++ crates/typst/src/layout/corners.rs | 10 + crates/typst/src/math/attach.rs | 378 ++++++++++++++------- crates/typst/src/math/fragment.rs | 16 +- tests/ref/gradient-math-misc.png | Bin 2999 -> 2993 bytes tests/ref/math-attach-high.png | Bin 1296 -> 1297 bytes tests/ref/math-attach-horizontal-align.png | Bin 1900 -> 1894 bytes tests/ref/math-attach-integral.png | Bin 965 -> 964 bytes tests/ref/math-attach-kerning-mixed.png | Bin 0 -> 2418 bytes tests/ref/math-attach-kerning.png | Bin 0 -> 2321 bytes tests/ref/math-attach-limit-long.png | Bin 0 -> 1941 bytes tests/ref/math-attach-mixed.png | Bin 2357 -> 2327 bytes tests/ref/math-attach-nested-base.png | Bin 1062 -> 1047 bytes tests/ref/math-attach-nested.png | Bin 952 -> 957 bytes tests/ref/math-attach-prescripts.png | Bin 675 -> 670 bytes tests/ref/math-spacing-decorated.png | Bin 2382 -> 2385 bytes tests/suite/math/attach.typ | 31 ++ 19 files changed, 333 insertions(+), 127 deletions(-) create mode 100644 tests/ref/math-attach-kerning-mixed.png create mode 100644 tests/ref/math-attach-kerning.png create mode 100644 tests/ref/math-attach-limit-long.png diff --git a/Cargo.lock b/Cargo.lock index 7efcd441f..e1716f4dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2704,7 +2704,7 @@ dependencies = [ [[package]] name = "typst-dev-assets" version = "0.11.0" -source = "git+https://github.com/typst/typst-dev-assets?rev=48a924d#48a924d9de82b631bc775124a69384c8d860db04" +source = "git+https://github.com/typst/typst-dev-assets?rev=e9f8127#e9f81271547c0d7003770b4fa1e59343e51f7ae8" [[package]] name = "typst-docs" diff --git a/Cargo.toml b/Cargo.toml index 18b670f0c..cb0ef9400 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ typst-syntax = { path = "crates/typst-syntax", version = "0.11.0" } typst-timing = { path = "crates/typst-timing", version = "0.11.0" } typst-utils = { path = "crates/typst-utils", version = "0.11.0" } typst-assets = { git = "https://github.com/typst/typst-assets", rev = "4ee794c" } -typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "48a924d" } +typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "e9f8127" } arrayvec = "0.7.4" az = "1.2" base64 = "0.22" diff --git a/crates/typst-utils/src/lib.rs b/crates/typst-utils/src/lib.rs index 4d5f8e0c3..831b2374c 100644 --- a/crates/typst-utils/src/lib.rs +++ b/crates/typst-utils/src/lib.rs @@ -83,6 +83,27 @@ impl ArcExt for Arc { } } +/// Extra methods for [`Option`]. +pub trait OptionExt { + /// Maps an `Option` to `U` by applying a function to a contained value + /// (if `Some`) or returns a default (if `None`). + fn map_or_default(self, f: F) -> U + where + F: FnOnce(T) -> U; +} + +impl OptionExt for Option { + fn map_or_default(self, f: F) -> U + where + F: FnOnce(T) -> U, + { + match self { + Some(x) => f(x), + None => U::default(), + } + } +} + /// Extra methods for [`[T]`](slice). pub trait SliceExt { /// Split a slice into consecutive runs with the same key and yield for diff --git a/crates/typst/src/layout/corners.rs b/crates/typst/src/layout/corners.rs index a3b6ba93e..cfd6b7acf 100644 --- a/crates/typst/src/layout/corners.rs +++ b/crates/typst/src/layout/corners.rs @@ -262,6 +262,16 @@ pub enum Corner { } impl Corner { + /// The opposite corner. + pub fn inv(self) -> Self { + match self { + Self::TopLeft => Self::BottomRight, + Self::TopRight => Self::BottomLeft, + Self::BottomRight => Self::TopLeft, + Self::BottomLeft => Self::TopRight, + } + } + /// The next corner, clockwise. pub fn next_cw(self) -> Self { match self { diff --git a/crates/typst/src/math/attach.rs b/crates/typst/src/math/attach.rs index 70bf2e5e9..2d491dfd3 100644 --- a/crates/typst/src/math/attach.rs +++ b/crates/typst/src/math/attach.rs @@ -2,12 +2,13 @@ use unicode_math_class::MathClass; use crate::diag::SourceResult; use crate::foundations::{elem, Content, Packed, StyleChain}; -use crate::layout::{Abs, Frame, Point, Size}; +use crate::layout::{Abs, Corner, Frame, Point, Size}; use crate::math::{ style_for_subscript, style_for_superscript, EquationElem, FrameFragment, LayoutMath, MathContext, MathFragment, MathSize, Scaled, }; use crate::text::TextElem; +use crate::utils::OptionExt; /// A base with optional attachments. /// @@ -240,7 +241,7 @@ impl Limits { } } - /// Whether limits should be displayed in this context + /// Whether limits should be displayed in this context. pub fn active(&self, styles: StyleChain) -> bool { match self { Self::Always => true, @@ -300,145 +301,231 @@ fn layout_attachments( base: MathFragment, [tl, t, tr, bl, b, br]: [Option; 6], ) -> SourceResult<()> { - let (shift_up, shift_down) = if [&tl, &tr, &bl, &br].iter().all(|e| e.is_none()) { - (Abs::zero(), Abs::zero()) - } else { - compute_shifts_up_and_down(ctx, styles, &base, [&tl, &tr, &bl, &br]) - }; - - 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(); - let mut ascent = base_ascent - .max(shift_up + measure!(tr, ascent)) - .max(shift_up + measure!(tl, ascent)) - .max(shift_up + measure!(t, height)); - - let mut 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_width_max = - (sup_delta + measure!(tr, width)).max(sub_delta + measure!(br, width)); - - let (center_frame, base_offset) = if t.is_none() && b.is_none() { - (base.into_frame(), Abs::zero()) + // Calculate the distance from the base's baseline to the superscripts' and + // subscripts' baseline. + let (tx_shift, bx_shift) = if [&tl, &tr, &bl, &br].iter().all(|e| e.is_none()) { + (Abs::zero(), Abs::zero()) } else { - attach_top_and_bottom(ctx, styles, base, t, b) + compute_script_shifts(ctx, styles, &base, [&tl, &tr, &bl, &br]) }; - if [&tl, &bl, &tr, &br].iter().all(|&e| e.is_none()) { - ctx.push(FrameFragment::new(ctx, styles, center_frame).with_class(base_class)); - return Ok(()); - } - ascent.set_max(center_frame.ascent()); - descent.set_max(center_frame.descent()); + // Calculate the distance from the base's baseline to the top attachment's + // and bottom attachment's baseline. + let (t_shift, b_shift) = + compute_limit_shifts(ctx, styles, &base, [t.as_ref(), b.as_ref()]); - let mut frame = Frame::soft(Size::new( - pre_width_max - + base_width - + post_width_max - + scaled!(ctx, styles, space_after_script), - ascent + descent, - )); - frame.set_baseline(ascent); - frame.push_frame( - Point::new(sup_delta + pre_width_max, frame.ascent() - base_ascent - base_offset), - center_frame, + // Calculate the final frame height. + let ascent = base + .ascent() + .max(tx_shift + measure!(tr, ascent)) + .max(tx_shift + measure!(tl, ascent)) + .max(t_shift + measure!(t, ascent)); + let descent = base + .descent() + .max(bx_shift + measure!(br, descent)) + .max(bx_shift + measure!(bl, descent)) + .max(b_shift + measure!(b, descent)); + let height = ascent + descent; + + // Calculate the vertical position of each element in the final frame. + let base_y = ascent - base.ascent(); + let tx_y = |tx: &MathFragment| ascent - tx_shift - tx.ascent(); + let bx_y = |bx: &MathFragment| ascent + bx_shift - bx.ascent(); + let t_y = |t: &MathFragment| ascent - t_shift - t.ascent(); + let b_y = |b: &MathFragment| ascent + b_shift - b.ascent(); + + // Calculate the distance each limit extends to the left and right of the + // base's width. + let ((t_pre_width, t_post_width), (b_pre_width, b_post_width)) = + compute_limit_widths(&base, [t.as_ref(), b.as_ref()]); + + // `space_after_script` is extra spacing that is at the start before each + // pre-script, and at the end after each post-script (see the MathConstants + // table in the OpenType MATH spec). + let space_after_script = scaled!(ctx, styles, space_after_script); + + // Calculate the distance each pre-script extends to the left of the base's + // width. + let (tl_pre_width, bl_pre_width) = compute_pre_script_widths( + ctx, + &base, + [tl.as_ref(), bl.as_ref()], + (tx_shift, bx_shift), + space_after_script, ); - 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()); + // Calculate the distance each post-script extends to the right of the + // base's width. Also calculate each post-script's kerning (we need this for + // its position later). + let ((tr_post_width, tr_kern), (br_post_width, br_kern)) = compute_post_script_widths( + ctx, + &base, + [tr.as_ref(), br.as_ref()], + (tx_shift, bx_shift), + space_after_script, + ); + + // Calculate the final frame width. + let pre_width = t_pre_width.max(b_pre_width).max(tl_pre_width).max(bl_pre_width); + let base_width = base.width(); + let post_width = t_post_width.max(b_post_width).max(tr_post_width).max(br_post_width); + let width = pre_width + base_width + post_width; + + // Calculate the horizontal position of each element in the final frame. + let base_x = pre_width; + let tl_x = pre_width - tl_pre_width + space_after_script; + let bl_x = pre_width - bl_pre_width + space_after_script; + let tr_x = pre_width + base_width + tr_kern; + let br_x = pre_width + base_width + br_kern; + let t_x = pre_width - t_pre_width; + let b_x = pre_width - b_pre_width; + + // Create the final frame. + let mut frame = Frame::soft(Size::new(width, height)); + frame.set_baseline(ascent); + frame.push_frame(Point::new(base_x, base_y), base.into_frame()); + + macro_rules! layout { + ($e: ident, $x: ident, $y: ident) => { + if let Some($e) = $e { + frame.push_frame(Point::new($x, $y(&$e)), $e.into_frame()); + } + }; } - 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()); - } - - 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()); - } + layout!(tl, tl_x, tx_y); // pre-superscript + layout!(bl, bl_x, bx_y); // pre-subscript + layout!(tr, tr_x, tx_y); // post-superscript + layout!(br, br_x, bx_y); // post-subscript + layout!(t, t_x, t_y); // upper-limit + layout!(b, b_x, b_y); // lower-limit + // Done! Note that we retain the class of the base. ctx.push(FrameFragment::new(ctx, styles, frame).with_class(base_class)); Ok(()) } -fn attach_top_and_bottom( +/// Calculate the distance each post-script extends to the right of the base's +/// width, as well as its kerning value. Requires the distance from the base's +/// baseline to each post-script's baseline to obtain the correct kerning value. +/// Returns 2 tuples of two lengths, each first containing the distance the +/// post-script extends left of the base's width and second containing the +/// post-script's kerning value. The first tuple is for the post-superscript, +/// and the second is for the post-subscript. +fn compute_post_script_widths( ctx: &MathContext, - styles: StyleChain, - base: MathFragment, - t: Option, - b: Option, -) -> (Frame, Abs) { - let upper_gap_min = scaled!(ctx, styles, upper_limit_gap_min); - let upper_rise_min = scaled!(ctx, styles, upper_limit_baseline_rise_min); - let lower_gap_min = scaled!(ctx, styles, lower_limit_gap_min); - let lower_drop_min = scaled!(ctx, styles, lower_limit_baseline_drop_min); + base: &MathFragment, + [tr, br]: [Option<&MathFragment>; 2], + (tr_shift, br_shift): (Abs, Abs), + space_after_post_script: Abs, +) -> ((Abs, Abs), (Abs, Abs)) { + let tr_values = tr.map_or_default(|tr| { + let kern = math_kern(ctx, base, tr, tr_shift, Corner::TopRight); + (space_after_post_script + tr.width() + kern, kern) + }); - let mut base_offset = Abs::zero(); - let mut width = base.width(); - let mut height = base.height(); + // The base's bounding box already accounts for its italic correction, so we + // need to shift the post-subscript left by the base's italic correction + // (see the kerning algorithm as described in the OpenType MATH spec). + let br_values = br.map_or_default(|br| { + let kern = math_kern(ctx, base, br, br_shift, Corner::BottomRight) + - base.italics_correction(); + (space_after_post_script + br.width() + kern, kern) + }); - 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(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 delta = base.italics_correction() / 2.0; - - let mut frame = Frame::soft(Size::new(width, height)); - frame.set_baseline(base_pos.y + base.ascent()); - frame.push_frame(base_pos, base.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(b) = b { - let bottom_pos = - Point::new((width - b.width()) / 2.0 - delta, height - b.height()); - frame.push_frame(bottom_pos, b.into_frame()); - } - - (frame, base_offset) + (tr_values, br_values) } -fn compute_shifts_up_and_down( +/// Calculate the distance each pre-script extends to the left of the base's +/// width. Requires the distance from the base's baseline to each pre-script's +/// baseline to obtain the correct kerning value. +/// Returns two lengths, the first being the distance the pre-superscript +/// extends left of the base's width and the second being the distance the +/// pre-subscript extends left of the base's width. +fn compute_pre_script_widths( + ctx: &MathContext, + base: &MathFragment, + [tl, bl]: [Option<&MathFragment>; 2], + (tl_shift, bl_shift): (Abs, Abs), + space_before_pre_script: Abs, +) -> (Abs, Abs) { + let tl_pre_width = tl.map_or_default(|tl| { + let kern = math_kern(ctx, base, tl, tl_shift, Corner::TopLeft); + space_before_pre_script + tl.width() + kern + }); + + let bl_pre_width = bl.map_or_default(|bl| { + let kern = math_kern(ctx, base, bl, bl_shift, Corner::BottomLeft); + space_before_pre_script + bl.width() + kern + }); + + (tl_pre_width, bl_pre_width) +} + +/// Calculate the distance each limit extends beyond the base's width, in each +/// direction. Can be a negative value if the limit does not extend beyond the +/// base's width, indicating how far into the base's width the limit extends. +/// Returns 2 tuples of two lengths, each first containing the distance the +/// limit extends leftward beyond the base's width and second containing the +/// distance the limit extends rightward beyond the base's width. The first +/// tuple is for the upper-limit, and the second is for the lower-limit. +fn compute_limit_widths( + base: &MathFragment, + [t, b]: [Option<&MathFragment>; 2], +) -> ((Abs, Abs), (Abs, Abs)) { + // The upper- (lower-) limit is shifted to the right (left) of the base's + // center by half the base's italic correction. + let delta = base.italics_correction() / 2.0; + + let t_widths = t.map_or_default(|t| { + let half = (t.width() - base.width()) / 2.0; + (half - delta, half + delta) + }); + + let b_widths = b.map_or_default(|b| { + let half = (b.width() - base.width()) / 2.0; + (half + delta, half - delta) + }); + + (t_widths, b_widths) +} + +/// Calculate the distance from the base's baseline to each limit's baseline. +/// Returns two lengths, the first being the distance to the upper-limit's +/// baseline and the second being the distance to the lower-limit's baseline. +fn compute_limit_shifts( + ctx: &MathContext, + styles: StyleChain, + base: &MathFragment, + [t, b]: [Option<&MathFragment>; 2], +) -> (Abs, Abs) { + // `upper_gap_min` and `lower_gap_min` give gaps to the descender and + // ascender of the limits respectively, whereas `upper_rise_min` and + // `lower_drop_min` give gaps to each limit's baseline (see the + // MathConstants table in the OpenType MATH spec). + + let t_shift = t.map_or_default(|t| { + let upper_gap_min = scaled!(ctx, styles, upper_limit_gap_min); + let upper_rise_min = scaled!(ctx, styles, upper_limit_baseline_rise_min); + base.ascent() + upper_rise_min.max(upper_gap_min + t.descent()) + }); + + let b_shift = b.map_or_default(|b| { + let lower_gap_min = scaled!(ctx, styles, lower_limit_gap_min); + let lower_drop_min = scaled!(ctx, styles, lower_limit_baseline_drop_min); + base.descent() + lower_drop_min.max(lower_gap_min + b.ascent()) + }); + + (t_shift, b_shift) +} + +/// Calculate the distance from the base's baseline to each script's baseline. +/// Returns two lengths, the first being the distance to the superscripts' +/// baseline and the second being the distance to the subscripts' baseline. +fn compute_script_shifts( ctx: &MathContext, styles: StyleChain, base: &MathFragment, @@ -504,7 +591,56 @@ fn compute_shifts_up_and_down( (shift_up, shift_down) } -/// Determines if the character is one of a variety of integral signs +/// Calculate the kerning value for a script with respect to the base. A +/// positive value means shifting the script further away from the base, whereas +/// a negative value means shifting the script closer to the base. Requires the +/// distance from the base's baseline to the script's baseline, as well as the +/// script's corner (tl, tr, bl, br). +fn math_kern( + ctx: &MathContext, + base: &MathFragment, + script: &MathFragment, + shift: Abs, + pos: Corner, +) -> Abs { + // This process is described under the MathKernInfo table in the OpenType + // MATH spec. + + let (corr_height_top, corr_height_bot) = match pos { + // Calculate two correction heights for superscripts: + // - The distance from the superscript's baseline to the top of the + // base's bounding box. + // - The distance from the base's baseline to the bottom of the + // superscript's bounding box. + Corner::TopLeft | Corner::TopRight => { + (base.ascent() - shift, shift - script.descent()) + } + // Calculate two correction heights for subscripts: + // - The distance from the base's baseline to the top of the + // subscript's bounding box. + // - The distance from the subscript's baseline to the bottom of the + // base's bounding box. + Corner::BottomLeft | Corner::BottomRight => { + (script.ascent() - shift, shift - base.descent()) + } + }; + + // Calculate the sum of kerning values for each correction height. + let summed_kern = |height| { + let base_kern = base.kern_at_height(ctx, pos, height); + let attach_kern = script.kern_at_height(ctx, pos.inv(), height); + base_kern + attach_kern + }; + + // Take the smaller kerning amount (and so the larger value). Note that + // there is a bug in the spec (as of 2024-08-15): it says to take the + // minimum of the two sums, but as the kerning value is usually negative it + // really means the smaller kern. The current wording of the spec could + // result in glyphs colliding. + summed_kern(corr_height_top).max(summed_kern(corr_height_bot)) +} + +/// Determines if the character is one of a variety of integral signs. fn is_integral_char(c: char) -> bool { ('∫'..='∳').contains(&c) || ('⨋'..='⨜').contains(&c) } diff --git a/crates/typst/src/math/fragment.rs b/crates/typst/src/math/fragment.rs index c6de24863..2adf2d9d0 100644 --- a/crates/typst/src/math/fragment.rs +++ b/crates/typst/src/math/fragment.rs @@ -184,6 +184,18 @@ impl MathFragment { _ => Limits::Never, } } + + /// If no kern table is provided for a corner, a kerning amount of zero is + /// assumed. + pub fn kern_at_height(&self, ctx: &MathContext, corner: Corner, height: Abs) -> Abs { + match self { + Self::Glyph(glyph) => { + kern_at_height(ctx, glyph.font_size, glyph.id, corner, height) + .unwrap_or_default() + } + _ => Abs::zero(), + } + } } impl From for MathFragment { @@ -552,10 +564,6 @@ fn is_extended_shape(ctx: &MathContext, id: GlyphId) -> bool { } /// Look up a kerning value at a specific corner and height. -/// -/// This can be integrated once we've found a font that actually provides this -/// data. -#[allow(unused)] fn kern_at_height( ctx: &MathContext, font_size: Abs, diff --git a/tests/ref/gradient-math-misc.png b/tests/ref/gradient-math-misc.png index b9b55d4d921196dace3471393d4340ad69fea600..acf14c6fe20e84146001d4117700a949a4fda6d1 100644 GIT binary patch literal 2993 zcmV;i3r_TjP)78CddKnk^9FNMpclnl6oY9mTA*l)01XD1$s|GSI5U~VaqKjXerqJD0714k*jn_m(&?`I?x;u1Ha2uaVNStJiuLMMZLU&GV=$|nd zINhKc$EY8u6~t5 zUyN8R{tF=Rs%17F7CsB64GT>^GHw-Qg+jMYu|sR+H__yfulw23UHfWWgf{p*UiFl>4)o`(K&p6Ga;ttG8{yLuqc&?X_IZ!v#^Q9I1JG=Oshws z4K!-92tYz|u&#twZ5gRBn$D$Ko3m z@BP&N{nWwz+pFJ}Fd!;e7 zr5}|zjxFz+6Z7h%tMPcIKJ&OXJ6YcMG;oYLV)Jo&f3-03iQoM&k=U=D8rssCZSv51 zY2~{((D52w)sU;=_Gq`3j-~WJ1eHECabT#jI&6Om;GXk^3I$EVBKmLYduUVD6cQK> z=&S}3sw_-611Wjb zmCzQK=>~HI;#H1zFk2K;n&3awlcHw_mfmmg;LiNh>-*?2>-v%Kt@GIv_tF2MzW>*D zGIYXKv@U(vt%+uEY?x>4qv>8pHxDcnx|{aOAEb%dEt_-P{pKizZd=9N-mYH8Yjh|Issh}A{-bAxllBsYZkv$SeBqaP z6$hJhh8Nh_6ZRbkLz6Gf!Kg@6=!;md{95Cgla}LU0~(qLh`>p%*0?h?+X}asbMH3I3S!Uzo&No|p5lu#%v9!oh=!*;LLlu62 zV-73;22?arpnPo)(A%UPVba*rKgXFyozaZUQs_?Q+3u0EfK%*?Jr{G)8~@$$EOe6Z z;59j!8D|gZ8&=i_N6ADeC35BN66#6e(h#!5Sjo+-z zXB`zy3`hQcyz$AmwWU>+LU+MB+YPV;PEk?B$XJa+cR6&`F_cpXCxL;Af z4wfl&&zFvU3QeIY^xt6UodnXQ4ny}*=yQqRO~JG;96D)gLy7D)Z(%)SQs`5Ocug*b zX&>EvDPR3)CSTpFmuE7(U_1*=2bS*BYIb_BUJNLE9|aq`6(`Za>tY^w{^SG(wCJ5m zW*}SsHls~b=!*!Art>Q$oJvA)$QM5eE+Hs%d&(I@uQ~JY`t7Q%`fCt4LBb2~eHOZ_ z$G)v*@8&D_bGnN{x5vLy*qzAu?W3u$Q5;wlSSA z*KIy^sMFHX!_Z%6BJcPkZ>?uOep;o8re}r}nnIr)de~T4G{QA*p^42g?K8)hH45Dw zic|wEv2SNg$Wn8Rp?Wy$FWdAb-v z&s$Ibem?c~2F@d;ZSulX{Tfa(uOtn@aT394*YR9=sSx_1!;@QzfOv~JMD(fi2NgS<9pS2 zHM@B?n;1zHM?h;O98CW01pMD9+DRf8t3;8=gUtPbvAG++#yuYnnakJk)!AGOGavSfx zhu{1Hmw$$p&*@%Ar}9T?+EXj;)apAGeW&Dj@^Uu~UmX4jfodce3 zAXA5KzX;74_T~_or_fyxn#or~4FLL1$(V@9%_K?+c7pJD0HBsbe2qf)fWsl_DLA3f z6q->D0EK<);LPO3m|5@p4#_lqI*S3 zC!PPw;^iZI+UGo$kQBOmBHP$~*xZ^46@V^lcrGV%dptaAdS7-oxx{lq!o6oMuW6-% zL!n#n`WTlj^q^j90_-6|9VsCxd2|;UqLMefUB{5=?Oqd#F;M0R)G&Vh>R0o`#++nd^DxabQvC9%^V?^vsY@XbMfC n35BN6ghEqj3QZ{VU!?y9ccH)S@H?US00000NkvXXu0mjftK-t{ literal 2999 zcmV;o3rO^dP)79tnaA<|c|~sqn2SL#W{S>SOwpn(0yJpRrb&^+Zkp9j(j<;sIq{-KmTX6o9m$rp zSi7~66h)B~Nm1N+xSzwrv%br(j%@{WEJ~zEc6~k|1c;YkJb3xOXZa4u-^6z)0fnZ} zghEqj3QZ_9g(ei5Libix#DnfVJSQqb+rmqsd&3ZgW%EM`iKDFYrJx8>=H7=zKwUN{^eHLU za>y7&6CayNituIUtxdkL$(M1+%eU`1(-iuYe6=?#EQCWGu8Dd|>`k#@Jj(_9o$)S( zJ{>V%{C_~;S&M8uBK$Ti2R53#ZC)tI3We^N=7d+vFQQ3|uLan`kn?UrgxC2To^{ii zSa3HebjPxdNpI~t}H)@hWpTDuzIZIFlQbnYKc&j;B%~ zb$ue;*UXe^8fWs!->yP)foKLULp%e^?o((J z^_naKkdPd#C!uvmMk~zW2p1SSVH+$Hd0J13)}f`u`ebH*yL#<4-{w>{^stCSiS^3% zNP2f9y*HAI4W|;r$>L1@gwS(4P^$Rt1#E{gF?(F-%D&tl0*G^=dabR067ydO8F_pLuJ)dx3? z{SwEaMW1zNPM?^m-}Lz9Sz|3rTk9jaPF0;54= z`M~b1faY>0x9(>jecD>1?+!#Vds?IBa3;H&XaF-_Ec)Ow)b;KsdL#cr+L*$#qI_eM zO#>KkzCFBoS$IKzqy33)c4n@o*|MMu04Gj#vs>qOt9er`TE~TcH2J>S+~+TQl0kLz zUbd@=-p%0FDn7k~6+dPkg7ZGD(J2w!VAETCe$2hxlOMy&0d*~Fy*OD(*7$71M%(6x z4}vSjKmBq;Incc`x#L1}CJV`i5K>9#g3GQ#OCVK(kjNBayZzgBe=NQm5R0X&R<6HL(C!7Te-7z8Uxxx?e zEDkp74nJUH_t{Mjh9;j{*Ww~Wp-*G2^6LiA9<>}VnrNVjfOy4tM?zBQ(?}Y3;?@M3 zNJtNV;J_+N1x;cie%T(O2WU=-OKVyEEt+`Q)FK-ncR$^Qqc@38ceB~?79fjYgFMX$dR3QeI2g{IIHnowv8O(-;lrqKV?!hHXe#`wpz zxtyz`iRsEejMv}$qPDQ2Q|KO8W4<6uqJ@ekZj99^bdN*l9rLSPX(Fjn=>CSb1P^HX zrBInd_kHQurO*_bLjN0v_9T%hbs2hqLZ3*yF$v2#b?B7U7)<8QZ5P(E7KLs}Bx-U# zLi_06OZ(ORDW$rjmZ!43U_J^>2bSJz)SS$YS`2DCZ-?r>ikqzCc?Vv2|KbL(HsV(* z*{iwo7um)=3Vj;k(TuWGLQ4{YLq7dTa0x-7J5$b@M$Mi3UBIb2dY}e@BP6%6J;K66 z#=#fW+>lZk$s02ix-)^Lg6~c?a6OfsR_dNgPxjF=i!U)h~MapI0$3jgN=$!3Vf_7K1P0V3|U9>%dpN_`UhSKpaO(Mlty@Zv2!& z_rUsR2VZ{of$FKCmE^i82Pky6q`>?Op7rl9aMeL8%*103D`p#agVr!QVcYWFErosz zVJP;)|C~#|ypEGd=~%q*&^U*q%xNi8a2!Q&+I2iuUMPgWd&eBz!%1vjY884~O*S~k zvuc`{o72>)OQD}q-uD;(V?@Twy$!wQw`OM4ps!A$pOTKK{AfZ9c{&Z970OrE`fc6+ zZf;{Jm%NcI-iWG9;2Y=9=a?%g^ixi{QGe$lB-#l5Ui%YWs`AU1i?L9HLVt~EZsxU3 zX)zqjar24#@VfDSge|qdMvskE526NzhHz#0jGvV(bSG}BOiJ2Ex8yy)*NA9nCFd8} zWje5QYilr(TQ1>9wu!Y-{N;y`oI-ac@Iblp{gt)Xcf+r5XU}YDXP4O@?&IxIoV|!w zU&U{JfY-l=rH|-ZN4J!HJ>#tvJvG%+F+3&Lt4Lo4{saP1ghGhyAnt{wQs};69Jc-R z?940*-5X`UiYJ6b!vPcU1aa#Y!ak@4tX!nfJ)jqCtK&=i_bXbMfC$-hNtr8HDBR};>IvPe?serc3ui&g&9PfZ5RT*uIEwvfzdj!y$`HJXX-eR>+>7(*&o` zCyM(Knnnu}a+7ZXcuO0_V*;4V)$v%Yzzj=XzfmdX9SYrUXt%JE+j${d7|1p5C7iiY z>&!eGb6^9gNOjm)0Z`{|&1Dm5RkMXF9=A@rpIXVq@Qt5-Sh&^~EZMK9*%-@g|lAU{#L{Eg%M*&o5bD z4(Kz_FC8lpClGT+Z`X8H)8_Z!aBc75hHyOn}=sRJJx;+1PSN0erJY@3={t=*}4qTCm+*!CZ_x z_v3t%E-LkrT3-dEDa#)BHihn^a_ugF3f~K}1q$6qO6hvV+A6Xgx|P*O%*r}$p6(eE t3QeIYG@;NGnowv8O`!>erqHCdzXI!oCY1N0KvMt!002ovPDHLkV1iq)(Ut%J diff --git a/tests/ref/math-attach-high.png b/tests/ref/math-attach-high.png index 4bb6cb29fc3aeac40c2025bd83c133c349367098..0452c5eacc30c2b771394734d3eb5bf8a4dbf77c 100644 GIT binary patch delta 1277 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 XoEJ#<6#E9qt zF+>xCh{c6!G=SoQMrElBxS&Q+Ktv-#sAv?GO<4*BK|ljS5g`R+D{UbyblU#p*v^}G znm-OE^F8*L=95gC^u3t{g_@U~*b07w>1ReS0etTn(41q0I9KTX&yX(o zRUQGn)db1(iGSc^i~{7tlZ!aR!7tzx|AGRp2;dCM9pJRNOEzqd3|+Cj5mNQ91Si)* z$c0COy0QDgp=wB!8wyTUn`FZoAAuz;_QQ}UDhZsDqGZGIov_StV=p9H{T`gQr&GY6 z)Ld58%Qphp2PW^lUT|7wln+~RCkDVMnA#VE)7V^>d>Du*gk<@9z{#)y6!1D5 zB)fPUoF1O0fXBCRh8;h^De{wic*2#R#+lZD*LtUX_}(*(02qogn5E%3IQ54>J}i88 z0^Vrm%zvKyB&H7ltnW%iOGQG`cdAq7aAM7+X82{-w*UZQgfRH$dR+@D7~gtoJdnzL z(jL{J$i=**RzE-h(}0&Bft9g4s;)rDQ^doC4L;r{`SX;TP3I;;>J zWK2;6?$LGV{s~+rda^=F+z2&XR|2+k{a~Voi+}UM7E)xIUf3~)^BH>h zlkjP1>a%1wA#6_BFK7?}|0>P>X>SYRm#f$cLO8y`fy*t|;n&UXXcXs(z87mwqJ+0u zfZj6r7XUWLE`Sd13SSs6K%B4vY)u+UIBjByp&H_TZ3mnF3?-awTQ!ggapn8LcKZe? z+G-QJJ$sG zcfx(DLy^N?c~xqa5-^)kF{liD!5*v#>?eqx(Ed>ZMs~@AZuRb@!ZQ~B3;IBK2+h8k Q=>Px#07*qoM6N<$f+Xv?1^@s6 delta 943 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 literal 0 HcmV?d00001 diff --git a/tests/ref/math-attach-kerning.png b/tests/ref/math-attach-kerning.png new file mode 100644 index 0000000000000000000000000000000000000000..d3de7a2270e41a569ec5c5a3b3e662d660b57c8c GIT binary patch literal 2321 zcmV+s3GViZP)000QsNkl?(}`?OE?Nt4}do9)Kh?P0HNO{%tHTJfk?ty(FiQmG&!<&ZEH2IyI#*Asa;1DOr7-||5bN!V|3Wvx{Jur2EbGM^nEdEb0^_`71t)98B3 z04uAL+i3o#ypN7gD0Ve$|0xg(-c5#*%@ddsG8nuJUjrn@`^DXI&dQRk-Mjau*vCcF zvFf^R)tGZOfX>s)!(+B@{Emv^78;8CA%DTIz_lhgv&8k-15I&pEm?tz4JOW4!J*R= zF?dN`(Lj0{J965Jiw9WMkET*x1AUcY;i+^0z^I4|2Q08(A%mzfP$d4p`J z#N$e9Fm3@*B%nNKHXLKS(*Ysvm3)K?uLm!`3FxCXDOX+vXtBO>!UmvqWm(2108sC8 z#o-Q6`N~M!cK~R^&O~(rL^s^I6KghXf06kkG~EK~wg3ex;MNkdebE7HBTN8rXGuAr zgD$I!0MinTy-(8AL^e2I(8mK%TTvI<2@sN=9&v6W+e~=Ak@*J7_W-Ak0N)P-sge8` zL#_e#B|wP-Fg?cr+I-N>(E)r`3(TELhRtv^`pggj$qh7w0JuSVFE$SkYZub$1k+xC z->-mu-*>$@Rbkipz!|CO>|(_vt3itQ`?;0evq;DGo@s@wz%*c4$TX5Ylx-@Y%Z(#A zhfDjiM$@BM)7H8V$)VJL>LKBwt?a`&~g|CRshb56D&wy;9?y5wug%Mc8${F6hF%WNh%CPYuW&G zefv2~e*n<$3&I)@{JRl?%JY|DRK)=qMJ7{%5;&GrX_yNz7+U}UCj+;e4|hAU^;}%c zFi=`l;5t_OdKFN&Wds1Oeq4{R72#kimG5>qCK!SEpz97d0V;OU76;&c`Wyg+4(^M5 z5+_sD7{imGUMEZV&mA^BN>~-(RR<`24k(G=uYxwuc<)5dG3aqW z=9%Qx4?ynD^Nb&e0gk)YFyNPgknVXr(Yqa>CDSguwiUqOxa3j71O@|;@j3XafMfkP z))Fc~dV9xWV$}dlzW_#CEzt8OOnhTPXt9EydjYpA1v^SkA?$gi@aAg(m|h(`dJjNN z(m~XKnyg)@5$5WNx(mR|5HJkFS#PdN!2P`($V6uUNa5GC0B~siC4_C~5xg@2(DuXP zo~ASam}4w0Lgwuhu~w|2|3q9yNLl?T;j#Bn=2ZcgHbY5{8hCezt6>03+5sl^WPnX& z0K%fyU;s2WfK@wrNGAc)VgQPjG&u*&9yaPrJ=kjap%+5KsZ1I20?o@HEd`AK0GRd* z1iaP{Vk)>?1)ChE0EquckMtrybtzDk0icgnYZeaMp-bwk?==^$UF~+C6&%l@^fj9Y z!IdmW<@)z)um;9`|5}0ke<(5^f|^p(!MhfKifh{%HNG#D0~N_#3-WUS;A@=XakU;O ziLWm=7iJ2uBJ?}}vAKHdnde_-10VtG+1)G_sJ%14&O|s`!-xstD*%%brQ=D-OjwA{ zb=3i&bW14!fR5MOe*YU?oAN;x!swUX_---+zz#LCNf!_n-euM>BOn;Z5hjM@HC+D_ z2-6b}ObEImJ;Or)m?qtkjTe=OG17YWaZhH=Wy~8*97mQ{gHZmn<#n=z|Np{mhnXQM zLDu3__=koS{vgvQ?~MXsD6)4S2-BKBAY72G2v2yB5MD=0yn$X1fFc8=n>)PP6vgX4R5|rLGPB{-CQfQsL}X6!he_%^eJ8(+%eZ(#^i z-f(#-dI0$F~@QVW1BTyY1;@y(q&-~w0vl8V+NfuI(aNf3F^Z)b>? ze?SnFy_q~)m`NB%&?rVhp%_C@A>0E1<5q}04>0&S2E_mjDF#W?!Y2(rjeuQ6RBYiH z<0v4tKf#mPvY|OK(g2hvIwzYzDBo>hphOR_5nH`)+>^i6x>l+IQ0T^#AiEw6LMI*E rAA4Glgx2Z0vuii00000NkvXXu0mjfsy;L! literal 0 HcmV?d00001 diff --git a/tests/ref/math-attach-limit-long.png b/tests/ref/math-attach-limit-long.png new file mode 100644 index 0000000000000000000000000000000000000000..b79e6ed4adf14df3b8f1d182c16356b1b6cc5879 GIT binary patch literal 1941 zcmV;G2Wt37%*V4fna06!8RsfYz)}O_eT`|q#x z`s;bVU;UoK4)qiTQ!oWn@K*v$u0kWYhI_ZWK_uhj^D1X)!RnEym}Nn&r>?0RP{o00 z&ol@+ih+3CI>|*9Tb>$u+{EWjiJEKuh7kjfG!i5d%<)e<27~Bqd%Iq1G85&_wDbu@q%?to~Tq^zI-JS72tPR zrmk!Zbj)d(cw5X19;?Uc?rM1f&SF$z%vpJy41Zy9jJoyohZY`N2u1XK?_IgB;U?y4 zpy4zV6;&EwDn_ggjp!NZU|=y?F6Xs^%{1UTJS^d4VnG1|NMVn(+KI7Cwb@VxweB9L&)Z|U?m1jzptogOrU&&9n{ zz@9!tr|&C7$D4He>#a}h%DgdR+mZ7&E2UXV9rP4T!Cwtb3Wu7#fq`3ov-c+_*9zVH zokKI&qUk?unR{2jd6~4D!XzCGi*d=+B!=;s>jkl@us;WIGAk8tb&{Yu!IGN84ew0Y&Mwez{2K$bp_CPfhCw7 z^YCb(v)a6F1F+~nEIEs=vD5A>ehKtZh)*=?3K^@X5+WwWtd(ox5pNw@ubdGtD(>PUd_ZQL|V52<1rKN#>#$Y9E{FSqNcCwkPRjC?Zq1I;f(=z zjt8>D8Fd^?h`Do9avM1yZiTknPev6f?Grox zwLXB)6rx-Nt9e0V44fY?8)70H6CzKs1nKY>7MH3;Sy}FfO*$w_=jEJbx1bBlV%WJo z=)Xq*Q>Eg$tSqIvV;GC2@>!k|11)Ym06K)rk2urRT}{Ch{G?%hnibup6AIpEKG5L@-HaPF)x*)G34yKdJyX?`UQLKpp0T|3>e_>=SIqP+? za{C9+MQQmfz$ZGh3QdSL)~4z66+r(TuyDkpNS*Q&F&te^q|BI9=vv8~VhF3{H?{!- z)e>}PLXfYW6kk>zm(-wt0wuqe)b&MoufrHG!E}PXdxWuCU!?oJDid}##Bb#3a~e{& z1CI@9>pds?8_sYz_*iwx6M=JZRl6<`subHgY~pAoGgYixHxh=l4V*B9gC#;aVxhg; z;xD|AQ11$jtR4`43gyVx=(?3jNJ0Dd|WU|9CMmnyM*WKTS{ zsEdhL4DhXWQ0#InJW%VmfzZNeH4xdUgbsE)IC9eN4f-0j@d-VCs4JZ0=bA z_|ag1Vc9zeDzW_G{sj2Q8n*uekiP$@emDQCJt7inp4%xH2PSPZ%{-U|i^EMVI(@GV z{b~SHkFmrxLevYm#y#u?(%Y$AtZk9m2DF$M@*7@=S#mO^*PAV&@E+GbV9g_q5Z-T2 zGgX4clwC1_VBJB9J4`Rgm_!-Fkur+HCBCrnM&H`NY{7%J?Yx0lhVmfHhA>Cl(atr7 zw5nik$E9#ZhAw;K@x?-%!gTEFWE;boUr+w6LkO_Z`fh`W`7m`mu(Ql#Md(CrT$dj4 zBT3{rTqRN7oM3E;RIkHe7#-f+?qc?3i=(maeMF#_2iDMgXeuf77bfEs-q;Fe4jF1$ z`bflfT_965`8WZ0g@+|tx(a|(>@CU7t9Jp!kisLfVPzPY1$2F+lWzuAhld?f@gs~(0J0&B&BD`9Lg$5DxQt#Eb#qbh b*986#&Pu3~(p}5g00000NkvXXu0mjfh}oea literal 0 HcmV?d00001 diff --git a/tests/ref/math-attach-mixed.png b/tests/ref/math-attach-mixed.png index 4be327e3e902f873341cfa60ad84aa3916017228..e1268b43bc653fba21a4cee084db66b3aacbc578 100644 GIT binary patch literal 2327 zcmV+y3F!8TP)DSbH#=LW-EFs>ovu@Ny0zVQcRO{rb=y(b9##>l0^)kM^(s_p)gq!CR*^$M z$`LL>F1f;$fEWT23}@s%0wE-jkoV3$-+?60gpr62yVJaWdcM!|eDa>jJo7x?=U5}p z`NF=iFYF8dPlZpeQMmE1-D?xpUSe&+*Xx>A5w5>LA~K59{0<&(w2D=P^&#y);?A1! zv(os?pX&CXLL+7)LybhxtZX+hq{4$bH z^m!rJRCb&S!W`=y0352(e!voqKYtto;A*_(C2zyRukEct05osr$6Va~etb&gMqfh_ z$&xLNY((l5gIcF z=!vWvM>3vqe;S!;#Sjv8N?4o)WwuzNx$%9pncA$u6@~*^vx1ndoJEpx(XK&IXcWCw{xKaO)f|* zajaWt3BhTdt!?kh!Z~O8yLJqKIhcEcxFpTu3O~)<+Zqr{VfY_>SD?j)F!3Je6s^o7 zOuxg$pEWBAPn}NR90$O{udHx3_s~h5E`@xK-GGW~30rfgQ${Lhx;6mA6$Oc$R($#0 zVBt<+v?4Eo(44?fL+Pg=gyrl%-Q(@My2*ji;XnQrs1Wog2KG2;1iFTfjUW-r@qas) zGG2u%LFm4eT#VWbjA(%UR(}?10M)fz6*QP&TnnfYGgL^7_g52knk;yK7Rm5_pgb%I ziMq9X0``p~G;7Moky%|hUr~clbhHL@RHAJAJoN0p$H#gPTW~kKYv$;)4mz zQ}FD=ngl&^iUi-D%NO>A9~;i^WA(0pFrZjzxcj|2RxNd#5s4|}!b-#VvwBHN5cb+v z8tw~kWz~WQhn6;Tp2B^x6d{gch(|J)Hi}G3T7MbKX-lFAxynWmvLAX1_w2-P=_r%s zv>cc%WI4Kk!IR!U)xq~pX&A~LhTuUJlDWc8vum_uhT8OUGfooVy?y*>Bu+X zLYFs3`x&DS& zw)q(kyDS&xm;-rOuHz91LF|MwfKCelVl~mVqsN3K$h03*Ba3=WV6F%B5gD=L09a}P zwBCK4YNEpsc8=lHpI3e#q3}Q6A;f3&6E5f!CBOdf#zJaUHOGohN zgVH%K!cLY?r;f7%TWtIBr0sa)%_j2=!d;nEBaSl6w{e<}@55;%iE7Z~ z&oGkcNJr9AY-@nD`v|u9UKVmYbLJ2Oj%v{6nHUuV@^Htlse_A0q_G^GpTiGvw0j{H zN!h8gTevgCR{p%Oc-!KDzdhAMxaUGf^aw+DZ5Y09@50HUG}v$o>^;_ssdO3wr>cn_3DBI3L|;CIP+6019{zmf^&V=K%RXG|M<+Mqu5ceA_iz_b%$vBI zdX+oj02BvsNR7LFg=|Fd72N{T?+$@anH{hjI{>PoKrnu z!ZJ1A{Zq-QLCPH<3^IzuRtab{;jPS%+Kt#Nfl15seDAfQPy=@tU~KS{Zj&_7vTuI3 zhj4$xr4A$mXG(8ze;DZd)c%l#5n#XF4~khdJB84t2YRC#J7CxWy*dyBAvQ69acFn1 zRSfK#I_M!>R;=hmVoQ5}6oB~~xfNC>KL%=&LvWEBg$qSd{vd3!A{ajcYHtQ1Y0J!Q zhJpw{5me$9mdnVNJiKIHyYWkd4FuL(jgPv9aZzL)kZabCTv6H}vl zknuAdKxTO7gIFXfNzr;_cK3+^Z^B3CL#)VzIQAk_dONiW$ye3`vmOS#4mYLbB6mBo x8=0BJiwO5p6v!1`();#YzOXOs3;(}|{{#CLpbIyp9qIr8002ovPDHLkV1l=Cferuw literal 2357 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 delta 1041 zcmV+s1n&En2&M>-B!BHmL_t(|+U?g{Zxm%1z;QzG!YdPRjUT{hj5kJ&F&8|g%=#(M|xvB@Ab5l zzxKc-X2^1?0h38|qZ_9gdT<+v-N++IeTVMT}uWqyYm1+LHI-vBEScoWDE`{ z0oUfh(`Rv8T!7zbEZSl;PJsPoSXTjJ6-MLhMq>{wKLWYC!1_bnaJ>oN<6%CgKyuVW z`9xlD0Z5K|F&VP099}k!#|!&v%pFJT<_g%k7=I0O+I;@Y&xBZ**7mlJ1MW<0Re1Ws z@xX{&0q+I?@Q)#zov7aAa>GjX&D8i|cKZ+T_xs|fN>{|?-K+o{?)C8xns5L|oZ1YD zJ=F&_ftQ<`d-Zv&!~Zcj;?V{Unk?@r0f&*z56I`g8rrA;Jg^11+AUdfv?Ff}IB8b^ zCVv~bF0pMInaYRYRNHAKVc!VRjU7R(KNempE^dqu_RWj1blD?x2N}PO@{kpMiqIC;<RCP#1($h1Msr_=DJ{EOYhjsY>1pfwesVrPKUAmC~015yA LNkvXXu0mjfb7J_0 diff --git a/tests/ref/math-attach-nested.png b/tests/ref/math-attach-nested.png index 8b4309cf8c1a816723dd63b42a9f122631a0c20d..c6cc99b083b0d3fce13731cfa16451517282c300 100644 GIT binary patch delta 935 zcmV;Y16cgH2fYW7B!7fSL_t(|+U?j$PZMDr$8leS(X%lzUW_MCMiafL2aSo*L){K4 ziHQkn)W$7>R+gyLEiM%mg}P7)AW~9FTPn4nErK13QfUEgWof6&&~_-DEczEmS44%b#Hx(|_DhsEZfs014IAAQeX`K5U4A zUSq>}L2v<2t|yHRNWorc)C$i55Ih*a_^E-vS09B~Jo@x7fX$k2v6x6d-0+B#6jNGR z;}&oUuLs69&GX2l+?+g#oy!{g!A3M+bepRTc++M6-CS(SXsP=O4FDW_S!1_K3E-_5 zGt}a4V}IGP`w$(k(t0`yGYEU)I^6&O)U!{*E)jr`g1yf9A<7bj&q4zQga00$$fL~D z2dxS-jgFrL;3V)@)kA0aaUi+AuN>Xq<&t@aV+hn%LMfqaEu4FZP*<}rA@UUUY`>rv z)AWLU@@QuX;7lhxQT@gSzpRO>y;B(_gp6uW9e>i(Q4}Ux8(727I04t8bi#Nlhy!^e zzk107j^HI9MttDb(bAMVYwL|j$2Gz3!3BNG+<6`;Xd4&p`&c%H*~Pjq&|Qti zk&!+tF!Uq9+m}SdRc3pQ(_gL2)Ihn=%Ujs{>LCsKD6r8CNO=Bsq93{JIQS0o-#mw} z?%z$K;Eqq0mA^g}C(Gkm32Rpq$Vzy)I3V|DFBi^*bK(Ck{0AWkSAR6MBQO8}002ov JPDHLkV1ix5y+{B6 delta 930 zcmV;T16};R2e=22B!7QNL_t(|+U?lgYm-+Tz;W+)HReAsc0ZWxX0V%$K?R54*bD3h z*Ub*c7;IQ@7TLtHwwvN+m)JJ7vrXHOq}o_b)uc&m7OBx@NxQTr?K0EO*wqrvyUp8^ zuLudW;Yk`zeR7k}<>A2h>hL?~Y@45%pXT=4&gK1T;D6KbjLEZA;q&I-DZ@Lk%B~@Z zJ{MMA>JmkSWHQUq6X4rHI+>*Muv;bW z0RaA@RV~FllR4t=!3i@dOQ6ajk<9?iQX~nG^RV9E)~g2@>8K9{lzQR}jD#EvKpzhBCvq?5Oa}mG+1^y|BwIwL9>2l#~ z51%F+4GzeVNoWReL4j4}iZ=Gi$Q%Dge%*TS_>+cxy8jsI_WYqbg5w`V zEKfvPXU0vmZGOdIC~RiRU`V1mOIlM=SlM8L?|%-&<-(gp4&TMtgSf1hb%F|`&^ctG zNdMF^3~d#Kv)ch#hPBL|%!vLnB`MtRkoR@J(9_jHCIQ7FJf2QJo{DZ7=EV?}1<0NB z5%5u(o1NsH)ke{&5%+jHUApYsJp6_;p#=7~G*O=V8UT=*7@el9D+{X4!=Ih&O+(J> z^?y^A^7OCdxtx?YKbhM+eDve%R3NA5)@<$dcHj$2)2hc=@FSx7aD0u@((Nwu?8!d5 znkV6hRzb3tge`JpIXNa{@nzt60?ER2<{0 diff --git a/tests/ref/math-attach-prescripts.png b/tests/ref/math-attach-prescripts.png index cd105e9df44909b74089318c90d8fbf47cb61eac..f0d21cb8a67f9040d94dcc678d8da1024d481ab6 100644 GIT binary patch delta 645 zcmV;00($+U1)c?vB!6W|L_t(|+U?cPOOtT`$8j&6`bWCerGk>gOF`Hr42yVFeQN61czio(%9vkF2gw3aav6F}?Bw1#>~fOuNS(9t+c`j=O|ae*a5)?fKcX3< z>#4VMID8kB3V+CX!+DUD6Xni!?`PspiNbS5M%1m|DjBeJG@`hgEwD?TU%lM%mQ|$~ z_MMvIWP3wD5iNcZ+84x@okdQJ<^=Sjn!kGa0%x0O{Y$e)7 zw~z&li_Tvmp9DJThGqxlU~%04IdC^o{*j9jvrc z+)WSBQ$y!cKm1J?{&~D0OhoyvW&#AVHxXH1A`&tmM1-}1AI~ez0R10c%(FjCt(9=I zYI4u?yMDn4rFvzeaJ{Qg1Cl12fN0Bt$Y@gxP#+PweaB^Lee00000NkvXXu0mjf2Zk^P delta 651 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