mirror of
https://github.com/typst/typst
synced 2025-06-28 08:12:53 +08:00
Page style modification functions 📜
- `page.size` - `page.margins`
This commit is contained in:
parent
1eb25f86dd
commit
1a6fb48bc5
@ -103,7 +103,9 @@ pub enum Command<'a> {
|
|||||||
|
|
||||||
BreakParagraph,
|
BreakParagraph,
|
||||||
|
|
||||||
SetStyle(TextStyle),
|
SetTextStyle(TextStyle),
|
||||||
|
SetPageStyle(PageStyle),
|
||||||
|
|
||||||
SetAxes(LayoutAxes),
|
SetAxes(LayoutAxes),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,9 @@ use super::*;
|
|||||||
/// However, it can be any layout.
|
/// However, it can be any layout.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FlexLayouter {
|
pub struct FlexLayouter {
|
||||||
ctx: FlexContext,
|
axes: LayoutAxes,
|
||||||
|
flex_spacing: Size,
|
||||||
|
|
||||||
stack: StackLayouter,
|
stack: StackLayouter,
|
||||||
units: Vec<FlexUnit>,
|
units: Vec<FlexUnit>,
|
||||||
|
|
||||||
@ -69,14 +71,16 @@ impl FlexLayouter {
|
|||||||
/// Create a new flex layouter.
|
/// Create a new flex layouter.
|
||||||
pub fn new(ctx: FlexContext) -> FlexLayouter {
|
pub fn new(ctx: FlexContext) -> FlexLayouter {
|
||||||
let stack = StackLayouter::new(StackContext {
|
let stack = StackLayouter::new(StackContext {
|
||||||
spaces: ctx.spaces.clone(),
|
spaces: ctx.spaces,
|
||||||
axes: ctx.axes,
|
axes: ctx.axes,
|
||||||
shrink_to_fit: ctx.shrink_to_fit,
|
shrink_to_fit: ctx.shrink_to_fit,
|
||||||
});
|
});
|
||||||
|
|
||||||
let usable = stack.usable().x;
|
let usable = stack.usable().x;
|
||||||
FlexLayouter {
|
FlexLayouter {
|
||||||
ctx,
|
axes: ctx.axes,
|
||||||
|
flex_spacing: ctx.flex_spacing,
|
||||||
|
|
||||||
units: vec![],
|
units: vec![],
|
||||||
stack,
|
stack,
|
||||||
|
|
||||||
@ -126,6 +130,18 @@ impl FlexLayouter {
|
|||||||
self.units.push(FlexUnit::SetAxes(axes));
|
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.
|
/// Compute the justified layout.
|
||||||
///
|
///
|
||||||
/// The layouter is not consumed by this to prevent ownership problems
|
/// 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
|
/// Layout a content box into the current flex run or start a new run if
|
||||||
/// it does not fit.
|
/// it does not fit.
|
||||||
fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> {
|
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() {
|
if size.x > self.size_left() {
|
||||||
self.space = None;
|
self.space = None;
|
||||||
@ -213,7 +229,7 @@ impl FlexLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn layout_set_axes(&mut self, axes: LayoutAxes) {
|
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.finish_aligned_run();
|
||||||
|
|
||||||
self.usable = match axes.primary.alignment {
|
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.stack.set_axes(axes);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ctx.axes = axes;
|
self.axes = axes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the current flex run.
|
/// Finish the current flex run.
|
||||||
@ -248,7 +264,7 @@ impl FlexLayouter {
|
|||||||
|
|
||||||
let actions = std::mem::replace(&mut self.merged_actions, LayoutActionList::new());
|
let actions = std::mem::replace(&mut self.merged_actions, LayoutActionList::new());
|
||||||
self.stack.add(Layout {
|
self.stack.add(Layout {
|
||||||
dimensions: self.ctx.axes.specialize(self.merged_dimensions),
|
dimensions: self.axes.specialize(self.merged_dimensions),
|
||||||
actions: actions.into_vec(),
|
actions: actions.into_vec(),
|
||||||
debug_render: false,
|
debug_render: false,
|
||||||
})?;
|
})?;
|
||||||
@ -265,38 +281,33 @@ impl FlexLayouter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let factor = if self.ctx.axes.primary.axis.is_positive() { 1 } else { -1 };
|
let factor = if self.axes.primary.axis.is_positive() { 1 } else { -1 };
|
||||||
let anchor = self.ctx.axes.primary.anchor(self.total_usable)
|
let anchor = self.axes.primary.anchor(self.total_usable)
|
||||||
- self.ctx.axes.primary.anchor(self.run.size.x);
|
- self.axes.primary.anchor(self.run.size.x);
|
||||||
|
|
||||||
self.max_extent = crate::size::max(self.max_extent, anchor + factor * 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(..) {
|
for (offset, layout) in self.run.content.drain(..) {
|
||||||
let general_position = Size2D::with_x(anchor + factor * offset);
|
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_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::Origin => self.run.size.x,
|
||||||
Alignment::Center | Alignment::End => self.total_usable,
|
Alignment::Center | Alignment::End => self.total_usable,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.merged_dimensions.y = crate::size::max(
|
self.merged_dimensions.y = crate::size::max(
|
||||||
self.merged_dimensions.y,
|
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.last_run_remaining = Size2D::new(self.size_left(), self.merged_dimensions.y);
|
||||||
self.run.size = Size2D::zero();
|
self.run.size = Size2D::zero();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This layouter's context.
|
|
||||||
pub fn ctx(&self) -> &FlexContext {
|
|
||||||
&self.ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remaining(&self) -> LayoutResult<(LayoutSpaces, LayoutSpaces)> {
|
pub fn remaining(&self) -> LayoutResult<(LayoutSpaces, LayoutSpaces)> {
|
||||||
let mut future = self.clone();
|
let mut future = self.clone();
|
||||||
future.finish_box()?;
|
future.finish_box()?;
|
||||||
@ -310,7 +321,6 @@ impl FlexLayouter {
|
|||||||
Ok((flex_spaces, stack_spaces))
|
Ok((flex_spaces, stack_spaces))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this layouter contains any items.
|
|
||||||
pub fn box_is_empty(&self) -> bool {
|
pub fn box_is_empty(&self) -> bool {
|
||||||
!self.units.iter().any(|unit| matches!(unit, FlexUnit::Boxed(_)))
|
!self.units.iter().any(|unit| matches!(unit, FlexUnit::Boxed(_)))
|
||||||
}
|
}
|
||||||
|
@ -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::TextStyle;
|
use crate::style::{PageStyle, TextStyle};
|
||||||
use crate::syntax::{FuncCall, Node, SyntaxTree};
|
use crate::syntax::{FuncCall, Node, SyntaxTree};
|
||||||
|
|
||||||
mod actions;
|
mod actions;
|
||||||
@ -131,9 +131,15 @@ pub struct LayoutContext<'a, 'p> {
|
|||||||
/// using [`layout_text`].
|
/// using [`layout_text`].
|
||||||
pub loader: &'a SharedFontLoader<'p>,
|
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
|
/// The style to set text with. This includes sizes and font classes
|
||||||
/// which determine which font from the loaders selection is used.
|
/// 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.
|
/// The spaces to layout in.
|
||||||
pub spaces: LayoutSpaces,
|
pub spaces: LayoutSpaces,
|
||||||
@ -281,6 +287,8 @@ pub enum Alignment {
|
|||||||
|
|
||||||
/// The error type for layouting.
|
/// The error type for layouting.
|
||||||
pub enum LayoutError {
|
pub enum LayoutError {
|
||||||
|
/// An action is unallowed in the active context.
|
||||||
|
Unallowed(&'static str),
|
||||||
/// There is not enough space to add an item.
|
/// There is not enough space to add an item.
|
||||||
NotEnoughSpace(&'static str),
|
NotEnoughSpace(&'static str),
|
||||||
/// There was no suitable font for the given character.
|
/// There was no suitable font for the given character.
|
||||||
@ -295,6 +303,7 @@ pub type LayoutResult<T> = Result<T, LayoutError>;
|
|||||||
error_type! {
|
error_type! {
|
||||||
err: LayoutError,
|
err: LayoutError,
|
||||||
show: f => match err {
|
show: f => match err {
|
||||||
|
LayoutError::Unallowed(desc) => write!(f, "unallowed: {}", desc),
|
||||||
LayoutError::NotEnoughSpace(desc) => write!(f, "not enough space: {}", desc),
|
LayoutError::NotEnoughSpace(desc) => write!(f, "not enough space: {}", desc),
|
||||||
LayoutError::NoSuitableFont(c) => write!(f, "no suitable font for '{}'", c),
|
LayoutError::NoSuitableFont(c) => write!(f, "no suitable font for '{}'", c),
|
||||||
LayoutError::Font(err) => write!(f, "font error: {}", err),
|
LayoutError::Font(err) => write!(f, "font error: {}", err),
|
||||||
|
@ -96,9 +96,21 @@ impl StackLayouter {
|
|||||||
pub fn set_axes(&mut self, axes: LayoutAxes) {
|
pub fn set_axes(&mut self, axes: LayoutAxes) {
|
||||||
if axes != self.ctx.axes {
|
if axes != self.ctx.axes {
|
||||||
self.finish_boxes();
|
self.finish_boxes();
|
||||||
|
self.ctx.axes = axes;
|
||||||
self.usable = self.remains();
|
self.usable = self.remains();
|
||||||
self.dimensions = Size2D::zero();
|
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.
|
/// Compose all cached boxes into a layout.
|
||||||
fn finish_boxes(&mut self) {
|
fn finish_boxes(&mut self) {
|
||||||
|
if self.boxes.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let space = self.ctx.spaces[self.active_space];
|
let space = self.ctx.spaces[self.active_space];
|
||||||
let start = space.start() + Size2D::with_y(self.merged_dimensions.y);
|
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);
|
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.
|
/// The (generalized) usable area of the current space.
|
||||||
pub fn usable(&self) -> Size2D {
|
pub fn usable(&self) -> Size2D {
|
||||||
self.usable
|
self.usable
|
||||||
@ -198,6 +209,10 @@ impl StackLayouter {
|
|||||||
Size2D::new(self.usable.x, self.usable.y - self.dimensions.y)
|
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.
|
/// Whether this layouter is in its last space.
|
||||||
pub fn in_last_space(&self) -> bool {
|
pub fn in_last_space(&self) -> bool {
|
||||||
self.active_space == self.ctx.spaces.len() - 1
|
self.active_space == self.ctx.spaces.len() - 1
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use smallvec::smallvec;
|
||||||
|
|
||||||
/// Layouts syntax trees into boxes.
|
/// Layouts syntax trees into boxes.
|
||||||
pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout> {
|
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> {
|
fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> {
|
||||||
TreeLayouter {
|
TreeLayouter {
|
||||||
flex: FlexLayouter::new(FlexContext {
|
flex: FlexLayouter::new(FlexContext {
|
||||||
flex_spacing: flex_spacing(&ctx.style),
|
flex_spacing: flex_spacing(&ctx.text_style),
|
||||||
spaces: ctx.spaces.clone(),
|
spaces: ctx.spaces.clone(),
|
||||||
axes: ctx.axes,
|
axes: ctx.axes,
|
||||||
shrink_to_fit: ctx.shrink_to_fit,
|
shrink_to_fit: ctx.shrink_to_fit,
|
||||||
}),
|
}),
|
||||||
style: ctx.style.clone(),
|
style: ctx.text_style.clone(),
|
||||||
ctx,
|
ctx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,7 +69,8 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
let (flex_spaces, stack_spaces) = self.flex.remaining()?;
|
let (flex_spaces, stack_spaces) = self.flex.remaining()?;
|
||||||
|
|
||||||
let ctx = |spaces| LayoutContext {
|
let ctx = |spaces| LayoutContext {
|
||||||
style: &self.style,
|
top_level: false,
|
||||||
|
text_style: &self.style,
|
||||||
spaces: spaces,
|
spaces: spaces,
|
||||||
shrink_to_fit: true,
|
shrink_to_fit: true,
|
||||||
.. self.ctx
|
.. self.ctx
|
||||||
@ -107,7 +109,21 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
|
|
||||||
Command::BreakParagraph => self.break_paragraph()?,
|
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) => {
|
Command::SetAxes(axes) => {
|
||||||
self.flex.set_axes(axes);
|
self.flex.set_axes(axes);
|
||||||
self.ctx.axes = axes;
|
self.ctx.axes = axes;
|
||||||
|
@ -21,7 +21,7 @@ use smallvec::smallvec;
|
|||||||
use toddle::query::{FontLoader, FontProvider, SharedFontLoader};
|
use toddle::query::{FontLoader, FontProvider, SharedFontLoader};
|
||||||
|
|
||||||
use crate::func::Scope;
|
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::{LayoutAxes, AlignedAxis, 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};
|
||||||
@ -98,7 +98,9 @@ impl<'p> Typesetter<'p> {
|
|||||||
&tree,
|
&tree,
|
||||||
LayoutContext {
|
LayoutContext {
|
||||||
loader: &self.loader,
|
loader: &self.loader,
|
||||||
style: &self.text_style,
|
top_level: true,
|
||||||
|
text_style: &self.text_style,
|
||||||
|
page_style: self.page_style,
|
||||||
spaces: smallvec![LayoutSpace {
|
spaces: smallvec![LayoutSpace {
|
||||||
dimensions: self.page_style.dimensions,
|
dimensions: self.page_style.dimensions,
|
||||||
padding: self.page_style.margins,
|
padding: self.page_style.margins,
|
||||||
|
@ -6,6 +6,7 @@ pub_use_mod!(boxed);
|
|||||||
pub_use_mod!(axes);
|
pub_use_mod!(axes);
|
||||||
pub_use_mod!(spacing);
|
pub_use_mod!(spacing);
|
||||||
pub_use_mod!(style);
|
pub_use_mod!(style);
|
||||||
|
pub_use_mod!(page);
|
||||||
|
|
||||||
/// Create a scope with all standard functions.
|
/// Create a scope with all standard functions.
|
||||||
pub fn std() -> Scope {
|
pub fn std() -> Scope {
|
||||||
@ -19,7 +20,6 @@ pub fn std() -> Scope {
|
|||||||
std.add::<LineBreak>("line.break");
|
std.add::<LineBreak>("line.break");
|
||||||
std.add::<ParagraphBreak>("paragraph.break");
|
std.add::<ParagraphBreak>("paragraph.break");
|
||||||
std.add::<PageBreak>("page.break");
|
std.add::<PageBreak>("page.break");
|
||||||
|
|
||||||
std.add::<HorizontalSpace>("h");
|
std.add::<HorizontalSpace>("h");
|
||||||
std.add::<VerticalSpace>("v");
|
std.add::<VerticalSpace>("v");
|
||||||
|
|
||||||
@ -27,5 +27,8 @@ pub fn std() -> Scope {
|
|||||||
std.add::<Italic>("italic");
|
std.add::<Italic>("italic");
|
||||||
std.add::<Monospace>("mono");
|
std.add::<Monospace>("mono");
|
||||||
|
|
||||||
|
std.add::<PageSize>("page.size");
|
||||||
|
std.add::<PageMargins>("page.margins");
|
||||||
|
|
||||||
std
|
std
|
||||||
}
|
}
|
||||||
|
79
src/library/page.rs
Normal file
79
src/library/page.rs
Normal file
@ -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)])
|
||||||
|
}
|
||||||
|
}
|
@ -22,16 +22,6 @@ function! {
|
|||||||
layout(_, _) { Ok(commands![FinishBox]) }
|
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 {
|
macro_rules! space_func {
|
||||||
($ident:ident, $doc:expr, $var:ident => $command:expr) => (
|
($ident:ident, $doc:expr, $var:ident => $command:expr) => (
|
||||||
#[doc = $doc]
|
#[doc = $doc]
|
||||||
@ -57,7 +47,7 @@ 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.style.font_size,
|
Spacing::Relative(f) => f * ctx.text_style.font_size,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(commands![$command])
|
Ok(commands![$command])
|
||||||
|
@ -18,16 +18,16 @@ macro_rules! stylefunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
layout(this, ctx) {
|
layout(this, ctx) {
|
||||||
let mut style = ctx.style.clone();
|
let mut style = ctx.text_style.clone();
|
||||||
style.toggle_class(FontClass::$ident);
|
style.toggle_class(FontClass::$ident);
|
||||||
|
|
||||||
Ok(match &this.body {
|
Ok(match &this.body {
|
||||||
Some(body) => commands![
|
Some(body) => commands![
|
||||||
SetStyle(style),
|
SetTextStyle(style),
|
||||||
LayoutTree(body),
|
LayoutTree(body),
|
||||||
SetStyle(ctx.style.clone()),
|
SetTextStyle(ctx.text_style.clone()),
|
||||||
],
|
],
|
||||||
None => commands![SetStyle(style)]
|
None => commands![SetTextStyle(style)]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ impl Default for TextStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Defines the size and margins of a page.
|
/// Defines the size and margins of a page.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct PageStyle {
|
pub struct PageStyle {
|
||||||
/// The width and height of the page.
|
/// The width and height of the page.
|
||||||
pub dimensions: Size2D,
|
pub dimensions: Size2D,
|
||||||
|
@ -9,8 +9,6 @@ use regex::{Regex, Captures};
|
|||||||
use typst::export::pdf::PdfExporter;
|
use typst::export::pdf::PdfExporter;
|
||||||
use typst::layout::LayoutAction;
|
use typst::layout::LayoutAction;
|
||||||
use typst::toddle::query::FileSystemFontProvider;
|
use typst::toddle::query::FileSystemFontProvider;
|
||||||
use typst::size::{Size, Size2D, SizeBox};
|
|
||||||
use typst::style::PageStyle;
|
|
||||||
use typst::Typesetter;
|
use typst::Typesetter;
|
||||||
|
|
||||||
const CACHE_DIR: &str = "tests/cache";
|
const CACHE_DIR: &str = "tests/cache";
|
||||||
@ -62,19 +60,12 @@ fn main() {
|
|||||||
fn test(name: &str, src: &str) {
|
fn test(name: &str, src: &str) {
|
||||||
println!("Testing: {}.", name);
|
println!("Testing: {}.", name);
|
||||||
|
|
||||||
let (src, size) = preprocess(src);
|
let src = preprocess(src);
|
||||||
|
|
||||||
let mut typesetter = Typesetter::new();
|
let mut typesetter = Typesetter::new();
|
||||||
let provider = FileSystemFontProvider::from_listing("fonts/fonts.toml").unwrap();
|
let provider = FileSystemFontProvider::from_listing("fonts/fonts.toml").unwrap();
|
||||||
typesetter.add_font_provider(provider.clone());
|
typesetter.add_font_provider(provider.clone());
|
||||||
|
|
||||||
if let Some(dimensions) = size {
|
|
||||||
typesetter.set_page_style(PageStyle {
|
|
||||||
dimensions,
|
|
||||||
margins: SizeBox::zero()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make run warm.
|
// Make run warm.
|
||||||
#[cfg(not(debug_assertions))] let warmup_start = Instant::now();
|
#[cfg(not(debug_assertions))] let warmup_start = Instant::now();
|
||||||
#[cfg(not(debug_assertions))] typesetter.typeset(&src).unwrap();
|
#[cfg(not(debug_assertions))] typesetter.typeset(&src).unwrap();
|
||||||
@ -138,24 +129,11 @@ fn test(name: &str, src: &str) {
|
|||||||
exporter.export(&layouts, typesetter.loader(), file).unwrap();
|
exporter.export(&layouts, typesetter.loader(), file).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preprocess<'a>(src: &'a str) -> (String, Option<Size2D>) {
|
fn preprocess<'a>(src: &'a str) -> String {
|
||||||
let include_regex = Regex::new(r"\{include:((.|\.|\-)*)\}").unwrap();
|
let include_regex = Regex::new(r"\{include:((.|\.|\-)*)\}").unwrap();
|
||||||
let lorem_regex = Regex::new(r"\{lorem:(\d*)\}").unwrap();
|
let lorem_regex = Regex::new(r"\{lorem:(\d*)\}").unwrap();
|
||||||
let size_regex = Regex::new(r"\{(size:(([\d\w]*)\sx\s([\d\w]*)))\}").unwrap();
|
|
||||||
|
|
||||||
let mut size = None;
|
let mut preprocessed = src.to_string();
|
||||||
|
|
||||||
let mut preprocessed = size_regex.replace_all(&src, |cap: &Captures| {
|
|
||||||
let width_str = cap.get(3).unwrap().as_str();
|
|
||||||
let height_str = cap.get(4).unwrap().as_str();
|
|
||||||
|
|
||||||
let width = width_str.parse::<Size>().unwrap();
|
|
||||||
let height = height_str.parse::<Size>().unwrap();
|
|
||||||
|
|
||||||
size = Some(Size2D::new(width, height));
|
|
||||||
|
|
||||||
"".to_string()
|
|
||||||
}).to_string();
|
|
||||||
|
|
||||||
let mut changed = true;
|
let mut changed = true;
|
||||||
while changed {
|
while changed {
|
||||||
@ -179,7 +157,7 @@ fn preprocess<'a>(src: &'a str) -> (String, Option<Size2D>) {
|
|||||||
generate_lorem(num_words)
|
generate_lorem(num_words)
|
||||||
}).to_string();
|
}).to_string();
|
||||||
|
|
||||||
(preprocessed, size)
|
preprocessed
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_lorem(num_words: usize) -> String {
|
fn generate_lorem(num_words: usize) -> String {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{size:150pt x 215pt}
|
[page.size: width=150pt, height=215pt]
|
||||||
|
[page.margins: 0pt]
|
||||||
|
|
||||||
// ---------------------------------- //
|
// ---------------------------------- //
|
||||||
// Without newline in between.
|
// Without newline in between.
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{size:420pt x 300pt}
|
[page.size: width=450pt, height=300pt]
|
||||||
|
[page.margins: 1cm]
|
||||||
|
|
||||||
[box][
|
[box][
|
||||||
*Technical University Berlin* [n]
|
*Technical University Berlin* [n]
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
{size:150pt x 200pt}
|
[page.size: width=150pt, height=200pt]
|
||||||
|
[page.margins: 0pt]
|
||||||
|
|
||||||
{lorem:100}
|
{lorem:100}
|
||||||
|
|
||||||
[page.break]
|
[page.break]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{size:250pt x 500pt}
|
[page.size: width=250pt, height=300pt]
|
||||||
|
[page.margins: 10pt]
|
||||||
|
|
||||||
_Emoji:_ Hello World! 🌍
|
_Emoji:_ Hello World! 🌍
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user