Math tests
@ -23,7 +23,7 @@ macro_rules! percent {
|
||||
}
|
||||
|
||||
/// 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 regions: Regions<'a>,
|
||||
pub font: &'a Font,
|
||||
|
@ -1,5 +1,7 @@
|
||||
use super::*;
|
||||
|
||||
const FRAC_AROUND: Em = Em::new(0.1);
|
||||
|
||||
/// # Fraction
|
||||
/// A mathematical fraction.
|
||||
///
|
||||
@ -130,7 +132,7 @@ fn layout(
|
||||
let denom = ctx.layout_frame(denom)?;
|
||||
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 denom_gap = (shift_down + axis - denom.ascent()).max(denom_min + thickness / 2.0);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) enum MathFragment {
|
||||
pub enum MathFragment {
|
||||
Glyph(GlyphFragment),
|
||||
Variant(VariantFragment),
|
||||
Frame(FrameFragment),
|
||||
@ -118,7 +118,7 @@ impl From<Frame> for MathFragment {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(super) struct GlyphFragment {
|
||||
pub struct GlyphFragment {
|
||||
pub id: GlyphId,
|
||||
pub c: char,
|
||||
pub lang: Lang,
|
||||
|
@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
|
||||
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);
|
||||
|
||||
/// # Vector
|
||||
|
@ -5,29 +5,29 @@ mod ctx;
|
||||
mod accent;
|
||||
mod align;
|
||||
mod attach;
|
||||
mod delimited;
|
||||
mod frac;
|
||||
mod fragment;
|
||||
mod lr;
|
||||
mod matrix;
|
||||
mod op;
|
||||
mod root;
|
||||
mod row;
|
||||
mod spacing;
|
||||
mod stack;
|
||||
mod stretch;
|
||||
mod style;
|
||||
mod symbols;
|
||||
mod underover;
|
||||
|
||||
pub use self::accent::*;
|
||||
pub use self::align::*;
|
||||
pub use self::attach::*;
|
||||
pub use self::delimited::*;
|
||||
pub use self::frac::*;
|
||||
pub use self::lr::*;
|
||||
pub use self::matrix::*;
|
||||
pub use self::op::*;
|
||||
pub use self::root::*;
|
||||
pub use self::stack::*;
|
||||
pub use self::style::*;
|
||||
pub use self::underover::*;
|
||||
|
||||
use ttf_parser::{GlyphId, Rect};
|
||||
use typst::font::Font;
|
||||
@ -230,7 +230,7 @@ impl Layout for FormulaNode {
|
||||
impl Inline for FormulaNode {}
|
||||
|
||||
#[capability]
|
||||
trait LayoutMath {
|
||||
pub trait LayoutMath {
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
use super::*;
|
||||
|
||||
const MIN_INDEX_SHIFT: Em = Em::new(0.35);
|
||||
|
||||
/// # Square Root
|
||||
/// A square root.
|
||||
///
|
||||
@ -126,7 +128,7 @@ fn layout(
|
||||
|
||||
if let Some(index) = &index {
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,10 @@ use crate::layout::AlignNode;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub const TIGHT_LEADING: Em = Em::new(0.25);
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub(super) struct MathRow(pub Vec<MathFragment>);
|
||||
pub struct MathRow(pub Vec<MathFragment>);
|
||||
|
||||
impl MathRow {
|
||||
pub fn new() -> Self {
|
||||
@ -85,8 +87,12 @@ impl MathRow {
|
||||
) -> Frame {
|
||||
if self.0.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) {
|
||||
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
|
||||
.split(|frag| matches!(frag, MathFragment::Linebreak))
|
||||
.map(|slice| Self(slice.to_vec()))
|
||||
|
@ -24,11 +24,8 @@ pub(super) fn spacing(
|
||||
) -> Em {
|
||||
use MathClass::*;
|
||||
let script = style.size <= MathSize::Script;
|
||||
let (Some(l), Some(r)) = (left.class(), right.class()) else {
|
||||
return ZERO;
|
||||
};
|
||||
|
||||
match (l, r) {
|
||||
let class = |frag: &MathFragment| frag.class().unwrap_or(Special);
|
||||
match (class(left), class(right)) {
|
||||
// No spacing before punctuation; thin spacing after punctuation, unless
|
||||
// in script size.
|
||||
(_, Punctuation) => ZERO,
|
||||
|
@ -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) {
|
||||
return c;
|
||||
}
|
||||
|
@ -919,32 +919,26 @@ impl Eval for ast::FuncCall {
|
||||
// field access and does not evaluate to a module.
|
||||
let (callee, mut args) = if let ast::Expr::FieldAccess(access) = callee {
|
||||
let target = access.target();
|
||||
let method = access.field();
|
||||
let method_span = method.span();
|
||||
let method = method.take();
|
||||
let point = || Tracepoint::Call(Some(method.clone()));
|
||||
if methods::is_mutating(&method) {
|
||||
let field = access.field();
|
||||
let field_span = field.span();
|
||||
let field = field.take();
|
||||
let point = || Tracepoint::Call(Some(field.clone()));
|
||||
if methods::is_mutating(&field) {
|
||||
let args = args.eval(vm)?;
|
||||
let value = target.access(vm)?;
|
||||
|
||||
let value = if let Value::Module(module) = &value {
|
||||
module.get(&method).cloned().at(method_span)?
|
||||
} else {
|
||||
return methods::call_mut(value, &method, args, span)
|
||||
let target = target.access(vm)?;
|
||||
if !matches!(target, Value::Symbol(_) | Value::Module(_)) {
|
||||
return methods::call_mut(target, &field, args, span)
|
||||
.trace(vm.world, point, span);
|
||||
};
|
||||
|
||||
(value, args)
|
||||
}
|
||||
(target.field(&field).at(field_span)?, args)
|
||||
} else {
|
||||
let target = target.eval(vm)?;
|
||||
let args = args.eval(vm)?;
|
||||
let value = if let Value::Module(module) = &target {
|
||||
module.get(&method).cloned().at(method_span)?
|
||||
} else {
|
||||
return methods::call(vm, target, &method, args, span)
|
||||
if !matches!(target, Value::Symbol(_) | Value::Module(_)) {
|
||||
return methods::call(vm, target, &field, args, span)
|
||||
.trace(vm.world, point, span);
|
||||
};
|
||||
(value, args)
|
||||
}
|
||||
(target.field(&field).at(field_span)?, args)
|
||||
}
|
||||
} else {
|
||||
(callee.eval(vm)?, args.eval(vm)?)
|
||||
|
@ -426,11 +426,14 @@ node! {
|
||||
impl Shorthand {
|
||||
/// A list of all shorthands.
|
||||
pub const LIST: &[(&'static str, char)] = &[
|
||||
// Text only.
|
||||
("~", '\u{00A0}'),
|
||||
("--", '\u{2013}'),
|
||||
("---", '\u{2014}'),
|
||||
("-?", '\u{00AD}'),
|
||||
("...", '…'),
|
||||
// Math only.
|
||||
("-", '\u{2212}'),
|
||||
("'", '′'),
|
||||
("*", '∗'),
|
||||
("!=", '≠'),
|
||||
("<<", '≪'),
|
||||
@ -450,6 +453,8 @@ impl Shorthand {
|
||||
("[|", '⟦'),
|
||||
("|]", '⟧'),
|
||||
("||", '‖'),
|
||||
// Both.
|
||||
("...", '…'),
|
||||
];
|
||||
|
||||
/// Get the shorthanded character.
|
||||
|
@ -380,17 +380,16 @@ impl Lexer<'_> {
|
||||
'\\' => self.backslash(),
|
||||
'"' => 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,
|
||||
@ -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,
|
||||
'*' | '\'' | '-' => SyntaxKind::Shorthand,
|
||||
|
||||
'#' => SyntaxKind::Hashtag,
|
||||
'_' => SyntaxKind::Underscore,
|
||||
|
BIN
tests/ref/math/accent.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 1.5 KiB |
BIN
tests/ref/math/attach.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
tests/ref/math/cases.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
tests/ref/math/content.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
tests/ref/math/delimited.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
tests/ref/math/frac.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 4.8 KiB |
BIN
tests/ref/math/multiline.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
tests/ref/math/op.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
tests/ref/math/root.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
tests/ref/math/spacing.png
Normal file
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 4.5 KiB |
BIN
tests/ref/math/underover.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
tests/ref/math/vec.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
22
tests/typ/math/accent.typ
Normal 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) $
|
@ -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
@ -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
@ -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",
|
||||
) $
|
14
tests/typ/math/content.typ
Normal 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])
|
38
tests/typ/math/delimited.typ
Normal 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
@ -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) $
|
@ -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: "|")
|
||||
$ vec(1, 2) $
|
||||
|
||||
---
|
||||
$ 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) $
|
||||
// Test alternative delimiter.
|
||||
#set math.mat(delim: "[")
|
||||
$ mat(1, 2; 3, 4) $
|
||||
|
35
tests/typ/math/multiline.typ
Normal 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
@ -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
@ -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) $
|
@ -1,4 +0,0 @@
|
||||
// Test math shorthands.
|
||||
|
||||
---
|
||||
$ f : NN <=> RR, n |-> sqrt(n) $
|
@ -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
|
31
tests/typ/math/spacing.typ
Normal 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 } $
|
@ -1,15 +1,30 @@
|
||||
#let part = $ a B pi Delta $
|
||||
#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)))
|
||||
// Test text styling in math.
|
||||
|
||||
#let cells = (sym.triangle.nested, [--], [`italic`], [`bold`], [both])
|
||||
#for kk in kinds {
|
||||
cells.push(raw(repr(kk).trim("<function ").trim(">")))
|
||||
for mm in modifiers {
|
||||
cells.push($ mm(kk(part)) $)
|
||||
}
|
||||
}
|
||||
---
|
||||
// Test italic defaults.
|
||||
$a, A, delta, ϵ, diff, Delta, ϴ$
|
||||
|
||||
#set page(width: auto)
|
||||
#set align(center)
|
||||
#table(columns: 1 + modifiers.len(), ..cells)
|
||||
---
|
||||
// Test forcing a specific style.
|
||||
$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 $
|
||||
|
@ -1,23 +1,18 @@
|
||||
#set page(width: auto)
|
||||
#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()
|
||||
)
|
||||
// Test math syntax.
|
||||
|
||||
```
|
||||
Let $x in NN$ be ...
|
||||
$ (1 + x/2)^2 $
|
||||
$ x arrow.l y $
|
||||
$ sum_(n=1)^mu 1 + (2pi(5 + n)) / k $
|
||||
$ { x in RR | x "is natural" and x < 10 } $
|
||||
$ sqrt(x^2) = frac(x, 1) $
|
||||
$ "profit" = "income" - "expenses" $
|
||||
$ x < #for i in range(5) [$ #i < $] y $
|
||||
$ 1 + 2 = #{1 + 2} $
|
||||
$ A subset.eq.not B $
|
||||
```
|
||||
<table>
|
||||
---
|
||||
// Test Unicode math.
|
||||
$ ∑_(i=0)^ℕ a ∘ b = \u{2211}_(i=0)^NN a compose b $
|
||||
|
||||
---
|
||||
// Test a few shorthands.
|
||||
$ underline(f' : NN -> RR) \
|
||||
n |-> cases(
|
||||
[|1|] &"if" n >>> 10,
|
||||
2 * 3 &"if" n != 5,
|
||||
1 - 0 thick &...,
|
||||
) $
|
||||
|
||||
---
|
||||
// Error: 1:3 expected dollar sign
|
||||
$a
|
||||
|
21
tests/typ/math/underover.typ
Normal 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
@ -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: "%")
|
@ -108,6 +108,12 @@ function getWebviewContent(pngSrc, refSrc, stdout, stderr) {
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.flex > * {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|