Math tests

This commit is contained in:
Laurenz 2023-02-02 14:13:56 +01:00
parent 21dd99926a
commit 5f5c659279
49 changed files with 422 additions and 146 deletions

View File

@ -23,7 +23,7 @@ macro_rules! percent {
} }
/// The context for math layout. /// The context for math layout.
pub(super) struct MathContext<'a, 'b, 'v> { pub struct MathContext<'a, 'b, 'v> {
pub vt: &'v mut Vt<'b>, pub vt: &'v mut Vt<'b>,
pub regions: Regions<'a>, pub regions: Regions<'a>,
pub font: &'a Font, pub font: &'a Font,

View File

@ -1,5 +1,7 @@
use super::*; use super::*;
const FRAC_AROUND: Em = Em::new(0.1);
/// # Fraction /// # Fraction
/// A mathematical fraction. /// A mathematical fraction.
/// ///
@ -130,7 +132,7 @@ fn layout(
let denom = ctx.layout_frame(denom)?; let denom = ctx.layout_frame(denom)?;
ctx.unstyle(); ctx.unstyle();
let around = Em::new(0.1).scaled(ctx); let around = FRAC_AROUND.scaled(ctx);
let num_gap = (shift_up - axis - num.descent()).max(num_min + thickness / 2.0); let num_gap = (shift_up - axis - num.descent()).max(num_min + thickness / 2.0);
let denom_gap = (shift_down + axis - denom.ascent()).max(denom_min + thickness / 2.0); let denom_gap = (shift_down + axis - denom.ascent()).max(denom_min + thickness / 2.0);

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(super) enum MathFragment { pub enum MathFragment {
Glyph(GlyphFragment), Glyph(GlyphFragment),
Variant(VariantFragment), Variant(VariantFragment),
Frame(FrameFragment), Frame(FrameFragment),
@ -118,7 +118,7 @@ impl From<Frame> for MathFragment {
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(super) struct GlyphFragment { pub struct GlyphFragment {
pub id: GlyphId, pub id: GlyphId,
pub c: char, pub c: char,
pub lang: Lang, pub lang: Lang,

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
const ROW_GAP: Em = Em::new(0.5); const ROW_GAP: Em = Em::new(0.5);
const COL_GAP: Em = Em::new(0.75); const COL_GAP: Em = Em::new(0.5);
const VERTICAL_PADDING: Ratio = Ratio::new(0.1); const VERTICAL_PADDING: Ratio = Ratio::new(0.1);
/// # Vector /// # Vector

View File

@ -5,29 +5,29 @@ mod ctx;
mod accent; mod accent;
mod align; mod align;
mod attach; mod attach;
mod delimited;
mod frac; mod frac;
mod fragment; mod fragment;
mod lr;
mod matrix; mod matrix;
mod op; mod op;
mod root; mod root;
mod row; mod row;
mod spacing; mod spacing;
mod stack;
mod stretch; mod stretch;
mod style; mod style;
mod symbols; mod symbols;
mod underover;
pub use self::accent::*; pub use self::accent::*;
pub use self::align::*; pub use self::align::*;
pub use self::attach::*; pub use self::attach::*;
pub use self::delimited::*;
pub use self::frac::*; pub use self::frac::*;
pub use self::lr::*;
pub use self::matrix::*; pub use self::matrix::*;
pub use self::op::*; pub use self::op::*;
pub use self::root::*; pub use self::root::*;
pub use self::stack::*;
pub use self::style::*; pub use self::style::*;
pub use self::underover::*;
use ttf_parser::{GlyphId, Rect}; use ttf_parser::{GlyphId, Rect};
use typst::font::Font; use typst::font::Font;
@ -230,7 +230,7 @@ impl Layout for FormulaNode {
impl Inline for FormulaNode {} impl Inline for FormulaNode {}
#[capability] #[capability]
trait LayoutMath { pub trait LayoutMath {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>; fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>;
} }

View File

@ -1,5 +1,7 @@
use super::*; use super::*;
const MIN_INDEX_SHIFT: Em = Em::new(0.35);
/// # Square Root /// # Square Root
/// A square root. /// A square root.
/// ///
@ -126,7 +128,7 @@ fn layout(
if let Some(index) = &index { if let Some(index) = &index {
sqrt_offset = kern_before + index.width() + kern_after; sqrt_offset = kern_before + index.width() + kern_after;
shift_up.set_max(index.descent() + Em::new(0.35).scaled(ctx)); shift_up.set_max(index.descent() + MIN_INDEX_SHIFT.scaled(ctx));
ascent.set_max(shift_up + index.ascent()); ascent.set_max(shift_up + index.ascent());
} }

View File

@ -2,8 +2,10 @@ use crate::layout::AlignNode;
use super::*; use super::*;
pub const TIGHT_LEADING: Em = Em::new(0.25);
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub(super) struct MathRow(pub Vec<MathFragment>); pub struct MathRow(pub Vec<MathFragment>);
impl MathRow { impl MathRow {
pub fn new() -> Self { pub fn new() -> Self {
@ -85,8 +87,12 @@ impl MathRow {
) -> Frame { ) -> Frame {
if self.0.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) { if self.0.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) {
let fragments = std::mem::take(&mut self.0); let fragments = std::mem::take(&mut self.0);
let leading = if ctx.style.size >= MathSize::Text {
ctx.styles().get(ParNode::LEADING)
} else {
TIGHT_LEADING.scaled(ctx)
};
let leading = ctx.styles().get(ParNode::LEADING) * ctx.style.size.factor(ctx);
let rows: Vec<_> = fragments let rows: Vec<_> = fragments
.split(|frag| matches!(frag, MathFragment::Linebreak)) .split(|frag| matches!(frag, MathFragment::Linebreak))
.map(|slice| Self(slice.to_vec())) .map(|slice| Self(slice.to_vec()))

View File

@ -24,11 +24,8 @@ pub(super) fn spacing(
) -> Em { ) -> Em {
use MathClass::*; use MathClass::*;
let script = style.size <= MathSize::Script; let script = style.size <= MathSize::Script;
let (Some(l), Some(r)) = (left.class(), right.class()) else { let class = |frag: &MathFragment| frag.class().unwrap_or(Special);
return ZERO; match (class(left), class(right)) {
};
match (l, r) {
// No spacing before punctuation; thin spacing after punctuation, unless // No spacing before punctuation; thin spacing after punctuation, unless
// in script size. // in script size.
(_, Punctuation) => ZERO, (_, Punctuation) => ZERO,

View File

@ -443,10 +443,6 @@ pub(super) fn styled_char(style: MathStyle, c: char) -> char {
'∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ' '∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ'
)); ));
if c == '-' {
return '';
}
if let Some(c) = latin_exception(c, variant, bold, italic) { if let Some(c) = latin_exception(c, variant, bold, italic) {
return c; return c;
} }

View File

@ -919,32 +919,26 @@ impl Eval for ast::FuncCall {
// field access and does not evaluate to a module. // field access and does not evaluate to a module.
let (callee, mut args) = if let ast::Expr::FieldAccess(access) = callee { let (callee, mut args) = if let ast::Expr::FieldAccess(access) = callee {
let target = access.target(); let target = access.target();
let method = access.field(); let field = access.field();
let method_span = method.span(); let field_span = field.span();
let method = method.take(); let field = field.take();
let point = || Tracepoint::Call(Some(method.clone())); let point = || Tracepoint::Call(Some(field.clone()));
if methods::is_mutating(&method) { if methods::is_mutating(&field) {
let args = args.eval(vm)?; let args = args.eval(vm)?;
let value = target.access(vm)?; let target = target.access(vm)?;
if !matches!(target, Value::Symbol(_) | Value::Module(_)) {
let value = if let Value::Module(module) = &value { return methods::call_mut(target, &field, args, span)
module.get(&method).cloned().at(method_span)?
} else {
return methods::call_mut(value, &method, args, span)
.trace(vm.world, point, span); .trace(vm.world, point, span);
}; }
(target.field(&field).at(field_span)?, args)
(value, args)
} else { } else {
let target = target.eval(vm)?; let target = target.eval(vm)?;
let args = args.eval(vm)?; let args = args.eval(vm)?;
let value = if let Value::Module(module) = &target { if !matches!(target, Value::Symbol(_) | Value::Module(_)) {
module.get(&method).cloned().at(method_span)? return methods::call(vm, target, &field, args, span)
} else {
return methods::call(vm, target, &method, args, span)
.trace(vm.world, point, span); .trace(vm.world, point, span);
}; }
(value, args) (target.field(&field).at(field_span)?, args)
} }
} else { } else {
(callee.eval(vm)?, args.eval(vm)?) (callee.eval(vm)?, args.eval(vm)?)

View File

@ -426,11 +426,14 @@ node! {
impl Shorthand { impl Shorthand {
/// A list of all shorthands. /// A list of all shorthands.
pub const LIST: &[(&'static str, char)] = &[ pub const LIST: &[(&'static str, char)] = &[
// Text only.
("~", '\u{00A0}'), ("~", '\u{00A0}'),
("--", '\u{2013}'), ("--", '\u{2013}'),
("---", '\u{2014}'), ("---", '\u{2014}'),
("-?", '\u{00AD}'), ("-?", '\u{00AD}'),
("...", '…'), // Math only.
("-", '\u{2212}'),
("'", ''),
("*", ''), ("*", ''),
("!=", '≠'), ("!=", '≠'),
("<<", '≪'), ("<<", '≪'),
@ -450,6 +453,8 @@ impl Shorthand {
("[|", '⟦'), ("[|", '⟦'),
("|]", '⟧'), ("|]", '⟧'),
("||", '‖'), ("||", '‖'),
// Both.
("...", '…'),
]; ];
/// Get the shorthanded character. /// Get the shorthanded character.

View File

@ -380,17 +380,16 @@ impl Lexer<'_> {
'\\' => self.backslash(), '\\' => self.backslash(),
'"' => self.string(), '"' => self.string(),
'*' => SyntaxKind::Shorthand,
'.' if self.s.eat_if("..") => SyntaxKind::Shorthand, '.' if self.s.eat_if("..") => SyntaxKind::Shorthand,
'|' if self.s.eat_if("->") => SyntaxKind::Shorthand, '|' if self.s.eat_if("->") => SyntaxKind::Shorthand,
'|' if self.s.eat_if("=>") => SyntaxKind::Shorthand, '|' if self.s.eat_if("=>") => SyntaxKind::Shorthand,
'!' if self.s.eat_if('=') => SyntaxKind::Shorthand,
'<' if self.s.eat_if("<<") => SyntaxKind::Shorthand, '<' if self.s.eat_if("<<") => SyntaxKind::Shorthand,
'<' if self.s.eat_if('<') => SyntaxKind::Shorthand,
'>' if self.s.eat_if(">>") => SyntaxKind::Shorthand, '>' if self.s.eat_if(">>") => SyntaxKind::Shorthand,
'>' if self.s.eat_if('>') => SyntaxKind::Shorthand,
'<' if self.s.eat_if("=>") => SyntaxKind::Shorthand, '<' if self.s.eat_if("=>") => SyntaxKind::Shorthand,
'<' if self.s.eat_if("->") => SyntaxKind::Shorthand, '<' if self.s.eat_if("->") => SyntaxKind::Shorthand,
'!' if self.s.eat_if('=') => SyntaxKind::Shorthand,
'<' if self.s.eat_if('<') => SyntaxKind::Shorthand,
'>' if self.s.eat_if('>') => SyntaxKind::Shorthand,
'<' if self.s.eat_if('=') => SyntaxKind::Shorthand, '<' if self.s.eat_if('=') => SyntaxKind::Shorthand,
'>' if self.s.eat_if('=') => SyntaxKind::Shorthand, '>' if self.s.eat_if('=') => SyntaxKind::Shorthand,
'<' if self.s.eat_if('-') => SyntaxKind::Shorthand, '<' if self.s.eat_if('-') => SyntaxKind::Shorthand,
@ -400,6 +399,7 @@ impl Lexer<'_> {
'[' if self.s.eat_if('|') => SyntaxKind::Shorthand, '[' if self.s.eat_if('|') => SyntaxKind::Shorthand,
'|' if self.s.eat_if(']') => SyntaxKind::Shorthand, '|' if self.s.eat_if(']') => SyntaxKind::Shorthand,
'|' if self.s.eat_if('|') => SyntaxKind::Shorthand, '|' if self.s.eat_if('|') => SyntaxKind::Shorthand,
'*' | '\'' | '-' => SyntaxKind::Shorthand,
'#' => SyntaxKind::Hashtag, '#' => SyntaxKind::Hashtag,
'_' => SyntaxKind::Underscore, '_' => SyntaxKind::Underscore,

BIN
tests/ref/math/accent.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

BIN
tests/ref/math/attach.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
tests/ref/math/cases.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
tests/ref/math/content.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
tests/ref/math/frac.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
tests/ref/math/op.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
tests/ref/math/root.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
tests/ref/math/spacing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
tests/ref/math/vec.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

22
tests/typ/math/accent.typ Normal file
View File

@ -0,0 +1,22 @@
// Test math accents.
---
// Test function call.
$grave(a), acute(b), hat(f), tilde(§), macron(ä), diaer(a), ä, \
breve(\&), dot(!), circle(a), caron(@), arrow(Z), arrow.l(Z)$
---
// Test `accent` function.
$accent(ö, .), accent(v, <-), accent(ZZ, \u{0303})$
---
// Test accent bounds.
$sqrt(tilde(T)) + hat(f)/hat(g)$
---
// Test wide base.
$arrow("ABC" + d), tilde(sum)$
---
// Test high base.
$ tilde(integral), tilde(integral)_a^b, tilde(integral_a^b) $

View File

@ -1,15 +0,0 @@
// Test math accents.
---
#set page(width: auto)
$ grave(a),
acute(a),
hat(a),
tilde(a),
macron(a),
breve(a),
dot(a),
diaer(a),
caron(a),
arrow(a) $

34
tests/typ/math/attach.typ Normal file
View File

@ -0,0 +1,34 @@
// Test top and bottom attachments.
---
// Test basics.
$f_x + t^b + V_1^2
+ attach(A, top: alpha, bottom: beta)$
---
// Test text vs ident parsing.
$pi_1(Y), a_f(x) != a_zeta(x)$
---
// Test associativity and scaling.
$ 1/(V^2^3^4^5) $
---
// Test high subscript and superscript.
$sqrt(a_(1/2)^zeta)$
$sqrt(a_alpha^(1/2))$
$sqrt(a_(1/2)^(3/4))$
---
// Test frame base.
$ (-1)^n + (1/2 + 3)^(-1/2) $
---
// Test limit.
$ lim_(n->infty \ n "grows") sum_(k=0 \ k in NN)^n k $
---
// Test forcing scripts and limits.
$ limits(A)_1^2 != A_1^2 $
$ scripts(sum)_1^2 != sum_1^2 $
$ limits(integral)_a^b != integral_a^b $

9
tests/typ/math/cases.typ Normal file
View File

@ -0,0 +1,9 @@
// Test case distinction.
---
$ f(x, y) := cases(
1 quad &"if" (x dot y)/2 <= 0,
2 &"if" x divides 2,
3 &"if" x in NN,
4 &"else",
) $

View File

@ -0,0 +1,14 @@
// Test arbitrary content in math.
---
// Test images and font fallback.
#let monkey = move(dy: 0.2em, image("/res/monkey.svg", height: 1em))
$ sum_(i=#emoji.apple)^#emoji.apple.red i + monkey/2 $
---
// Test table above fraction.
$ x := #table(columns: 2)[x][y]/mat(1, 2, 3) $
---
// Test non-formula math directly in content.
#math.attach($a$, top: [b])

View File

@ -0,0 +1,38 @@
// Test delimiter matching and scaling.
---
// Test automatic matching.
$ (a) + {b/2} + |a|/2 + (b) $
$f(x/2) < zeta(c^2 + |a + b/2|)$
---
// Test unmatched.
$[1,2[ = [1,2) != zeta\(x/2\) $
---
// Test manual matching.
$ [|a/b|] != lr(|]a/b|]) != [a/b) $
$ lr(| ]1,2\[ + 1/2|) $
---
// Test fence confusion.
$ |x + |y| + z/a| \
|x + lr(|y|) + z/a| $
---
// Test that symbols aren't matched automatically.
$ bracket.l a/b bracket.r
= lr(bracket.l a/b bracket.r) $
---
// Test half LRs.
$ lr(a/b\]) = a = lr(\{a/b) $
---
// Test manual scaling.
$ lr(]sum_(x=1)^n x], size: #70%)
< lr((1, 2), size: #200%) $
---
// Test predefined delimiter pairings.
$floor(x/2), ceil(x/2), abs(x), norm(x)$

25
tests/typ/math/frac.typ Normal file
View File

@ -0,0 +1,25 @@
// Test fractions.
---
// Test that denominator baseline matches in the common case.
$ x = 1/2 = a/(a h) = a/a = a/(1/2) $
---
// Test parenthesis removal.
$ (|x| + |y|)/2 < [1+2]/3 $
---
// Test associativity.
$ 1/2/3 = (1/2)/3 = 1/(2/3) $
---
// Test large fraction.
$ x = (-b plus.minus sqrt(b^2 - 4a c))/(2a) $
---
// Test binomial.
$ binom(circle, square) $
---
// Error: 8-13 missing argument: lower index
$ binom(x^2) $

View File

@ -1,27 +1,26 @@
// Test vectors, matrices, and cases. // Test matrices.
--- ---
$ v = vec(1, 2+3, 4) $ // Test semicolon syntax.
#set align(center)
$mat() dot
mat(;) dot
mat(1, 2) dot
mat(1, 2;) \
mat(1; 2) dot
mat(1, 2; 3, 4) dot
mat(1 + &2, 1/2; &3, 4)$
--- ---
$ binom(n, 1) = 1/2 n (n-1) $ // Test sparse matrix.
$mat(
1, 2, ..., 10;
2, 2, ..., 10;
dots.v, dots.v, dots.down, dots.v;
10, 10, ..., 10;
)$
--- ---
#set math.vec(delim: "|") // Test alternative delimiter.
$ vec(1, 2) $ #set math.mat(delim: "[")
$ mat(1, 2; 3, 4) $
---
$ f(x, y) := cases(
1 "if" (x dot y)/2 <= 0,
2 "if" x in NN,
3 "if" x "is even",
4 "else",
) $
---
// Error: 22-25 expected "(", "[", "{", "|", or "||"
#set math.vec(delim: "%")
---
// Error: 8-13 missing argument: lower index
$ binom(x^2) $

View File

@ -0,0 +1,35 @@
// Test multiline math.
---
// Test basic alignment.
$ x &= x + y \
&= x + 2z \
&= sum x dot 2z $
---
// Test text before first alignment point.
$ x + 1 &= a^2 + b^2 \
y &= a + b^2 \
z &= alpha dot beta $
---
// Test space between inner alignment points.
$ a + b &= 2 + 3 &= 5 \
b &= c &= 3 $
---
// Test in case distinction.
$ f := cases(
1 + 2 &"iff" &x,
3 &"if" &y,
) $
---
// Test mixing lines with and some without alignment points.
$ "abc" &= c \
&= d + 1 \
= x $
---
// Test multiline subscript.
$ sum_(n in NN \ n <= 5) n = (5(5+1))/2 = 15 $

21
tests/typ/math/op.typ Normal file
View File

@ -0,0 +1,21 @@
// Test text operators.
---
// Test predefined.
$ max_(1<=n<=m) n $
---
// With or without parens.
$ &sin x + log_2 x \
= &sin(x) + log_2(x) $
---
// Test scripts vs limits.
#set text("Latin Modern Roman")
Discuss $lim_(n->infty) 1/n$ now.
$ lim_(n->infty) 1/n = 0 $
---
// Test custom operator.
$ op("myop", limits: #false)_(x:=1) x \
op("myop", limits: #true)_(x:=1) x $

38
tests/typ/math/root.typ Normal file
View File

@ -0,0 +1,38 @@
// Test roots.
---
// Test root with more than one character.
$A = sqrt(x + y) = c$
---
// Test root size with radicals containing attachments.
$ sqrt(a) quad
sqrt(f) quad
sqrt(q) quad
sqrt(a^2) \
sqrt(n_0) quad
sqrt(b^()) quad
sqrt(b^2) quad
sqrt(q_1^2) $
---
// Test precomposed vs constructed roots.
// 3 and 4 are precomposed.
$sqrt(x)$
$root(2, x)$
$root(3, x)$
$root(4, x)$
$root(5, x)$
---
// Test large bodies
$ sqrt([|x|]^2 + [|y|]^2) < [|z|] $
$ v = sqrt((1/2) / (4/5))
= root(3, (1/2/3) / (4/5/6))
= root(4, ((1/2) / (3/4)) / ((1/2) / (3/4))) $
---
// Test large index.
$ root(2, x) quad
root(3/(2/1), x) quad
root(1/11, x) $

View File

@ -1,4 +0,0 @@
// Test math shorthands.
---
$ f : NN <=> RR, n |-> sqrt(n) $

View File

@ -1,24 +0,0 @@
// Test math formulas.
---
The sum of $a$ and $b$ is $a + b$.
---
We will show that:
$ a^2 + b^2 = c^2 $
---
Prove by induction:
$ sum_(k=0)^n k = (n(n+1))/2 $
---
We know that:
$ floor(x/2) <= ceil(x/2) $
---
// Test that blackboard style looks nice.
$ f: NN -> RR $
---
// Error: 1:3 expected dollar sign
$a

View File

@ -0,0 +1,31 @@
// Test spacing in math formulas.
---
// Test spacing cases.
$ä, +, c, (, )$ \
$=), (+), {times}$
$<, |-|, [=$ \
$a=b, a==b$ \
$-a, +a$ \
$a not b$ \
$a+b, a*b$ \
$sum x, sum(x)$ \
$sum prod x$ \
$f(x), zeta(x), "frac"(x)$
---
// Test ignored vs non-ignored spaces.
$f (x), f(x)$ \
$[a|b], [a | b]$ \
$a"is"b, a "is" b$
---
// Test predefined spacings.
$a thin b, a med b, a thick b, a quad b$ \
$a = thin b$ \
$a - b ident c quad (mod 2)$
---
// Test spacing for set comprehension.
#set page(width: auto)
$ { x in RR | x "is natural" and x < 10 } $

View File

@ -1,15 +1,30 @@
#let part = $ a B pi Delta $ // Test text styling in math.
#let kinds = (math.serif, math.sans, math.cal, math.frak, math.mono, math.bb)
#let modifiers = (v => v, math.italic, math.bold, v => math.italic(math.bold(v)))
#let cells = (sym.triangle.nested, [--], [`italic`], [`bold`], [both]) ---
#for kk in kinds { // Test italic defaults.
cells.push(raw(repr(kk).trim("<function ").trim(">"))) $a, A, delta, ϵ, diff, Delta, ϴ$
for mm in modifiers {
cells.push($ mm(kk(part)) $)
}
}
#set page(width: auto) ---
#set align(center) // Test forcing a specific style.
#table(columns: 1 + modifiers.len(), ..cells) $A, italic(A), upright(A), bold(A), bold(upright(A)), \
serif(A), sans(A), cal(A), frak(A), mono(A), bb(A), \
italic(diff), upright(diff), \
bb("hello") + bold(cal("world")), \
mono("SQRT")(x) wreath mono(123 + 456)$
---
// Test a few style exceptions.
$h, bb(N), frak(R), Theta, italic(Theta), sans(Theta), sans(italic(Theta))$
---
// Test font fallback.
$ and 🏳🌈 $
---
// Test text properties.
$text(#red, "time"^2) + sqrt("place")$
---
// Test different font.
#show math.formula: set text(family: "Fira Math")
$ v := vec(1 + 2, 2 - 4, sqrt(3), arrow(x)) + 1 $

View File

@ -1,23 +1,18 @@
#set page(width: auto) // Test math syntax.
#show <table>: it => table(
columns: 2,
inset: 8pt,
..it.text
.split("\n")
.map(line => (raw(line, lang: "typ"), text("Latin Modern Roman", eval(line))))
.flatten()
)
``` ---
Let $x in NN$ be ... // Test Unicode math.
$ (1 + x/2)^2 $ $ _(i=0)^ a b = \u{2211}_(i=0)^NN a compose b $
$ x arrow.l y $
$ sum_(n=1)^mu 1 + (2pi(5 + n)) / k $ ---
$ { x in RR | x "is natural" and x < 10 } $ // Test a few shorthands.
$ sqrt(x^2) = frac(x, 1) $ $ underline(f' : NN -> RR) \
$ "profit" = "income" - "expenses" $ n |-> cases(
$ x < #for i in range(5) [$ #i < $] y $ [|1|] &"if" n >>> 10,
$ 1 + 2 = #{1 + 2} $ 2 * 3 &"if" n != 5,
$ A subset.eq.not B $ 1 - 0 thick &...,
``` ) $
<table>
---
// Error: 1:3 expected dollar sign
$a

View File

@ -0,0 +1,21 @@
// Test under/over things.
---
// Test braces.
$ x = underbrace(
1 + 2 + ... + 5,
underbrace("numbers", x + y)
) $
---
// Test lines and brackets.
$ x = overbracket(
overline(underline(x + y)),
1 + 2 + ... + 5,
) $
---
// Test brackets.
$ underbracket([1, 2/3], "relevant stuff")
arrow.l.r.double.long
overbracket([4/5,6], "irrelevant stuff") $

14
tests/typ/math/vec.typ Normal file
View File

@ -0,0 +1,14 @@
// Test vectors.
---
// Test wide cell.
$ v = vec(1, 2+3, 4) $
---
// Test alternative delimiter.
#set math.vec(delim: "[")
$ vec(1, 2) $
---
// Error: 22-25 expected "(", "[", "{", "|", or "||"
#set math.vec(delim: "%")

View File

@ -108,6 +108,12 @@ function getWebviewContent(pngSrc, refSrc, stdout, stderr) {
} }
.flex { .flex {
display: flex; display: flex;
flex-wrap: wrap;
}
.flex > * {
flex-grow: 1;
flex-shrink: 0;
max-width: 100%;
} }
</style> </style>
</head> </head>