summaryrefslogtreecommitdiff
path: root/library
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-01-29 16:14:04 +0100
committerLaurenz <laurmaedje@gmail.com>2023-01-29 17:44:50 +0100
commit60dfe8f8931ca0a18f07e281bdd9751fe61e5adf (patch)
tree5184c386d8ec779b9a91f3f2a7da80dab2f06aae /library
parent8fbfa594e072aba384becaf5f4bd358bcc2ebc68 (diff)
Fix math styling
Diffstat (limited to 'library')
-rw-r--r--library/src/math/ctx.rs99
-rw-r--r--library/src/math/frac.rs6
-rw-r--r--library/src/math/fragment.rs13
-rw-r--r--library/src/math/lr.rs2
-rw-r--r--library/src/math/matrix.rs20
-rw-r--r--library/src/math/mod.rs48
-rw-r--r--library/src/math/root.rs2
-rw-r--r--library/src/math/row.rs2
-rw-r--r--library/src/math/style.rs107
9 files changed, 167 insertions, 132 deletions
diff --git a/library/src/math/ctx.rs b/library/src/math/ctx.rs
index 547d3cc8..fafb96f2 100644
--- a/library/src/math/ctx.rs
+++ b/library/src/math/ctx.rs
@@ -1,4 +1,5 @@
use ttf_parser::math::MathValue;
+use typst::font::{FontStyle, FontWeight};
use unicode_segmentation::UnicodeSegmentation;
use super::*;
@@ -24,21 +25,18 @@ macro_rules! percent {
/// The context for math layout.
pub(super) struct MathContext<'a, 'b, 'v> {
pub vt: &'v mut Vt<'b>,
- pub outer: StyleChain<'a>,
- pub map: StyleMap,
pub regions: Regions<'a>,
pub font: &'a Font,
pub ttf: &'a ttf_parser::Face<'a>,
pub table: ttf_parser::math::Table<'a>,
pub constants: ttf_parser::math::Constants<'a>,
pub space_width: Em,
- pub fill: Paint,
- pub lang: Lang,
pub row: MathRow,
+ pub map: StyleMap,
pub style: MathStyle,
- base_size: Abs,
- scaled_size: Abs,
- style_stack: Vec<MathStyle>,
+ pub size: Abs,
+ outer: StyleChain<'a>,
+ style_stack: Vec<(MathStyle, Abs)>,
}
impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
@@ -52,7 +50,6 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
let table = font.ttf().tables().math.unwrap();
let constants = table.constants.unwrap();
let size = styles.get(TextNode::SIZE);
-
let ttf = font.ttf();
let space_width = ttf
.glyph_index(' ')
@@ -60,38 +57,38 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
.map(|advance| font.to_em(advance))
.unwrap_or(THICK);
+ let variant = variant(styles);
Self {
vt,
- outer: styles,
- map: StyleMap::new(),
regions: {
let size = Size::new(regions.first.x, regions.base.y);
Regions::one(size, regions.base, Axes::splat(false))
},
- style: MathStyle {
- variant: MathVariant::Serif,
- size: if block { MathSize::Display } else { MathSize::Text },
- cramped: false,
- bold: false,
- italic: true,
- },
- fill: styles.get(TextNode::FILL),
- lang: styles.get(TextNode::LANG),
font: &font,
ttf: font.ttf(),
table,
constants,
space_width,
row: MathRow::new(),
- base_size: size,
- scaled_size: size,
+ map: StyleMap::new(),
+ style: MathStyle {
+ variant: MathVariant::Serif,
+ size: if block { MathSize::Display } else { MathSize::Text },
+ cramped: false,
+ bold: variant.weight >= FontWeight::BOLD,
+ italic: match variant.style {
+ FontStyle::Normal => Smart::Auto,
+ FontStyle::Italic | FontStyle::Oblique => Smart::Custom(true),
+ },
+ },
+ size,
+ outer: styles,
style_stack: vec![],
}
}
pub fn push(&mut self, fragment: impl Into<MathFragment>) {
- self.row
- .push(self.scaled_size, self.space_width, self.style, fragment);
+ self.row.push(self.size, self.space_width, self.style, fragment);
}
pub fn extend(&mut self, row: MathRow) {
@@ -130,11 +127,12 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
Ok(self.layout_fragment(node)?.to_frame(self))
}
- pub fn layout_text(&mut self, text: &EcoString) -> SourceResult<()> {
+ pub fn layout_text(&mut self, text: &str) -> SourceResult<()> {
let mut chars = text.chars();
if let Some(glyph) = chars
.next()
.filter(|_| chars.next().is_none())
+ .map(|c| self.style.styled_char(c))
.and_then(|c| GlyphFragment::try_new(self, c))
{
// A single letter that is available in the math font.
@@ -147,10 +145,10 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
self.push(glyph);
}
} else if text.chars().all(|c| c.is_ascii_digit()) {
- // A number that should respect math styling and can therefore
- // not fall back to the normal text layout.
+ // Numbers aren't that difficult.
let mut vec = vec![];
for c in text.chars() {
+ let c = self.style.styled_char(c);
vec.push(GlyphFragment::new(self, c).into());
}
let frame = MathRow(vec).to_frame(self);
@@ -158,7 +156,12 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
} else {
// Anything else is handled by Typst's standard text layout.
let spaced = text.graphemes(true).count() > 1;
- let frame = self.layout_non_math(&TextNode::packed(text.clone()))?;
+ let mut style = self.style;
+ if self.style.italic == Smart::Auto {
+ style = style.with_italic(false);
+ }
+ let text: EcoString = text.chars().map(|c| style.styled_char(c)).collect();
+ let frame = self.layout_non_math(&TextNode::packed(text))?;
self.push(
FrameFragment::new(frame)
.with_class(MathClass::Alphabetic)
@@ -169,33 +172,35 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
Ok(())
}
- pub fn size(&self) -> Abs {
- self.scaled_size
+ pub fn styles(&self) -> StyleChain {
+ self.outer.chain(&self.map)
}
pub fn style(&mut self, style: MathStyle) {
- self.style_stack.push(self.style);
+ self.style_stack.push((self.style, self.size));
+ let base_size = self.styles().get(TextNode::SIZE) / self.style.size.factor(self);
+ self.size = base_size * style.size.factor(self);
+ self.map.set(TextNode::SIZE, TextSize(self.size.into()));
+ self.map.set(
+ TextNode::STYLE,
+ if style.italic == Smart::Custom(true) {
+ FontStyle::Italic
+ } else {
+ FontStyle::Normal
+ },
+ );
+ self.map.set(
+ TextNode::WEIGHT,
+ if style.bold { FontWeight::BOLD } else { FontWeight::REGULAR },
+ );
self.style = style;
- self.rescale();
- self.map.set(TextNode::SIZE, TextSize(self.scaled_size.into()));
}
pub fn unstyle(&mut self) {
- self.style = self.style_stack.pop().unwrap();
- self.rescale();
+ (self.style, self.size) = self.style_stack.pop().unwrap();
+ self.map.unset();
+ self.map.unset();
self.map.unset();
- }
-
- fn rescale(&mut self) {
- self.scaled_size = match self.style.size {
- MathSize::Display | MathSize::Text => self.base_size,
- MathSize::Script => {
- self.base_size * percent!(self, script_percent_scale_down)
- }
- MathSize::ScriptScript => {
- self.base_size * percent!(self, script_script_percent_scale_down)
- }
- };
}
}
@@ -217,7 +222,7 @@ impl Scaled for u16 {
impl Scaled for Em {
fn scaled(self, ctx: &MathContext) -> Abs {
- self.at(ctx.size())
+ self.at(ctx.size)
}
}
diff --git a/library/src/math/frac.rs b/library/src/math/frac.rs
index e945473c..ebdb5c02 100644
--- a/library/src/math/frac.rs
+++ b/library/src/math/frac.rs
@@ -157,8 +157,10 @@ fn layout(
frame.push(
line_pos,
Element::Shape(
- Geometry::Line(Point::with_x(line_width))
- .stroked(Stroke { paint: ctx.fill, thickness }),
+ Geometry::Line(Point::with_x(line_width)).stroked(Stroke {
+ paint: ctx.styles().get(TextNode::FILL),
+ thickness,
+ }),
),
);
ctx.push(frame);
diff --git a/library/src/math/fragment.rs b/library/src/math/fragment.rs
index fef57a0a..a7ca8db3 100644
--- a/library/src/math/fragment.rs
+++ b/library/src/math/fragment.rs
@@ -121,6 +121,8 @@ impl From<Frame> for MathFragment {
pub(super) struct GlyphFragment {
pub id: GlyphId,
pub c: char,
+ pub lang: Lang,
+ pub fill: Paint,
pub font_size: Abs,
pub width: Abs,
pub ascent: Abs,
@@ -131,7 +133,6 @@ pub(super) struct GlyphFragment {
impl GlyphFragment {
pub fn new(ctx: &MathContext, c: char) -> Self {
- let c = ctx.style.styled_char(c);
let id = ctx.ttf.glyph_index(c).unwrap_or_default();
Self::with_id(ctx, c, id)
}
@@ -154,7 +155,9 @@ impl GlyphFragment {
Self {
id,
c,
- font_size: ctx.size(),
+ lang: ctx.styles().get(TextNode::LANG),
+ fill: ctx.styles().get(TextNode::FILL),
+ font_size: ctx.size,
width: advance.scaled(ctx),
ascent: bbox.y_max.scaled(ctx),
descent: -bbox.y_min.scaled(ctx),
@@ -184,12 +187,12 @@ impl GlyphFragment {
let text = Text {
font: ctx.font.clone(),
size: self.font_size,
- fill: ctx.fill,
- lang: ctx.lang,
+ fill: self.fill,
+ lang: self.lang,
glyphs: vec![Glyph {
id: self.id.0,
c: self.c,
- x_advance: Em::from_length(self.width, ctx.size()),
+ x_advance: Em::from_length(self.width, ctx.size),
x_offset: Em::zero(),
}],
};
diff --git a/library/src/math/lr.rs b/library/src/math/lr.rs
index 30ff532a..e265affb 100644
--- a/library/src/math/lr.rs
+++ b/library/src/math/lr.rs
@@ -66,7 +66,7 @@ impl LayoutMath for LrNode {
let height = self
.size
.unwrap_or(Rel::one())
- .resolve(ctx.outer.chain(&ctx.map))
+ .resolve(ctx.styles())
.relative_to(2.0 * max_extent);
match row.0.as_mut_slice() {
diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs
index 45ebdda7..72ef0253 100644
--- a/library/src/math/matrix.rs
+++ b/library/src/math/matrix.rs
@@ -39,20 +39,11 @@ impl VecNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.all()?).pack())
}
-
- fn field(&self, name: &str) -> Option<Value> {
- match name {
- "elements" => {
- Some(Value::Array(self.0.iter().cloned().map(Value::Content).collect()))
- }
- _ => None,
- }
- }
}
impl LayoutMath for VecNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- let delim = ctx.outer.get(Self::DELIM);
+ let delim = ctx.styles().get(Self::DELIM);
layout(ctx, &self.0, Align::Center, Some(delim.open()), Some(delim.close()))
}
}
@@ -86,15 +77,6 @@ impl CasesNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.all()?).pack())
}
-
- fn field(&self, name: &str) -> Option<Value> {
- match name {
- "branches" => {
- Some(Value::Array(self.0.iter().cloned().map(Value::Content).collect()))
- }
- _ => None,
- }
- }
}
impl LayoutMath for CasesNode {
diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs
index f1bcd8f4..ab67a0d3 100644
--- a/library/src/math/mod.rs
+++ b/library/src/math/mod.rs
@@ -29,29 +29,27 @@ pub use self::root::*;
pub use self::stack::*;
pub use self::style::*;
-use ttf_parser::GlyphId;
-use ttf_parser::Rect;
+use ttf_parser::{GlyphId, Rect};
use typst::font::Font;
-use typst::model::{Guard, Module, Scope, SequenceNode};
+use typst::model::{Guard, Module, Scope, SequenceNode, StyledNode};
use unicode_math_class::MathClass;
use self::ctx::*;
use self::fragment::*;
use self::row::*;
use self::spacing::*;
-use crate::layout::HNode;
-use crate::layout::ParNode;
-use crate::layout::Spacing;
+use crate::layout::{HNode, ParNode, Spacing};
use crate::prelude::*;
-use crate::text::LinebreakNode;
-use crate::text::TextNode;
-use crate::text::TextSize;
-use crate::text::{families, variant, FallbackList, FontFamily, SpaceNode};
+use crate::text::{
+ families, variant, FallbackList, FontFamily, LinebreakNode, SpaceNode, TextNode,
+ TextSize,
+};
/// Create a module with all math definitions.
pub fn module(sym: &Module) -> Module {
let mut math = Scope::deduplicating();
math.def_func::<FormulaNode>("formula");
+ math.def_func::<TextNode>("text");
// Grouping.
math.def_func::<LrNode>("lr");
@@ -83,6 +81,7 @@ pub fn module(sym: &Module) -> Module {
math.def_func::<RootNode>("root");
// Styles.
+ math.def_func::<UprightNode>("upright");
math.def_func::<BoldNode>("bold");
math.def_func::<ItalicNode>("italic");
math.def_func::<SerifNode>("serif");
@@ -243,6 +242,24 @@ impl LayoutMath for FormulaNode {
impl LayoutMath for Content {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ if let Some(node) = self.to::<SequenceNode>() {
+ for child in &node.0 {
+ child.layout_math(ctx)?;
+ }
+ return Ok(());
+ }
+
+ if let Some(styled) = self.to::<StyledNode>() {
+ let prev_map = std::mem::replace(&mut ctx.map, styled.map.clone());
+ let prev_size = ctx.size;
+ ctx.map.apply(prev_map.clone());
+ ctx.size = ctx.styles().get(TextNode::SIZE);
+ styled.sub.layout_math(ctx)?;
+ ctx.size = prev_size;
+ ctx.map = prev_map;
+ return Ok(());
+ }
+
if self.is::<SpaceNode>() {
ctx.push(MathFragment::Space);
return Ok(());
@@ -256,9 +273,7 @@ impl LayoutMath for Content {
if let Some(node) = self.to::<HNode>() {
if let Spacing::Relative(rel) = node.amount {
if rel.rel.is_zero() {
- ctx.push(MathFragment::Spacing(
- rel.abs.resolve(ctx.outer.chain(&ctx.map)),
- ));
+ ctx.push(MathFragment::Spacing(rel.abs.resolve(ctx.styles())));
}
}
return Ok(());
@@ -269,13 +284,6 @@ impl LayoutMath for Content {
return Ok(());
}
- if let Some(node) = self.to::<SequenceNode>() {
- for child in &node.0 {
- child.layout_math(ctx)?;
- }
- return Ok(());
- }
-
if let Some(node) = self.with::<dyn LayoutMath>() {
return node.layout_math(ctx);
}
diff --git a/library/src/math/root.rs b/library/src/math/root.rs
index 79bcfe38..1416325c 100644
--- a/library/src/math/root.rs
+++ b/library/src/math/root.rs
@@ -144,7 +144,7 @@ fn layout(
line_pos,
Element::Shape(
Geometry::Line(Point::with_x(line_length))
- .stroked(Stroke { paint: ctx.fill, thickness }),
+ .stroked(Stroke { paint: ctx.styles().get(TextNode::FILL), thickness }),
),
);
frame.push_frame(radicand_pos, radicand);
diff --git a/library/src/math/row.rs b/library/src/math/row.rs
index f75aed99..bc013182 100644
--- a/library/src/math/row.rs
+++ b/library/src/math/row.rs
@@ -77,7 +77,7 @@ impl MathRow {
let mut frame = Frame::new(Size::zero());
let fragments = std::mem::take(&mut self.0);
- let leading = ctx.outer.chain(&ctx.map).get(ParNode::LEADING);
+ let leading = ctx.styles().get(ParNode::LEADING);
let rows: Vec<_> = fragments
.split(|frag| matches!(frag, MathFragment::Linebreak))
.map(|slice| Self(slice.to_vec()))
diff --git a/library/src/math/style.rs b/library/src/math/style.rs
index d408b532..14f97ae8 100644
--- a/library/src/math/style.rs
+++ b/library/src/math/style.rs
@@ -1,5 +1,40 @@
use super::*;
+/// # Upright
+/// Upright (non-italic) font style in math.
+///
+/// ## Example
+/// ```
+/// $ upright(A) != A $
+/// ```
+///
+/// ## Parameters
+/// - body: Content (positional, required)
+/// The piece of formula to style.
+///
+/// ## Category
+/// math
+#[func]
+#[capable(LayoutMath)]
+#[derive(Debug, Hash)]
+pub struct UprightNode(pub Content);
+
+#[node]
+impl UprightNode {
+ fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
+ Ok(Self(args.expect("body")?).pack())
+ }
+}
+
+impl LayoutMath for UprightNode {
+ fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ ctx.style(ctx.style.with_italic(false));
+ self.0.layout_math(ctx)?;
+ ctx.unstyle();
+ Ok(())
+ }
+}
+
/// # Bold
/// Bold font style in math.
///
@@ -28,7 +63,7 @@ impl BoldNode {
impl LayoutMath for BoldNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- ctx.style(ctx.style.with_italic(false).with_bold_toggled());
+ ctx.style(ctx.style.with_bold(true));
self.0.layout_math(ctx)?;
ctx.unstyle();
Ok(())
@@ -60,7 +95,7 @@ impl ItalicNode {
impl LayoutMath for ItalicNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
- ctx.style(ctx.style.with_italic_toggled());
+ ctx.style(ctx.style.with_italic(true));
self.0.layout_math(ctx)?;
ctx.unstyle();
Ok(())
@@ -290,22 +325,7 @@ pub struct MathStyle {
/// Whether to use bold glyphs.
pub bold: bool,
/// Whether to use italic glyphs.
- pub italic: bool,
-}
-
-/// The size of elements in a formula.
-///
-/// See the TeXbook p. 141.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
-pub enum MathSize {
- /// Second-level sub- and superscripts.
- ScriptScript,
- /// Sub- and superscripts.
- Script,
- /// Math in text.
- Text,
- /// Math on its own line.
- Display,
+ pub italic: Smart<bool>,
}
impl MathStyle {
@@ -331,17 +351,7 @@ impl MathStyle {
/// This style, with `italic` set to the given value.
pub fn with_italic(self, italic: bool) -> Self {
- Self { italic, ..self }
- }
-
- /// This style, with boldness inverted.
- pub fn with_bold_toggled(self) -> Self {
- self.with_bold(!self.bold)
- }
-
- /// This style, with italicness inverted.
- pub fn with_italic_toggled(self) -> Self {
- self.with_italic(!self.italic)
+ Self { italic: Smart::Custom(italic), ..self }
}
/// The style for subscripts in the current style.
@@ -377,6 +387,31 @@ impl MathStyle {
}
}
+/// The size of elements in a formula.
+///
+/// See the TeXbook p. 141.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+pub enum MathSize {
+ /// Second-level sub- and superscripts.
+ ScriptScript,
+ /// Sub- and superscripts.
+ Script,
+ /// Math in text.
+ Text,
+ /// Math on its own line.
+ Display,
+}
+
+impl MathSize {
+ pub(super) fn factor(self, ctx: &MathContext) -> f64 {
+ match self {
+ Self::Display | Self::Text => 1.0,
+ Self::Script => percent!(ctx, script_percent_scale_down),
+ Self::ScriptScript => percent!(ctx, script_script_percent_scale_down),
+ }
+ }
+}
+
/// A mathematical style variant, as defined by Unicode.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum MathVariant {
@@ -401,17 +436,17 @@ impl Default for MathVariant {
pub(super) fn styled_char(style: MathStyle, c: char) -> char {
use MathVariant::*;
- let tuple = (style.variant, style.bold, style.italic);
- let base = match c {
- 'a'..='z' => 'a',
- 'A'..='Z' => 'A',
- 'α'..='ω' => 'α',
- 'Α'..='Ω' => 'Α',
- '0'..='9' => '0',
+ let (base, default_italic) = match c {
+ 'a'..='z' => ('a', true),
+ 'A'..='Z' => ('A', true),
+ 'α'..='ω' => ('α', false),
+ 'Α'..='Ω' => ('Α', false),
+ '0'..='9' => ('0', false),
'-' => return '−',
_ => return c,
};
+ let tuple = (style.variant, style.bold, style.italic.unwrap_or(default_italic));
let start = match c {
// Latin upper.
'A'..='Z' => match tuple {