Align node

This commit is contained in:
Laurenz 2021-11-17 17:09:19 +01:00
parent 9a800daa82
commit 89f2e71852
33 changed files with 328 additions and 306 deletions

View File

@ -58,7 +58,7 @@ impl Template {
pub fn from_inline<F, T>(f: F) -> Self pub fn from_inline<F, T>(f: F) -> Self
where where
F: Fn(&Style) -> T + 'static, F: Fn(&Style) -> T + 'static,
T: Layout + Hash + 'static, T: Layout + Debug + Hash + 'static,
{ {
let node = TemplateNode::Inline(Rc::new(move |s| f(s).pack())); let node = TemplateNode::Inline(Rc::new(move |s| f(s).pack()));
Self(Rc::new(vec![node])) Self(Rc::new(vec![node]))
@ -68,7 +68,7 @@ impl Template {
pub fn from_block<F, T>(f: F) -> Self pub fn from_block<F, T>(f: F) -> Self
where where
F: Fn(&Style) -> T + 'static, F: Fn(&Style) -> T + 'static,
T: Layout + Hash + 'static, T: Layout + Debug + Hash + 'static,
{ {
let node = TemplateNode::Block(Rc::new(move |s| f(s).pack())); let node = TemplateNode::Block(Rc::new(move |s| f(s).pack()));
Self(Rc::new(vec![node])) Self(Rc::new(vec![node]))
@ -332,14 +332,13 @@ impl Builder {
/// Push an inline node into the active paragraph. /// Push an inline node into the active paragraph.
fn inline(&mut self, node: impl Into<PackedNode>) { fn inline(&mut self, node: impl Into<PackedNode>) {
let align = self.style.aligns.inline; self.flow.par.push(ParChild::Node(node.into()));
self.flow.par.push(ParChild::Node(node.into(), align));
} }
/// Push a block node into the active flow, finishing the active paragraph. /// Push a block node into the active flow, finishing the active paragraph.
fn block(&mut self, node: impl Into<PackedNode>) { fn block(&mut self, node: impl Into<PackedNode>) {
self.parbreak(); self.parbreak();
self.flow.push(FlowChild::Node(node.into(), self.style.aligns.block)); self.flow.push(FlowChild::Node(node.into()));
self.parbreak(); self.parbreak();
} }
@ -372,11 +371,7 @@ impl Builder {
/// Construct a text node with the given text and settings from the current /// Construct a text node with the given text and settings from the current
/// style. /// style.
fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild { fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild {
ParChild::Text( ParChild::Text(text.into(), Rc::clone(&self.style.text))
text.into(),
self.style.aligns.inline,
Rc::clone(&self.style.text),
)
} }
} }
@ -451,8 +446,8 @@ impl FlowBuilder {
} }
struct ParBuilder { struct ParBuilder {
align: Align,
dir: Dir, dir: Dir,
align: Align,
leading: Length, leading: Length,
children: Vec<ParChild>, children: Vec<ParChild>,
last: Last<ParChild>, last: Last<ParChild>,
@ -461,8 +456,8 @@ struct ParBuilder {
impl ParBuilder { impl ParBuilder {
fn new(style: &Style) -> Self { fn new(style: &Style) -> Self {
Self { Self {
align: style.aligns.block,
dir: style.par.dir, dir: style.par.dir,
align: style.par.align,
leading: style.leading(), leading: style.leading(),
children: vec![], children: vec![],
last: Last::None, last: Last::None,
@ -486,12 +481,10 @@ impl ParBuilder {
} }
fn push_inner(&mut self, child: ParChild) { fn push_inner(&mut self, child: ParChild) {
if let ParChild::Text(curr_text, curr_align, curr_props) = &child { if let ParChild::Text(text2, style2) = &child {
if let Some(ParChild::Text(prev_text, prev_align, prev_props)) = if let Some(ParChild::Text(text1, style1)) = self.children.last_mut() {
self.children.last_mut() if Rc::ptr_eq(style1, style2) {
{ text1.push_str(text2);
if prev_align == curr_align && Rc::ptr_eq(prev_props, curr_props) {
prev_text.push_str(curr_text);
return; return;
} }
} }
@ -501,9 +494,9 @@ impl ParBuilder {
} }
fn build(self) -> Option<FlowChild> { fn build(self) -> Option<FlowChild> {
let Self { align, dir, leading, children, .. } = self; let Self { dir, align, leading, children, .. } = self;
(!children.is_empty()) (!children.is_empty())
.then(|| FlowChild::Node(ParNode { dir, leading, children }.pack(), align)) .then(|| FlowChild::Node(ParNode { dir, align, leading, children }.pack()))
} }
} }

View File

@ -126,12 +126,9 @@ fn walk_item(ctx: &mut EvalContext, label: EcoString, body: Template) {
ctx.template += Template::from_block(move |style| { ctx.template += Template::from_block(move |style| {
let label = Layout::pack(ParNode { let label = Layout::pack(ParNode {
dir: style.par.dir, dir: style.par.dir,
align: style.par.align,
leading: style.leading(), leading: style.leading(),
children: vec![ParChild::Text( children: vec![ParChild::Text(label.clone(), Rc::clone(&style.text))],
label.clone(),
style.aligns.inline,
Rc::clone(&style.text),
)],
}); });
let spacing = style.text.size / 2.0; let spacing = style.text.size / 2.0;

View File

@ -3,18 +3,14 @@ use super::*;
/// Where to align something along an axis. /// Where to align something along an axis.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Align { pub enum Align {
/// Align at the start of the axis.
Start,
/// Align in the middle of the axis.
Center,
/// Align at the end of the axis.
End,
/// Align at the left side of the axis. /// Align at the left side of the axis.
Left, Left,
/// Align at the right side of the axis.
Right,
/// Align at the top side of the axis. /// Align at the top side of the axis.
Top, Top,
/// Align in the middle of the axis.
Center,
/// Align at the right side of the axis.
Right,
/// Align at the bottom side of the axis. /// Align at the bottom side of the axis.
Bottom, Bottom,
} }
@ -23,12 +19,10 @@ impl Align {
/// The axis this alignment belongs to if it is specific. /// The axis this alignment belongs to if it is specific.
pub fn axis(self) -> Option<SpecAxis> { pub fn axis(self) -> Option<SpecAxis> {
match self { match self {
Self::Start => None,
Self::Center => None,
Self::End => None,
Self::Left => Some(SpecAxis::Horizontal), Self::Left => Some(SpecAxis::Horizontal),
Self::Right => Some(SpecAxis::Horizontal),
Self::Top => Some(SpecAxis::Vertical), Self::Top => Some(SpecAxis::Vertical),
Self::Center => None,
Self::Right => Some(SpecAxis::Horizontal),
Self::Bottom => Some(SpecAxis::Vertical), Self::Bottom => Some(SpecAxis::Vertical),
} }
} }
@ -36,60 +30,42 @@ impl Align {
/// The inverse alignment. /// The inverse alignment.
pub fn inv(self) -> Self { pub fn inv(self) -> Self {
match self { match self {
Self::Start => Self::End,
Self::Center => Self::Center,
Self::End => Self::Start,
Self::Left => Self::Right, Self::Left => Self::Right,
Self::Right => Self::Left,
Self::Top => Self::Bottom, Self::Top => Self::Bottom,
Self::Center => Self::Center,
Self::Right => Self::Left,
Self::Bottom => Self::Top, Self::Bottom => Self::Top,
} }
} }
/// Returns the position of this alignment in the given range. /// Returns the position of this alignment in the given range.
pub fn resolve(self, dir: Dir, range: Range<Length>) -> Length { pub fn resolve(self, range: Range<Length>) -> Length {
#[cfg(debug_assertions)]
if let Some(axis) = self.axis() {
debug_assert_eq!(axis, dir.axis())
}
match self { match self {
Self::Start => {
if dir.is_positive() {
range.start
} else {
range.end
}
}
Self::Center => (range.start + range.end) / 2.0,
Self::End => {
if dir.is_positive() {
range.end
} else {
range.start
}
}
Self::Left | Self::Top => range.start, Self::Left | Self::Top => range.start,
Self::Right | Self::Bottom => range.end, Self::Right | Self::Bottom => range.end,
Self::Center => (range.start + range.end) / 2.0,
} }
} }
} }
impl Default for Align { impl From<Side> for Align {
fn default() -> Self { fn from(side: Side) -> Self {
Self::Start match side {
Side::Left => Self::Left,
Side::Top => Self::Top,
Side::Right => Self::Right,
Side::Bottom => Self::Bottom,
}
} }
} }
impl Debug for Align { impl Debug for Align {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self { f.pad(match self {
Self::Start => "start",
Self::Center => "center",
Self::End => "end",
Self::Left => "left", Self::Left => "left",
Self::Right => "right",
Self::Top => "top", Self::Top => "top",
Self::Center => "center",
Self::Right => "right",
Self::Bottom => "bottom", Self::Bottom => "bottom",
}) })
} }

View File

@ -23,6 +23,11 @@ impl<T> Spec<T> {
Self { x: v.clone(), y: v } Self { x: v.clone(), y: v }
} }
/// Borrows the individual fields.
pub fn as_ref(&self) -> Spec<&T> {
Spec { x: &self.x, y: &self.y }
}
/// Maps the individual fields with `f`. /// Maps the individual fields with `f`.
pub fn map<F, U>(self, mut f: F) -> Spec<U> pub fn map<F, U>(self, mut f: F) -> Spec<U>
where where
@ -40,6 +45,14 @@ impl<T> Spec<T> {
} }
} }
/// Whether a condition is true for at least one of fields.
pub fn any<F>(self, mut f: F) -> bool
where
F: FnMut(&T) -> bool,
{
f(&self.x) || f(&self.y)
}
/// Whether a condition is true for both fields. /// Whether a condition is true for both fields.
pub fn all<F>(self, mut f: F) -> bool pub fn all<F>(self, mut f: F) -> bool
where where

View File

@ -10,15 +10,16 @@ pub use constraints::*;
pub use incremental::*; pub use incremental::*;
pub use regions::*; pub use regions::*;
use std::any::Any;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::rc::Rc; use std::rc::Rc;
use crate::font::FontStore; use crate::font::FontStore;
use crate::frame::Frame; use crate::frame::Frame;
use crate::geom::{Linear, Spec}; use crate::geom::{Align, Linear, Spec};
use crate::image::ImageStore; use crate::image::ImageStore;
use crate::library::{DocumentNode, SizedNode}; use crate::library::{AlignNode, DocumentNode, SizedNode};
use crate::Context; use crate::Context;
/// Layout a document node into a collection of frames. /// Layout a document node into a collection of frames.
@ -59,7 +60,7 @@ impl<'a> LayoutContext<'a> {
/// ///
/// Layout return one frame per used region alongside constraints that define /// Layout return one frame per used region alongside constraints that define
/// whether the result is reusable in other regions. /// whether the result is reusable in other regions.
pub trait Layout: Debug { pub trait Layout {
/// Layout the node into the given regions, producing constrained frames. /// Layout the node into the given regions, producing constrained frames.
fn layout( fn layout(
&self, &self,
@ -70,12 +71,11 @@ pub trait Layout: Debug {
/// Convert to a packed node. /// Convert to a packed node.
fn pack(self) -> PackedNode fn pack(self) -> PackedNode
where where
Self: Sized + Hash + 'static, Self: Debug + Hash + Sized + 'static,
{ {
PackedNode { PackedNode {
#[cfg(feature = "layout-cache")] #[cfg(feature = "layout-cache")]
hash: { hash: {
use std::any::Any;
let mut state = fxhash::FxHasher64::default(); let mut state = fxhash::FxHasher64::default();
self.type_id().hash(&mut state); self.type_id().hash(&mut state);
self.hash(&mut state); self.hash(&mut state);
@ -89,26 +89,40 @@ pub trait Layout: Debug {
/// A packed layouting node with precomputed hash. /// A packed layouting node with precomputed hash.
#[derive(Clone)] #[derive(Clone)]
pub struct PackedNode { pub struct PackedNode {
node: Rc<dyn Layout>, node: Rc<dyn Bounds>,
#[cfg(feature = "layout-cache")] #[cfg(feature = "layout-cache")]
hash: u64, hash: u64,
} }
impl PackedNode { impl PackedNode {
/// Try to downcast to a specific layout node.
pub fn downcast<T>(&self) -> Option<&T>
where
T: Layout + Debug + Hash + 'static,
{
self.node.as_any().downcast_ref()
}
/// Force a size for this node. /// Force a size for this node.
///
/// If at least one of `width` and `height` is `Some`, this wraps the node
/// in a [`SizedNode`]. Otherwise, it returns the node unchanged.
pub fn sized(self, width: Option<Linear>, height: Option<Linear>) -> PackedNode { pub fn sized(self, width: Option<Linear>, height: Option<Linear>) -> PackedNode {
if width.is_some() || height.is_some() { if width.is_some() || height.is_some() {
Layout::pack(SizedNode { Layout::pack(SizedNode {
sizing: Spec::new(width, height),
child: self, child: self,
sizing: Spec::new(width, height),
}) })
} else { } else {
self self
} }
} }
/// Set alignments for this node.
pub fn aligned(self, x: Option<Align>, y: Option<Align>) -> PackedNode {
if x.is_some() || y.is_some() {
Layout::pack(AlignNode { child: self, aligns: Spec::new(x, y) })
} else {
self
}
}
} }
impl Layout for PackedNode { impl Layout for PackedNode {
@ -166,3 +180,16 @@ impl Debug for PackedNode {
self.node.fmt(f) self.node.fmt(f)
} }
} }
trait Bounds: Layout + Debug + 'static {
fn as_any(&self) -> &dyn Any;
}
impl<T> Bounds for T
where
T: Layout + Debug + 'static,
{
fn as_any(&self) -> &dyn Any {
self
}
}

View File

@ -1,51 +1,77 @@
use super::prelude::*; use super::prelude::*;
/// `align`: Configure the alignment along the layouting axes. /// `align`: Configure the alignment along the layouting axes.
pub fn align(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let first = args.find::<Align>(); let mut x = args.named("horizontal")?;
let second = args.find::<Align>(); let mut y = args.named("vertical")?;
let body = args.find::<Template>(); for Spanned { v, span } in args.all::<Spanned<Align>>() {
match v.axis() {
let mut horizontal = args.named("horizontal")?; None | Some(SpecAxis::Horizontal) if x.is_none() => x = Some(v),
let mut vertical = args.named("vertical")?; None | Some(SpecAxis::Vertical) if y.is_none() => y = Some(v),
_ => bail!(span, "unexpected argument"),
for value in first.into_iter().chain(second) {
match value.axis() {
Some(SpecAxis::Horizontal) | None if horizontal.is_none() => {
horizontal = Some(value);
}
Some(SpecAxis::Vertical) | None if vertical.is_none() => {
vertical = Some(value);
}
_ => {}
} }
} }
let realign = |template: &mut Template| { let body = args.expect::<Template>("body")?;
template.modify(move |style| {
if let Some(horizontal) = horizontal { Ok(Value::Template(Template::from_block(move |style| {
style.aligns.inline = horizontal; let mut style = style.clone();
if let Some(x) = x {
style.par_mut().align = x;
} }
if let Some(vertical) = vertical { body.pack(&style).aligned(x, y)
style.aligns.block = vertical; })))
} }
});
/// A node that aligns its child.
if vertical.is_some() { #[derive(Debug, Hash)]
template.parbreak(); pub struct AlignNode {
} /// The node to be aligned.
}; pub child: PackedNode,
/// How to align the node horizontally and vertically.
Ok(if let Some(body) = body { pub aligns: Spec<Option<Align>>,
let mut template = Template::new(); }
template.save();
realign(&mut template); impl Layout for AlignNode {
template += body; fn layout(
template.restore(); &self,
Value::Template(template) ctx: &mut LayoutContext,
} else { regions: &Regions,
realign(&mut ctx.template); ) -> Vec<Constrained<Rc<Frame>>> {
Value::None // Along axes with specified alignment, the child doesn't need to expand.
}) let mut pod = regions.clone();
pod.expand.x &= self.aligns.x.is_none();
pod.expand.y &= self.aligns.y.is_none();
let mut frames = self.child.layout(ctx, &pod);
for (Constrained { item: frame, cts }, (current, _)) in
frames.iter_mut().zip(regions.iter())
{
let canvas = Size::new(
if regions.expand.x { current.w } else { frame.size.w },
if regions.expand.y { current.h } else { frame.size.h },
);
let aligns = self.aligns.unwrap_or(Spec::new(Align::Left, Align::Top));
let offset = Point::new(
aligns.x.resolve(Length::zero() .. canvas.w - frame.size.w),
aligns.y.resolve(Length::zero() .. canvas.h - frame.size.h),
);
let frame = Rc::make_mut(frame);
frame.size = canvas;
frame.baseline += offset.y;
for (point, _) in &mut frame.elements {
*point += offset;
}
cts.expand = regions.expand;
cts.exact = current.to_spec().map(Some);
}
frames
}
} }

View File

@ -1,7 +1,7 @@
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use super::prelude::*; use super::prelude::*;
use super::Spacing; use super::{AlignNode, ParNode, Spacing};
/// `flow`: A vertical flow of paragraphs and other layout nodes. /// `flow`: A vertical flow of paragraphs and other layout nodes.
pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
@ -27,7 +27,7 @@ pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
.iter() .iter()
.map(|child| match child { .map(|child| match child {
Child::Spacing(spacing) => FlowChild::Spacing(*spacing), Child::Spacing(spacing) => FlowChild::Spacing(*spacing),
Child::Any(node) => FlowChild::Node(node.pack(style), style.aligns.block), Child::Any(node) => FlowChild::Node(node.pack(style)),
}) })
.collect(); .collect();
@ -62,14 +62,14 @@ pub enum FlowChild {
/// Vertical spacing between other children. /// Vertical spacing between other children.
Spacing(Spacing), Spacing(Spacing),
/// A node and how to align it in the flow. /// A node and how to align it in the flow.
Node(PackedNode, Align), Node(PackedNode),
} }
impl Debug for FlowChild { impl Debug for FlowChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Self::Spacing(v) => write!(f, "Spacing({:?})", v), Self::Spacing(v) => write!(f, "Spacing({:?})", v),
Self::Node(node, _) => node.fmt(f), Self::Node(node) => node.fmt(f),
} }
} }
} }
@ -101,8 +101,8 @@ enum FlowItem {
Absolute(Length), Absolute(Length),
/// Fractional spacing between other items. /// Fractional spacing between other items.
Fractional(Fractional), Fractional(Fractional),
/// A layouted child node. /// A layouted child node and how to align it vertically.
Frame(Rc<Frame>, Align), Frame(Rc<Frame>, Spec<Align>),
} }
impl<'a> FlowLayouter<'a> { impl<'a> FlowLayouter<'a> {
@ -135,8 +135,8 @@ impl<'a> FlowLayouter<'a> {
self.items.push(FlowItem::Fractional(v)); self.items.push(FlowItem::Fractional(v));
self.fr += v; self.fr += v;
} }
FlowChild::Node(ref node, align) => { FlowChild::Node(ref node) => {
self.layout_node(ctx, node, align); self.layout_node(ctx, node);
} }
} }
} }
@ -156,7 +156,17 @@ impl<'a> FlowLayouter<'a> {
} }
/// Layout a node. /// Layout a node.
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode, align: Align) { fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) {
let aligns = Spec::new(
// For non-expanding paragraphs it is crucial that we align the
// whole paragraph according to its internal alignment.
node.downcast::<ParNode>().map_or(Align::Left, |node| node.align),
// Vertical align node alignment is respected by the flow node.
node.downcast::<AlignNode>()
.and_then(|node| node.aligns.y)
.unwrap_or(Align::Top),
);
let frames = node.layout(ctx, &self.regions); let frames = node.layout(ctx, &self.regions);
let len = frames.len(); let len = frames.len();
for (i, frame) in frames.into_iter().enumerate() { for (i, frame) in frames.into_iter().enumerate() {
@ -165,7 +175,7 @@ impl<'a> FlowLayouter<'a> {
self.used.h += size.h; self.used.h += size.h;
self.used.w.set_max(size.w); self.used.w.set_max(size.w);
self.regions.current.h -= size.h; self.regions.current.h -= size.h;
self.items.push(FlowItem::Frame(frame.item, align)); self.items.push(FlowItem::Frame(frame.item, aligns));
if i + 1 < len { if i + 1 < len {
self.finish_region(); self.finish_region();
@ -177,43 +187,44 @@ impl<'a> FlowLayouter<'a> {
fn finish_region(&mut self) { fn finish_region(&mut self) {
// Determine the size of the flow in this region dependening on whether // Determine the size of the flow in this region dependening on whether
// the region expands. // the region expands.
let size = Size::new( let mut size = Size::new(
if self.expand.x { self.full.w } else { self.used.w }, if self.expand.x { self.full.w } else { self.used.w },
if self.expand.y || (self.fr.get() > 0.0 && self.full.h.is_finite()) { if self.expand.y { self.full.h } else { self.used.h },
self.full.h
} else {
self.used.h
},
); );
// Account for fractional spacing in the size calculation.
let remaining = self.full.h - self.used.h;
if self.fr.get() > 0.0 && self.full.h.is_finite() {
self.used.h = self.full.h;
size.h = self.full.h;
}
let mut output = Frame::new(size, size.h); let mut output = Frame::new(size, size.h);
let mut before = Length::zero(); let mut before = Length::zero();
let mut ruler = Align::Start; let mut ruler = Align::Top;
let mut first = true; let mut first = true;
// Place all frames. // Place all frames.
for item in self.items.drain(..) { for item in self.items.drain(..) {
match item { match item {
FlowItem::Absolute(v) => before += v, FlowItem::Absolute(v) => before += v,
FlowItem::Fractional(v) => { FlowItem::Fractional(v) => before += v.resolve(self.fr, remaining),
let remaining = self.full.h - self.used.h; FlowItem::Frame(frame, aligns) => {
before += v.resolve(self.fr, remaining); ruler = ruler.max(aligns.y);
}
FlowItem::Frame(frame, align) => {
ruler = ruler.max(align);
// Align vertically. // Align horizontally and vertically.
let range = before .. before + size.h - self.used.h; let x = aligns.x.resolve(Length::zero() .. size.w - frame.size.w);
let y = ruler.resolve(Dir::TTB, range); let y = ruler.resolve(before .. before + size.h - self.used.h);
let pos = Point::new(x, y);
before += frame.size.h; before += frame.size.h;
// The baseline of the flow is that of the first frame. // The baseline of the flow is that of the first frame.
if first { if first {
output.baseline = y + frame.baseline; output.baseline = pos.y + frame.baseline;
first = false; first = false;
} }
output.push_frame(Point::new(Length::zero(), y), frame); output.push_frame(pos, frame);
} }
} }
} }

View File

@ -54,9 +54,9 @@ impl Layout for ImageNode {
let canvas = if expand.x && expand.y { let canvas = if expand.x && expand.y {
current current
} else if expand.x || (wide && current.w.is_finite()) { } else if expand.x || (wide && current.w.is_finite()) {
Size::new(current.w, current.w.safe_div(pixel_ratio)) Size::new(current.w, current.h.min(current.w.safe_div(pixel_ratio)))
} else if current.h.is_finite() { } else if current.h.is_finite() {
Size::new(current.h * pixel_ratio, current.h) Size::new(current.w.min(current.h * pixel_ratio), current.h)
} else { } else {
Size::new(Length::pt(pixel_w), Length::pt(pixel_h)) Size::new(Length::pt(pixel_w), Length::pt(pixel_h))
}; };

View File

@ -117,12 +117,10 @@ 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("start", Align::Start);
std.def_const("center", Align::Center);
std.def_const("end", Align::End);
std.def_const("left", Align::Left); std.def_const("left", Align::Left);
std.def_const("right", Align::Right);
std.def_const("top", Align::Top); std.def_const("top", Align::Top);
std.def_const("center", Align::Center);
std.def_const("right", Align::Right);
std.def_const("bottom", Align::Bottom); std.def_const("bottom", Align::Bottom);
std.def_const("serif", FontFamily::Serif); std.def_const("serif", FontFamily::Serif);
std.def_const("sans-serif", FontFamily::SansSerif); std.def_const("sans-serif", FontFamily::SansSerif);

View File

@ -36,6 +36,7 @@ pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
if let Some(dir) = dir { if let Some(dir) = dir {
par.dir = dir; par.dir = dir;
par.align = if dir == Dir::LTR { Align::Left } else { Align::Right };
} }
if let Some(leading) = leading { if let Some(leading) = leading {
@ -55,8 +56,10 @@ pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
/// A node that arranges its children into a paragraph. /// A node that arranges its children into a paragraph.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct ParNode { pub struct ParNode {
/// The inline direction of this paragraph. /// The text direction (either LTR or RTL).
pub dir: Dir, pub dir: Dir,
/// How to align text in its line.
pub align: Align,
/// The spacing to insert between each line. /// The spacing to insert between each line.
pub leading: Length, pub leading: Length,
/// The children to be arranged in a paragraph. /// The children to be arranged in a paragraph.
@ -124,9 +127,9 @@ pub enum ParChild {
/// Spacing between other nodes. /// Spacing between other nodes.
Spacing(Spacing), Spacing(Spacing),
/// A run of text and how to align it in its line. /// A run of text and how to align it in its line.
Text(EcoString, Align, Rc<TextStyle>), Text(EcoString, Rc<TextStyle>),
/// Any child node and how to align it in its line. /// Any child node and how to align it in its line.
Node(PackedNode, Align), Node(PackedNode),
/// A decoration that applies until a matching `Undecorate`. /// A decoration that applies until a matching `Undecorate`.
Decorate(Decoration), Decorate(Decoration),
/// The end of a decoration. /// The end of a decoration.
@ -137,8 +140,8 @@ impl Debug for ParChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Self::Spacing(v) => write!(f, "Spacing({:?})", v), Self::Spacing(v) => write!(f, "Spacing({:?})", v),
Self::Text(text, ..) => write!(f, "Text({:?})", text), Self::Text(text, _) => write!(f, "Text({:?})", text),
Self::Node(node, ..) => node.fmt(f), Self::Node(node) => node.fmt(f),
Self::Decorate(deco) => write!(f, "Decorate({:?})", deco), Self::Decorate(deco) => write!(f, "Decorate({:?})", deco),
Self::Undecorate => write!(f, "Undecorate"), Self::Undecorate => write!(f, "Undecorate"),
} }
@ -148,9 +151,9 @@ impl Debug for ParChild {
/// A paragraph representation in which children are already layouted and text /// A paragraph representation in which children are already layouted and text
/// is separated into shapable runs. /// is separated into shapable runs.
struct ParLayouter<'a> { struct ParLayouter<'a> {
/// The top-level direction. /// How to align text in its line.
dir: Dir, align: Align,
/// The line spacing. /// The spacing to insert between each line.
leading: Length, leading: Length,
/// Bidirectional text embedding levels for the paragraph. /// Bidirectional text embedding levels for the paragraph.
bidi: BidiInfo<'a>, bidi: BidiInfo<'a>,
@ -172,9 +175,9 @@ enum ParItem<'a> {
/// Fractional spacing between other items. /// Fractional spacing between other items.
Fractional(Fractional), Fractional(Fractional),
/// A shaped text run with consistent direction. /// A shaped text run with consistent direction.
Text(ShapedText<'a>, Align), Text(ShapedText<'a>),
/// A layouted child node. /// A layouted child node.
Frame(Frame, Align), Frame(Frame),
} }
impl<'a> ParLayouter<'a> { impl<'a> ParLayouter<'a> {
@ -202,7 +205,7 @@ impl<'a> ParLayouter<'a> {
items.push(ParItem::Fractional(v)); items.push(ParItem::Fractional(v));
ranges.push(range); ranges.push(range);
} }
ParChild::Text(_, align, ref style) => { ParChild::Text(_, ref style) => {
// TODO: Also split by language and script. // TODO: Also split by language and script.
let mut cursor = range.start; let mut cursor = range.start;
for (level, group) in bidi.levels[range].group_by_key(|&lvl| lvl) { for (level, group) in bidi.levels[range].group_by_key(|&lvl| lvl) {
@ -211,16 +214,16 @@ impl<'a> ParLayouter<'a> {
let subrange = start .. cursor; let subrange = start .. cursor;
let text = &bidi.text[subrange.clone()]; let text = &bidi.text[subrange.clone()];
let shaped = shape(ctx, text, style, level.dir()); let shaped = shape(ctx, text, style, level.dir());
items.push(ParItem::Text(shaped, align)); items.push(ParItem::Text(shaped));
ranges.push(subrange); ranges.push(subrange);
} }
} }
ParChild::Node(ref node, align) => { ParChild::Node(ref node) => {
let size = Size::new(regions.current.w, regions.base.h); let size = Size::new(regions.current.w, regions.base.h);
let expand = Spec::splat(false); let expand = Spec::splat(false);
let pod = Regions::one(size, regions.base, expand); let pod = Regions::one(size, regions.base, expand);
let frame = node.layout(ctx, &pod).remove(0); let frame = node.layout(ctx, &pod).remove(0);
items.push(ParItem::Frame(Rc::take(frame.item), align)); items.push(ParItem::Frame(Rc::take(frame.item)));
ranges.push(range); ranges.push(range);
} }
ParChild::Decorate(ref deco) => { ParChild::Decorate(ref deco) => {
@ -234,7 +237,7 @@ impl<'a> ParLayouter<'a> {
} }
Self { Self {
dir: par.dir, align: par.align,
leading: par.leading, leading: par.leading,
bidi, bidi,
items, items,
@ -392,7 +395,7 @@ impl<'a> LineLayout<'a> {
// Reshape the last item if it's split in half. // Reshape the last item if it's split in half.
let mut last = None; let mut last = None;
if let Some((ParItem::Text(shaped, align), rest)) = items.split_last() { if let Some((ParItem::Text(shaped), rest)) = items.split_last() {
// Compute the range we want to shape, trimming whitespace at the // Compute the range we want to shape, trimming whitespace at the
// end of the line. // end of the line.
let base = par.ranges[last_idx].start; let base = par.ranges[last_idx].start;
@ -408,7 +411,7 @@ impl<'a> LineLayout<'a> {
if !range.is_empty() || rest.is_empty() { if !range.is_empty() || rest.is_empty() {
// Reshape that part. // Reshape that part.
let reshaped = shaped.reshape(ctx, range); let reshaped = shaped.reshape(ctx, range);
last = Some(ParItem::Text(reshaped, *align)); last = Some(ParItem::Text(reshaped));
} }
items = rest; items = rest;
@ -418,7 +421,7 @@ impl<'a> LineLayout<'a> {
// Reshape the start item if it's split in half. // Reshape the start item if it's split in half.
let mut first = None; let mut first = None;
if let Some((ParItem::Text(shaped, align), rest)) = items.split_first() { if let Some((ParItem::Text(shaped), rest)) = items.split_first() {
// Compute the range we want to shape. // Compute the range we want to shape.
let Range { start: base, end: first_end } = par.ranges[first_idx]; let Range { start: base, end: first_end } = par.ranges[first_idx];
let start = line.start; let start = line.start;
@ -429,7 +432,7 @@ impl<'a> LineLayout<'a> {
if range.len() < shaped.text.len() { if range.len() < shaped.text.len() {
if !range.is_empty() { if !range.is_empty() {
let reshaped = shaped.reshape(ctx, range); let reshaped = shaped.reshape(ctx, range);
first = Some(ParItem::Text(reshaped, *align)); first = Some(ParItem::Text(reshaped));
} }
items = rest; items = rest;
@ -446,8 +449,8 @@ impl<'a> LineLayout<'a> {
match *item { match *item {
ParItem::Absolute(v) => width += v, ParItem::Absolute(v) => width += v,
ParItem::Fractional(v) => fr += v, ParItem::Fractional(v) => fr += v,
ParItem::Text(ShapedText { size, baseline, .. }, _) ParItem::Text(ShapedText { size, baseline, .. })
| ParItem::Frame(Frame { size, baseline, .. }, _) => { | ParItem::Frame(Frame { size, baseline, .. }) => {
width += size.w; width += size.w;
top.set_max(baseline); top.set_max(baseline);
bottom.set_max(size.h - baseline); bottom.set_max(size.h - baseline);
@ -475,10 +478,9 @@ impl<'a> LineLayout<'a> {
let mut output = Frame::new(size, self.baseline); let mut output = Frame::new(size, self.baseline);
let mut offset = Length::zero(); let mut offset = Length::zero();
let mut ruler = Align::Start;
for (range, item) in self.reordered() { for (range, item) in self.reordered() {
let mut position = |mut frame: Frame, align: Align| { let mut position = |mut frame: Frame| {
// Decorate. // Decorate.
for (deco_range, deco) in &self.par.decos { for (deco_range, deco) in &self.par.decos {
if deco_range.contains(&range.start) { if deco_range.contains(&range.start) {
@ -486,9 +488,7 @@ impl<'a> LineLayout<'a> {
} }
} }
// FIXME: Ruler alignment for RTL. let x = self.par.align.resolve(offset .. remaining + offset);
ruler = ruler.max(align);
let x = ruler.resolve(self.par.dir, offset .. remaining + offset);
let y = self.baseline - frame.baseline; let y = self.baseline - frame.baseline;
offset += frame.size.w; offset += frame.size.w;
@ -499,8 +499,8 @@ impl<'a> LineLayout<'a> {
match *item { match *item {
ParItem::Absolute(v) => offset += v, ParItem::Absolute(v) => offset += v,
ParItem::Fractional(v) => offset += v.resolve(self.fr, remaining), ParItem::Fractional(v) => offset += v.resolve(self.fr, remaining),
ParItem::Text(ref shaped, align) => position(shaped.build(), align), ParItem::Text(ref shaped) => position(shaped.build()),
ParItem::Frame(ref frame, align) => position(frame.clone(), align), ParItem::Frame(ref frame) => position(frame.clone()),
} }
} }

View File

@ -140,7 +140,7 @@ impl Layout for ShapeNode {
// When there's no child, fill the area if expansion is on, // When there's no child, fill the area if expansion is on,
// otherwise fall back to a default size. // otherwise fall back to a default size.
let default = Length::pt(30.0); let default = Length::pt(30.0);
let size = Size::new( let mut size = Size::new(
if regions.expand.x { if regions.expand.x {
regions.current.w regions.current.w
} else { } else {
@ -154,6 +154,11 @@ impl Layout for ShapeNode {
if regions.expand.y { regions.current.h } else { default }, if regions.expand.y { regions.current.h } else { default },
); );
if matches!(self.kind, ShapeKind::Square | ShapeKind::Circle) {
size.w = size.w.min(size.h);
size.h = size.w;
}
Frame::new(size, size.h) Frame::new(size, size.h)
}; };
@ -171,6 +176,13 @@ impl Layout for ShapeNode {
frame.prepend(pos, Element::Geometry(geometry, fill)); frame.prepend(pos, Element::Geometry(geometry, fill));
} }
// Ensure frame size matches regions size if expansion is on.
let expand = regions.expand;
frame.size = Size::new(
if expand.x { regions.current.w } else { frame.size.w },
if expand.y { regions.current.h } else { frame.size.h },
);
// Return tight constraints for now. // Return tight constraints for now.
let mut cts = Constraints::new(regions.expand); let mut cts = Constraints::new(regions.expand);
cts.exact = regions.current.to_spec().map(Some); cts.exact = regions.current.to_spec().map(Some);

View File

@ -23,7 +23,7 @@ pub fn block(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
/// A node that sizes its child. /// A node that sizes its child.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct SizedNode { pub struct SizedNode {
/// The node to-be-sized. /// The node to be sized.
pub child: PackedNode, pub child: PackedNode,
/// How to size the node horizontally and vertically. /// How to size the node horizontally and vertically.
pub sizing: Spec<Option<Linear>>, pub sizing: Spec<Option<Linear>>,

View File

@ -1,7 +1,7 @@
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use super::prelude::*; use super::prelude::*;
use super::Spacing; use super::{AlignNode, Spacing};
/// `stack`: Stack children along an axis. /// `stack`: Stack children along an axis.
pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
@ -118,7 +118,7 @@ enum StackItem {
/// Fractional spacing between other items. /// Fractional spacing between other items.
Fractional(Fractional), Fractional(Fractional),
/// A layouted child node. /// A layouted child node.
Frame(Rc<Frame>), Frame(Rc<Frame>, Align),
} }
impl<'a> StackLayouter<'a> { impl<'a> StackLayouter<'a> {
@ -176,6 +176,12 @@ impl<'a> StackLayouter<'a> {
/// Layout a node. /// Layout a node.
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) { fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) {
// Align nodes' block-axis alignment is respected by the stack node.
let align = node
.downcast::<AlignNode>()
.and_then(|node| node.aligns.get(self.axis))
.unwrap_or(self.stack.dir.start().into());
let frames = node.layout(ctx, &self.regions); let frames = node.layout(ctx, &self.regions);
let len = frames.len(); let len = frames.len();
for (i, frame) in frames.into_iter().enumerate() { for (i, frame) in frames.into_iter().enumerate() {
@ -184,7 +190,7 @@ impl<'a> StackLayouter<'a> {
self.used.block += size.block; self.used.block += size.block;
self.used.inline.set_max(size.inline); self.used.inline.set_max(size.inline);
*self.regions.current.get_mut(self.axis) -= size.block; *self.regions.current.get_mut(self.axis) -= size.block;
self.items.push(StackItem::Frame(frame.item)); self.items.push(StackItem::Frame(frame.item, align));
if i + 1 < len { if i + 1 < len {
self.finish_region(); self.finish_region();
@ -204,29 +210,35 @@ impl<'a> StackLayouter<'a> {
// Expand fully if there are fr spacings. // Expand fully if there are fr spacings.
let full = self.full.get(self.axis); let full = self.full.get(self.axis);
let remaining = full - self.used.block;
if self.fr.get() > 0.0 && full.is_finite() { if self.fr.get() > 0.0 && full.is_finite() {
self.used.block = full;
size.set(self.axis, full); size.set(self.axis, full);
} }
let mut output = Frame::new(size, size.h); let mut output = Frame::new(size, size.h);
let mut before = Length::zero(); let mut before = Length::zero();
let mut ruler: Align = self.stack.dir.start().into();
// Place all frames. // Place all frames.
for item in self.items.drain(..) { for item in self.items.drain(..) {
match item { match item {
StackItem::Absolute(v) => before += v, StackItem::Absolute(v) => before += v,
StackItem::Fractional(v) => { StackItem::Fractional(v) => before += v.resolve(self.fr, remaining),
let remaining = self.full.get(self.axis) - self.used.block; StackItem::Frame(frame, align) => {
before += v.resolve(self.fr, remaining) ruler = ruler.max(align);
}
StackItem::Frame(frame) => { // 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 = if self.stack.dir.is_positive() { let block = ruler.resolve(if self.stack.dir.is_positive() {
before let after = self.used.block - before;
before .. parent - after
} else { } else {
parent - (before + child) let before_with_self = before + child;
}; let after = self.used.block - before_with_self;
after .. parent - before_with_self
});
before += child; before += child;

View File

@ -16,8 +16,6 @@ use crate::util::EcoString;
/// Defines a set of properties a template can be instantiated with. /// Defines a set of properties a template can be instantiated with.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Style { pub struct Style {
/// The alignments of layouts in their parents.
pub aligns: Gen<Align>,
/// The page settings. /// The page settings.
pub page: Rc<PageStyle>, pub page: Rc<PageStyle>,
/// The paragraph settings. /// The paragraph settings.
@ -56,7 +54,6 @@ impl Style {
impl Default for Style { impl Default for Style {
fn default() -> Self { fn default() -> Self {
Self { Self {
aligns: Gen::splat(Align::Start),
page: Rc::new(PageStyle::default()), page: Rc::new(PageStyle::default()),
par: Rc::new(ParStyle::default()), par: Rc::new(ParStyle::default()),
text: Rc::new(TextStyle::default()), text: Rc::new(TextStyle::default()),
@ -103,8 +100,10 @@ impl Default for PageStyle {
/// Defines style properties of paragraphs. /// Defines style properties of paragraphs.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct ParStyle { pub struct ParStyle {
/// The direction for text and other inline objects. /// The direction for text and inline objects.
pub dir: Dir, pub dir: Dir,
/// How to align text and inline objects in their line.
pub align: Align,
/// The spacing between lines (dependent on scaled font size). /// The spacing between lines (dependent on scaled font size).
pub leading: Linear, pub leading: Linear,
/// The spacing between paragraphs (dependent on scaled font size). /// The spacing between paragraphs (dependent on scaled font size).
@ -115,6 +114,7 @@ impl Default for ParStyle {
fn default() -> Self { fn default() -> Self {
Self { Self {
dir: Dir::LTR, dir: Dir::LTR,
align: Align::Left,
leading: Relative::new(0.65).into(), leading: Relative::new(0.65).into(),
spacing: Relative::new(1.2).into(), spacing: Relative::new(1.2).into(),
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

BIN
tests/ref/layout/align.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -163,7 +163,6 @@
--- ---
// Test with operator. // Test with operator.
// Ref: true
// Apply positional arguments. // Apply positional arguments.
#let add(x, y) = x + y #let add(x, y) = x + y
@ -175,6 +174,9 @@
#test(f(4), 6) #test(f(4), 6)
// Make sure that named arguments are overridable. // Make sure that named arguments are overridable.
#let align with (horizontal: right) #let inc(x, y: 1) = x + y
#align[Right] \ #test(inc(1), 2)
#align(horizontal: left)[Left]
#let inc with (y: 2)
#test(inc(2), 4)
#test(inc(2, y: 4), 6)

View File

@ -1,35 +1,19 @@
// Configuration with `page` and `font` functions.
#page(width: 450pt, margins: 1cm) #page(width: 450pt, margins: 1cm)
// There are variables and they can take normal values like strings, ... *Technische Universität Berlin* #h(1fr) *WiSe 2019/2020* \
#let city = "Berlin" *Fakultät II, Institut for Mathematik* #h(1fr) Woche 3 \
// ... but also "template" values. While these contain markup,
// they are also values and can be summed, stored in arrays etc.
// There are also more standard control flow structures, like #if and #for.
#let university = [*Technische Universität {city}*]
#let faculty = [*Fakultät II, Institut for Mathematik*]
// Backslashs add forced line breaks.
#university #align(right)[*WiSe 2019/2020*] \
#faculty #align(right)[Woche 3] \
Sekretariat MA \ Sekretariat MA \
Dr. Max Mustermann \ Dr. Max Mustermann \
Ola Nordmann, John Doe Ola Nordmann, John Doe
// Adds vertical spacing.
#v(6mm) #v(6mm)
// If the last argument to a function is a template, we can also place it behind
// the parentheses.
#align(center)[ #align(center)[
// Markdown-like syntax for headings.
==== 3. Übungsblatt Computerorientierte Mathematik II #v(4mm) ==== 3. Übungsblatt Computerorientierte Mathematik II #v(4mm)
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) #v(4mm) *Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) #v(4mm)
*Alle Antworten sind zu beweisen.* *Alle Antworten sind zu beweisen.*
] ]
*1. Aufgabe* #align(right)[(1 + 1 + 2 Punkte)] *1. Aufgabe* #h(1fr) (1 + 1 + 2 Punkte)
Ein _Binärbaum_ ist ein Wurzelbaum, in dem jeder Knoten 2 Kinder hat. Ein _Binärbaum_ ist ein Wurzelbaum, in dem jeder Knoten 2 Kinder hat.
Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel
@ -37,7 +21,4 @@ zu _v_, und die Höhe von _v_ ist die Länge eines längsten (absteigenden) Wege
von _v_ zu einem Blatt. Die Höhe des Baumes ist die Höhe der Wurzel. von _v_ zu einem Blatt. Die Höhe des Baumes ist die Höhe der Wurzel.
#v(6mm) #v(6mm)
// The `image` function returns a "template" value of the same type as
// the `[...]` literals.
#align(center, image("../res/graph.png", width: 75%)) #align(center, image("../res/graph.png", width: 75%))

View File

@ -9,8 +9,7 @@
Auto-sized circle. \ Auto-sized circle. \
#circle(fill: rgb("eb5278"))[ #circle(fill: rgb("eb5278"))[
#align(center, center) #align(center, center)[But, soft!]
But, soft!
] ]
Center-aligned rect in auto-sized circle. Center-aligned rect in auto-sized circle.
@ -30,15 +29,19 @@ Rect in auto-sized circle. \
Expanded by height. Expanded by height.
#circle(fill: conifer)[A \ B \ C] #circle(fill: conifer)[A \ B \ C]
---
// Ensure circle directly in rect works.
#rect(width: 40pt, height: 30pt, circle(fill: forest))
--- ---
// Test relative sizing. // Test relative sizing.
#rect(width: 100pt, height: 50pt, fill: rgb("aaa"))[ #let centered(body) = align(center, center, body)
#align(center, center) #font(fill: white)
#font(fill: white) #rect(width: 100pt, height: 50pt, fill: rgb("aaa"), centered[
#circle(radius: 10pt, fill: eastern)[A] // D=20pt #circle(radius: 10pt, fill: eastern, centered[A]) // D=20pt
#circle(height: 60%, fill: eastern)[B] // D=30pt #circle(height: 60%, fill: eastern, centered[B]) // D=30pt
#circle(width: 20% + 20pt, fill: eastern)[C] // D=40pt #circle(width: 20% + 20pt, fill: eastern, centered[C]) // D=40pt
] ])
--- ---
// Radius wins over width and height. // Radius wins over width and height.

View File

@ -8,11 +8,12 @@
Rect in ellipse in fixed rect. \ Rect in ellipse in fixed rect. \
#rect(width: 3cm, height: 2cm, fill: rgb("2a631a"), #rect(width: 3cm, height: 2cm, fill: rgb("2a631a"),
ellipse(fill: forest, ellipse(fill: forest,
rect(fill: conifer)[ rect(fill: conifer,
#align(center, center) align(center, center)[
Stuff inside an ellipse! Stuff inside an ellipse!
] ]
) )
)
) )
Auto-sized ellipse. \ Auto-sized ellipse. \

View File

@ -21,8 +21,7 @@
#image("../../res/tiger.jpg", width: 100%, height: 20pt, fit: "stretch") #image("../../res/tiger.jpg", width: 100%, height: 20pt, fit: "stretch")
// Make sure the bounding-box of the image is correct. // Make sure the bounding-box of the image is correct.
#align(bottom, right) #align(bottom, right, image("../../res/tiger.jpg", width: 40pt))
#image("../../res/tiger.jpg", width: 40pt)
--- ---
// Test all three fit modes. // Test all three fit modes.

View File

@ -9,8 +9,7 @@
// Test auto-sized square. // Test auto-sized square.
#square(fill: eastern)[ #square(fill: eastern)[
#font(fill: white, weight: "bold") #font(fill: white, weight: "bold")
#align(center) #align(center, pad(5pt)[Typst])
#pad(5pt)[Typst]
] ]
--- ---

View File

@ -0,0 +1,21 @@
// Test alignment.
---
#page(height: 100pt)
#stack(dir: ltr,
align(left, square(size: 15pt, fill: eastern)),
align(center, square(size: 20pt, fill: eastern)),
align(right, square(size: 15pt, fill: eastern)),
)
#align(center, center, rect(fill: eastern, height: 10pt))
#align(bottom, stack(
align(center, rect(fill: conifer, height: 10pt)),
rect(fill: forest, height: 10pt),
))
---
#align(center)[
Lorem Ipsum
Dolor
]

View File

@ -19,24 +19,3 @@ First!
But, soft! what light through yonder window breaks? It is the east, and Juliet But, soft! what light through yonder window breaks? It is the east, and Juliet
is the sun. is the sun.
] ]
---
// Test shrink-to-fit vs expand.
// Top-level paragraph fills page
L #align(right)[R]
// Block also fills page.
#block[
L #align(right)[R]
]
// Boxed paragraph respects width.
#box(width: 50pt)[
L #align(right)[R]
]
// Boxed paragraph without width doesn't expand.
#box[
L #align(right)[R]
]

View File

@ -34,11 +34,10 @@
--- ---
#page(height: 3cm, margins: 0pt) #page(height: 3cm, margins: 0pt)
#align(center)
#grid( #grid(
columns: (1fr,), columns: (1fr,),
rows: (1fr, auto, 2fr), rows: (1fr, auto, 2fr),
[], [],
[A bit more to the top], align(center)[A bit more to the top],
[], [],
) )

View File

@ -24,7 +24,7 @@
row-gutter: 10pt, row-gutter: 10pt,
column-gutter: (0pt, 10%), column-gutter: (0pt, 10%),
align(top, image("../../res/rhino.png")), align(top, image("../../res/rhino.png")),
align(right, rect(width: 100%, fill: eastern)[LoL]), align(top, rect(fill: eastern, align(right)[LoL])),
[rofl], [rofl],
[\ A] * 3, [\ A] * 3,
[Ha!\ ] * 3, [Ha!\ ] * 3,
@ -55,7 +55,7 @@
column-gutter: (0pt, 10%), column-gutter: (0pt, 10%),
[A], [B], [C], [D], [A], [B], [C], [D],
grid(columns: 2, [A], [B], [C\ ]*3, [D]), grid(columns: 2, [A], [B], [C\ ]*3, [D]),
align(right, rect(width: 100%, fill: eastern)[LoL]), align(top, rect(fill: eastern, align(right)[LoL])),
[rofl], [rofl],
[E\ ]*4, [E\ ]*4,
) )

View File

@ -16,13 +16,14 @@
#grid( #grid(
columns: 2, columns: 2,
gutter: 10pt, gutter: 10pt,
[#align(bottom) A], align(bottom)[A],
[ [
Top Top
#align(bottom) #align(bottom)[
Bottom \ Bottom \
Bottom \ Bottom \
Top Top
]
], ],
[#align(top) B], align(top)[B],
) )

View File

@ -15,10 +15,10 @@
// Set individual margins. // Set individual margins.
#page(height: 40pt) #page(height: 40pt)
[#page(left: 0pt) #align(left) Left] [#page(left: 0pt) #align(left)[Left]]
[#page(right: 0pt) #align(right) Right] [#page(right: 0pt) #align(right)[Right]]
[#page(top: 0pt) #align(top) Top] [#page(top: 0pt) #align(top)[Top]]
[#page(bottom: 0pt) #align(bottom) Bottom] [#page(bottom: 0pt) #align(bottom)[Bottom]]
// Ensure that specific margins override general margins. // Ensure that specific margins override general margins.
[#page(margins: 0pt, left: 20pt) Overriden] [#page(margins: 0pt, left: 20pt) Overriden]

View File

@ -1,28 +0,0 @@
// Test text alignment.
---
// Test that alignment depends on the paragraph's full width.
#box[
Hello World \
#align(right)[World]
]
---
// Test that a line with multiple alignments respects the paragraph's full
// width.
#box[
Hello #align(center)[World] \
Hello from the World
]
---
// Test that `start` alignment after `end` alignment doesn't do anything until
// the next line break ...
L #align(right)[R] R
// ... but make sure it resets to left after the line break.
L #align(right)[R] \ L
---
// FIXME: There should be a line break opportunity on alignment change.
LLLLLLLLLLLLLLLLL#align(center)[CCCC]