Extend math library

This commit is contained in:
Laurenz 2022-12-07 12:33:48 +01:00
parent e1c0cda6c8
commit 11c7ceb29e
16 changed files with 451 additions and 68 deletions

2
Cargo.lock generated
View File

@ -1027,7 +1027,7 @@ dependencies = [
[[package]]
name = "symmie"
version = "0.1.0"
source = "git+https://github.com/typst/symmie#8504bf7ec0d8996d160832c2724ba024ab6e988a"
source = "git+https://github.com/typst/symmie#6280fb20455cb63e6886ba5bb35b95a4b376da68"
[[package]]
name = "syn"

View File

@ -51,10 +51,22 @@ fn scope() -> Scope {
// Math.
std.def_node::<math::MathNode>("math");
std.def_node::<math::AtomNode>("atom");
std.def_node::<math::AccNode>("acc");
std.def_node::<math::FracNode>("frac");
std.def_node::<math::BinomNode>("binom");
std.def_node::<math::SqrtNode>("sqrt");
std.def_node::<math::FloorNode>("floor");
std.def_node::<math::CeilNode>("ceil");
std.def_node::<math::VecNode>("vec");
std.def_node::<math::CasesNode>("cases");
std.def_node::<math::SerifNode>("serif");
std.def_node::<math::SansNode>("sans");
std.def_node::<math::BoldNode>("bold");
std.def_node::<math::ItalNode>("ital");
std.def_node::<math::CalNode>("cal");
std.def_node::<math::FrakNode>("frak");
std.def_node::<math::MonoNode>("mono");
std.def_node::<math::BbNode>("bb");
// Layout.
std.def_node::<layout::PageNode>("page");

View File

@ -0,0 +1,84 @@
use super::*;
/// A column vector in a mathematical formula.
#[derive(Debug, Hash)]
pub struct VecNode(Vec<Content>);
#[node(Texify)]
impl VecNode {
/// The kind of delimiter.
pub const DELIM: Delimiter = Delimiter::Paren;
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.all()?).pack())
}
}
impl Texify for VecNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
let kind = match t.styles.get(Self::DELIM) {
Delimiter::Paren => "pmatrix",
Delimiter::Bracket => "bmatrix",
Delimiter::Brace => "Bmatrix",
Delimiter::Bar => "vmatrix",
};
t.push_str("\\begin{");
t.push_str(kind);
t.push_str("}");
for component in &self.0 {
component.texify(t)?;
t.push_str("\\\\");
}
t.push_str("\\end{");
t.push_str(kind);
t.push_str("}");
Ok(())
}
}
/// A vector / matrix delimiter.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Delimiter {
Paren,
Bracket,
Brace,
Bar,
}
castable! {
Delimiter,
Expected: "type of bracket or bar",
Value::Str(s) => match s.as_str() {
"(" => Self::Paren,
"[" => Self::Bracket,
"{" => Self::Brace,
"|" => Self::Bar,
_ => Err("expected \"(\", \"[\", \"{\", or \"|\"")?,
},
}
/// A case distinction in a mathematical formula.
#[derive(Debug, Hash)]
pub struct CasesNode(Vec<Content>);
#[node(Texify)]
impl CasesNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.all()?).pack())
}
}
impl Texify for CasesNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\begin{cases}");
for component in &self.0 {
component.texify(t)?;
t.push_str("\\\\");
}
t.push_str("\\end{cases}");
Ok(())
}
}

View File

