Align set rule

This commit is contained in:
Laurenz 2022-12-09 10:21:11 +01:00
parent 495b525694
commit cd089b6194
27 changed files with 90 additions and 154 deletions

View File

@ -1,62 +1,22 @@
use super::{HorizontalAlign, ParNode};
use crate::prelude::*; use crate::prelude::*;
/// Align content along the layouting axes. /// Just an empty shell to scope styles.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct AlignNode { pub enum AlignNode {}
/// How to align the content horizontally and vertically.
pub aligns: Axes<Option<GenAlign>>,
/// The content to be aligned.
pub body: Content,
}
#[node(Layout)] #[node]
impl AlignNode { impl AlignNode {
/// The alignment.
#[property(fold, skip)]
pub const ALIGNS: Axes<Option<GenAlign>> =
Axes::new(GenAlign::Start, GenAlign::Specific(Align::Top));
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
args.expect("body")
}
fn set(...) {
let aligns: Axes<Option<GenAlign>> = args.find()?.unwrap_or_default(); let aligns: Axes<Option<GenAlign>> = args.find()?.unwrap_or_default();
let body: Content = args.expect("body")?; styles.set(Self::ALIGNS, aligns);
if let Axes { x: Some(x), y: None } = aligns {
if !body.has::<dyn Layout>() || body.has::<dyn Inline>() {
return Ok(body.styled(ParNode::ALIGN, HorizontalAlign(x)));
}
}
Ok(Self { aligns, body }.pack())
}
}
impl Layout for AlignNode {
fn layout(
&self,
vt: &mut Vt,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
// The child only needs to expand along an axis if there's no alignment.
let mut pod = regions.clone();
pod.expand &= self.aligns.as_ref().map(Option::is_none);
// Align paragraphs inside the child.
let mut map = StyleMap::new();
if let Some(align) = self.aligns.x {
map.set(ParNode::ALIGN, HorizontalAlign(align));
}
// Layout the child.
let mut fragment = self.body.layout(vt, styles.chain(&map), pod)?;
for (region, frame) in regions.iter().zip(&mut fragment) {
// Align in the target size. The target size depends on whether we
// should expand.
let target = regions.expand.select(region, frame.size());
let aligns = self
.aligns
.map(|align| align.resolve(styles))
.unwrap_or(Axes::new(Align::Left, Align::Top));
frame.resize(target, aligns);
}
Ok(fragment)
} }
} }

View File

@ -125,7 +125,7 @@ impl<'a> FlowLayouter<'a> {
par: &ParNode, par: &ParNode,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<()> { ) -> SourceResult<()> {
let aligns = Axes::new(styles.get(ParNode::ALIGN), Align::Top); let aligns = styles.get(AlignNode::ALIGNS).resolve(styles);
let leading = styles.get(ParNode::LEADING); let leading = styles.get(ParNode::LEADING);
let consecutive = self.last_was_par; let consecutive = self.last_was_par;
let fragment = par.layout( let fragment = par.layout(
@ -172,17 +172,7 @@ impl<'a> FlowLayouter<'a> {
} }
// How to align the block. // How to align the block.
let aligns = Axes::new( let aligns = styles.get(AlignNode::ALIGNS).resolve(styles);
// For non-expanding paragraphs it is crucial that we align the
// whole paragraph as it is itself aligned.
styles.get(ParNode::ALIGN),
// Vertical align node alignment is respected by the flow.
block
.to::<AlignNode>()
.and_then(|aligned| aligned.aligns.y)
.map(|align| align.resolve(styles))
.unwrap_or(Align::Top),
);
// Layout the block itself. // Layout the block itself.
let sticky = styles.get(BlockNode::STICKY); let sticky = styles.get(BlockNode::STICKY);

View File

@ -412,7 +412,9 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
bail!(span, "not allowed here"); bail!(span, "not allowed here");
} }
self.interrupt_page(styles)?; self.interrupt_page(styles)?;
} else if map.interruption::<ParNode>().is_some() { } else if map.interruption::<ParNode>().is_some()
|| map.interruption::<AlignNode>().is_some()
{
self.interrupt_par()?; self.interrupt_par()?;
} else if map.interruption::<ListNode>().is_some() } else if map.interruption::<ListNode>().is_some()
|| map.interruption::<EnumNode>().is_some() || map.interruption::<EnumNode>().is_some()

