Refactor layouting base ♻

This commit is contained in:
Laurenz 2019-11-30 18:54:46 +01:00
parent b13ed627ff
commit 5782b82770
16 changed files with 228 additions and 321 deletions

View File

@ -13,7 +13,7 @@ pub mod helpers;
pub mod prelude { pub mod prelude {
pub use crate::func::{Command, CommandList, Function}; pub use crate::func::{Command, CommandList, Function};
pub use crate::layout::{layout_tree, Layout, MultiLayout, LayoutContext, LayoutSpace}; pub use crate::layout::{layout_tree, Layout, MultiLayout, LayoutContext, LayoutSpace};
pub use crate::layout::{LayoutAxes, AlignedAxis, Axis, Alignment}; pub use crate::layout::{LayoutAxes, Axis, Alignment};
pub use crate::layout::{LayoutError, LayoutResult}; pub use crate::layout::{LayoutError, LayoutResult};
pub use crate::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span}; pub use crate::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span};
pub use crate::syntax::{parse, ParseContext, ParseError, ParseResult}; pub use crate::syntax::{parse, ParseContext, ParseError, ParseResult};
@ -86,6 +86,9 @@ where T: Debug + PartialEq + 'static
} }
} }
/// A sequence of commands requested for execution by a function.
pub type CommandList<'a> = Vec<Command<'a>>;
/// Commands requested for execution by functions. /// Commands requested for execution by functions.
#[derive(Debug)] #[derive(Debug)]
pub enum Command<'a> { pub enum Command<'a> {
@ -109,60 +112,6 @@ pub enum Command<'a> {
SetAxes(LayoutAxes), SetAxes(LayoutAxes),
} }
/// A sequence of commands requested for execution by a function.
#[derive(Debug)]
pub struct CommandList<'a> {
pub commands: Vec<Command<'a>>,
}
impl<'a> CommandList<'a> {
/// Create an empty command list.
pub fn new() -> CommandList<'a> {
CommandList { commands: vec![] }
}
/// Create a command list with commands from a vector.
pub fn from_vec(commands: Vec<Command<'a>>) -> CommandList<'a> {
CommandList { commands }
}
/// Add a command to the sequence.
pub fn add(&mut self, command: Command<'a>) {
self.commands.push(command);
}
/// Whether there are any commands in this sequence.
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
}
impl<'a> IntoIterator for CommandList<'a> {
type Item = Command<'a>;
type IntoIter = std::vec::IntoIter<Command<'a>>;
fn into_iter(self) -> Self::IntoIter {
self.commands.into_iter()
}
}
impl<'a> IntoIterator for &'a CommandList<'a> {
type Item = &'a Command<'a>;
type IntoIter = std::slice::Iter<'a, Command<'a>>;
fn into_iter(self) -> Self::IntoIter {
self.commands.iter()
}
}
/// Create a list of commands.
#[macro_export]
macro_rules! commands {
($($x:expr),*$(,)*) => (
$crate::func::CommandList::from_vec(vec![$($x,)*])
);
}
/// A map from identifiers to functions. /// A map from identifiers to functions.
pub struct Scope { pub struct Scope {
parsers: HashMap<String, Box<ParseFunc>>, parsers: HashMap<String, Box<ParseFunc>>,

View File

@ -1,10 +1,8 @@
//! Drawing and cofiguration actions composing layouts. //! Drawing and cofiguration actions composing layouts.
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::io::{self, Write};
use super::Layout; use super::*;
use crate::size::{Size, Size2D};
use LayoutAction::*; use LayoutAction::*;
/// A layouting action. /// A layouting action.
@ -21,9 +19,8 @@ pub enum LayoutAction {
DebugBox(Size2D, Size2D), DebugBox(Size2D, Size2D),
} }
impl LayoutAction { impl Serialize for LayoutAction {
/// Serialize this layout action into an easy-to-parse string representation. fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
match self { match self {
MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()), MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()),
SetFont(i, s) => write!(f, "f {} {}", i, s.to_pt()), SetFont(i, s) => write!(f, "f {} {}", i, s.to_pt()),
@ -121,10 +118,6 @@ impl LayoutActionList {
self.origin = position; self.origin = position;
self.next_pos = Some(position); self.next_pos = Some(position);
if layout.debug_render {
self.actions.push(DebugBox(position, layout.dimensions));
}
self.extend(layout.actions); self.extend(layout.actions);
} }

View File

@ -41,7 +41,7 @@ struct PartialLine {
usable: Size, usable: Size,
content: Vec<(Size, Layout)>, content: Vec<(Size, Layout)>,
dimensions: Size2D, dimensions: Size2D,
space: SpaceState, space: LastSpacing,
} }
impl PartialLine { impl PartialLine {
@ -50,7 +50,7 @@ impl PartialLine {
usable, usable,
content: vec![], content: vec![],
dimensions: Size2D::zero(), dimensions: Size2D::zero(),
space: SpaceState::Forbidden, space: LastSpacing::Forbidden,
} }
} }
} }
@ -237,7 +237,7 @@ impl FlexLayouter {
} }
} }
if let SpaceState::Soft(space) = self.part.space { if let LastSpacing::Soft(space) = self.part.space {
self.layout_space(space, SpaceKind::Hard); self.layout_space(space, SpaceKind::Hard);
} }
@ -246,15 +246,15 @@ impl FlexLayouter {
self.part.dimensions.x += size.x; self.part.dimensions.x += size.x;
self.part.dimensions.y.max_eq(size.y); self.part.dimensions.y.max_eq(size.y);
self.part.space = SpaceState::Allowed; self.part.space = LastSpacing::Allowed;
Ok(()) Ok(())
} }
fn layout_space(&mut self, space: Size, kind: SpaceKind) { fn layout_space(&mut self, space: Size, kind: SpaceKind) {
if kind == SpaceKind::Soft { if kind == SpaceKind::Soft {
if self.part.space != SpaceState::Forbidden { if self.part.space != LastSpacing::Forbidden {
self.part.space = SpaceState::Soft(space); self.part.space = LastSpacing::Soft(space);
} }
} else { } else {
if self.part.dimensions.x + space > self.part.usable { if self.part.dimensions.x + space > self.part.usable {
@ -264,7 +264,7 @@ impl FlexLayouter {
} }
if kind == SpaceKind::Hard { if kind == SpaceKind::Hard {
self.part.space = SpaceState::Forbidden; self.part.space = LastSpacing::Forbidden;
} }
} }
} }

View File

@ -9,7 +9,7 @@ use toddle::Error as FontError;
use crate::func::Command; use crate::func::Command;
use crate::size::{Size, Size2D, SizeBox}; use crate::size::{Size, Size2D, SizeBox};
use crate::style::{PageStyle, TextStyle}; use crate::style::{LayoutStyle, TextStyle};
use crate::syntax::{FuncCall, Node, SyntaxTree}; use crate::syntax::{FuncCall, Node, SyntaxTree};
mod actions; mod actions;
@ -29,99 +29,20 @@ pub mod layouters {
pub use actions::{LayoutAction, LayoutActionList}; pub use actions::{LayoutAction, LayoutActionList};
pub use layouters::*; pub use layouters::*;
/// A collection of layouts.
pub type MultiLayout = Vec<Layout>;
/// A sequence of layouting actions inside a box. /// A sequence of layouting actions inside a box.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Layout { pub struct Layout {
/// The size of the box. /// The size of the box.
pub dimensions: Size2D, pub dimensions: Size2D,
/// The baseline of the layout (as an offset from the top-left).
pub baseline: Option<Size>,
/// How to align this layout in a parent container.
pub alignment: LayoutAlignment,
/// The actions composing this layout. /// The actions composing this layout.
pub actions: Vec<LayoutAction>, pub actions: Vec<LayoutAction>,
/// Whether to debug-render this box.
pub debug_render: bool,
}
impl Layout {
/// Serialize this layout into an output buffer.
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
writeln!(
f,
"{:.4} {:.4}",
self.dimensions.x.to_pt(),
self.dimensions.y.to_pt()
)?;
writeln!(f, "{}", self.actions.len())?;
for action in &self.actions {
action.serialize(f)?;
writeln!(f)?;
}
Ok(())
}
}
/// A collection of layouts.
#[derive(Debug, Clone)]
pub struct MultiLayout {
pub layouts: Vec<Layout>,
}
impl MultiLayout {
/// Create an empty multi-layout.
pub fn new() -> MultiLayout {
MultiLayout { layouts: vec![] }
}
/// Extract the single sublayout. This panics if the layout does not have
/// exactly one child.
pub fn into_single(mut self) -> Layout {
if self.layouts.len() != 1 {
panic!("into_single: contains not exactly one layout");
}
self.layouts.pop().unwrap()
}
/// Add a sublayout.
pub fn add(&mut self, layout: Layout) {
self.layouts.push(layout);
}
/// The count of sublayouts.
pub fn count(&self) -> usize {
self.layouts.len()
}
/// Whether this layout contains any sublayouts.
pub fn is_empty(&self) -> bool {
self.layouts.is_empty()
}
}
impl MultiLayout {
/// Serialize this collection of layouts into an output buffer.
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
writeln!(f, "{}", self.count())?;
for layout in self {
layout.serialize(f)?;
}
Ok(())
}
}
impl IntoIterator for MultiLayout {
type Item = Layout;
type IntoIter = std::vec::IntoIter<Layout>;
fn into_iter(self) -> Self::IntoIter {
self.layouts.into_iter()
}
}
impl<'a> IntoIterator for &'a MultiLayout {
type Item = &'a Layout;
type IntoIter = std::slice::Iter<'a, Layout>;
fn into_iter(self) -> Self::IntoIter {
self.layouts.iter()
}
} }
/// The general context for layouting. /// The general context for layouting.
@ -130,20 +51,16 @@ pub struct LayoutContext<'a, 'p> {
/// The font loader to retrieve fonts from when typesetting text /// The font loader to retrieve fonts from when typesetting text
/// using [`layout_text`]. /// using [`layout_text`].
pub loader: &'a SharedFontLoader<'p>, pub loader: &'a SharedFontLoader<'p>,
/// The style for pages and text.
pub style: &'a LayoutStyle,
/// Whether this layouting process handles the top-level pages. /// Whether this layouting process handles the top-level pages.
pub top_level: bool, pub top_level: bool,
/// The style to set text with. This includes sizes and font classes
/// which determine which font from the loaders selection is used.
pub text_style: &'a TextStyle,
/// The current size and margins of the top-level pages.
pub page_style: PageStyle,
/// The spaces to layout in. /// The spaces to layout in.
pub spaces: LayoutSpaces, pub spaces: LayoutSpaces,
/// The axes to flow on. /// The initial axes along which content is laid out.
pub axes: LayoutAxes, pub axes: LayoutAxes,
/// Whether layouts should expand to the full dimensions of the space /// The alignment for the two axes.
/// they lie on or whether should tightly fit the content. pub alignment: LayoutAlignment,
pub expand: bool,
} }
/// A possibly stack-allocated vector of layout spaces. /// A possibly stack-allocated vector of layout spaces.
@ -154,26 +71,31 @@ pub type LayoutSpaces = SmallVec<[LayoutSpace; 2]>;
pub struct LayoutSpace { pub struct LayoutSpace {
/// The maximum size of the box to layout in. /// The maximum size of the box to layout in.
pub dimensions: Size2D, pub dimensions: Size2D,
/// Whether to expand the dimensions of the resulting layout to the full
/// dimensions of this space or to shrink them to fit the content for the
/// vertical and horizontal axis.
pub expand: (bool, bool),
/// Padding that should be respected on each side. /// Padding that should be respected on each side.
pub padding: SizeBox, pub padding: SizeBox,
} }
impl LayoutSpace { impl LayoutSpace {
/// The actually usable area (dimensions minus padding).
pub fn usable(&self) -> Size2D {
self.dimensions.unpadded(self.padding)
}
/// The offset from the origin to the start of content, that is, /// The offset from the origin to the start of content, that is,
/// `(padding.left, padding.top)`. /// `(padding.left, padding.top)`.
pub fn start(&self) -> Size2D { pub fn start(&self) -> Size2D {
Size2D::new(self.padding.left, self.padding.right) Size2D::new(self.padding.left, self.padding.right)
} }
/// The actually usable area (dimensions minus padding).
pub fn usable(&self) -> Size2D {
self.dimensions.unpadded(self.padding)
}
/// A layout space without padding and dimensions reduced by the padding. /// A layout space without padding and dimensions reduced by the padding.
pub fn usable_space(&self) -> LayoutSpace { pub fn usable_space(&self) -> LayoutSpace {
LayoutSpace { LayoutSpace {
dimensions: self.usable(), dimensions: self.usable(),
expand: (false, false),
padding: SizeBox::zero(), padding: SizeBox::zero(),
} }
} }
@ -182,17 +104,21 @@ impl LayoutSpace {
/// The axes along which the content is laid out. /// The axes along which the content is laid out.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct LayoutAxes { pub struct LayoutAxes {
pub primary: AlignedAxis, pub primary: Axis,
pub secondary: AlignedAxis, pub secondary: Axis,
} }
impl LayoutAxes { impl LayoutAxes {
pub fn new(primary: Axis, secondary: Axis) -> LayoutAxes {
LayoutAxes { primary, secondary }
}
/// Returns the generalized version of a `Size2D` dependent on /// Returns the generalized version of a `Size2D` dependent on
/// the layouting axes, that is: /// the layouting axes, that is:
/// - The x coordinate describes the primary axis instead of the horizontal one. /// - The x coordinate describes the primary axis instead of the horizontal one.
/// - The y coordinate describes the secondary axis instead of the vertical one. /// - The y coordinate describes the secondary axis instead of the vertical one.
pub fn generalize(&self, size: Size2D) -> Size2D { pub fn generalize(&self, size: Size2D) -> Size2D {
if self.primary.axis.is_horizontal() { if self.primary.is_horizontal() {
size size
} else { } else {
Size2D { x: size.y, y: size.x } Size2D { x: size.y, y: size.x }
@ -206,58 +132,9 @@ impl LayoutAxes {
// at the call site, we still have this second function. // at the call site, we still have this second function.
self.generalize(size) self.generalize(size)
} }
/// The position of the anchor specified by the two aligned axes
/// in the given generalized space.
pub fn anchor(&self, space: Size2D) -> Size2D {
Size2D::new(self.primary.anchor(space.x), self.secondary.anchor(space.y))
} }
/// This axes with `expand` set to the given value for both axes. /// Directions along which content is laid out.
pub fn expanding(&self, expand: bool) -> LayoutAxes {
LayoutAxes {
primary: self.primary.expanding(expand),
secondary: self.secondary.expanding(expand),
}
}
}
/// An axis with an alignment.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct AlignedAxis {
pub axis: Axis,
pub alignment: Alignment,
pub expand: bool,
}
impl AlignedAxis {
/// Creates an aligned axis from its three components.
pub fn new(axis: Axis, alignment: Alignment, expand: bool) -> AlignedAxis {
AlignedAxis { axis, alignment, expand }
}
/// The position of the anchor specified by this axis on the given line.
pub fn anchor(&self, line: Size) -> Size {
use Alignment::*;
match (self.axis.is_positive(), self.alignment) {
(true, Origin) | (false, End) => Size::zero(),
(_, Center) => line / 2,
(true, End) | (false, Origin) => line,
}
}
/// This axis with `expand` set to the given value.
pub fn expanding(&self, expand: bool) -> AlignedAxis {
AlignedAxis { expand, ..*self }
}
/// Whether this axis needs expansion.
pub fn needs_expansion(&self) -> bool {
self.expand || self.alignment != Alignment::Origin
}
}
/// Where to put content.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Axis { pub enum Axis {
LeftToRight, LeftToRight,
@ -292,6 +169,19 @@ impl Axis {
} }
} }
/// The place to put a layout in a container.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct LayoutAlignment {
pub primary: Alignment,
pub secondary: Alignment,
}
impl LayoutAlignment {
pub fn new(primary: Alignment, secondary: Alignment) -> LayoutAlignment {
LayoutAlignment { primary, secondary }
}
}
/// Where to align content. /// Where to align content.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Alignment { pub enum Alignment {
@ -300,26 +190,74 @@ pub enum Alignment {
End, End,
} }
/// The specialized anchor position for an item with the given alignment in a
/// container with a given size along the given axis.
pub fn anchor(axis: Axis, size: Size, alignment: Alignment) -> Size {
use Alignment::*;
match (axis.is_positive(), alignment) {
(true, Origin) | (false, End) => Size::zero(),
(_, Center) => size / 2,
(true, End) | (false, Origin) => size,
}
}
/// Whitespace between boxes with different interaction properties.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub enum SpaceKind { pub enum SpacingKind {
/// Soft spaces are eaten up by hard spaces before or after them. /// A hard space consumes surrounding soft spaces and is always layouted.
Soft,
/// Independent do not eat up soft spaces and are not eaten up by hard spaces.
Independent,
/// Hard spaces eat up soft spaces before or after them.
Hard, Hard,
/// A soft space consumes surrounding soft spaces with higher value.
Soft(u32),
} }
/// The standard spacing kind used for paragraph spacing.
const PARAGRAPH_KIND: SpacingKind = SpacingKind::Soft(1);
/// The standard spacing kind used for normal spaces between boxes.
const SPACE_KIND: SpacingKind = SpacingKind::Soft(2);
/// The last appeared spacing.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
enum SpaceState { enum LastSpacing {
Soft(Size), Hard,
Forbidden, Soft(Size, u32),
Allowed, None,
} }
impl SpaceState { impl LastSpacing {
fn soft_or_zero(&self) -> Size { fn soft_or_zero(&self) -> Size {
if let SpaceState::Soft(space) = self { *space } else { Size::zero() } match self {
LastSpacing::Soft(space, _) => *space,
_ => Size::zero(),
}
}
}
/// Layout components that can be serialized.
trait Serialize {
/// Serialize the data structure into an output writable.
fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()>;
}
impl Serialize for Layout {
fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
writeln!(f, "{:.4} {:.4}", self.dimensions.x.to_pt(), self.dimensions.y.to_pt())?;
writeln!(f, "{}", self.actions.len())?;
for action in &self.actions {
action.serialize(f)?;
writeln!(f)?;
}
Ok(())
}
}
impl Serialize for MultiLayout {
fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
writeln!(f, "{}", self.len())?;
for layout in self {
layout.serialize(f)?;
}
Ok(())
} }
} }

