Fix math styling

This commit is contained in:
Laurenz 2023-01-29 16:14:04 +01:00
parent 8fbfa594e0
commit 60dfe8f893
9 changed files with 167 additions and 132 deletions

View File

@ -1,4 +1,5 @@
use ttf_parser::math::MathValue; use ttf_parser::math::MathValue;
use typst::font::{FontStyle, FontWeight};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use super::*; use super::*;
@ -24,21 +25,18 @@ macro_rules! percent {
/// The context for math layout. /// The context for math layout.
pub(super) struct MathContext<'a, 'b, 'v> { pub(super) struct MathContext<'a, 'b, 'v> {
pub vt: &'v mut Vt<'b>, pub vt: &'v mut Vt<'b>,
pub outer: StyleChain<'a>,
pub map: StyleMap,
pub regions: Regions<'a>, pub regions: Regions<'a>,
pub font: &'a Font, pub font: &'a Font,
pub ttf: &'a ttf_parser::Face<'a>, pub ttf: &'a ttf_parser::Face<'a>,
pub table: ttf_parser::math::Table<'a>, pub table: ttf_parser::math::Table<'a>,
pub constants: ttf_parser::math::Constants<'a>, pub constants: ttf_parser::math::Constants<'a>,
pub space_width: Em, pub space_width: Em,
pub fill: Paint,
pub lang: Lang,
pub row: MathRow, pub row: MathRow,
pub map: StyleMap,
pub style: MathStyle, pub style: MathStyle,
base_size: Abs, pub size: Abs,
scaled_size: Abs, outer: StyleChain<'a>,
style_stack: Vec<MathStyle>, style_stack: Vec<(MathStyle, Abs)>,
} }
impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { 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 table = font.ttf().tables().math.unwrap();
let constants = table.constants.unwrap(); let constants = table.constants.unwrap();
let size = styles.get(TextNode::SIZE); let size = styles.get(TextNode::SIZE);
let ttf = font.ttf(); let ttf = font.ttf();
let space_width = ttf let space_width = ttf
.glyph_index(' ') .glyph_index(' ')
@ -60,38 +57,38 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
.map(|advance| font.to_em(advance)) .map(|advance| font.to_em(advance))
.unwrap_or(THICK); .unwrap_or(THICK);
let variant = variant(styles);
Self { Self {
vt, vt,
outer: styles,
map: StyleMap::new(),
regions: { regions: {
let size = Size::new(regions.first.x, regions.base.y); let size = Size::new(regions.first.x, regions.base.y);
Regions::one(size, regions.base, Axes::splat(false)) 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, font: &font,
ttf: font.ttf(), ttf: font.ttf(),
table, table,
constants, constants,
space_width, space_width,
row: MathRow::new(), row: MathRow::new(),
base_size: size, map: StyleMap::new(),
scaled_size: size, 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![], style_stack: vec![],
} }
} }
pub fn push(&mut self, fragment: impl Into<MathFragment>) { pub fn push(&mut self, fragment: impl Into<MathFragment>) {
self.row self.row.push(self.size, self.space_width, self.style, fragment);
.push(self.scaled_size, self.space_width, self.style, fragment);
} }
pub fn extend(&mut self, row: MathRow) { 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)) 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(); let mut chars = text.chars();
if let Some(glyph) = chars if let Some(glyph) = chars
.next() .next()
.filter(|_| chars.next().is_none()) .filter(|_| chars.next().is_none())
.map(|c| self.style.styled_char(c))
.and_then(|c| GlyphFragment::try_new(self, c)) .and_then(|c| GlyphFragment::try_new(self, c))
{ {
// A single letter that is available in the math font. // 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); self.push(glyph);
} }
} else if text.chars().all(|c| c.is_ascii_digit()) { } else if text.chars().all(|c| c.is_ascii_digit()) {
// A number that should respect math styling and can therefore // Numbers aren't that difficult.
// not fall back to the normal text layout.
let mut vec = vec![]; let mut vec = vec![];
for c in text.chars() { for c in text.chars() {
let c = self.style.styled_char(c);
vec.push(GlyphFragment::new(self, c).into()); vec.push(GlyphFragment::new(self, c).into());
} }
let frame = MathRow(vec).to_frame(self); let frame = MathRow(vec).to_frame(self);
@ -158,7 +156,12 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
} else { } else {
// Anything else is handled by Typst's standard text layout. // Anything else is handled by Typst's standard text layout.
let spaced = text.graphemes(true).count() > 1; 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( self.push(
FrameFragment::new(frame) FrameFragment::new(frame)
.with_class(MathClass::Alphabetic) .with_class(MathClass::Alphabetic)
@ -169,33 +172,35 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
Ok(()) Ok(())
} }
pub fn size(&self) -> Abs { pub fn styles(&self) -> StyleChain {
self.scaled_size self.outer.chain(&self.map)
} }
pub fn style(&mut self, style: MathStyle) { 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.style = style;
self.rescale();
self.map.set(TextNode::SIZE, TextSize(self.scaled_size.into()));
} }
pub fn unstyle(&mut self) { pub fn unstyle(&mut self) {
self.style = self.style_stack.pop().unwrap(); (self.style, self.size) = self.style_stack.pop().unwrap();
self.rescale(); self.map.unset();
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 { impl Scaled for Em {
fn scaled(self, ctx: &MathContext) -> Abs { fn scaled(self, ctx: &MathContext) -> Abs {
self.at(ctx.size()) self.at(ctx.size)
} }
} }

View File

@ -157,8 +157,10 @@ fn layout(
frame.push( frame.push(
line_pos, line_pos,
Element::Shape( Element::Shape(
Geometry::Line(Point::with_x(line_width)) Geometry::Line(Point::with_x(line_width)).stroked(Stroke {
.stroked(Stroke { paint: ctx.fill, thickness }), paint: ctx.styles().get(TextNode::FILL),
thickness,
}),
), ),
); );
ctx.push(frame); ctx.push(frame);

View File

@ -121,6 +121,8 @@ impl From<Frame> for MathFragment {
pub(super) struct GlyphFragment { pub(super) struct GlyphFragment {
pub id: GlyphId, pub id: GlyphId,
pub c: char, pub c: char,
pub lang: Lang,
pub fill: Paint,
pub font_size: Abs, pub font_size: Abs,
pub width: Abs, pub width: Abs,
pub ascent: Abs, pub ascent: Abs,
@ -131,7 +133,6 @@ pub(super) struct GlyphFragment {
impl GlyphFragment { impl GlyphFragment {
pub fn new(ctx: &MathContext, c: char) -> Self { 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(); let id = ctx.ttf.glyph_index(c).unwrap_or_default();
Self::with_id(ctx, c, id) Self::with_id(ctx, c, id)
} }
@ -154,7 +155,9 @@ impl GlyphFragment {
Self { Self {
id, id,
c, 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), width: advance.scaled(ctx),
ascent: bbox.y_max.scaled(ctx), ascent: bbox.y_max.scaled(ctx),
descent: -bbox.y_min.scaled(ctx), descent: -bbox.y_min.scaled(ctx),
@ -184,12 +187,12 @@ impl GlyphFragment {
let text = Text { let text = Text {
font: ctx.font.clone(), font: ctx.font.clone(),
size: self.font_size, size: self.font_size,
fill: ctx.fill, fill: self.fill,
lang: ctx.lang, lang: self.lang,
glyphs: vec![Glyph { glyphs: vec![Glyph {
id: self.id.0, id: self.id.0,
c: self.c, 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(), x_offset: Em::zero(),
}], }],
}; };

View File

@ -66,7 +66,7 @@ impl LayoutMath for LrNode {
let height = self let height = self
.size .size
.unwrap_or(Rel::one()) .unwrap_or(Rel::one())
.resolve(ctx.outer.chain(&ctx.map)) .resolve(ctx.styles())
.relative_to(2.0 * max_extent); .relative_to(2.0 * max_extent);
match row.0.as_mut_slice() { match row.0.as_mut_slice() {

View File

@ -39,20 +39,11 @@ impl VecNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.all()?).pack()) 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 { impl LayoutMath for VecNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { 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())) 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> { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.all()?).pack()) 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 { impl LayoutMath for CasesNode {

View File

@ -29,29 +29,27 @@ pub use self::root::*;
pub use self::stack::*; pub use self::stack::*;
pub use self::style::*; pub use self::style::*;
use ttf_parser::GlyphId; use ttf_parser::{GlyphId, Rect};
use ttf_parser::Rect;
use typst::font::Font; 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 unicode_math_class::MathClass;
use self::ctx::*; use self::ctx::*;
use self::fragment::*; use self::fragment::*;
use self::row::*; use self::row::*;
use self::spacing::*; use self::spacing::*;
use crate::layout::HNode; use crate::layout::{HNode, ParNode, Spacing};
use crate::layout::ParNode;
use crate::layout::Spacing;
use crate::prelude::*; use crate::prelude::*;
use crate::text::LinebreakNode; use crate::text::{
use crate::text::TextNode; families, variant, FallbackList, FontFamily, LinebreakNode, SpaceNode, TextNode,
use crate::text::TextSize; TextSize,
use crate::text::{families, variant, FallbackList, FontFamily, SpaceNode}; };
/// Create a module with all math definitions. /// Create a module with all math definitions.
pub fn module(sym: &Module) -> Module { pub fn module(sym: &Module) -> Module {
let mut math = Scope::deduplicating(); let mut math = Scope::deduplicating();
math.def_func::<FormulaNode>("formula"); math.def_func::<FormulaNode>("formula");
math.def_func::<TextNode>("text");
// Grouping. // Grouping.
math.def_func::<LrNode>("lr"); math.def_func::<LrNode>("lr");
@ -83,6 +81,7 @@ pub fn module(sym: &Module) -> Module {
math.def_func::<RootNode>("root"); math.def_func::<RootNode>("root");
// Styles. // Styles.
math.def_func::<UprightNode>("upright");
math.def_func::<BoldNode>("bold"); math.def_func::<BoldNode>("bold");
math.def_func::<ItalicNode>("italic"); math.def_func::<ItalicNode>("italic");
math.def_func::<SerifNode>("serif"); math.def_func::<SerifNode>("serif");
@ -243,6 +242,24 @@ impl LayoutMath for FormulaNode {
impl LayoutMath for Content { impl LayoutMath for Content {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { 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>() { if self.is::<SpaceNode>() {
ctx.push(MathFragment::Space); ctx.push(MathFragment::Space);
return Ok(()); return Ok(());
@ -256,9 +273,7 @@ impl LayoutMath for Content {
if let Some(node) = self.to::<HNode>() { if let Some(node) = self.to::<HNode>() {
if let Spacing::Relative(rel) = node.amount { if let Spacing::Relative(rel) = node.amount {
if rel.rel.is_zero() { if rel.rel.is_zero() {
ctx.push(MathFragment::Spacing( ctx.push(MathFragment::Spacing(rel.abs.resolve(ctx.styles())));
rel.abs.resolve(ctx.outer.chain(&ctx.map)),
));
} }
} }
return Ok(()); return Ok(());
@ -269,13 +284,6 @@ impl LayoutMath for Content {
return Ok(()); 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>() { if let Some(node) = self.with::<dyn LayoutMath>() {
return node.layout_math(ctx); return node.layout_math(ctx);
} }

View File

@ -144,7 +144,7 @@ fn layout(
line_pos, line_pos,
Element::Shape( Element::Shape(
Geometry::Line(Point::with_x(line_length)) 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); frame.push_frame(radicand_pos, radicand);

View File

@ -77,7 +77,7 @@ impl MathRow {
let mut frame = Frame::new(Size::zero()); let mut frame = Frame::new(Size::zero());
let fragments = std::mem::take(&mut self.0); 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 let rows: Vec<_> = fragments
.split(|frag| matches!(frag, MathFragment::Linebreak)) .split(|frag| matches!(frag, MathFragment::Linebreak))
.map(|slice| Self(slice.to_vec())) .map(|slice| Self(slice.to_vec()))

View File

@ -1,5 +1,40 @@
use super::*; 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
/// Bold font style in math. /// Bold font style in math.
/// ///
@ -28,7 +63,7 @@ impl BoldNode {
impl LayoutMath for BoldNode { impl LayoutMath for BoldNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { 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)?; self.0.layout_math(ctx)?;
ctx.unstyle(); ctx.unstyle();
Ok(()) Ok(())
@ -60,7 +95,7 @@ impl ItalicNode {
impl LayoutMath for ItalicNode { impl LayoutMath for ItalicNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { 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)?; self.0.layout_math(ctx)?;
ctx.unstyle(); ctx.unstyle();
Ok(()) Ok(())
@ -290,22 +325,7 @@ pub struct MathStyle {
/// Whether to use bold glyphs. /// Whether to use bold glyphs.
pub bold: bool, pub bold: bool,
/// Whether to use italic glyphs. /// Whether to use italic glyphs.
pub italic: bool, pub italic: Smart<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,
} }
impl MathStyle { impl MathStyle {
@ -331,17 +351,7 @@ impl MathStyle {
/// This style, with `italic` set to the given value. /// This style, with `italic` set to the given value.
pub fn with_italic(self, italic: bool) -> Self { pub fn with_italic(self, italic: bool) -> Self {
Self { italic, ..self } Self { italic: Smart::Custom(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)
} }
/// The style for subscripts in the current style. /// 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. /// A mathematical style variant, as defined by Unicode.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum MathVariant { pub enum MathVariant {
@ -401,17 +436,17 @@ impl Default for MathVariant {
pub(super) fn styled_char(style: MathStyle, c: char) -> char { pub(super) fn styled_char(style: MathStyle, c: char) -> char {
use MathVariant::*; use MathVariant::*;
let tuple = (style.variant, style.bold, style.italic); let (base, default_italic) = match c {
let base = match c { 'a'..='z' => ('a', true),
'a'..='z' => 'a', 'A'..='Z' => ('A', true),
'A'..='Z' => 'A', 'α'..='ω' => ('α', false),
'α'..='ω' => 'α', 'Α'..='Ω' => ('Α', false),
'Α'..='Ω' => 'Α', '0'..='9' => ('0', false),
'0'..='9' => '0',
'-' => return '', '-' => return '',
_ => return c, _ => return c,
}; };
let tuple = (style.variant, style.bold, style.italic.unwrap_or(default_italic));
let start = match c { let start = match c {
// Latin upper. // Latin upper.
'A'..='Z' => match tuple { 'A'..='Z' => match tuple {