mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Customizable math classes (#1681)
This commit is contained in:
parent
7404f85a02
commit
be0f8fe6d7
36
crates/typst-library/src/math/class.rs
Normal file
36
crates/typst-library/src/math/class.rs
Normal 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(())
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 }
|
||||
|
@ -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,
|
||||
}
|
||||
|
BIN
tests/ref/math/class.png
Normal file
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
33
tests/typ/math/class.typ
Normal 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 $
|
Loading…
x
Reference in New Issue
Block a user