summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Biedert <github@ericbiedert.de>2023-07-10 11:00:12 +0200
committerGitHub <noreply@github.com>2023-07-10 11:00:12 +0200
commitbe0f8fe6d70bc5919e4351b73a2835e89001b000 (patch)
treeeb7c9ad54fcc1a53171f1e0b9ea67663a64fe596
parent7404f85a02450c291ee408700e7874cb85a06e42 (diff)
Customizable math classes (#1681)
-rw-r--r--crates/typst-library/src/math/class.rs36
-rw-r--r--crates/typst-library/src/math/ctx.rs4
-rw-r--r--crates/typst-library/src/math/fragment.rs35
-rw-r--r--crates/typst-library/src/math/mod.rs4
-rw-r--r--crates/typst-library/src/math/style.rs7
-rw-r--r--crates/typst/src/eval/cast.rs32
-rw-r--r--tests/ref/math/class.pngbin0 -> 5308 bytes
-rw-r--r--tests/typ/math/class.typ33
8 files changed, 144 insertions, 7 deletions
diff --git a/crates/typst-library/src/math/class.rs b/crates/typst-library/src/math/class.rs
new file mode 100644
index 00000000..0d6a370b
--- /dev/null
+++ b/crates/typst-library/src/math/class.rs
@@ -0,0 +1,36 @@
+use super::*;
+
+/// Forced use of a certain math class.
+///
+/// This is useful to treat certain symbols as if they were of a different
+/// class, e.g. to make text behave like a binary operator.
+///
+/// # Example
+/// ```example
+/// $x class("relation", "<=") 5$
+/// ```
+///
+/// Display: Class
+/// Category: math
+#[element(LayoutMath)]
+pub struct ClassElem {
+ /// The class to apply to the content.
+ #[required]
+ pub class: MathClass,
+
+ /// The content to which the class is applied.
+ #[required]
+ pub body: Content,
+}
+
+impl LayoutMath for ClassElem {
+ fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ ctx.style(ctx.style.with_class(self.class()));
+ let mut fragment = ctx.layout_fragment(&self.body())?;
+ ctx.unstyle();
+
+ fragment.set_class(self.class());
+ ctx.push(fragment);
+ Ok(())
+ }
+}
diff --git a/crates/typst-library/src/math/ctx.rs b/crates/typst-library/src/math/ctx.rs
index a1684ffa..999a5ccb 100644
--- a/crates/typst-library/src/math/ctx.rs
+++ b/crates/typst-library/src/math/ctx.rs
@@ -101,6 +101,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
style: MathStyle {
variant: MathVariant::Serif,
size: if block { MathSize::Display } else { MathSize::Text },
+ class: Smart::Auto,
cramped: false,
bold: variant.weight >= FontWeight::BOLD,
italic: match variant.style {
@@ -167,7 +168,8 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
// A single letter that is available in the math font.
match self.style.size {
MathSize::Display => {
- if glyph.class == Some(MathClass::Large) {
+ let class = self.style.class.as_custom().or(glyph.class);
+ if class == Some(MathClass::Large) {
let height = scaled!(self, display_operator_min_height);
glyph.stretch_vertical(self, height, Abs::zero()).into()
} else {
diff --git a/crates/typst-library/src/math/fragment.rs b/crates/typst-library/src/math/fragment.rs
index a1702aef..1a90eaad 100644
--- a/crates/typst-library/src/math/fragment.rs
+++ b/crates/typst-library/src/math/fragment.rs
@@ -61,12 +61,12 @@ impl MathFragment {
}
pub fn class(&self) -> Option<MathClass> {
- match self {
+ self.style().and_then(|style| style.class.as_custom()).or(match self {
Self::Glyph(glyph) => glyph.class,
Self::Variant(variant) => variant.class,
Self::Frame(fragment) => Some(fragment.class),
_ => None,
- }
+ })
}
pub fn style(&self) -> Option<MathStyle> {
@@ -88,10 +88,27 @@ impl MathFragment {
}
pub fn set_class(&mut self, class: MathClass) {
+ macro_rules! set_style_class {
+ ($fragment:ident) => {
+ if $fragment.style.class.is_custom() {
+ $fragment.style.class = Smart::Custom(class);
+ }
+ };
+ }
+
match self {
- Self::Glyph(glyph) => glyph.class = Some(class),
- Self::Variant(variant) => variant.class = Some(class),
- Self::Frame(fragment) => fragment.class = class,
+ Self::Glyph(glyph) => {
+ glyph.class = Some(class);
+ set_style_class!(glyph);
+ }
+ Self::Variant(variant) => {
+ variant.class = Some(class);
+ set_style_class!(variant);
+ }
+ Self::Frame(fragment) => {
+ fragment.class = class;
+ set_style_class!(fragment);
+ }
_ => {}
}
}
@@ -107,7 +124,13 @@ impl MathFragment {
pub fn is_spaced(&self) -> bool {
match self {
- MathFragment::Frame(frame) => frame.spaced,
+ MathFragment::Frame(frame) => {
+ match self.style().and_then(|style| style.class.as_custom()) {
+ Some(MathClass::Fence) => true,
+ Some(_) => false,
+ None => frame.spaced,
+ }
+ }
_ => self.class() == Some(MathClass::Fence),
}
}
diff --git a/crates/typst-library/src/math/mod.rs b/crates/typst-library/src/math/mod.rs
index f4199fd8..b5410a03 100644
--- a/crates/typst-library/src/math/mod.rs
+++ b/crates/typst-library/src/math/mod.rs
@@ -6,6 +6,7 @@ mod accent;
mod align;
mod attach;
mod cancel;
+mod class;
mod delimited;
mod frac;
mod fragment;
@@ -22,6 +23,7 @@ pub use self::accent::*;
pub use self::align::*;
pub use self::attach::*;
pub use self::cancel::*;
+pub use self::class::*;
pub use self::delimited::*;
pub use self::frac::*;
pub use self::matrix::*;
@@ -106,6 +108,8 @@ pub fn module() -> Module {
math.define("script", script_func());
math.define("sscript", sscript_func());
+ math.define("class", ClassElem::func());
+
// Text operators.
math.define("op", OpElem::func());
op::define(&mut math);
diff --git a/crates/typst-library/src/math/style.rs b/crates/typst-library/src/math/style.rs
index 235770db..35a4cfdb 100644
--- a/crates/typst-library/src/math/style.rs
+++ b/crates/typst-library/src/math/style.rs
@@ -320,6 +320,8 @@ pub struct MathStyle {
pub variant: MathVariant,
/// The size of the glyphs.
pub size: MathSize,
+ /// The class of the element.
+ pub class: Smart<MathClass>,
/// Affects the height of exponents.
pub cramped: bool,
/// Whether to use bold glyphs.
@@ -339,6 +341,11 @@ impl MathStyle {
Self { size, ..self }
}
+ // This style, with the given `class`.
+ pub fn with_class(self, class: MathClass) -> Self {
+ Self { class: Smart::Custom(class), ..self }
+ }
+
/// This style, with `cramped` set to the given value.
pub fn with_cramped(self, cramped: bool) -> Self {
Self { cramped, ..self }
diff --git a/crates/typst/src/eval/cast.rs b/crates/typst/src/eval/cast.rs
index 917972ed..6a49d128 100644
--- a/crates/typst/src/eval/cast.rs
+++ b/crates/typst/src/eval/cast.rs
@@ -1,4 +1,5 @@
pub use typst_macros::{cast, Cast};
+use unicode_math_class::MathClass;
use std::fmt::Write;
use std::ops::Add;
@@ -314,3 +315,34 @@ impl FromValue for Never {
Err(Self::error(&value))
}
}
+
+cast! {
+ MathClass,
+ self => IntoValue::into_value(match self {
+ MathClass::Normal => "normal",
+ MathClass::Alphabetic => "alphabetic",
+ MathClass::Binary => "binary",
+ MathClass::Closing => "closing",
+ MathClass::Diacritic => "diacritic",
+ MathClass::Fence => "fence",
+ MathClass::GlyphPart => "glyph-part",
+ MathClass::Large => "large",
+ MathClass::Opening => "opening",
+ MathClass::Punctuation => "punctuation",
+ MathClass::Relation => "relation",
+ MathClass::Space => "space",
+ MathClass::Unary => "unary",
+ MathClass::Vary => "vary",
+ MathClass::Special => "special",
+ }),
+ "normal" => MathClass::Normal,
+ "binary" => MathClass::Binary,
+ "closing" => MathClass::Closing,
+ "fence" => MathClass::Fence,
+ "large" => MathClass::Large,
+ "opening" => MathClass::Opening,
+ "punctuation" => MathClass::Punctuation,
+ "relation" => MathClass::Relation,
+ "unary" => MathClass::Unary,
+ "vary" => MathClass::Vary,
+}
diff --git a/tests/ref/math/class.png b/tests/ref/math/class.png
new file mode 100644
index 00000000..dffb6f7a
--- /dev/null
+++ b/tests/ref/math/class.png
Binary files differ
diff --git a/tests/typ/math/class.typ b/tests/typ/math/class.typ
new file mode 100644
index 00000000..188e7d90
--- /dev/null
+++ b/tests/typ/math/class.typ
@@ -0,0 +1,33 @@
+// Test math classes.
+
+---
+// Test characters.
+$ a class("normal", +) b \
+ a class("binary", .) b \
+ lr(class("opening", \/) a/b class("closing", \\)) \
+ { x class("fence", \;) x > 0} \
+ a class("large", \/) b \
+ a class("punctuation", :) b \
+ a class("relation", ~) b \
+ a + class("unary", times) b \
+ class("vary", :) a class("vary", :) b $
+
+---
+// Test custom content.
+#let dotsq = square(
+ size: 0.7em,
+ stroke: 0.5pt,
+ align(center+horizon, circle(radius: 0.15em, fill: black))
+)
+
+$ a dotsq b \
+ a class("normal", dotsq) b \
+ a class("vary", dotsq) b \
+ a + class("vary", dotsq) b \
+ a class("punctuation", dotsq) b $
+
+---
+// Test nested.
+#let normal = math.class.with("normal")
+#let pluseq = $class("binary", normal(+) normal(=))$
+$ a pluseq 5 $