View File

@ -5,6 +5,7 @@ use xi_unicode::LineBreakIterator;
use typst::model::Key; use typst::model::Key;
use super::{HNode, RepeatNode, Spacing}; use super::{HNode, RepeatNode, Spacing};
use crate::layout::AlignNode;
use crate::prelude::*; use crate::prelude::*;
use crate::text::{ use crate::text::{
shape, LinebreakNode, Quoter, Quotes, ShapedText, SmartQuoteNode, SpaceNode, TextNode, shape, LinebreakNode, Quoter, Quotes, ShapedText, SmartQuoteNode, SpaceNode, TextNode,
@ -22,9 +23,6 @@ impl ParNode {
/// The spacing between lines. /// The spacing between lines.
#[property(resolve)] #[property(resolve)]
pub const LEADING: Length = Em::new(0.65).into(); pub const LEADING: Length = Em::new(0.65).into();
/// How to align text and inline objects in their line.
#[property(resolve)]
pub const ALIGN: HorizontalAlign = HorizontalAlign(GenAlign::Start);
/// Whether to justify text in its line. /// Whether to justify text in its line.
pub const JUSTIFY: bool = false; pub const JUSTIFY: bool = false;
/// How to determine line breaks. /// How to determine line breaks.
@ -554,7 +552,7 @@ fn prepare<'a>(
styles, styles,
hyphenate: shared_get(styles, &par.0, TextNode::HYPHENATE), hyphenate: shared_get(styles, &par.0, TextNode::HYPHENATE),
lang: shared_get(styles, &par.0, TextNode::LANG), lang: shared_get(styles, &par.0, TextNode::LANG),
align: styles.get(ParNode::ALIGN), align: styles.get(AlignNode::ALIGNS).x.resolve(styles),
justify: styles.get(ParNode::JUSTIFY), justify: styles.get(ParNode::JUSTIFY),
}) })
} }

View File

@ -1,9 +1,8 @@
use super::AlignNode;
use crate::prelude::*; use crate::prelude::*;
/// Place content at an absolute position. /// Place content at an absolute position.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct PlaceNode(pub Content); pub struct PlaceNode(pub Content, bool);
#[node(Layout, Behave)] #[node(Layout, Behave)]
impl PlaceNode { impl PlaceNode {
@ -12,7 +11,8 @@ impl PlaceNode {
let dx = args.named("dx")?.unwrap_or_default(); let dx = args.named("dx")?.unwrap_or_default();
let dy = args.named("dy")?.unwrap_or_default(); let dy = args.named("dy")?.unwrap_or_default();
let body = args.expect::<Content>("body")?; let body = args.expect::<Content>("body")?;
Ok(Self(body.moved(Axes::new(dx, dy)).aligned(aligns)).pack()) let out_of_flow = aligns.y.is_some();
Ok(Self(body.moved(Axes::new(dx, dy)).aligned(aligns), out_of_flow).pack())
} }
} }
@ -49,7 +49,7 @@ impl PlaceNode {
/// origin. Instead of relative to the parent's current flow/cursor /// origin. Instead of relative to the parent's current flow/cursor
/// position. /// position.
pub fn out_of_flow(&self) -> bool { pub fn out_of_flow(&self) -> bool {
self.0.to::<AlignNode>().map_or(false, |node| node.aligns.y.is_some()) self.1
} }
} }

View File