View File

@ -1,23 +1,54 @@
use smallvec::smallvec; use smallvec::smallvec;
use super::*; use super::*;
/// The stack layouter arranges boxes stacked onto each other.
///
/// The boxes are laid out in the direction of the secondary layouting axis and
/// are aligned along both axes.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct StackLayouter { pub struct StackLayouter {
/// The context for layouter.
ctx: StackContext, ctx: StackContext,
/// The output layouts.
layouts: MultiLayout, layouts: MultiLayout,
/// The full layout space.
space: Space, space: Space,
/// The currently active subspace.
sub: Subspace, sub: Subspace,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct Space { struct Space {
/// The index of this space in the list of spaces.
index: usize, index: usize,
/// Whether to add the layout for this space even if it would be empty.
hard: bool, hard: bool,
/// The layouting actions accumulated from the subspaces.
actions: LayoutActionList, actions: LayoutActionList,
/// The used size of this space from the top-left corner to
/// the bottomright-most point of used space (specialized).
combined_dimensions: Size2D, combined_dimensions: Size2D,
} }
#[derive(Debug, Clone)]
struct Subspace {
/// The axes along which contents in this subspace are laid out.
axes: LayoutAxes,
/// The beginning of this subspace in the parent space (specialized).
origin: Size2D,
/// The total usable space of this subspace (generalized).
usable: Size2D,
/// The used size of this subspace (generalized), with
/// - `x` being the maximum of the primary size of all boxes.
/// - `y` being the total extent of all boxes and space in the secondary
/// direction.
size: Size2D,
/// The so-far accumulated (offset, anchor, box) triples.
boxes: Vec<(Size, Size, Layout)>,
/// The last added spacing if the last was spacing.
last_spacing: LastSpacing,
}
impl Space { impl Space {
fn new(index: usize, hard: bool) -> Space { fn new(index: usize, hard: bool) -> Space {
Space { Space {
@ -29,20 +60,6 @@ impl Space {
} }
} }
#[derive(Debug, Clone)]
struct Subspace {
origin: Size2D,
anchor: Size2D,
factor: i32,
boxes: Vec<(Size, Size, Layout)>,
usable: Size2D,
dimensions: Size2D,
space: SpaceState,
}
impl Subspace { impl Subspace {
fn new(origin: Size2D, usable: Size2D, axes: LayoutAxes) -> Subspace { fn new(origin: Size2D, usable: Size2D, axes: LayoutAxes) -> Subspace {
Subspace { Subspace {
@ -52,7 +69,7 @@ impl Subspace {
boxes: vec![], boxes: vec![],
usable: axes.generalize(usable), usable: axes.generalize(usable),
dimensions: Size2D::zero(), dimensions: Size2D::zero(),
space: SpaceState::Forbidden, space: LastSpacing::Forbidden,
} }
} }
} }
@ -82,7 +99,7 @@ impl StackLayouter {
} }
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
if let SpaceState::Soft(space) = self.sub.space { if let LastSpacing::Soft(space) = self.sub.space {
self.add_space(space, SpaceKind::Hard); self.add_space(space, SpaceKind::Hard);
} }
@ -107,7 +124,7 @@ impl StackLayouter {
self.sub.boxes.push((offset, anchor, layout)); self.sub.boxes.push((offset, anchor, layout));
self.sub.dimensions = new_dimensions; self.sub.dimensions = new_dimensions;
self.sub.space = SpaceState::Allowed; self.sub.space = LastSpacing::Allowed;
Ok(()) Ok(())
} }
@ -121,8 +138,8 @@ impl StackLayouter {
pub fn add_space(&mut self, space: Size, kind: SpaceKind) { pub fn add_space(&mut self, space: Size, kind: SpaceKind) {
if kind == SpaceKind::Soft { if kind == SpaceKind::Soft {
if self.sub.space != SpaceState::Forbidden { if self.sub.space != LastSpacing::Forbidden {
self.sub.space = SpaceState::Soft(space); self.sub.space = LastSpacing::Soft(space);
} }
} else { } else {
if self.sub.dimensions.y + space > self.sub.usable.y { if self.sub.dimensions.y + space > self.sub.usable.y {
@ -132,7 +149,7 @@ impl StackLayouter {
} }
if kind == SpaceKind::Hard { if kind == SpaceKind::Hard {
self.sub.space = SpaceState::Forbidden; self.sub.space = LastSpacing::Forbidden;
} }
} }
} }

View File

@ -73,7 +73,6 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
Ok(Layout { Ok(Layout {
dimensions: Size2D::new(self.width, self.ctx.style.font_size), dimensions: Size2D::new(self.width, self.ctx.style.font_size),
actions: self.actions.to_vec(), actions: self.actions.to_vec(),
debug_render: false,
}) })
} }

View File

@ -11,7 +11,7 @@ pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiL
struct TreeLayouter<'a, 'p> { struct TreeLayouter<'a, 'p> {
ctx: LayoutContext<'a, 'p>, ctx: LayoutContext<'a, 'p>,
flex: FlexLayouter, flex: FlexLayouter,
style: TextStyle, style: LayoutStyle,
} }
impl<'a, 'p> TreeLayouter<'a, 'p> { impl<'a, 'p> TreeLayouter<'a, 'p> {
@ -19,12 +19,12 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> { fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> {
TreeLayouter { TreeLayouter {
flex: FlexLayouter::new(FlexContext { flex: FlexLayouter::new(FlexContext {
flex_spacing: flex_spacing(&ctx.text_style), flex_spacing: flex_spacing(&ctx.style.text),
spaces: ctx.spaces.clone(), spaces: ctx.spaces.clone(),
axes: ctx.axes, axes: ctx.axes,
expand: ctx.expand, expand: ctx.expand,
}), }),
style: ctx.text_style.clone(), style: ctx.style.clone(),
ctx, ctx,
} }
} }
@ -37,9 +37,9 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
Node::Space => self.layout_space(), Node::Space => self.layout_space(),
Node::Newline => self.layout_paragraph()?, Node::Newline => self.layout_paragraph()?,
Node::ToggleItalics => self.style.toggle_class(FontClass::Italic), Node::ToggleItalics => self.style.text.toggle_class(FontClass::Italic),
Node::ToggleBold => self.style.toggle_class(FontClass::Bold), Node::ToggleBold => self.style.text.toggle_class(FontClass::Bold),
Node::ToggleMonospace => self.style.toggle_class(FontClass::Monospace), Node::ToggleMonospace => self.style.text.toggle_class(FontClass::Monospace),
Node::Func(func) => self.layout_func(func)?, Node::Func(func) => self.layout_func(func)?,
} }
@ -51,34 +51,35 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
fn layout_text(&mut self, text: &str) -> LayoutResult<()> { fn layout_text(&mut self, text: &str) -> LayoutResult<()> {
let layout = layout_text(text, TextContext { let layout = layout_text(text, TextContext {
loader: &self.ctx.loader, loader: &self.ctx.loader,
style: &self.style, style: &self.style.text,
})?; })?;
Ok(self.flex.add(layout)) Ok(self.flex.add(layout))
} }
fn layout_space(&mut self) { fn layout_space(&mut self) {
self.flex.add_primary_space(word_spacing(&self.style), SpaceKind::Soft); self.flex.add_primary_space(
word_spacing(&self.style.text),
SPACE_KIND,
);
} }
fn layout_paragraph(&mut self) -> LayoutResult<()> { fn layout_paragraph(&mut self) -> LayoutResult<()> {
self.flex.add_secondary_space(paragraph_spacing(&self.style), SpaceKind::Soft) self.flex.add_secondary_space(
paragraph_spacing(&self.style.text),
PARAGRAPH_KIND,
)
} }
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
let spaces = self.flex.remaining(); let spaces = self.flex.remaining();
let mut axes = self.ctx.axes.expanding(false);
axes.secondary.alignment = Alignment::Origin;
let commands = func.body.val.layout(LayoutContext { let commands = func.body.val.layout(LayoutContext {
loader: self.ctx.loader, loader: self.ctx.loader,
style: &self.style,
top_level: false, top_level: false,
text_style: &self.style,
page_style: self.ctx.page_style,
spaces, spaces,
axes, .. self.ctx
expand: false,
})?; })?;
for command in commands { for command in commands {
@ -95,10 +96,8 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
Command::Add(layout) => self.flex.add(layout), Command::Add(layout) => self.flex.add(layout),
Command::AddMultiple(layouts) => self.flex.add_multiple(layouts), Command::AddMultiple(layouts) => self.flex.add_multiple(layouts),
Command::AddPrimarySpace(space) Command::AddPrimarySpace(space) => self.flex.add_primary_space(space, SpacingKind::Hard),
=> self.flex.add_primary_space(space, SpaceKind::Hard), Command::AddSecondarySpace(space) => self.flex.add_secondary_space(space, SpacingKind::Hard)?,
Command::AddSecondarySpace(space)
=> self.flex.add_secondary_space(space, SpaceKind::Hard)?,
Command::FinishLine => self.flex.add_break(), Command::FinishLine => self.flex.add_break(),
Command::FinishRun => { self.flex.finish_run()?; }, Command::FinishRun => { self.flex.finish_run()?; },
@ -106,16 +105,17 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
Command::BreakParagraph => self.layout_paragraph()?, Command::BreakParagraph => self.layout_paragraph()?,
Command::SetTextStyle(style) => self.style = style, Command::SetTextStyle(style) => self.style.text = style,
Command::SetPageStyle(style) => { Command::SetPageStyle(style) => {
if !self.ctx.top_level { if !self.ctx.top_level {
lerr!("page style cannot only be altered in the top-level context"); lerr!("page style cannot only be altered in the top-level context");
} }
self.ctx.page_style = style; self.style.page = style;
self.flex.set_spaces(smallvec![ self.flex.set_spaces(smallvec![
LayoutSpace { LayoutSpace {
dimensions: style.dimensions, dimensions: style.dimensions,
expand: (true, true),
padding: style.margins, padding: style.margins,
} }
], true); ], true);

View File

@ -22,10 +22,10 @@ use toddle::query::{FontLoader, FontProvider, SharedFontLoader};
use crate::func::Scope; use crate::func::Scope;
use crate::layout::{layout_tree, MultiLayout, LayoutContext}; use crate::layout::{layout_tree, MultiLayout, LayoutContext};
use crate::layout::{LayoutAxes, AlignedAxis, Axis, Alignment}; use crate::layout::{LayoutAxes, LayoutAlignment, Axis, Alignment};
use crate::layout::{LayoutError, LayoutResult, LayoutSpace}; use crate::layout::{LayoutError, LayoutResult, LayoutSpace};
use crate::syntax::{SyntaxTree, parse, ParseContext, ParseError, ParseResult}; use crate::syntax::{SyntaxTree, parse, ParseContext, ParseError, ParseResult};
use crate::style::{PageStyle, TextStyle}; use crate::style::{LayoutStyle, PageStyle, TextStyle};
#[macro_use] #[macro_use]
mod macros; mod macros;
@ -44,10 +44,8 @@ pub mod syntax;
pub struct Typesetter<'p> { pub struct Typesetter<'p> {
/// The font loader shared by all typesetting processes. /// The font loader shared by all typesetting processes.
loader: SharedFontLoader<'p>, loader: SharedFontLoader<'p>,
/// The base text style. /// The base layouting style.
text_style: TextStyle, style: LayoutStyle,
/// The base page style.
page_style: PageStyle,
} }
impl<'p> Typesetter<'p> { impl<'p> Typesetter<'p> {
@ -56,21 +54,20 @@ impl<'p> Typesetter<'p> {
pub fn new() -> Typesetter<'p> { pub fn new() -> Typesetter<'p> {
Typesetter { Typesetter {
loader: RefCell::new(FontLoader::new()), loader: RefCell::new(FontLoader::new()),
text_style: TextStyle::default(), style: LayoutStyle::default(),
page_style: PageStyle::default(),
} }
} }
/// Set the base page style. /// Set the base page style.
#[inline] #[inline]
pub fn set_page_style(&mut self, style: PageStyle) { pub fn set_page_style(&mut self, style: PageStyle) {
self.page_style = style; self.style.page = style;
} }
/// Set the base text style. /// Set the base text style.
#[inline] #[inline]
pub fn set_text_style(&mut self, style: TextStyle) { pub fn set_text_style(&mut self, style: TextStyle) {
self.text_style = style; self.style.text = style;
} }
/// Add a font provider to the context of this typesetter. /// Add a font provider to the context of this typesetter.
@ -99,17 +96,14 @@ impl<'p> Typesetter<'p> {
LayoutContext { LayoutContext {
loader: &self.loader, loader: &self.loader,
top_level: true, top_level: true,
text_style: &self.text_style, style: &self.style,
page_style: self.page_style,
spaces: smallvec![LayoutSpace { spaces: smallvec![LayoutSpace {
dimensions: self.page_style.dimensions, dimensions: self.style.page.dimensions,
padding: self.page_style.margins, expand: (true, true),
padding: self.style.page.margins,
}], }],
axes: LayoutAxes { axes: LayoutAxes::new(Axis::LeftToRight, Axis::TopToBottom),
primary: AlignedAxis::new(Axis::LeftToRight, Alignment::Origin, false), alignment: LayoutAlignment::new(Alignment::Origin, Alignment::Origin),
secondary: AlignedAxis::new(Axis::TopToBottom, Alignment::Origin, false),
},
expand: true,
}, },
)?) )?)
} }

View File

@ -69,7 +69,7 @@ function! {
layout(this, ctx) { layout(this, ctx) {
let mut axes = ctx.axes; let mut axes = ctx.axes;
let primary_horizontal = axes.primary.axis.is_horizontal(); let primary_horizontal = axes.primary.is_horizontal();
let mut primary = false; let mut primary = false;
let mut secondary = false; let mut secondary = false;
@ -87,7 +87,7 @@ function! {
*was_set = true; *was_set = true;
let horizontal = axis.axis.is_horizontal(); let horizontal = axis.is_horizontal();
let alignment = generic_alignment(spec, horizontal)?; let alignment = generic_alignment(spec, horizontal)?;
if axis.alignment == Alignment::End && alignment == Alignment::Origin { if axis.alignment == Alignment::End && alignment == Alignment::Origin {
@ -116,13 +116,13 @@ function! {
set_axis(!primary_horizontal, this.vertical)?; set_axis(!primary_horizontal, this.vertical)?;
Ok(match &this.body { Ok(match &this.body {
Some(body) => commands![AddMultiple( Some(body) => vec![AddMultiple(
layout_tree(body, LayoutContext { layout_tree(body, LayoutContext {
axes, axes,
.. ctx.clone() .. ctx.clone()
})? })?
)], )],
None => commands![Command::SetAxes(axes)] None => vec![Command::SetAxes(axes)]
}) })
} }
} }

View File

@ -32,6 +32,6 @@ function! {
ctx.spaces[0].dimensions.y = height; ctx.spaces[0].dimensions.y = height;
} }
Ok(commands![AddMultiple(layout_tree(&this.body, ctx)?)]) Ok(vec![AddMultiple(layout_tree(&this.body, ctx)?)])
} }
} }

