mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Refactor execution context 🏗
- The execution context is a lot more structured: Instead of a magic stack of arbitrary objects there are static objects for pages, stacks and paragraphs - Page softness/keeping mechanic is now a lot simpler than before
This commit is contained in:
parent
584a43277d
commit
c3acb491e3
@ -1,11 +1,11 @@
|
|||||||
use std::any::Any;
|
use std::mem;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use fontdock::FontStyle;
|
use fontdock::FontStyle;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::diag::{Diag, DiagSet};
|
use crate::diag::{Diag, DiagSet};
|
||||||
use crate::geom::{Dir, Gen, LayoutAligns, LayoutDirs, Length, Linear, Sides, Size};
|
use crate::geom::{Dir, Gen, Linear, Sides, Size};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Node, NodePad, NodePages, NodePar, NodeSpacing, NodeStack, NodeText, Tree,
|
Node, NodePad, NodePages, NodePar, NodeSpacing, NodeStack, NodeText, Tree,
|
||||||
};
|
};
|
||||||
@ -20,17 +20,15 @@ pub struct ExecContext<'a> {
|
|||||||
pub state: State,
|
pub state: State,
|
||||||
/// Execution diagnostics.
|
/// Execution diagnostics.
|
||||||
pub diags: DiagSet,
|
pub diags: DiagSet,
|
||||||
/// The finished page runs.
|
/// The tree of finished page runs.
|
||||||
runs: Vec<NodePages>,
|
tree: Tree,
|
||||||
/// The stack of logical groups (paragraphs and such).
|
/// Metrics of the active page.
|
||||||
///
|
page: PageData,
|
||||||
/// Each entry contains metadata about the group and nodes that are at the
|
/// The content of the active stack. This may be the top-level stack for the
|
||||||
/// same level as the group, which will return to `inner` once the group is
|
/// page or a lower one created by [`exec`](Self::exec).
|
||||||
/// finished.
|
stack: NodeStack,
|
||||||
groups: Vec<(Box<dyn Any>, Vec<Node>)>,
|
/// The content of the active paragraph.
|
||||||
/// The nodes in the current innermost group
|
par: NodePar,
|
||||||
/// (whose metadata is in `groups.last()`).
|
|
||||||
inner: Vec<Node>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ExecContext<'a> {
|
impl<'a> ExecContext<'a> {
|
||||||
@ -38,185 +36,23 @@ impl<'a> ExecContext<'a> {
|
|||||||
pub fn new(env: &'a mut Env, state: State) -> Self {
|
pub fn new(env: &'a mut Env, state: State) -> Self {
|
||||||
Self {
|
Self {
|
||||||
env,
|
env,
|
||||||
state,
|
|
||||||
diags: DiagSet::new(),
|
diags: DiagSet::new(),
|
||||||
runs: vec![],
|
tree: Tree { runs: vec![] },
|
||||||
groups: vec![],
|
page: PageData::new(&state, Softness::Hard),
|
||||||
inner: vec![],
|
stack: NodeStack::new(&state),
|
||||||
|
par: NodePar::new(&state),
|
||||||
|
state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish execution and return the created layout tree.
|
|
||||||
pub fn finish(self) -> Pass<Tree> {
|
|
||||||
assert!(self.groups.is_empty(), "unfinished group");
|
|
||||||
Pass::new(Tree { runs: self.runs }, self.diags)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a diagnostic.
|
/// Add a diagnostic.
|
||||||
pub fn diag(&mut self, diag: Diag) {
|
pub fn diag(&mut self, diag: Diag) {
|
||||||
self.diags.insert(diag);
|
self.diags.insert(diag);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a layout node to the active group.
|
/// Set the directions.
|
||||||
///
|
///
|
||||||
/// Spacing nodes will be handled according to their [`Softness`].
|
/// Produces an error if the axes aligned.
|
||||||
pub fn push(&mut self, node: impl Into<Node>) {
|
|
||||||
let node = node.into();
|
|
||||||
|
|
||||||
if let Node::Spacing(this) = node {
|
|
||||||
if this.softness == Softness::Soft && self.inner.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(&Node::Spacing(other)) = self.inner.last() {
|
|
||||||
if this.softness > other.softness {
|
|
||||||
self.inner.pop();
|
|
||||||
} else if this.softness == Softness::Soft {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.inner.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Push a normal word space.
|
|
||||||
pub fn push_space(&mut self) {
|
|
||||||
let em = self.state.font.font_size();
|
|
||||||
self.push(NodeSpacing {
|
|
||||||
amount: self.state.par.word_spacing.resolve(em),
|
|
||||||
softness: Softness::Soft,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Push text into the context.
|
|
||||||
///
|
|
||||||
/// The text is split into lines at newlines.
|
|
||||||
pub fn push_text(&mut self, text: &str) {
|
|
||||||
let mut newline = false;
|
|
||||||
for line in text.split_terminator(is_newline) {
|
|
||||||
if newline {
|
|
||||||
self.apply_linebreak();
|
|
||||||
}
|
|
||||||
|
|
||||||
let node = self.make_text_node(line.into());
|
|
||||||
self.push(node);
|
|
||||||
newline = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute a template and return the result as a stack node.
|
|
||||||
pub fn exec(&mut self, template: &ValueTemplate) -> Node {
|
|
||||||
let dirs = self.state.dirs;
|
|
||||||
let aligns = self.state.aligns;
|
|
||||||
|
|
||||||
self.start_group(ContentGroup);
|
|
||||||
self.start_par_group();
|
|
||||||
template.exec(self);
|
|
||||||
self.end_par_group();
|
|
||||||
let children = self.end_group::<ContentGroup>().1;
|
|
||||||
|
|
||||||
NodeStack { dirs, aligns, children }.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start a page group based on the active page state.
|
|
||||||
///
|
|
||||||
/// The `softness` is a hint on whether empty pages should be kept in the
|
|
||||||
/// output.
|
|
||||||
///
|
|
||||||
/// This also starts an inner paragraph.
|
|
||||||
pub fn start_page_group(&mut self, softness: Softness) {
|
|
||||||
self.start_group(PageGroup {
|
|
||||||
size: self.state.page.size,
|
|
||||||
padding: self.state.page.margins(),
|
|
||||||
dirs: self.state.dirs,
|
|
||||||
aligns: self.state.aligns,
|
|
||||||
softness,
|
|
||||||
});
|
|
||||||
self.start_par_group();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// End a page group, returning its [`Softness`].
|
|
||||||
///
|
|
||||||
/// Whether the page is kept when it's empty is decided by `keep_empty`
|
|
||||||
/// based on its softness. If kept, the page is pushed to the finished page
|
|
||||||
/// runs.
|
|
||||||
///
|
|
||||||
/// This also ends an inner paragraph.
|
|
||||||
pub fn end_page_group<F>(&mut self, keep_empty: F) -> Softness
|
|
||||||
where
|
|
||||||
F: FnOnce(Softness) -> bool,
|
|
||||||
{
|
|
||||||
self.end_par_group();
|
|
||||||
let (group, children) = self.end_group::<PageGroup>();
|
|
||||||
if !children.is_empty() || keep_empty(group.softness) {
|
|
||||||
self.runs.push(NodePages {
|
|
||||||
size: group.size,
|
|
||||||
child: NodePad {
|
|
||||||
padding: group.padding,
|
|
||||||
child: NodeStack {
|
|
||||||
dirs: group.dirs,
|
|
||||||
aligns: group.aligns,
|
|
||||||
children,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
group.softness
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start a paragraph group based on the active text state.
|
|
||||||
pub fn start_par_group(&mut self) {
|
|
||||||
let em = self.state.font.font_size();
|
|
||||||
self.start_group(ParGroup {
|
|
||||||
dirs: self.state.dirs,
|
|
||||||
aligns: self.state.aligns,
|
|
||||||
line_spacing: self.state.par.line_spacing.resolve(em),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// End a paragraph group and push it to its parent group if it's not empty.
|
|
||||||
pub fn end_par_group(&mut self) {
|
|
||||||
let (group, children) = self.end_group::<ParGroup>();
|
|
||||||
if !children.is_empty() {
|
|
||||||
self.push(NodePar {
|
|
||||||
dirs: group.dirs,
|
|
||||||
aligns: group.aligns,
|
|
||||||
line_spacing: group.line_spacing,
|
|
||||||
children,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start a layouting group.
|
|
||||||
///
|
|
||||||
/// All further calls to [`push`](Self::push) will collect nodes for this
|
|
||||||
/// group. The given metadata will be returned alongside the collected nodes
|
|
||||||
/// in a matching call to [`end_group`](Self::end_group).
|
|
||||||
fn start_group<T: 'static>(&mut self, meta: T) {
|
|
||||||
self.groups.push((Box::new(meta), std::mem::take(&mut self.inner)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// End a layouting group started with [`start_group`](Self::start_group).
|
|
||||||
///
|
|
||||||
/// This returns the stored metadata and the collected nodes.
|
|
||||||
#[track_caller]
|
|
||||||
fn end_group<T: 'static>(&mut self) -> (T, Vec<Node>) {
|
|
||||||
if let Some(&Node::Spacing(spacing)) = self.inner.last() {
|
|
||||||
if spacing.softness == Softness::Soft {
|
|
||||||
self.inner.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (any, outer) = self.groups.pop().expect("no pushed group");
|
|
||||||
let group = *any.downcast::<T>().expect("bad group type");
|
|
||||||
(group, std::mem::replace(&mut self.inner, outer))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the directions if they would apply to different axes, producing an
|
|
||||||
/// appropriate error otherwise.
|
|
||||||
pub fn set_dirs(&mut self, new: Gen<Option<Spanned<Dir>>>) {
|
pub fn set_dirs(&mut self, new: Gen<Option<Spanned<Dir>>>) {
|
||||||
let dirs = Gen::new(
|
let dirs = Gen::new(
|
||||||
new.main.map(|s| s.v).unwrap_or(self.state.dirs.main),
|
new.main.map(|s| s.v).unwrap_or(self.state.dirs.main),
|
||||||
@ -233,27 +69,77 @@ impl<'a> ExecContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set the font to monospace.
|
/// Set the font to monospace.
|
||||||
pub fn apply_monospace(&mut self) {
|
pub fn set_monospace(&mut self) {
|
||||||
let families = self.state.font.families_mut();
|
let families = self.state.font.families_mut();
|
||||||
families.list.insert(0, "monospace".to_string());
|
families.list.insert(0, "monospace".to_string());
|
||||||
families.flatten();
|
families.flatten();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push a layout node into the active paragraph.
|
||||||
|
///
|
||||||
|
/// Spacing nodes will be handled according to their [`Softness`].
|
||||||
|
pub fn push(&mut self, node: impl Into<Node>) {
|
||||||
|
push(&mut self.par.children, node.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a word space into the active paragraph.
|
||||||
|
pub fn push_space(&mut self) {
|
||||||
|
let em = self.state.font.font_size();
|
||||||
|
self.push(NodeSpacing {
|
||||||
|
amount: self.state.par.word_spacing.resolve(em),
|
||||||
|
softness: Softness::Soft,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push text into the active paragraph.
|
||||||
|
///
|
||||||
|
/// The text is split into lines at newlines.
|
||||||
|
pub fn push_text(&mut self, text: &str) {
|
||||||
|
let mut newline = false;
|
||||||
|
for line in text.split_terminator(is_newline) {
|
||||||
|
if newline {
|
||||||
|
self.push_linebreak();
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = self.make_text_node(line.into());
|
||||||
|
self.push(node);
|
||||||
|
newline = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Apply a forced line break.
|
/// Apply a forced line break.
|
||||||
pub fn apply_linebreak(&mut self) {
|
pub fn push_linebreak(&mut self) {
|
||||||
self.end_par_group();
|
self.finish_par();
|
||||||
self.start_par_group();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a forced paragraph break.
|
/// Apply a forced paragraph break.
|
||||||
pub fn apply_parbreak(&mut self) {
|
pub fn push_parbreak(&mut self) {
|
||||||
self.end_par_group();
|
|
||||||
let em = self.state.font.font_size();
|
let em = self.state.font.font_size();
|
||||||
self.push(NodeSpacing {
|
self.push_into_stack(NodeSpacing {
|
||||||
amount: self.state.par.par_spacing.resolve(em),
|
amount: self.state.par.par_spacing.resolve(em),
|
||||||
softness: Softness::Soft,
|
softness: Softness::Soft,
|
||||||
});
|
});
|
||||||
self.start_par_group();
|
}
|
||||||
|
|
||||||
|
/// Push a node directly into the stack above the paragraph. This finishes
|
||||||
|
/// the active paragraph and starts a new one.
|
||||||
|
pub fn push_into_stack(&mut self, node: impl Into<Node>) {
|
||||||
|
self.finish_par();
|
||||||
|
push(&mut self.stack.children, node.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a template and return the result as a stack node.
|
||||||
|
pub fn exec(&mut self, template: &ValueTemplate) -> NodeStack {
|
||||||
|
let prev_par = mem::replace(&mut self.par, NodePar::new(&self.state));
|
||||||
|
let prev_stack = mem::replace(&mut self.stack, NodeStack::new(&self.state));
|
||||||
|
|
||||||
|
template.exec(self);
|
||||||
|
let stack = self.finish_stack();
|
||||||
|
|
||||||
|
self.par = prev_par;
|
||||||
|
self.stack = prev_stack;
|
||||||
|
|
||||||
|
stack
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a text node from the given string based on the active text
|
/// Construct a text node from the given string based on the active text
|
||||||
@ -282,35 +168,113 @@ impl<'a> ExecContext<'a> {
|
|||||||
variant,
|
variant,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finish the active paragraph.
|
||||||
|
fn finish_par(&mut self) {
|
||||||
|
let mut par = mem::replace(&mut self.par, NodePar::new(&self.state));
|
||||||
|
trim(&mut par.children);
|
||||||
|
|
||||||
|
if !par.children.is_empty() {
|
||||||
|
self.stack.children.push(par.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish the active stack.
|
||||||
|
fn finish_stack(&mut self) -> NodeStack {
|
||||||
|
self.finish_par();
|
||||||
|
|
||||||
|
let mut stack = mem::replace(&mut self.stack, NodeStack::new(&self.state));
|
||||||
|
trim(&mut stack.children);
|
||||||
|
|
||||||
|
stack
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish the active page.
|
||||||
|
pub fn finish_page(&mut self, keep: bool, new_softnes: Softness) {
|
||||||
|
let stack = self.finish_stack();
|
||||||
|
let data = mem::replace(&mut self.page, PageData::new(&self.state, new_softnes));
|
||||||
|
if !stack.children.is_empty() || (keep && data.softness == Softness::Hard) {
|
||||||
|
self.tree.runs.push(NodePages {
|
||||||
|
size: data.size,
|
||||||
|
child: NodePad {
|
||||||
|
padding: data.padding,
|
||||||
|
child: stack.into(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish execution and return the created layout tree.
|
||||||
|
pub fn finish(mut self) -> Pass<Tree> {
|
||||||
|
self.finish_page(true, Softness::Soft);
|
||||||
|
Pass::new(self.tree, self.diags)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines how an item interacts with surrounding items.
|
/// Push a node into a list, taking care of spacing softness.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
fn push(nodes: &mut Vec<Node>, node: Node) {
|
||||||
pub enum Softness {
|
if let Node::Spacing(spacing) = node {
|
||||||
/// A soft item can be skipped in some circumstances.
|
if spacing.softness == Softness::Soft && nodes.is_empty() {
|
||||||
Soft,
|
return;
|
||||||
/// A hard item is always retained.
|
}
|
||||||
Hard,
|
|
||||||
|
if let Some(&Node::Spacing(other)) = nodes.last() {
|
||||||
|
if spacing.softness > other.softness {
|
||||||
|
nodes.pop();
|
||||||
|
} else if spacing.softness == Softness::Soft {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove trailing soft spacing from a node list.
|
||||||
|
fn trim(nodes: &mut Vec<Node>) {
|
||||||
|
if let Some(&Node::Spacing(spacing)) = nodes.last() {
|
||||||
|
if spacing.softness == Softness::Soft {
|
||||||
|
nodes.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A group for a page run.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct PageGroup {
|
struct PageData {
|
||||||
dirs: LayoutDirs,
|
|
||||||
aligns: LayoutAligns,
|
|
||||||
size: Size,
|
size: Size,
|
||||||
padding: Sides<Linear>,
|
padding: Sides<Linear>,
|
||||||
softness: Softness,
|
softness: Softness,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A group for generic content.
|
impl PageData {
|
||||||
#[derive(Debug)]
|
fn new(state: &State, softness: Softness) -> Self {
|
||||||
struct ContentGroup;
|
Self {
|
||||||
|
size: state.page.size,
|
||||||
/// A group for a paragraph.
|
padding: state.page.margins(),
|
||||||
#[derive(Debug)]
|
softness,
|
||||||
struct ParGroup {
|
}
|
||||||
dirs: LayoutDirs,
|
}
|
||||||
aligns: LayoutAligns,
|
}
|
||||||
line_spacing: Length,
|
|
||||||
|
impl NodeStack {
|
||||||
|
fn new(state: &State) -> Self {
|
||||||
|
Self {
|
||||||
|
dirs: state.dirs,
|
||||||
|
aligns: state.aligns,
|
||||||
|
children: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodePar {
|
||||||
|
fn new(state: &State) -> Self {
|
||||||
|
let em = state.font.font_size();
|
||||||
|
Self {
|
||||||
|
dirs: state.dirs,
|
||||||
|
aligns: state.aligns,
|
||||||
|
line_spacing: state.par.line_spacing.resolve(em),
|
||||||
|
children: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,12 +31,19 @@ pub fn exec(
|
|||||||
state: State,
|
state: State,
|
||||||
) -> Pass<layout::Tree> {
|
) -> Pass<layout::Tree> {
|
||||||
let mut ctx = ExecContext::new(env, state);
|
let mut ctx = ExecContext::new(env, state);
|
||||||
ctx.start_page_group(Softness::Hard);
|
|
||||||
tree.exec_with_map(&mut ctx, &map);
|
tree.exec_with_map(&mut ctx, &map);
|
||||||
ctx.end_page_group(|s| s == Softness::Hard);
|
|
||||||
ctx.finish()
|
ctx.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines how an item interacts with surrounding items.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
pub enum Softness {
|
||||||
|
/// A soft item can be skipped in some circumstances.
|
||||||
|
Soft,
|
||||||
|
/// A hard item is always retained.
|
||||||
|
Hard,
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute a node.
|
/// Execute a node.
|
||||||
///
|
///
|
||||||
/// This manipulates active styling and document state and produces layout
|
/// This manipulates active styling and document state and produces layout
|
||||||
@ -68,8 +75,8 @@ impl ExecWithMap for Node {
|
|||||||
match self {
|
match self {
|
||||||
Node::Text(text) => ctx.push_text(text),
|
Node::Text(text) => ctx.push_text(text),
|
||||||
Node::Space => ctx.push_space(),
|
Node::Space => ctx.push_space(),
|
||||||
Node::Linebreak => ctx.apply_linebreak(),
|
Node::Linebreak => ctx.push_linebreak(),
|
||||||
Node::Parbreak => ctx.apply_parbreak(),
|
Node::Parbreak => ctx.push_parbreak(),
|
||||||
Node::Strong => ctx.state.font.strong ^= true,
|
Node::Strong => ctx.state.font.strong ^= true,
|
||||||
Node::Emph => ctx.state.font.emph ^= true,
|
Node::Emph => ctx.state.font.emph ^= true,
|
||||||
Node::Heading(heading) => heading.exec_with_map(ctx, map),
|
Node::Heading(heading) => heading.exec_with_map(ctx, map),
|
||||||
@ -87,7 +94,7 @@ impl ExecWithMap for NodeHeading {
|
|||||||
ctx.state.font.strong = true;
|
ctx.state.font.strong = true;
|
||||||
|
|
||||||
self.contents.exec_with_map(ctx, map);
|
self.contents.exec_with_map(ctx, map);
|
||||||
ctx.apply_parbreak();
|
ctx.push_parbreak();
|
||||||
|
|
||||||
ctx.state = prev;
|
ctx.state = prev;
|
||||||
}
|
}
|
||||||
@ -96,7 +103,7 @@ impl ExecWithMap for NodeHeading {
|
|||||||
impl Exec for NodeRaw {
|
impl Exec for NodeRaw {
|
||||||
fn exec(&self, ctx: &mut ExecContext) {
|
fn exec(&self, ctx: &mut ExecContext) {
|
||||||
let prev = Rc::clone(&ctx.state.font.families);
|
let prev = Rc::clone(&ctx.state.font.families);
|
||||||
ctx.apply_monospace();
|
ctx.set_monospace();
|
||||||
|
|
||||||
let em = ctx.state.font.font_size();
|
let em = ctx.state.font.font_size();
|
||||||
let line_spacing = ctx.state.par.line_spacing.resolve(em);
|
let line_spacing = ctx.state.par.line_spacing.resolve(em);
|
||||||
@ -116,7 +123,7 @@ impl Exec for NodeRaw {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.block {
|
if self.block {
|
||||||
ctx.apply_parbreak();
|
ctx.push_parbreak();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is wrapped in a fixed node to make sure the stack fits to its
|
// This is wrapped in a fixed node to make sure the stack fits to its
|
||||||
@ -133,7 +140,7 @@ impl Exec for NodeRaw {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if self.block {
|
if self.block {
|
||||||
ctx.apply_parbreak();
|
ctx.push_parbreak();
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.state.font.families = prev;
|
ctx.state.font.families = prev;
|
||||||
@ -153,7 +160,7 @@ impl Exec for Value {
|
|||||||
// For values which can't be shown "naturally", we print
|
// For values which can't be shown "naturally", we print
|
||||||
// the representation in monospace.
|
// the representation in monospace.
|
||||||
let prev = Rc::clone(&ctx.state.font.families);
|
let prev = Rc::clone(&ctx.state.font.families);
|
||||||
ctx.apply_monospace();
|
ctx.set_monospace();
|
||||||
ctx.push_text(&pretty(other));
|
ctx.push_text(&pretty(other));
|
||||||
ctx.state.font.families = prev;
|
ctx.state.font.families = prev;
|
||||||
}
|
}
|
||||||
|
@ -89,15 +89,14 @@ pub fn align(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If `had_center` wasn't flushed by now, it's the only argument and then we
|
// If `had_center` wasn't flushed by now, it's the only argument and
|
||||||
// default to applying it to the cross axis.
|
// then we default to applying it to the cross axis.
|
||||||
if had_center {
|
if had_center {
|
||||||
ctx.state.aligns.cross = Align::Center;
|
ctx.state.aligns.cross = Align::Center;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.state.aligns.main != snapshot.aligns.main {
|
if ctx.state.aligns.main != snapshot.aligns.main {
|
||||||
ctx.end_par_group();
|
ctx.push_linebreak();
|
||||||
ctx.start_par_group();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(body) = &body {
|
if let Some(body) = &body {
|
||||||
|
@ -29,7 +29,7 @@ pub fn pad(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
|
|||||||
Value::template("pad", move |ctx| {
|
Value::template("pad", move |ctx| {
|
||||||
let snapshot = ctx.state.clone();
|
let snapshot = ctx.state.clone();
|
||||||
|
|
||||||
let child = ctx.exec(&body);
|
let child = ctx.exec(&body).into();
|
||||||
ctx.push(NodePad { padding, child });
|
ctx.push(NodePad { padding, child });
|
||||||
|
|
||||||
ctx.state = snapshot;
|
ctx.state = snapshot;
|
||||||
|
@ -83,25 +83,20 @@ pub fn page(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.set_dirs(Gen::new(main, cross));
|
ctx.set_dirs(Gen::new(main, cross));
|
||||||
|
ctx.finish_page(false, Softness::Hard);
|
||||||
|
|
||||||
let mut softness = ctx.end_page_group(|_| false);
|
|
||||||
if let Some(body) = &body {
|
if let Some(body) = &body {
|
||||||
// TODO: Restrict body to a single page?
|
// TODO: Restrict body to a single page?
|
||||||
ctx.start_page_group(Softness::Hard);
|
|
||||||
body.exec(ctx);
|
body.exec(ctx);
|
||||||
ctx.end_page_group(|s| s == Softness::Hard);
|
|
||||||
softness = Softness::Soft;
|
|
||||||
ctx.state = snapshot;
|
ctx.state = snapshot;
|
||||||
|
ctx.finish_page(true, Softness::Soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.start_page_group(softness);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `pagebreak`: Start a new page.
|
/// `pagebreak`: Start a new page.
|
||||||
pub fn pagebreak(_: &mut EvalContext, _: &mut ValueArgs) -> Value {
|
pub fn pagebreak(_: &mut EvalContext, _: &mut ValueArgs) -> Value {
|
||||||
Value::template("pagebreak", move |ctx| {
|
Value::template("pagebreak", move |ctx| {
|
||||||
ctx.end_page_group(|_| true);
|
ctx.finish_page(true, Softness::Hard);
|
||||||
ctx.start_page_group(Softness::Hard);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ pub fn box_(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
|
|||||||
|
|
||||||
ctx.set_dirs(Gen::new(main, cross));
|
ctx.set_dirs(Gen::new(main, cross));
|
||||||
|
|
||||||
let child = ctx.exec(&body);
|
let child = ctx.exec(&body).into();
|
||||||
let fixed = NodeFixed { width, height, child };
|
let fixed = NodeFixed { width, height, child };
|
||||||
if let Some(color) = color {
|
if let Some(color) = color {
|
||||||
ctx.push(NodeBackground {
|
ctx.push(NodeBackground {
|
||||||
|
@ -25,9 +25,7 @@ fn spacing(ctx: &mut EvalContext, args: &mut ValueArgs, axis: SpecAxis) -> Value
|
|||||||
let amount = linear.resolve(ctx.state.font.font_size());
|
let amount = linear.resolve(ctx.state.font.font_size());
|
||||||
let spacing = NodeSpacing { amount, softness: Softness::Hard };
|
let spacing = NodeSpacing { amount, softness: Softness::Hard };
|
||||||
if axis == ctx.state.dirs.main.axis() {
|
if axis == ctx.state.dirs.main.axis() {
|
||||||
ctx.end_par_group();
|
ctx.push_into_stack(spacing);
|
||||||
ctx.push(spacing);
|
|
||||||
ctx.start_par_group();
|
|
||||||
} else {
|
} else {
|
||||||
ctx.push(spacing);
|
ctx.push(spacing);
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ Right to left!
|
|||||||
#page[First]
|
#page[First]
|
||||||
#page[Second]
|
#page[Second]
|
||||||
#pagebreak()
|
#pagebreak()
|
||||||
|
#pagebreak()
|
||||||
Fourth
|
Fourth
|
||||||
#page[]
|
#page[]
|
||||||
Sixth
|
Sixth
|
||||||
|
Loading…
x
Reference in New Issue
Block a user