Math framework

This commit is contained in:
Laurenz 2023-01-22 13:26:42 +01:00
parent a2d77e36ba
commit 83b6858146
8 changed files with 628 additions and 277 deletions

View File

@ -49,24 +49,7 @@ fn scope() -> Scope {
std.def_func::<text::RawNode>("raw");
// Math.
std.def_func::<math::MathNode>("math");
std.def_func::<math::AccNode>("acc");
std.def_func::<math::FracNode>("frac");
std.def_func::<math::BinomNode>("binom");
std.def_func::<math::ScriptNode>("script");
std.def_func::<math::SqrtNode>("sqrt");
std.def_func::<math::FloorNode>("floor");
std.def_func::<math::CeilNode>("ceil");
std.def_func::<math::VecNode>("vec");
std.def_func::<math::CasesNode>("cases");
std.def_func::<math::SerifNode>("serif");
std.def_func::<math::SansNode>("sans");
std.def_func::<math::BoldNode>("bold");
std.def_func::<math::ItalNode>("ital");
std.def_func::<math::CalNode>("cal");
std.def_func::<math::FrakNode>("frak");
std.def_func::<math::MonoNode>("mono");
std.def_func::<math::BbNode>("bb");
math::define(&mut std);
// Layout.
std.def_func::<layout::PageNode>("page");
@ -204,7 +187,7 @@ fn items() -> LangItems {
term_item: |term, description| {
layout::ListItem::Term(basics::TermItem { term, description }).pack()
},
math: |children, block| math::MathNode { children, block }.pack(),
math: |body, block| math::MathNode { body, block }.pack(),
math_atom: |atom| math::AtomNode(atom).pack(),
math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(),
math_frac: |num, denom| math::FracNode { num, denom }.pack(),

169
library/src/math/ctx.rs Normal file
View File

@ -0,0 +1,169 @@
use ttf_parser::math::MathValue;
use super::*;
macro_rules! scaled {
($ctx:expr, text: $text:ident, display: $display:ident $(,)?) => {
match $ctx.style.size {
MathSize::Display => scaled!($ctx, $display),
_ => scaled!($ctx, $text),
}
};
($ctx:expr, $name:ident) => {
$ctx.constants.$name().scaled($ctx)
};
}
macro_rules! percent {
($ctx:expr, $name:ident) => {
$ctx.constants.$name() as f64 / 100.0
};
}
/// 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 fill: Paint,
pub lang: Lang,
pub row: MathRow,
pub style: MathStyle,
base_size: Abs,
scaled_size: Abs,
style_stack: Vec<MathStyle>,
}
impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
pub fn new(
vt: &'v mut Vt<'b>,
styles: StyleChain<'a>,
regions: Regions,
font: &'a Font,
block: bool,
) -> Self {
let table = font.ttf().tables().math.unwrap();
let constants = table.constants.unwrap();
let size = styles.get(TextNode::SIZE);
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,
row: MathRow::new(),
base_size: size,
scaled_size: size,
style_stack: vec![],
}
}
pub fn push(&mut self, fragment: impl Into<MathFragment>) {
self.row.push(self.scaled_size, self.style, fragment);
}
pub fn layout_non_math(&mut self, content: &Content) -> SourceResult<Frame> {
Ok(content
.layout(&mut self.vt, self.outer.chain(&self.map), self.regions)?
.into_frame())
}
pub fn layout_fragment(
&mut self,
node: &dyn LayoutMath,
) -> SourceResult<MathFragment> {
let row = self.layout_row(node)?;
Ok(if row.0.len() == 1 {
row.0.into_iter().next().unwrap()
} else {
row.to_frame(self).into()
})
}
pub fn layout_row(&mut self, node: &dyn LayoutMath) -> SourceResult<MathRow> {
let prev = std::mem::take(&mut self.row);
node.layout_math(self)?;
Ok(std::mem::replace(&mut self.row, prev))
}
pub fn layout_frame(&mut self, node: &dyn LayoutMath) -> SourceResult<Frame> {
Ok(self.layout_fragment(node)?.to_frame(self))
}
pub fn size(&self) -> Abs {
self.scaled_size
}
pub fn style(&mut self, style: MathStyle) {
self.style_stack.push(self.style);
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.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)
}
};
}
}
pub(super) trait Scaled {
fn scaled(self, ctx: &MathContext) -> Abs;
}
impl Scaled for i16 {
fn scaled(self, ctx: &MathContext) -> Abs {
ctx.font.to_em(self).scaled(ctx)
}
}
impl Scaled for u16 {
fn scaled(self, ctx: &MathContext) -> Abs {
ctx.font.to_em(self).scaled(ctx)
}
}
impl Scaled for Em {
fn scaled(self, ctx: &MathContext) -> Abs {
self.at(ctx.size())
}
}
impl Scaled for MathValue<'_> {
fn scaled(self, ctx: &MathContext) -> Abs {
self.value.scaled(ctx)
}
}

