summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/typst/src/layout/inline/mod.rs32
-rw-r--r--crates/typst/src/math/equation.rs6
-rw-r--r--crates/typst/src/math/row.rs10
-rw-r--r--tests/ref/issue-3696-equation-rtl.pngbin0 -> 660 bytes
-rw-r--r--tests/ref/math-at-line-end.pngbin0 -> 479 bytes
-rw-r--r--tests/ref/math-at-line-start.pngbin0 -> 476 bytes
-rw-r--r--tests/ref/math-at-par-end.pngbin0 -> 450 bytes
-rw-r--r--tests/ref/math-at-par-start.pngbin0 -> 418 bytes
-rw-r--r--tests/ref/math-consecutive.pngbin0 -> 176 bytes
-rw-r--r--tests/ref/math-linebreaking-empty.pngbin615 -> 664 bytes
-rw-r--r--tests/suite/math/equation.typ5
-rw-r--r--tests/suite/math/interactions.typ20
12 files changed, 61 insertions, 12 deletions
diff --git a/crates/typst/src/layout/inline/mod.rs b/crates/typst/src/layout/inline/mod.rs
index f8b17f46..5a74d3d2 100644
--- a/crates/typst/src/layout/inline/mod.rs
+++ b/crates/typst/src/layout/inline/mod.rs
@@ -102,6 +102,13 @@ type Range = std::ops::Range<usize>;
const SPACING_REPLACE: char = ' '; // Space
const OBJ_REPLACE: char = '\u{FFFC}'; // Object Replacement Character
+// Unicode BiDi control characters.
+const LTR_EMBEDDING: char = '\u{202A}';
+const RTL_EMBEDDING: char = '\u{202B}';
+const POP_EMBEDDING: char = '\u{202C}';
+const LTR_ISOLATE: char = '\u{2066}';
+const POP_ISOLATE: char = '\u{2069}';
+
/// A paragraph representation in which children are already layouted and text
/// is already preshaped.
///
@@ -207,9 +214,12 @@ impl Segment<'_> {
Self::Box(_, frac) => {
(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::Equation(ref par_items) => par_items
+ .iter()
+ .map(MathParItem::text)
+ .chain([LTR_ISOLATE, POP_ISOLATE])
+ .map(char::len_utf8)
+ .sum(),
Self::Meta => 0,
}
}
@@ -228,6 +238,9 @@ enum Item<'a> {
Frame(Frame),
/// Metadata.
Meta(Frame),
+ /// An item that is invisible and needs to be skipped, e.g. a Unicode
+ /// isolate.
+ Skip(char),
}
impl<'a> Item<'a> {
@@ -254,6 +267,7 @@ impl<'a> Item<'a> {
Self::Absolute(_) | Self::Fractional(_, _) => SPACING_REPLACE.len_utf8(),
Self::Frame(_) => OBJ_REPLACE.len_utf8(),
Self::Meta(_) => 0,
+ Self::Skip(c) => c.len_utf8(),
}
}
@@ -264,6 +278,7 @@ impl<'a> Item<'a> {
Self::Absolute(v) => *v,
Self::Frame(frame) => frame.width(),
Self::Fractional(_, _) | Self::Meta(_) => Abs::zero(),
+ Self::Skip(_) => Abs::zero(),
}
}
}
@@ -466,8 +481,8 @@ fn collect<'a>(
if dir != outer_dir {
// Insert "Explicit Directional Embedding".
match dir {
- Dir::LTR => full.push('\u{202A}'),
- Dir::RTL => full.push('\u{202B}'),
+ Dir::LTR => full.push(LTR_EMBEDDING),
+ Dir::RTL => full.push(RTL_EMBEDDING),
_ => {}
}
}
@@ -480,7 +495,7 @@ fn collect<'a>(
if dir != outer_dir {
// Insert "Pop Directional Formatting".
- full.push('\u{202C}');
+ full.push(POP_EMBEDDING);
}
Segment::Text(full.len() - prev)
} else if let Some(elem) = child.to_packed::<HElem>() {
@@ -535,7 +550,9 @@ fn collect<'a>(
let MathParItem::Frame(frame) = item else { continue };
frame.meta(styles, false);
}
+ full.push(LTR_ISOLATE);
full.extend(items.iter().map(MathParItem::text));
+ full.push(POP_ISOLATE);
Segment::Equation(items)
} else if let Some(elem) = child.to_packed::<BoxElem>() {
let frac = elem.width(styles).is_fractional();
@@ -608,6 +625,7 @@ fn prepare<'a>(
}
},
Segment::Equation(par_items) => {
+ items.push(Item::Skip(LTR_ISOLATE));
for item in par_items {
match item {
MathParItem::Space(s) => items.push(Item::Absolute(s)),
@@ -617,6 +635,7 @@ fn prepare<'a>(
}
}
}
+ items.push(Item::Skip(POP_ISOLATE));
}
Segment::Box(elem, _) => {
if let Sizing::Fr(v) = elem.width(styles) {
@@ -1407,6 +1426,7 @@ fn commit(
Item::Frame(frame) | Item::Meta(frame) => {
push(&mut offset, frame.clone());
}
+ Item::Skip(_) => {}
}
}
diff --git a/crates/typst/src/math/equation.rs b/crates/typst/src/math/equation.rs
index eb3fcc4c..947f01f2 100644
--- a/crates/typst/src/math/equation.rs
+++ b/crates/typst/src/math/equation.rs
@@ -223,6 +223,12 @@ impl Packed<EquationElem> {
vec![MathParItem::Frame(run.into_fragment(&ctx, styles).into_frame())]
};
+ // 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())));
+ }
+
for item in &mut items {
let MathParItem::Frame(frame) = item else { continue };
diff --git a/crates/typst/src/math/row.rs b/crates/typst/src/math/row.rs
index 59661f72..6454f491 100644
--- a/crates/typst/src/math/row.rs
+++ b/crates/typst/src/math/row.rs
@@ -3,7 +3,7 @@ use std::iter::once;
use unicode_math_class::MathClass;
use crate::foundations::{Resolve, StyleChain};
-use crate::layout::{Abs, AlignElem, Em, Frame, FrameKind, Point, Size};
+use crate::layout::{Abs, AlignElem, Em, Frame, Point, Size};
use crate::math::{
alignments, scaled_font_size, spacing, EquationElem, FrameFragment, MathContext,
MathFragment, MathParItem, MathSize,
@@ -257,7 +257,7 @@ impl MathRun {
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 mut frame = Frame::soft(Size::zero());
let mut empty = true;
let finalize_frame = |frame: &mut Frame, x, ascent, descent| {
@@ -301,10 +301,8 @@ impl MathRun {
|| (class == 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),
- );
+ let mut frame_prev =
+ std::mem::replace(&mut frame, Frame::soft(Size::zero()));
finalize_frame(&mut frame_prev, x, ascent, descent);
items.push(MathParItem::Frame(frame_prev));
diff --git a/tests/ref/issue-3696-equation-rtl.png b/tests/ref/issue-3696-equation-rtl.png
new file mode 100644
index 00000000..1ebf2dc2
--- /dev/null
+++ b/tests/ref/issue-3696-equation-rtl.png
Binary files differ
diff --git a/tests/ref/math-at-line-end.png b/tests/ref/math-at-line-end.png
new file mode 100644
index 00000000..6e4173a7
--- /dev/null
+++ b/tests/ref/math-at-line-end.png
Binary files differ
diff --git a/tests/ref/math-at-line-start.png b/tests/ref/math-at-line-start.png
new file mode 100644
index 00000000..05221db1
--- /dev/null
+++ b/tests/ref/math-at-line-start.png
Binary files differ
diff --git a/tests/ref/math-at-par-end.png b/tests/ref/math-at-par-end.png
new file mode 100644
index 00000000..dd3393fa
--- /dev/null
+++ b/tests/ref/math-at-par-end.png
Binary files differ
diff --git a/tests/ref/math-at-par-start.png b/tests/ref/math-at-par-start.png
new file mode 100644
index 00000000..d69b214a
--- /dev/null
+++ b/tests/ref/math-at-par-start.png
Binary files differ
diff --git a/tests/ref/math-consecutive.png b/tests/ref/math-consecutive.png
new file mode 100644
index 00000000..63209657
--- /dev/null
+++ b/tests/ref/math-consecutive.png
Binary files differ
diff --git a/tests/ref/math-linebreaking-empty.png b/tests/ref/math-linebreaking-empty.png
index 8fd4dbb9..2b0917a6 100644
--- a/tests/ref/math-linebreaking-empty.png
+++ b/tests/ref/math-linebreaking-empty.png
Binary files differ
diff --git a/tests/suite/math/equation.typ b/tests/suite/math/equation.typ
index d5771f95..d4fba36b 100644
--- a/tests/suite/math/equation.typ
+++ b/tests/suite/math/equation.typ
@@ -224,3 +224,8 @@ $ <quadratic>
// Error: 14-24 cannot reference equation without numbering
// Hint: 14-24 you can enable equation numbering with `#set math.equation(numbering: "1.")`
Looks at the @quadratic formula.
+
+--- issue-3696-equation-rtl ---
+#set page(width: 150pt)
+#set text(lang: "he")
+תהא סדרה $a_n$: $[a_n: 1, 1/2, 1/3, dots]$
diff --git a/tests/suite/math/interactions.typ b/tests/suite/math/interactions.typ
index 03d5a6e7..9a9b13ec 100644
--- a/tests/suite/math/interactions.typ
+++ b/tests/suite/math/interactions.typ
@@ -35,6 +35,26 @@ $#here[f] := #here[Hi there]$.
// Test boxes with a baseline are respected
#box(stroke: 0.2pt, $a #box(baseline:0.5em, stroke: 0.2pt, $a$)$)
+--- math-at-par-start ---
+// Test that equation at start of paragraph works fine.
+$x$ is a variable.
+
+--- math-at-par-end ---
+// Test that equation at end of paragraph works fine.
+One number is $1$
+
+--- math-at-line-start ---
+// Test math at the natural end of a line.
+#h(60pt) Number $1$ exists.
+
+--- math-at-line-end ---
+// Test math at the natural end of a line.
+#h(50pt) Number $1$ exists.
+
+--- math-consecutive ---
+// Test immediately consecutive equations.
+$x$$y$
+
--- issue-2821-missing-fields ---
// Issue #2821: Setting a figure's supplement to none removes the field
#show figure.caption: it => {