Customizable math classes (#1681)

This commit is contained in:
Eric Biedert 2023-07-10 11:00:12 +02:00 committed by GitHub
parent 7404f85a02
commit be0f8fe6d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 144 additions and 7 deletions

View File

@ -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(())
}
}

View File

@ -101,6 +101,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
style: MathStyle { style: MathStyle {
variant: MathVariant::Serif, variant: MathVariant::Serif,
size: if block { MathSize::Display } else { MathSize::Text }, size: if block { MathSize::Display } else { MathSize::Text },
class: Smart::Auto,
cramped: false, cramped: false,
bold: variant.weight >= FontWeight::BOLD, bold: variant.weight >= FontWeight::BOLD,
italic: match variant.style { 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. // A single letter that is available in the math font.
match self.style.size { match self.style.size {
MathSize::Display => { 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); let height = scaled!(self, display_operator_min_height);
glyph.stretch_vertical(self, height, Abs::zero()).into() glyph.stretch_vertical(self, height, Abs::zero()).into()
} else { } else {

View File

@ -61,12 +61,12 @@ impl MathFragment {
} }
pub fn class(&self) -> Option<MathClass> { 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::Glyph(glyph) => glyph.class,
Self::Variant(variant) => variant.class, Self::Variant(variant) => variant.class,
Self::Frame(fragment) => Some(fragment.class), Self::Frame(fragment) => Some(fragment.class),
_ => None, _ => None,
} })
} }
pub fn style(&self) -> Option<MathStyle> { pub fn style(&self) -> Option<MathStyle> {
@ -88,10 +88,27 @@ impl MathFragment {
} }
pub fn set_class(&mut self, class: MathClass) { 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 { match self {
Self::Glyph(glyph) => glyph.class = Some(class), Self::Glyph(glyph) => {
Self::Variant(variant) => variant.class = Some(class), glyph.class = Some(class);
Self::Frame(fragment) => fragment.class = 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 { pub fn is_spaced(&self) -> bool {
match self { 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), _ => self.class() == Some(MathClass::Fence),
} }
} }

View File

@ -6,6 +6,7 @@ mod accent;
mod align; mod align;
mod attach; mod attach;
mod cancel; mod cancel;
mod class;
mod delimited; mod delimited;
mod frac; mod frac;
mod fragment; mod fragment;
@ -22,6 +23,7 @@ pub use self::accent::*;
pub use self::align::*; pub use self::align::*;
pub use self::attach::*; pub use self::attach::*;
pub use self::cancel::*; pub use self::cancel::*;
pub use self::class::*;
pub use self::delimited::*; pub use self::delimited::*;
pub use self::frac::*; pub use self::frac::*;
pub use self::matrix::*; pub use self::matrix::*;
@ -106,6 +108,8 @@ pub fn module() -> Module {
math.define("script", script_func()); math.define("script", script_func());
math.define("sscript", sscript_func()); math.define("sscript", sscript_func());
math.define("class", ClassElem::func());
// Text operators. // Text operators.
math.define("op", OpElem::func()); math.define("op", OpElem::func());
op::define(&mut math); op::define(&mut math);

View File

@ -320,6 +320,8 @@ pub struct MathStyle {
pub variant: MathVariant, pub variant: MathVariant,
/// The size of the glyphs. /// The size of the glyphs.
pub size: MathSize, pub size: MathSize,
/// The class of the element.
pub class: Smart<MathClass>,
/// Affects the height of exponents. /// Affects the height of exponents.
pub cramped: bool, pub cramped: bool,
/// Whether to use bold glyphs. /// Whether to use bold glyphs.
@ -339,6 +341,11 @@ impl MathStyle {
Self { size, ..self } 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. /// This style, with `cramped` set to the given value.
pub fn with_cramped(self, cramped: bool) -> Self { pub fn with_cramped(self, cramped: bool) -> Self {
Self { cramped, ..self } Self { cramped, ..self }

View File

@ -1,4 +1,5 @@
pub use typst_macros::{cast, Cast}; pub use typst_macros::{cast, Cast};
use unicode_math_class::MathClass;
use std::fmt::Write; use std::fmt::Write;
use std::ops::Add; use std::ops::Add;
@ -314,3 +315,34 @@ impl FromValue for Never {
Err(Self::error(&value)) 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,
}

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

33
tests/typ/math/class.typ Normal file
View File

@ -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 $