View File

@ -7,7 +7,7 @@ pub struct PageBreak;
function! { function! {
data: PageBreak, data: PageBreak,
parse: plain, parse: plain,
layout(_, _) { Ok(commands![FinishSpace]) } layout(_, _) { Ok(vec![FinishSpace]) }
} }
/// `page.size`: Set the size of pages. /// `page.size`: Set the size of pages.
@ -29,12 +29,12 @@ function! {
} }
layout(this, ctx) { layout(this, ctx) {
let mut style = ctx.page_style; let mut style = ctx.style.page;
if let Some(width) = this.width { style.dimensions.x = width; } if let Some(width) = this.width { style.dimensions.x = width; }
if let Some(height) = this.height { style.dimensions.y = height; } if let Some(height) = this.height { style.dimensions.y = height; }
Ok(commands![SetPageStyle(style)]) Ok(vec![SetPageStyle(style)])
} }
} }
@ -67,13 +67,13 @@ function! {
} }
layout(this, ctx) { layout(this, ctx) {
let mut style = ctx.page_style; let mut style = ctx.style.page;
if let Some(left) = this.left { style.margins.left = left; } if let Some(left) = this.left { style.margins.left = left; }
if let Some(top) = this.top { style.margins.top = top; } if let Some(top) = this.top { style.margins.top = top; }
if let Some(right) = this.right { style.margins.right = right; } if let Some(right) = this.right { style.margins.right = right; }
if let Some(bottom) = this.bottom { style.margins.bottom = bottom; } if let Some(bottom) = this.bottom { style.margins.bottom = bottom; }
Ok(commands![SetPageStyle(style)]) Ok(vec![SetPageStyle(style)])
} }
} }

