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 {
|
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 {
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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 }
|
||||||
|
@ -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
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