@ -1,6 +1,6 @@
use typst::model::StyledNode; use typst::model::StyledNode;
use super::{AlignNode, ParNode, Spacing}; use super::{AlignNode, Spacing};
use crate::prelude::*; use crate::prelude::*;
/// Arrange content and spacing along an axis. /// Arrange content and spacing along an axis.
@ -180,21 +180,13 @@ impl<'a> StackLayouter<'a> {
// Block-axis alignment of the `AlignNode` is respected // Block-axis alignment of the `AlignNode` is respected
// by the stack node. // by the stack node.
let align = block let aligns = if let Some(styled) = block.to::<StyledNode>() {
.to::<AlignNode>() styles.chain(&styled.map).get(AlignNode::ALIGNS)
.and_then(|node| node.aligns.get(self.axis)) } else {
.map(|align| align.resolve(styles)) styles.get(AlignNode::ALIGNS)
.unwrap_or_else(|| { };
if let Some(styled) = block.to::<StyledNode>() {
let map = &styled.map;
if map.contains(ParNode::ALIGN) {
return StyleChain::new(map).get(ParNode::ALIGN);
}
}
self.dir.start().into()
});
let align = aligns.get(self.axis).resolve(styles);
let fragment = block.layout(vt, styles, self.regions)?; let fragment = block.layout(vt, styles, self.regions)?;
let len = fragment.len(); let len = fragment.len();
for (i, frame) in fragment.into_iter().enumerate() { for (i, frame) in fragment.into_iter().enumerate() {

View File

@ -208,6 +208,6 @@ fn items() -> LangItems {
math_atom: |atom| math::AtomNode(atom).pack(), math_atom: |atom| math::AtomNode(atom).pack(),
math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(), math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(),
math_frac: |num, denom| math::FracNode { num, denom }.pack(), math_frac: |num, denom| math::FracNode { num, denom }.pack(),
math_align: |count| math::AlignNode(count).pack(), math_align_point: |count| math::AlignPointNode(count).pack(),
} }
} }

View File

@ -442,14 +442,14 @@ impl Texify for ScriptNode {
} }
} }
/// A math alignment indicator: `&`, `&&`. /// A math alignment point: `&`, `&&`.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct AlignNode(pub usize); pub struct AlignPointNode(pub usize);
#[node(Texify)] #[node(Texify)]
impl AlignNode {} impl AlignPointNode {}
impl Texify for AlignNode { impl Texify for AlignPointNode {
fn texify(&self, _: &mut Texifier) -> SourceResult<()> { fn texify(&self, _: &mut Texifier) -> SourceResult<()> {
Ok(()) Ok(())
} }

View File

@ -57,7 +57,7 @@ impl ContentExt for Content {
} }
fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self { fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self {
crate::layout::AlignNode { aligns, body: self }.pack() self.styled(crate::layout::AlignNode::ALIGNS, aligns)
} }
fn padded(self, padding: Sides<Rel<Length>>) -> Self { fn padded(self, padding: Sides<Rel<Length>>) -> Self {

View File

@ -45,16 +45,13 @@ impl<T> Corners<T> {
} }
} }
/// Zip two instances into an instance. /// Zip two instances into one.
pub fn zip<F, V, W>(self, other: Corners<V>, mut f: F) -> Corners<W> pub fn zip<U>(self, other: Corners<U>) -> Corners<(T, U)> {
where
F: FnMut(T, V) -> W,
{
Corners { Corners {
top_left: f(self.top_left, other.top_left), top_left: (self.top_left, other.top_left),
top_right: f(self.top_right, other.top_right), top_right: (self.top_right, other.top_right),
bottom_right: f(self.bottom_right, other.bottom_right), bottom_right: (self.bottom_right, other.bottom_right),
bottom_left: f(self.bottom_left, other.bottom_left), bottom_left: (self.bottom_left, other.bottom_left),
} }
} }

View File

@ -45,16 +45,13 @@ impl<T> Sides<T> {
} }
} }
/// Zip two instances into an instance. /// Zip two instances into one.
pub fn zip<F, V, W>(self, other: Sides<V>, mut f: F) -> Sides<W> pub fn zip<U>(self, other: Sides<U>) -> Sides<(T, U)> {
where
F: FnMut(T, V) -> W,
{
Sides { Sides {
left: f(self.left, other.left), left: (self.left, other.left),
top: f(self.top, other.top), top: (self.top, other.top),
right: f(self.right, other.right), right: (self.right, other.right),
bottom: f(self.bottom, other.bottom), bottom: (self.bottom, other.bottom),
} }
} }

View File