@ -1,7 +1,12 @@
//! Mathematical formulas.
mod matrix;
mod style;
mod tex;
pub use self::matrix::*;
pub use self::style::*;
use typst::model::{Guard, SequenceNode};
use unicode_segmentation::UnicodeSegmentation;
@ -272,6 +277,79 @@ impl Texify for AtomNode {
}
}
/// An accented node.
#[derive(Debug, Hash)]
pub struct AccNode {
/// The accent base.
pub base: Content,
/// The Unicode accent character.
pub accent: char,
}
#[node(Texify)]
impl AccNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let base = args.expect("base")?;
let Spanned { v, span } = args.expect::<Spanned<Content>>("accent")?;
let accent = match extract(&v) {
Some(Ok(c)) => c,
Some(Err(msg)) => bail!(span, "{}", msg),
None => bail!(span, "not an accent"),
};
Ok(Self { base, accent }.pack())
}
}
#[rustfmt::skip]
fn extract(content: &Content) -> Option<Result<char, &'static str>> {
let MathNode { children, .. } = content.to::<MathNode>()?;
let [child] = children.as_slice() else { return None };
let c = if let Some(atom) = child.to::<AtomNode>() {
let mut chars = atom.0.chars();
chars.next().filter(|_| chars.next().is_none())?
} else if let Some(symbol) = child.to::<SymbolNode>() {
match symmie::get(&symbol.0) {
Some(c) => c,
None => return Some(Err("unknown symbol")),
}
} else {
return None;
};
Some(Ok(match c {
'`' | '\u{300}' => '\u{300}', // Grave
'´' | '\u{301}' => '\u{301}', // Acute
'^' | '\u{302}' => '\u{302}', // Circumflex
'~' | '\u{223C}' | '\u{303}' => '\u{303}', // Tilde
'¯' | '\u{304}' => '\u{304}', // Macron
'‾' | '\u{305}' => '\u{305}', // Overline
'˘' | '\u{306}' => '\u{306}', // Breve
'.' | '\u{22C5}' | '\u{307}' => '\u{307}', // Dot
'¨' | '\u{308}' => '\u{308}', // Diaeresis
'ˇ' | '\u{30C}' => '\u{30C}', // Caron
'→' | '\u{20D7}' => '\u{20D7}', // Arrow
_ => return None,
}))
}
impl Texify for AccNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
if let Some(sym) = unicode_math::SYMBOLS.iter().find(|sym| {
sym.codepoint == self.accent
&& sym.atom_type == unicode_math::AtomType::Accent
}) {
t.push_str("\\");
t.push_str(sym.name);
t.push_str("{");
self.base.texify(t)?;
t.push_str("}");
} else {
self.base.texify(t)?;
}
Ok(())
}
}
/// A fraction in a mathematical formula.
#[derive(Debug, Hash)]
pub struct FracNode {
@ -301,6 +379,35 @@ impl Texify for FracNode {
}
}
/// A binomial in a mathematical formula.
#[derive(Debug, Hash)]
pub struct BinomNode {
/// The upper index.
pub upper: Content,
/// The lower index.
pub lower: Content,
}
#[node(Texify)]
impl BinomNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let upper = args.expect("upper index")?;
let lower = args.expect("lower index")?;
Ok(Self { upper, lower }.pack())
}
}
impl Texify for BinomNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\binom{");
self.upper.texify(t)?;
t.push_str("}{");
self.lower.texify(t)?;
t.push_str("}");
Ok(())
}
}
/// A sub- and/or superscript in a mathematical formula.
#[derive(Debug, Hash)]
pub struct ScriptNode {
@ -348,7 +455,7 @@ impl Texify for AlignNode {
}
}
/// A square root.
/// A square root in a mathematical formula.
#[derive(Debug, Hash)]
pub struct SqrtNode(Content);
@ -362,91 +469,48 @@ impl SqrtNode {
impl Texify for SqrtNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\sqrt{");
self.0.texify_unparen(t)?;
self.0.texify(t)?;
t.push_str("}");
Ok(())
}
}
/// A column vector.
/// A floored expression in a mathematical formula.
#[derive(Debug, Hash)]
pub struct VecNode(Vec<Content>);
pub struct FloorNode(Content);
#[node(Texify)]
impl VecNode {
/// The kind of delimiter.
pub const DELIM: Delimiter = Delimiter::Paren;
impl FloorNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.all()?).pack())
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for VecNode {
impl Texify for FloorNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
let kind = match t.styles.get(Self::DELIM) {
Delimiter::Paren => "pmatrix",
Delimiter::Bracket => "bmatrix",
Delimiter::Brace => "Bmatrix",
Delimiter::Bar => "vmatrix",
};
t.push_str("\\begin{");
t.push_str(kind);
t.push_str("}");
for component in &self.0 {
component.texify_unparen(t)?;
t.push_str("\\\\");
}
t.push_str("\\end{");
t.push_str(kind);
t.push_str("}");
t.push_str("\\left\\lfloor ");
self.0.texify(t)?;
t.push_str("\\right\\rfloor ");
Ok(())
}
}
/// A vector / matrix delimiter.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Delimiter {
Paren,
Bracket,
Brace,
Bar,
}
castable! {
Delimiter,
Expected: "type of bracket or bar",
Value::Str(s) => match s.as_str() {
"(" => Self::Paren,
"[" => Self::Bracket,
"{" => Self::Brace,
"|" => Self::Bar,
_ => Err("expected \"(\", \"[\", \"{\", or \"|\"")?,
},
}
/// A case distinction.
/// A ceiled expression in a mathematical formula.
#[derive(Debug, Hash)]
pub struct CasesNode(Vec<Content>);
pub struct CeilNode(Content);
#[node(Texify)]
impl CasesNode {
impl CeilNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.all()?).pack())
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for CasesNode {
impl Texify for CeilNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\begin{cases}");
for component in &self.0 {
component.texify_unparen(t)?;
t.push_str("\\\\");
}
t.push_str("\\end{cases}");
t.push_str("\\left\\lceil ");
self.0.texify(t)?;
t.push_str("\\right\\rceil ");
Ok(())
}
}

161
library/src/math/style.rs Normal file
View File

