mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Add pagebreak function ⏭
This commit is contained in:
parent
9a1d57a11a
commit
f22f9513ae
66
src/func.rs
66
src/func.rs
@ -35,6 +35,35 @@ impl PartialEq for dyn Function {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A helper trait that describes requirements for types that can implement
|
||||||
|
/// [`Function`].
|
||||||
|
///
|
||||||
|
/// Automatically implemented for all types which fulfill to the bounds `Debug +
|
||||||
|
/// PartialEq + 'static`. There should be no need to implement this manually.
|
||||||
|
pub trait FunctionBounds: Debug {
|
||||||
|
/// Cast self into `Any`.
|
||||||
|
fn help_cast_as_any(&self) -> &dyn Any;
|
||||||
|
|
||||||
|
/// Compare self with another function.
|
||||||
|
fn help_eq(&self, other: &dyn Function) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> FunctionBounds for T
|
||||||
|
where T: Debug + PartialEq + 'static
|
||||||
|
{
|
||||||
|
fn help_cast_as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn help_eq(&self, other: &dyn Function) -> bool {
|
||||||
|
if let Some(other) = other.help_cast_as_any().downcast_ref::<Self>() {
|
||||||
|
self == other
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A sequence of commands requested for execution by a function.
|
/// A sequence of commands requested for execution by a function.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CommandList<'a> {
|
pub struct CommandList<'a> {
|
||||||
@ -47,6 +76,11 @@ impl<'a> CommandList<'a> {
|
|||||||
CommandList { commands: vec![] }
|
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.
|
/// Add a command to the sequence.
|
||||||
pub fn add(&mut self, command: Command<'a>) {
|
pub fn add(&mut self, command: Command<'a>) {
|
||||||
self.commands.push(command);
|
self.commands.push(command);
|
||||||
@ -84,35 +118,13 @@ pub enum Command<'a> {
|
|||||||
AddMany(MultiLayout),
|
AddMany(MultiLayout),
|
||||||
SetAlignment(Alignment),
|
SetAlignment(Alignment),
|
||||||
SetStyle(TextStyle),
|
SetStyle(TextStyle),
|
||||||
|
FinishLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A helper trait that describes requirements for types that can implement
|
macro_rules! commands {
|
||||||
/// [`Function`].
|
($($x:expr),*$(,)*) => ({
|
||||||
///
|
$crate::func::CommandList::from_vec(vec![$($x,)*])
|
||||||
/// Automatically implemented for all types which fulfill to the bounds `Debug +
|
});
|
||||||
/// PartialEq + 'static`. There should be no need to implement this manually.
|
|
||||||
pub trait FunctionBounds: Debug {
|
|
||||||
/// Cast self into `Any`.
|
|
||||||
fn help_cast_as_any(&self) -> &dyn Any;
|
|
||||||
|
|
||||||
/// Compare self with another function.
|
|
||||||
fn help_eq(&self, other: &dyn Function) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> FunctionBounds for T
|
|
||||||
where T: Debug + PartialEq + 'static
|
|
||||||
{
|
|
||||||
fn help_cast_as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn help_eq(&self, other: &dyn Function) -> bool {
|
|
||||||
if let Some(other) = other.help_cast_as_any().downcast_ref::<Self>() {
|
|
||||||
self == other
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A map from identifiers to functions.
|
/// A map from identifiers to functions.
|
||||||
|
@ -17,6 +17,7 @@ use super::*;
|
|||||||
/// flows into a new line. A _glue_ layout is typically used for a space character
|
/// flows into a new line. A _glue_ layout is typically used for a space character
|
||||||
/// since it prevents a space from appearing in the beginning or end of a line.
|
/// since it prevents a space from appearing in the beginning or end of a line.
|
||||||
/// However, it can be any layout.
|
/// However, it can be any layout.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct FlexLayouter {
|
pub struct FlexLayouter {
|
||||||
ctx: FlexContext,
|
ctx: FlexContext,
|
||||||
units: Vec<FlexUnit>,
|
units: Vec<FlexUnit>,
|
||||||
@ -64,6 +65,7 @@ impl FlexContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
enum FlexUnit {
|
enum FlexUnit {
|
||||||
/// A content unit to be arranged flexibly.
|
/// A content unit to be arranged flexibly.
|
||||||
Boxed(Layout),
|
Boxed(Layout),
|
||||||
@ -73,6 +75,7 @@ enum FlexUnit {
|
|||||||
Glue(Size2D),
|
Glue(Size2D),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
struct FlexRun {
|
struct FlexRun {
|
||||||
content: Vec<(Size, Layout)>,
|
content: Vec<(Size, Layout)>,
|
||||||
size: Size2D,
|
size: Size2D,
|
||||||
@ -168,7 +171,6 @@ impl FlexLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn layout_glue(&mut self, glue: Size2D) {
|
fn layout_glue(&mut self, glue: Size2D) {
|
||||||
self.flush_glue();
|
|
||||||
self.cached_glue = Some(glue);
|
self.cached_glue = Some(glue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ use super::*;
|
|||||||
/// Layouts boxes stack-like.
|
/// Layouts boxes stack-like.
|
||||||
///
|
///
|
||||||
/// The boxes are arranged vertically, each layout gettings it's own "line".
|
/// The boxes are arranged vertically, each layout gettings it's own "line".
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct StackLayouter {
|
pub struct StackLayouter {
|
||||||
ctx: StackContext,
|
ctx: StackContext,
|
||||||
layouts: MultiLayout,
|
layouts: MultiLayout,
|
||||||
|
@ -7,6 +7,7 @@ pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiL
|
|||||||
layouter.finish()
|
layouter.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
struct TreeLayouter<'a, 'p> {
|
struct TreeLayouter<'a, 'p> {
|
||||||
ctx: LayoutContext<'a, 'p>,
|
ctx: LayoutContext<'a, 'p>,
|
||||||
stack: StackLayouter,
|
stack: StackLayouter,
|
||||||
@ -85,13 +86,18 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
|
|
||||||
/// Layout a function.
|
/// Layout a function.
|
||||||
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
|
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
|
||||||
|
// Finish the current flex layout on a copy to find out how
|
||||||
|
// much space would be remaining if we finished.
|
||||||
|
let mut lookahead_stack = self.stack.clone();
|
||||||
|
let layouts = self.flex.clone().finish()?;
|
||||||
|
lookahead_stack.add_many(layouts)?;
|
||||||
|
let remaining = lookahead_stack.remaining();
|
||||||
|
|
||||||
let mut ctx = self.ctx;
|
let mut ctx = self.ctx;
|
||||||
ctx.style = &self.style;
|
ctx.style = &self.style;
|
||||||
ctx.shrink_to_fit = true;
|
ctx.shrink_to_fit = true;
|
||||||
|
ctx.space.dimensions = remaining;
|
||||||
ctx.space.dimensions = self.stack.remaining();
|
|
||||||
ctx.space.padding = SizeBox::zero();
|
ctx.space.padding = SizeBox::zero();
|
||||||
|
|
||||||
if let Some(space) = ctx.followup_spaces.as_mut() {
|
if let Some(space) = ctx.followup_spaces.as_mut() {
|
||||||
*space = space.usable_space();
|
*space = space.usable_space();
|
||||||
}
|
}
|
||||||
@ -127,6 +133,12 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
Command::SetStyle(style) => {
|
Command::SetStyle(style) => {
|
||||||
*self.style.to_mut() = style;
|
*self.style.to_mut() = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Command::FinishLayout => {
|
||||||
|
self.finish_flex()?;
|
||||||
|
self.stack.finish_layout(true)?;
|
||||||
|
self.start_new_flex();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ use crate::syntax::SyntaxTree;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
pub mod export;
|
pub mod export;
|
||||||
|
#[macro_use]
|
||||||
pub mod func;
|
pub mod func;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod library;
|
pub mod library;
|
||||||
|
@ -12,7 +12,7 @@ impl Function for AlignFunc {
|
|||||||
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
|
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
|
||||||
where Self: Sized {
|
where Self: Sized {
|
||||||
if header.args.len() != 1 || !header.kwargs.is_empty() {
|
if header.args.len() != 1 || !header.kwargs.is_empty() {
|
||||||
return err("expected exactly one positional argument specifying the alignment");
|
return err("align: expected exactly one positional argument");
|
||||||
}
|
}
|
||||||
|
|
||||||
let alignment = if let Expression::Ident(ident) = &header.args[0] {
|
let alignment = if let Expression::Ident(ident) = &header.args[0] {
|
||||||
@ -29,11 +29,7 @@ impl Function for AlignFunc {
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let body = if let Some(body) = body {
|
let body = parse_maybe_body(body, ctx)?;
|
||||||
Some(parse(body, ctx)?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(AlignFunc { alignment, body })
|
Ok(AlignFunc { alignment, body })
|
||||||
}
|
}
|
||||||
@ -45,14 +41,9 @@ impl Function for AlignFunc {
|
|||||||
.. ctx
|
.. ctx
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut commands = CommandList::new();
|
Ok(commands![Command::AddMany(layouts)])
|
||||||
commands.add(Command::AddMany(layouts));
|
|
||||||
Ok(commands)
|
|
||||||
} else {
|
} else {
|
||||||
let mut commands = CommandList::new();
|
Ok(commands![Command::SetAlignment(self.alignment)])
|
||||||
commands.add(Command::SetAlignment(self.alignment));
|
|
||||||
|
|
||||||
Ok(commands)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
src/library/breaks.rs
Normal file
24
src/library/breaks.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use super::prelude::*;
|
||||||
|
|
||||||
|
/// Ends the current page.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct PagebreakFunc;
|
||||||
|
|
||||||
|
impl Function for PagebreakFunc {
|
||||||
|
fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult<Self>
|
||||||
|
where Self: Sized {
|
||||||
|
if has_arguments(header) {
|
||||||
|
return err("pagebreak: expected no arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.is_some() {
|
||||||
|
return err("pagebreak: expected no body");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(PagebreakFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(&self, _: LayoutContext) -> LayoutResult<CommandList> {
|
||||||
|
Ok(commands![Command::FinishLayout])
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ use crate::func::Scope;
|
|||||||
|
|
||||||
mod align;
|
mod align;
|
||||||
mod styles;
|
mod styles;
|
||||||
|
mod breaks;
|
||||||
|
|
||||||
/// Useful imports for creating your own functions.
|
/// Useful imports for creating your own functions.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
@ -12,13 +13,11 @@ pub mod prelude {
|
|||||||
pub use crate::layout::{LayoutError, LayoutResult};
|
pub use crate::layout::{LayoutError, LayoutResult};
|
||||||
pub use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
|
pub use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
|
||||||
pub use crate::syntax::{Expression, FuncHeader, SyntaxTree};
|
pub use crate::syntax::{Expression, FuncHeader, SyntaxTree};
|
||||||
|
pub use super::helpers::*;
|
||||||
pub fn err<S: Into<String>, T>(message: S) -> ParseResult<T> {
|
|
||||||
Err(ParseError::new(message))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use align::AlignFunc;
|
pub use align::AlignFunc;
|
||||||
|
pub use breaks::PagebreakFunc;
|
||||||
pub use styles::{BoldFunc, ItalicFunc, MonospaceFunc};
|
pub use styles::{BoldFunc, ItalicFunc, MonospaceFunc};
|
||||||
|
|
||||||
/// Create a scope with all standard functions.
|
/// Create a scope with all standard functions.
|
||||||
@ -28,5 +27,26 @@ pub fn std() -> Scope {
|
|||||||
std.add::<ItalicFunc>("italic");
|
std.add::<ItalicFunc>("italic");
|
||||||
std.add::<MonospaceFunc>("mono");
|
std.add::<MonospaceFunc>("mono");
|
||||||
std.add::<AlignFunc>("align");
|
std.add::<AlignFunc>("align");
|
||||||
|
std.add::<PagebreakFunc>("pagebreak");
|
||||||
std
|
std
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod helpers {
|
||||||
|
use super::prelude::*;
|
||||||
|
|
||||||
|
pub fn has_arguments(header: &FuncHeader) -> bool {
|
||||||
|
!header.args.is_empty() || !header.kwargs.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_maybe_body(body: Option<&str>, ctx: ParseContext) -> ParseResult<Option<SyntaxTree>> {
|
||||||
|
if let Some(body) = body {
|
||||||
|
Ok(Some(parse(body, ctx)?))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn err<S: Into<String>, T>(message: S) -> ParseResult<T> {
|
||||||
|
Err(ParseError::new(message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -10,35 +10,37 @@ macro_rules! style_func {
|
|||||||
) => {
|
) => {
|
||||||
$(#[$outer])*
|
$(#[$outer])*
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct $struct { body: SyntaxTree }
|
pub struct $struct {
|
||||||
|
body: Option<SyntaxTree>
|
||||||
|
}
|
||||||
|
|
||||||
impl Function for $struct {
|
impl Function for $struct {
|
||||||
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
|
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
|
||||||
-> ParseResult<Self> where Self: Sized {
|
-> ParseResult<Self> where Self: Sized {
|
||||||
// Accept only invocations without arguments and with body.
|
// Accept only invocations without arguments and with body.
|
||||||
if header.args.is_empty() && header.kwargs.is_empty() {
|
if has_arguments(header) {
|
||||||
if let Some(body) = body {
|
return err(format!("{}: expected no arguments", $name));
|
||||||
Ok($struct { body: parse(body, ctx)? })
|
|
||||||
} else {
|
|
||||||
Err(ParseError::new(format!("expected body for function `{}`", $name)))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ParseError::new(format!("unexpected arguments to function `{}`", $name)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let body = parse_maybe_body(body, ctx)?;
|
||||||
|
|
||||||
|
Ok($struct { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
|
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
|
||||||
let mut commands = CommandList::new();
|
|
||||||
|
|
||||||
let saved_style = ctx.style.clone();
|
|
||||||
let mut new_style = ctx.style.clone();
|
let mut new_style = ctx.style.clone();
|
||||||
new_style.toggle_class(FontClass::$class);
|
new_style.toggle_class(FontClass::$class);
|
||||||
|
|
||||||
commands.add(Command::SetStyle(new_style));
|
if let Some(body) = &self.body {
|
||||||
commands.add(Command::Layout(&self.body));
|
let saved_style = ctx.style.clone();
|
||||||
commands.add(Command::SetStyle(saved_style));
|
Ok(commands![
|
||||||
|
Command::SetStyle(new_style),
|
||||||
Ok(commands)
|
Command::Layout(body),
|
||||||
|
Command::SetStyle(saved_style),
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
Ok(commands![Command::SetStyle(new_style)])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,25 +1,33 @@
|
|||||||
{size:150pt*208pt}
|
{size:150pt*215pt}
|
||||||
|
|
||||||
// Without newline in between
|
// ---------------------------------- //
|
||||||
|
// Without newline in between.
|
||||||
[align: left][Left: {lorem:20}]
|
[align: left][Left: {lorem:20}]
|
||||||
[align: right][Right: {lorem:20}]
|
[align: right][Right: {lorem:20}]
|
||||||
|
|
||||||
// With newline in between
|
// Over three pages.
|
||||||
[align: center][Center: {lorem:80}]
|
[align: center][Center: {lorem:80}]
|
||||||
|
|
||||||
[align: left][Left: {lorem:20}]
|
// Over multiple pages after the pervious 3-page run.
|
||||||
|
[align: left][Left: {lorem:80}]
|
||||||
|
|
||||||
// Context-modifying align
|
[pagebreak]
|
||||||
|
|
||||||
|
// ---------------------------------- //
|
||||||
|
// Context-modifying align.
|
||||||
[align: right]
|
[align: right]
|
||||||
|
|
||||||
New Right: {lorem:30}
|
Context Right: {lorem:10}
|
||||||
|
|
||||||
[align: left][Inside Left: {lorem:10}]
|
[align: left][In-between Left: {lorem:10}]
|
||||||
|
|
||||||
Right Again: {lorem:10}
|
Right Again: {lorem:10}
|
||||||
|
|
||||||
// Reset context-modifier
|
// Reset context-modifier.
|
||||||
[align: left]
|
[align: left]
|
||||||
|
|
||||||
// All in one line
|
[pagebreak]
|
||||||
{lorem:25} [align: right][{lorem:50}] {lorem:15}
|
|
||||||
|
// ---------------------------------- //
|
||||||
|
// All in one line.
|
||||||
|
All in one line: {lorem:25} [align: right][{lorem:50}] {lorem:15}
|
||||||
|
@ -1,2 +1,10 @@
|
|||||||
{size:200pt*200pt}
|
{size:150pt*200pt}
|
||||||
{lorem:300}
|
{lorem:100}
|
||||||
|
|
||||||
|
[pagebreak]
|
||||||
|
[pagebreak]
|
||||||
|
|
||||||
|
{lorem:20}
|
||||||
|
[pagebreak]
|
||||||
|
|
||||||
|
{lorem:150}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
_Multiline:_
|
{size:250pt*500pt}
|
||||||
{lorem:45}
|
|
||||||
|
|
||||||
_Emoji:_ Hello World! 🌍
|
_Emoji:_ Hello World! 🌍
|
||||||
|
|
||||||
@ -8,3 +7,13 @@ built-in syntax!
|
|||||||
|
|
||||||
_Styles with functions:_ This [bold][word] is made bold and [italic][that] italic
|
_Styles with functions:_ This [bold][word] is made bold and [italic][that] italic
|
||||||
using the standard library functions `bold` and `italic`!
|
using the standard library functions `bold` and `italic`!
|
||||||
|
|
||||||
|
[italic]
|
||||||
|
Styles can also be changed through [bold] context modification.
|
||||||
|
This works basically in the same way as the built-in syntax.
|
||||||
|
_
|
||||||
|
|
||||||
|
This is not italic anymore, but still bold.
|
||||||
|
[bold]
|
||||||
|
|
||||||
|
This is completely reset. 😀
|
||||||
|
Loading…
x
Reference in New Issue
Block a user