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 use crate::func::{Command, CommandList, Function};
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::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span};
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.
#[derive(Debug)]
pub enum Command<'a> {
@ -109,60 +112,6 @@ pub enum Command<'a> {
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.
pub struct Scope {
parsers: HashMap<String, Box<ParseFunc>>,

View File

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

View File

@ -41,7 +41,7 @@ struct PartialLine {
usable: Size,
content: Vec<(Size, Layout)>,
dimensions: Size2D,
space: SpaceState,
space: LastSpacing,
}
impl PartialLine {
@ -50,7 +50,7 @@ impl PartialLine {
usable,
content: vec![],
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);
}
@ -246,15 +246,15 @@ impl FlexLayouter {
self.part.dimensions.x += size.x;
self.part.dimensions.y.max_eq(size.y);
self.part.space = SpaceState::Allowed;
self.part.space = LastSpacing::Allowed;
Ok(())
}
fn layout_space(&mut self, space: Size, kind: SpaceKind) {
if kind == SpaceKind::Soft {
if self.part.space != SpaceState::Forbidden {
self.part.space = SpaceState::Soft(space);
if self.part.space != LastSpacing::Forbidden {
self.part.space = LastSpacing::Soft(space);
}
} else {
if self.part.dimensions.x + space > self.part.usable {
@ -264,7 +264,7 @@ impl FlexLayouter {
}
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::size::{Size, Size2D, SizeBox};
use crate::style::{PageStyle, TextStyle};
use crate::style::{LayoutStyle, TextStyle};
use crate::syntax::{FuncCall, Node, SyntaxTree};
mod actions;
@ -29,99 +29,20 @@ pub mod layouters {
pub use actions::{LayoutAction, LayoutActionList};
pub use layouters::*;
/// A collection of layouts.
pub type MultiLayout = Vec<Layout>;
/// A sequence of layouting actions inside a box.
#[derive(Debug, Clone)]
pub struct Layout {
/// The size of the box.
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.
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.
@ -130,20 +51,16 @@ pub struct LayoutContext<'a, 'p> {
/// The font loader to retrieve fonts from when typesetting text
/// using [`layout_text`].
pub loader: &'a SharedFontLoader<'p>,
/// The style for pages and text.
pub style: &'a LayoutStyle,
/// Whether this layouting process handles the top-level pages.
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.
pub spaces: LayoutSpaces,
/// The axes to flow on.
/// The initial axes along which content is laid out.
pub axes: LayoutAxes,
/// Whether layouts should expand to the full dimensions of the space
/// they lie on or whether should tightly fit the content.
pub expand: bool,
/// The alignment for the two axes.
pub alignment: LayoutAlignment,
}
/// A possibly stack-allocated vector of layout spaces.
@ -154,26 +71,31 @@ pub type LayoutSpaces = SmallVec<[LayoutSpace; 2]>;
pub struct LayoutSpace {
/// The maximum size of the box to layout in.
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.
pub padding: SizeBox,
}
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,
/// `(padding.left, padding.top)`.
pub fn start(&self) -> Size2D {
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.
pub fn usable_space(&self) -> LayoutSpace {
LayoutSpace {
dimensions: self.usable(),
expand: (false, false),
padding: SizeBox::zero(),
}
}
@ -182,17 +104,21 @@ impl LayoutSpace {
/// The axes along which the content is laid out.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct LayoutAxes {
pub primary: AlignedAxis,
pub secondary: AlignedAxis,
pub primary: Axis,
pub secondary: Axis,
}
impl LayoutAxes {
pub fn new(primary: Axis, secondary: Axis) -> LayoutAxes {
LayoutAxes { primary, secondary }
}
/// Returns the generalized version of a `Size2D` dependent on
/// the layouting axes, that is:
/// - The x coordinate describes the primary axis instead of the horizontal one.
/// - The y coordinate describes the secondary axis instead of the vertical one.
pub fn generalize(&self, size: Size2D) -> Size2D {
if self.primary.axis.is_horizontal() {
if self.primary.is_horizontal() {
size
} else {
Size2D { x: size.y, y: size.x }
@ -206,58 +132,9 @@ impl LayoutAxes {
// at the call site, we still have this second function.
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.
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.
/// Directions along which content is laid out.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Axis {
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.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Alignment {
@ -300,26 +190,74 @@ pub enum Alignment {
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)]
pub enum SpaceKind {
/// Soft spaces are eaten up by hard spaces before or after them.
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.
pub enum SpacingKind {
/// A hard space consumes surrounding soft spaces and is always layouted.
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)]
enum SpaceState {
Soft(Size),
Forbidden,
Allowed,
enum LastSpacing {
Hard,
Soft(Size, u32),
None,
}
impl SpaceState {
impl LastSpacing {
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 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)]
pub struct StackLayouter {
/// The context for layouter.
ctx: StackContext,
/// The output layouts.
layouts: MultiLayout,
/// The full layout space.
space: Space,
/// The currently active subspace.
sub: Subspace,
}
#[derive(Debug, Clone)]
struct Space {
/// The index of this space in the list of spaces.
index: usize,
/// Whether to add the layout for this space even if it would be empty.
hard: bool,
/// The layouting actions accumulated from the subspaces.
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,
}
#[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 {
fn new(index: usize, hard: bool) -> 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 {
fn new(origin: Size2D, usable: Size2D, axes: LayoutAxes) -> Subspace {
Subspace {
@ -52,7 +69,7 @@ impl Subspace {
boxes: vec![],
usable: axes.generalize(usable),
dimensions: Size2D::zero(),
space: SpaceState::Forbidden,
space: LastSpacing::Forbidden,
}
}
}
@ -82,7 +99,7 @@ impl StackLayouter {
}
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);
}
@ -107,7 +124,7 @@ impl StackLayouter {
self.sub.boxes.push((offset, anchor, layout));
self.sub.dimensions = new_dimensions;
self.sub.space = SpaceState::Allowed;
self.sub.space = LastSpacing::Allowed;
Ok(())
}
@ -121,8 +138,8 @@ impl StackLayouter {
pub fn add_space(&mut self, space: Size, kind: SpaceKind) {
if kind == SpaceKind::Soft {
if self.sub.space != SpaceState::Forbidden {
self.sub.space = SpaceState::Soft(space);
if self.sub.space != LastSpacing::Forbidden {
self.sub.space = LastSpacing::Soft(space);
}
} else {
if self.sub.dimensions.y + space > self.sub.usable.y {
@ -132,7 +149,7 @@ impl StackLayouter {
}
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 {
dimensions: Size2D::new(self.width, self.ctx.style.font_size),
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> {
ctx: LayoutContext<'a, 'p>,
flex: FlexLayouter,
style: TextStyle,
style: LayoutStyle,
}
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> {
TreeLayouter {
flex: FlexLayouter::new(FlexContext {
flex_spacing: flex_spacing(&ctx.text_style),
flex_spacing: flex_spacing(&ctx.style.text),
spaces: ctx.spaces.clone(),
axes: ctx.axes,
expand: ctx.expand,
}),
style: ctx.text_style.clone(),
style: ctx.style.clone(),
ctx,
}
}
@ -37,9 +37,9 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
Node::Space => self.layout_space(),
Node::Newline => self.layout_paragraph()?,
Node::ToggleItalics => self.style.toggle_class(FontClass::Italic),
Node::ToggleBold => self.style.toggle_class(FontClass::Bold),
Node::ToggleMonospace => self.style.toggle_class(FontClass::Monospace),
Node::ToggleItalics => self.style.text.toggle_class(FontClass::Italic),
Node::ToggleBold => self.style.text.toggle_class(FontClass::Bold),
Node::ToggleMonospace => self.style.text.toggle_class(FontClass::Monospace),
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<()> {
let layout = layout_text(text, TextContext {
loader: &self.ctx.loader,
style: &self.style,
style: &self.style.text,
})?;
Ok(self.flex.add(layout))
}
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<()> {
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<()> {
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 {
loader: self.ctx.loader,
style: &self.style,
top_level: false,
text_style: &self.style,
page_style: self.ctx.page_style,
spaces,
axes,
expand: false,
.. self.ctx
})?;
for command in commands {
@ -95,10 +96,8 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
Command::Add(layout) => self.flex.add(layout),
Command::AddMultiple(layouts) => self.flex.add_multiple(layouts),
Command::AddPrimarySpace(space)
=> self.flex.add_primary_space(space, SpaceKind::Hard),
Command::AddSecondarySpace(space)
=> self.flex.add_secondary_space(space, SpaceKind::Hard)?,
Command::AddPrimarySpace(space) => self.flex.add_primary_space(space, SpacingKind::Hard),
Command::AddSecondarySpace(space) => self.flex.add_secondary_space(space, SpacingKind::Hard)?,
Command::FinishLine => self.flex.add_break(),
Command::FinishRun => { self.flex.finish_run()?; },
@ -106,16 +105,17 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
Command::BreakParagraph => self.layout_paragraph()?,
Command::SetTextStyle(style) => self.style = style,
Command::SetTextStyle(style) => self.style.text = style,
Command::SetPageStyle(style) => {
if !self.ctx.top_level {
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![
LayoutSpace {
dimensions: style.dimensions,
expand: (true, true),
padding: style.margins,
}
], true);

View File

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

View File

@ -69,7 +69,7 @@ function! {
layout(this, ctx) {
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 secondary = false;
@ -87,7 +87,7 @@ function! {
*was_set = true;
let horizontal = axis.axis.is_horizontal();
let horizontal = axis.is_horizontal();
let alignment = generic_alignment(spec, horizontal)?;
if axis.alignment == Alignment::End && alignment == Alignment::Origin {
@ -116,13 +116,13 @@ function! {
set_axis(!primary_horizontal, this.vertical)?;
Ok(match &this.body {
Some(body) => commands![AddMultiple(
Some(body) => vec![AddMultiple(
layout_tree(body, LayoutContext {
axes,
.. 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;
}
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! {
data: PageBreak,
parse: plain,
layout(_, _) { Ok(commands![FinishSpace]) }
layout(_, _) { Ok(vec![FinishSpace]) }
}
/// `page.size`: Set the size of pages.
@ -29,12 +29,12 @@ function! {
}
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(height) = this.height { style.dimensions.y = height; }
Ok(commands![SetPageStyle(style)])
Ok(vec![SetPageStyle(style)])
}
}
@ -67,13 +67,13 @@ function! {
}
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(top) = this.top { style.margins.top = top; }
if let Some(right) = this.right { style.margins.right = right; }
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! {
data: LineBreak,
parse: plain,
layout(_, _) { Ok(commands![FinishLine]) }
layout(_, _) { Ok(vec![FinishLine]) }
}
/// `paragraph.break`: Ends the current paragraph.
@ -19,7 +19,7 @@ pub struct ParagraphBreak;
function! {
data: ParagraphBreak,
parse: plain,
layout(_, _) { Ok(commands![BreakParagraph]) }
layout(_, _) { Ok(vec![BreakParagraph]) }
}
macro_rules! space_func {
@ -47,10 +47,10 @@ macro_rules! space_func {
layout(this, ctx) {
let $var = match this.0 {
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) {
let mut style = ctx.text_style.clone();
let mut style = ctx.style.text.clone();
style.toggle_class(FontClass::$ident);
Ok(match &this.body {
Some(body) => commands![
Some(body) => vec![
SetTextStyle(style),
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 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.
#[derive(Debug, Clone)]
pub struct TextStyle {

View File

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