@ -0,0 +1,161 @@
use super::*;
/// Serif (roman) font style.
#[derive(Debug, Hash)]
pub struct SerifNode(Content);
#[node(Texify)]
impl SerifNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for SerifNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\mathrm{");
self.0.texify_unparen(t)?;
t.push_str("}");
Ok(())
}
}
/// Sans-serif font style.
#[derive(Debug, Hash)]
pub struct SansNode(Content);
#[node(Texify)]
impl SansNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for SansNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\mathsf{");
self.0.texify_unparen(t)?;
t.push_str("}");
Ok(())
}
}
/// Bold font style.
#[derive(Debug, Hash)]
pub struct BoldNode(Content);
#[node(Texify)]
impl BoldNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for BoldNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\mathbf{");
self.0.texify_unparen(t)?;
t.push_str("}");
Ok(())
}
}
/// Italic font style.
#[derive(Debug, Hash)]
pub struct ItalNode(Content);
#[node(Texify)]
impl ItalNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for ItalNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\mathit{");
self.0.texify_unparen(t)?;
t.push_str("}");
Ok(())
}
}
/// Calligraphic font style.
#[derive(Debug, Hash)]
pub struct CalNode(Content);
#[node(Texify)]
impl CalNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for CalNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\mathcal{");
self.0.texify_unparen(t)?;
t.push_str("}");
Ok(())
}
}
/// Fraktur font style.
#[derive(Debug, Hash)]
pub struct FrakNode(Content);
#[node(Texify)]
impl FrakNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for FrakNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\mathfrak{");
self.0.texify_unparen(t)?;
t.push_str("}");
Ok(())
}
}
/// Monospace font style.
#[derive(Debug, Hash)]
pub struct MonoNode(Content);
#[node(Texify)]
impl MonoNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for MonoNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\mathtt{");
self.0.texify_unparen(t)?;
t.push_str("}");
Ok(())
}
}
/// Blackboard bold (double-struck) font style.
#[derive(Debug, Hash)]
pub struct BbNode(Content);
#[node(Texify)]
impl BbNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for BbNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\mathbb{");
self.0.texify_unparen(t)?;
t.push_str("}");
Ok(())
}
}

View File

@ -125,6 +125,10 @@ impl Backend for FrameBackend {
}
fn rule(&mut self, pos: Cursor, width: f64, height: f64) {
if height == 0.0 {
return;
}
self.frame.push(
self.transform(pos) + Point::with_y(Abs::pt(height) / 2.0),
Element::Shape(Shape {

View File

@ -434,7 +434,7 @@ impl Eval for ast::MathNode {
Self::Expr(v) => {
if let ast::Expr::Ident(ident) = v {
if self.as_untyped().len() == ident.len()
&& !vm.scopes.get(ident).is_ok()
&& matches!(vm.scopes.get(ident), Ok(Value::Func(_)) | Err(_))
{
let node = (vm.items.symbol)(ident.get().clone() + ":op".into());
return Ok(node.spanned(self.span()));

View File

@ -7,6 +7,10 @@ pub fn applicable(target: &Content, styles: StyleChain) -> bool {
return true;
}
if target.has::<dyn Show>() && target.is_pristine() {
return true;
}
// Find out how many recipes there are.
let mut n = styles.recipes().count();

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,28 @@
// Test math accents.
---
#set page(width: auto)
$ acc(a,`),
acc(a,´),
acc(a,\^),
acc(a,~),
acc(a,¯),
acc(a,‾),
acc(a,˘),
acc(a,.),
acc(a,¨),
acc(a,ˇ),
acc(a,->) $
$ acc(a, grave),
acc(a, acute),
acc(a, circum),
acc(a, tilde),
acc(a, macron),
acc(a, overline),
acc(a, breve),
acc(a, dot),
acc(a, dia),
acc(a, caron),
acc(a, arrow) $

View File

@ -4,12 +4,11 @@
$ v = vec(1, 2+3, 4) $
---
#set vec(delim: "|")
$ vec(1, 2) $
$ binom(n, 1) = 1/2 n (n-1) $
---
// Error: 17-20 expected "(", "[", "{", or "|"
#set vec(delim: "%")
#set vec(delim: "|")
$ vec(1, 2) $
---
$ f(x, y) := cases(
@ -18,3 +17,11 @@ $ f(x, y) := cases(
3 "if" x "is even",
4 "else",
) $
---
// Error: 17-20 expected "(", "[", "{", or "|"
#set vec(delim: "%")
---
// Error: 9-12 missing argument: lower index
$ binom(x^2) $

View File

@ -11,9 +11,13 @@ $ 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 arrow RR $
$ f: NN -> RR $
---
// Error: 1:3 expected dollar sign

15
tests/typ/math/style.typ Normal file
View File

@ -0,0 +1,15 @@
#let part = $ a b A B $
#let kinds = (serif, sans, cal, frak, mono, bb)
#let modifiers = (v => v, ital, bold, v => ital(bold(v)))
#let cells = ([:triangle:nested:], [--], [`ital`], [`bold`], [both])
#for k in kinds {
cells.push(raw(repr(k).trim("<function ").trim(">")))
for m in modifiers {
cells.push($ #m(#k(part)) $)
}
}
#set page(width: auto)
#set par(align: center)
#table(columns: 1 + modifiers.len(), ..cells)