mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Add missing under/over-delimiters (#4637)
This commit is contained in:
parent
211b546e4e
commit
810491c9d3
@ -176,6 +176,10 @@ pub fn module() -> Module {
|
|||||||
math.define_elem::<OverbraceElem>();
|
math.define_elem::<OverbraceElem>();
|
||||||
math.define_elem::<UnderbracketElem>();
|
math.define_elem::<UnderbracketElem>();
|
||||||
math.define_elem::<OverbracketElem>();
|
math.define_elem::<OverbracketElem>();
|
||||||
|
math.define_elem::<UnderparenElem>();
|
||||||
|
math.define_elem::<OverparenElem>();
|
||||||
|
math.define_elem::<UndershellElem>();
|
||||||
|
math.define_elem::<OvershellElem>();
|
||||||
math.define_elem::<CancelElem>();
|
math.define_elem::<CancelElem>();
|
||||||
math.define_elem::<FracElem>();
|
math.define_elem::<FracElem>();
|
||||||
math.define_elem::<BinomElem>();
|
math.define_elem::<BinomElem>();
|
||||||
|
@ -2,9 +2,9 @@ use crate::diag::SourceResult;
|
|||||||
use crate::foundations::{elem, Content, Packed, StyleChain};
|
use crate::foundations::{elem, Content, Packed, StyleChain};
|
||||||
use crate::layout::{Abs, Em, FixedAlignment, Frame, FrameItem, Point, Size};
|
use crate::layout::{Abs, Em, FixedAlignment, Frame, FrameItem, Point, Size};
|
||||||
use crate::math::{
|
use crate::math::{
|
||||||
alignments, scaled_font_size, style_cramped, style_for_subscript, AlignmentResult,
|
alignments, scaled_font_size, style_cramped, style_for_subscript,
|
||||||
FrameFragment, GlyphFragment, LayoutMath, LeftRightAlternator, MathContext, MathRun,
|
style_for_superscript, AlignmentResult, FrameFragment, GlyphFragment, LayoutMath,
|
||||||
Scaled,
|
LeftRightAlternator, MathContext, MathRun, Scaled,
|
||||||
};
|
};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::text::TextElem;
|
use crate::text::TextElem;
|
||||||
@ -12,11 +12,13 @@ use crate::visualize::{FixedStroke, Geometry};
|
|||||||
|
|
||||||
const BRACE_GAP: Em = Em::new(0.25);
|
const BRACE_GAP: Em = Em::new(0.25);
|
||||||
const BRACKET_GAP: Em = Em::new(0.25);
|
const BRACKET_GAP: Em = Em::new(0.25);
|
||||||
|
const PAREN_GAP: Em = Em::new(0.25);
|
||||||
|
const SHELL_GAP: Em = Em::new(0.25);
|
||||||
|
|
||||||
/// A marker to distinguish under- vs. overlines.
|
/// A marker to distinguish under- vs. overlines.
|
||||||
enum LineKind {
|
enum Position {
|
||||||
Over,
|
|
||||||
Under,
|
Under,
|
||||||
|
Over,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A horizontal line under content.
|
/// A horizontal line under content.
|
||||||
@ -34,7 +36,7 @@ pub struct UnderlineElem {
|
|||||||
impl LayoutMath for Packed<UnderlineElem> {
|
impl LayoutMath for Packed<UnderlineElem> {
|
||||||
#[typst_macros::time(name = "math.underline", span = self.span())]
|
#[typst_macros::time(name = "math.underline", span = self.span())]
|
||||||
fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
|
||||||
layout_underoverline(ctx, styles, self.body(), self.span(), LineKind::Under)
|
layout_underoverline(ctx, styles, self.body(), self.span(), Position::Under)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +55,7 @@ pub struct OverlineElem {
|
|||||||
impl LayoutMath for Packed<OverlineElem> {
|
impl LayoutMath for Packed<OverlineElem> {
|
||||||
#[typst_macros::time(name = "math.overline", span = self.span())]
|
#[typst_macros::time(name = "math.overline", span = self.span())]
|
||||||
fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
|
||||||
layout_underoverline(ctx, styles, self.body(), self.span(), LineKind::Over)
|
layout_underoverline(ctx, styles, self.body(), self.span(), Position::Over)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,11 +65,11 @@ fn layout_underoverline(
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
body: &Content,
|
body: &Content,
|
||||||
span: Span,
|
span: Span,
|
||||||
line: LineKind,
|
position: Position,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let (extra_height, content, line_pos, content_pos, baseline, bar_height);
|
let (extra_height, content, line_pos, content_pos, baseline, bar_height);
|
||||||
match line {
|
match position {
|
||||||
LineKind::Under => {
|
Position::Under => {
|
||||||
let sep = scaled!(ctx, styles, underbar_extra_descender);
|
let sep = scaled!(ctx, styles, underbar_extra_descender);
|
||||||
bar_height = scaled!(ctx, styles, underbar_rule_thickness);
|
bar_height = scaled!(ctx, styles, underbar_rule_thickness);
|
||||||
let gap = scaled!(ctx, styles, underbar_vertical_gap);
|
let gap = scaled!(ctx, styles, underbar_vertical_gap);
|
||||||
@ -79,7 +81,7 @@ fn layout_underoverline(
|
|||||||
content_pos = Point::zero();
|
content_pos = Point::zero();
|
||||||
baseline = content.ascent()
|
baseline = content.ascent()
|
||||||
}
|
}
|
||||||
LineKind::Over => {
|
Position::Over => {
|
||||||
let sep = scaled!(ctx, styles, overbar_extra_ascender);
|
let sep = scaled!(ctx, styles, overbar_extra_ascender);
|
||||||
bar_height = scaled!(ctx, styles, overbar_rule_thickness);
|
bar_height = scaled!(ctx, styles, overbar_rule_thickness);
|
||||||
let gap = scaled!(ctx, styles, overbar_vertical_gap);
|
let gap = scaled!(ctx, styles, overbar_vertical_gap);
|
||||||
@ -145,7 +147,7 @@ impl LayoutMath for Packed<UnderbraceElem> {
|
|||||||
&self.annotation(styles),
|
&self.annotation(styles),
|
||||||
'⏟',
|
'⏟',
|
||||||
BRACE_GAP,
|
BRACE_GAP,
|
||||||
false,
|
Position::Under,
|
||||||
self.span(),
|
self.span(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -177,7 +179,7 @@ impl LayoutMath for Packed<OverbraceElem> {
|
|||||||
&self.annotation(styles),
|
&self.annotation(styles),
|
||||||
'⏞',
|
'⏞',
|
||||||
BRACE_GAP,
|
BRACE_GAP,
|
||||||
true,
|
Position::Over,
|
||||||
self.span(),
|
self.span(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -200,7 +202,7 @@ pub struct UnderbracketElem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for Packed<UnderbracketElem> {
|
impl LayoutMath for Packed<UnderbracketElem> {
|
||||||
#[typst_macros::time(name = "math.underbrace", span = self.span())]
|
#[typst_macros::time(name = "math.underbracket", span = self.span())]
|
||||||
fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
|
||||||
layout_underoverspreader(
|
layout_underoverspreader(
|
||||||
ctx,
|
ctx,
|
||||||
@ -209,7 +211,7 @@ impl LayoutMath for Packed<UnderbracketElem> {
|
|||||||
&self.annotation(styles),
|
&self.annotation(styles),
|
||||||
'⎵',
|
'⎵',
|
||||||
BRACKET_GAP,
|
BRACKET_GAP,
|
||||||
false,
|
Position::Under,
|
||||||
self.span(),
|
self.span(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -241,7 +243,135 @@ impl LayoutMath for Packed<OverbracketElem> {
|
|||||||
&self.annotation(styles),
|
&self.annotation(styles),
|
||||||
'⎴',
|
'⎴',
|
||||||
BRACKET_GAP,
|
BRACKET_GAP,
|
||||||
true,
|
Position::Over,
|
||||||
|
self.span(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A horizontal parenthesis under content, with an optional annotation below.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// $ underparen(1 + 2 + ... + 5, "numbers") $
|
||||||
|
/// ```
|
||||||
|
#[elem(LayoutMath)]
|
||||||
|
pub struct UnderparenElem {
|
||||||
|
/// The content above the parenthesis.
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
|
|
||||||
|
/// The optional content below the parenthesis.
|
||||||
|
#[positional]
|
||||||
|
pub annotation: Option<Content>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutMath for Packed<UnderparenElem> {
|
||||||
|
#[typst_macros::time(name = "math.underparen", span = self.span())]
|
||||||
|
fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
|
||||||
|
layout_underoverspreader(
|
||||||
|
ctx,
|
||||||
|
styles,
|
||||||
|
self.body(),
|
||||||
|
&self.annotation(styles),
|
||||||
|
'⏝',
|
||||||
|
PAREN_GAP,
|
||||||
|
Position::Under,
|
||||||
|
self.span(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A horizontal parenthesis over content, with an optional annotation above.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// $ overparen(1 + 2 + ... + 5, "numbers") $
|
||||||
|
/// ```
|
||||||
|
#[elem(LayoutMath)]
|
||||||
|
pub struct OverparenElem {
|
||||||
|
/// The content below the parenthesis.
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
|
|
||||||
|
/// The optional content above the parenthesis.
|
||||||
|
#[positional]
|
||||||
|
pub annotation: Option<Content>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutMath for Packed<OverparenElem> {
|
||||||
|
#[typst_macros::time(name = "math.overparen", span = self.span())]
|
||||||
|
fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
|
||||||
|
layout_underoverspreader(
|
||||||
|
ctx,
|
||||||
|
styles,
|
||||||
|
self.body(),
|
||||||
|
&self.annotation(styles),
|
||||||
|
'⏜',
|
||||||
|
PAREN_GAP,
|
||||||
|
Position::Over,
|
||||||
|
self.span(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A horizontal tortoise shell bracket under content, with an optional annotation below.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// $ undershell(1 + 2 + ... + 5, "numbers") $
|
||||||
|
/// ```
|
||||||
|
#[elem(LayoutMath)]
|
||||||
|
pub struct UndershellElem {
|
||||||
|
/// The content above the tortoise shell bracket.
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
|
|
||||||
|
/// The optional content below the tortoise shell bracket.
|
||||||
|
#[positional]
|
||||||
|
pub annotation: Option<Content>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutMath for Packed<UndershellElem> {
|
||||||
|
#[typst_macros::time(name = "math.undershell", span = self.span())]
|
||||||
|
fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
|
||||||
|
layout_underoverspreader(
|
||||||
|
ctx,
|
||||||
|
styles,
|
||||||
|
self.body(),
|
||||||
|
&self.annotation(styles),
|
||||||
|
'⏡',
|
||||||
|
SHELL_GAP,
|
||||||
|
Position::Under,
|
||||||
|
self.span(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A horizontal tortoise shell bracket over content, with an optional annotation above.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// $ overshell(1 + 2 + ... + 5, "numbers") $
|
||||||
|
/// ```
|
||||||
|
#[elem(LayoutMath)]
|
||||||
|
pub struct OvershellElem {
|
||||||
|
/// The content below the tortoise shell bracket.
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
|
|
||||||
|
/// The optional content above the tortoise shell bracket.
|
||||||
|
#[positional]
|
||||||
|
pub annotation: Option<Content>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutMath for Packed<OvershellElem> {
|
||||||
|
#[typst_macros::time(name = "math.overshell", span = self.span())]
|
||||||
|
fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
|
||||||
|
layout_underoverspreader(
|
||||||
|
ctx,
|
||||||
|
styles,
|
||||||
|
self.body(),
|
||||||
|
&self.annotation(styles),
|
||||||
|
'⏠',
|
||||||
|
SHELL_GAP,
|
||||||
|
Position::Over,
|
||||||
self.span(),
|
self.span(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -256,7 +386,7 @@ fn layout_underoverspreader(
|
|||||||
annotation: &Option<Content>,
|
annotation: &Option<Content>,
|
||||||
c: char,
|
c: char,
|
||||||
gap: Em,
|
gap: Em,
|
||||||
reverse: bool,
|
position: Position,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let font_size = scaled_font_size(ctx, styles);
|
let font_size = scaled_font_size(ctx, styles);
|
||||||
@ -267,30 +397,30 @@ fn layout_underoverspreader(
|
|||||||
let glyph = GlyphFragment::new(ctx, styles, c, span);
|
let glyph = GlyphFragment::new(ctx, styles, c, span);
|
||||||
let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
|
let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
|
||||||
|
|
||||||
let mut rows = vec![MathRun::new(vec![body]), stretched.into()];
|
let mut rows = vec![];
|
||||||
|
let baseline = match position {
|
||||||
let (sup_style, sub_style);
|
Position::Under => {
|
||||||
let row_styles = if reverse {
|
rows.push(MathRun::new(vec![body]));
|
||||||
sup_style = style_for_subscript(styles);
|
rows.push(stretched.into());
|
||||||
styles.chain(&sup_style)
|
if let Some(annotation) = annotation {
|
||||||
} else {
|
let under_style = style_for_subscript(styles);
|
||||||
sub_style = style_for_subscript(styles);
|
let annotation_styles = styles.chain(&under_style);
|
||||||
styles.chain(&sub_style)
|
rows.push(ctx.layout_into_run(annotation, annotation_styles)?);
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
Position::Over => {
|
||||||
|
if let Some(annotation) = annotation {
|
||||||
|
let over_style = style_for_superscript(styles);
|
||||||
|
let annotation_styles = styles.chain(&over_style);
|
||||||
|
rows.push(ctx.layout_into_run(annotation, annotation_styles)?);
|
||||||
|
}
|
||||||
|
rows.push(stretched.into());
|
||||||
|
rows.push(MathRun::new(vec![body]));
|
||||||
|
rows.len() - 1
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
rows.extend(
|
|
||||||
annotation
|
|
||||||
.as_ref()
|
|
||||||
.map(|annotation| ctx.layout_into_run(annotation, row_styles))
|
|
||||||
.transpose()?,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut baseline = 0;
|
|
||||||
if reverse {
|
|
||||||
rows.reverse();
|
|
||||||
baseline = rows.len() - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let frame = stack(
|
let frame = stack(
|
||||||
rows,
|
rows,
|
||||||
FixedAlignment::Center,
|
FixedAlignment::Center,
|
||||||
|
@ -46,6 +46,10 @@
|
|||||||
"overbrace",
|
"overbrace",
|
||||||
"underbracket",
|
"underbracket",
|
||||||
"overbracket",
|
"overbracket",
|
||||||
|
"underparen",
|
||||||
|
"overparen",
|
||||||
|
"undershell",
|
||||||
|
"overshell",
|
||||||
]
|
]
|
||||||
details: |
|
details: |
|
||||||
Delimiters above or below parts of an equation.
|
Delimiters above or below parts of an equation.
|
||||||
|
BIN
tests/ref/math-underover-parens.png
Normal file
BIN
tests/ref/math-underover-parens.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 726 B |
BIN
tests/ref/math-underover-shells.png
Normal file
BIN
tests/ref/math-underover-shells.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 551 B |
@ -19,3 +19,17 @@ $ x = overbracket(
|
|||||||
$ underbracket([1, 2/3], "relevant stuff")
|
$ underbracket([1, 2/3], "relevant stuff")
|
||||||
arrow.l.r.double.long
|
arrow.l.r.double.long
|
||||||
overbracket([4/5,6], "irrelevant stuff") $
|
overbracket([4/5,6], "irrelevant stuff") $
|
||||||
|
|
||||||
|
--- math-underover-parens ---
|
||||||
|
// Test parentheses.
|
||||||
|
$ overparen(
|
||||||
|
underparen(x + y, "long comment"),
|
||||||
|
1 + 2 + ... + 5
|
||||||
|
) $
|
||||||
|
|
||||||
|
--- math-underover-shells ---
|
||||||
|
// Test tortoise shell brackets.
|
||||||
|
$ undershell(
|
||||||
|
1 + overshell(2 + ..., x + y),
|
||||||
|
"all stuff"
|
||||||
|
) $
|
||||||
|
Loading…
x
Reference in New Issue
Block a user