summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-08-27 14:36:48 +0200
committerLaurenz <laurmaedje@gmail.com>2024-08-27 14:38:00 +0200
commit7eb3b3f0f7d61116ebe7f7da1952e742d94849fb (patch)
tree335d2351fe545b657c40e905778186dd97920e6a
parentb5ef9244eb2b546b47530d40860e9e04256aafb2 (diff)
Better lists
-rw-r--r--crates/typst/src/layout/sides.rs11
-rw-r--r--crates/typst/src/model/enum.rs128
-rw-r--r--crates/typst/src/model/figure.rs8
-rw-r--r--crates/typst/src/model/list.rs90
-rw-r--r--crates/typst/src/model/outline.rs10
-rw-r--r--crates/typst/src/model/terms.rs112
-rw-r--r--crates/typst/src/realize/mod.rs6
-rw-r--r--crates/typst/src/text/mod.rs6
8 files changed, 195 insertions, 176 deletions
diff --git a/crates/typst/src/layout/sides.rs b/crates/typst/src/layout/sides.rs
index 75d4e25b..1c9125ab 100644
--- a/crates/typst/src/layout/sides.rs
+++ b/crates/typst/src/layout/sides.rs
@@ -41,6 +41,17 @@ impl<T> Sides<T> {
}
}
+ /// Create an instance where a single has the value and all others are
+ /// default.
+ pub fn one(value: T, side: Side) -> Self
+ where
+ T: Default,
+ {
+ let mut this = Self::default();
+ this.set(side, value);
+ this
+ }
+
/// Map the individual fields with `f`.
pub fn map<F, U>(self, mut f: F) -> Sides<U>
where
diff --git a/crates/typst/src/model/enum.rs b/crates/typst/src/model/enum.rs
index 336a8fc9..ac9addd1 100644
--- a/crates/typst/src/model/enum.rs
+++ b/crates/typst/src/model/enum.rs
@@ -6,16 +6,16 @@ use smallvec::{smallvec, SmallVec};
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, scope, Array, Content, Context, NativeElement, Packed, Show, Smart,
- StyleChain, Styles,
+ cast, elem, scope, Array, Content, Context, NativeElement, Packed, Show, ShowSet,
+ Smart, StyleChain, Styles,
};
-use crate::introspection::Locator;
+use crate::introspection::{Locatable, Locator, LocatorLink};
use crate::layout::{
- Alignment, Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment,
- Length, Regions, Sizing, VAlignment, VElem,
+ layout_fragment, layout_frame, Abs, Axes, BlockElem, Em, Fragment, HElem, Length,
+ Region, Regions, Sides, Size, VElem,
};
use crate::model::{Numbering, NumberingPattern, ParElem};
-use crate::text::TextElem;
+use crate::text::{isolate, TextElem};
/// A numbered list.
///
@@ -73,7 +73,7 @@ use crate::text::TextElem;
/// Enumeration items can contain multiple paragraphs and other block-level
/// content. All content that is indented more than an item's marker becomes
/// part of that item.
-#[elem(scope, title = "Numbered List", Show)]
+#[elem(scope, title = "Numbered List", Locatable, Show, ShowSet)]
pub struct EnumElem {
/// If this is `{false}`, the items are spaced apart with
/// [enum spacing]($enum.spacing). If it is `{true}`, they use normal
@@ -146,14 +146,15 @@ pub struct EnumElem {
#[default(false)]
pub full: bool,
- /// The indentation of each item.
+ /// The indentation of the enumeration.
#[resolve]
+ #[default(Em::new(1.75).into())]
pub indent: Length,
- /// The space between the numbering and the body of each item.
+ /// The spacing between the number and the body of each item.
#[resolve]
#[default(Em::new(0.5).into())]
- pub body_indent: Length,
+ pub number_gap: Length,
/// The spacing between the items of the enumeration.
///
@@ -162,32 +163,6 @@ pub struct EnumElem {
/// (non-tight) enumerations.
pub spacing: Smart<Length>,
- /// The alignment that enum numbers should have.
- ///
- /// By default, this is set to `{end + top}`, which aligns enum numbers
- /// towards end of the current text direction (in left-to-right script,
- /// for example, this is the same as `{right}`) and at the top of the line.
- /// The choice of `{end}` for horizontal alignment of enum numbers is
- /// usually preferred over `{start}`, as numbers then grow away from the
- /// text instead of towards it, avoiding certain visual issues. This option
- /// lets you override this behaviour, however. (Also to note is that the
- /// [unordered list]($list) uses a different method for this, by giving the
- /// `marker` content an alignment directly.).
- ///
- /// ````example
- /// #set enum(number-align: start + bottom)
- ///
- /// Here are some powers of two:
- /// 1. One
- /// 2. Two
- /// 4. Four
- /// 8. Eight
- /// 16. Sixteen
- /// 32. Thirty two
- /// ````
- #[default(HAlignment::End + VAlignment::Top)]
- pub number_align: Alignment,
-
/// The numbered list's items.
///
/// When using the enum syntax, adjacent items are automatically collected
@@ -218,7 +193,11 @@ impl EnumElem {
impl Show for Packed<EnumElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ let indent = self.indent(styles);
+ let inset = Sides::one(Some(indent.into()), TextElem::dir_in(styles).start());
+
let mut realized = BlockElem::multi_layouter(self.clone(), layout_enum)
+ .with_inset(inset)
.pack()
.spanned(self.span());
@@ -232,6 +211,14 @@ impl Show for Packed<EnumElem> {
}
}
+impl ShowSet for Packed<EnumElem> {
+ fn show_set(&self, _: StyleChain) -> Styles {
+ let mut out = Styles::new();
+ out.set(ParElem::set_first_line_indent(Length::zero()));
+ out
+ }
+}
+
/// Layout the enumeration.
#[typst_macros::time(span = elem.span())]
fn layout_enum(
@@ -242,9 +229,10 @@ fn layout_enum(
regions: Regions,
) -> SourceResult<Fragment> {
let numbering = elem.numbering(styles);
+ let full = elem.full(styles);
let indent = elem.indent(styles);
- let body_indent = elem.body_indent(styles);
- let gutter = elem.spacing(styles).unwrap_or_else(|| {
+ let number_gap = elem.number_gap(styles);
+ let spacing = elem.spacing(styles).unwrap_or_else(|| {
if elem.tight(styles) {
ParElem::leading_in(styles).into()
} else {
@@ -252,21 +240,19 @@ fn layout_enum(
}
});
- let mut cells = vec![];
- let mut locator = locator.split();
+ let spacing_elem = VElem::block_spacing(spacing.into()).pack();
+ let number_gap_elem = HElem::new(number_gap.into()).pack();
+
let mut number = elem.start(styles);
let mut parents = EnumElem::parents_in(styles);
+ let mut seq = vec![];
- let full = elem.full(styles);
-
- // Horizontally align based on the given respective parameter.
- // Vertically align to the top to avoid inheriting `horizon` or `bottom`
- // alignment from the context and having the number be displaced in
- // relation to the item it refers to.
- let number_align = elem.number_align(styles);
+ for (i, child) in elem.children.iter().enumerate() {
+ if i > 0 {
+ seq.push(spacing_elem.clone());
+ }
- for item in elem.children() {
- number = item.number(styles).unwrap_or(number);
+ number = child.number(styles).unwrap_or(number);
let context = Context::new(None, Some(styles));
let resolved = if full {
@@ -283,34 +269,28 @@ fn layout_enum(
}
};
- // Disable overhang as a workaround to end-aligned dots glitching
- // and decreasing spacing between numbers and items.
- let resolved =
- resolved.aligned(number_align).styled(TextElem::set_overhang(false));
-
- cells.push(Cell::new(Content::empty(), locator.next(&())));
- cells.push(Cell::new(resolved, locator.next(&())));
- cells.push(Cell::new(Content::empty(), locator.next(&())));
- cells.push(Cell::new(
- item.body.clone().styled(EnumElem::set_parents(smallvec![number])),
- locator.next(&item.body.span()),
- ));
+ let number_width = layout_frame(
+ engine,
+ &resolved,
+ Locator::link(&LocatorLink::measure(elem.location().unwrap())),
+ styles,
+ Region::new(Size::splat(Abs::inf()), Axes::splat(false)),
+ )?
+ .width();
+
+ let dedent = -(number_width + number_gap).min(indent);
+ let dedent_elem = HElem::new(dedent.into()).pack();
+
+ seq.push(dedent_elem);
+ isolate(&mut seq, resolved, styles);
+ seq.push(number_gap_elem.clone());
+ seq.push(child.body.clone().styled(EnumElem::set_parents(smallvec![number])));
+
number = number.saturating_add(1);
}
- let grid = CellGrid::new(
- Axes::with_x(&[
- Sizing::Rel(indent.into()),
- Sizing::Auto,
- Sizing::Rel(body_indent.into()),
- Sizing::Auto,
- ]),
- Axes::with_y(&[gutter.into()]),
- cells,
- );
- let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
-
- layouter.layout(engine)
+ let realized = Content::sequence(seq);
+ layout_fragment(engine, &realized, locator, styles, regions)
}
/// An enumeration item.
diff --git a/crates/typst/src/model/figure.rs b/crates/typst/src/model/figure.rs
index 164d1b48..49160d0d 100644
--- a/crates/typst/src/model/figure.rs
+++ b/crates/typst/src/model/figure.rs
@@ -339,10 +339,10 @@ impl ShowSet for Packed<FigureElem> {
fn show_set(&self, _: StyleChain) -> Styles {
// Still allows breakable figures with
// `show figure: set block(breakable: true)`.
- let mut map = Styles::new();
- map.set(BlockElem::set_breakable(false));
- map.set(AlignElem::set_alignment(Alignment::CENTER));
- map
+ let mut out = Styles::new();
+ out.set(BlockElem::set_breakable(false));
+ out.set(AlignElem::set_alignment(Alignment::CENTER));
+ out
}
}
diff --git a/crates/typst/src/model/list.rs b/crates/typst/src/model/list.rs
index a0d76770..0c633f70 100644
--- a/crates/typst/src/model/list.rs
+++ b/crates/typst/src/model/list.rs
@@ -4,15 +4,15 @@ use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Array, Content, Context, Depth, Func, NativeElement, Packed, Show,
- Smart, StyleChain, Styles, Value,
+ ShowSet, Smart, StyleChain, Styles, Value,
};
-use crate::introspection::Locator;
+use crate::introspection::{Locatable, Locator, LocatorLink};
use crate::layout::{
- Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment, Length,
- Regions, Sizing, VAlignment, VElem,
+ layout_fragment, layout_frame, Abs, Axes, BlockElem, Em, Fragment, HElem, Length,
+ Region, Regions, Sides, Size, VElem,
};
use crate::model::ParElem;
-use crate::text::TextElem;
+use crate::text::{isolate, TextElem};
/// A bullet list.
///
@@ -45,7 +45,7 @@ use crate::text::TextElem;
/// followed by a space to create a list item. A list item can contain multiple
/// paragraphs and other block-level content. All content that is indented
/// more than an item's marker becomes part of that item.
-#[elem(scope, title = "Bullet List", Show)]
+#[elem(scope, title = "Bullet List", Locatable, Show, ShowSet)]
pub struct ListElem {
/// If this is `{false}`, the items are spaced apart with
/// [list spacing]($list.spacing). If it is `{true}`, they use normal
@@ -98,14 +98,15 @@ pub struct ListElem {
]))]
pub marker: ListMarker,
- /// The indent of each item.
+ /// The indentation of the list.
#[resolve]
+ #[default(Em::new(1.75).into())]
pub indent: Length,
/// The spacing between the marker and the body of each item.
#[resolve]
#[default(Em::new(0.5).into())]
- pub body_indent: Length,
+ pub marker_gap: Length,
/// The spacing between the items of the list.
///
@@ -142,7 +143,11 @@ impl ListElem {
impl Show for Packed<ListElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ let indent = self.indent(styles);
+ let inset = Sides::one(Some(indent.into()), TextElem::dir_in(styles).start());
+
let mut realized = BlockElem::multi_layouter(self.clone(), layout_list)
+ .with_inset(inset)
.pack()
.spanned(self.span());
@@ -156,6 +161,14 @@ impl Show for Packed<ListElem> {
}
}
+impl ShowSet for Packed<ListElem> {
+ fn show_set(&self, _: StyleChain) -> Styles {
+ let mut out = Styles::new();
+ out.set(ParElem::set_first_line_indent(Length::zero()));
+ out
+ }
+}
+
/// Layout the list.
#[typst_macros::time(span = elem.span())]
fn layout_list(
@@ -165,9 +178,11 @@ fn layout_list(
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
+ let Depth(depth) = ListElem::depth_in(styles);
+ let marker = elem.marker(styles).resolve(engine, styles, depth)?;
let indent = elem.indent(styles);
- let body_indent = elem.body_indent(styles);
- let gutter = elem.spacing(styles).unwrap_or_else(|| {
+ let marker_gap = elem.marker_gap(styles);
+ let spacing = elem.spacing(styles).unwrap_or_else(|| {
if elem.tight(styles) {
ParElem::leading_in(styles).into()
} else {
@@ -175,39 +190,36 @@ fn layout_list(
}
});
- let Depth(depth) = ListElem::depth_in(styles);
- let marker = elem
- .marker(styles)
- .resolve(engine, styles, depth)?
- // avoid '#set align' interference with the list
- .aligned(HAlignment::Start + VAlignment::Top);
+ let marker_width = layout_frame(
+ engine,
+ &marker,
+ Locator::link(&LocatorLink::measure(elem.location().unwrap())),
+ styles,
+ Region::new(Size::splat(Abs::inf()), Axes::splat(false)),
+ )?
+ .width();
- let mut cells = vec![];
- let mut locator = locator.split();
+ let dedent = -(marker_width + marker_gap).min(indent);
- for item in elem.children() {
- cells.push(Cell::new(Content::empty(), locator.next(&())));
- cells.push(Cell::new(marker.clone(), locator.next(&marker.span())));
- cells.push(Cell::new(Content::empty(), locator.next(&())));
- cells.push(Cell::new(
- item.body.clone().styled(ListElem::set_depth(Depth(1))),
- locator.next(&item.body.span()),
- ));
- }
+ let spacing_elem = VElem::block_spacing(spacing.into()).pack();
+ let dedent_elem = HElem::new(dedent.into()).pack();
+ let marker_gap_elem = HElem::new(marker_gap.into()).pack();
- let grid = CellGrid::new(
- Axes::with_x(&[
- Sizing::Rel(indent.into()),
- Sizing::Auto,
- Sizing::Rel(body_indent.into()),
- Sizing::Auto,
- ]),
- Axes::with_y(&[gutter.into()]),
- cells,
- );
- let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
+ let mut seq = vec![];
+ for (i, child) in elem.children.iter().enumerate() {
+ if i > 0 {
+ seq.push(spacing_elem.clone());
+ }
+
+ seq.push(dedent_elem.clone());
+ isolate(&mut seq, marker.clone(), styles);
+ seq.push(marker_gap_elem.clone());
+ seq.push(child.body.clone());
+ }
- layouter.layout(engine)
+ let depth = ListElem::set_depth(Depth(1)).wrap();
+ let realized = Content::sequence(seq);
+ layout_fragment(engine, &realized, locator, styles.chain(&depth), regions)
}
/// A bullet list item.
diff --git a/crates/typst/src/model/outline.rs b/crates/typst/src/model/outline.rs
index b90c4e3b..adae5216 100644
--- a/crates/typst/src/model/outline.rs
+++ b/crates/typst/src/model/outline.rs
@@ -10,12 +10,12 @@ use crate::foundations::{
NativeElement, Packed, Show, ShowSet, Smart, StyleChain, Styles,
};
use crate::introspection::{Counter, CounterKey, Locatable};
-use crate::layout::{BoxElem, Em, Fr, HElem, HideElem, Length, Rel, RepeatElem, Spacing};
+use crate::layout::{BoxElem, Fr, HElem, HideElem, Length, Rel, RepeatElem, Spacing};
use crate::model::{
Destination, HeadingElem, NumberingPattern, ParElem, ParbreakElem, Refable,
};
use crate::syntax::Span;
-use crate::text::{LinebreakElem, LocalName, SpaceElem, TextElem};
+use crate::text::{isolate, LinebreakElem, LocalName, SpaceElem, TextElem};
use crate::utils::NonZeroExt;
/// A table of contents, figures, or other elements.
@@ -263,7 +263,7 @@ impl ShowSet for Packed<OutlineElem> {
let mut out = Styles::new();
out.set(HeadingElem::set_outlined(false));
out.set(HeadingElem::set_numbering(None));
- out.set(ParElem::set_first_line_indent(Em::new(0.0).into()));
+ out.set(ParElem::set_first_line_indent(Length::zero()));
out
}
}
@@ -500,10 +500,10 @@ impl Show for Packed<OutlineEntry> {
};
// The body text remains overridable.
- crate::text::isolate(
+ isolate(
+ &mut seq,
self.body().clone().linked(Destination::Location(location)),
styles,
- &mut seq,
);
// Add filler symbols between the section name and page number.
diff --git a/crates/typst/src/model/terms.rs b/crates/typst/src/model/terms.rs
index 98fe3ff1..f44d3fce 100644
--- a/crates/typst/src/model/terms.rs
+++ b/crates/typst/src/model/terms.rs
@@ -1,13 +1,15 @@
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain,
- Styles,
+ cast, elem, scope, Array, Content, NativeElement, Packed, Show, ShowSet, Smart,
+ StyleChain, Styles,
+};
+use crate::introspection::{Locatable, Locator};
+use crate::layout::{
+ layout_fragment, BlockElem, Em, Fragment, HElem, Length, Regions, Sides, VElem,
};
-use crate::layout::{Dir, Em, HElem, Length, Sides, StackChild, StackElem, VElem};
use crate::model::ParElem;
-use crate::text::TextElem;
-use crate::utils::Numeric;
+use crate::text::{isolate, TextElem};
/// A list of terms and their descriptions.
///
@@ -25,7 +27,7 @@ use crate::utils::Numeric;
/// # Syntax
/// This function also has dedicated syntax: Starting a line with a slash,
/// followed by a term, a colon and a description creates a term list item.
-#[elem(scope, title = "Term List", Show)]
+#[elem(scope, title = "Term List", Locatable, Show, ShowSet)]
pub struct TermsElem {
/// If this is `{false}`, the items are spaced apart with
/// [term list spacing]($terms.spacing). If it is `{true}`, they use normal
@@ -65,20 +67,15 @@ pub struct TermsElem {
#[borrowed]
pub separator: Content,
- /// The indentation of each item.
- pub indent: Length,
-
- /// The hanging indent of the description.
- ///
- /// This is in addition to the whole item's `indent`.
+ /// The indentation of the term list.
///
/// ```example
- /// #set terms(hanging-indent: 0pt)
+ /// #set terms(indent: 0pt)
/// / Term: This term list does not
- /// make use of hanging indents.
+ /// make use of indents.
/// ```
- #[default(Em::new(2.0).into())]
- pub hanging_indent: Length,
+ #[default(Em::new(1.75).into())]
+ pub indent: Length,
/// The spacing between the items of the term list.
///
@@ -111,42 +108,13 @@ impl TermsElem {
impl Show for Packed<TermsElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- let separator = self.separator(styles);
let indent = self.indent(styles);
- let hanging_indent = self.hanging_indent(styles);
- let gutter = self.spacing(styles).unwrap_or_else(|| {
- if self.tight(styles) {
- ParElem::leading_in(styles).into()
- } else {
- ParElem::spacing_in(styles).into()
- }
- });
-
- let pad = hanging_indent + indent;
- let unpad = (!hanging_indent.is_zero())
- .then(|| HElem::new((-hanging_indent).into()).pack());
-
- let mut children = vec![];
- for child in self.children().iter() {
- let mut seq = vec![];
- seq.extend(unpad.clone());
- seq.push(child.term().clone().strong());
- seq.push((*separator).clone());
- seq.push(child.description().clone());
- children.push(StackChild::Block(Content::sequence(seq)));
- }
+ let inset = Sides::one(Some(indent.into()), TextElem::dir_in(styles).start());
- let mut padding = Sides::default();
- if TextElem::dir_in(styles) == Dir::LTR {
- padding.left = pad.into();
- } else {
- padding.right = pad.into();
- }
-
- let mut realized = StackElem::new(children)
- .with_spacing(Some(gutter.into()))
+ let mut realized = BlockElem::multi_layouter(self.clone(), layout_terms)
+ .with_inset(inset)
.pack()
- .padded(padding);
+ .spanned(self.span());
if self.tight(styles) {
let leading = ParElem::leading_in(styles);
@@ -158,6 +126,52 @@ impl Show for Packed<TermsElem> {
}
}
+impl ShowSet for Packed<TermsElem> {
+ fn show_set(&self, _: StyleChain) -> Styles {
+ let mut out = Styles::new();
+ out.set(ParElem::set_first_line_indent(Length::zero()));
+ out
+ }
+}
+
+/// Layout the term list.
+#[typst_macros::time(span = elem.span())]
+fn layout_terms(
+ elem: &Packed<TermsElem>,
+ engine: &mut Engine,
+ locator: Locator,
+ styles: StyleChain,
+ regions: Regions,
+) -> SourceResult<Fragment> {
+ let separator = elem.separator(styles);
+ let indent = elem.indent(styles);
+ let spacing = elem.spacing(styles).unwrap_or_else(|| {
+ if elem.tight(styles) {
+ ParElem::leading_in(styles).into()
+ } else {
+ ParElem::spacing_in(styles).into()
+ }
+ });
+
+ let spacing_elem = VElem::block_spacing(spacing.into()).pack();
+ let dedent_elem = HElem::new((-indent).into()).pack();
+
+ let mut seq = vec![];
+ for (i, child) in elem.children.iter().enumerate() {
+ if i > 0 {
+ seq.push(spacing_elem.clone());
+ }
+
+ seq.push(dedent_elem.clone());
+ isolate(&mut seq, child.term().clone().strong(), styles);
+ seq.push(separator.clone());
+ seq.push(child.description().clone());
+ }
+
+ let realized = Content::sequence(seq);
+ layout_fragment(engine, &realized, locator, styles, regions)
+}
+
/// A term list item.
#[elem(name = "item", title = "Term List Item")]
pub struct TermItem {
diff --git a/crates/typst/src/realize/mod.rs b/crates/typst/src/realize/mod.rs
index a154fce1..56954942 100644
--- a/crates/typst/src/realize/mod.rs
+++ b/crates/typst/src/realize/mod.rs
@@ -207,8 +207,10 @@ impl<'a, 'v> Builder<'a, 'v> {
.store(VElem::par_spacing(ParElem::spacing_in(styles).into()).pack())
});
- if content.is::<TagElem>()
- || content.is::<PlaceElem>()
+ if content.is::<TagElem>() {
+ self.sink.push(content, styles);
+ self.last_was_par = last_was_par;
+ } else if content.is::<PlaceElem>()
|| content.is::<FlushElem>()
|| content.is::<ColbreakElem>()
{
diff --git a/crates/typst/src/text/mod.rs b/crates/typst/src/text/mod.rs
index d1e841af..b641b743 100644
--- a/crates/typst/src/text/mod.rs
+++ b/crates/typst/src/text/mod.rs
@@ -1319,12 +1319,12 @@ cast! {
},
}
-/// Pushes `text` wrapped in LRE/RLE + PDF to `out`.
-pub(crate) fn isolate(text: Content, styles: StyleChain, out: &mut Vec<Content>) {
+/// Pushes `content` wrapped in LRE/RLE + PDF to `out`.
+pub(crate) fn isolate(out: &mut Vec<Content>, content: Content, styles: StyleChain) {
out.push(TextElem::packed(match TextElem::dir_in(styles) {
Dir::RTL => "\u{202B}",
_ => "\u{202A}",
}));
- out.push(text);
+ out.push(content);
out.push(TextElem::packed("\u{202C}"));
}