mirror of
https://github.com/typst/typst
synced 2025-05-20 03:55:29 +08:00
Inline equations linebreak at appropriate places (#2938)
Co-authored-by: David Maxwell <damaxwell@alaska.edu> Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
parent
3aeb150c95
commit
34e3bd52aa
@ -19,7 +19,7 @@ use crate::layout::{
|
|||||||
Abs, AlignElem, Axes, BoxElem, Dir, Em, FixedAlign, Fr, Fragment, Frame, HElem,
|
Abs, AlignElem, Axes, BoxElem, Dir, Em, FixedAlign, Fr, Fragment, Frame, HElem,
|
||||||
Layout, Point, Regions, Size, Sizing, Spacing,
|
Layout, Point, Regions, Size, Sizing, Spacing,
|
||||||
};
|
};
|
||||||
use crate::math::EquationElem;
|
use crate::math::{EquationElem, MathParItem};
|
||||||
use crate::model::{Linebreaks, ParElem};
|
use crate::model::{Linebreaks, ParElem};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::text::{
|
use crate::text::{
|
||||||
@ -61,7 +61,8 @@ pub(crate) fn layout_inline(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Collect all text into one string for BiDi analysis.
|
// Collect all text into one string for BiDi analysis.
|
||||||
let (text, segments, spans) = collect(children, &styles, consecutive)?;
|
let (text, segments, spans) =
|
||||||
|
collect(children, &mut engine, &styles, region, consecutive)?;
|
||||||
|
|
||||||
// Perform BiDi analysis and then prepare paragraph layout by building a
|
// Perform BiDi analysis and then prepare paragraph layout by building a
|
||||||
// representation on which we can do line breaking without layouting
|
// representation on which we can do line breaking without layouting
|
||||||
@ -180,7 +181,7 @@ impl<'a> Preparation<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A segment of one or multiple collapsed children.
|
/// A segment of one or multiple collapsed children.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Segment<'a> {
|
enum Segment<'a> {
|
||||||
/// One or multiple collapsed text or text-equivalent children. Stores how
|
/// One or multiple collapsed text or text-equivalent children. Stores how
|
||||||
/// long the segment is (in bytes of the full text string).
|
/// long the segment is (in bytes of the full text string).
|
||||||
@ -188,7 +189,7 @@ enum Segment<'a> {
|
|||||||
/// Horizontal spacing between other segments.
|
/// Horizontal spacing between other segments.
|
||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// A mathematical equation.
|
/// A mathematical equation.
|
||||||
Equation(&'a EquationElem),
|
Equation(&'a EquationElem, Vec<MathParItem>),
|
||||||
/// A box with arbitrary content.
|
/// A box with arbitrary content.
|
||||||
Box(&'a BoxElem, bool),
|
Box(&'a BoxElem, bool),
|
||||||
/// Metadata.
|
/// Metadata.
|
||||||
@ -201,8 +202,12 @@ impl Segment<'_> {
|
|||||||
match *self {
|
match *self {
|
||||||
Self::Text(len) => len,
|
Self::Text(len) => len,
|
||||||
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
|
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
|
||||||
Self::Box(_, true) => SPACING_REPLACE.len_utf8(),
|
Self::Box(_, frac) => {
|
||||||
Self::Equation(_) | Self::Box(_, _) => OBJ_REPLACE.len_utf8(),
|
(if frac { SPACING_REPLACE } else { OBJ_REPLACE }).len_utf8()
|
||||||
|
}
|
||||||
|
Self::Equation(_, ref par_items) => {
|
||||||
|
par_items.iter().map(MathParItem::text).map(char::len_utf8).sum()
|
||||||
|
}
|
||||||
Self::Meta => 0,
|
Self::Meta => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -395,12 +400,14 @@ impl<'a> Line<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collect all text of the paragraph into one string. This also performs
|
/// Collect all text of the paragraph into one string and layout equations. This
|
||||||
/// string-level preprocessing like case transformations.
|
/// also performs string-level preprocessing like case transformations.
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn collect<'a>(
|
fn collect<'a>(
|
||||||
children: &'a [Prehashed<Content>],
|
children: &'a [Prehashed<Content>],
|
||||||
|
engine: &mut Engine<'_>,
|
||||||
styles: &'a StyleChain<'a>,
|
styles: &'a StyleChain<'a>,
|
||||||
|
region: Size,
|
||||||
consecutive: bool,
|
consecutive: bool,
|
||||||
) -> SourceResult<(String, Vec<(Segment<'a>, StyleChain<'a>)>, SpanMapper)> {
|
) -> SourceResult<(String, Vec<(Segment<'a>, StyleChain<'a>)>, SpanMapper)> {
|
||||||
let mut full = String::new();
|
let mut full = String::new();
|
||||||
@ -493,8 +500,10 @@ fn collect<'a>(
|
|||||||
}
|
}
|
||||||
Segment::Text(full.len() - prev)
|
Segment::Text(full.len() - prev)
|
||||||
} else if let Some(elem) = child.to::<EquationElem>() {
|
} else if let Some(elem) = child.to::<EquationElem>() {
|
||||||
full.push(OBJ_REPLACE);
|
let pod = Regions::one(region, Axes::splat(false));
|
||||||
Segment::Equation(elem)
|
let items = elem.layout_inline(engine, styles, pod)?;
|
||||||
|
full.extend(items.iter().map(MathParItem::text));
|
||||||
|
Segment::Equation(elem, items)
|
||||||
} else if let Some(elem) = child.to::<BoxElem>() {
|
} else if let Some(elem) = child.to::<BoxElem>() {
|
||||||
let frac = elem.width(styles).is_fractional();
|
let frac = elem.width(styles).is_fractional();
|
||||||
full.push(if frac { SPACING_REPLACE } else { OBJ_REPLACE });
|
full.push(if frac { SPACING_REPLACE } else { OBJ_REPLACE });
|
||||||
@ -512,7 +521,7 @@ fn collect<'a>(
|
|||||||
spans.push(segment.len(), child.span());
|
spans.push(segment.len(), child.span());
|
||||||
|
|
||||||
if let (Some((Segment::Text(last_len), last_styles)), Segment::Text(len)) =
|
if let (Some((Segment::Text(last_len), last_styles)), Segment::Text(len)) =
|
||||||
(segments.last_mut(), segment)
|
(segments.last_mut(), &segment)
|
||||||
{
|
{
|
||||||
if *last_styles == styles {
|
if *last_styles == styles {
|
||||||
*last_len += len;
|
*last_len += len;
|
||||||
@ -526,8 +535,7 @@ fn collect<'a>(
|
|||||||
Ok((full, segments, spans))
|
Ok((full, segments, spans))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare paragraph layout by shaping the whole paragraph and layouting all
|
/// Prepare paragraph layout by shaping the whole paragraph.
|
||||||
/// contained inline-level content.
|
|
||||||
fn prepare<'a>(
|
fn prepare<'a>(
|
||||||
engine: &mut Engine,
|
engine: &mut Engine,
|
||||||
children: &'a [Prehashed<Content>],
|
children: &'a [Prehashed<Content>],
|
||||||
@ -566,12 +574,17 @@ fn prepare<'a>(
|
|||||||
items.push(Item::Fractional(v, None));
|
items.push(Item::Fractional(v, None));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Segment::Equation(equation) => {
|
Segment::Equation(_, par_items) => {
|
||||||
let pod = Regions::one(region, Axes::splat(false));
|
for item in par_items {
|
||||||
let mut frame = equation.layout(engine, styles, pod)?.into_frame();
|
match item {
|
||||||
|
MathParItem::Space(s) => items.push(Item::Absolute(s)),
|
||||||
|
MathParItem::Frame(mut frame) => {
|
||||||
frame.translate(Point::with_y(TextElem::baseline_in(styles)));
|
frame.translate(Point::with_y(TextElem::baseline_in(styles)));
|
||||||
items.push(Item::Frame(frame));
|
items.push(Item::Frame(frame));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Segment::Box(elem, _) => {
|
Segment::Box(elem, _) => {
|
||||||
if let Sizing::Fr(v) = elem.width(styles) {
|
if let Sizing::Fr(v) = elem.width(styles) {
|
||||||
items.push(Item::Fractional(v, Some((elem, styles))));
|
items.push(Item::Fractional(v, Some((elem, styles))));
|
||||||
|
@ -140,6 +140,11 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
self.fragments.extend(fragments);
|
self.fragments.extend(fragments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn layout_root(&mut self, elem: &dyn LayoutMath) -> SourceResult<MathRow> {
|
||||||
|
let row = self.layout_fragments(elem)?;
|
||||||
|
Ok(MathRow::new(row))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn layout_fragment(
|
pub fn layout_fragment(
|
||||||
&mut self,
|
&mut self,
|
||||||
elem: &dyn LayoutMath,
|
elem: &dyn LayoutMath,
|
||||||
|
@ -8,13 +8,14 @@ use crate::foundations::{
|
|||||||
};
|
};
|
||||||
use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
|
use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, Align, AlignElem, Axes, Dir, Em, FixedAlign, Fragment, Layout, Point, Regions,
|
Abs, Align, AlignElem, Axes, Dir, Em, FixedAlign, Fragment, Frame, Layout, Point,
|
||||||
Size,
|
Regions, Size,
|
||||||
};
|
};
|
||||||
use crate::math::{LayoutMath, MathContext};
|
use crate::math::{LayoutMath, MathContext};
|
||||||
use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement};
|
use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement};
|
||||||
|
use crate::syntax::Span;
|
||||||
use crate::text::{
|
use crate::text::{
|
||||||
families, variant, FontFamily, FontList, FontWeight, Lang, LocalName, Region,
|
families, variant, Font, FontFamily, FontList, FontWeight, Lang, LocalName, Region,
|
||||||
TextElem,
|
TextElem,
|
||||||
};
|
};
|
||||||
use crate::util::{option_eq, NonZeroExt, Numeric};
|
use crate::util::{option_eq, NonZeroExt, Numeric};
|
||||||
@ -136,6 +137,66 @@ impl Finalize for EquationElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Layouted items suitable for placing in a paragraph.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum MathParItem {
|
||||||
|
Space(Abs),
|
||||||
|
Frame(Frame),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MathParItem {
|
||||||
|
/// The text representation of this item.
|
||||||
|
pub fn text(&self) -> char {
|
||||||
|
match self {
|
||||||
|
MathParItem::Space(_) => ' ', // Space
|
||||||
|
MathParItem::Frame(_) => '\u{FFFC}', // Object Replacement Character
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EquationElem {
|
||||||
|
pub fn layout_inline(
|
||||||
|
&self,
|
||||||
|
engine: &mut Engine<'_>,
|
||||||
|
styles: StyleChain,
|
||||||
|
regions: Regions,
|
||||||
|
) -> SourceResult<Vec<MathParItem>> {
|
||||||
|
assert!(!self.block(styles));
|
||||||
|
|
||||||
|
// Find a math font.
|
||||||
|
let font = find_math_font(engine, styles, self.span())?;
|
||||||
|
|
||||||
|
let mut ctx = MathContext::new(engine, styles, regions, &font, false);
|
||||||
|
let rows = ctx.layout_root(self)?;
|
||||||
|
|
||||||
|
let mut items = if rows.row_count() == 1 {
|
||||||
|
rows.into_par_items()
|
||||||
|
} else {
|
||||||
|
vec![MathParItem::Frame(rows.into_fragment(&ctx).into_frame())]
|
||||||
|
};
|
||||||
|
|
||||||
|
for item in &mut items {
|
||||||
|
let MathParItem::Frame(frame) = item else { continue };
|
||||||
|
|
||||||
|
let font_size = TextElem::size_in(styles);
|
||||||
|
let slack = ParElem::leading_in(styles) * 0.7;
|
||||||
|
let top_edge = TextElem::top_edge_in(styles).resolve(font_size, &font, None);
|
||||||
|
let bottom_edge =
|
||||||
|
-TextElem::bottom_edge_in(styles).resolve(font_size, &font, None);
|
||||||
|
|
||||||
|
let ascent = top_edge.max(frame.ascent() - slack);
|
||||||
|
let descent = bottom_edge.max(frame.descent() - slack);
|
||||||
|
frame.translate(Point::with_y(ascent - frame.baseline()));
|
||||||
|
frame.size_mut().y = ascent + descent;
|
||||||
|
|
||||||
|
// Apply metadata.
|
||||||
|
frame.meta(styles, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Layout for EquationElem {
|
impl Layout for EquationElem {
|
||||||
#[typst_macros::time(name = "math.equation", span = self.span())]
|
#[typst_macros::time(name = "math.equation", span = self.span())]
|
||||||
fn layout(
|
fn layout(
|
||||||
@ -146,24 +207,14 @@ impl Layout for EquationElem {
|
|||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
const NUMBER_GUTTER: Em = Em::new(0.5);
|
const NUMBER_GUTTER: Em = Em::new(0.5);
|
||||||
|
|
||||||
let block = self.block(styles);
|
assert!(self.block(styles));
|
||||||
|
|
||||||
// Find a math font.
|
// Find a math font.
|
||||||
let variant = variant(styles);
|
let font = find_math_font(engine, styles, self.span())?;
|
||||||
let world = engine.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 {
|
|
||||||
bail!(self.span(), "current font does not support math");
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ctx = MathContext::new(engine, styles, regions, &font, block);
|
let mut ctx = MathContext::new(engine, styles, regions, &font, true);
|
||||||
let mut frame = ctx.layout_frame(self)?;
|
let mut frame = ctx.layout_frame(self)?;
|
||||||
|
|
||||||
if block {
|
|
||||||
if let Some(numbering) = self.numbering(styles) {
|
if let Some(numbering) = self.numbering(styles) {
|
||||||
let pod = Regions::one(regions.base(), Axes::splat(false));
|
let pod = Regions::one(regions.base(), Axes::splat(false));
|
||||||
let counter = Counter::of(Self::elem())
|
let counter = Counter::of(Self::elem())
|
||||||
@ -199,18 +250,6 @@ impl Layout for EquationElem {
|
|||||||
|
|
||||||
frame.push_frame(Point::new(x, y), counter)
|
frame.push_frame(Point::new(x, y), counter)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
let font_size = TextElem::size_in(styles);
|
|
||||||
let slack = ParElem::leading_in(styles) * 0.7;
|
|
||||||
let top_edge = TextElem::top_edge_in(styles).resolve(font_size, &font, None);
|
|
||||||
let bottom_edge =
|
|
||||||
-TextElem::bottom_edge_in(styles).resolve(font_size, &font, None);
|
|
||||||
|
|
||||||
let ascent = top_edge.max(frame.ascent() - slack);
|
|
||||||
let descent = bottom_edge.max(frame.descent() - slack);
|
|
||||||
frame.translate(Point::with_y(ascent - frame.baseline()));
|
|
||||||
frame.size_mut().y = ascent + descent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply metadata.
|
// Apply metadata.
|
||||||
frame.meta(styles, false);
|
frame.meta(styles, false);
|
||||||
@ -316,3 +355,21 @@ impl LayoutMath for EquationElem {
|
|||||||
self.body().layout_math(ctx)
|
self.body().layout_math(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, 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)
|
||||||
|
}
|
||||||
|
@ -3,10 +3,10 @@ use std::iter::once;
|
|||||||
use unicode_math_class::MathClass;
|
use unicode_math_class::MathClass;
|
||||||
|
|
||||||
use crate::foundations::Resolve;
|
use crate::foundations::Resolve;
|
||||||
use crate::layout::{Abs, AlignElem, Em, FixedAlign, Frame, Point, Size};
|
use crate::layout::{Abs, AlignElem, Em, FixedAlign, Frame, FrameKind, Point, Size};
|
||||||
use crate::math::{
|
use crate::math::{
|
||||||
alignments, spacing, AlignmentResult, FrameFragment, MathContext, MathFragment,
|
alignments, spacing, AlignmentResult, FrameFragment, MathContext, MathFragment,
|
||||||
MathSize, Scaled,
|
MathParItem, MathSize, Scaled,
|
||||||
};
|
};
|
||||||
use crate::model::ParElem;
|
use crate::model::ParElem;
|
||||||
|
|
||||||
@ -103,6 +103,19 @@ impl MathRow {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn row_count(&self) -> usize {
|
||||||
|
let mut count =
|
||||||
|
1 + self.0.iter().filter(|f| matches!(f, MathFragment::Linebreak)).count();
|
||||||
|
|
||||||
|
// A linebreak at the very end does not introduce an extra row.
|
||||||
|
if let Some(f) = self.0.last() {
|
||||||
|
if matches!(f, MathFragment::Linebreak) {
|
||||||
|
count -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ascent(&self) -> Abs {
|
pub fn ascent(&self) -> Abs {
|
||||||
self.iter().map(MathFragment::ascent).max().unwrap_or_default()
|
self.iter().map(MathFragment::ascent).max().unwrap_or_default()
|
||||||
}
|
}
|
||||||
@ -239,6 +252,85 @@ impl MathRow {
|
|||||||
frame.size_mut().x = x;
|
frame.size_mut().x = x;
|
||||||
frame
|
frame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_par_items(self) -> Vec<MathParItem> {
|
||||||
|
let mut items = vec![];
|
||||||
|
|
||||||
|
let mut x = Abs::zero();
|
||||||
|
let mut ascent = Abs::zero();
|
||||||
|
let mut descent = Abs::zero();
|
||||||
|
let mut frame = Frame::new(Size::zero(), FrameKind::Soft);
|
||||||
|
|
||||||
|
let finalize_frame = |frame: &mut Frame, x, ascent, descent| {
|
||||||
|
frame.set_size(Size::new(x, ascent + descent));
|
||||||
|
frame.set_baseline(Abs::zero());
|
||||||
|
frame.translate(Point::with_y(ascent));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut space_is_visible = false;
|
||||||
|
|
||||||
|
let is_relation =
|
||||||
|
|f: &MathFragment| matches!(f.class(), Some(MathClass::Relation));
|
||||||
|
let is_space = |f: &MathFragment| {
|
||||||
|
matches!(f, MathFragment::Space(_) | MathFragment::Spacing(_))
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut iter = self.0.into_iter().peekable();
|
||||||
|
while let Some(fragment) = iter.next() {
|
||||||
|
if space_is_visible {
|
||||||
|
match fragment {
|
||||||
|
MathFragment::Space(s) | MathFragment::Spacing(s) => {
|
||||||
|
items.push(MathParItem::Space(s));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let class = fragment.class();
|
||||||
|
let y = fragment.ascent();
|
||||||
|
|
||||||
|
ascent.set_max(y);
|
||||||
|
descent.set_max(fragment.descent());
|
||||||
|
|
||||||
|
let pos = Point::new(x, -y);
|
||||||
|
x += fragment.width();
|
||||||
|
frame.push_frame(pos, fragment.into_frame());
|
||||||
|
|
||||||
|
if class == Some(MathClass::Binary)
|
||||||
|
|| (class == Some(MathClass::Relation)
|
||||||
|
&& !iter.peek().map(is_relation).unwrap_or_default())
|
||||||
|
{
|
||||||
|
let mut frame_prev = std::mem::replace(
|
||||||
|
&mut frame,
|
||||||
|
Frame::new(Size::zero(), FrameKind::Soft),
|
||||||
|
);
|
||||||
|
|
||||||
|
finalize_frame(&mut frame_prev, x, ascent, descent);
|
||||||
|
items.push(MathParItem::Frame(frame_prev));
|
||||||
|
|
||||||
|
x = Abs::zero();
|
||||||
|
ascent = Abs::zero();
|
||||||
|
descent = Abs::zero();
|
||||||
|
|
||||||
|
space_is_visible = true;
|
||||||
|
if let Some(f_next) = iter.peek() {
|
||||||
|
if !is_space(f_next) {
|
||||||
|
items.push(MathParItem::Space(Abs::zero()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
space_is_visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !frame.is_empty() {
|
||||||
|
finalize_frame(&mut frame, x, ascent, descent);
|
||||||
|
items.push(MathParItem::Frame(frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Into<MathFragment>> From<T> for MathRow {
|
impl<T: Into<MathFragment>> From<T> for MathRow {
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
BIN
tests/ref/math/linebreak.png
Normal file
BIN
tests/ref/math/linebreak.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test automatic matching.
|
// Test automatic matching.
|
||||||
|
#set page(width:122pt)
|
||||||
$ (a) + {b/2} + abs(a)/2 + (b) $
|
$ (a) + {b/2} + abs(a)/2 + (b) $
|
||||||
$f(x/2) < zeta(c^2 + abs(a + b/2))$
|
$f(x/2) < zeta(c^2 + abs(a + b/2))$
|
||||||
|
|
||||||
|
50
tests/typ/math/linebreak.typ
Normal file
50
tests/typ/math/linebreak.typ
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Test inline equation line breaking.
|
||||||
|
|
||||||
|
---
|
||||||
|
// Basic breaking after binop, rel
|
||||||
|
#let hrule(x) = box(line(length: x))
|
||||||
|
#hrule(45pt)$e^(pi i)+1 = 0$\
|
||||||
|
#hrule(55pt)$e^(pi i)+1 = 0$\
|
||||||
|
#hrule(70pt)$e^(pi i)+1 = 0$
|
||||||
|
|
||||||
|
---
|
||||||
|
// LR groups prevent linbreaking.
|
||||||
|
#let hrule(x) = box(line(length: x))
|
||||||
|
#hrule(76pt)$a+b$\
|
||||||
|
#hrule(74pt)$(a+b)$\
|
||||||
|
#hrule(74pt)$paren.l a+b paren.r$
|
||||||
|
|
||||||
|
---
|
||||||
|
// Multiline yet inline does not linebreak
|
||||||
|
#let hrule(x) = box(line(length: x))
|
||||||
|
#hrule(80pt)$a + b \ c + d$\
|
||||||
|
|
||||||
|
---
|
||||||
|
// A single linebreak at the end still counts as one line.
|
||||||
|
#let hrule(x) = box(line(length: x))
|
||||||
|
#hrule(60pt)$e^(pi i)+1 = 0\ $
|
||||||
|
|
||||||
|
---
|
||||||
|
// Inline, in a box, doesn't linebreak.
|
||||||
|
#let hrule(x) = box(line(length: x))
|
||||||
|
#hrule(80pt)#box($a+b$)
|
||||||
|
|
||||||
|
---
|
||||||
|
// A relation followed by a relation doesn't linebreak
|
||||||
|
#let hrule(x) = box(line(length: x))
|
||||||
|
#hrule(70pt)$a < = b$\
|
||||||
|
#hrule(74pt)$a < = b$
|
||||||
|
|
||||||
|
---
|
||||||
|
// Page breaks can happen after a relation even if there is no
|
||||||
|
// explicit space.
|
||||||
|
#let hrule(x) = box(line(length: x))
|
||||||
|
#hrule(90pt)$<;$\
|
||||||
|
#hrule(95pt)$<;$\
|
||||||
|
#hrule(90pt)$<)$\
|
||||||
|
#hrule(95pt)$<)$
|
||||||
|
|
||||||
|
---
|
||||||
|
// Verify empty rows are handled ok.
|
||||||
|
$ $\
|
||||||
|
Nothing: $ $, just empty.
|
@ -3,7 +3,7 @@
|
|||||||
---
|
---
|
||||||
// Test spacing cases.
|
// Test spacing cases.
|
||||||
$ä, +, c, (, )$ \
|
$ä, +, c, (, )$ \
|
||||||
$=), (+), {times}$
|
$=), (+), {times}$ \
|
||||||
$⟧<⟦, abs(-), [=$ \
|
$⟧<⟦, abs(-), [=$ \
|
||||||
$a=b, a==b$ \
|
$a=b, a==b$ \
|
||||||
$-a, +a$ \
|
$-a, +a$ \
|
||||||
|
Loading…
x
Reference in New Issue
Block a user