View File

@ -7,7 +7,7 @@ pub struct LineBreak;
function! { function! {
data: LineBreak, data: LineBreak,
parse: plain, parse: plain,
layout(_, _) { Ok(commands![FinishLine]) } layout(_, _) { Ok(vec![FinishLine]) }
} }
/// `paragraph.break`: Ends the current paragraph. /// `paragraph.break`: Ends the current paragraph.
@ -19,7 +19,7 @@ pub struct ParagraphBreak;
function! { function! {
data: ParagraphBreak, data: ParagraphBreak,
parse: plain, parse: plain,
layout(_, _) { Ok(commands![BreakParagraph]) } layout(_, _) { Ok(vec![BreakParagraph]) }
} }
macro_rules! space_func { macro_rules! space_func {
@ -47,10 +47,10 @@ macro_rules! space_func {
layout(this, ctx) { layout(this, ctx) {
let $var = match this.0 { let $var = match this.0 {
Spacing::Absolute(s) => s, Spacing::Absolute(s) => s,
Spacing::Relative(f) => f * ctx.text_style.font_size, Spacing::Relative(f) => f * ctx.style.text.font_size,
}; };
Ok(commands![$command]) Ok(vec![$command])
} }
} }
); );

View File

@ -18,16 +18,16 @@ macro_rules! stylefunc {
} }
layout(this, ctx) { layout(this, ctx) {
let mut style = ctx.text_style.clone(); let mut style = ctx.style.text.clone();
style.toggle_class(FontClass::$ident); style.toggle_class(FontClass::$ident);
Ok(match &this.body { Ok(match &this.body {
Some(body) => commands![ Some(body) => vec![
SetTextStyle(style), SetTextStyle(style),
LayoutTree(body), LayoutTree(body),
SetTextStyle(ctx.text_style.clone()), SetTextStyle(ctx.style.text.clone()),
], ],
None => commands![SetTextStyle(style)] None => vec![SetTextStyle(style)]
}) })
} }
} }