@ -429,7 +429,7 @@ impl Eval for ast::MathNode {
Self::Symbol(v) => (vm.items.symbol)(v.get().clone() + ":op".into()), Self::Symbol(v) => (vm.items.symbol)(v.get().clone() + ":op".into()),
Self::Script(v) => v.eval(vm)?, Self::Script(v) => v.eval(vm)?,
Self::Frac(v) => v.eval(vm)?, Self::Frac(v) => v.eval(vm)?,
Self::Align(v) => v.eval(vm)?, Self::AlignPoint(v) => v.eval(vm)?,
Self::Group(v) => v.eval(vm)?, Self::Group(v) => v.eval(vm)?,
Self::Expr(v) => { Self::Expr(v) => {
if let ast::Expr::Ident(ident) = v { if let ast::Expr::Ident(ident) = v {
@ -480,11 +480,11 @@ impl Eval for ast::Frac {
} }
} }
impl Eval for ast::Align { impl Eval for ast::AlignPoint {
type Output = Content; type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items.math_align)(self.count())) Ok((vm.items.math_align_point)(self.count()))
} }
} }

View File

@ -74,8 +74,8 @@ pub struct LangItems {
fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content, fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content,
/// A fraction in a formula: `x/2`. /// A fraction in a formula: `x/2`.
pub math_frac: fn(num: Content, denom: Content) -> Content, pub math_frac: fn(num: Content, denom: Content) -> Content,
/// An alignment indicator in a formula: `&`, `&&`. /// An alignment point in a formula: `&`, `&&`.
pub math_align: fn(count: usize) -> Content, pub math_align_point: fn(count: usize) -> Content,
} }
impl Debug for LangItems { impl Debug for LangItems {
@ -107,7 +107,7 @@ impl Hash for LangItems {
self.math_atom.hash(state); self.math_atom.hash(state);
self.math_script.hash(state); self.math_script.hash(state);
self.math_frac.hash(state); self.math_frac.hash(state);
self.math_align.hash(state); self.math_align_point.hash(state);
} }
} }

View File

@ -913,6 +913,14 @@ where
} }
} }
impl Fold for Axes<Option<GenAlign>> {
type Output = Axes<GenAlign>;
fn fold(self, outer: Self::Output) -> Self::Output {
self.zip(outer).map(|(inner, outer)| inner.unwrap_or(outer))
}
}
impl<T> Fold for Sides<T> impl<T> Fold for Sides<T>
where where
T: Fold, T: Fold,
@ -920,7 +928,7 @@ where
type Output = Sides<T::Output>; type Output = Sides<T::Output>;
fn fold(self, outer: Self::Output) -> Self::Output { fn fold(self, outer: Self::Output) -> Self::Output {
self.zip(outer, |inner, outer| inner.fold(outer)) self.zip(outer).map(|(inner, outer)| inner.fold(outer))
} }
} }
@ -928,7 +936,7 @@ impl Fold for Sides<Option<Rel<Abs>>> {
type Output = Sides<Rel<Abs>>; type Output = Sides<Rel<Abs>>;
fn fold(self, outer: Self::Output) -> Self::Output { fn fold(self, outer: Self::Output) -> Self::Output {
self.zip(outer, |inner, outer| inner.unwrap_or(outer)) self.zip(outer).map(|(inner, outer)| inner.unwrap_or(outer))
} }
} }
@ -936,7 +944,7 @@ impl Fold for Sides<Option<Smart<Rel<Length>>>> {
type Output = Sides<Smart<Rel<Length>>>; type Output = Sides<Smart<Rel<Length>>>;
fn fold(self, outer: Self::Output) -> Self::Output { fn fold(self, outer: Self::Output) -> Self::Output {
self.zip(outer, |inner, outer| inner.unwrap_or(outer)) self.zip(outer).map(|(inner, outer)| inner.unwrap_or(outer))
} }
} }
@ -947,7 +955,7 @@ where
type Output = Corners<T::Output>; type Output = Corners<T::Output>;
fn fold(self, outer: Self::Output) -> Self::Output { fn fold(self, outer: Self::Output) -> Self::Output {
self.zip(outer, |inner, outer| inner.fold(outer)) self.zip(outer).map(|(inner, outer)| inner.fold(outer))
} }
} }
@ -955,7 +963,7 @@ impl Fold for Corners<Option<Rel<Abs>>> {
type Output = Corners<Rel<Abs>>; type Output = Corners<Rel<Abs>>;
fn fold(self, outer: Self::Output) -> Self::Output { fn fold(self, outer: Self::Output) -> Self::Output {
self.zip(outer, |inner, outer| inner.unwrap_or(outer)) self.zip(outer).map(|(inner, outer)| inner.unwrap_or(outer))
} }
} }

