mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Set Rules Episode I: The Phantom Style
This commit is contained in:
parent
738ff7e1f5
commit
26bdc1f0f6
@ -60,7 +60,7 @@ fn bench_eval(iai: &mut Iai) {
|
||||
fn bench_to_tree(iai: &mut Iai) {
|
||||
let (mut ctx, id) = context();
|
||||
let module = ctx.evaluate(id).unwrap();
|
||||
iai.run(|| module.template.to_document(ctx.style()));
|
||||
iai.run(|| module.node.clone().into_document());
|
||||
}
|
||||
|
||||
fn bench_layout(iai: &mut Iai) {
|
||||
|
147
src/eval/mod.rs
147
src/eval/mod.rs
@ -8,19 +8,17 @@ mod dict;
|
||||
mod value;
|
||||
mod capture;
|
||||
mod function;
|
||||
mod node;
|
||||
mod ops;
|
||||
mod scope;
|
||||
mod template;
|
||||
mod walk;
|
||||
|
||||
pub use array::*;
|
||||
pub use capture::*;
|
||||
pub use dict::*;
|
||||
pub use function::*;
|
||||
pub use node::*;
|
||||
pub use scope::*;
|
||||
pub use template::*;
|
||||
pub use value::*;
|
||||
pub use walk::*;
|
||||
|
||||
use std::cell::RefMut;
|
||||
use std::collections::HashMap;
|
||||
@ -31,29 +29,31 @@ use std::path::PathBuf;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult};
|
||||
use crate::geom::{Angle, Fractional, Length, Relative};
|
||||
use crate::geom::{Angle, Fractional, Length, Relative, Spec};
|
||||
use crate::image::ImageStore;
|
||||
use crate::library::{GridNode, TrackSizing};
|
||||
use crate::loading::Loader;
|
||||
use crate::source::{SourceId, SourceStore};
|
||||
use crate::style::Style;
|
||||
use crate::syntax::ast::*;
|
||||
use crate::syntax::{Span, Spanned};
|
||||
use crate::util::{EcoString, RefMutExt};
|
||||
use crate::util::{BoolExt, EcoString, RefMutExt};
|
||||
use crate::Context;
|
||||
|
||||
/// Evaluate a parsed source file into a module.
|
||||
pub fn eval(ctx: &mut Context, source: SourceId, markup: &Markup) -> TypResult<Module> {
|
||||
let mut ctx = EvalContext::new(ctx, source);
|
||||
let template = markup.eval(&mut ctx)?;
|
||||
Ok(Module { scope: ctx.scopes.top, template })
|
||||
let node = markup.eval(&mut ctx)?;
|
||||
Ok(Module { scope: ctx.scopes.top, node })
|
||||
}
|
||||
|
||||
/// An evaluated module, ready for importing or instantiation.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Module {
|
||||
/// The top-level definitions that were bound in this module.
|
||||
pub scope: Scope,
|
||||
/// The template defined by this module.
|
||||
pub template: Template,
|
||||
/// The node defined by this module.
|
||||
pub node: Node,
|
||||
}
|
||||
|
||||
/// The context for evaluation.
|
||||
@ -70,8 +70,8 @@ pub struct EvalContext<'a> {
|
||||
pub modules: HashMap<SourceId, Module>,
|
||||
/// The active scopes.
|
||||
pub scopes: Scopes<'a>,
|
||||
/// The currently built template.
|
||||
pub template: Template,
|
||||
/// The active style.
|
||||
pub style: Style,
|
||||
}
|
||||
|
||||
impl<'a> EvalContext<'a> {
|
||||
@ -84,7 +84,7 @@ impl<'a> EvalContext<'a> {
|
||||
route: vec![source],
|
||||
modules: HashMap::new(),
|
||||
scopes: Scopes::new(Some(&ctx.std)),
|
||||
template: Template::new(),
|
||||
style: ctx.style.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,7 +126,7 @@ impl<'a> EvalContext<'a> {
|
||||
self.route.pop().unwrap();
|
||||
|
||||
// Save the evaluated module.
|
||||
let module = Module { scope: new_scopes.top, template };
|
||||
let module = Module { scope: new_scopes.top, node: template };
|
||||
self.modules.insert(id, module);
|
||||
|
||||
Ok(id)
|
||||
@ -155,19 +155,116 @@ pub trait Eval {
|
||||
}
|
||||
|
||||
impl Eval for Markup {
|
||||
type Output = Template;
|
||||
type Output = Node;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
Ok({
|
||||
let prev = mem::take(&mut ctx.template);
|
||||
ctx.template.save();
|
||||
self.walk(ctx)?;
|
||||
ctx.template.restore();
|
||||
mem::replace(&mut ctx.template, prev)
|
||||
let snapshot = ctx.style.clone();
|
||||
|
||||
let mut result = Node::new();
|
||||
for piece in self.nodes() {
|
||||
result += piece.eval(ctx)?;
|
||||
}
|
||||
|
||||
ctx.style = snapshot;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for MarkupNode {
|
||||
type Output = Node;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
Ok(match self {
|
||||
Self::Space => Node::Space,
|
||||
Self::Linebreak => Node::Linebreak,
|
||||
Self::Parbreak => Node::Parbreak,
|
||||
Self::Strong => {
|
||||
ctx.style.text_mut().strong.flip();
|
||||
Node::new()
|
||||
}
|
||||
Self::Emph => {
|
||||
ctx.style.text_mut().emph.flip();
|
||||
Node::new()
|
||||
}
|
||||
Self::Text(text) => Node::Text(text.clone()),
|
||||
Self::Raw(raw) => raw.eval(ctx)?,
|
||||
Self::Math(math) => math.eval(ctx)?,
|
||||
Self::Heading(heading) => heading.eval(ctx)?,
|
||||
Self::List(list) => list.eval(ctx)?,
|
||||
Self::Enum(enum_) => enum_.eval(ctx)?,
|
||||
Self::Expr(expr) => expr.eval(ctx)?.display(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for RawNode {
|
||||
type Output = Node;
|
||||
|
||||
fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
// TODO(set): Styled in monospace.
|
||||
let text = Node::Text(self.text.clone());
|
||||
Ok(if self.block {
|
||||
Node::Block(text.into_block())
|
||||
} else {
|
||||
text
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for MathNode {
|
||||
type Output = Node;
|
||||
|
||||
fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
// TODO(set): Styled in monospace.
|
||||
let text = Node::Text(self.formula.clone());
|
||||
Ok(if self.display {
|
||||
Node::Block(text.into_block())
|
||||
} else {
|
||||
text
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for HeadingNode {
|
||||
type Output = Node;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
// TODO(set): Styled appropriately.
|
||||
Ok(Node::Block(self.body().eval(ctx)?.into_block()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ListNode {
|
||||
type Output = Node;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
let body = self.body().eval(ctx)?;
|
||||
labelled(ctx, '•'.into(), body)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for EnumNode {
|
||||
type Output = Node;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
let body = self.body().eval(ctx)?;
|
||||
let label = format_eco!("{}.", self.number().unwrap_or(1));
|
||||
labelled(ctx, label, body)
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a labelled list / enum.
|
||||
fn labelled(_: &mut EvalContext, label: EcoString, body: Node) -> TypResult<Node> {
|
||||
// Create a grid containing the label, a bit of gutter space and then
|
||||
// the item's body.
|
||||
// TODO: Switch to em units for gutter once available.
|
||||
Ok(Node::block(GridNode {
|
||||
tracks: Spec::new(vec![TrackSizing::Auto; 2], vec![]),
|
||||
gutter: Spec::new(vec![TrackSizing::Linear(Length::pt(6.0).into())], vec![]),
|
||||
children: vec![Node::Text(label).into_block(), body.into_block()],
|
||||
}))
|
||||
}
|
||||
|
||||
impl Eval for Expr {
|
||||
type Output = Value;
|
||||
|
||||
@ -177,7 +274,7 @@ impl Eval for Expr {
|
||||
Self::Ident(v) => v.eval(ctx),
|
||||
Self::Array(v) => v.eval(ctx).map(Value::Array),
|
||||
Self::Dict(v) => v.eval(ctx).map(Value::Dict),
|
||||
Self::Template(v) => v.eval(ctx).map(Value::Template),
|
||||
Self::Template(v) => v.eval(ctx).map(Value::Node),
|
||||
Self::Group(v) => v.eval(ctx),
|
||||
Self::Block(v) => v.eval(ctx),
|
||||
Self::Call(v) => v.eval(ctx),
|
||||
@ -244,7 +341,7 @@ impl Eval for DictExpr {
|
||||
}
|
||||
|
||||
impl Eval for TemplateExpr {
|
||||
type Output = Template;
|
||||
type Output = Node;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
self.body().eval(ctx)
|
||||
@ -665,7 +762,7 @@ impl Eval for IncludeExpr {
|
||||
let resolved = path.eval(ctx)?.cast::<EcoString>().at(path.span())?;
|
||||
let file = ctx.import(&resolved, path.span())?;
|
||||
let module = &ctx.modules[&file];
|
||||
Ok(Value::Template(module.template.clone()))
|
||||
Ok(Value::Node(module.node.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
|
213
src/eval/node.rs
Normal file
213
src/eval/node.rs
Normal file
@ -0,0 +1,213 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::Hash;
|
||||
use std::mem;
|
||||
use std::ops::{Add, AddAssign};
|
||||
|
||||
use crate::diag::StrResult;
|
||||
use crate::geom::SpecAxis;
|
||||
use crate::layout::{Layout, PackedNode};
|
||||
use crate::library::{
|
||||
Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, Spacing,
|
||||
};
|
||||
use crate::util::EcoString;
|
||||
|
||||
/// A partial representation of a layout node.
|
||||
///
|
||||
/// A node is a composable intermediate representation that can be converted
|
||||
/// into a proper layout node by lifting it to the block or page level.
|
||||
#[derive(Clone)]
|
||||
pub enum Node {
|
||||
/// A word space.
|
||||
Space,
|
||||
/// A line break.
|
||||
Linebreak,
|
||||
/// A paragraph break.
|
||||
Parbreak,
|
||||
/// A page break.
|
||||
Pagebreak,
|
||||
/// Plain text.
|
||||
Text(EcoString),
|
||||
/// Spacing.
|
||||
Spacing(SpecAxis, Spacing),
|
||||
/// An inline node.
|
||||
Inline(PackedNode),
|
||||
/// A block node.
|
||||
Block(PackedNode),
|
||||
/// A sequence of nodes (which may themselves contain sequences).
|
||||
Seq(Vec<Self>),
|
||||
}
|
||||
|
||||
impl Node {
|
||||
/// Create an empty node.
|
||||
pub fn new() -> Self {
|
||||
Self::Seq(vec![])
|
||||
}
|
||||
|
||||
/// Create an inline-level node.
|
||||
pub fn inline<T>(node: T) -> Self
|
||||
where
|
||||
T: Layout + Debug + Hash + 'static,
|
||||
{
|
||||
Self::Inline(node.pack())
|
||||
}
|
||||
|
||||
/// Create a block-level node.
|
||||
pub fn block<T>(node: T) -> Self
|
||||
where
|
||||
T: Layout + Debug + Hash + 'static,
|
||||
{
|
||||
Self::Block(node.pack())
|
||||
}
|
||||
|
||||
/// Decoration this node.
|
||||
pub fn decorate(self, _: Decoration) -> Self {
|
||||
// TODO(set): Actually decorate.
|
||||
self
|
||||
}
|
||||
|
||||
/// Lift to a type-erased block-level node.
|
||||
pub fn into_block(self) -> PackedNode {
|
||||
if let Node::Block(packed) = self {
|
||||
packed
|
||||
} else {
|
||||
let mut packer = NodePacker::new();
|
||||
packer.walk(self);
|
||||
packer.into_block()
|
||||
}
|
||||
}
|
||||
|
||||
/// Lift to a document node, the root of the layout tree.
|
||||
pub fn into_document(self) -> DocumentNode {
|
||||
let mut packer = NodePacker::new();
|
||||
packer.walk(self);
|
||||
packer.into_document()
|
||||
}
|
||||
|
||||
/// Repeat this template `n` times.
|
||||
pub fn repeat(&self, n: i64) -> StrResult<Self> {
|
||||
let count = usize::try_from(n)
|
||||
.map_err(|_| format!("cannot repeat this template {} times", n))?;
|
||||
|
||||
// TODO(set): Make more efficient.
|
||||
Ok(Self::Seq(vec![self.clone(); count]))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Node {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad("<node>")
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Node {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Node {
|
||||
fn eq(&self, _: &Self) -> bool {
|
||||
// TODO(set): Figure out what to do here.
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Node {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
// TODO(set): Make more efficient.
|
||||
Self::Seq(vec![self, rhs])
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Node {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
*self = mem::take(self) + rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Packs a `Node` into a flow or whole document.
|
||||
struct NodePacker {
|
||||
document: Vec<PageNode>,
|
||||
flow: Vec<FlowChild>,
|
||||
par: Vec<ParChild>,
|
||||
}
|
||||
|
||||
impl NodePacker {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
document: vec![],
|
||||
flow: vec![],
|
||||
par: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn into_block(mut self) -> PackedNode {
|
||||
self.parbreak();
|
||||
FlowNode(self.flow).pack()
|
||||
}
|
||||
|
||||
fn into_document(mut self) -> DocumentNode {
|
||||
self.parbreak();
|
||||
self.pagebreak();
|
||||
DocumentNode(self.document)
|
||||
}
|
||||
|
||||
fn walk(&mut self, node: Node) {
|
||||
match node {
|
||||
Node::Space => {
|
||||
self.push_inline(ParChild::Text(' '.into()));
|
||||
}
|
||||
Node::Linebreak => {
|
||||
self.push_inline(ParChild::Text('\n'.into()));
|
||||
}
|
||||
Node::Parbreak => {
|
||||
self.parbreak();
|
||||
}
|
||||
Node::Pagebreak => {
|
||||
self.pagebreak();
|
||||
}
|
||||
Node::Text(text) => {
|
||||
self.push_inline(ParChild::Text(text));
|
||||
}
|
||||
Node::Spacing(axis, amount) => match axis {
|
||||
SpecAxis::Horizontal => self.push_inline(ParChild::Spacing(amount)),
|
||||
SpecAxis::Vertical => self.push_block(FlowChild::Spacing(amount)),
|
||||
},
|
||||
Node::Inline(inline) => {
|
||||
self.push_inline(ParChild::Node(inline));
|
||||
}
|
||||
Node::Block(block) => {
|
||||
self.push_block(FlowChild::Node(block));
|
||||
}
|
||||
Node::Seq(list) => {
|
||||
for node in list {
|
||||
self.walk(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parbreak(&mut self) {
|
||||
let children = mem::take(&mut self.par);
|
||||
if !children.is_empty() {
|
||||
self.flow.push(FlowChild::Node(ParNode(children).pack()));
|
||||
}
|
||||
}
|
||||
|
||||
fn pagebreak(&mut self) {
|
||||
let children = mem::take(&mut self.flow);
|
||||
self.document.push(PageNode(FlowNode(children).pack()));
|
||||
}
|
||||
|
||||
fn push_inline(&mut self, child: ParChild) {
|
||||
self.par.push(child);
|
||||
}
|
||||
|
||||
fn push_block(&mut self, child: FlowChild) {
|
||||
self.parbreak();
|
||||
self.flow.push(child);
|
||||
}
|
||||
}
|
@ -22,9 +22,9 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
(Str(a), Str(b)) => Str(a + b),
|
||||
(Array(a), Array(b)) => Array(a + b),
|
||||
(Dict(a), Dict(b)) => Dict(a + b),
|
||||
(Template(a), Template(b)) => Template(a + b),
|
||||
(Template(a), Str(b)) => Template(a + b),
|
||||
(Str(a), Template(b)) => Template(a + b),
|
||||
(Node(a), Node(b)) => Node(a + b),
|
||||
(Node(a), Str(b)) => Node(a + super::Node::Text(b)),
|
||||
(Str(a), Node(b)) => Node(super::Node::Text(a) + b),
|
||||
(a, b) => mismatch!("cannot join {} with {}", a, b),
|
||||
})
|
||||
}
|
||||
@ -84,9 +84,9 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
(Str(a), Str(b)) => Str(a + b),
|
||||
(Array(a), Array(b)) => Array(a + b),
|
||||
(Dict(a), Dict(b)) => Dict(a + b),
|
||||
(Template(a), Template(b)) => Template(a + b),
|
||||
(Template(a), Str(b)) => Template(a + b),
|
||||
(Str(a), Template(b)) => Template(a + b),
|
||||
(Node(a), Node(b)) => Node(a + b),
|
||||
(Node(a), Str(b)) => Node(a + super::Node::Text(b)),
|
||||
(Str(a), Node(b)) => Node(super::Node::Text(a) + b),
|
||||
|
||||
(a, b) => {
|
||||
if let (Dyn(a), Dyn(b)) = (&a, &b) {
|
||||
@ -179,8 +179,8 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
(Int(a), Str(b)) => Str(repeat_str(b, a)?),
|
||||
(Array(a), Int(b)) => Array(a.repeat(b)?),
|
||||
(Int(a), Array(b)) => Array(b.repeat(a)?),
|
||||
(Template(a), Int(b)) => Template(a.repeat(b)?),
|
||||
(Int(a), Template(b)) => Template(b.repeat(a)?),
|
||||
(Node(a), Int(b)) => Node(a.repeat(b)?),
|
||||
(Int(a), Node(b)) => Node(b.repeat(a)?),
|
||||
|
||||
(a, b) => mismatch!("cannot multiply {} with {}", a, b),
|
||||
})
|
||||
@ -297,7 +297,7 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool {
|
||||
(Str(a), Str(b)) => a == b,
|
||||
(Array(a), Array(b)) => a == b,
|
||||
(Dict(a), Dict(b)) => a == b,
|
||||
(Template(a), Template(b)) => a == b,
|
||||
(Node(a), Node(b)) => a == b,
|
||||
(Func(a), Func(b)) => a == b,
|
||||
(Dyn(a), Dyn(b)) => a == b,
|
||||
|
||||
|
@ -1,547 +0,0 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::Hash;
|
||||
use std::mem;
|
||||
use std::ops::{Add, AddAssign};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::diag::StrResult;
|
||||
use crate::geom::{Align, Dir, Length, Linear, Paint, Sides, Size, SpecAxis};
|
||||
use crate::layout::{Layout, PackedNode};
|
||||
use crate::library::{
|
||||
Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode,
|
||||
PlacedNode, Spacing,
|
||||
};
|
||||
use crate::style::Style;
|
||||
use crate::util::EcoString;
|
||||
|
||||
/// A template value: `[*Hi* there]`.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Template(Rc<Vec<TemplateNode>>);
|
||||
|
||||
/// One node in a template.
|
||||
#[derive(Clone)]
|
||||
enum TemplateNode {
|
||||
/// A word space.
|
||||
Space,
|
||||
/// A line break.
|
||||
Linebreak,
|
||||
/// A paragraph break.
|
||||
Parbreak,
|
||||
/// A page break.
|
||||
Pagebreak(bool),
|
||||
/// Plain text.
|
||||
Text(EcoString),
|
||||
/// Spacing.
|
||||
Spacing(SpecAxis, Spacing),
|
||||
/// A decorated template.
|
||||
Decorated(Decoration, Template),
|
||||
/// An inline node builder.
|
||||
Inline(Rc<dyn Fn(&Style) -> PackedNode>),
|
||||
/// A block node builder.
|
||||
Block(Rc<dyn Fn(&Style) -> PackedNode>),
|
||||
/// Save the current style.
|
||||
Save,
|
||||
/// Restore the last saved style.
|
||||
Restore,
|
||||
/// A function that can modify the current style.
|
||||
Modify(Rc<dyn Fn(&mut Style)>),
|
||||
}
|
||||
|
||||
impl Template {
|
||||
/// Create a new, empty template.
|
||||
pub fn new() -> Self {
|
||||
Self(Rc::new(vec![]))
|
||||
}
|
||||
|
||||
/// Create a template from a builder for an inline-level node.
|
||||
pub fn from_inline<F, T>(f: F) -> Self
|
||||
where
|
||||
F: Fn(&Style) -> T + 'static,
|
||||
T: Layout + Debug + Hash + 'static,
|
||||
{
|
||||
let node = TemplateNode::Inline(Rc::new(move |s| f(s).pack()));
|
||||
Self(Rc::new(vec![node]))
|
||||
}
|
||||
|
||||
/// Create a template from a builder for a block-level node.
|
||||
pub fn from_block<F, T>(f: F) -> Self
|
||||
where
|
||||
F: Fn(&Style) -> T + 'static,
|
||||
T: Layout + Debug + Hash + 'static,
|
||||
{
|
||||
let node = TemplateNode::Block(Rc::new(move |s| f(s).pack()));
|
||||
Self(Rc::new(vec![node]))
|
||||
}
|
||||
|
||||
/// Add a word space to the template.
|
||||
pub fn space(&mut self) {
|
||||
self.make_mut().push(TemplateNode::Space);
|
||||
}
|
||||
|
||||
/// Add a line break to the template.
|
||||
pub fn linebreak(&mut self) {
|
||||
self.make_mut().push(TemplateNode::Linebreak);
|
||||
}
|
||||
|
||||
/// Add a paragraph break to the template.
|
||||
pub fn parbreak(&mut self) {
|
||||
self.make_mut().push(TemplateNode::Parbreak);
|
||||
}
|
||||
|
||||
/// Add a page break to the template.
|
||||
pub fn pagebreak(&mut self, keep: bool) {
|
||||
self.make_mut().push(TemplateNode::Pagebreak(keep));
|
||||
}
|
||||
|
||||
/// Add text to the template.
|
||||
pub fn text(&mut self, text: impl Into<EcoString>) {
|
||||
self.make_mut().push(TemplateNode::Text(text.into()));
|
||||
}
|
||||
|
||||
/// Add text, but in monospace.
|
||||
pub fn monospace(&mut self, text: impl Into<EcoString>) {
|
||||
self.save();
|
||||
self.modify(|style| style.text_mut().monospace = true);
|
||||
self.text(text);
|
||||
self.restore();
|
||||
}
|
||||
|
||||
/// Add spacing along an axis.
|
||||
pub fn spacing(&mut self, axis: SpecAxis, spacing: Spacing) {
|
||||
self.make_mut().push(TemplateNode::Spacing(axis, spacing));
|
||||
}
|
||||
|
||||
/// Register a restorable snapshot.
|
||||
pub fn save(&mut self) {
|
||||
self.make_mut().push(TemplateNode::Save);
|
||||
}
|
||||
|
||||
/// Ensure that later nodes are untouched by style modifications made since
|
||||
/// the last snapshot.
|
||||
pub fn restore(&mut self) {
|
||||
self.make_mut().push(TemplateNode::Restore);
|
||||
}
|
||||
|
||||
/// Modify the style.
|
||||
pub fn modify<F>(&mut self, f: F)
|
||||
where
|
||||
F: Fn(&mut Style) + 'static,
|
||||
{
|
||||
self.make_mut().push(TemplateNode::Modify(Rc::new(f)));
|
||||
}
|
||||
|
||||
/// Return a new template which is modified from start to end.
|
||||
pub fn modified<F>(self, f: F) -> Self
|
||||
where
|
||||
F: Fn(&mut Style) + 'static,
|
||||
{
|
||||
let mut wrapper = Self::new();
|
||||
wrapper.save();
|
||||
wrapper.modify(f);
|
||||
wrapper += self;
|
||||
wrapper.restore();
|
||||
wrapper
|
||||
}
|
||||
|
||||
/// Add a decoration to all contained nodes.
|
||||
pub fn decorate(self, deco: Decoration) -> Self {
|
||||
Self(Rc::new(vec![TemplateNode::Decorated(deco, self)]))
|
||||
}
|
||||
|
||||
/// Pack the template into a layout node.
|
||||
pub fn pack(&self, style: &Style) -> PackedNode {
|
||||
if let [TemplateNode::Block(f)] = self.0.as_slice() {
|
||||
f(style)
|
||||
} else {
|
||||
let mut builder = Builder::new(style, false);
|
||||
builder.template(self);
|
||||
builder.build_flow().pack()
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the layout tree resulting from instantiating the template with the
|
||||
/// given style.
|
||||
pub fn to_document(&self, style: &Style) -> DocumentNode {
|
||||
let mut builder = Builder::new(style, true);
|
||||
builder.template(self);
|
||||
builder.build_document()
|
||||
}
|
||||
|
||||
/// Repeat this template `n` times.
|
||||
pub fn repeat(&self, n: i64) -> StrResult<Self> {
|
||||
let count = usize::try_from(n)
|
||||
.ok()
|
||||
.and_then(|n| self.0.len().checked_mul(n))
|
||||
.ok_or_else(|| format!("cannot repeat this template {} times", n))?;
|
||||
|
||||
Ok(Self(Rc::new(
|
||||
self.0.iter().cloned().cycle().take(count).collect(),
|
||||
)))
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the inner vector.
|
||||
fn make_mut(&mut self) -> &mut Vec<TemplateNode> {
|
||||
Rc::make_mut(&mut self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Template {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad("<template>")
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Template {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Rc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Template {
|
||||
type Output = Self;
|
||||
|
||||
fn add(mut self, rhs: Self) -> Self::Output {
|
||||
self += rhs;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Template {
|
||||
fn add_assign(&mut self, rhs: Template) {
|
||||
let sink = Rc::make_mut(&mut self.0);
|
||||
match Rc::try_unwrap(rhs.0) {
|
||||
Ok(source) => sink.extend(source),
|
||||
Err(rc) => sink.extend(rc.iter().cloned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<EcoString> for Template {
|
||||
type Output = Self;
|
||||
|
||||
fn add(mut self, rhs: EcoString) -> Self::Output {
|
||||
Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Template> for EcoString {
|
||||
type Output = Template;
|
||||
|
||||
fn add(self, mut rhs: Template) -> Self::Output {
|
||||
Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self));
|
||||
rhs
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms from template to layout representation.
|
||||
struct Builder {
|
||||
/// The current style.
|
||||
style: Style,
|
||||
/// Snapshots of the style.
|
||||
snapshots: Vec<Style>,
|
||||
/// The finished page nodes.
|
||||
finished: Vec<PageNode>,
|
||||
/// When we are building the top-level layout trees, this contains metrics
|
||||
/// of the page. While building a flow, this is `None`.
|
||||
page: Option<PageBuilder>,
|
||||
/// The currently built flow of paragraphs.
|
||||
flow: FlowBuilder,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// Create a new builder with a base style.
|
||||
fn new(style: &Style, pages: bool) -> Self {
|
||||
Self {
|
||||
style: style.clone(),
|
||||
snapshots: vec![],
|
||||
finished: vec![],
|
||||
page: pages.then(|| PageBuilder::new(style, true)),
|
||||
flow: FlowBuilder::new(style),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a template.
|
||||
fn template(&mut self, template: &Template) {
|
||||
for node in template.0.iter() {
|
||||
self.node(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a template node.
|
||||
fn node(&mut self, node: &TemplateNode) {
|
||||
match node {
|
||||
TemplateNode::Save => self.snapshots.push(self.style.clone()),
|
||||
TemplateNode::Restore => {
|
||||
let style = self.snapshots.pop().unwrap();
|
||||
let newpage = style.page != self.style.page;
|
||||
self.style = style;
|
||||
if newpage {
|
||||
self.pagebreak(true, false);
|
||||
}
|
||||
}
|
||||
TemplateNode::Space => self.space(),
|
||||
TemplateNode::Linebreak => self.linebreak(),
|
||||
TemplateNode::Parbreak => self.parbreak(),
|
||||
TemplateNode::Pagebreak(keep) => self.pagebreak(*keep, true),
|
||||
TemplateNode::Text(text) => self.text(text),
|
||||
TemplateNode::Spacing(axis, amount) => self.spacing(*axis, *amount),
|
||||
TemplateNode::Decorated(deco, template) => {
|
||||
self.flow.par.push(ParChild::Decorate(deco.clone()));
|
||||
self.template(template);
|
||||
self.flow.par.push(ParChild::Undecorate);
|
||||
}
|
||||
TemplateNode::Inline(f) => self.inline(f(&self.style)),
|
||||
TemplateNode::Block(f) => self.block(f(&self.style)),
|
||||
TemplateNode::Modify(f) => f(&mut self.style),
|
||||
}
|
||||
}
|
||||
|
||||
/// Push a word space into the active paragraph.
|
||||
fn space(&mut self) {
|
||||
self.flow.par.push_soft(self.make_text_node(' '));
|
||||
}
|
||||
|
||||
/// Apply a forced line break.
|
||||
fn linebreak(&mut self) {
|
||||
self.flow.par.push_hard(self.make_text_node('\n'));
|
||||
}
|
||||
|
||||
/// Apply a forced paragraph break.
|
||||
fn parbreak(&mut self) {
|
||||
let amount = self.style.par_spacing();
|
||||
self.flow.finish_par(&self.style);
|
||||
self.flow
|
||||
.push_soft(FlowChild::Spacing(Spacing::Linear(amount.into())));
|
||||
}
|
||||
|
||||
/// Apply a forced page break.
|
||||
fn pagebreak(&mut self, keep: bool, hard: bool) {
|
||||
if let Some(builder) = &mut self.page {
|
||||
let page = mem::replace(builder, PageBuilder::new(&self.style, hard));
|
||||
let flow = mem::replace(&mut self.flow, FlowBuilder::new(&self.style));
|
||||
self.finished.extend(page.build(flow.build(), keep));
|
||||
}
|
||||
}
|
||||
|
||||
/// Push text into the active paragraph.
|
||||
fn text(&mut self, text: impl Into<EcoString>) {
|
||||
self.flow.par.push(self.make_text_node(text));
|
||||
}
|
||||
|
||||
/// Push an inline node into the active paragraph.
|
||||
fn inline(&mut self, node: PackedNode) {
|
||||
self.flow.par.push(ParChild::Node(node.into()));
|
||||
}
|
||||
|
||||
/// Push a block node into the active flow, finishing the active paragraph.
|
||||
fn block(&mut self, node: PackedNode) {
|
||||
let mut is_placed = false;
|
||||
if let Some(placed) = node.downcast::<PlacedNode>() {
|
||||
is_placed = true;
|
||||
|
||||
// This prevents paragraph spacing after the placed node if it
|
||||
// is completely out-of-flow.
|
||||
if placed.out_of_flow() {
|
||||
self.flow.last = Last::None;
|
||||
}
|
||||
}
|
||||
|
||||
self.parbreak();
|
||||
self.flow.push(FlowChild::Node(node));
|
||||
self.parbreak();
|
||||
|
||||
// This prevents paragraph spacing between the placed node and
|
||||
// the paragraph below it.
|
||||
if is_placed {
|
||||
self.flow.last = Last::None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Push spacing into the active paragraph or flow depending on the `axis`.
|
||||
fn spacing(&mut self, axis: SpecAxis, spacing: Spacing) {
|
||||
match axis {
|
||||
SpecAxis::Vertical => {
|
||||
self.flow.finish_par(&self.style);
|
||||
self.flow.push_hard(FlowChild::Spacing(spacing));
|
||||
}
|
||||
SpecAxis::Horizontal => {
|
||||
self.flow.par.push_hard(ParChild::Spacing(spacing));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Finish building and return the created flow.
|
||||
fn build_flow(self) -> FlowNode {
|
||||
assert!(self.page.is_none());
|
||||
self.flow.build()
|
||||
}
|
||||
|
||||
/// Finish building and return the created layout tree.
|
||||
fn build_document(mut self) -> DocumentNode {
|
||||
assert!(self.page.is_some());
|
||||
self.pagebreak(true, false);
|
||||
DocumentNode { pages: self.finished }
|
||||
}
|
||||
|
||||
/// Construct a text node with the given text and settings from the current
|
||||
/// style.
|
||||
fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild {
|
||||
ParChild::Text(text.into(), Rc::clone(&self.style.text))
|
||||
}
|
||||
}
|
||||
|
||||
struct PageBuilder {
|
||||
size: Size,
|
||||
padding: Sides<Linear>,
|
||||
fill: Option<Paint>,
|
||||
hard: bool,
|
||||
}
|
||||
|
||||
impl PageBuilder {
|
||||
fn new(style: &Style, hard: bool) -> Self {
|
||||
Self {
|
||||
size: style.page.size,
|
||||
padding: style.page.margins(),
|
||||
fill: style.page.fill,
|
||||
hard,
|
||||
}
|
||||
}
|
||||
|
||||
fn build(self, child: FlowNode, keep: bool) -> Option<PageNode> {
|
||||
let Self { size, padding, fill, hard } = self;
|
||||
(!child.children.is_empty() || (keep && hard)).then(|| PageNode {
|
||||
child: child.pack().padded(padding),
|
||||
size,
|
||||
fill,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct FlowBuilder {
|
||||
children: Vec<FlowChild>,
|
||||
last: Last<FlowChild>,
|
||||
par: ParBuilder,
|
||||
}
|
||||
|
||||
impl FlowBuilder {
|
||||
fn new(style: &Style) -> Self {
|
||||
Self {
|
||||
children: vec![],
|
||||
last: Last::None,
|
||||
par: ParBuilder::new(style),
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, child: FlowChild) {
|
||||
self.children.extend(self.last.any());
|
||||
self.children.push(child);
|
||||
}
|
||||
|
||||
fn push_soft(&mut self, child: FlowChild) {
|
||||
self.last.soft(child);
|
||||
}
|
||||
|
||||
fn push_hard(&mut self, child: FlowChild) {
|
||||
self.last.hard();
|
||||
self.children.push(child);
|
||||
}
|
||||
|
||||
fn finish_par(&mut self, style: &Style) {
|
||||
let par = mem::replace(&mut self.par, ParBuilder::new(style));
|
||||
if let Some(par) = par.build() {
|
||||
self.push(par);
|
||||
}
|
||||
}
|
||||
|
||||
fn build(self) -> FlowNode {
|
||||
let Self { mut children, par, mut last } = self;
|
||||
if let Some(par) = par.build() {
|
||||
children.extend(last.any());
|
||||
children.push(par);
|
||||
}
|
||||
FlowNode { children }
|
||||
}
|
||||
}
|
||||
|
||||
struct ParBuilder {
|
||||
dir: Dir,
|
||||
align: Align,
|
||||
leading: Length,
|
||||
children: Vec<ParChild>,
|
||||
last: Last<ParChild>,
|
||||
}
|
||||
|
||||
impl ParBuilder {
|
||||
fn new(style: &Style) -> Self {
|
||||
Self {
|
||||
dir: style.par.dir,
|
||||
align: style.par.align,
|
||||
leading: style.leading(),
|
||||
children: vec![],
|
||||
last: Last::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, child: ParChild) {
|
||||
if let Some(soft) = self.last.any() {
|
||||
self.push_inner(soft);
|
||||
}
|
||||
self.push_inner(child);
|
||||
}
|
||||
|
||||
fn push_soft(&mut self, child: ParChild) {
|
||||
self.last.soft(child);
|
||||
}
|
||||
|
||||
fn push_hard(&mut self, child: ParChild) {
|
||||
self.last.hard();
|
||||
self.push_inner(child);
|
||||
}
|
||||
|
||||
fn push_inner(&mut self, child: ParChild) {
|
||||
if let ParChild::Text(text2, style2) = &child {
|
||||
if let Some(ParChild::Text(text1, style1)) = self.children.last_mut() {
|
||||
if Rc::ptr_eq(style1, style2) {
|
||||
text1.push_str(text2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.children.push(child);
|
||||
}
|
||||
|
||||
fn build(self) -> Option<FlowChild> {
|
||||
let Self { dir, align, leading, children, .. } = self;
|
||||
(!children.is_empty())
|
||||
.then(|| FlowChild::Node(ParNode { dir, align, leading, children }.pack()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Finite state machine for spacing coalescing.
|
||||
enum Last<N> {
|
||||
None,
|
||||
Any,
|
||||
Soft(N),
|
||||
}
|
||||
|
||||
impl<N> Last<N> {
|
||||
fn any(&mut self) -> Option<N> {
|
||||
match mem::replace(self, Self::Any) {
|
||||
Self::Soft(soft) => Some(soft),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn soft(&mut self, soft: N) {
|
||||
if let Self::Any = self {
|
||||
*self = Self::Soft(soft);
|
||||
}
|
||||
}
|
||||
|
||||
fn hard(&mut self) {
|
||||
*self = Self::None;
|
||||
}
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
use std::any::Any;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::Hash;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::{ops, Array, Dict, Function, Template};
|
||||
use super::{ops, Array, Dict, Function, Node};
|
||||
use crate::diag::StrResult;
|
||||
use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor};
|
||||
use crate::layout::Layout;
|
||||
use crate::syntax::Spanned;
|
||||
use crate::util::EcoString;
|
||||
|
||||
@ -40,8 +42,8 @@ pub enum Value {
|
||||
Array(Array),
|
||||
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
|
||||
Dict(Dict),
|
||||
/// A template value: `[*Hi* there]`.
|
||||
Template(Template),
|
||||
/// A node value: `[*Hi* there]`.
|
||||
Node(Node),
|
||||
/// An executable function.
|
||||
Func(Function),
|
||||
/// A dynamic value.
|
||||
@ -49,6 +51,22 @@ pub enum Value {
|
||||
}
|
||||
|
||||
impl Value {
|
||||
/// Create an inline-level node value.
|
||||
pub fn inline<T>(node: T) -> Self
|
||||
where
|
||||
T: Layout + Debug + Hash + 'static,
|
||||
{
|
||||
Self::Node(Node::inline(node))
|
||||
}
|
||||
|
||||
/// Create a block-level node value.
|
||||
pub fn block<T>(node: T) -> Self
|
||||
where
|
||||
T: Layout + Debug + Hash + 'static,
|
||||
{
|
||||
Self::Node(Node::block(node))
|
||||
}
|
||||
|
||||
/// The name of the stored value's type.
|
||||
pub fn type_name(&self) -> &'static str {
|
||||
match self {
|
||||
@ -66,7 +84,7 @@ impl Value {
|
||||
Self::Str(_) => EcoString::TYPE_NAME,
|
||||
Self::Array(_) => Array::TYPE_NAME,
|
||||
Self::Dict(_) => Dict::TYPE_NAME,
|
||||
Self::Template(_) => Template::TYPE_NAME,
|
||||
Self::Node(_) => Node::TYPE_NAME,
|
||||
Self::Func(_) => Function::TYPE_NAME,
|
||||
Self::Dyn(v) => v.type_name(),
|
||||
}
|
||||
@ -80,14 +98,29 @@ impl Value {
|
||||
T::cast(self)
|
||||
}
|
||||
|
||||
/// Join the value with another value.
|
||||
pub fn join(self, rhs: Self) -> StrResult<Self> {
|
||||
ops::join(self, rhs)
|
||||
}
|
||||
|
||||
/// Return the debug representation of the value.
|
||||
pub fn repr(&self) -> EcoString {
|
||||
format_eco!("{:?}", self)
|
||||
}
|
||||
|
||||
/// Join the value with another value.
|
||||
pub fn join(self, rhs: Self) -> StrResult<Self> {
|
||||
ops::join(self, rhs)
|
||||
/// Return the display representation of a value in form of a node.
|
||||
pub fn display(self) -> Node {
|
||||
match self {
|
||||
Value::None => Node::new(),
|
||||
Value::Int(v) => Node::Text(format_eco!("{}", v)),
|
||||
Value::Float(v) => Node::Text(format_eco!("{}", v)),
|
||||
Value::Str(v) => Node::Text(v),
|
||||
Value::Node(v) => v,
|
||||
// For values which can't be shown "naturally", we print the
|
||||
// representation in monospace.
|
||||
// TODO(set): Styled in monospace.
|
||||
v => Node::Text(v.repr()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +147,7 @@ impl Debug for Value {
|
||||
Self::Str(v) => Debug::fmt(v, f),
|
||||
Self::Array(v) => Debug::fmt(v, f),
|
||||
Self::Dict(v) => Debug::fmt(v, f),
|
||||
Self::Template(v) => Debug::fmt(v, f),
|
||||
Self::Node(v) => Debug::fmt(v, f),
|
||||
Self::Func(v) => Debug::fmt(v, f),
|
||||
Self::Dyn(v) => Debug::fmt(v, f),
|
||||
}
|
||||
@ -360,7 +393,7 @@ primitive! { Color: "color", Color }
|
||||
primitive! { EcoString: "string", Str }
|
||||
primitive! { Array: "array", Array }
|
||||
primitive! { Dict: "dictionary", Dict }
|
||||
primitive! { Template: "template", Template }
|
||||
primitive! { Node: "node", Node }
|
||||
primitive! { Function: "function", Func }
|
||||
|
||||
impl Cast<Value> for Value {
|
||||
|
141
src/eval/walk.rs
141
src/eval/walk.rs
@ -1,141 +0,0 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::{Eval, EvalContext, Template, Value};
|
||||
use crate::diag::TypResult;
|
||||
use crate::geom::Spec;
|
||||
use crate::layout::Layout;
|
||||
use crate::library::{GridNode, ParChild, ParNode, TrackSizing};
|
||||
use crate::syntax::ast::*;
|
||||
use crate::util::{BoolExt, EcoString};
|
||||
|
||||
/// Walk markup, filling the currently built template.
|
||||
pub trait Walk {
|
||||
/// Walk the node.
|
||||
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()>;
|
||||
}
|
||||
|
||||
impl Walk for Markup {
|
||||
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
|
||||
for node in self.nodes() {
|
||||
node.walk(ctx)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Walk for MarkupNode {
|
||||
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
|
||||
match self {
|
||||
Self::Space => ctx.template.space(),
|
||||
Self::Linebreak => ctx.template.linebreak(),
|
||||
Self::Parbreak => ctx.template.parbreak(),
|
||||
Self::Strong => ctx.template.modify(|s| s.text_mut().strong.flip()),
|
||||
Self::Emph => ctx.template.modify(|s| s.text_mut().emph.flip()),
|
||||
Self::Text(text) => ctx.template.text(text),
|
||||
Self::Raw(raw) => raw.walk(ctx)?,
|
||||
Self::Math(math) => math.walk(ctx)?,
|
||||
Self::Heading(heading) => heading.walk(ctx)?,
|
||||
Self::List(list) => list.walk(ctx)?,
|
||||
Self::Enum(enum_) => enum_.walk(ctx)?,
|
||||
Self::Expr(expr) => match expr.eval(ctx)? {
|
||||
Value::None => {}
|
||||
Value::Int(v) => ctx.template.text(format_eco!("{}", v)),
|
||||
Value::Float(v) => ctx.template.text(format_eco!("{}", v)),
|
||||
Value::Str(v) => ctx.template.text(v),
|
||||
Value::Template(v) => ctx.template += v,
|
||||
// For values which can't be shown "naturally", we print the
|
||||
// representation in monospace.
|
||||
other => ctx.template.monospace(other.repr()),
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Walk for RawNode {
|
||||
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
|
||||
if self.block {
|
||||
ctx.template.parbreak();
|
||||
}
|
||||
|
||||
ctx.template.monospace(&self.text);
|
||||
|
||||
if self.block {
|
||||
ctx.template.parbreak();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Walk for MathNode {
|
||||
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
|
||||
if self.display {
|
||||
ctx.template.parbreak();
|
||||
}
|
||||
|
||||
ctx.template.monospace(self.formula.trim());
|
||||
|
||||
if self.display {
|
||||
ctx.template.parbreak();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Walk for HeadingNode {
|
||||
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
|
||||
let level = self.level();
|
||||
let body = self.body().eval(ctx)?;
|
||||
|
||||
ctx.template.parbreak();
|
||||
ctx.template.save();
|
||||
ctx.template.modify(move |style| {
|
||||
let text = style.text_mut();
|
||||
let upscale = (1.6 - 0.1 * level as f64).max(0.75);
|
||||
text.size *= upscale;
|
||||
text.strong = true;
|
||||
});
|
||||
ctx.template += body;
|
||||
ctx.template.restore();
|
||||
ctx.template.parbreak();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Walk for ListNode {
|
||||
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
|
||||
let body = self.body().eval(ctx)?;
|
||||
walk_item(ctx, EcoString::from('•'), body);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Walk for EnumNode {
|
||||
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
|
||||
let body = self.body().eval(ctx)?;
|
||||
let label = format_eco!("{}.", self.number().unwrap_or(1));
|
||||
walk_item(ctx, label, body);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_item(ctx: &mut EvalContext, label: EcoString, body: Template) {
|
||||
ctx.template += Template::from_block(move |style| {
|
||||
let label = Layout::pack(ParNode {
|
||||
dir: style.par.dir,
|
||||
align: style.par.align,
|
||||
leading: style.leading(),
|
||||
children: vec![ParChild::Text(label.clone(), Rc::clone(&style.text))],
|
||||
});
|
||||
|
||||
let spacing = style.text.size / 2.0;
|
||||
GridNode {
|
||||
tracks: Spec::new(vec![TrackSizing::Auto; 2], vec![]),
|
||||
gutter: Spec::new(vec![TrackSizing::Linear(spacing.into())], vec![]),
|
||||
children: vec![label, body.pack(style)],
|
||||
}
|
||||
});
|
||||
}
|
@ -110,7 +110,7 @@ impl Context {
|
||||
/// Execute a source file and produce the resulting page nodes.
|
||||
pub fn execute(&mut self, id: SourceId) -> TypResult<DocumentNode> {
|
||||
let module = self.evaluate(id)?;
|
||||
Ok(module.template.to_document(&self.style))
|
||||
Ok(module.node.into_document())
|
||||
}
|
||||
|
||||
/// Typeset a source file into a collection of layouted frames.
|
||||
|
@ -15,15 +15,10 @@ pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
}
|
||||
|
||||
let aligns = args.expect::<Spec<_>>("alignment")?;
|
||||
let body = args.expect::<Template>("body")?;
|
||||
Ok(Value::Template(Template::from_block(move |style| {
|
||||
let mut style = style.clone();
|
||||
if let Some(x) = aligns.x {
|
||||
style.par_mut().align = x;
|
||||
}
|
||||
let body = args.expect::<Node>("body")?;
|
||||
|
||||
body.pack(&style).aligned(aligns)
|
||||
})))
|
||||
// TODO(set): Style paragraphs with x alignment.
|
||||
Ok(Value::block(body.into_block().aligned(aligns)))
|
||||
}
|
||||
|
||||
/// A node that aligns its child.
|
||||
|
@ -21,8 +21,8 @@ fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> {
|
||||
let thickness = args.named::<Linear>("thickness")?.or_else(|| args.find());
|
||||
let offset = args.named("offset")?;
|
||||
let extent = args.named("extent")?.unwrap_or_default();
|
||||
let body: Template = args.expect("body")?;
|
||||
Ok(Value::Template(body.decorate(Decoration::Line(
|
||||
let body: Node = args.expect("body")?;
|
||||
Ok(Value::Node(body.decorate(Decoration::Line(
|
||||
LineDecoration { kind, stroke, thickness, offset, extent },
|
||||
))))
|
||||
}
|
||||
@ -31,12 +31,9 @@ fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> {
|
||||
pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
let url = args.expect::<EcoString>("url")?;
|
||||
let body = args.find().unwrap_or_else(|| {
|
||||
let mut template = Template::new();
|
||||
template.text(url.trim_start_matches("mailto:").trim_start_matches("tel:"));
|
||||
template
|
||||
Node::Text(url.trim_start_matches("mailto:").trim_start_matches("tel:").into())
|
||||
});
|
||||
|
||||
Ok(Value::Template(body.decorate(Decoration::Link(url))))
|
||||
Ok(Value::Node(body.decorate(Decoration::Link(url))))
|
||||
}
|
||||
|
||||
/// A decoration for a frame.
|
||||
|
@ -3,14 +3,11 @@ use super::PageNode;
|
||||
|
||||
/// The root layout node, a document consisting of top-level page runs.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct DocumentNode {
|
||||
/// The page runs.
|
||||
pub pages: Vec<PageNode>,
|
||||
}
|
||||
pub struct DocumentNode(pub Vec<PageNode>);
|
||||
|
||||
impl DocumentNode {
|
||||
/// Layout the document into a sequence of frames, one per page.
|
||||
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
|
||||
self.pages.iter().flat_map(|node| node.layout(ctx)).collect()
|
||||
self.0.iter().flat_map(|node| node.layout(ctx)).collect()
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use super::prelude::*;
|
||||
use super::{AlignNode, ParNode, PlacedNode, Spacing};
|
||||
use super::{AlignNode, PlacedNode, Spacing};
|
||||
|
||||
/// `flow`: A vertical flow of paragraphs and other layout nodes.
|
||||
pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
enum Child {
|
||||
Spacing(Spacing),
|
||||
Any(Template),
|
||||
Any(Node),
|
||||
}
|
||||
|
||||
castable! {
|
||||
@ -17,22 +17,18 @@ pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
Value::Relative(v) => Self::Spacing(Spacing::Linear(v.into())),
|
||||
Value::Linear(v) => Self::Spacing(Spacing::Linear(v)),
|
||||
Value::Fractional(v) => Self::Spacing(Spacing::Fractional(v)),
|
||||
Value::Template(v) => Self::Any(v),
|
||||
Value::Node(v) => Self::Any(v),
|
||||
}
|
||||
|
||||
let children: Vec<Child> = args.all().collect();
|
||||
|
||||
Ok(Value::Template(Template::from_block(move |style| {
|
||||
let children = children
|
||||
.iter()
|
||||
let children = args
|
||||
.all()
|
||||
.map(|child| match child {
|
||||
Child::Spacing(spacing) => FlowChild::Spacing(*spacing),
|
||||
Child::Any(node) => FlowChild::Node(node.pack(style)),
|
||||
Child::Spacing(spacing) => FlowChild::Spacing(spacing),
|
||||
Child::Any(node) => FlowChild::Node(node.into_block()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
FlowNode { children }
|
||||
})))
|
||||
Ok(Value::block(FlowNode(children)))
|
||||
}
|
||||
|
||||
/// A vertical flow of content consisting of paragraphs and other layout nodes.
|
||||
@ -40,11 +36,7 @@ pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
/// This node is reponsible for layouting both the top-level content flow and
|
||||
/// the contents of boxes.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct FlowNode {
|
||||
/// The children that compose the flow. There are different kinds of
|
||||
/// children for different purposes.
|
||||
pub children: Vec<FlowChild>,
|
||||
}
|
||||
pub struct FlowNode(pub Vec<FlowChild>);
|
||||
|
||||
impl Layout for FlowNode {
|
||||
fn layout(
|
||||
@ -118,7 +110,7 @@ impl<'a> FlowLayouter<'a> {
|
||||
regions.expand.y = false;
|
||||
|
||||
Self {
|
||||
children: &flow.children,
|
||||
children: &flow.0,
|
||||
expand,
|
||||
full,
|
||||
regions,
|
||||
@ -175,9 +167,8 @@ impl<'a> FlowLayouter<'a> {
|
||||
}
|
||||
|
||||
let aligns = Spec::new(
|
||||
// For non-expanding paragraphs it is crucial that we align the
|
||||
// whole paragraph according to its internal alignment.
|
||||
node.downcast::<ParNode>().map_or(Align::Left, |par| par.align),
|
||||
// TODO(set): Align paragraph according to its internal alignment.
|
||||
Align::Left,
|
||||
// Vertical align node alignment is respected by the flow node.
|
||||
node.downcast::<AlignNode>()
|
||||
.and_then(|aligned| aligned.aligns.y)
|
||||
|
@ -39,15 +39,8 @@ pub fn grid(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
row_gutter.unwrap_or(base_gutter),
|
||||
);
|
||||
|
||||
let children: Vec<Template> = args.all().collect();
|
||||
|
||||
Ok(Value::Template(Template::from_block(move |style| {
|
||||
GridNode {
|
||||
tracks: tracks.clone(),
|
||||
gutter: gutter.clone(),
|
||||
children: children.iter().map(|child| child.pack(style)).collect(),
|
||||
}
|
||||
})))
|
||||
let children = args.all().map(Node::into_block).collect();
|
||||
Ok(Value::block(GridNode { tracks, gutter, children }))
|
||||
}
|
||||
|
||||
/// A node that arranges its children in a grid.
|
||||
|
@ -20,9 +20,9 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(Value::Template(Template::from_inline(move |_| {
|
||||
ImageNode { id, fit }.pack().sized(Spec::new(width, height))
|
||||
})))
|
||||
Ok(Value::inline(
|
||||
ImageNode { id, fit }.pack().sized(Spec::new(width, height)),
|
||||
))
|
||||
}
|
||||
|
||||
/// An image node.
|
||||
|
@ -26,7 +26,7 @@ mod prelude {
|
||||
pub use std::rc::Rc;
|
||||
|
||||
pub use crate::diag::{At, TypResult};
|
||||
pub use crate::eval::{Args, EvalContext, Smart, Template, Value};
|
||||
pub use crate::eval::{Args, EvalContext, Node, Smart, Value};
|
||||
pub use crate::frame::*;
|
||||
pub use crate::geom::*;
|
||||
pub use crate::layout::*;
|
||||
|
@ -7,7 +7,7 @@ pub fn pad(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
let top = args.named("top")?;
|
||||
let right = args.named("right")?;
|
||||
let bottom = args.named("bottom")?;
|
||||
let body: Template = args.expect("body")?;
|
||||
let body: Node = args.expect("body")?;
|
||||
let padding = Sides::new(
|
||||
left.or(all).unwrap_or_default(),
|
||||
top.or(all).unwrap_or_default(),
|
||||
@ -15,9 +15,7 @@ pub fn pad(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
bottom.or(all).unwrap_or_default(),
|
||||
);
|
||||
|
||||
Ok(Value::Template(Template::from_block(move |style| {
|
||||
body.pack(style).padded(padding)
|
||||
})))
|
||||
Ok(Value::block(body.into_block().padded(padding)))
|
||||
}
|
||||
|
||||
/// A node that adds padding to its child.
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::prelude::*;
|
||||
use super::PadNode;
|
||||
use crate::style::{Paper, PaperClass};
|
||||
|
||||
/// `page`: Configure pages.
|
||||
@ -20,8 +21,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
let bottom = args.named("bottom")?;
|
||||
let fill = args.named("fill")?;
|
||||
|
||||
ctx.template.modify(move |style| {
|
||||
let page = style.page_mut();
|
||||
let page = ctx.style.page_mut();
|
||||
|
||||
if let Some(paper) = paper {
|
||||
page.class = paper.class();
|
||||
@ -65,45 +65,38 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
if let Some(fill) = fill {
|
||||
page.fill = fill;
|
||||
}
|
||||
});
|
||||
|
||||
ctx.template.pagebreak(false);
|
||||
|
||||
Ok(Value::None)
|
||||
}
|
||||
|
||||
/// `pagebreak`: Start a new page.
|
||||
pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
|
||||
let mut template = Template::new();
|
||||
template.pagebreak(true);
|
||||
Ok(Value::Template(template))
|
||||
Ok(Value::Node(Node::Pagebreak))
|
||||
}
|
||||
|
||||
/// Layouts its children onto one or multiple pages.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct PageNode {
|
||||
/// The size of the page.
|
||||
pub size: Size,
|
||||
/// The background fill.
|
||||
pub fill: Option<Paint>,
|
||||
/// The node that produces the actual pages.
|
||||
pub child: PackedNode,
|
||||
}
|
||||
pub struct PageNode(pub PackedNode);
|
||||
|
||||
impl PageNode {
|
||||
/// Layout the page run into a sequence of frames, one per page.
|
||||
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
|
||||
// TODO(set): Get style from styles.
|
||||
let style = crate::style::PageStyle::default();
|
||||
|
||||
// When one of the lengths is infinite the page fits its content along
|
||||
// that axis.
|
||||
let expand = self.size.map(Length::is_finite);
|
||||
let regions = Regions::repeat(self.size, self.size, expand);
|
||||
let expand = style.size.map(Length::is_finite);
|
||||
let regions = Regions::repeat(style.size, style.size, expand);
|
||||
|
||||
// Layout the child.
|
||||
let padding = style.margins();
|
||||
let padded = PadNode { child: self.0.clone(), padding }.pack();
|
||||
let mut frames: Vec<_> =
|
||||
self.child.layout(ctx, ®ions).into_iter().map(|c| c.item).collect();
|
||||
padded.layout(ctx, ®ions).into_iter().map(|c| c.item).collect();
|
||||
|
||||
// Add background fill if requested.
|
||||
if let Some(fill) = self.fill {
|
||||
if let Some(fill) = style.fill {
|
||||
for frame in &mut frames {
|
||||
let shape = Shape::filled(Geometry::Rect(frame.size), fill);
|
||||
Rc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
|
||||
|
@ -38,8 +38,7 @@ pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
align = Some(v);
|
||||
}
|
||||
|
||||
ctx.template.modify(move |style| {
|
||||
let par = style.par_mut();
|
||||
let par = ctx.style.par_mut();
|
||||
|
||||
if let Some(dir) = dir {
|
||||
par.dir = dir;
|
||||
@ -57,25 +56,13 @@ pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
if let Some(spacing) = spacing {
|
||||
par.spacing = spacing;
|
||||
}
|
||||
});
|
||||
|
||||
ctx.template.parbreak();
|
||||
|
||||
Ok(Value::None)
|
||||
}
|
||||
|
||||
/// A node that arranges its children into a paragraph.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct ParNode {
|
||||
/// The text direction (either LTR or RTL).
|
||||
pub dir: Dir,
|
||||
/// How to align text in its line.
|
||||
pub align: Align,
|
||||
/// The spacing to insert between each line.
|
||||
pub leading: Length,
|
||||
/// The children to be arranged in a paragraph.
|
||||
pub children: Vec<ParChild>,
|
||||
}
|
||||
pub struct ParNode(pub Vec<ParChild>);
|
||||
|
||||
impl Layout for ParNode {
|
||||
fn layout(
|
||||
@ -87,11 +74,14 @@ impl Layout for ParNode {
|
||||
let text = self.collect_text();
|
||||
|
||||
// Find out the BiDi embedding levels.
|
||||
let bidi = BidiInfo::new(&text, Level::from_dir(self.dir));
|
||||
// TODO(set): Get dir from styles.
|
||||
let bidi = BidiInfo::new(&text, Level::from_dir(Dir::LTR));
|
||||
|
||||
// Prepare paragraph layout by building a representation on which we can
|
||||
// do line breaking without layouting each and every line from scratch.
|
||||
let layouter = ParLayouter::new(self, ctx, regions, bidi);
|
||||
// TODO(set): Get text style from styles.
|
||||
let style = crate::style::TextStyle::default();
|
||||
let layouter = ParLayouter::new(self, ctx, regions, bidi, &style);
|
||||
|
||||
// Find suitable linebreaks.
|
||||
layouter.layout(ctx, regions.clone())
|
||||
@ -123,7 +113,7 @@ impl ParNode {
|
||||
|
||||
/// The string representation of each child.
|
||||
fn strings(&self) -> impl Iterator<Item = &str> {
|
||||
self.children.iter().map(|child| match child {
|
||||
self.0.iter().map(|child| match child {
|
||||
ParChild::Spacing(_) => " ",
|
||||
ParChild::Text(ref piece, ..) => piece,
|
||||
ParChild::Node(..) => "\u{FFFC}",
|
||||
@ -138,7 +128,7 @@ pub enum ParChild {
|
||||
/// Spacing between other nodes.
|
||||
Spacing(Spacing),
|
||||
/// A run of text and how to align it in its line.
|
||||
Text(EcoString, Rc<TextStyle>),
|
||||
Text(EcoString),
|
||||
/// Any child node and how to align it in its line.
|
||||
Node(PackedNode),
|
||||
/// A decoration that applies until a matching `Undecorate`.
|
||||
@ -151,7 +141,7 @@ impl Debug for ParChild {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Spacing(v) => write!(f, "Spacing({:?})", v),
|
||||
Self::Text(text, _) => write!(f, "Text({:?})", text),
|
||||
Self::Text(text) => write!(f, "Text({:?})", text),
|
||||
Self::Node(node) => node.fmt(f),
|
||||
Self::Decorate(deco) => write!(f, "Decorate({:?})", deco),
|
||||
Self::Undecorate => write!(f, "Undecorate"),
|
||||
@ -198,6 +188,7 @@ impl<'a> ParLayouter<'a> {
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
bidi: BidiInfo<'a>,
|
||||
style: &'a TextStyle,
|
||||
) -> Self {
|
||||
let mut items = vec![];
|
||||
let mut ranges = vec![];
|
||||
@ -205,7 +196,7 @@ impl<'a> ParLayouter<'a> {
|
||||
let mut decos = vec![];
|
||||
|
||||
// Layout the children and collect them into items.
|
||||
for (range, child) in par.ranges().zip(&par.children) {
|
||||
for (range, child) in par.ranges().zip(&par.0) {
|
||||
match *child {
|
||||
ParChild::Spacing(Spacing::Linear(v)) => {
|
||||
let resolved = v.resolve(regions.current.x);
|
||||
@ -216,7 +207,7 @@ impl<'a> ParLayouter<'a> {
|
||||
items.push(ParItem::Fractional(v));
|
||||
ranges.push(range);
|
||||
}
|
||||
ParChild::Text(_, ref style) => {
|
||||
ParChild::Text(_) => {
|
||||
// TODO: Also split by language and script.
|
||||
let mut cursor = range.start;
|
||||
for (level, group) in bidi.levels[range].group_by_key(|&lvl| lvl) {
|
||||
@ -252,8 +243,9 @@ impl<'a> ParLayouter<'a> {
|
||||
}
|
||||
|
||||
Self {
|
||||
align: par.align,
|
||||
leading: par.leading,
|
||||
// TODO(set): Get alignment and leading from styles.
|
||||
align: Align::Left,
|
||||
leading: Length::pt(6.0),
|
||||
bidi,
|
||||
items,
|
||||
ranges,
|
||||
|
@ -6,12 +6,10 @@ pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
let aligns = args.find().unwrap_or(Spec::new(Some(Align::Left), None));
|
||||
let tx = args.named("dx")?.unwrap_or_default();
|
||||
let ty = args.named("dy")?.unwrap_or_default();
|
||||
let body: Template = args.expect("body")?;
|
||||
Ok(Value::Template(Template::from_block(move |style| {
|
||||
PlacedNode {
|
||||
child: body.pack(style).moved(Point::new(tx, ty)).aligned(aligns),
|
||||
}
|
||||
})))
|
||||
let body: Node = args.expect("body")?;
|
||||
Ok(Value::block(PlacedNode {
|
||||
child: body.into_block().moved(Point::new(tx, ty)).aligned(aligns),
|
||||
}))
|
||||
}
|
||||
|
||||
/// A node that places its child absolutely.
|
||||
|
@ -76,20 +76,18 @@ fn shape_impl(
|
||||
}
|
||||
|
||||
// The shape's contents.
|
||||
let body = args.find::<Template>();
|
||||
let body = args.find::<Node>();
|
||||
|
||||
Ok(Value::Template(Template::from_inline(move |style| {
|
||||
Ok(Value::inline(
|
||||
ShapeNode {
|
||||
kind,
|
||||
fill,
|
||||
stroke,
|
||||
child: body
|
||||
.as_ref()
|
||||
.map(|body| body.pack(style).padded(Sides::splat(padding))),
|
||||
child: body.map(|body| body.into_block().padded(Sides::splat(padding))),
|
||||
}
|
||||
.pack()
|
||||
.sized(Spec::new(width, height))
|
||||
})))
|
||||
.sized(Spec::new(width, height)),
|
||||
))
|
||||
}
|
||||
|
||||
/// Places its child into a sizable and fillable shape.
|
||||
|
@ -4,18 +4,16 @@ use super::prelude::*;
|
||||
pub fn box_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
let width = args.named("width")?;
|
||||
let height = args.named("height")?;
|
||||
let body: Template = args.find().unwrap_or_default();
|
||||
Ok(Value::Template(Template::from_inline(move |style| {
|
||||
body.pack(style).sized(Spec::new(width, height))
|
||||
})))
|
||||
let body: Node = args.find().unwrap_or_default();
|
||||
Ok(Value::inline(
|
||||
body.into_block().sized(Spec::new(width, height)),
|
||||
))
|
||||
}
|
||||
|
||||
/// `block`: Place content into the flow.
|
||||
pub fn block(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
let body: Template = args.find().unwrap_or_default();
|
||||
Ok(Value::Template(Template::from_block(move |style| {
|
||||
body.pack(style)
|
||||
})))
|
||||
let body: Node = args.find().unwrap_or_default();
|
||||
Ok(Value::block(body.into_block()))
|
||||
}
|
||||
|
||||
/// A node that sizes its child.
|
||||
|
@ -2,16 +2,18 @@ use super::prelude::*;
|
||||
|
||||
/// `h`: Horizontal spacing.
|
||||
pub fn h(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
let mut template = Template::new();
|
||||
template.spacing(SpecAxis::Horizontal, args.expect("spacing")?);
|
||||
Ok(Value::Template(template))
|
||||
Ok(Value::Node(Node::Spacing(
|
||||
SpecAxis::Horizontal,
|
||||
args.expect("spacing")?,
|
||||
)))
|
||||
}
|
||||
|
||||
/// `v`: Vertical spacing.
|
||||
pub fn v(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
let mut template = Template::new();
|
||||
template.spacing(SpecAxis::Vertical, args.expect("spacing")?);
|
||||
Ok(Value::Template(template))
|
||||
Ok(Value::Node(Node::Spacing(
|
||||
SpecAxis::Vertical,
|
||||
args.expect("spacing")?,
|
||||
)))
|
||||
}
|
||||
|
||||
/// Kinds of spacing.
|
||||
|
@ -7,7 +7,7 @@ use super::{AlignNode, Spacing};
|
||||
pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
enum Child {
|
||||
Spacing(Spacing),
|
||||
Any(Template),
|
||||
Any(Node),
|
||||
}
|
||||
|
||||
castable! {
|
||||
@ -17,22 +17,20 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
Value::Relative(v) => Self::Spacing(Spacing::Linear(v.into())),
|
||||
Value::Linear(v) => Self::Spacing(Spacing::Linear(v)),
|
||||
Value::Fractional(v) => Self::Spacing(Spacing::Fractional(v)),
|
||||
Value::Template(v) => Self::Any(v),
|
||||
Value::Node(v) => Self::Any(v),
|
||||
}
|
||||
|
||||
let dir = args.named("dir")?.unwrap_or(Dir::TTB);
|
||||
let spacing = args.named("spacing")?;
|
||||
let list: Vec<Child> = args.all().collect();
|
||||
|
||||
Ok(Value::Template(Template::from_block(move |style| {
|
||||
let mut children = vec![];
|
||||
let mut delayed = None;
|
||||
|
||||
// Build the list of stack children.
|
||||
for child in &list {
|
||||
for child in args.all() {
|
||||
match child {
|
||||
Child::Spacing(v) => {
|
||||
children.push(StackChild::Spacing(*v));
|
||||
children.push(StackChild::Spacing(v));
|
||||
delayed = None;
|
||||
}
|
||||
Child::Any(child) => {
|
||||
@ -40,15 +38,13 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
children.push(StackChild::Spacing(v));
|
||||
}
|
||||
|
||||
let node = child.pack(style);
|
||||
children.push(StackChild::Node(node));
|
||||
children.push(StackChild::Node(child.into_block()));
|
||||
delayed = spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StackNode { dir, children }
|
||||
})))
|
||||
Ok(Value::block(StackNode { dir, children }))
|
||||
}
|
||||
|
||||
/// A node that stacks its children.
|
||||
|
@ -12,8 +12,8 @@ use crate::font::{
|
||||
};
|
||||
use crate::geom::{Dir, Em, Length, Point, Size};
|
||||
use crate::style::{
|
||||
FontFamily, FontFeatures, NumberPosition, NumberType, NumberWidth, Style,
|
||||
StylisticSet, TextStyle,
|
||||
FontFamily, FontFeatures, NumberPosition, NumberType, NumberWidth, StylisticSet,
|
||||
TextStyle,
|
||||
};
|
||||
use crate::util::{EcoString, SliceExt};
|
||||
|
||||
@ -179,7 +179,6 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
let slashed_zero = args.named("slashed-zero")?;
|
||||
let fractions = args.named("fractions")?;
|
||||
let features = args.named("features")?;
|
||||
let body = args.find::<Template>();
|
||||
|
||||
macro_rules! set {
|
||||
($target:expr => $source:expr) => {
|
||||
@ -189,8 +188,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
};
|
||||
}
|
||||
|
||||
let f = move |style_: &mut Style| {
|
||||
let text = style_.text_mut();
|
||||
let text = ctx.style.text_mut();
|
||||
set!(text.families_mut().list => list.clone());
|
||||
set!(text.families_mut().serif => serif.clone());
|
||||
set!(text.families_mut().sans_serif => sans_serif.clone());
|
||||
@ -217,14 +215,8 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
set!(text.features_mut().numbers.slashed_zero => slashed_zero);
|
||||
set!(text.features_mut().numbers.fractions => fractions);
|
||||
set!(text.features_mut().raw => features.clone());
|
||||
};
|
||||
|
||||
Ok(if let Some(body) = body {
|
||||
Value::Template(body.modified(f))
|
||||
} else {
|
||||
ctx.template.modify(f);
|
||||
Value::None
|
||||
})
|
||||
Ok(Value::None)
|
||||
}
|
||||
|
||||
/// Shape text into [`ShapedText`].
|
||||
|
@ -26,15 +26,14 @@ pub fn rotate(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
}
|
||||
|
||||
fn transform_impl(args: &mut Args, transform: Transform) -> TypResult<Value> {
|
||||
let body: Template = args.expect("body")?;
|
||||
let body: Node = args.expect("body")?;
|
||||
let origin = args
|
||||
.named("origin")?
|
||||
.unwrap_or(Spec::splat(None))
|
||||
.unwrap_or(Align::CENTER_HORIZON);
|
||||
|
||||
Ok(Value::Template(Template::from_inline(move |style| {
|
||||
body.pack(style).transformed(transform, origin)
|
||||
})))
|
||||
Ok(Value::inline(
|
||||
body.into_block().transformed(transform, origin),
|
||||
))
|
||||
}
|
||||
|
||||
/// A node that transforms its child without affecting layout.
|
||||
|
Loading…
x
Reference in New Issue
Block a user