Support multiple fonts in math
@ -4,8 +4,7 @@ use typst_library::layout::{Em, Frame, Point, Size};
|
|||||||
use typst_library::math::{Accent, AccentElem};
|
use typst_library::math::{Accent, AccentElem};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
style_cramped, style_dtls, style_flac, FrameFragment, GlyphFragment, MathContext,
|
style_cramped, style_dtls, style_flac, FrameFragment, MathContext, MathFragment,
|
||||||
MathFragment,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// How much the accent can be shorter than the base.
|
/// How much the accent can be shorter than the base.
|
||||||
@ -31,14 +30,16 @@ pub fn layout_accent(
|
|||||||
|
|
||||||
let width = elem.size(styles).relative_to(base.width());
|
let width = elem.size(styles).relative_to(base.width());
|
||||||
|
|
||||||
// Try to replace the accent glyph with its flattened variant.
|
|
||||||
let flattened_base_height = scaled!(ctx, styles, flattened_accent_base_height);
|
|
||||||
let flac = style_flac();
|
|
||||||
let accent_styles =
|
|
||||||
if base.ascent() > flattened_base_height { styles.chain(&flac) } else { styles };
|
|
||||||
|
|
||||||
let Accent(c) = elem.accent;
|
let Accent(c) = elem.accent;
|
||||||
let mut glyph = GlyphFragment::new(ctx.font, accent_styles, c, elem.span());
|
let mut glyph = ctx.layout_into_glyph(c, elem.span(), styles)?;
|
||||||
|
let flattened_base_height = value!(glyph.text, flattened_accent_base_height);
|
||||||
|
let accent_base_height = value!(glyph.text, accent_base_height);
|
||||||
|
|
||||||
|
// Try to replace the accent glyph with its flattened variant.
|
||||||
|
let flac = style_flac();
|
||||||
|
if base.ascent() > flattened_base_height {
|
||||||
|
glyph = ctx.layout_into_glyph(c, elem.span(), styles.chain(&flac))?;
|
||||||
|
}
|
||||||
|
|
||||||
// Forcing the accent to be at least as large as the base makes it too
|
// Forcing the accent to be at least as large as the base makes it too
|
||||||
// wide in many case.
|
// wide in many case.
|
||||||
@ -51,7 +52,6 @@ pub fn layout_accent(
|
|||||||
// baseline. Therefore, the default gap is the accent's negated descent
|
// baseline. Therefore, the default gap is the accent's negated descent
|
||||||
// minus the accent base height. Only if the base is very small, we need
|
// minus the accent base height. Only if the base is very small, we need
|
||||||
// a larger gap so that the accent doesn't move too low.
|
// a larger gap so that the accent doesn't move too low.
|
||||||
let accent_base_height = scaled!(ctx, styles, accent_base_height);
|
|
||||||
let gap = -accent.descent() - base.ascent().min(accent_base_height);
|
let gap = -accent.descent() - base.ascent().min(accent_base_height);
|
||||||
let size = Size::new(base.width(), accent.height() + gap + base.height());
|
let size = Size::new(base.width(), accent.height() + gap + base.height());
|
||||||
let accent_pos = Point::with_x(base_attach - accent_attach);
|
let accent_pos = Point::with_x(base_attach - accent_attach);
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
use typst_library::diag::SourceResult;
|
use typst_library::diag::SourceResult;
|
||||||
use typst_library::foundations::{Packed, StyleChain, SymbolElem};
|
use typst_library::foundations::{Packed, StyleChain};
|
||||||
use typst_library::layout::{Abs, Axis, Corner, Frame, Point, Rel, Size};
|
use typst_library::layout::{Abs, Axis, Corner, Frame, Point, Rel, Size};
|
||||||
use typst_library::math::{
|
use typst_library::math::{
|
||||||
AttachElem, EquationElem, LimitsElem, PrimesElem, ScriptsElem, StretchElem,
|
AttachElem, EquationElem, LimitsElem, PrimesElem, ScriptsElem, StretchElem,
|
||||||
};
|
};
|
||||||
|
use typst_library::text::Font;
|
||||||
|
use typst_syntax::Span;
|
||||||
use typst_utils::OptionExt;
|
use typst_utils::OptionExt;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
stretch_fragment, style_for_subscript, style_for_superscript, FrameFragment, Limits,
|
find_math_font, stretch_fragment, style_for_subscript, style_for_superscript,
|
||||||
MathContext, MathFragment,
|
FrameFragment, Limits, MathContext, MathFragment,
|
||||||
};
|
};
|
||||||
|
|
||||||
macro_rules! measure {
|
macro_rules! measure {
|
||||||
@ -102,14 +104,12 @@ pub fn layout_primes(
|
|||||||
4 => '⁗',
|
4 => '⁗',
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let f = ctx.layout_into_fragment(&SymbolElem::packed(c), styles)?;
|
let f = ctx.layout_into_glyph(c, elem.span(), styles)?;
|
||||||
ctx.push(f);
|
ctx.push(f);
|
||||||
}
|
}
|
||||||
count => {
|
count => {
|
||||||
// Custom amount of primes
|
// Custom amount of primes
|
||||||
let prime = ctx
|
let prime = ctx.layout_into_glyph('′', elem.span(), styles)?.into_frame();
|
||||||
.layout_into_fragment(&SymbolElem::packed('′'), styles)?
|
|
||||||
.into_frame();
|
|
||||||
let width = prime.width() * (count + 1) as f64 / 2.0;
|
let width = prime.width() * (count + 1) as f64 / 2.0;
|
||||||
let mut frame = Frame::soft(Size::new(width, prime.height()));
|
let mut frame = Frame::soft(Size::new(width, prime.height()));
|
||||||
frame.set_baseline(prime.ascent());
|
frame.set_baseline(prime.ascent());
|
||||||
@ -173,18 +173,21 @@ fn layout_attachments(
|
|||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let base_class = base.class();
|
let base_class = base.class();
|
||||||
|
|
||||||
|
// TODO: should use base's font.
|
||||||
|
let font = find_math_font(ctx.engine, styles, Span::detached())?;
|
||||||
|
|
||||||
// Calculate the distance from the base's baseline to the superscripts' and
|
// Calculate the distance from the base's baseline to the superscripts' and
|
||||||
// subscripts' baseline.
|
// subscripts' baseline.
|
||||||
let (tx_shift, bx_shift) = if [&tl, &tr, &bl, &br].iter().all(|e| e.is_none()) {
|
let (tx_shift, bx_shift) = if [&tl, &tr, &bl, &br].iter().all(|e| e.is_none()) {
|
||||||
(Abs::zero(), Abs::zero())
|
(Abs::zero(), Abs::zero())
|
||||||
} else {
|
} else {
|
||||||
compute_script_shifts(ctx, styles, &base, [&tl, &tr, &bl, &br])
|
compute_script_shifts(&font, styles, &base, [&tl, &tr, &bl, &br])
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate the distance from the base's baseline to the top attachment's
|
// Calculate the distance from the base's baseline to the top attachment's
|
||||||
// and bottom attachment's baseline.
|
// and bottom attachment's baseline.
|
||||||
let (t_shift, b_shift) =
|
let (t_shift, b_shift) =
|
||||||
compute_limit_shifts(ctx, styles, &base, [t.as_ref(), b.as_ref()]);
|
compute_limit_shifts(&font, styles, &base, [t.as_ref(), b.as_ref()]);
|
||||||
|
|
||||||
// Calculate the final frame height.
|
// Calculate the final frame height.
|
||||||
let ascent = base
|
let ascent = base
|
||||||
@ -214,7 +217,7 @@ fn layout_attachments(
|
|||||||
// `space_after_script` is extra spacing that is at the start before each
|
// `space_after_script` is extra spacing that is at the start before each
|
||||||
// pre-script, and at the end after each post-script (see the MathConstants
|
// pre-script, and at the end after each post-script (see the MathConstants
|
||||||
// table in the OpenType MATH spec).
|
// table in the OpenType MATH spec).
|
||||||
let space_after_script = scaled!(ctx, styles, space_after_script);
|
let space_after_script = constant!(font, styles, space_after_script);
|
||||||
|
|
||||||
// Calculate the distance each pre-script extends to the left of the base's
|
// Calculate the distance each pre-script extends to the left of the base's
|
||||||
// width.
|
// width.
|
||||||
@ -363,7 +366,7 @@ fn compute_limit_widths(
|
|||||||
/// Returns two lengths, the first being the distance to the upper-limit's
|
/// Returns two lengths, the first being the distance to the upper-limit's
|
||||||
/// baseline and the second being the distance to the lower-limit's baseline.
|
/// baseline and the second being the distance to the lower-limit's baseline.
|
||||||
fn compute_limit_shifts(
|
fn compute_limit_shifts(
|
||||||
ctx: &MathContext,
|
font: &Font,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
base: &MathFragment,
|
base: &MathFragment,
|
||||||
[t, b]: [Option<&MathFragment>; 2],
|
[t, b]: [Option<&MathFragment>; 2],
|
||||||
@ -374,14 +377,14 @@ fn compute_limit_shifts(
|
|||||||
// MathConstants table in the OpenType MATH spec).
|
// MathConstants table in the OpenType MATH spec).
|
||||||
|
|
||||||
let t_shift = t.map_or_default(|t| {
|
let t_shift = t.map_or_default(|t| {
|
||||||
let upper_gap_min = scaled!(ctx, styles, upper_limit_gap_min);
|
let upper_gap_min = constant!(font, styles, upper_limit_gap_min);
|
||||||
let upper_rise_min = scaled!(ctx, styles, upper_limit_baseline_rise_min);
|
let upper_rise_min = constant!(font, styles, upper_limit_baseline_rise_min);
|
||||||
base.ascent() + upper_rise_min.max(upper_gap_min + t.descent())
|
base.ascent() + upper_rise_min.max(upper_gap_min + t.descent())
|
||||||
});
|
});
|
||||||
|
|
||||||
let b_shift = b.map_or_default(|b| {
|
let b_shift = b.map_or_default(|b| {
|
||||||
let lower_gap_min = scaled!(ctx, styles, lower_limit_gap_min);
|
let lower_gap_min = constant!(font, styles, lower_limit_gap_min);
|
||||||
let lower_drop_min = scaled!(ctx, styles, lower_limit_baseline_drop_min);
|
let lower_drop_min = constant!(font, styles, lower_limit_baseline_drop_min);
|
||||||
base.descent() + lower_drop_min.max(lower_gap_min + b.ascent())
|
base.descent() + lower_drop_min.max(lower_gap_min + b.ascent())
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -392,25 +395,25 @@ fn compute_limit_shifts(
|
|||||||
/// Returns two lengths, the first being the distance to the superscripts'
|
/// Returns two lengths, the first being the distance to the superscripts'
|
||||||
/// baseline and the second being the distance to the subscripts' baseline.
|
/// baseline and the second being the distance to the subscripts' baseline.
|
||||||
fn compute_script_shifts(
|
fn compute_script_shifts(
|
||||||
ctx: &MathContext,
|
font: &Font,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
base: &MathFragment,
|
base: &MathFragment,
|
||||||
[tl, tr, bl, br]: [&Option<MathFragment>; 4],
|
[tl, tr, bl, br]: [&Option<MathFragment>; 4],
|
||||||
) -> (Abs, Abs) {
|
) -> (Abs, Abs) {
|
||||||
let sup_shift_up = if EquationElem::cramped_in(styles) {
|
let sup_shift_up = if EquationElem::cramped_in(styles) {
|
||||||
scaled!(ctx, styles, superscript_shift_up_cramped)
|
constant!(font, styles, superscript_shift_up_cramped)
|
||||||
} else {
|
} else {
|
||||||
scaled!(ctx, styles, superscript_shift_up)
|
constant!(font, styles, superscript_shift_up)
|
||||||
};
|
};
|
||||||
|
|
||||||
let sup_bottom_min = scaled!(ctx, styles, superscript_bottom_min);
|
let sup_bottom_min = constant!(font, styles, superscript_bottom_min);
|
||||||
let sup_bottom_max_with_sub =
|
let sup_bottom_max_with_sub =
|
||||||
scaled!(ctx, styles, superscript_bottom_max_with_subscript);
|
constant!(font, styles, superscript_bottom_max_with_subscript);
|
||||||
let sup_drop_max = scaled!(ctx, styles, superscript_baseline_drop_max);
|
let sup_drop_max = constant!(font, styles, superscript_baseline_drop_max);
|
||||||
let gap_min = scaled!(ctx, styles, sub_superscript_gap_min);
|
let gap_min = constant!(font, styles, sub_superscript_gap_min);
|
||||||
let sub_shift_down = scaled!(ctx, styles, subscript_shift_down);
|
let sub_shift_down = constant!(font, styles, subscript_shift_down);
|
||||||
let sub_top_max = scaled!(ctx, styles, subscript_top_max);
|
let sub_top_max = constant!(font, styles, subscript_top_max);
|
||||||
let sub_drop_min = scaled!(ctx, styles, subscript_baseline_drop_min);
|
let sub_drop_min = constant!(font, styles, subscript_baseline_drop_min);
|
||||||
|
|
||||||
let mut shift_up = Abs::zero();
|
let mut shift_up = Abs::zero();
|
||||||
let mut shift_down = Abs::zero();
|
let mut shift_down = Abs::zero();
|
||||||
|
@ -7,7 +7,7 @@ use typst_library::visualize::{FixedStroke, Geometry};
|
|||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
style_for_denominator, style_for_numerator, FrameFragment, GlyphFragment,
|
find_math_font, style_for_denominator, style_for_numerator, FrameFragment,
|
||||||
MathContext, DELIM_SHORT_FALL,
|
MathContext, DELIM_SHORT_FALL,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,29 +49,30 @@ fn layout_frac_like(
|
|||||||
binom: bool,
|
binom: bool,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let short_fall = DELIM_SHORT_FALL.resolve(styles);
|
let font = find_math_font(ctx.engine, styles, span)?;
|
||||||
let axis = scaled!(ctx, styles, axis_height);
|
let axis = constant!(font, styles, axis_height);
|
||||||
let thickness = scaled!(ctx, styles, fraction_rule_thickness);
|
let thickness = constant!(font, styles, fraction_rule_thickness);
|
||||||
let shift_up = scaled!(
|
let shift_up = constant!(
|
||||||
ctx, styles,
|
font, styles,
|
||||||
text: fraction_numerator_shift_up,
|
text: fraction_numerator_shift_up,
|
||||||
display: fraction_numerator_display_style_shift_up,
|
display: fraction_numerator_display_style_shift_up,
|
||||||
);
|
);
|
||||||
let shift_down = scaled!(
|
let shift_down = constant!(
|
||||||
ctx, styles,
|
font, styles,
|
||||||
text: fraction_denominator_shift_down,
|
text: fraction_denominator_shift_down,
|
||||||
display: fraction_denominator_display_style_shift_down,
|
display: fraction_denominator_display_style_shift_down,
|
||||||
);
|
);
|
||||||
let num_min = scaled!(
|
let num_min = constant!(
|
||||||
ctx, styles,
|
font, styles,
|
||||||
text: fraction_numerator_gap_min,
|
text: fraction_numerator_gap_min,
|
||||||
display: fraction_num_display_style_gap_min,
|
display: fraction_num_display_style_gap_min,
|
||||||
);
|
);
|
||||||
let denom_min = scaled!(
|
let denom_min = constant!(
|
||||||
ctx, styles,
|
font, styles,
|
||||||
text: fraction_denominator_gap_min,
|
text: fraction_denominator_gap_min,
|
||||||
display: fraction_denom_display_style_gap_min,
|
display: fraction_denom_display_style_gap_min,
|
||||||
);
|
);
|
||||||
|
let short_fall = DELIM_SHORT_FALL.resolve(styles);
|
||||||
|
|
||||||
let num_style = style_for_numerator(styles);
|
let num_style = style_for_numerator(styles);
|
||||||
let num = ctx.layout_into_frame(num, styles.chain(&num_style))?;
|
let num = ctx.layout_into_frame(num, styles.chain(&num_style))?;
|
||||||
@ -109,12 +110,12 @@ fn layout_frac_like(
|
|||||||
frame.push_frame(denom_pos, denom);
|
frame.push_frame(denom_pos, denom);
|
||||||
|
|
||||||
if binom {
|
if binom {
|
||||||
let mut left = GlyphFragment::new(ctx.font, styles, '(', span);
|
let mut left = ctx.layout_into_glyph('(', span, styles)?;
|
||||||
left.stretch_vertical(ctx, height, short_fall);
|
left.stretch_vertical(ctx, height, short_fall);
|
||||||
left.center_on_axis();
|
left.center_on_axis();
|
||||||
ctx.push(left);
|
ctx.push(left);
|
||||||
ctx.push(FrameFragment::new(styles, frame));
|
ctx.push(FrameFragment::new(styles, frame));
|
||||||
let mut right = GlyphFragment::new(ctx.font, styles, ')', span);
|
let mut right = ctx.layout_into_glyph(')', span, styles)?;
|
||||||
right.stretch_vertical(ctx, height, short_fall);
|
right.stretch_vertical(ctx, height, short_fall);
|
||||||
right.center_on_axis();
|
right.center_on_axis();
|
||||||
ctx.push(right);
|
ctx.push(right);
|
||||||
|
@ -3,14 +3,17 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use rustybuzz::{BufferFlags, UnicodeBuffer};
|
use rustybuzz::{BufferFlags, UnicodeBuffer};
|
||||||
use ttf_parser::math::{GlyphAssembly, GlyphConstruction, GlyphPart};
|
use ttf_parser::math::{GlyphAssembly, GlyphConstruction, GlyphPart};
|
||||||
use ttf_parser::GlyphId;
|
use ttf_parser::GlyphId;
|
||||||
use typst_library::diag::warning;
|
use typst_library::diag::{bail, warning, SourceResult};
|
||||||
use typst_library::foundations::StyleChain;
|
use typst_library::foundations::StyleChain;
|
||||||
use typst_library::introspection::Tag;
|
use typst_library::introspection::Tag;
|
||||||
use typst_library::layout::{
|
use typst_library::layout::{
|
||||||
Abs, Axes, Axis, Corner, Em, Frame, FrameItem, Point, Size, VAlignment,
|
Abs, Axes, Axis, Corner, Em, Frame, FrameItem, Point, Size, VAlignment,
|
||||||
};
|
};
|
||||||
use typst_library::math::{EquationElem, MathSize};
|
use typst_library::math::{EquationElem, MathSize};
|
||||||
use typst_library::text::{features, language, Font, Glyph, TextElem, TextItem};
|
use typst_library::text::{
|
||||||
|
families, features, language, variant, Font, Glyph, TextElem, TextItem,
|
||||||
|
};
|
||||||
|
use typst_library::World;
|
||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
use typst_utils::{default_math_class, Get};
|
use typst_utils::{default_math_class, Get};
|
||||||
use unicode_math_class::MathClass;
|
use unicode_math_class::MathClass;
|
||||||
@ -258,18 +261,51 @@ pub struct GlyphFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GlyphFragment {
|
impl GlyphFragment {
|
||||||
pub fn new(font: &Font, styles: StyleChain, c: char, span: Span) -> Self {
|
pub fn new(
|
||||||
Self::try_new(font, styles, c.encode_utf8(&mut [0; 4]), span).unwrap()
|
ctx: &MathContext,
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_new(
|
|
||||||
font: &Font,
|
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
str: &str,
|
text: &str,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Option<Self> {
|
) -> SourceResult<Option<Self>> {
|
||||||
|
let families = families(styles);
|
||||||
|
let variant = variant(styles);
|
||||||
|
let fallback = TextElem::fallback_in(styles);
|
||||||
|
let end = text.char_indices().nth(1).map(|(i, _)| i).unwrap_or(text.len());
|
||||||
|
|
||||||
|
// Find the next available family.
|
||||||
|
let world = ctx.engine.world;
|
||||||
|
let book = world.book();
|
||||||
|
let mut selection = None;
|
||||||
|
for family in families {
|
||||||
|
selection = book
|
||||||
|
.select(family.as_str(), variant)
|
||||||
|
.and_then(|id| world.font(id))
|
||||||
|
.filter(|font| {
|
||||||
|
font.ttf().tables().math.and_then(|math| math.constants).is_some()
|
||||||
|
})
|
||||||
|
.filter(|_| family.covers().is_none_or(|cov| cov.is_match(&text[..end])));
|
||||||
|
if selection.is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do font fallback if the families are exhausted and fallback is enabled.
|
||||||
|
if selection.is_none() && fallback {
|
||||||
|
selection = book
|
||||||
|
.select_fallback(None, variant, text)
|
||||||
|
.and_then(|id| world.font(id))
|
||||||
|
.filter(|font| {
|
||||||
|
font.ttf().tables().math.and_then(|math| math.constants).is_some()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error out if no math font could be found at all.
|
||||||
|
let Some(font) = selection else {
|
||||||
|
bail!(span, "current font does not support math");
|
||||||
|
};
|
||||||
|
|
||||||
let mut buffer = UnicodeBuffer::new();
|
let mut buffer = UnicodeBuffer::new();
|
||||||
buffer.push_str(str);
|
buffer.push_str(text);
|
||||||
buffer.set_language(language(styles));
|
buffer.set_language(language(styles));
|
||||||
// TODO: Bug in rustybuzz? rustybuzz::script::SCRIPT_MATH does not work.
|
// TODO: Bug in rustybuzz? rustybuzz::script::SCRIPT_MATH does not work.
|
||||||
// i.e. ssty is not applied
|
// i.e. ssty is not applied
|
||||||
@ -282,7 +318,7 @@ impl GlyphFragment {
|
|||||||
|
|
||||||
let features = features(styles);
|
let features = features(styles);
|
||||||
let plan = create_shape_plan(
|
let plan = create_shape_plan(
|
||||||
font,
|
&font,
|
||||||
buffer.direction(),
|
buffer.direction(),
|
||||||
buffer.script(),
|
buffer.script(),
|
||||||
buffer.language().as_ref(),
|
buffer.language().as_ref(),
|
||||||
@ -290,20 +326,18 @@ impl GlyphFragment {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let buffer = rustybuzz::shape_with_plan(font.rusty(), &plan, buffer);
|
let buffer = rustybuzz::shape_with_plan(font.rusty(), &plan, buffer);
|
||||||
if buffer.len() != 1 {
|
|
||||||
return None;
|
match buffer.len() {
|
||||||
|
0 => return Ok(None),
|
||||||
|
1 => {}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
let info = buffer.glyph_infos()[0];
|
let info = buffer.glyph_infos()[0];
|
||||||
let pos = buffer.glyph_positions()[0];
|
let pos = buffer.glyph_positions()[0];
|
||||||
|
|
||||||
// TODO: add support for coverage and fallback, like in normal text shaping.
|
|
||||||
if info.glyph_id == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cluster = info.cluster as usize;
|
let cluster = info.cluster as usize;
|
||||||
let c = str[cluster..].chars().next().unwrap();
|
let c = text[cluster..].chars().next().unwrap();
|
||||||
let limits = Limits::for_char(c);
|
let limits = Limits::for_char(c);
|
||||||
let class = EquationElem::class_in(styles)
|
let class = EquationElem::class_in(styles)
|
||||||
.or_else(|| default_math_class(c))
|
.or_else(|| default_math_class(c))
|
||||||
@ -316,14 +350,14 @@ impl GlyphFragment {
|
|||||||
stroke: TextElem::stroke_in(styles).map(|s| s.unwrap_or_default()),
|
stroke: TextElem::stroke_in(styles).map(|s| s.unwrap_or_default()),
|
||||||
lang: TextElem::lang_in(styles),
|
lang: TextElem::lang_in(styles),
|
||||||
region: TextElem::region_in(styles),
|
region: TextElem::region_in(styles),
|
||||||
text: str.into(),
|
text: text.into(),
|
||||||
glyphs: vec![Glyph {
|
glyphs: vec![Glyph {
|
||||||
id: info.glyph_id as u16,
|
id: info.glyph_id as u16,
|
||||||
x_advance: font.to_em(pos.x_advance),
|
x_advance: font.to_em(pos.x_advance),
|
||||||
x_offset: Em::zero(),
|
x_offset: Em::zero(),
|
||||||
y_advance: Em::zero(),
|
y_advance: Em::zero(),
|
||||||
y_offset: Em::zero(),
|
y_offset: Em::zero(),
|
||||||
range: 0..str.len() as u16,
|
range: 0..text.len() as u16,
|
||||||
span: (span, 0),
|
span: (span, 0),
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
@ -348,7 +382,7 @@ impl GlyphFragment {
|
|||||||
modifiers: FrameModifiers::get_in(styles),
|
modifiers: FrameModifiers::get_in(styles),
|
||||||
};
|
};
|
||||||
fragment.update_glyph();
|
fragment.update_glyph();
|
||||||
Some(fragment)
|
Ok(Some(fragment))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets element id and boxes in appropriate way without changing other
|
/// Sets element id and boxes in appropriate way without changing other
|
||||||
@ -505,7 +539,7 @@ impl GlyphFragment {
|
|||||||
/// to the given alignment on the axis.
|
/// to the given alignment on the axis.
|
||||||
pub fn align_on_axis(&mut self, align: VAlignment) {
|
pub fn align_on_axis(&mut self, align: VAlignment) {
|
||||||
let h = self.size.y;
|
let h = self.size.y;
|
||||||
let axis = axis_height(&self.text.font).unwrap().at(self.text.size);
|
let axis = value!(self.text, axis_height);
|
||||||
self.align += self.baseline();
|
self.align += self.baseline();
|
||||||
self.baseline = Some(align.inv().position(h + axis * 2.0));
|
self.baseline = Some(align.inv().position(h + axis * 2.0));
|
||||||
self.align -= self.baseline();
|
self.align -= self.baseline();
|
||||||
@ -641,10 +675,6 @@ fn kern_at_height(font: &Font, id: GlyphId, corner: Corner, height: Em) -> Optio
|
|||||||
Some(font.to_em(kern.kern(i)?.value))
|
Some(font.to_em(kern.kern(i)?.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn axis_height(font: &Font) -> Option<Em> {
|
|
||||||
Some(font.to_em(font.ttf().tables().math?.constants?.axis_height().value))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stretch_axes(font: &Font, id: GlyphId) -> Axes<bool> {
|
pub fn stretch_axes(font: &Font, id: GlyphId) -> Axes<bool> {
|
||||||
let horizontal = font
|
let horizontal = font
|
||||||
.ttf()
|
.ttf()
|
||||||
|
@ -5,7 +5,9 @@ use typst_library::math::{EquationElem, LrElem, MidElem};
|
|||||||
use typst_utils::SliceExt;
|
use typst_utils::SliceExt;
|
||||||
use unicode_math_class::MathClass;
|
use unicode_math_class::MathClass;
|
||||||
|
|
||||||
use super::{stretch_fragment, MathContext, MathFragment, DELIM_SHORT_FALL};
|
use super::{
|
||||||
|
find_math_font, stretch_fragment, MathContext, MathFragment, DELIM_SHORT_FALL,
|
||||||
|
};
|
||||||
|
|
||||||
/// Lays out an [`LrElem`].
|
/// Lays out an [`LrElem`].
|
||||||
#[typst_macros::time(name = "math.lr", span = elem.span())]
|
#[typst_macros::time(name = "math.lr", span = elem.span())]
|
||||||
@ -33,7 +35,8 @@ pub fn layout_lr(
|
|||||||
let (start_idx, end_idx) = fragments.split_prefix_suffix(|f| f.is_ignorant());
|
let (start_idx, end_idx) = fragments.split_prefix_suffix(|f| f.is_ignorant());
|
||||||
let inner_fragments = &mut fragments[start_idx..end_idx];
|
let inner_fragments = &mut fragments[start_idx..end_idx];
|
||||||
|
|
||||||
let axis = scaled!(ctx, styles, axis_height);
|
let font = find_math_font(ctx.engine, styles, elem.span())?;
|
||||||
|
let axis = constant!(font, styles, axis_height);
|
||||||
let max_extent = inner_fragments
|
let max_extent = inner_fragments
|
||||||
.iter()
|
.iter()
|
||||||
.map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis))
|
.map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis))
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use typst_library::diag::{bail, warning, SourceResult};
|
use typst_library::diag::{bail, warning, SourceResult};
|
||||||
use typst_library::foundations::{Content, Packed, Resolve, StyleChain};
|
use typst_library::foundations::{Content, Packed, Resolve, StyleChain, SymbolElem};
|
||||||
use typst_library::layout::{
|
use typst_library::layout::{
|
||||||
Abs, Axes, Em, FixedAlignment, Frame, FrameItem, Point, Ratio, Rel, Size,
|
Abs, Axes, Em, FixedAlignment, Frame, FrameItem, Point, Ratio, Rel, Size,
|
||||||
};
|
};
|
||||||
@ -9,8 +9,8 @@ use typst_library::visualize::{FillRule, FixedStroke, Geometry, LineCap, Shape};
|
|||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
alignments, delimiter_alignment, style_for_denominator, AlignmentResult,
|
alignments, delimiter_alignment, find_math_font, style_for_denominator,
|
||||||
FrameFragment, GlyphFragment, LeftRightAlternator, MathContext, DELIM_SHORT_FALL,
|
AlignmentResult, FrameFragment, LeftRightAlternator, MathContext, DELIM_SHORT_FALL,
|
||||||
};
|
};
|
||||||
|
|
||||||
const VERTICAL_PADDING: Ratio = Ratio::new(0.1);
|
const VERTICAL_PADDING: Ratio = Ratio::new(0.1);
|
||||||
@ -184,7 +184,7 @@ fn layout_body(
|
|||||||
// to ensure that normal matrices are aligned with others unless they are
|
// to ensure that normal matrices are aligned with others unless they are
|
||||||
// way too big.
|
// way too big.
|
||||||
let paren =
|
let paren =
|
||||||
GlyphFragment::new(ctx.font, styles.chain(&denom_style), '(', Span::detached());
|
ctx.layout_into_fragment(&SymbolElem::packed('('), styles.chain(&denom_style))?;
|
||||||
|
|
||||||
for (column, col) in columns.iter().zip(&mut cols) {
|
for (column, col) in columns.iter().zip(&mut cols) {
|
||||||
for (cell, (ascent, descent)) in column.iter().zip(&mut heights) {
|
for (cell, (ascent, descent)) in column.iter().zip(&mut heights) {
|
||||||
@ -307,13 +307,14 @@ fn layout_delimiters(
|
|||||||
span: Span,
|
span: Span,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let short_fall = DELIM_SHORT_FALL.resolve(styles);
|
let short_fall = DELIM_SHORT_FALL.resolve(styles);
|
||||||
let axis = scaled!(ctx, styles, axis_height);
|
let font = find_math_font(ctx.engine, styles, span)?;
|
||||||
|
let axis = constant!(font, styles, axis_height);
|
||||||
let height = frame.height();
|
let height = frame.height();
|
||||||
let target = height + VERTICAL_PADDING.of(height);
|
let target = height + VERTICAL_PADDING.of(height);
|
||||||
frame.set_baseline(height / 2.0 + axis);
|
frame.set_baseline(height / 2.0 + axis);
|
||||||
|
|
||||||
if let Some(left_c) = left {
|
if let Some(left_c) = left {
|
||||||
let mut left = GlyphFragment::new(ctx.font, styles, left_c, span);
|
let mut left = ctx.layout_into_glyph(left_c, span, styles)?;
|
||||||
left.stretch_vertical(ctx, target, short_fall);
|
left.stretch_vertical(ctx, target, short_fall);
|
||||||
left.align_on_axis(delimiter_alignment(left_c));
|
left.align_on_axis(delimiter_alignment(left_c));
|
||||||
ctx.push(left);
|
ctx.push(left);
|
||||||
@ -322,7 +323,7 @@ fn layout_delimiters(
|
|||||||
ctx.push(FrameFragment::new(styles, frame));
|
ctx.push(FrameFragment::new(styles, frame));
|
||||||
|
|
||||||
if let Some(right_c) = right {
|
if let Some(right_c) = right {
|
||||||
let mut right = GlyphFragment::new(ctx.font, styles, right_c, span);
|
let mut right = ctx.layout_into_glyph(right_c, span, styles)?;
|
||||||
right.stretch_vertical(ctx, target, short_fall);
|
right.stretch_vertical(ctx, target, short_fall);
|
||||||
right.align_on_axis(delimiter_alignment(right_c));
|
right.align_on_axis(delimiter_alignment(right_c));
|
||||||
ctx.push(right);
|
ctx.push(right);
|
||||||
|
@ -13,7 +13,7 @@ mod stretch;
|
|||||||
mod text;
|
mod text;
|
||||||
mod underover;
|
mod underover;
|
||||||
|
|
||||||
use typst_library::diag::{bail, SourceResult};
|
use typst_library::diag::SourceResult;
|
||||||
use typst_library::engine::Engine;
|
use typst_library::engine::Engine;
|
||||||
use typst_library::foundations::{
|
use typst_library::foundations::{
|
||||||
Content, NativeElement, Packed, Resolve, StyleChain, SymbolElem,
|
Content, NativeElement, Packed, Resolve, StyleChain, SymbolElem,
|
||||||
@ -27,10 +27,7 @@ use typst_library::layout::{
|
|||||||
use typst_library::math::*;
|
use typst_library::math::*;
|
||||||
use typst_library::model::ParElem;
|
use typst_library::model::ParElem;
|
||||||
use typst_library::routines::{Arenas, RealizationKind};
|
use typst_library::routines::{Arenas, RealizationKind};
|
||||||
use typst_library::text::{
|
use typst_library::text::{LinebreakElem, SpaceElem, TextEdgeBounds, TextElem};
|
||||||
families, variant, Font, LinebreakElem, SpaceElem, TextEdgeBounds, TextElem,
|
|
||||||
};
|
|
||||||
use typst_library::World;
|
|
||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
use typst_utils::Numeric;
|
use typst_utils::Numeric;
|
||||||
use unicode_math_class::MathClass;
|
use unicode_math_class::MathClass;
|
||||||
@ -53,12 +50,11 @@ pub fn layout_equation_inline(
|
|||||||
) -> SourceResult<Vec<InlineItem>> {
|
) -> SourceResult<Vec<InlineItem>> {
|
||||||
assert!(!elem.block(styles));
|
assert!(!elem.block(styles));
|
||||||
|
|
||||||
let font = find_math_font(engine, styles, elem.span())?;
|
|
||||||
|
|
||||||
let mut locator = locator.split();
|
let mut locator = locator.split();
|
||||||
let mut ctx = MathContext::new(engine, &mut locator, region, &font);
|
let mut ctx = MathContext::new(engine, &mut locator, region);
|
||||||
|
|
||||||
let scale_style = style_for_script_scale(&ctx);
|
let font = find_math_font(ctx.engine, styles, elem.span())?;
|
||||||
|
let scale_style = style_for_script_scale(&font);
|
||||||
let styles = styles.chain(&scale_style);
|
let styles = styles.chain(&scale_style);
|
||||||
|
|
||||||
let run = ctx.layout_into_run(&elem.body, styles)?;
|
let run = ctx.layout_into_run(&elem.body, styles)?;
|
||||||
@ -108,12 +104,12 @@ pub fn layout_equation_block(
|
|||||||
assert!(elem.block(styles));
|
assert!(elem.block(styles));
|
||||||
|
|
||||||
let span = elem.span();
|
let span = elem.span();
|
||||||
let font = find_math_font(engine, styles, span)?;
|
|
||||||
|
|
||||||
let mut locator = locator.split();
|
let mut locator = locator.split();
|
||||||
let mut ctx = MathContext::new(engine, &mut locator, regions.base(), &font);
|
let mut ctx = MathContext::new(engine, &mut locator, regions.base());
|
||||||
|
|
||||||
let scale_style = style_for_script_scale(&ctx);
|
let font = find_math_font(ctx.engine, styles, elem.span())?;
|
||||||
|
let scale_style = style_for_script_scale(&font);
|
||||||
let styles = styles.chain(&scale_style);
|
let styles = styles.chain(&scale_style);
|
||||||
|
|
||||||
let full_equation_builder = ctx
|
let full_equation_builder = ctx
|
||||||
@ -234,24 +230,6 @@ pub fn layout_equation_block(
|
|||||||
Ok(Fragment::frames(frames))
|
Ok(Fragment::frames(frames))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_math_font(
|
|
||||||
engine: &mut Engine<'_>,
|
|
||||||
styles: StyleChain,
|
|
||||||
span: Span,
|
|
||||||
) -> SourceResult<Font> {
|
|
||||||
let variant = variant(styles);
|
|
||||||
let world = engine.world;
|
|
||||||
let Some(font) = families(styles).find_map(|family| {
|
|
||||||
let id = world.book().select(family.as_str(), variant)?;
|
|
||||||
let font = world.font(id)?;
|
|
||||||
let _ = font.ttf().tables().math?.constants?;
|
|
||||||
Some(font)
|
|
||||||
}) else {
|
|
||||||
bail!(span, "current font does not support math");
|
|
||||||
};
|
|
||||||
Ok(font)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_equation_number(
|
fn add_equation_number(
|
||||||
equation_builder: MathRunFrameBuilder,
|
equation_builder: MathRunFrameBuilder,
|
||||||
number: Frame,
|
number: Frame,
|
||||||
@ -370,9 +348,6 @@ struct MathContext<'a, 'v, 'e> {
|
|||||||
engine: &'v mut Engine<'e>,
|
engine: &'v mut Engine<'e>,
|
||||||
locator: &'v mut SplitLocator<'a>,
|
locator: &'v mut SplitLocator<'a>,
|
||||||
region: Region,
|
region: Region,
|
||||||
// Font-related.
|
|
||||||
font: &'a Font,
|
|
||||||
constants: ttf_parser::math::Constants<'a>,
|
|
||||||
// Mutable.
|
// Mutable.
|
||||||
fragments: Vec<MathFragment>,
|
fragments: Vec<MathFragment>,
|
||||||
}
|
}
|
||||||
@ -383,19 +358,11 @@ impl<'a, 'v, 'e> MathContext<'a, 'v, 'e> {
|
|||||||
engine: &'v mut Engine<'e>,
|
engine: &'v mut Engine<'e>,
|
||||||
locator: &'v mut SplitLocator<'a>,
|
locator: &'v mut SplitLocator<'a>,
|
||||||
base: Size,
|
base: Size,
|
||||||
font: &'a Font,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// These unwraps are safe as the font given is one returned by the
|
|
||||||
// find_math_font function, which only returns fonts that have a math
|
|
||||||
// constants table.
|
|
||||||
let constants = font.ttf().tables().math.unwrap().constants.unwrap();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
engine,
|
engine,
|
||||||
locator,
|
locator,
|
||||||
region: Region::new(base, Axes::splat(false)),
|
region: Region::new(base, Axes::splat(false)),
|
||||||
font,
|
|
||||||
constants,
|
|
||||||
fragments: vec![],
|
fragments: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -434,6 +401,23 @@ impl<'a, 'v, 'e> MathContext<'a, 'v, 'e> {
|
|||||||
Ok(std::mem::replace(&mut self.fragments, prev))
|
Ok(std::mem::replace(&mut self.fragments, prev))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn layout_into_glyph(
|
||||||
|
&mut self,
|
||||||
|
c: char,
|
||||||
|
span: Span,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<GlyphFragment> {
|
||||||
|
let prev = std::mem::take(&mut self.fragments);
|
||||||
|
let elem = SymbolElem::packed(c).spanned(span);
|
||||||
|
self.layout_into_self(&elem, styles)?;
|
||||||
|
let MathFragment::Glyph(glyph) =
|
||||||
|
std::mem::replace(&mut self.fragments, prev).remove(0)
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
Ok(glyph)
|
||||||
|
}
|
||||||
|
|
||||||
/// Layout the given element and return the result as a
|
/// Layout the given element and return the result as a
|
||||||
/// unified [`MathFragment`].
|
/// unified [`MathFragment`].
|
||||||
fn layout_into_fragment(
|
fn layout_into_fragment(
|
||||||
@ -469,15 +453,7 @@ impl<'a, 'v, 'e> MathContext<'a, 'v, 'e> {
|
|||||||
styles,
|
styles,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let outer = styles;
|
|
||||||
for (elem, styles) in pairs {
|
for (elem, styles) in pairs {
|
||||||
// Hack because the font is fixed in math.
|
|
||||||
if styles != outer && TextElem::font_in(styles) != TextElem::font_in(outer) {
|
|
||||||
let frame = layout_external(elem, self, styles)?;
|
|
||||||
self.push(FrameFragment::new(styles, frame).with_spaced(true));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
layout_realized(elem, self, styles)?;
|
layout_realized(elem, self, styles)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,8 +470,9 @@ fn layout_realized(
|
|||||||
if let Some(elem) = elem.to_packed::<TagElem>() {
|
if let Some(elem) = elem.to_packed::<TagElem>() {
|
||||||
ctx.push(MathFragment::Tag(elem.tag.clone()));
|
ctx.push(MathFragment::Tag(elem.tag.clone()));
|
||||||
} else if elem.is::<SpaceElem>() {
|
} else if elem.is::<SpaceElem>() {
|
||||||
let space_width = ctx.font.space_width().unwrap_or(THICK);
|
let font = find_math_font(ctx.engine, styles, elem.span())?;
|
||||||
ctx.push(MathFragment::Space(space_width.resolve(styles)));
|
let space_width = font.space_width().unwrap_or(THICK).resolve(styles);
|
||||||
|
ctx.push(MathFragment::Space(space_width));
|
||||||
} else if elem.is::<LinebreakElem>() {
|
} else if elem.is::<LinebreakElem>() {
|
||||||
ctx.push(MathFragment::Linebreak);
|
ctx.push(MathFragment::Linebreak);
|
||||||
} else if let Some(elem) = elem.to_packed::<HElem>() {
|
} else if let Some(elem) = elem.to_packed::<HElem>() {
|
||||||
@ -565,7 +542,8 @@ fn layout_realized(
|
|||||||
} else {
|
} else {
|
||||||
let mut frame = layout_external(elem, ctx, styles)?;
|
let mut frame = layout_external(elem, ctx, styles)?;
|
||||||
if !frame.has_baseline() {
|
if !frame.has_baseline() {
|
||||||
let axis = scaled!(ctx, styles, axis_height);
|
let font = find_math_font(ctx.engine, styles, elem.span())?;
|
||||||
|
let axis = constant!(font, styles, axis_height);
|
||||||
frame.set_baseline(frame.height() / 2.0 + axis);
|
frame.set_baseline(frame.height() / 2.0 + axis);
|
||||||
}
|
}
|
||||||
ctx.push(
|
ctx.push(
|
||||||
|
@ -2,10 +2,9 @@ use typst_library::diag::SourceResult;
|
|||||||
use typst_library::foundations::{Packed, StyleChain};
|
use typst_library::foundations::{Packed, StyleChain};
|
||||||
use typst_library::layout::{Abs, Frame, FrameItem, Point, Size};
|
use typst_library::layout::{Abs, Frame, FrameItem, Point, Size};
|
||||||
use typst_library::math::{EquationElem, MathSize, RootElem};
|
use typst_library::math::{EquationElem, MathSize, RootElem};
|
||||||
use typst_library::text::TextElem;
|
|
||||||
use typst_library::visualize::{FixedStroke, Geometry};
|
use typst_library::visualize::{FixedStroke, Geometry};
|
||||||
|
|
||||||
use super::{style_cramped, FrameFragment, GlyphFragment, MathContext};
|
use super::{find_math_font, style_cramped, FrameFragment, MathContext};
|
||||||
|
|
||||||
/// Lays out a [`RootElem`].
|
/// Lays out a [`RootElem`].
|
||||||
///
|
///
|
||||||
@ -17,20 +16,8 @@ pub fn layout_root(
|
|||||||
ctx: &mut MathContext,
|
ctx: &mut MathContext,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let index = elem.index(styles);
|
|
||||||
let span = elem.span();
|
let span = elem.span();
|
||||||
|
|
||||||
let gap = scaled!(
|
|
||||||
ctx, styles,
|
|
||||||
text: radical_vertical_gap,
|
|
||||||
display: radical_display_style_vertical_gap,
|
|
||||||
);
|
|
||||||
let thickness = scaled!(ctx, styles, radical_rule_thickness);
|
|
||||||
let extra_ascender = scaled!(ctx, styles, radical_extra_ascender);
|
|
||||||
let kern_before = scaled!(ctx, styles, radical_kern_before_degree);
|
|
||||||
let kern_after = scaled!(ctx, styles, radical_kern_after_degree);
|
|
||||||
let raise_factor = percent!(ctx, radical_degree_bottom_raise_percent);
|
|
||||||
|
|
||||||
// Layout radicand.
|
// Layout radicand.
|
||||||
let radicand = {
|
let radicand = {
|
||||||
let cramped = style_cramped();
|
let cramped = style_cramped();
|
||||||
@ -39,23 +26,41 @@ pub fn layout_root(
|
|||||||
let multiline = run.is_multiline();
|
let multiline = run.is_multiline();
|
||||||
let mut radicand = run.into_fragment(styles).into_frame();
|
let mut radicand = run.into_fragment(styles).into_frame();
|
||||||
if multiline {
|
if multiline {
|
||||||
|
let font = find_math_font(ctx.engine, styles, elem.span())?;
|
||||||
|
let axis = constant!(font, styles, axis_height);
|
||||||
// Align the frame center line with the math axis.
|
// Align the frame center line with the math axis.
|
||||||
radicand.set_baseline(
|
radicand.set_baseline(radicand.height() / 2.0 + axis);
|
||||||
radicand.height() / 2.0 + scaled!(ctx, styles, axis_height),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
radicand
|
radicand
|
||||||
};
|
};
|
||||||
|
|
||||||
// Layout root symbol.
|
// Layout root symbol.
|
||||||
|
let mut sqrt = ctx.layout_into_glyph('√', span, styles)?;
|
||||||
|
let gap = value!(
|
||||||
|
sqrt.text, styles,
|
||||||
|
inline: radical_vertical_gap,
|
||||||
|
display: radical_display_style_vertical_gap,
|
||||||
|
);
|
||||||
|
let thickness = value!(sqrt.text, radical_rule_thickness);
|
||||||
|
let extra_ascender = value!(sqrt.text, radical_extra_ascender);
|
||||||
|
let kern_before = value!(sqrt.text, radical_kern_before_degree);
|
||||||
|
let kern_after = value!(sqrt.text, radical_kern_after_degree);
|
||||||
|
let raise_factor = percent!(sqrt.text, radical_degree_bottom_raise_percent);
|
||||||
|
|
||||||
|
let line = FrameItem::Shape(
|
||||||
|
Geometry::Line(Point::with_x(radicand.width()))
|
||||||
|
.stroked(FixedStroke::from_pair(sqrt.text.fill.clone(), thickness)),
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
|
||||||
let target = radicand.height() + thickness + gap;
|
let target = radicand.height() + thickness + gap;
|
||||||
let mut sqrt = GlyphFragment::new(ctx.font, styles, '√', span);
|
|
||||||
sqrt.stretch_vertical(ctx, target, Abs::zero());
|
sqrt.stretch_vertical(ctx, target, Abs::zero());
|
||||||
let sqrt = sqrt.into_frame();
|
let sqrt = sqrt.into_frame();
|
||||||
|
|
||||||
// Layout the index.
|
// Layout the index.
|
||||||
let sscript = EquationElem::set_size(MathSize::ScriptScript).wrap();
|
let sscript = EquationElem::set_size(MathSize::ScriptScript).wrap();
|
||||||
let index = index
|
let index = elem
|
||||||
|
.index(styles)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|elem| ctx.layout_into_frame(elem, styles.chain(&sscript)))
|
.map(|elem| ctx.layout_into_frame(elem, styles.chain(&sscript)))
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
@ -107,19 +112,7 @@ pub fn layout_root(
|
|||||||
}
|
}
|
||||||
|
|
||||||
frame.push_frame(sqrt_pos, sqrt);
|
frame.push_frame(sqrt_pos, sqrt);
|
||||||
frame.push(
|
frame.push(line_pos, line);
|
||||||
line_pos,
|
|
||||||
FrameItem::Shape(
|
|
||||||
Geometry::Line(Point::with_x(radicand.width())).stroked(
|
|
||||||
FixedStroke::from_pair(
|
|
||||||
TextElem::fill_in(styles).as_decoration(),
|
|
||||||
thickness,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
frame.push_frame(radicand_pos, radicand);
|
frame.push_frame(radicand_pos, radicand);
|
||||||
ctx.push(FrameFragment::new(styles, frame));
|
ctx.push(FrameFragment::new(styles, frame));
|
||||||
|
|
||||||
|
@ -1,59 +1,103 @@
|
|||||||
use ttf_parser::math::MathValue;
|
|
||||||
use ttf_parser::Tag;
|
use ttf_parser::Tag;
|
||||||
|
use typst_library::diag::{bail, SourceResult};
|
||||||
|
use typst_library::engine::Engine;
|
||||||
use typst_library::foundations::{Style, StyleChain};
|
use typst_library::foundations::{Style, StyleChain};
|
||||||
use typst_library::layout::{Abs, Em, FixedAlignment, Frame, Point, Size, VAlignment};
|
use typst_library::layout::{Abs, Em, FixedAlignment, Frame, Point, Size, VAlignment};
|
||||||
use typst_library::math::{EquationElem, MathSize};
|
use typst_library::math::{EquationElem, MathSize};
|
||||||
use typst_library::text::{FontFeatures, TextElem};
|
use typst_library::text::{families, variant, Font, FontFeatures, TextElem};
|
||||||
|
use typst_library::World;
|
||||||
|
use typst_syntax::Span;
|
||||||
use typst_utils::LazyHash;
|
use typst_utils::LazyHash;
|
||||||
|
|
||||||
use super::{LeftRightAlternator, MathContext, MathFragment, MathRun};
|
use super::{LeftRightAlternator, MathFragment, MathRun};
|
||||||
|
|
||||||
macro_rules! scaled {
|
macro_rules! percent {
|
||||||
($ctx:expr, $styles:expr, text: $text:ident, display: $display:ident $(,)?) => {
|
($text:expr, $name:ident) => {
|
||||||
match typst_library::math::EquationElem::size_in($styles) {
|
$text
|
||||||
typst_library::math::MathSize::Display => scaled!($ctx, $styles, $display),
|
.font
|
||||||
_ => scaled!($ctx, $styles, $text),
|
.ttf()
|
||||||
}
|
.tables()
|
||||||
};
|
.math
|
||||||
($ctx:expr, $styles:expr, $name:ident) => {
|
.and_then(|math| math.constants)
|
||||||
$crate::math::Scaled::scaled(
|
.map(|constants| constants.$name())
|
||||||
$ctx.constants.$name(),
|
.unwrap() as f64
|
||||||
$ctx,
|
/ 100.0
|
||||||
typst_library::text::TextElem::size_in($styles),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! percent {
|
macro_rules! word {
|
||||||
($ctx:expr, $name:ident) => {
|
($text:expr, $name:ident) => {
|
||||||
$ctx.constants.$name() as f64 / 100.0
|
$text
|
||||||
|
.font
|
||||||
|
.ttf()
|
||||||
|
.tables()
|
||||||
|
.math
|
||||||
|
.and_then(|math| math.constants)
|
||||||
|
.map(|constants| $text.font.to_em(constants.$name()).at($text.size))
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! value {
|
||||||
|
($text:expr, $styles:expr, inline: $inline:ident, display: $display:ident $(,)?) => {
|
||||||
|
match typst_library::math::EquationElem::size_in($styles) {
|
||||||
|
typst_library::math::MathSize::Display => value!($text, $display),
|
||||||
|
_ => value!($text, $inline),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($text:expr, $name:ident) => {
|
||||||
|
$text
|
||||||
|
.font
|
||||||
|
.ttf()
|
||||||
|
.tables()
|
||||||
|
.math
|
||||||
|
.and_then(|math| math.constants)
|
||||||
|
.map(|constants| $text.font.to_em(constants.$name().value).at($text.size))
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! constant {
|
||||||
|
($font:expr, $styles:expr, text: $text:ident, display: $display:ident $(,)?) => {
|
||||||
|
match typst_library::math::EquationElem::size_in($styles) {
|
||||||
|
typst_library::math::MathSize::Display => constant!($font, $styles, $display),
|
||||||
|
_ => constant!($font, $styles, $text),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($font:expr, $styles:expr, $name:ident) => {
|
||||||
|
typst_library::foundations::Resolve::resolve(
|
||||||
|
$font
|
||||||
|
.ttf()
|
||||||
|
.tables()
|
||||||
|
.math
|
||||||
|
.and_then(|math| math.constants)
|
||||||
|
.map(|constants| $font.to_em(constants.$name().value))
|
||||||
|
.unwrap(),
|
||||||
|
$styles,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How much less high scaled delimiters can be than what they wrap.
|
/// How much less high scaled delimiters can be than what they wrap.
|
||||||
pub const DELIM_SHORT_FALL: Em = Em::new(0.1);
|
pub const DELIM_SHORT_FALL: Em = Em::new(0.1);
|
||||||
|
|
||||||
/// Converts some unit to an absolute length with the current font & font size.
|
pub fn find_math_font(
|
||||||
pub trait Scaled {
|
engine: &mut Engine<'_>,
|
||||||
fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs;
|
styles: StyleChain,
|
||||||
}
|
span: Span,
|
||||||
|
) -> SourceResult<Font> {
|
||||||
impl Scaled for i16 {
|
let variant = variant(styles);
|
||||||
fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
|
let world = engine.world;
|
||||||
ctx.font.to_em(self).at(font_size)
|
let Some(font) = families(styles).find_map(|family| {
|
||||||
}
|
let id = world.book().select(family.as_str(), variant)?;
|
||||||
}
|
let font = world.font(id)?;
|
||||||
|
let _ = font.ttf().tables().math?.constants?;
|
||||||
impl Scaled for u16 {
|
// Take the base font as the "main" math font.
|
||||||
fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
|
family.covers().map_or(Some(font), |_| None)
|
||||||
ctx.font.to_em(self).at(font_size)
|
}) else {
|
||||||
}
|
bail!(span, "current font does not support math");
|
||||||
}
|
};
|
||||||
|
Ok(font)
|
||||||
impl Scaled for MathValue<'_> {
|
|
||||||
fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
|
|
||||||
self.value.scaled(ctx, font_size)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Styles something as cramped.
|
/// Styles something as cramped.
|
||||||
@ -99,10 +143,11 @@ pub fn style_for_denominator(styles: StyleChain) -> [LazyHash<Style>; 2] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Styles to add font constants to the style chain.
|
/// Styles to add font constants to the style chain.
|
||||||
pub fn style_for_script_scale(ctx: &MathContext) -> LazyHash<Style> {
|
pub fn style_for_script_scale(font: &Font) -> LazyHash<Style> {
|
||||||
|
let constants = font.ttf().tables().math.and_then(|math| math.constants).unwrap();
|
||||||
EquationElem::set_script_scale((
|
EquationElem::set_script_scale((
|
||||||
ctx.constants.script_percent_scale_down(),
|
constants.script_percent_scale_down(),
|
||||||
ctx.constants.script_script_percent_scale_down(),
|
constants.script_script_percent_scale_down(),
|
||||||
))
|
))
|
||||||
.wrap()
|
.wrap()
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,8 @@ use unicode_math_class::MathClass;
|
|||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
has_dtls_feat, style_dtls, FrameFragment, GlyphFragment, MathContext, MathFragment,
|
find_math_font, has_dtls_feat, style_dtls, FrameFragment, GlyphFragment, MathContext,
|
||||||
MathRun,
|
MathFragment, MathRun,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Lays out a [`TextElem`].
|
/// Lays out a [`TextElem`].
|
||||||
@ -51,7 +51,8 @@ fn layout_text_lines<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut frame = MathRun::new(fragments).into_frame(styles);
|
let mut frame = MathRun::new(fragments).into_frame(styles);
|
||||||
let axis = scaled!(ctx, styles, axis_height);
|
let font = find_math_font(ctx.engine, styles, span)?;
|
||||||
|
let axis = constant!(font, styles, axis_height);
|
||||||
frame.set_baseline(frame.height() / 2.0 + axis);
|
frame.set_baseline(frame.height() / 2.0 + axis);
|
||||||
Ok(FrameFragment::new(styles, frame))
|
Ok(FrameFragment::new(styles, frame))
|
||||||
}
|
}
|
||||||
@ -70,7 +71,9 @@ fn layout_inline_text(
|
|||||||
let mut fragments = vec![];
|
let mut fragments = vec![];
|
||||||
for unstyled_c in text.chars() {
|
for unstyled_c in text.chars() {
|
||||||
let c = styled_char(styles, unstyled_c, false);
|
let c = styled_char(styles, unstyled_c, false);
|
||||||
let glyph = GlyphFragment::new(ctx.font, styles, c, span);
|
let glyph =
|
||||||
|
GlyphFragment::new(ctx, styles, c.encode_utf8(&mut [0; 4]), span)?
|
||||||
|
.unwrap();
|
||||||
fragments.push(glyph.into());
|
fragments.push(glyph.into());
|
||||||
}
|
}
|
||||||
let frame = MathRun::new(fragments).into_frame(styles);
|
let frame = MathRun::new(fragments).into_frame(styles);
|
||||||
@ -119,46 +122,32 @@ pub fn layout_symbol(
|
|||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
// Switch dotless char to normal when we have the dtls OpenType feature.
|
// Switch dotless char to normal when we have the dtls OpenType feature.
|
||||||
// This should happen before the main styling pass.
|
// This should happen before the main styling pass.
|
||||||
let dtls = style_dtls();
|
// let dtls = style_dtls();
|
||||||
let (unstyled_c, symbol_styles) = match try_dotless(elem.text) {
|
// let (unstyled_c, symbol_styles) = match try_dotless(elem.text) {
|
||||||
Some(c) if has_dtls_feat(ctx.font) => (c, styles.chain(&dtls)),
|
// Some(c) if has_dtls_feat(ctx.font) => (c, styles.chain(&dtls)),
|
||||||
_ => (elem.text, styles),
|
// _ => (elem.text, styles),
|
||||||
|
// };
|
||||||
|
let c = styled_char(styles, elem.text, true);
|
||||||
|
let Some(mut glyph) =
|
||||||
|
GlyphFragment::new(ctx, styles, c.encode_utf8(&mut [0; 4]), elem.span())?
|
||||||
|
else {
|
||||||
|
return Ok(());
|
||||||
};
|
};
|
||||||
let c = styled_char(styles, unstyled_c, true);
|
|
||||||
let fragment: MathFragment = match GlyphFragment::try_new(
|
|
||||||
ctx.font,
|
|
||||||
symbol_styles,
|
|
||||||
c.encode_utf8(&mut [0; 4]),
|
|
||||||
elem.span(),
|
|
||||||
) {
|
|
||||||
Some(mut glyph) => {
|
|
||||||
layout_glyph(&mut glyph, ctx, styles);
|
|
||||||
glyph.into()
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// Not in the math font, fallback to normal inline text layout.
|
|
||||||
// TODO: Should replace this with proper fallback in [`GlyphFragment::try_new`].
|
|
||||||
layout_inline_text(c.encode_utf8(&mut [0; 4]), elem.span(), ctx, styles)?
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ctx.push(fragment);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Layout a [`GlyphFragment`].
|
|
||||||
fn layout_glyph(glyph: &mut GlyphFragment, ctx: &mut MathContext, styles: StyleChain) {
|
|
||||||
let math_size = EquationElem::size_in(styles);
|
let math_size = EquationElem::size_in(styles);
|
||||||
if glyph.class == MathClass::Large {
|
if glyph.class == MathClass::Large {
|
||||||
if math_size == MathSize::Display {
|
if math_size == MathSize::Display {
|
||||||
let height = scaled!(ctx, styles, display_operator_min_height)
|
let height =
|
||||||
.max(SQRT_2 * glyph.size.y);
|
word!(glyph.text, display_operator_min_height).max(SQRT_2 * glyph.size.y);
|
||||||
glyph.stretch_vertical(ctx, height, Abs::zero());
|
glyph.stretch_vertical(ctx, height, Abs::zero());
|
||||||
};
|
};
|
||||||
// TeXbook p 155. Large operators are always vertically centered on the
|
// TeXbook p 155. Large operators are always vertically centered on the
|
||||||
// axis.
|
// axis.
|
||||||
glyph.center_on_axis();
|
glyph.center_on_axis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.push(glyph);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Style the character by selecting the unicode codepoint for italic, bold,
|
/// Style the character by selecting the unicode codepoint for italic, bold,
|
||||||
|
@ -10,8 +10,8 @@ use typst_library::visualize::{FixedStroke, Geometry};
|
|||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
stack, style_cramped, style_for_subscript, style_for_superscript, FrameFragment,
|
find_math_font, stack, style_cramped, style_for_subscript, style_for_superscript,
|
||||||
GlyphFragment, LeftRightAlternator, MathContext, MathRun,
|
FrameFragment, LeftRightAlternator, MathContext, MathRun,
|
||||||
};
|
};
|
||||||
|
|
||||||
const BRACE_GAP: Em = Em::new(0.25);
|
const BRACE_GAP: Em = Em::new(0.25);
|
||||||
@ -206,11 +206,12 @@ fn layout_underoverline(
|
|||||||
position: Position,
|
position: Position,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let (extra_height, content, line_pos, content_pos, baseline, bar_height, line_adjust);
|
let (extra_height, content, line_pos, content_pos, baseline, bar_height, line_adjust);
|
||||||
|
let font = find_math_font(ctx.engine, styles, span)?;
|
||||||
match position {
|
match position {
|
||||||
Position::Under => {
|
Position::Under => {
|
||||||
let sep = scaled!(ctx, styles, underbar_extra_descender);
|
let sep = constant!(font, styles, underbar_extra_descender);
|
||||||
bar_height = scaled!(ctx, styles, underbar_rule_thickness);
|
bar_height = constant!(font, styles, underbar_rule_thickness);
|
||||||
let gap = scaled!(ctx, styles, underbar_vertical_gap);
|
let gap = constant!(font, styles, underbar_vertical_gap);
|
||||||
extra_height = sep + bar_height + gap;
|
extra_height = sep + bar_height + gap;
|
||||||
|
|
||||||
content = ctx.layout_into_fragment(body, styles)?;
|
content = ctx.layout_into_fragment(body, styles)?;
|
||||||
@ -221,9 +222,9 @@ fn layout_underoverline(
|
|||||||
line_adjust = -content.italics_correction();
|
line_adjust = -content.italics_correction();
|
||||||
}
|
}
|
||||||
Position::Over => {
|
Position::Over => {
|
||||||
let sep = scaled!(ctx, styles, overbar_extra_ascender);
|
let sep = constant!(font, styles, overbar_extra_ascender);
|
||||||
bar_height = scaled!(ctx, styles, overbar_rule_thickness);
|
bar_height = constant!(font, styles, overbar_rule_thickness);
|
||||||
let gap = scaled!(ctx, styles, overbar_vertical_gap);
|
let gap = constant!(font, styles, overbar_vertical_gap);
|
||||||
extra_height = sep + bar_height + gap;
|
extra_height = sep + bar_height + gap;
|
||||||
|
|
||||||
let cramped = style_cramped();
|
let cramped = style_cramped();
|
||||||
@ -285,7 +286,7 @@ fn layout_underoverspreader(
|
|||||||
let body = ctx.layout_into_run(body, styles)?;
|
let body = ctx.layout_into_run(body, styles)?;
|
||||||
let body_class = body.class();
|
let body_class = body.class();
|
||||||
let body = body.into_fragment(styles);
|
let body = body.into_fragment(styles);
|
||||||
let mut glyph = GlyphFragment::new(ctx.font, styles, c, span);
|
let mut glyph = ctx.layout_into_glyph(c, span, styles)?;
|
||||||
glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
|
glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
|
||||||
|
|
||||||
let mut rows = vec![];
|
let mut rows = vec![];
|
||||||
|
@ -80,14 +80,11 @@ pub fn sans(
|
|||||||
/// ```example
|
/// ```example
|
||||||
/// #let scr(it) = text(
|
/// #let scr(it) = text(
|
||||||
/// features: ("ss01",),
|
/// features: ("ss01",),
|
||||||
/// box($cal(it)$),
|
/// $cal(it)$,
|
||||||
/// )
|
/// )
|
||||||
///
|
///
|
||||||
/// We establish $cal(P) != scr(P)$.
|
/// We establish $cal(P) != scr(P)$.
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
/// (The box is not conceptually necessary, but unfortunately currently needed
|
|
||||||
/// due to limitations in Typst's text style handling in math.)
|
|
||||||
#[func(title = "Calligraphic", keywords = ["mathcal", "mathscr"])]
|
#[func(title = "Calligraphic", keywords = ["mathcal", "mathscr"])]
|
||||||
pub fn cal(
|
pub fn cal(
|
||||||
/// The content to style.
|
/// The content to style.
|
||||||
|
@ -938,6 +938,7 @@ pub fn families(styles: StyleChain) -> impl Iterator<Item = &FontFamily> + Clone
|
|||||||
"noto color emoji",
|
"noto color emoji",
|
||||||
"apple color emoji",
|
"apple color emoji",
|
||||||
"segoe ui emoji",
|
"segoe ui emoji",
|
||||||
|
"new computer modern math",
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(FontFamily::new)
|
.map(FontFamily::new)
|
||||||
|
Before Width: | Height: | Size: 868 B After Width: | Height: | Size: 884 B |
Before Width: | Height: | Size: 402 B After Width: | Height: | Size: 492 B |
Before Width: | Height: | Size: 611 B After Width: | Height: | Size: 607 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.1 KiB |
@ -179,7 +179,7 @@ $ a0 + a1 + a0_2 \
|
|||||||
#{
|
#{
|
||||||
let var = $x^1$
|
let var = $x^1$
|
||||||
for i in range(24) {
|
for i in range(24) {
|
||||||
var = $var$
|
var = $var$
|
||||||
}
|
}
|
||||||
$var_2$
|
$var_2$
|
||||||
}
|
}
|
||||||
|
@ -196,12 +196,7 @@ $ mat(#1, #(foo: "bar")) $
|
|||||||
|
|
||||||
--- issue-2268-mat-augment-color ---
|
--- issue-2268-mat-augment-color ---
|
||||||
// The augment line should be of the same color as the text
|
// The augment line should be of the same color as the text
|
||||||
#set text(
|
#set text(fill: yellow)
|
||||||
font: "New Computer Modern",
|
|
||||||
lang: "en",
|
|
||||||
fill: yellow,
|
|
||||||
)
|
|
||||||
|
|
||||||
$mat(augment: #1, M, v) arrow.r.squiggly mat(augment: #1, R, b)$
|
$mat(augment: #1, M, v) arrow.r.squiggly mat(augment: #1, R, b)$
|
||||||
|
|
||||||
--- math-mat-delims ---
|
--- math-mat-delims ---
|
||||||
@ -263,7 +258,7 @@ $ mat(a; b; c) mat(a \ b \ c) $
|
|||||||
--- math-mat-vec-cases-unity ---
|
--- math-mat-vec-cases-unity ---
|
||||||
// Test that matrices, vectors, and cases are all laid out the same.
|
// Test that matrices, vectors, and cases are all laid out the same.
|
||||||
$ mat(z_(n_p); a^2)
|
$ mat(z_(n_p); a^2)
|
||||||
vec(z_(n_p), a^2)
|
vec(z_(n_p), a^2)
|
||||||
cases(reverse: #true, delim: \(, z_(n_p), a^2)
|
cases(reverse: #true, delim: \(, z_(n_p), a^2)
|
||||||
cases(delim: \(, z_(n_p), a^2) $
|
cases(delim: \(, z_(n_p), a^2) $
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
--- math-font-fallback ---
|
--- math-font-fallback ---
|
||||||
// Test font fallback.
|
// Test font fallback.
|
||||||
$ よ and 🏳️🌈 $
|
$ よ and "よ" and 🏳️🌈 $
|
||||||
|
|
||||||
--- math-text-color ---
|
--- math-text-color ---
|
||||||
// Test text properties.
|
// Test text properties.
|
||||||
|