Refactor layouting base ♻
This commit is contained in:
parent
b13ed627ff
commit
5782b82770
@ -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>>,
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
32
src/lib.rs
32
src/lib.rs
@ -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),
|
||||
},
|
||||
)?)
|
||||
}
|
||||
|
@ -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)]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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)?)])
|
||||
}
|
||||
}
|
||||
|
@ -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)])
|
||||
}
|
||||
}
|
||||
|
@ -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])
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -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)]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
10
tests/layouts/align.typ
Normal 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.
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user