View File

@ -4,6 +4,13 @@ use toddle::query::FontClass;
use FontClass::*; use FontClass::*;
use crate::size::{Size, Size2D, SizeBox}; use crate::size::{Size, Size2D, SizeBox};
/// Defines properties of pages and text.
#[derive(Debug, Default, Clone)]
pub struct LayoutStyle {
pub page: PageStyle,
pub text: TextStyle,
}
/// Defines which fonts to use and how to space text. /// Defines which fonts to use and how to space text.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TextStyle { pub struct TextStyle {

View File

@ -472,7 +472,7 @@ mod tests {
data: TreeFn, data: TreeFn,
parse(_args, body, ctx) { Ok(TreeFn(parse!(required: body, ctx))) } parse(_args, body, ctx) { Ok(TreeFn(parse!(required: body, ctx))) }
layout(_, _) { Ok(commands![]) } layout(_, _) { Ok(vec![]) }
} }
impl PartialEq for TreeFn { impl PartialEq for TreeFn {
@ -490,7 +490,7 @@ mod tests {
data: BodylessFn, data: BodylessFn,
parse(_args, body, _ctx) { parse!(forbidden: body); Ok(BodylessFn) } parse(_args, body, _ctx) { parse!(forbidden: body); Ok(BodylessFn) }
layout(_, _) { Ok(commands![]) } layout(_, _) { Ok(vec![]) }
} }
impl PartialEq for BodylessFn { impl PartialEq for BodylessFn {

10
tests/layouts/align.typ Normal file
View File

@ -0,0 +1,10 @@
[box][
A short sentence. [align: right][words.]
A short sentence. [n] [align: right][words.]
A short sentence. [paragraph.break] [align: right][words.]
[align: bottom]
A longer sentence with a few more words.
]