View File

@ -0,0 +1,239 @@
use super::*;
#[derive(Debug, Clone)]
pub(super) enum MathFragment {
Glyph(GlyphFragment),
Variant(VariantFragment),
Frame(FrameFragment),
Spacing(Abs),
Align,
Linebreak,
}
impl MathFragment {
pub fn size(&self) -> Size {
Size::new(self.width(), self.height())
}
pub fn width(&self) -> Abs {
match self {
Self::Glyph(glyph) => glyph.width,
Self::Variant(variant) => variant.frame.width(),
Self::Frame(fragment) => fragment.frame.width(),
Self::Spacing(amount) => *amount,
_ => Abs::zero(),
}
}
pub fn height(&self) -> Abs {
match self {
Self::Glyph(glyph) => glyph.height(),
Self::Variant(variant) => variant.frame.height(),
Self::Frame(fragment) => fragment.frame.height(),
_ => Abs::zero(),
}
}
pub fn ascent(&self) -> Abs {
match self {
Self::Glyph(glyph) => glyph.ascent,
Self::Variant(variant) => variant.frame.ascent(),
Self::Frame(fragment) => fragment.frame.baseline(),
_ => Abs::zero(),
}
}
pub fn descent(&self) -> Abs {
match self {
Self::Glyph(glyph) => glyph.descent,
Self::Variant(variant) => variant.frame.descent(),
Self::Frame(fragment) => fragment.frame.descent(),
_ => Abs::zero(),
}
}
pub fn class(&self) -> Option<MathClass> {
match self {
Self::Glyph(glyph) => glyph.class(),
Self::Variant(variant) => variant.class(),
Self::Frame(fragment) => Some(fragment.class),
_ => None,
}
}
pub fn italics_correction(&self) -> Abs {
match self {
Self::Glyph(glyph) => glyph.italics_correction,
Self::Variant(variant) => variant.italics_correction,
_ => Abs::zero(),
}
}
pub fn to_frame(self, ctx: &MathContext) -> Frame {
match self {
Self::Glyph(glyph) => glyph.to_frame(ctx),
Self::Variant(variant) => variant.frame,
Self::Frame(fragment) => fragment.frame,
_ => Frame::new(self.size()),
}
}
}
impl From<GlyphFragment> for MathFragment {
fn from(glyph: GlyphFragment) -> Self {
Self::Glyph(glyph)
}
}
impl From<VariantFragment> for MathFragment {
fn from(variant: VariantFragment) -> Self {
Self::Variant(variant)
}
}
impl From<FrameFragment> for MathFragment {
fn from(fragment: FrameFragment) -> Self {
Self::Frame(fragment)
}
}
impl From<Frame> for MathFragment {
fn from(frame: Frame) -> Self {
Self::Frame(FrameFragment { frame, class: MathClass::Normal, limits: false })
}
}
#[derive(Debug, Clone, Copy)]
pub(super) struct GlyphFragment {
pub id: GlyphId,
pub c: char,
pub font_size: Abs,
pub width: Abs,
pub ascent: Abs,
pub descent: Abs,
pub italics_correction: Abs,
}
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)
}
pub fn try_new(ctx: &MathContext, c: char) -> Option<Self> {
let c = ctx.style.styled_char(c);
let id = ctx.ttf.glyph_index(c)?;
Some(Self::with_id(ctx, c, id))
}
pub fn with_id(ctx: &MathContext, c: char, id: GlyphId) -> Self {
let advance = ctx.ttf.glyph_hor_advance(id).unwrap_or_default();
let italics = italics_correction(ctx, id).unwrap_or_default();
let bbox = ctx.ttf.glyph_bounding_box(id).unwrap_or(Rect {
x_min: 0,
y_min: 0,
x_max: 0,
y_max: 0,
});
Self {
id,
c,
font_size: ctx.size(),
width: advance.scaled(ctx),
ascent: bbox.y_max.scaled(ctx),
descent: -bbox.y_min.scaled(ctx),
italics_correction: italics,
}
}
pub fn height(&self) -> Abs {
self.ascent + self.descent
}
pub fn class(&self) -> Option<MathClass> {
unicode_math_class::class(self.c)
}
pub fn to_variant(&self, ctx: &MathContext) -> VariantFragment {
VariantFragment {
c: self.c,
id: Some(self.id),
frame: self.to_frame(ctx),
italics_correction: self.italics_correction,
}
}
pub fn to_frame(&self, ctx: &MathContext) -> Frame {
let text = Text {
font: ctx.font.clone(),
size: self.font_size,
fill: ctx.fill,
lang: ctx.lang,
glyphs: vec![Glyph {
id: self.id.0,
c: self.c,
x_advance: Em::from_length(self.width, ctx.size()),
x_offset: Em::zero(),
}],
};
let size = Size::new(self.width, self.ascent + self.descent);
let mut frame = Frame::new(size);
frame.set_baseline(self.ascent);
frame.push(Point::with_y(self.ascent), Element::Text(text));
frame
}
}
#[derive(Debug, Clone)]
pub struct VariantFragment {
pub c: char,
pub id: Option<GlyphId>,
pub frame: Frame,
pub italics_correction: Abs,
}
impl VariantFragment {
pub fn class(&self) -> Option<MathClass> {
unicode_math_class::class(self.c)
}
}
#[derive(Debug, Clone)]
pub struct FrameFragment {
pub frame: Frame,
pub class: MathClass,
pub limits: bool,
}
/// Look up the italics correction for a glyph.
fn italics_correction(ctx: &MathContext, id: GlyphId) -> Option<Abs> {
Some(ctx.table.glyph_info?.italic_corrections?.get(id)?.scaled(ctx))
}
/// Look up a kerning value at a specific corner and height.
///
/// This can be integrated once we've found a font that actually provides this
/// data.
#[allow(unused)]
fn kern_at_height(
ctx: &MathContext,
id: GlyphId,
corner: Corner,
height: Abs,
) -> Option<Abs> {
let kerns = ctx.table.glyph_info?.kern_infos?.get(id)?;
let kern = match corner {
Corner::TopLeft => kerns.top_left,
Corner::TopRight => kerns.top_right,
Corner::BottomRight => kerns.bottom_right,
Corner::BottomLeft => kerns.bottom_left,
}?;
let mut i = 0;
while i < kern.count() && height > kern.height(i)?.scaled(ctx) {
i += 1;
}
Some(kern.kern(i)?.scaled(ctx))
}

