mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Start & end alignment
This commit is contained in:
parent
e1d7edb7c1
commit
977ac77e6a
@ -5,7 +5,7 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{Barrier, StyleChain};
|
use super::{Barrier, RawAlign, StyleChain};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
|
use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
@ -182,7 +182,7 @@ impl LayoutNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set alignments for this node.
|
/// Set alignments for this node.
|
||||||
pub fn aligned(self, aligns: Spec<Option<Align>>) -> Self {
|
pub fn aligned(self, aligns: Spec<Option<RawAlign>>) -> Self {
|
||||||
if aligns.any(Option::is_some) {
|
if aligns.any(Option::is_some) {
|
||||||
AlignNode { aligns, child: self }.pack()
|
AlignNode { aligns, child: self }.pack()
|
||||||
} else {
|
} else {
|
||||||
|
@ -17,6 +17,7 @@ mod func;
|
|||||||
mod layout;
|
mod layout;
|
||||||
mod module;
|
mod module;
|
||||||
mod ops;
|
mod ops;
|
||||||
|
mod raw;
|
||||||
mod scope;
|
mod scope;
|
||||||
mod show;
|
mod show;
|
||||||
mod str;
|
mod str;
|
||||||
@ -32,6 +33,7 @@ pub use dict::*;
|
|||||||
pub use func::*;
|
pub use func::*;
|
||||||
pub use layout::*;
|
pub use layout::*;
|
||||||
pub use module::*;
|
pub use module::*;
|
||||||
|
pub use raw::*;
|
||||||
pub use scope::*;
|
pub use scope::*;
|
||||||
pub use show::*;
|
pub use show::*;
|
||||||
pub use styles::*;
|
pub use styles::*;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use super::{Dynamic, StrExt, Value};
|
use super::{Dynamic, RawAlign, StrExt, Value};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::geom::{Align, Numeric, Spec, SpecAxis};
|
use crate::geom::{Numeric, Spec, SpecAxis};
|
||||||
use Value::*;
|
use Value::*;
|
||||||
|
|
||||||
/// Bail with a type mismatch error.
|
/// Bail with a type mismatch error.
|
||||||
@ -94,7 +94,7 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
|
|||||||
if let (Dyn(a), Dyn(b)) = (&a, &b) {
|
if let (Dyn(a), Dyn(b)) = (&a, &b) {
|
||||||
// 1D alignments can be summed into 2D alignments.
|
// 1D alignments can be summed into 2D alignments.
|
||||||
if let (Some(&a), Some(&b)) =
|
if let (Some(&a), Some(&b)) =
|
||||||
(a.downcast::<Align>(), b.downcast::<Align>())
|
(a.downcast::<RawAlign>(), b.downcast::<RawAlign>())
|
||||||
{
|
{
|
||||||
return if a.axis() != b.axis() {
|
return if a.axis() != b.axis() {
|
||||||
Ok(Dyn(Dynamic::new(match a.axis() {
|
Ok(Dyn(Dynamic::new(match a.axis() {
|
||||||
|
49
src/eval/raw.rs
Normal file
49
src/eval/raw.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
|
use super::{Resolve, StyleChain};
|
||||||
|
use crate::geom::{Align, SpecAxis};
|
||||||
|
use crate::library::text::ParNode;
|
||||||
|
|
||||||
|
/// The unresolved alignment representation.
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub enum RawAlign {
|
||||||
|
/// Align at the start side of the text direction.
|
||||||
|
Start,
|
||||||
|
/// Align at the end side of the text direction.
|
||||||
|
End,
|
||||||
|
/// Align at a specific alignment.
|
||||||
|
Specific(Align),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolve for RawAlign {
|
||||||
|
type Output = Align;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
let dir = styles.get(ParNode::DIR);
|
||||||
|
match self {
|
||||||
|
Self::Start => dir.start().into(),
|
||||||
|
Self::End => dir.end().into(),
|
||||||
|
Self::Specific(align) => align,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawAlign {
|
||||||
|
/// The axis this alignment belongs to.
|
||||||
|
pub const fn axis(self) -> SpecAxis {
|
||||||
|
match self {
|
||||||
|
Self::Start | Self::End => SpecAxis::Horizontal,
|
||||||
|
Self::Specific(align) => align.axis(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for RawAlign {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Start => f.pad("left"),
|
||||||
|
Self::End => f.pad("center"),
|
||||||
|
Self::Specific(align) => align.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -79,8 +79,8 @@ impl Frame {
|
|||||||
pub fn resize(&mut self, target: Size, aligns: Spec<Align>) {
|
pub fn resize(&mut self, target: Size, aligns: Spec<Align>) {
|
||||||
if self.size != target {
|
if self.size != target {
|
||||||
let offset = Point::new(
|
let offset = Point::new(
|
||||||
aligns.x.resolve(target.x - self.size.x),
|
aligns.x.position(target.x - self.size.x),
|
||||||
aligns.y.resolve(target.y - self.size.y),
|
aligns.y.position(target.y - self.size.y),
|
||||||
);
|
);
|
||||||
self.size = target;
|
self.size = target;
|
||||||
self.translate(offset);
|
self.translate(offset);
|
||||||
|
@ -45,7 +45,7 @@ impl Align {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the position of this alignment in the given length.
|
/// Returns the position of this alignment in the given length.
|
||||||
pub fn resolve(self, length: Length) -> Length {
|
pub fn position(self, length: Length) -> Length {
|
||||||
match self {
|
match self {
|
||||||
Self::Left | Self::Top => Length::zero(),
|
Self::Left | Self::Top => Length::zero(),
|
||||||
Self::Center | Self::Horizon => length / 2.0,
|
Self::Center | Self::Horizon => length / 2.0,
|
||||||
|
@ -22,7 +22,8 @@ pub type ScaleNode = TransformNode<SCALE>;
|
|||||||
#[node]
|
#[node]
|
||||||
impl<const T: TransformKind> TransformNode<T> {
|
impl<const T: TransformKind> TransformNode<T> {
|
||||||
/// The origin of the transformation.
|
/// The origin of the transformation.
|
||||||
pub const ORIGIN: Spec<Option<Align>> = Spec::default();
|
#[property(resolve)]
|
||||||
|
pub const ORIGIN: Spec<Option<RawAlign>> = Spec::default();
|
||||||
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
let transform = match T {
|
let transform = match T {
|
||||||
@ -61,7 +62,7 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
|
|||||||
let mut frames = self.child.layout(ctx, regions, styles)?;
|
let mut frames = self.child.layout(ctx, regions, styles)?;
|
||||||
|
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
let Spec { x, y } = origin.zip(frame.size).map(|(o, s)| o.resolve(s));
|
let Spec { x, y } = origin.zip(frame.size).map(|(o, s)| o.position(s));
|
||||||
let transform = Transform::translate(x, y)
|
let transform = Transform::translate(x, y)
|
||||||
.pre_concat(self.transform)
|
.pre_concat(self.transform)
|
||||||
.pre_concat(Transform::translate(-x, -y));
|
.pre_concat(Transform::translate(-x, -y));
|
||||||
|
@ -5,7 +5,7 @@ use crate::library::text::ParNode;
|
|||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct AlignNode {
|
pub struct AlignNode {
|
||||||
/// How to align the node horizontally and vertically.
|
/// How to align the node horizontally and vertically.
|
||||||
pub aligns: Spec<Option<Align>>,
|
pub aligns: Spec<Option<RawAlign>>,
|
||||||
/// The node to be aligned.
|
/// The node to be aligned.
|
||||||
pub child: LayoutNode,
|
pub child: LayoutNode,
|
||||||
}
|
}
|
||||||
@ -42,30 +42,14 @@ impl Layout for AlignNode {
|
|||||||
// Align in the target size. The target size depends on whether we
|
// Align in the target size. The target size depends on whether we
|
||||||
// should expand.
|
// should expand.
|
||||||
let target = regions.expand.select(region, frame.size);
|
let target = regions.expand.select(region, frame.size);
|
||||||
let default = Spec::new(Align::Left, Align::Top);
|
let aligns = self
|
||||||
let aligns = self.aligns.unwrap_or(default);
|
.aligns
|
||||||
|
.map(|align| align.resolve(styles))
|
||||||
|
.unwrap_or(Spec::new(Align::Left, Align::Top));
|
||||||
|
|
||||||
Arc::make_mut(frame).resize(target, aligns);
|
Arc::make_mut(frame).resize(target, aligns);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic! {
|
|
||||||
Align: "alignment",
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic! {
|
|
||||||
Spec<Align>: "2d alignment",
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
Spec<Option<Align>>,
|
|
||||||
Expected: "1d or 2d alignment",
|
|
||||||
@align: Align => {
|
|
||||||
let mut aligns = Spec::default();
|
|
||||||
aligns.set(align.axis(), Some(*align));
|
|
||||||
aligns
|
|
||||||
},
|
|
||||||
@aligns: Spec<Align> => aligns.map(Some),
|
|
||||||
}
|
|
||||||
|
@ -189,6 +189,7 @@ impl FlowLayouter {
|
|||||||
// Vertical align node alignment is respected by the flow node.
|
// Vertical align node alignment is respected by the flow node.
|
||||||
node.downcast::<AlignNode>()
|
node.downcast::<AlignNode>()
|
||||||
.and_then(|aligned| aligned.aligns.y)
|
.and_then(|aligned| aligned.aligns.y)
|
||||||
|
.map(|align| align.resolve(styles))
|
||||||
.unwrap_or(Align::Top),
|
.unwrap_or(Align::Top),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -238,8 +239,8 @@ impl FlowLayouter {
|
|||||||
}
|
}
|
||||||
FlowItem::Frame(frame, aligns) => {
|
FlowItem::Frame(frame, aligns) => {
|
||||||
ruler = ruler.max(aligns.y);
|
ruler = ruler.max(aligns.y);
|
||||||
let x = aligns.x.resolve(size.x - frame.size.x);
|
let x = aligns.x.position(size.x - frame.size.x);
|
||||||
let y = offset + ruler.resolve(size.y - self.used.y);
|
let y = offset + ruler.position(size.y - self.used.y);
|
||||||
let pos = Point::new(x, y);
|
let pos = Point::new(x, y);
|
||||||
offset += frame.size.y;
|
offset += frame.size.y;
|
||||||
output.push_frame(pos, frame);
|
output.push_frame(pos, frame);
|
||||||
|
@ -8,9 +8,9 @@ pub struct PlaceNode(pub LayoutNode);
|
|||||||
#[node]
|
#[node]
|
||||||
impl PlaceNode {
|
impl PlaceNode {
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
let aligns = args.find()?.unwrap_or(Spec::with_x(Some(Align::Left)));
|
|
||||||
let tx = args.named("dx")?.unwrap_or_default();
|
let tx = args.named("dx")?.unwrap_or_default();
|
||||||
let ty = args.named("dy")?.unwrap_or_default();
|
let ty = args.named("dy")?.unwrap_or_default();
|
||||||
|
let aligns = args.find()?.unwrap_or(Spec::with_x(Some(RawAlign::Start)));
|
||||||
let body: LayoutNode = args.expect("body")?;
|
let body: LayoutNode = args.expect("body")?;
|
||||||
Ok(Content::block(Self(
|
Ok(Content::block(Self(
|
||||||
body.moved(Point::new(tx, ty)).aligned(aligns),
|
body.moved(Point::new(tx, ty)).aligned(aligns),
|
||||||
|
@ -175,6 +175,7 @@ impl StackLayouter {
|
|||||||
let align = node
|
let align = node
|
||||||
.downcast::<AlignNode>()
|
.downcast::<AlignNode>()
|
||||||
.and_then(|node| node.aligns.get(self.axis))
|
.and_then(|node| node.aligns.get(self.axis))
|
||||||
|
.map(|align| align.resolve(styles))
|
||||||
.unwrap_or(self.dir.start().into());
|
.unwrap_or(self.dir.start().into());
|
||||||
|
|
||||||
let frames = node.layout(ctx, &self.regions, styles)?;
|
let frames = node.layout(ctx, &self.regions, styles)?;
|
||||||
@ -229,7 +230,7 @@ impl StackLayouter {
|
|||||||
// Align along the block axis.
|
// Align along the block axis.
|
||||||
let parent = size.get(self.axis);
|
let parent = size.get(self.axis);
|
||||||
let child = frame.size.get(self.axis);
|
let child = frame.size.get(self.axis);
|
||||||
let block = ruler.resolve(parent - self.used.main)
|
let block = ruler.position(parent - self.used.main)
|
||||||
+ if self.dir.is_positive() {
|
+ if self.dir.is_positive() {
|
||||||
cursor
|
cursor
|
||||||
} else {
|
} else {
|
||||||
|
@ -113,12 +113,14 @@ pub fn new() -> Scope {
|
|||||||
std.def_const("rtl", Dir::RTL);
|
std.def_const("rtl", Dir::RTL);
|
||||||
std.def_const("ttb", Dir::TTB);
|
std.def_const("ttb", Dir::TTB);
|
||||||
std.def_const("btt", Dir::BTT);
|
std.def_const("btt", Dir::BTT);
|
||||||
std.def_const("left", Align::Left);
|
std.def_const("start", RawAlign::Start);
|
||||||
std.def_const("center", Align::Center);
|
std.def_const("end", RawAlign::End);
|
||||||
std.def_const("right", Align::Right);
|
std.def_const("left", RawAlign::Specific(Align::Left));
|
||||||
std.def_const("top", Align::Top);
|
std.def_const("center", RawAlign::Specific(Align::Center));
|
||||||
std.def_const("horizon", Align::Horizon);
|
std.def_const("right", RawAlign::Specific(Align::Right));
|
||||||
std.def_const("bottom", Align::Bottom);
|
std.def_const("top", RawAlign::Specific(Align::Top));
|
||||||
|
std.def_const("horizon", RawAlign::Specific(Align::Horizon));
|
||||||
|
std.def_const("bottom", RawAlign::Specific(Align::Bottom));
|
||||||
|
|
||||||
std
|
std
|
||||||
}
|
}
|
||||||
@ -127,6 +129,25 @@ dynamic! {
|
|||||||
Dir: "direction",
|
Dir: "direction",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dynamic! {
|
||||||
|
RawAlign: "alignment",
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic! {
|
||||||
|
Spec<RawAlign>: "2d alignment",
|
||||||
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
Spec<Option<RawAlign>>,
|
||||||
|
Expected: "1d or 2d alignment",
|
||||||
|
@align: RawAlign => {
|
||||||
|
let mut aligns = Spec::default();
|
||||||
|
aligns.set(align.axis(), Some(*align));
|
||||||
|
aligns
|
||||||
|
},
|
||||||
|
@aligns: Spec<RawAlign> => aligns.map(Some),
|
||||||
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
usize,
|
usize,
|
||||||
Expected: "non-negative integer",
|
Expected: "non-negative integer",
|
||||||
|
@ -10,7 +10,8 @@ pub use typst_macros::node;
|
|||||||
pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult};
|
pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult};
|
||||||
pub use crate::eval::{
|
pub use crate::eval::{
|
||||||
Arg, Args, Array, Cast, Content, Dict, Fold, Func, Key, Layout, LayoutNode, Merge,
|
Arg, Args, Array, Cast, Content, Dict, Fold, Func, Key, Layout, LayoutNode, Merge,
|
||||||
Node, Regions, Scope, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Value,
|
Node, RawAlign, Regions, Resolve, Scope, Show, ShowNode, Smart, StyleChain, StyleMap,
|
||||||
|
StyleVec, Value,
|
||||||
};
|
};
|
||||||
pub use crate::frame::*;
|
pub use crate::frame::*;
|
||||||
pub use crate::geom::*;
|
pub use crate::geom::*;
|
||||||
|
@ -33,7 +33,8 @@ impl ParNode {
|
|||||||
/// The direction for text and inline objects.
|
/// The direction for text and inline objects.
|
||||||
pub const DIR: Dir = Dir::LTR;
|
pub const DIR: Dir = Dir::LTR;
|
||||||
/// How to align text and inline objects in their line.
|
/// How to align text and inline objects in their line.
|
||||||
pub const ALIGN: Align = Align::Left;
|
#[property(resolve)]
|
||||||
|
pub const ALIGN: RawAlign = RawAlign::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.
|
||||||
@ -74,14 +75,12 @@ impl ParNode {
|
|||||||
dir = Some(v);
|
dir = Some(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
let align =
|
let mut align = None;
|
||||||
if let Some(Spanned { v, span }) = args.named::<Spanned<Align>>("align")? {
|
if let Some(Spanned { v, span }) = args.named::<Spanned<RawAlign>>("align")? {
|
||||||
if v.axis() != SpecAxis::Horizontal {
|
if v.axis() != SpecAxis::Horizontal {
|
||||||
bail!(span, "must be horizontal");
|
bail!(span, "must be horizontal");
|
||||||
}
|
}
|
||||||
Some(v)
|
align = Some(v);
|
||||||
} else {
|
|
||||||
dir.map(|dir| dir.start().into())
|
|
||||||
};
|
};
|
||||||
|
|
||||||
styles.set_opt(Self::LANG, lang);
|
styles.set_opt(Self::LANG, lang);
|
||||||
@ -880,7 +879,7 @@ fn commit(
|
|||||||
// Construct the line's frame from left to right.
|
// Construct the line's frame from left to right.
|
||||||
for item in reordered {
|
for item in reordered {
|
||||||
let mut position = |frame: Frame| {
|
let mut position = |frame: Frame| {
|
||||||
let x = offset + align.resolve(remaining);
|
let x = offset + align.position(remaining);
|
||||||
let y = line.baseline - frame.baseline();
|
let y = line.baseline - frame.baseline();
|
||||||
offset += frame.size.x;
|
offset += frame.size.x;
|
||||||
output.merge_frame(Point::new(x, y), frame);
|
output.merge_frame(Point::new(x, y), frame);
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 7.6 KiB |
@ -14,12 +14,25 @@
|
|||||||
))
|
))
|
||||||
|
|
||||||
---
|
---
|
||||||
|
// Test that multiple paragraphs in subflow also respect alignment.
|
||||||
#align(center)[
|
#align(center)[
|
||||||
Lorem Ipsum
|
Lorem Ipsum
|
||||||
|
|
||||||
Dolor
|
Dolor
|
||||||
]
|
]
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test start and end alignment.
|
||||||
|
#rotate(-30deg, origin: end + horizon)[Hello]
|
||||||
|
|
||||||
|
#set par(lang: "de")
|
||||||
|
#align(start)[Start]
|
||||||
|
#align(end)[Ende]
|
||||||
|
|
||||||
|
#set par(lang: "ar")
|
||||||
|
#align(start)[يبدأ]
|
||||||
|
#align(end)[نهاية]
|
||||||
|
|
||||||
---
|
---
|
||||||
// Ref: false
|
// Ref: false
|
||||||
#test(type(center), "alignment")
|
#test(type(center), "alignment")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user