summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-05-17 10:36:07 +0200
committerGitHub <noreply@github.com>2024-05-17 08:36:07 +0000
commit2188a4bf48d802b516f4721a02a7b080edc8f46d (patch)
treebaacf34d2a79b5ff117aa7e0552703a984f7bed3 /crates
parent4ae376f2c7ba9621479640afbe45936c6c859cff (diff)
Fix equations in RTL text (#4150)
Diffstat (limited to 'crates')
-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
3 files changed, 36 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));