View File

@ -1,71 +0,0 @@
use super::*;
/// # Floor
/// A floored expression.
///
/// ## Example
/// ```
/// $ floor(x/2) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The expression to floor.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct FloorNode(pub Content);
#[node]
impl FloorNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for FloorNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\left\\lfloor ");
self.0.texify(t)?;
t.push_str("\\right\\rfloor ");
Ok(())
}
}
/// # Ceil
/// A ceiled expression.
///
/// ## Example
/// ```
/// $ ceil(x/2) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The expression to ceil.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct CeilNode(pub Content);
#[node]
impl CeilNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for CeilNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\left\\lceil ");
self.0.texify(t)?;
t.push_str("\\right\\rceil ");
Ok(())
}
}

View File

@ -1,5 +1,7 @@
//! Mathematical formulas.
#[macro_use]
mod ctx;
mod accent;
mod atom;
mod frac;
@ -8,23 +10,54 @@ mod matrix;
mod root;
mod script;
mod style;
mod tex;
pub use self::accent::*;
pub use self::align::*;
pub use self::atom::*;
pub use self::braced::*;
pub use self::frac::*;
pub use self::group::*;
pub use self::lr::*;
pub use self::matrix::*;
pub use self::op::*;
pub use self::root::*;
pub use self::script::*;
pub use self::style::*;
use typst::model::{Guard, SequenceNode};
use unicode_segmentation::UnicodeSegmentation;
use ttf_parser::GlyphId;
use ttf_parser::Rect;
use typst::font::Font;
use typst::model::{Guard, Scope, SequenceNode};
use unicode_math_class::MathClass;
use self::tex::layout_tex;
use self::ctx::*;
use self::fragment::*;
use self::row::*;
use self::spacing::*;
use crate::layout::HNode;
use crate::layout::ParNode;
use crate::prelude::*;
use crate::text::{FontFamily, LinebreakNode, SpaceNode, SymbolNode, TextNode};
use crate::text::LinebreakNode;
use crate::text::TextNode;
use crate::text::TextSize;
use crate::text::{families, variant, FallbackList, FontFamily, SpaceNode, SymbolNode};
/// Hook up all math definitions.
pub fn define(scope: &mut Scope) {
scope.def_func::<MathNode>("math");
scope.def_func::<FracNode>("frac");
scope.def_func::<ScriptNode>("script");
scope.def_func::<SqrtNode>("sqrt");
scope.def_func::<VecNode>("vec");
scope.def_func::<CasesNode>("cases");
scope.def_func::<BoldNode>("bold");
scope.def_func::<ItalicNode>("italic");
scope.def_func::<SerifNode>("serif");
scope.def_func::<SansNode>("sans");
scope.def_func::<CalNode>("cal");
scope.def_func::<FrakNode>("frak");
scope.def_func::<MonoNode>("mono");
scope.def_func::<BbNode>("bb");
}
/// # Math
/// A mathematical formula.
@ -67,8 +100,8 @@ use crate::text::{FontFamily, LinebreakNode, SpaceNode, SymbolNode, TextNode};
/// ```
///
/// ## Parameters
/// - items: Content (positional, variadic)
/// The individual parts of the formula.
/// - body: Content (positional, required)
/// The contents of the formula.
///
/// - block: bool (named)
/// Whether the formula is displayed as a separate block.
@ -76,21 +109,21 @@ use crate::text::{FontFamily, LinebreakNode, SpaceNode, SymbolNode, TextNode};
/// ## Category
/// math
#[func]
#[capable(Show, Layout, Inline, Texify)]
#[capable(Show, Finalize, Layout, Inline, LayoutMath)]
#[derive(Debug, Clone, Hash)]
pub struct MathNode {
/// Whether the formula is displayed as a separate block.
pub block: bool,
/// The pieces of the formula.
pub children: Vec<Content>,
/// The content of the formula.
pub body: Content,
}
#[node]
impl MathNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let body = args.expect("body")?;
let block = args.named("block")?.unwrap_or(false);
let children = args.all()?;
Ok(Self { block, children }.pack())
Ok(Self { block, body }.pack())
}
fn field(&self, name: &str) -> Option<Value> {
@ -125,213 +158,74 @@ impl Layout for MathNode {
&self,
vt: &mut Vt,
styles: StyleChain,
_: Regions,
regions: Regions,
) -> SourceResult<Fragment> {
let mut t = Texifier::new(styles);
self.texify(&mut t)?;
Ok(layout_tex(vt, &t.finish(), self.block, styles)
.unwrap_or(Fragment::frame(Frame::new(Size::zero()))))
// Find a math font.
let variant = variant(styles);
let world = vt.world();
let Some(font) = families(styles)
.find_map(|family| {
let id = world.book().select(family, variant)?;
let font = world.font(id)?;
let _ = font.ttf().tables().math?.constants?;
Some(font)
})
else {
return Ok(Fragment::frame(Frame::new(Size::zero())))
};
let mut ctx = MathContext::new(vt, styles, regions, &font, self.block);
let frame = ctx.layout_frame(self)?;
Ok(Fragment::frame(frame))
}
}
impl Inline for MathNode {}
/// Turn a math node into TeX math code.
#[capability]
trait Texify {
/// Perform the conversion.
fn texify(&self, t: &mut Texifier) -> SourceResult<()>;
trait LayoutMath {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>;
}
/// Texify the node, but trim parentheses..
fn texify_unparen(&self, t: &mut Texifier) -> SourceResult<()> {
let s = {
let mut sub = Texifier::new(t.styles);
self.texify(&mut sub)?;
sub.finish()
};
let unparened = if s.starts_with("\\left(") && s.ends_with("\\right)") {
s[6..s.len() - 7].into()
} else {
s
};
t.push_str(&unparened);
Ok(())
impl LayoutMath for MathNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
self.body.layout_math(ctx)
}
}
/// Builds the TeX representation of the formula.
struct Texifier<'a> {
tex: EcoString,
support: bool,
space: bool,
styles: StyleChain<'a>,
}
impl<'a> Texifier<'a> {
/// Create a new texifier.
fn new(styles: StyleChain<'a>) -> Self {
Self {
tex: EcoString::new(),
support: false,
space: false,
styles,
}
}
/// Finish texifier and return the TeX string.
fn finish(self) -> EcoString {
self.tex
}
/// Push a weak space.
fn push_space(&mut self) {
self.space = !self.tex.is_empty();
}
/// Mark this position as supportive. This allows a space before or after
/// to exist.
fn support(&mut self) {
self.support = true;
}
/// Flush a space.
fn flush(&mut self) {
if self.space && self.support {
self.tex.push_str("\\ ");
}
self.space = false;
self.support = false;
}
/// Push a string.
fn push_str(&mut self, s: &str) {
self.flush();
self.tex.push_str(s);
}
/// Escape and push a char for TeX usage.
#[rustfmt::skip]
fn push_escaped(&mut self, c: char) {
self.flush();
match c {
' ' => self.tex.push_str("\\ "),
'%' | '&' | '$' | '#' => {
self.tex.push('\\');
self.tex.push(c);
self.tex.push(' ');
}
'{' => self.tex.push_str("\\left\\{"),
'}' => self.tex.push_str("\\right\\}"),
'[' | '(' => {
self.tex.push_str("\\left");
self.tex.push(c);
}
']' | ')' => {
self.tex.push_str("\\right");
self.tex.push(c);
}
'a' ..= 'z' | 'A' ..= 'Z' | '0' ..= '9' | 'Α' ..= 'Ω' | 'α' ..= 'ω' |
'*' | '+' | '-' | '?' | '!' | '=' | '<' | '>' |
':' | ',' | ';' | '|' | '/' | '@' | '.' | '"' => self.tex.push(c),
c => {
if let Some(sym) = unicode_math::SYMBOLS
.iter()
.find(|sym| sym.codepoint == c) {
self.tex.push('\\');
self.tex.push_str(sym.name);
self.tex.push(' ');
}
}
}
}
}
impl Texify for MathNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
for child in &self.children {
child.texify(t)?;
}
Ok(())
}
}
impl Texify for Content {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
impl LayoutMath for Content {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
if self.is::<SpaceNode>() {
t.push_space();
return Ok(());
}
if self.is::<LinebreakNode>() {
t.push_str("\\");
ctx.push(MathFragment::Linebreak);
return Ok(());
}
if let Some(node) = self.to::<SymbolNode>() {
if let Some(c) = symmie::get(&node.0) {
t.push_escaped(c);
return Ok(());
return AtomNode(c.into()).layout_math(ctx);
} else if let Some(span) = self.span() {
bail!(span, "unknown symbol");
}
}
if let Some(node) = self.to::<TextNode>() {
t.support();
t.push_str("\\mathrm{");
for c in node.0.chars() {
t.push_escaped(c);
}
t.push_str("}");
t.support();
return Ok(());
}
if let Some(node) = self.to::<SequenceNode>() {
for child in &node.0 {
child.texify(t)?;
child.layout_math(ctx)?;
}
return Ok(());
}
if let Some(node) = self.with::<dyn Texify>() {
return node.texify(t);
if let Some(node) = self.with::<dyn LayoutMath>() {
return node.layout_math(ctx);
}
if let Some(span) = self.span() {
bail!(span, "not allowed here");
}
let frame = ctx.layout_non_math(self)?;
ctx.push(frame);
Ok(())
}
}
/// # Alignment Point
/// A math alignment point: `&`, `&&`.
///
/// ## Parameters
/// - index: usize (positional, required)
/// The alignment point's index.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct AlignPointNode;
#[node]
impl AlignPointNode {
fn construct(_: &Vm, _: &mut Args) -> SourceResult<Content> {
Ok(Self.pack())
}
}
impl Texify for AlignPointNode {
fn texify(&self, _: &mut Texifier) -> SourceResult<()> {
Ok(())
}
}

118
library/src/math/row.rs Normal file
View File

@ -0,0 +1,118 @@
use super::*;
#[derive(Debug, Default, Clone)]
pub(super) struct MathRow(pub Vec<MathFragment>);
impl MathRow {
pub fn new() -> Self {
Self(vec![])
}
pub fn width(&self) -> Abs {
self.0.iter().map(|fragment| fragment.width()).sum()
}
pub fn push(
&mut self,
font_size: Abs,
style: MathStyle,
fragment: impl Into<MathFragment>,
) {
let fragment = fragment.into();
if let Some(fragment_class) = fragment.class() {
for (i, prev) in self.0.iter().enumerate().rev() {
if matches!(prev, MathFragment::Align) {
continue;
}
let mut amount = Abs::zero();
if let MathFragment::Glyph(glyph) = *prev {
if !glyph.italics_correction.is_zero()
&& fragment_class != MathClass::Alphabetic
{
amount += glyph.italics_correction;
}
}
if let Some(prev_class) = prev.class() {
amount += spacing(prev_class, fragment_class, style).at(font_size);
}
if !amount.is_zero() {
self.0.insert(i + 1, MathFragment::Spacing(amount));
}
break;
}
}
self.0.push(fragment);
}
pub fn to_frame(mut self, ctx: &MathContext) -> Frame {
if self.0.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) {
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 rows: Vec<_> = fragments
.split(|frag| matches!(frag, MathFragment::Linebreak))
.map(|slice| Self(slice.to_vec()))
.collect();
let points = alignments(&rows);
for (i, row) in rows.into_iter().enumerate() {
let size = frame.size_mut();
let sub = row.to_line_frame(ctx, &points);
if i > 0 {
size.y += leading;
}
let pos = Point::with_y(size.y);
size.y += sub.height();
size.x.set_max(sub.width());
frame.push_frame(pos, sub);
}
frame
} else {
self.to_line_frame(ctx, &[])
}
}
pub fn to_line_frame(self, ctx: &MathContext, points: &[Abs]) -> Frame {
let ascent = self.0.iter().map(MathFragment::ascent).max().unwrap_or_default();
let descent = self.0.iter().map(MathFragment::descent).max().unwrap_or_default();
let size = Size::new(Abs::zero(), ascent + descent);
let mut frame = Frame::new(size);
let mut x = Abs::zero();
frame.set_baseline(ascent);
let mut fragments = self.0.into_iter().peekable();
let mut i = 0;
while let Some(fragment) = fragments.next() {
if matches!(fragment, MathFragment::Align) {
if let Some(&point) = points.get(i) {
x = point;
}
i += 1;
continue;
}
let y = ascent - fragment.ascent();
let pos = Point::new(x, y);
x += fragment.width();
frame.push_frame(pos, fragment.to_frame(ctx));
}
frame.size_mut().x = x;
frame
}
}
impl<T> From<T> for MathRow
where
T: Into<MathFragment>,
{
fn from(fragment: T) -> Self {
Self(vec![fragment.into()])
}
}

