summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-11-18 15:10:11 +0100
committerLaurenz <laurmaedje@gmail.com>2019-11-18 15:10:11 +0100
commit1a6fb48bc5e95d0a9ef243ab62517557189c0eea (patch)
tree06f704fe638d5ae25f4976874500c5da75316348 /src
parent1eb25f86dd6763c4f2d7e60b6d09af60ada50af6 (diff)
Page style modification functions 📜
- `page.size` - `page.margins`
Diffstat (limited to 'src')
-rw-r--r--src/func/mod.rs4
-rw-r--r--src/layout/flex.rs50
-rw-r--r--src/layout/mod.rs13
-rw-r--r--src/layout/stacked.rs27
-rw-r--r--src/layout/tree.rs24
-rw-r--r--src/lib.rs6
-rw-r--r--src/library/mod.rs5
-rw-r--r--src/library/page.rs79
-rw-r--r--src/library/spacing.rs12
-rw-r--r--src/library/style.rs8
-rw-r--r--src/style.rs2
11 files changed, 178 insertions, 52 deletions
diff --git a/src/func/mod.rs b/src/func/mod.rs
index 27dceee3..360c19f8 100644
--- a/src/func/mod.rs
+++ b/src/func/mod.rs
@@ -103,7 +103,9 @@ pub enum Command<'a> {
BreakParagraph,
- SetStyle(TextStyle),
+ SetTextStyle(TextStyle),
+ SetPageStyle(PageStyle),
+
SetAxes(LayoutAxes),
}
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index 0ad31521..142530a6 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -19,7 +19,9 @@ use super::*;
/// However, it can be any layout.
#[derive(Debug, Clone)]
pub struct FlexLayouter {
- ctx: FlexContext,
+ axes: LayoutAxes,
+ flex_spacing: Size,
+
stack: StackLayouter,
units: Vec<FlexUnit>,
@@ -69,14 +71,16 @@ impl FlexLayouter {
/// Create a new flex layouter.
pub fn new(ctx: FlexContext) -> FlexLayouter {
let stack = StackLayouter::new(StackContext {
- spaces: ctx.spaces.clone(),
+ spaces: ctx.spaces,
axes: ctx.axes,
shrink_to_fit: ctx.shrink_to_fit,
});
let usable = stack.usable().x;
FlexLayouter {
- ctx,
+ axes: ctx.axes,
+ flex_spacing: ctx.flex_spacing,
+
units: vec![],
stack,
@@ -126,6 +130,18 @@ impl FlexLayouter {
self.units.push(FlexUnit::SetAxes(axes));
}
+ /// Update the followup space to be used by this flex layouter.
+ pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
+ if replace_empty && self.box_is_empty() && self.stack.space_is_empty() {
+ self.stack.set_spaces(spaces, true);
+ self.total_usable = self.stack.usable().x;
+ self.usable = self.total_usable;
+ self.space = None;
+ } else {
+ self.stack.set_spaces(spaces, false);
+ }
+ }
+
/// Compute the justified layout.
///
/// The layouter is not consumed by this to prevent ownership problems
@@ -176,7 +192,7 @@ impl FlexLayouter {
/// Layout a content box into the current flex run or start a new run if
/// it does not fit.
fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> {
- let size = self.ctx.axes.generalize(boxed.dimensions);
+ let size = self.axes.generalize(boxed.dimensions);
if size.x > self.size_left() {
self.space = None;
@@ -213,7 +229,7 @@ impl FlexLayouter {
}
fn layout_set_axes(&mut self, axes: LayoutAxes) {
- if axes.primary != self.ctx.axes.primary {
+ if axes.primary != self.axes.primary {
self.finish_aligned_run();
self.usable = match axes.primary.alignment {
@@ -231,11 +247,11 @@ impl FlexLayouter {
};
}
- if axes.secondary != self.ctx.axes.secondary {
+ if axes.secondary != self.axes.secondary {
self.stack.set_axes(axes);
}
- self.ctx.axes = axes;
+ self.axes = axes;
}
/// Finish the current flex run.
@@ -248,7 +264,7 @@ impl FlexLayouter {
let actions = std::mem::replace(&mut self.merged_actions, LayoutActionList::new());
self.stack.add(Layout {
- dimensions: self.ctx.axes.specialize(self.merged_dimensions),
+ dimensions: self.axes.specialize(self.merged_dimensions),
actions: actions.into_vec(),
debug_render: false,
})?;
@@ -265,38 +281,33 @@ impl FlexLayouter {
return;
}
- let factor = if self.ctx.axes.primary.axis.is_positive() { 1 } else { -1 };
- let anchor = self.ctx.axes.primary.anchor(self.total_usable)
- - self.ctx.axes.primary.anchor(self.run.size.x);
+ let factor = if self.axes.primary.axis.is_positive() { 1 } else { -1 };
+ let anchor = self.axes.primary.anchor(self.total_usable)
+ - self.axes.primary.anchor(self.run.size.x);
self.max_extent = crate::size::max(self.max_extent, anchor + factor * self.run.size.x);
for (offset, layout) in self.run.content.drain(..) {
let general_position = Size2D::with_x(anchor + factor * offset);
- let position = self.ctx.axes.specialize(general_position);
+ let position = self.axes.specialize(general_position);
self.merged_actions.add_layout(position, layout);
}
- self.merged_dimensions.x = match self.ctx.axes.primary.alignment {
+ self.merged_dimensions.x = match self.axes.primary.alignment {
Alignment::Origin => self.run.size.x,
Alignment::Center | Alignment::End => self.total_usable,
};
self.merged_dimensions.y = crate::size::max(
self.merged_dimensions.y,
- self.run.size.y + self.ctx.flex_spacing,
+ self.run.size.y + self.flex_spacing,
);
self.last_run_remaining = Size2D::new(self.size_left(), self.merged_dimensions.y);
self.run.size = Size2D::zero();
}
- /// This layouter's context.
- pub fn ctx(&self) -> &FlexContext {
- &self.ctx
- }
-
pub fn remaining(&self) -> LayoutResult<(LayoutSpaces, LayoutSpaces)> {
let mut future = self.clone();
future.finish_box()?;
@@ -310,7 +321,6 @@ impl FlexLayouter {
Ok((flex_spaces, stack_spaces))
}
- /// Whether this layouter contains any items.
pub fn box_is_empty(&self) -> bool {
!self.units.iter().any(|unit| matches!(unit, FlexUnit::Boxed(_)))
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index a4514704..426a51ec 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -9,7 +9,7 @@ use toddle::Error as FontError;
use crate::func::Command;
use crate::size::{Size, Size2D, SizeBox};
-use crate::style::TextStyle;
+use crate::style::{PageStyle, TextStyle};
use crate::syntax::{FuncCall, Node, SyntaxTree};
mod actions;
@@ -131,9 +131,15 @@ pub struct LayoutContext<'a, 'p> {
/// using [`layout_text`].
pub loader: &'a SharedFontLoader<'p>,
+ /// 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 style: &'a TextStyle,
+ 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,
@@ -281,6 +287,8 @@ pub enum Alignment {
/// The error type for layouting.
pub enum LayoutError {
+ /// An action is unallowed in the active context.
+ Unallowed(&'static str),
/// There is not enough space to add an item.
NotEnoughSpace(&'static str),
/// There was no suitable font for the given character.
@@ -295,6 +303,7 @@ pub type LayoutResult<T> = Result<T, LayoutError>;
error_type! {
err: LayoutError,
show: f => match err {
+ LayoutError::Unallowed(desc) => write!(f, "unallowed: {}", desc),
LayoutError::NotEnoughSpace(desc) => write!(f, "not enough space: {}", desc),
LayoutError::NoSuitableFont(c) => write!(f, "no suitable font for '{}'", c),
LayoutError::Font(err) => write!(f, "font error: {}", err),
diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs
index a7585046..a5db5638 100644
--- a/src/layout/stacked.rs
+++ b/src/layout/stacked.rs
@@ -96,9 +96,21 @@ impl StackLayouter {
pub fn set_axes(&mut self, axes: LayoutAxes) {
if axes != self.ctx.axes {
self.finish_boxes();
+ self.ctx.axes = axes;
self.usable = self.remains();
self.dimensions = Size2D::zero();
- self.ctx.axes = axes;
+ }
+ }
+
+ /// Update the followup space to be used by this flex layouter.
+ pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
+ if replace_empty && self.space_is_empty() {
+ self.usable = self.ctx.axes.generalize(spaces[0].usable());
+ self.active_space = 0;
+ self.ctx.spaces = spaces;
+ } else {
+ self.ctx.spaces.truncate(self.active_space + 1);
+ self.ctx.spaces.extend(spaces);
}
}
@@ -143,6 +155,10 @@ impl StackLayouter {
/// Compose all cached boxes into a layout.
fn finish_boxes(&mut self) {
+ if self.boxes.is_empty() {
+ return;
+ }
+
let space = self.ctx.spaces[self.active_space];
let start = space.start() + Size2D::with_y(self.merged_dimensions.y);
@@ -170,11 +186,6 @@ impl StackLayouter {
self.merged_dimensions = merge_sizes(self.merged_dimensions, dimensions);
}
- /// This layouter's context.
- pub fn ctx(&self) -> &StackContext {
- &self.ctx
- }
-
/// The (generalized) usable area of the current space.
pub fn usable(&self) -> Size2D {
self.usable
@@ -198,6 +209,10 @@ impl StackLayouter {
Size2D::new(self.usable.x, self.usable.y - self.dimensions.y)
}
+ pub fn space_is_empty(&self) -> bool {
+ self.boxes.is_empty() && self.merged_dimensions == Size2D::zero()
+ }
+
/// Whether this layouter is in its last space.
pub fn in_last_space(&self) -> bool {
self.active_space == self.ctx.spaces.len() - 1
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
index 177a6308..ae6a15c8 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -1,4 +1,5 @@
use super::*;
+use smallvec::smallvec;
/// Layouts syntax trees into boxes.
pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout> {
@@ -19,12 +20,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.style),
+ flex_spacing: flex_spacing(&ctx.text_style),
spaces: ctx.spaces.clone(),
axes: ctx.axes,
shrink_to_fit: ctx.shrink_to_fit,
}),
- style: ctx.style.clone(),
+ style: ctx.text_style.clone(),
ctx,
}
}
@@ -68,7 +69,8 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
let (flex_spaces, stack_spaces) = self.flex.remaining()?;
let ctx = |spaces| LayoutContext {
- style: &self.style,
+ top_level: false,
+ text_style: &self.style,
spaces: spaces,
shrink_to_fit: true,
.. self.ctx
@@ -107,7 +109,21 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
Command::BreakParagraph => self.break_paragraph()?,
- Command::SetStyle(style) => self.style = style,
+ Command::SetTextStyle(style) => self.style = style,
+ Command::SetPageStyle(style) => {
+ if !self.ctx.top_level {
+ Err(LayoutError::Unallowed("can only set page style from top level"))?;
+ }
+
+ self.ctx.page_style = style;
+ self.flex.set_spaces(smallvec![
+ LayoutSpace {
+ dimensions: style.dimensions,
+ padding: style.margins,
+ }
+ ], true);
+ },
+
Command::SetAxes(axes) => {
self.flex.set_axes(axes);
self.ctx.axes = axes;
diff --git a/src/lib.rs b/src/lib.rs
index d2494166..c6a21c51 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -21,7 +21,7 @@ use smallvec::smallvec;
use toddle::query::{FontLoader, FontProvider, SharedFontLoader};
use crate::func::Scope;
-use crate::layout::{layout_tree, LayoutContext, MultiLayout};
+use crate::layout::{layout_tree, MultiLayout, LayoutContext};
use crate::layout::{LayoutAxes, AlignedAxis, Axis, Alignment};
use crate::layout::{LayoutError, LayoutResult, LayoutSpace};
use crate::syntax::{SyntaxTree, parse, ParseContext, ParseError, ParseResult};
@@ -98,7 +98,9 @@ impl<'p> Typesetter<'p> {
&tree,
LayoutContext {
loader: &self.loader,
- style: &self.text_style,
+ top_level: true,
+ text_style: &self.text_style,
+ page_style: self.page_style,
spaces: smallvec![LayoutSpace {
dimensions: self.page_style.dimensions,
padding: self.page_style.margins,
diff --git a/src/library/mod.rs b/src/library/mod.rs
index d795d488..5fa326c8 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -6,6 +6,7 @@ pub_use_mod!(boxed);
pub_use_mod!(axes);
pub_use_mod!(spacing);
pub_use_mod!(style);
+pub_use_mod!(page);
/// Create a scope with all standard functions.
pub fn std() -> Scope {
@@ -19,7 +20,6 @@ pub fn std() -> Scope {
std.add::<LineBreak>("line.break");
std.add::<ParagraphBreak>("paragraph.break");
std.add::<PageBreak>("page.break");
-
std.add::<HorizontalSpace>("h");
std.add::<VerticalSpace>("v");
@@ -27,5 +27,8 @@ pub fn std() -> Scope {
std.add::<Italic>("italic");
std.add::<Monospace>("mono");
+ std.add::<PageSize>("page.size");
+ std.add::<PageMargins>("page.margins");
+
std
}
diff --git a/src/library/page.rs b/src/library/page.rs
new file mode 100644
index 00000000..4efcbea0
--- /dev/null
+++ b/src/library/page.rs
@@ -0,0 +1,79 @@
+use crate::func::prelude::*;
+
+/// `page.break`: Ends the current page.
+#[derive(Debug, PartialEq)]
+pub struct PageBreak;
+
+function! {
+ data: PageBreak,
+ parse: plain,
+ layout(_, _) { Ok(commands![FinishLayout]) }
+}
+
+/// `page.size`: Set the size of pages.
+#[derive(Debug, PartialEq)]
+pub struct PageSize {
+ width: Option<Size>,
+ height: Option<Size>,
+}
+
+function! {
+ data: PageSize,
+
+ parse(args, body, _ctx) {
+ parse!(forbidden: body);
+ Ok(PageSize {
+ width: args.get_key_opt::<ArgSize>("width")?.map(|a| a.val),
+ height: args.get_key_opt::<ArgSize>("height")?.map(|a| a.val),
+ })
+ }
+
+ layout(this, ctx) {
+ let mut style = ctx.page_style;
+
+ if let Some(width) = this.width { style.dimensions.x = width; }
+ if let Some(height) = this.height { style.dimensions.y = height; }
+
+ Ok(commands![SetPageStyle(style)])
+ }
+}
+
+/// `page.margins`: Set the margins of pages.
+#[derive(Debug, PartialEq)]
+pub struct PageMargins {
+ left: Option<Size>,
+ top: Option<Size>,
+ right: Option<Size>,
+ bottom: Option<Size>,
+}
+
+function! {
+ data: PageMargins,
+
+ parse(args, body, _ctx) {
+ parse!(forbidden: body);
+ let default = args.get_pos_opt::<ArgSize>()?;
+ let mut get = |which| {
+ args.get_key_opt::<ArgSize>(which)
+ .map(|size| size.or(default).map(|a| a.val))
+ };
+
+ Ok(PageMargins {
+ left: get("left")?,
+ top: get("top")?,
+ right: get("right")?,
+ bottom: get("bottom")?,
+ })
+ }
+
+ layout(this, ctx) {
+ let mut style = ctx.page_style;
+
+ 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)])
+ }
+}
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
index afd26d80..f83f333d 100644
--- a/src/library/spacing.rs
+++ b/src/library/spacing.rs
@@ -22,16 +22,6 @@ function! {
layout(_, _) { Ok(commands![FinishBox]) }
}
-/// `page.break`: Ends the current page.
-#[derive(Debug, PartialEq)]
-pub struct PageBreak;
-
-function! {
- data: PageBreak,
- parse: plain,
- layout(_, _) { Ok(commands![FinishLayout]) }
-}
-
macro_rules! space_func {
($ident:ident, $doc:expr, $var:ident => $command:expr) => (
#[doc = $doc]
@@ -57,7 +47,7 @@ macro_rules! space_func {
layout(this, ctx) {
let $var = match this.0 {
Spacing::Absolute(s) => s,
- Spacing::Relative(f) => f * ctx.style.font_size,
+ Spacing::Relative(f) => f * ctx.text_style.font_size,
};
Ok(commands![$command])
diff --git a/src/library/style.rs b/src/library/style.rs
index 0615c0e7..a63166cf 100644
--- a/src/library/style.rs
+++ b/src/library/style.rs
@@ -18,16 +18,16 @@ macro_rules! stylefunc {
}
layout(this, ctx) {
- let mut style = ctx.style.clone();
+ let mut style = ctx.text_style.clone();
style.toggle_class(FontClass::$ident);
Ok(match &this.body {
Some(body) => commands![
- SetStyle(style),
+ SetTextStyle(style),
LayoutTree(body),
- SetStyle(ctx.style.clone()),
+ SetTextStyle(ctx.text_style.clone()),
],
- None => commands![SetStyle(style)]
+ None => commands![SetTextStyle(style)]
})
}
}
diff --git a/src/style.rs b/src/style.rs
index e2ab0937..da190b46 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -74,7 +74,7 @@ impl Default for TextStyle {
}
/// Defines the size and margins of a page.
-#[derive(Debug, Clone)]
+#[derive(Debug, Copy, Clone)]
pub struct PageStyle {
/// The width and height of the page.
pub dimensions: Size2D,