Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support highlight, overline, underline, and strike for math equations #3953

Draft
wants to merge 28 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a55cb73
trim trailing space
LuxxxLucy May 7, 2024
5d167b4
update relation linebreak
LuxxxLucy May 7, 2024
adad118
update bidi whitespace reset
LuxxxLucy May 7, 2024
8818c63
update to only trim positive space
LuxxxLucy May 7, 2024
03cef07
update multiple line break test cases for binary relation and operator
LuxxxLucy May 7, 2024
90ec343
Revert "update to only trim positive space"
LuxxxLucy May 14, 2024
aa166e4
Revert "update bidi whitespace reset"
LuxxxLucy May 14, 2024
9211506
add weak bool to Segment::Spacing and Item::Absolute
LuxxxLucy May 14, 2024
9a5d8a1
trim the weak space at the beginnig or the end of the line
LuxxxLucy May 14, 2024
ffe7be5
Update test case
LuxxxLucy May 14, 2024
4c28744
support highlight for inline equation
LuxxxLucy Apr 16, 2024
592bd50
support highlight for block equation
LuxxxLucy Apr 16, 2024
d485bef
Update decoration test case for inline and block math equations
LuxxxLucy Apr 16, 2024
ee3b482
do not in_place modify frame in decoration function call
LuxxxLucy Apr 17, 2024
d5a23c6
update highlight stroke ref png
LuxxxLucy Apr 17, 2024
a75e007
add decorations together
LuxxxLucy Apr 17, 2024
c22cb6f
update comment
LuxxxLucy Apr 18, 2024
78e1045
refactor decorate_frame
LuxxxLucy Apr 18, 2024
3e6c603
support underline overline strike for frame
LuxxxLucy Apr 18, 2024
275d181
update test case
LuxxxLucy Apr 18, 2024
7dbb215
Update documentation.
LuxxxLucy Apr 18, 2024
c5e8512
Address review comments, handling decorations for different frames
LuxxxLucy Apr 19, 2024
6db2865
Revert "update highlight stroke ref png"
LuxxxLucy Apr 19, 2024
aa3422b
Update test case
LuxxxLucy Apr 19, 2024
c065cbf
Address code review
LuxxxLucy Apr 19, 2024
ff8af4c
remove multiple call of get_vertical_offset
LuxxxLucy Apr 19, 2024
fc75560
add test case inline math multiline
LuxxxLucy May 7, 2024
3446de0
WIP
LuxxxLucy May 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 30 additions & 14 deletions crates/typst/src/layout/inline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ enum Segment<'a> {
/// One or multiple collapsed text or text-equivalent children. Stores how
/// long the segment is (in bytes of the full text string).
Text(usize),
/// Horizontal spacing between other segments.
Spacing(Spacing),
/// Horizontal spacing between other segments. Bool when true indicate weak space
Spacing(Spacing, bool),
/// A mathematical equation.
Equation(Vec<MathParItem>),
/// A box with arbitrary content.
Expand All @@ -210,7 +210,7 @@ impl Segment<'_> {
fn len(&self) -> usize {
match *self {
Self::Text(len) => len,
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
Self::Spacing(_, _) => SPACING_REPLACE.len_utf8(),
Self::Box(_, frac) => {
(if frac { SPACING_REPLACE } else { OBJ_REPLACE }).len_utf8()
}
Expand All @@ -231,7 +231,7 @@ enum Item<'a> {
/// A shaped text run with consistent style and direction.
Text(ShapedText<'a>),
/// Absolute spacing between other items.
Absolute(Abs),
Absolute(Abs, bool),
/// Fractional spacing between other items.
Fractional(Fr, Option<(&'a Packed<BoxElem>, StyleChain<'a>)>),
/// Layouted inline-level content.
Expand Down Expand Up @@ -264,7 +264,7 @@ impl<'a> Item<'a> {
fn len(&self) -> usize {
match self {
Self::Text(shaped) => shaped.text.len(),
Self::Absolute(_) | Self::Fractional(_, _) => SPACING_REPLACE.len_utf8(),
Self::Absolute(_, _) | Self::Fractional(_, _) => SPACING_REPLACE.len_utf8(),
Self::Frame(_) => OBJ_REPLACE.len_utf8(),
Self::Tag(_) => 0,
Self::Skip(c) => c.len_utf8(),
Expand All @@ -275,7 +275,7 @@ impl<'a> Item<'a> {
fn width(&self) -> Abs {
match self {
Self::Text(shaped) => shaped.width,
Self::Absolute(v) => *v,
Self::Absolute(v, _) => *v,
Self::Frame(frame) => frame.width(),
Self::Fractional(_, _) | Self::Tag(_) => Abs::zero(),
Self::Skip(_) => Abs::zero(),
Expand Down Expand Up @@ -453,13 +453,13 @@ fn collect<'a>(
== TextElem::dir_in(*styles).start().into()
{
full.push(SPACING_REPLACE);
segments.push((Segment::Spacing(first_line_indent.into()), *styles));
segments.push((Segment::Spacing(first_line_indent.into(), false), *styles));
}

let hang = ParElem::hanging_indent_in(*styles);
if !hang.is_zero() {
full.push(SPACING_REPLACE);
segments.push((Segment::Spacing((-hang).into()), *styles));
segments.push((Segment::Spacing((-hang).into(), false), *styles));
}

let outer_dir = TextElem::dir_in(*styles);
Expand Down Expand Up @@ -504,7 +504,7 @@ fn collect<'a>(
}

full.push(SPACING_REPLACE);
Segment::Spacing(*elem.amount())
Segment::Spacing(*elem.amount(), elem.weak(styles))
} else if let Some(elem) = child.to_packed::<LinebreakElem>() {
let c = if elem.justify(styles) { '\u{2028}' } else { '\n' };
full.push(c);
Expand Down Expand Up @@ -618,10 +618,10 @@ fn prepare<'a>(
Segment::Text(_) => {
shape_range(&mut items, engine, &bidi, cursor..end, &spans, styles);
}
Segment::Spacing(spacing) => match spacing {
Segment::Spacing(spacing, weak) => match spacing {
Spacing::Rel(v) => {
let resolved = v.resolve(styles).relative_to(region.x);
items.push(Item::Absolute(resolved));
items.push(Item::Absolute(resolved, weak));
}
Spacing::Fr(v) => {
items.push(Item::Fractional(v, None));
Expand All @@ -631,7 +631,8 @@ fn prepare<'a>(
items.push(Item::Skip(LTR_ISOLATE));
for item in par_items {
match item {
MathParItem::Space(s) => items.push(Item::Absolute(s)),
// MathParItem space are assumed to be weak space
MathParItem::Space(s) => items.push(Item::Absolute(s, true)),
MathParItem::Frame(mut frame) => {
frame.translate(Point::with_y(TextElem::baseline_in(styles)));
items.push(Item::Frame(frame));
Expand Down Expand Up @@ -1079,9 +1080,24 @@ fn line<'a>(
}

// Slice out the relevant items.
let (expanded, mut inner) = p.slice(range.clone());
let (mut expanded, mut inner) = p.slice(range.clone());
let mut width = Abs::zero();

// Weak space (Absolute(_, weak=true)) would be removed if at the end of the line
while let Some((Item::Absolute(_, true), before)) = inner.split_last() {
// apply it recursively to ensure the last one is not space
inner = before;
range.end -= 1;
expanded.end -= 1;
}
// Weak space (Absolute(_, weak=true)) would be removed if at the beginning of the line
while let Some((Item::Absolute(_, true), after)) = inner.split_first() {
// apply it recursively to ensure the last one is not space
inner = after;
range.start += 1;
expanded.end += 1;
}

// Reshape the last item if it's split in half or hyphenated.
let mut last = None;
let mut dash = None;
Expand Down Expand Up @@ -1402,7 +1418,7 @@ fn commit(
};

match item {
Item::Absolute(v) => {
Item::Absolute(v, _) => {
offset += *v;
}
Item::Fractional(v, elem) => {
Expand Down
6 changes: 3 additions & 3 deletions crates/typst/src/layout/inline/shaping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use crate::foundations::StyleChain;
use crate::layout::{Abs, Dir, Em, Frame, FrameItem, Point, Size};
use crate::syntax::Span;
use crate::text::{
decorate, families, features, variant, Font, FontVariant, Glyph, Lang, Region,
TextElem, TextItem,
decorate_shaped_text, families, features, variant, Font, FontVariant, Glyph, Lang,
Region, TextElem, TextItem,
};
use crate::utils::SliceExt;
use crate::World;
Expand Down Expand Up @@ -320,7 +320,7 @@ impl<'a> ShapedText<'a> {
// Apply line decorations.
frame.push(pos, FrameItem::Text(item.clone()));
for deco in &decos {
decorate(&mut frame, deco, &item, width, shift, pos);
decorate_shaped_text(&mut frame, deco, &item, width, shift, pos);
}
}

Expand Down
116 changes: 114 additions & 2 deletions crates/typst/src/math/equation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::math::{
use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement};
use crate::syntax::Span;
use crate::text::{
decorate_frame,
families, variant, Font, FontFamily, FontList, FontWeight, LocalName, TextElem,
};
use crate::utils::{NonZeroExt, Numeric};
Expand Down Expand Up @@ -222,13 +223,101 @@ impl Packed<EquationElem> {
} else {
vec![MathParItem::Frame(run.into_fragment(&ctx, styles).into_frame())]
};
let mut pos_and_sizes: Vec<(Point, Size)> = Vec::new();
let mut x = Abs::zero();

// An empty equation should have a height, so we still create a frame
// (which is then resized in the loop).
if items.is_empty() {
items.push(MathParItem::Frame(Frame::soft(Size::zero())));
}

// A list of the vetical shift for each MathPatItem(frame), except space.
let vertical_shift_list: Vec<Option<Abs>> = items
.iter()
.map(|item| match item {
MathParItem::Frame(frame) => {
// determine the vertical shift for a frame from MathParItem
let font_size = scaled_font_size(&ctx, styles);
let slack = ParElem::leading_in(styles) * 0.7;
let top_edge =
TextElem::top_edge_in(styles).resolve(font_size, &font, None);
let ascent = top_edge.max(frame.ascent() - slack);
Some(ascent - frame.baseline())
}
_ => None,
})
.collect();

let mut first_non_space_idx: Option<usize> = None;
let mut last_non_space_idx: Option<usize> = None;
for (idx, item) in items.iter().enumerate() {
match item {
MathParItem::Frame(frame) => {
// determine the coordinates of the frame in the MathParItem Array
let y = vertical_shift_list[idx].unwrap();
let pos = Point::new(x, y);
let size = Size::new(frame.width().abs(), frame.height().abs());
pos_and_sizes.push((pos, size));
x += frame.width();
if first_non_space_idx.is_none() {
first_non_space_idx = Some(idx);
}
last_non_space_idx = Some(idx);
}
MathParItem::Space(space_width) => {
// A MarhParItem can also be space (consumes a width), in the latter
// case, we need to update the running x-coordinate
// also note that we compute starting from the first non-space MathParItem
if idx != 0 {
x += *space_width;
}
}
}
}
let first_non_space_idx = first_non_space_idx.unwrap_or(0);
let last_non_space_idx = last_non_space_idx.unwrap_or(0);
// computing the origin position and the size of the bounding box of the entire MathParItem
// Array.
let (pos, size) = compute_bounding_box(&pos_and_sizes);
let decos = TextElem::deco_in(styles);
let mut last_frame: Option<&mut Frame> = None;
let mut last_frame_shift: Option<Abs> = None;
for (idx, item) in items.iter_mut().enumerate() {
// Skip the spaces in the start and the end. But the space in between frames will be
// decorated as well. But space has no corresponding frame, so we will keep a reference
// to the previous non-space frame and add decorations to it.
if idx < first_non_space_idx || idx > last_non_space_idx {
continue;
}
match item {
MathParItem::Frame(ref mut frame) => {
let (size, shift) = (
Size::new(frame.width(), size.to_point().y),
vertical_shift_list[idx].unwrap(),
);
for deco in &decos {
decorate_frame(frame, deco, pos, size, shift);
}
last_frame = Some(frame);
last_frame_shift = Some(shift);
}
MathParItem::Space(_) => {}
// MathParItem::Space(width) => {
// if let Some(ref mut frame) = last_frame {
// let (size, shift) = (
// Size::new(*width, size.to_point().y),
// last_frame_shift.unwrap(),
// );
// let new_pos = pos + Point::new(frame.width(), Abs::zero());
// for deco in &decos {
// decorate_frame(frame, deco, new_pos, size, shift);
// }
// }
// }
};
}

for item in &mut items {
let MathParItem::Frame(frame) = item else { continue };

Expand All @@ -248,6 +337,27 @@ impl Packed<EquationElem> {
}
}

/// Computes the origin position and the size of the bounding box that covers
/// a list of boxes from left to right
fn compute_bounding_box(pos_and_sizes: &[(Point, Size)]) -> (Point, Size) {
let mut start_pos_x = Abs::inf();
let mut start_pos_y = Abs::inf();
let mut size_x = Abs::zero();
let mut size_y = Abs::zero();

for (p, s) in pos_and_sizes {
let s = s.to_point();

start_pos_x.set_min(p.x);
start_pos_y.set_min(p.y);

size_x.set_max(p.x + s.x);
size_y.set_max(s.y)
}

(Point::new(start_pos_x, start_pos_y), Size::new(size_x, size_y))
}

impl LayoutSingle for Packed<EquationElem> {
#[typst_macros::time(name = "math.equation", span = self.span())]
fn layout(
Expand All @@ -267,7 +377,7 @@ impl LayoutSingle for Packed<EquationElem> {
.multiline_frame_builder(&ctx, styles);

let Some(numbering) = (**self).numbering(styles) else {
return Ok(equation_builder.build());
return Ok(equation_builder.build(styles));
};

let pod = Regions::one(regions.base(), Axes::splat(false));
Expand All @@ -293,6 +403,7 @@ impl LayoutSingle for Packed<EquationElem> {
AlignElem::alignment_in(styles).resolve(styles).x,
regions.size.x,
full_number_width,
styles,
);

Ok(frame)
Expand Down Expand Up @@ -394,6 +505,7 @@ fn add_equation_number(
equation_align: FixedAlignment,
region_size_x: Abs,
full_number_width: Abs,
styles: StyleChain,
) -> Frame {
let first =
equation_builder.frames.first().map_or(
Expand All @@ -406,7 +518,7 @@ fn add_equation_number(
|(frame, pos)| (frame.size(), *pos, frame.baseline()),
);
let line_count = equation_builder.frames.len();
let mut equation = equation_builder.build();
let mut equation = equation_builder.build(styles);

let width = if region_size_x.is_finite() {
region_size_x
Expand Down
10 changes: 8 additions & 2 deletions crates/typst/src/math/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::math::{
MathFragment, MathParItem, MathSize,
};
use crate::model::ParElem;
use crate::text::{decorate_frame, TextElem};

use super::fragment::SpacingFragment;

Expand Down Expand Up @@ -142,7 +143,7 @@ impl MathRun {
if !self.is_multiline() {
self.into_line_frame(&[], LeftRightAlternator::Right)
} else {
self.multiline_frame_builder(ctx, styles).build()
self.multiline_frame_builder(ctx, styles).build(styles)
}
}

Expand Down Expand Up @@ -378,11 +379,16 @@ pub struct MathRunFrameBuilder {

impl MathRunFrameBuilder {
/// Consumes the builder and returns a [`Frame`].
pub fn build(self) -> Frame {
pub fn build(self, styles: StyleChain) -> Frame {
let mut frame = Frame::soft(self.size);
for (sub, pos) in self.frames.into_iter() {
frame.push_frame(pos, sub);
}
let decos = TextElem::deco_in(styles);
let size = frame.size();
for deco in &decos {
decorate_frame(&mut frame, deco, Point::zero(), size, Abs::zero());
}
frame
}
}