View File

@ -453,8 +453,8 @@ pub enum MathNode {
Script(Script), Script(Script),
/// A fraction: `x/2`. /// A fraction: `x/2`.
Frac(Frac), Frac(Frac),
/// An alignment indicator: `&`, `&&`. /// An alignment point: `&`, `&&`.
Align(Align), AlignPoint(AlignPoint),
/// Grouped mathematical material. /// Grouped mathematical material.
Group(Math), Group(Math),
/// An expression. /// An expression.
@ -472,7 +472,7 @@ impl AstNode for MathNode {
SyntaxKind::Symbol(_) => node.cast().map(Self::Symbol), SyntaxKind::Symbol(_) => node.cast().map(Self::Symbol),
SyntaxKind::Script => node.cast().map(Self::Script), SyntaxKind::Script => node.cast().map(Self::Script),
SyntaxKind::Frac => node.cast().map(Self::Frac), SyntaxKind::Frac => node.cast().map(Self::Frac),
SyntaxKind::Align => node.cast().map(Self::Align), SyntaxKind::AlignPoint => node.cast().map(Self::AlignPoint),
SyntaxKind::Math => node.cast().map(Self::Group), SyntaxKind::Math => node.cast().map(Self::Group),
_ => node.cast().map(Self::Expr), _ => node.cast().map(Self::Expr),
} }
@ -488,7 +488,7 @@ impl AstNode for MathNode {
Self::Symbol(v) => v.as_untyped(), Self::Symbol(v) => v.as_untyped(),
Self::Script(v) => v.as_untyped(), Self::Script(v) => v.as_untyped(),
Self::Frac(v) => v.as_untyped(), Self::Frac(v) => v.as_untyped(),
Self::Align(v) => v.as_untyped(), Self::AlignPoint(v) => v.as_untyped(),
Self::Group(v) => v.as_untyped(), Self::Group(v) => v.as_untyped(),
Self::Expr(v) => v.as_untyped(), Self::Expr(v) => v.as_untyped(),
} }
@ -558,11 +558,11 @@ impl Frac {
} }
node! { node! {
/// An alignment indicator in a formula: `&`, `&&`. /// An alignment point in a formula: `&`, `&&`.
Align AlignPoint
} }
impl Align { impl AlignPoint {
/// The number of ampersands. /// The number of ampersands.
pub fn count(&self) -> usize { pub fn count(&self) -> usize {
self.0.children().filter(|n| n.kind() == &SyntaxKind::Amp).count() self.0.children().filter(|n| n.kind() == &SyntaxKind::Amp).count()

View File

@ -302,7 +302,7 @@ impl Category {
SyntaxKind::Atom(_) => None, SyntaxKind::Atom(_) => None,
SyntaxKind::Script => None, SyntaxKind::Script => None,
SyntaxKind::Frac => None, SyntaxKind::Frac => None,
SyntaxKind::Align => None, SyntaxKind::AlignPoint => None,
SyntaxKind::Ident(_) => match parent.kind() { SyntaxKind::Ident(_) => match parent.kind() {
SyntaxKind::Markup { .. } SyntaxKind::Markup { .. }

View File

@ -179,8 +179,8 @@ pub enum SyntaxKind {
Script, Script,
/// A fraction in a formula: `x/2`. /// A fraction in a formula: `x/2`.
Frac, Frac,
/// An alignment indicator in a formula: `&`, `&&`. /// An alignment point in a formula: `&`, `&&`.
Align, AlignPoint,
/// An identifier: `it`. /// An identifier: `it`.
Ident(EcoString), Ident(EcoString),
@ -408,7 +408,7 @@ impl SyntaxKind {
Self::Atom(_) => "math atom", Self::Atom(_) => "math atom",
Self::Script => "script", Self::Script => "script",
Self::Frac => "fraction", Self::Frac => "fraction",
Self::Align => "alignment indicator", Self::AlignPoint => "alignment point",
Self::Ident(_) => "identifier", Self::Ident(_) => "identifier",
Self::Bool(_) => "boolean", Self::Bool(_) => "boolean",
Self::Int(_) => "integer", Self::Int(_) => "integer",
@ -528,7 +528,7 @@ impl Hash for SyntaxKind {
Self::Atom(c) => c.hash(state), Self::Atom(c) => c.hash(state),
Self::Script => {} Self::Script => {}
Self::Frac => {} Self::Frac => {}
Self::Align => {} Self::AlignPoint => {}
Self::Ident(v) => v.hash(state), Self::Ident(v) => v.hash(state),
Self::Bool(v) => v.hash(state), Self::Bool(v) => v.hash(state),
Self::Int(v) => v.hash(state), Self::Int(v) => v.hash(state),

View File

@ -499,7 +499,7 @@ fn math_group(p: &mut Parser, group: Group) {
} }
fn math_align(p: &mut Parser) { fn math_align(p: &mut Parser) {
p.perform(SyntaxKind::Align, |p| { p.perform(SyntaxKind::AlignPoint, |p| {
p.assert(SyntaxKind::Amp); p.assert(SyntaxKind::Amp);
while p.eat_if(SyntaxKind::Amp) {} while p.eat_if(SyntaxKind::Amp) {}
}) })

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -10,9 +10,9 @@
// Ensure that constructor styles win, but not over outer styles. // Ensure that constructor styles win, but not over outer styles.
// The outer paragraph should be right-aligned, // The outer paragraph should be right-aligned,
// but the B should be center-aligned. // but the B should be center-aligned.
#set par(align: center) #set list(label: [>])
#par(align: right)[ #list(label: [--])[
A #rect(width: 2cm, fill: conifer, inset: 4pt)[B] #rect(width: 2cm, fill: conifer, inset: 4pt, list[A])
] ]
--- ---

View File

@ -2,7 +2,7 @@
--- ---
// Test ragged-left. // Test ragged-left.
#set par(align: right) #set align(right)
To the right! Where the sunlight peeks behind the mountain. To the right! Where the sunlight peeks behind the mountain.
--- ---
@ -35,11 +35,3 @@ fn main() {}
- List - List
Paragraph Paragraph
---
// Error: 17-20 must be horizontal
#set par(align: top)
---
// Error: 17-33 expected alignment, found 2d alignment
#set par(align: horizon + center)

View File

@ -32,7 +32,7 @@ A#repeat(rect(width: 2.5em, height: 1em))B
// Test single repeat in both directions. // Test single repeat in both directions.
A#repeat(rect(width: 6em, height: 0.7em))B A#repeat(rect(width: 6em, height: 0.7em))B
#set par(align: center) #set align(center)
A#repeat(rect(width: 6em, height: 0.7em))B A#repeat(rect(width: 6em, height: 0.7em))B
#set text(dir: rtl) #set text(dir: rtl)

View File

@ -19,7 +19,7 @@ Add #h(10pt) #h(10pt) up
--- ---
// Test spacing collapsing before spacing. // Test spacing collapsing before spacing.
#set par(align: right) #set align(right)
A #h(0pt) B #h(0pt) \ A #h(0pt) B #h(0pt) \
A B \ A B \
A #h(-1fr) B A #h(-1fr) B

View File

@ -11,5 +11,5 @@
} }
#set page(width: auto) #set page(width: auto)
#set par(align: center) #set align(center)
#table(columns: 1 + modifiers.len(), ..cells) #table(columns: 1 + modifiers.len(), ..cells)

View File

@ -18,6 +18,6 @@
--- ---
// Test that lone punctuation doesn't overhang into the margin. // Test that lone punctuation doesn't overhang into the margin.
#set page(margin: 0pt) #set page(margin: 0pt)
#set par(align: end) #set align(end)
#set text(dir: rtl) #set text(dir: rtl)
: :

View File

@ -22,7 +22,7 @@
#let star(width, ..args) = box(width: width, height: width)[ #let star(width, ..args) = box(width: width, height: width)[
#set text(spacing: 0%) #set text(spacing: 0%)
#set line(..args) #set line(..args)
#set par(align: left) #set align(left)
#line(length: +30%, origin: (09.0%, 02%)) #line(length: +30%, origin: (09.0%, 02%))
#line(length: +30%, origin: (38.7%, 02%), angle: -72deg) #line(length: +30%, origin: (38.7%, 02%), angle: -72deg)
#line(length: +30%, origin: (57.5%, 02%), angle: 252deg) #line(length: +30%, origin: (57.5%, 02%), angle: 252deg)