View File

@ -7,7 +7,8 @@ use std::sync::Arc;
use crate::font::Font;
use crate::geom::{
Abs, Align, Axes, Dir, Em, Numeric, Paint, Point, Shape, Size, Transform,
self, Abs, Align, Axes, Color, Dir, Em, Geometry, Numeric, Paint, Point, RgbaColor,
Shape, Size, Stroke, Transform,
};
use crate::image::Image;
use crate::model::{
@ -160,7 +161,7 @@ impl Frame {
self.size.y
}
/// The baseline of the frame.
/// The vertical position of the frame's baseline.
pub fn baseline(&self) -> Abs {
self.baseline.unwrap_or(self.size.y)
}
@ -170,6 +171,19 @@ impl Frame {
self.baseline = Some(baseline);
}
/// The distance from the baseline to the top of the frame.
///
/// This is the same as `baseline()`, but more in line with the terminology
/// used in math layout.
pub fn ascent(&self) -> Abs {
self.baseline()
}
/// The distance from the baseline to the bottom of the frame.
pub fn descent(&self) -> Abs {
self.size.y - self.baseline()
}
/// An iterator over the elements inside this frame alongside their
/// positions relative to the top-left of the frame.
pub fn elements(&self) -> std::slice::Iter<'_, (Point, Element)> {

View File

@ -48,6 +48,11 @@ impl StyleMap {
}
}
/// Remove the style that was last set.
pub fn unset(&mut self) {
self.0.pop();
}
/// Whether the map contains a style property for the given key.
pub fn contains<K: Key>(&self, _: K) -> bool {
self.0