mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Move and rename many things 🚛
This commit is contained in:
parent
1c40dc42e7
commit
aae67bd572
@ -33,18 +33,18 @@ fn benchmarks(c: &mut Criterion) {
|
||||
|
||||
// Prepare intermediate results and run warm.
|
||||
let state = State::default();
|
||||
let tree = parse(COMA).output;
|
||||
let document = eval(&tree, Rc::clone(&env), state.clone()).output;
|
||||
let layouts = layout(&document, Rc::clone(&env));
|
||||
let syntax_tree = parse(COMA).output;
|
||||
let layout_tree = eval(&syntax_tree, Rc::clone(&env), state.clone()).output;
|
||||
let frames = layout(&layout_tree, Rc::clone(&env));
|
||||
|
||||
// Bench!
|
||||
bench!("parse-coma": parse(COMA));
|
||||
bench!("eval-coma": eval(&tree, Rc::clone(&env), state.clone()));
|
||||
bench!("layout-coma": layout(&document, Rc::clone(&env)));
|
||||
bench!("eval-coma": eval(&syntax_tree, Rc::clone(&env), state.clone()));
|
||||
bench!("layout-coma": layout(&layout_tree, Rc::clone(&env)));
|
||||
bench!("typeset-coma": typeset(COMA, Rc::clone(&env), state.clone()));
|
||||
|
||||
let env = env.borrow();
|
||||
bench!("export-pdf-coma": pdf::export(&layouts, &env));
|
||||
bench!("export-pdf-coma": pdf::export(&frames, &env));
|
||||
}
|
||||
|
||||
criterion_group!(benches, benchmarks);
|
||||
|
21
src/diag.rs
21
src/diag.rs
@ -1,8 +1,6 @@
|
||||
//! Diagnostics and decorations for source code.
|
||||
//!
|
||||
//! There are no fatal errors. The document will always compile and yield a
|
||||
//! layout on a best effort process, but diagnostics are nevertheless generated
|
||||
//! for incorrect things.
|
||||
//! Errors are never fatal, the document will always compile and yield a layout.
|
||||
|
||||
use crate::syntax::SpanVec;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
@ -21,22 +19,9 @@ impl<T> Pass<T> {
|
||||
pub fn new(output: T, feedback: Feedback) -> Self {
|
||||
Self { output, feedback }
|
||||
}
|
||||
|
||||
/// Create a new pass with empty feedback.
|
||||
pub fn okay(output: T) -> Self {
|
||||
Self { output, feedback: Feedback::new() }
|
||||
}
|
||||
|
||||
/// Map the output type and keep the feedback data.
|
||||
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Pass<U> {
|
||||
Pass {
|
||||
output: f(self.output),
|
||||
feedback: self.feedback,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Diagnostic and semantic syntax highlighting data.
|
||||
/// Diagnostics and semantic syntax highlighting information.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct Feedback {
|
||||
/// Diagnostics about the source code.
|
||||
@ -64,7 +49,7 @@ impl Feedback {
|
||||
}
|
||||
}
|
||||
|
||||
/// A diagnostic that arose in parsing or layouting.
|
||||
/// A diagnostic with severity level and message.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct Diag {
|
||||
|
13
src/env.rs
13
src/env.rs
@ -42,12 +42,13 @@ impl ResourceLoader {
|
||||
Self { paths: HashMap::new(), entries: vec![] }
|
||||
}
|
||||
|
||||
/// Load a resource from a path.
|
||||
pub fn load<R: 'static>(
|
||||
&mut self,
|
||||
path: impl AsRef<Path>,
|
||||
parse: impl FnOnce(Vec<u8>) -> Option<R>,
|
||||
) -> Option<(ResourceId, &R)> {
|
||||
/// Load a resource from a path and parse it.
|
||||
pub fn load<P, F, R>(&mut self, path: P, parse: F) -> Option<(ResourceId, &R)>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
F: FnOnce(Vec<u8>) -> Option<R>,
|
||||
R: 'static,
|
||||
{
|
||||
let path = path.as_ref();
|
||||
let id = match self.paths.entry(path.to_owned()) {
|
||||
Entry::Occupied(entry) => *entry.get(),
|
||||
|
@ -1,14 +1,37 @@
|
||||
use super::*;
|
||||
use crate::diag::Deco;
|
||||
|
||||
/// Evaluated arguments to a function.
|
||||
#[derive(Debug)]
|
||||
pub struct Args {
|
||||
span: Span,
|
||||
pos: SpanVec<Value>,
|
||||
named: Vec<(Spanned<String>, Spanned<Value>)>,
|
||||
impl Eval for Spanned<&ExprCall> {
|
||||
type Output = Value;
|
||||
|
||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||
let name = &self.v.name.v;
|
||||
let span = self.v.name.span;
|
||||
|
||||
if let Some(value) = ctx.state.scope.get(name) {
|
||||
if let Value::Func(func) = value {
|
||||
let func = func.clone();
|
||||
ctx.deco(Deco::Resolved.with_span(span));
|
||||
|
||||
let mut args = self.v.args.as_ref().eval(ctx);
|
||||
let returned = func(ctx, &mut args);
|
||||
args.finish(ctx);
|
||||
|
||||
return returned;
|
||||
} else {
|
||||
let ty = value.type_name();
|
||||
ctx.diag(error!(span, "a value of type {} is not callable", ty));
|
||||
}
|
||||
} else if !name.is_empty() {
|
||||
ctx.diag(error!(span, "unknown function"));
|
||||
}
|
||||
|
||||
ctx.deco(Deco::Unresolved.with_span(span));
|
||||
Value::Error
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for Spanned<&Arguments> {
|
||||
impl Eval for Spanned<&ExprArgs> {
|
||||
type Output = Args;
|
||||
|
||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||
@ -33,6 +56,14 @@ impl Eval for Spanned<&Arguments> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluated arguments to a function.
|
||||
#[derive(Debug)]
|
||||
pub struct Args {
|
||||
span: Span,
|
||||
pos: SpanVec<Value>,
|
||||
named: Vec<(Spanned<String>, Spanned<Value>)>,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
/// Find the first convertible positional argument.
|
||||
pub fn find<T>(&mut self, ctx: &mut EvalContext) -> Option<T>
|
||||
@ -66,9 +97,8 @@ impl Args {
|
||||
self.pos.iter_mut().filter_map(move |slot| try_cast(ctx, slot))
|
||||
}
|
||||
|
||||
/// Convert the value for the given named argument.
|
||||
///
|
||||
/// Generates an error if the conversion fails.
|
||||
/// Convert the value for the given named argument, producing an error if
|
||||
/// the conversion fails.
|
||||
pub fn get<'a, T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option<T>
|
||||
where
|
||||
T: Cast<Spanned<Value>>,
|
||||
@ -78,7 +108,7 @@ impl Args {
|
||||
cast(ctx, value)
|
||||
}
|
||||
|
||||
/// Generate "unexpected argument" errors for all remaining arguments.
|
||||
/// Produce "unexpected argument" errors for all remaining arguments.
|
||||
pub fn finish(self, ctx: &mut EvalContext) {
|
||||
let a = self.pos.iter().map(|v| v.as_ref());
|
||||
let b = self.named.iter().map(|(k, v)| (&v.v).with_span(k.span.join(v.span)));
|
298
src/eval/context.rs
Normal file
298
src/eval/context.rs
Normal file
@ -0,0 +1,298 @@
|
||||
use std::any::Any;
|
||||
use std::rc::Rc;
|
||||
|
||||
use fontdock::FontStyle;
|
||||
|
||||
use super::*;
|
||||
use crate::diag::Diag;
|
||||
use crate::diag::{Deco, Feedback, Pass};
|
||||
use crate::env::SharedEnv;
|
||||
use crate::geom::{ChildAlign, Dir, Gen, LayoutDirs, Length, Linear, Sides, Size};
|
||||
use crate::layout::{
|
||||
Expansion, Node, NodePad, NodePages, NodePar, NodeSpacing, NodeStack, NodeText, Tree,
|
||||
};
|
||||
|
||||
/// The context for evaluation.
|
||||
#[derive(Debug)]
|
||||
pub struct EvalContext {
|
||||
/// The environment from which resources are gathered.
|
||||
pub env: SharedEnv,
|
||||
/// The active evaluation state.
|
||||
pub state: State,
|
||||
/// The accumulated feedback.
|
||||
feedback: Feedback,
|
||||
/// The finished page runs.
|
||||
runs: Vec<NodePages>,
|
||||
/// The stack of logical groups (paragraphs and such).
|
||||
///
|
||||
/// Each entry contains metadata about the group and nodes that are at the
|
||||
/// same level as the group, which will return to `inner` once the group is
|
||||
/// finished.
|
||||
groups: Vec<(Box<dyn Any>, Vec<Node>)>,
|
||||
/// The nodes in the current innermost group
|
||||
/// (whose metadata is in `groups.last()`).
|
||||
inner: Vec<Node>,
|
||||
}
|
||||
|
||||
impl EvalContext {
|
||||
/// Create a new evaluation context with a base state.
|
||||
pub fn new(env: SharedEnv, state: State) -> Self {
|
||||
Self {
|
||||
env,
|
||||
state,
|
||||
groups: vec![],
|
||||
inner: vec![],
|
||||
runs: vec![],
|
||||
feedback: Feedback::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Finish evaluation and return the created document.
|
||||
pub fn finish(self) -> Pass<Tree> {
|
||||
assert!(self.groups.is_empty(), "unfinished group");
|
||||
Pass::new(Tree { runs: self.runs }, self.feedback)
|
||||
}
|
||||
|
||||
/// Add a diagnostic to the feedback.
|
||||
pub fn diag(&mut self, diag: Spanned<Diag>) {
|
||||
self.feedback.diags.push(diag);
|
||||
}
|
||||
|
||||
/// Add a decoration to the feedback.
|
||||
pub fn deco(&mut self, deco: Spanned<Deco>) {
|
||||
self.feedback.decos.push(deco);
|
||||
}
|
||||
|
||||
/// Push a layout node to the active group.
|
||||
///
|
||||
/// Spacing nodes will be handled according to their [`Softness`].
|
||||
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);
|
||||
}
|
||||
|
||||
/// 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,
|
||||
align: self.state.align,
|
||||
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: Node::any(NodePad {
|
||||
padding: group.padding,
|
||||
child: Node::any(NodeStack {
|
||||
dirs: group.dirs,
|
||||
align: group.align,
|
||||
expansion: Gen::uniform(Expansion::Fill),
|
||||
children,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
}
|
||||
group.softness
|
||||
}
|
||||
|
||||
/// Start a content group.
|
||||
///
|
||||
/// This also starts an inner paragraph.
|
||||
pub fn start_content_group(&mut self) {
|
||||
self.start_group(ContentGroup);
|
||||
self.start_par_group();
|
||||
}
|
||||
|
||||
/// End a content group and return the resulting nodes.
|
||||
///
|
||||
/// This also ends an inner paragraph.
|
||||
pub fn end_content_group(&mut self) -> Vec<Node> {
|
||||
self.end_par_group();
|
||||
self.end_group::<ContentGroup>().1
|
||||
}
|
||||
|
||||
/// 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,
|
||||
align: self.state.align,
|
||||
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,
|
||||
align: group.align,
|
||||
// FIXME: This is a hack and should be superseded by something
|
||||
// better.
|
||||
cross_expansion: if self.groups.len() <= 1 {
|
||||
Expansion::Fill
|
||||
} else {
|
||||
Expansion::Fit
|
||||
},
|
||||
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>>>) {
|
||||
let dirs = Gen::new(
|
||||
new.main.map(|s| s.v).unwrap_or(self.state.dirs.main),
|
||||
new.cross.map(|s| s.v).unwrap_or(self.state.dirs.cross),
|
||||
);
|
||||
|
||||
if dirs.main.axis() != dirs.cross.axis() {
|
||||
self.state.dirs = dirs;
|
||||
} else {
|
||||
for dir in new.main.iter().chain(new.cross.iter()) {
|
||||
self.diag(error!(dir.span, "aligned axis"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a forced line break.
|
||||
pub fn apply_linebreak(&mut self) {
|
||||
self.end_par_group();
|
||||
self.start_par_group();
|
||||
}
|
||||
|
||||
/// Apply a forced paragraph break.
|
||||
pub fn apply_parbreak(&mut self) {
|
||||
self.end_par_group();
|
||||
let em = self.state.font.font_size();
|
||||
self.push(NodeSpacing {
|
||||
amount: self.state.par.par_spacing.resolve(em),
|
||||
softness: Softness::Soft,
|
||||
});
|
||||
self.start_par_group();
|
||||
}
|
||||
|
||||
/// Construct a text node from the given string based on the active text
|
||||
/// state.
|
||||
pub fn make_text_node(&self, text: String) -> NodeText {
|
||||
let mut variant = self.state.font.variant;
|
||||
|
||||
if self.state.font.strong {
|
||||
variant.weight = variant.weight.thicken(300);
|
||||
}
|
||||
|
||||
if self.state.font.emph {
|
||||
variant.style = match variant.style {
|
||||
FontStyle::Normal => FontStyle::Italic,
|
||||
FontStyle::Italic => FontStyle::Normal,
|
||||
FontStyle::Oblique => FontStyle::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
NodeText {
|
||||
text,
|
||||
align: self.state.align,
|
||||
dir: self.state.dirs.cross,
|
||||
font_size: self.state.font.font_size(),
|
||||
families: Rc::clone(&self.state.font.families),
|
||||
variant,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// A group for a page run.
|
||||
#[derive(Debug)]
|
||||
struct PageGroup {
|
||||
size: Size,
|
||||
padding: Sides<Linear>,
|
||||
dirs: LayoutDirs,
|
||||
align: ChildAlign,
|
||||
softness: Softness,
|
||||
}
|
||||
|
||||
/// A group for generic content.
|
||||
#[derive(Debug)]
|
||||
struct ContentGroup;
|
||||
|
||||
/// A group for a paragraph.
|
||||
#[derive(Debug)]
|
||||
struct ParGroup {
|
||||
dirs: LayoutDirs,
|
||||
align: ChildAlign,
|
||||
line_spacing: Length,
|
||||
}
|
383
src/eval/mod.rs
383
src/eval/mod.rs
@ -1,36 +1,32 @@
|
||||
//! Evaluation of syntax trees.
|
||||
//! Evaluation of syntax trees into layout trees.
|
||||
|
||||
#[macro_use]
|
||||
mod value;
|
||||
mod args;
|
||||
mod call;
|
||||
mod context;
|
||||
mod scope;
|
||||
mod state;
|
||||
|
||||
pub use args::*;
|
||||
pub use call::*;
|
||||
pub use context::*;
|
||||
pub use scope::*;
|
||||
pub use state::*;
|
||||
pub use value::*;
|
||||
|
||||
use std::any::Any;
|
||||
use std::rc::Rc;
|
||||
|
||||
use fontdock::FontStyle;
|
||||
|
||||
use crate::color::Color;
|
||||
use crate::diag::Diag;
|
||||
use crate::diag::{Deco, Feedback, Pass};
|
||||
use crate::diag::Pass;
|
||||
use crate::env::SharedEnv;
|
||||
use crate::geom::{BoxAlign, Dir, Flow, Gen, Length, Linear, Relative, Sides, Size};
|
||||
use crate::layout::{
|
||||
Document, Expansion, LayoutNode, Pad, Pages, Par, Spacing, Stack, Text,
|
||||
};
|
||||
use crate::geom::{Gen, Length, Relative};
|
||||
use crate::layout::{self, Expansion, NodeSpacing, NodeStack};
|
||||
use crate::syntax::*;
|
||||
|
||||
/// Evaluate a syntax tree into a document.
|
||||
/// Evaluate a syntax tree into a layout tree.
|
||||
///
|
||||
/// The given `state` is the base state that may be updated over the course of
|
||||
/// evaluation.
|
||||
pub fn eval(tree: &SynTree, env: SharedEnv, state: State) -> Pass<Document> {
|
||||
pub fn eval(tree: &Tree, env: SharedEnv, state: State) -> Pass<layout::Tree> {
|
||||
let mut ctx = EvalContext::new(env, state);
|
||||
ctx.start_page_group(Softness::Hard);
|
||||
tree.eval(&mut ctx);
|
||||
@ -38,285 +34,6 @@ pub fn eval(tree: &SynTree, env: SharedEnv, state: State) -> Pass<Document> {
|
||||
ctx.finish()
|
||||
}
|
||||
|
||||
/// The context for evaluation.
|
||||
#[derive(Debug)]
|
||||
pub struct EvalContext {
|
||||
/// The environment from which resources are gathered.
|
||||
pub env: SharedEnv,
|
||||
/// The active evaluation state.
|
||||
pub state: State,
|
||||
/// The accumulated feedback.
|
||||
feedback: Feedback,
|
||||
/// The finished page runs.
|
||||
runs: Vec<Pages>,
|
||||
/// The stack of logical groups (paragraphs and such).
|
||||
///
|
||||
/// Each entry contains metadata about the group and nodes that are at the
|
||||
/// same level as the group, which will return to `inner` once the group is
|
||||
/// finished.
|
||||
groups: Vec<(Box<dyn Any>, Vec<LayoutNode>)>,
|
||||
/// The nodes in the current innermost group
|
||||
/// (whose metadata is in `groups.last()`).
|
||||
inner: Vec<LayoutNode>,
|
||||
}
|
||||
|
||||
impl EvalContext {
|
||||
/// Create a new evaluation context with a base state.
|
||||
pub fn new(env: SharedEnv, state: State) -> Self {
|
||||
Self {
|
||||
env,
|
||||
state,
|
||||
groups: vec![],
|
||||
inner: vec![],
|
||||
runs: vec![],
|
||||
feedback: Feedback::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Finish evaluation and return the created document.
|
||||
pub fn finish(self) -> Pass<Document> {
|
||||
assert!(self.groups.is_empty(), "unfinished group");
|
||||
Pass::new(Document { runs: self.runs }, self.feedback)
|
||||
}
|
||||
|
||||
/// Add a diagnostic to the feedback.
|
||||
pub fn diag(&mut self, diag: Spanned<Diag>) {
|
||||
self.feedback.diags.push(diag);
|
||||
}
|
||||
|
||||
/// Add a decoration to the feedback.
|
||||
pub fn deco(&mut self, deco: Spanned<Deco>) {
|
||||
self.feedback.decos.push(deco);
|
||||
}
|
||||
|
||||
/// Push a layout node to the active group.
|
||||
///
|
||||
/// Spacing nodes will be handled according to their [`Softness`].
|
||||
pub fn push(&mut self, node: impl Into<LayoutNode>) {
|
||||
let node = node.into();
|
||||
|
||||
if let LayoutNode::Spacing(this) = node {
|
||||
if this.softness == Softness::Soft && self.inner.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(&LayoutNode::Spacing(other)) = self.inner.last() {
|
||||
if this.softness > other.softness {
|
||||
self.inner.pop();
|
||||
} else if this.softness == Softness::Soft {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.inner.push(node);
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
flow: self.state.flow,
|
||||
align: self.state.align,
|
||||
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(
|
||||
&mut self,
|
||||
keep_empty: impl FnOnce(Softness) -> bool,
|
||||
) -> Softness {
|
||||
self.end_par_group();
|
||||
let (group, children) = self.end_group::<PageGroup>();
|
||||
if !children.is_empty() || keep_empty(group.softness) {
|
||||
self.runs.push(Pages {
|
||||
size: group.size,
|
||||
child: LayoutNode::dynamic(Pad {
|
||||
padding: group.padding,
|
||||
child: LayoutNode::dynamic(Stack {
|
||||
flow: group.flow,
|
||||
align: group.align,
|
||||
expansion: Gen::uniform(Expansion::Fill),
|
||||
children,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
}
|
||||
group.softness
|
||||
}
|
||||
|
||||
/// Start a content group.
|
||||
///
|
||||
/// This also starts an inner paragraph.
|
||||
pub fn start_content_group(&mut self) {
|
||||
self.start_group(ContentGroup);
|
||||
self.start_par_group();
|
||||
}
|
||||
|
||||
/// End a content group and return the resulting nodes.
|
||||
///
|
||||
/// This also ends an inner paragraph.
|
||||
pub fn end_content_group(&mut self) -> Vec<LayoutNode> {
|
||||
self.end_par_group();
|
||||
self.end_group::<ContentGroup>().1
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
flow: self.state.flow,
|
||||
align: self.state.align,
|
||||
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() {
|
||||
// FIXME: This is a hack and should be superseded by something
|
||||
// better.
|
||||
let cross_expansion = Expansion::fill_if(self.groups.len() <= 1);
|
||||
self.push(Par {
|
||||
flow: group.flow,
|
||||
align: group.align,
|
||||
cross_expansion,
|
||||
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<LayoutNode>) {
|
||||
if let Some(&LayoutNode::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))
|
||||
}
|
||||
|
||||
/// Updates the flow directions if the resulting main and cross directions
|
||||
/// apply to different axes. Generates an appropriate error, otherwise.
|
||||
pub fn set_flow(&mut self, new: Gen<Option<Spanned<Dir>>>) {
|
||||
let flow = Gen::new(
|
||||
new.main.map(|s| s.v).unwrap_or(self.state.flow.main),
|
||||
new.cross.map(|s| s.v).unwrap_or(self.state.flow.cross),
|
||||
);
|
||||
|
||||
if flow.main.axis() != flow.cross.axis() {
|
||||
self.state.flow = flow;
|
||||
} else {
|
||||
for dir in new.main.iter().chain(new.cross.iter()) {
|
||||
self.diag(error!(dir.span, "aligned axis"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a forced line break.
|
||||
pub fn apply_linebreak(&mut self) {
|
||||
self.end_par_group();
|
||||
self.start_par_group();
|
||||
}
|
||||
|
||||
/// Apply a forced paragraph break.
|
||||
pub fn apply_parbreak(&mut self) {
|
||||
self.end_par_group();
|
||||
let em = self.state.font.font_size();
|
||||
self.push(Spacing {
|
||||
amount: self.state.par.par_spacing.resolve(em),
|
||||
softness: Softness::Soft,
|
||||
});
|
||||
self.start_par_group();
|
||||
}
|
||||
|
||||
/// Construct a text node from the given string based on the active text
|
||||
/// state.
|
||||
pub fn make_text_node(&self, text: String) -> Text {
|
||||
let mut variant = self.state.font.variant;
|
||||
|
||||
if self.state.font.strong {
|
||||
variant.weight = variant.weight.thicken(300);
|
||||
}
|
||||
|
||||
if self.state.font.emph {
|
||||
variant.style = match variant.style {
|
||||
FontStyle::Normal => FontStyle::Italic,
|
||||
FontStyle::Italic => FontStyle::Normal,
|
||||
FontStyle::Oblique => FontStyle::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text,
|
||||
align: self.state.align,
|
||||
dir: self.state.flow.cross,
|
||||
font_size: self.state.font.font_size(),
|
||||
families: Rc::clone(&self.state.font.families),
|
||||
variant,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A group for page runs.
|
||||
struct PageGroup {
|
||||
size: Size,
|
||||
padding: Sides<Linear>,
|
||||
flow: Flow,
|
||||
align: BoxAlign,
|
||||
softness: Softness,
|
||||
}
|
||||
|
||||
/// A group for generic content.
|
||||
struct ContentGroup;
|
||||
|
||||
/// A group for paragraphs.
|
||||
struct ParGroup {
|
||||
flow: Flow,
|
||||
align: BoxAlign,
|
||||
line_spacing: Length,
|
||||
}
|
||||
|
||||
/// Defines how an item interact 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,
|
||||
}
|
||||
|
||||
/// Evaluate an item.
|
||||
///
|
||||
/// _Note_: Evaluation is not necessarily pure, it may change the active state.
|
||||
@ -339,7 +56,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for &[Spanned<SynNode>] {
|
||||
impl Eval for &[Spanned<Node>] {
|
||||
type Output = ();
|
||||
|
||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||
@ -349,33 +66,33 @@ impl Eval for &[Spanned<SynNode>] {
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for Spanned<&SynNode> {
|
||||
impl Eval for Spanned<&Node> {
|
||||
type Output = ();
|
||||
|
||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||
match self.v {
|
||||
SynNode::Text(text) => {
|
||||
Node::Text(text) => {
|
||||
let node = ctx.make_text_node(text.clone());
|
||||
ctx.push(node);
|
||||
}
|
||||
|
||||
SynNode::Space => {
|
||||
Node::Space => {
|
||||
let em = ctx.state.font.font_size();
|
||||
ctx.push(Spacing {
|
||||
ctx.push(NodeSpacing {
|
||||
amount: ctx.state.par.word_spacing.resolve(em),
|
||||
softness: Softness::Soft,
|
||||
});
|
||||
}
|
||||
SynNode::Linebreak => ctx.apply_linebreak(),
|
||||
SynNode::Parbreak => ctx.apply_parbreak(),
|
||||
Node::Linebreak => ctx.apply_linebreak(),
|
||||
Node::Parbreak => ctx.apply_parbreak(),
|
||||
|
||||
SynNode::Strong => ctx.state.font.strong ^= true,
|
||||
SynNode::Emph => ctx.state.font.emph ^= true,
|
||||
Node::Strong => ctx.state.font.strong ^= true,
|
||||
Node::Emph => ctx.state.font.emph ^= true,
|
||||
|
||||
SynNode::Heading(heading) => heading.with_span(self.span).eval(ctx),
|
||||
SynNode::Raw(raw) => raw.with_span(self.span).eval(ctx),
|
||||
Node::Heading(heading) => heading.with_span(self.span).eval(ctx),
|
||||
Node::Raw(raw) => raw.with_span(self.span).eval(ctx),
|
||||
|
||||
SynNode::Expr(expr) => {
|
||||
Node::Expr(expr) => {
|
||||
let value = expr.with_span(self.span).eval(ctx);
|
||||
value.eval(ctx)
|
||||
}
|
||||
@ -413,15 +130,15 @@ impl Eval for Spanned<&NodeRaw> {
|
||||
|
||||
let mut children = vec![];
|
||||
for line in &self.v.lines {
|
||||
children.push(LayoutNode::Text(ctx.make_text_node(line.clone())));
|
||||
children.push(LayoutNode::Spacing(Spacing {
|
||||
children.push(layout::Node::Text(ctx.make_text_node(line.clone())));
|
||||
children.push(layout::Node::Spacing(NodeSpacing {
|
||||
amount: line_spacing,
|
||||
softness: Softness::Hard,
|
||||
}));
|
||||
}
|
||||
|
||||
ctx.push(Stack {
|
||||
flow: ctx.state.flow,
|
||||
ctx.push(NodeStack {
|
||||
dirs: ctx.state.dirs,
|
||||
align: ctx.state.align,
|
||||
expansion: Gen::uniform(Expansion::Fit),
|
||||
children,
|
||||
@ -436,10 +153,13 @@ impl Eval for Spanned<&Expr> {
|
||||
|
||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||
match self.v {
|
||||
Expr::Lit(lit) => lit.with_span(self.span).eval(ctx),
|
||||
Expr::Call(call) => call.with_span(self.span).eval(ctx),
|
||||
Expr::Unary(unary) => unary.with_span(self.span).eval(ctx),
|
||||
Expr::Binary(binary) => binary.with_span(self.span).eval(ctx),
|
||||
Expr::Lit(v) => v.with_span(self.span).eval(ctx),
|
||||
Expr::Call(v) => v.with_span(self.span).eval(ctx),
|
||||
Expr::Unary(v) => v.with_span(self.span).eval(ctx),
|
||||
Expr::Binary(v) => v.with_span(self.span).eval(ctx),
|
||||
Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)),
|
||||
Expr::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)),
|
||||
Expr::Content(v) => Value::Content(v.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -463,14 +183,11 @@ impl Eval for Spanned<&Lit> {
|
||||
Lit::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
|
||||
Lit::Color(v) => Value::Color(Color::Rgba(v)),
|
||||
Lit::Str(ref v) => Value::Str(v.clone()),
|
||||
Lit::Array(ref v) => Value::Array(v.with_span(self.span).eval(ctx)),
|
||||
Lit::Dict(ref v) => Value::Dict(v.with_span(self.span).eval(ctx)),
|
||||
Lit::Content(ref v) => Value::Content(v.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for Spanned<&Array> {
|
||||
impl Eval for Spanned<&ExprArray> {
|
||||
type Output = ValueArray;
|
||||
|
||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||
@ -478,7 +195,7 @@ impl Eval for Spanned<&Array> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for Spanned<&Dict> {
|
||||
impl Eval for Spanned<&ExprDict> {
|
||||
type Output = ValueDict;
|
||||
|
||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||
@ -489,36 +206,6 @@ impl Eval for Spanned<&Dict> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for Spanned<&ExprCall> {
|
||||
type Output = Value;
|
||||
|
||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||
let name = &self.v.name.v;
|
||||
let span = self.v.name.span;
|
||||
|
||||
if let Some(value) = ctx.state.scope.get(name) {
|
||||
if let Value::Func(func) = value {
|
||||
let func = func.clone();
|
||||
ctx.feedback.decos.push(Deco::Resolved.with_span(span));
|
||||
|
||||
let mut args = self.v.args.as_ref().eval(ctx);
|
||||
let returned = func(ctx, &mut args);
|
||||
args.finish(ctx);
|
||||
|
||||
return returned;
|
||||
} else {
|
||||
let ty = value.type_name();
|
||||
ctx.diag(error!(span, "a value of type {} is not callable", ty));
|
||||
}
|
||||
} else if !name.is_empty() {
|
||||
ctx.diag(error!(span, "unknown function"));
|
||||
}
|
||||
|
||||
ctx.feedback.decos.push(Deco::Unresolved.with_span(span));
|
||||
Value::Error
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for Spanned<&ExprUnary> {
|
||||
type Output = Value;
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
//! Mapping from identifiers to functions.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use super::Value;
|
||||
|
||||
/// A map from identifiers to functions.
|
||||
/// A map from identifiers to values.
|
||||
#[derive(Default, Clone, PartialEq)]
|
||||
pub struct Scope {
|
||||
values: HashMap<String, Value>,
|
||||
|
@ -1,46 +1,46 @@
|
||||
//! Evaluation state.
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight};
|
||||
|
||||
use super::Scope;
|
||||
use crate::geom::{Align, BoxAlign, Dir, Flow, Length, Linear, Relative, Sides, Size};
|
||||
use crate::geom::{
|
||||
Align, ChildAlign, Dir, LayoutDirs, Length, Linear, Relative, Sides, Size,
|
||||
};
|
||||
use crate::paper::{Paper, PaperClass, PAPER_A4};
|
||||
|
||||
/// The active evaluation state.
|
||||
/// The evaluation state.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct State {
|
||||
/// The scope that contains function definitions.
|
||||
/// The scope that contains variable definitions.
|
||||
pub scope: Scope,
|
||||
/// The page state.
|
||||
pub page: PageState,
|
||||
/// The paragraph state.
|
||||
pub par: ParState,
|
||||
/// The font state.
|
||||
pub font: FontState,
|
||||
/// The active layouting directions.
|
||||
pub flow: Flow,
|
||||
/// The active box alignments.
|
||||
pub align: BoxAlign,
|
||||
/// The current page state.
|
||||
pub page: StatePage,
|
||||
/// The current paragraph state.
|
||||
pub par: StatePar,
|
||||
/// The current font state.
|
||||
pub font: StateFont,
|
||||
/// The current directions.
|
||||
pub dirs: LayoutDirs,
|
||||
/// The current alignments.
|
||||
pub align: ChildAlign,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
scope: crate::library::_std(),
|
||||
page: PageState::default(),
|
||||
par: ParState::default(),
|
||||
font: FontState::default(),
|
||||
flow: Flow::new(Dir::TTB, Dir::LTR),
|
||||
align: BoxAlign::new(Align::Start, Align::Start),
|
||||
page: StatePage::default(),
|
||||
par: StatePar::default(),
|
||||
font: StateFont::default(),
|
||||
dirs: LayoutDirs::new(Dir::TTB, Dir::LTR),
|
||||
align: ChildAlign::new(Align::Start, Align::Start),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines page properties.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct PageState {
|
||||
pub struct StatePage {
|
||||
/// The class of this page.
|
||||
pub class: PaperClass,
|
||||
/// The width and height of the page.
|
||||
@ -50,7 +50,7 @@ pub struct PageState {
|
||||
pub margins: Sides<Option<Linear>>,
|
||||
}
|
||||
|
||||
impl PageState {
|
||||
impl StatePage {
|
||||
/// The default page style for the given paper.
|
||||
pub fn new(paper: Paper) -> Self {
|
||||
Self {
|
||||
@ -72,7 +72,7 @@ impl PageState {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PageState {
|
||||
impl Default for StatePage {
|
||||
fn default() -> Self {
|
||||
Self::new(PAPER_A4)
|
||||
}
|
||||
@ -80,7 +80,7 @@ impl Default for PageState {
|
||||
|
||||
/// Defines paragraph properties.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct ParState {
|
||||
pub struct StatePar {
|
||||
/// The spacing between words (dependent on scaled font size).
|
||||
pub word_spacing: Linear,
|
||||
/// The spacing between lines (dependent on scaled font size).
|
||||
@ -89,7 +89,7 @@ pub struct ParState {
|
||||
pub par_spacing: Linear,
|
||||
}
|
||||
|
||||
impl Default for ParState {
|
||||
impl Default for StatePar {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
word_spacing: Relative::new(0.25).into(),
|
||||
@ -101,7 +101,7 @@ impl Default for ParState {
|
||||
|
||||
/// Defines font properties.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FontState {
|
||||
pub struct StateFont {
|
||||
/// A tree of font family names and generic class names.
|
||||
pub families: Rc<FallbackTree>,
|
||||
/// The selected font variant.
|
||||
@ -118,14 +118,14 @@ pub struct FontState {
|
||||
pub emph: bool,
|
||||
}
|
||||
|
||||
impl FontState {
|
||||
impl StateFont {
|
||||
/// The absolute font size.
|
||||
pub fn font_size(&self) -> Length {
|
||||
self.scale.resolve(self.size)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FontState {
|
||||
impl Default for StateFont {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
families: Rc::new(default_font_families()),
|
||||
@ -150,8 +150,6 @@ fn default_font_families() -> FallbackTree {
|
||||
"serif" => ["source serif pro", "noto serif"],
|
||||
"sans-serif" => ["source sans pro", "noto sans"],
|
||||
"monospace" => ["source code pro", "noto sans mono"],
|
||||
"emoji" => ["segoe ui emoji", "noto emoji"],
|
||||
"math" => ["latin modern math", "serif"],
|
||||
},
|
||||
base: [
|
||||
"source sans pro",
|
||||
|
@ -1,5 +1,3 @@
|
||||
//! Computational values.
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
@ -9,7 +7,7 @@ use std::rc::Rc;
|
||||
use super::{Args, Eval, EvalContext};
|
||||
use crate::color::Color;
|
||||
use crate::geom::{Length, Linear, Relative};
|
||||
use crate::syntax::{Spanned, SynTree, WithSpan};
|
||||
use crate::syntax::{Spanned, Tree, WithSpan};
|
||||
|
||||
/// A computational value.
|
||||
#[derive(Clone, PartialEq)]
|
||||
@ -47,6 +45,14 @@ pub enum Value {
|
||||
}
|
||||
|
||||
impl Value {
|
||||
/// Create a new dynamic value.
|
||||
pub fn any<T>(any: T) -> Self
|
||||
where
|
||||
T: Type + Debug + Clone + PartialEq + 'static,
|
||||
{
|
||||
Self::Any(ValueAny::new(any))
|
||||
}
|
||||
|
||||
/// Try to cast the value into a specific type.
|
||||
pub fn cast<T>(self) -> CastResult<T, Self>
|
||||
where
|
||||
@ -130,7 +136,7 @@ pub type ValueArray = Vec<Value>;
|
||||
pub type ValueDict = HashMap<String, Value>;
|
||||
|
||||
/// A content value: `{*Hi* there}`.
|
||||
pub type ValueContent = SynTree;
|
||||
pub type ValueContent = Tree;
|
||||
|
||||
/// A wrapper around a reference-counted executable function.
|
||||
#[derive(Clone)]
|
||||
@ -197,7 +203,7 @@ impl ValueAny {
|
||||
self.0.as_any().downcast_ref()
|
||||
}
|
||||
|
||||
/// The name of the stored object's type.
|
||||
/// The name of the stored value's type.
|
||||
pub fn type_name(&self) -> &'static str {
|
||||
self.0.dyn_type_name()
|
||||
}
|
||||
@ -289,7 +295,7 @@ pub enum CastResult<T, V> {
|
||||
}
|
||||
|
||||
impl<T, V> CastResult<T, V> {
|
||||
/// Access the conversion resulting, discarding a possibly existing warning.
|
||||
/// Access the conversion result, discarding a possibly existing warning.
|
||||
pub fn ok(self) -> Option<T> {
|
||||
match self {
|
||||
CastResult::Ok(t) | CastResult::Warn(t, _) => Some(t),
|
||||
@ -399,7 +405,7 @@ impl From<ValueAny> for Value {
|
||||
}
|
||||
}
|
||||
|
||||
/// Make a type usable with [`ValueAny`].
|
||||
/// Make a type usable as a [`Value`].
|
||||
///
|
||||
/// Given a type `T`, this implements the following traits:
|
||||
/// - [`Type`] for `T`,
|
||||
@ -419,7 +425,7 @@ macro_rules! impl_type {
|
||||
|
||||
impl From<$type> for $crate::eval::Value {
|
||||
fn from(any: $type) -> Self {
|
||||
$crate::eval::Value::Any($crate::eval::ValueAny::new(any))
|
||||
$crate::eval::Value::any(any)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,22 +15,22 @@ use ttf_parser::{name_id, GlyphId};
|
||||
|
||||
use crate::env::{Env, ImageResource, ResourceId};
|
||||
use crate::geom::Length;
|
||||
use crate::layout::{BoxLayout, LayoutElement};
|
||||
use crate::layout::{Element, Frame};
|
||||
|
||||
/// Export a list of layouts into a _PDF_ document.
|
||||
/// Export a collection of frames into a _PDF_ document.
|
||||
///
|
||||
/// This creates one page per layout. Additionally to the layouts, you need to
|
||||
/// pass in the font loader used for typesetting such that the fonts can be
|
||||
/// included in the _PDF_.
|
||||
/// This creates one page per frame. In addition to the frames, you need to pass
|
||||
/// in the environment used for typesetting such that things like fonts and
|
||||
/// images can be included in the _PDF_.
|
||||
///
|
||||
/// Returns the raw bytes making up the _PDF_ document.
|
||||
pub fn export(layouts: &[BoxLayout], env: &Env) -> Vec<u8> {
|
||||
PdfExporter::new(layouts, env).write()
|
||||
pub fn export(frames: &[Frame], env: &Env) -> Vec<u8> {
|
||||
PdfExporter::new(frames, env).write()
|
||||
}
|
||||
|
||||
struct PdfExporter<'a> {
|
||||
writer: PdfWriter,
|
||||
layouts: &'a [BoxLayout],
|
||||
frames: &'a [Frame],
|
||||
env: &'a Env,
|
||||
refs: Refs,
|
||||
fonts: Remapper<FaceId>,
|
||||
@ -38,7 +38,7 @@ struct PdfExporter<'a> {
|
||||
}
|
||||
|
||||
impl<'a> PdfExporter<'a> {
|
||||
fn new(layouts: &'a [BoxLayout], env: &'a Env) -> Self {
|
||||
fn new(frames: &'a [Frame], env: &'a Env) -> Self {
|
||||
let mut writer = PdfWriter::new(1, 7);
|
||||
writer.set_indent(2);
|
||||
|
||||
@ -46,11 +46,11 @@ impl<'a> PdfExporter<'a> {
|
||||
let mut images = Remapper::new();
|
||||
let mut alpha_masks = 0;
|
||||
|
||||
for layout in layouts {
|
||||
for (_, element) in &layout.elements {
|
||||
for frame in frames {
|
||||
for (_, element) in &frame.elements {
|
||||
match element {
|
||||
LayoutElement::Text(shaped) => fonts.insert(shaped.face),
|
||||
LayoutElement::Image(image) => {
|
||||
Element::Text(shaped) => fonts.insert(shaped.face),
|
||||
Element::Image(image) => {
|
||||
let img = env.resources.loaded::<ImageResource>(image.res);
|
||||
if img.buf.color().has_alpha() {
|
||||
alpha_masks += 1;
|
||||
@ -61,16 +61,9 @@ impl<'a> PdfExporter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let refs = Refs::new(layouts.len(), fonts.len(), images.len(), alpha_masks);
|
||||
let refs = Refs::new(frames.len(), fonts.len(), images.len(), alpha_masks);
|
||||
|
||||
Self {
|
||||
writer,
|
||||
layouts,
|
||||
env,
|
||||
refs,
|
||||
fonts,
|
||||
images,
|
||||
}
|
||||
Self { writer, frames, env, refs, fonts, images }
|
||||
}
|
||||
|
||||
fn write(mut self) -> Vec<u8> {
|
||||
@ -110,7 +103,7 @@ impl<'a> PdfExporter<'a> {
|
||||
|
||||
// The page objects (non-root nodes in the page tree).
|
||||
for ((page_id, content_id), page) in
|
||||
self.refs.pages().zip(self.refs.contents()).zip(self.layouts)
|
||||
self.refs.pages().zip(self.refs.contents()).zip(self.frames)
|
||||
{
|
||||
self.writer
|
||||
.page(page_id)
|
||||
@ -126,12 +119,12 @@ impl<'a> PdfExporter<'a> {
|
||||
}
|
||||
|
||||
fn write_pages(&mut self) {
|
||||
for (id, page) in self.refs.contents().zip(self.layouts) {
|
||||
for (id, page) in self.refs.contents().zip(self.frames) {
|
||||
self.write_page(id, &page);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_page(&mut self, id: Ref, page: &'a BoxLayout) {
|
||||
fn write_page(&mut self, id: Ref, page: &'a Frame) {
|
||||
let mut content = Content::new();
|
||||
|
||||
// We only write font switching actions when the used face changes. To
|
||||
@ -141,7 +134,7 @@ impl<'a> PdfExporter<'a> {
|
||||
|
||||
let mut text = content.text();
|
||||
for (pos, element) in &page.elements {
|
||||
if let LayoutElement::Text(shaped) = element {
|
||||
if let Element::Text(shaped) = element {
|
||||
// Check if we need to issue a font switching action.
|
||||
if shaped.face != face || shaped.font_size != size {
|
||||
face = shaped.face;
|
||||
@ -161,7 +154,7 @@ impl<'a> PdfExporter<'a> {
|
||||
drop(text);
|
||||
|
||||
for (pos, element) in &page.elements {
|
||||
if let LayoutElement::Image(image) = element {
|
||||
if let Element::Image(image) = element {
|
||||
let name = format!("Im{}", self.images.map(image.res));
|
||||
let size = image.size;
|
||||
let x = pos.x.to_pt() as f32;
|
||||
@ -359,12 +352,12 @@ struct FontRefs {
|
||||
impl Refs {
|
||||
const OBJECTS_PER_FONT: usize = 5;
|
||||
|
||||
fn new(layouts: usize, fonts: usize, images: usize, alpha_masks: usize) -> Self {
|
||||
fn new(frames: usize, fonts: usize, images: usize, alpha_masks: usize) -> Self {
|
||||
let catalog = 1;
|
||||
let page_tree = catalog + 1;
|
||||
let pages_start = page_tree + 1;
|
||||
let contents_start = pages_start + layouts as i32;
|
||||
let fonts_start = contents_start + layouts as i32;
|
||||
let contents_start = pages_start + frames as i32;
|
||||
let fonts_start = contents_start + frames as i32;
|
||||
let images_start = fonts_start + (Self::OBJECTS_PER_FONT * fonts) as i32;
|
||||
let alpha_masks_start = images_start + images as i32;
|
||||
let end = alpha_masks_start + alpha_masks as i32;
|
||||
|
15
src/font.rs
15
src/font.rs
@ -3,19 +3,16 @@
|
||||
use fontdock::{ContainsChar, FaceFromVec, FontSource};
|
||||
use ttf_parser::Face;
|
||||
|
||||
/// A font loader backed by a dynamic source.
|
||||
pub type FontLoader = fontdock::FontLoader<Box<DynSource>>;
|
||||
|
||||
/// The dynamic font source.
|
||||
pub type DynSource = dyn FontSource<Face = OwnedFace>;
|
||||
/// A font loader that is backed by a dynamic source.
|
||||
pub type FontLoader = fontdock::FontLoader<Box<dyn FontSource<Face = FaceBuf>>>;
|
||||
|
||||
/// An owned font face.
|
||||
pub struct OwnedFace {
|
||||
pub struct FaceBuf {
|
||||
data: Box<[u8]>,
|
||||
face: Face<'static>,
|
||||
}
|
||||
|
||||
impl OwnedFace {
|
||||
impl FaceBuf {
|
||||
/// Get a reference to the underlying face.
|
||||
pub fn get(&self) -> &Face<'_> {
|
||||
// We can't implement Deref because that would leak the internal 'static
|
||||
@ -29,7 +26,7 @@ impl OwnedFace {
|
||||
}
|
||||
}
|
||||
|
||||
impl FaceFromVec for OwnedFace {
|
||||
impl FaceFromVec for FaceBuf {
|
||||
fn from_vec(vec: Vec<u8>, i: u32) -> Option<Self> {
|
||||
let data = vec.into_boxed_slice();
|
||||
|
||||
@ -45,7 +42,7 @@ impl FaceFromVec for OwnedFace {
|
||||
}
|
||||
}
|
||||
|
||||
impl ContainsChar for OwnedFace {
|
||||
impl ContainsChar for FaceBuf {
|
||||
fn contains_char(&self, c: char) -> bool {
|
||||
self.get().glyph_index(c).is_some()
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
|
||||
/// The alignment of a box in a container.
|
||||
pub type BoxAlign = Gen<Align>;
|
||||
/// The alignment of a child in a container.
|
||||
pub type ChildAlign = Gen<Align>;
|
||||
|
||||
/// Where to align something along a directed axis.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
|
@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
|
||||
/// The directions along which content flows in a container.
|
||||
pub type Flow = Gen<Dir>;
|
||||
/// The directions along which nodes are layouted.
|
||||
pub type LayoutDirs = Gen<Dir>;
|
||||
|
||||
/// The four directions into which content can be laid out.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
|
@ -50,8 +50,8 @@ impl<T> Get<GenAxis> for Gen<T> {
|
||||
impl<T> Switch for Gen<T> {
|
||||
type Other = Spec<T>;
|
||||
|
||||
fn switch(self, flow: Flow) -> Self::Other {
|
||||
match flow.main.axis() {
|
||||
fn switch(self, dirs: LayoutDirs) -> Self::Other {
|
||||
match dirs.main.axis() {
|
||||
SpecAxis::Horizontal => Spec::new(self.main, self.cross),
|
||||
SpecAxis::Vertical => Spec::new(self.cross, self.main),
|
||||
}
|
||||
@ -80,10 +80,10 @@ impl GenAxis {
|
||||
impl Switch for GenAxis {
|
||||
type Other = SpecAxis;
|
||||
|
||||
fn switch(self, flow: Flow) -> Self::Other {
|
||||
fn switch(self, dirs: LayoutDirs) -> Self::Other {
|
||||
match self {
|
||||
Self::Main => flow.main.axis(),
|
||||
Self::Cross => flow.cross.axis(),
|
||||
Self::Main => dirs.main.axis(),
|
||||
Self::Cross => dirs.cross.axis(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,5 +50,5 @@ pub trait Switch {
|
||||
|
||||
/// The other version of this type based on the current layouting
|
||||
/// directions.
|
||||
fn switch(self, flow: Flow) -> Self::Other;
|
||||
fn switch(self, dirs: LayoutDirs) -> Self::Other;
|
||||
}
|
||||
|
@ -45,8 +45,8 @@ impl Get<SpecAxis> for Point {
|
||||
impl Switch for Point {
|
||||
type Other = Gen<Length>;
|
||||
|
||||
fn switch(self, flow: Flow) -> Self::Other {
|
||||
match flow.main.axis() {
|
||||
fn switch(self, dirs: LayoutDirs) -> Self::Other {
|
||||
match dirs.main.axis() {
|
||||
SpecAxis::Horizontal => Gen::new(self.x, self.y),
|
||||
SpecAxis::Vertical => Gen::new(self.y, self.x),
|
||||
}
|
||||
|
@ -53,8 +53,8 @@ impl Get<SpecAxis> for Size {
|
||||
impl Switch for Size {
|
||||
type Other = Gen<Length>;
|
||||
|
||||
fn switch(self, flow: Flow) -> Self::Other {
|
||||
match flow.main.axis() {
|
||||
fn switch(self, dirs: LayoutDirs) -> Self::Other {
|
||||
match dirs.main.axis() {
|
||||
SpecAxis::Horizontal => Gen::new(self.width, self.height),
|
||||
SpecAxis::Vertical => Gen::new(self.height, self.width),
|
||||
}
|
||||
|
@ -66,8 +66,8 @@ impl<T> Get<SpecAxis> for Spec<T> {
|
||||
impl<T> Switch for Spec<T> {
|
||||
type Other = Gen<T>;
|
||||
|
||||
fn switch(self, flow: Flow) -> Self::Other {
|
||||
match flow.main.axis() {
|
||||
fn switch(self, dirs: LayoutDirs) -> Self::Other {
|
||||
match dirs.main.axis() {
|
||||
SpecAxis::Horizontal => Gen::new(self.horizontal, self.vertical),
|
||||
SpecAxis::Vertical => Gen::new(self.vertical, self.horizontal),
|
||||
}
|
||||
@ -96,11 +96,11 @@ impl SpecAxis {
|
||||
impl Switch for SpecAxis {
|
||||
type Other = GenAxis;
|
||||
|
||||
fn switch(self, flow: Flow) -> Self::Other {
|
||||
if self == flow.main.axis() {
|
||||
fn switch(self, dirs: LayoutDirs) -> Self::Other {
|
||||
if self == dirs.main.axis() {
|
||||
GenAxis::Main
|
||||
} else {
|
||||
debug_assert_eq!(self, flow.cross.axis());
|
||||
debug_assert_eq!(self, dirs.cross.axis());
|
||||
GenAxis::Cross
|
||||
}
|
||||
}
|
||||
|
@ -3,16 +3,16 @@ use crate::geom::Linear;
|
||||
|
||||
/// A node that can fix its child's width and height.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Fixed {
|
||||
pub struct NodeFixed {
|
||||
/// The fixed width, if any.
|
||||
pub width: Option<Linear>,
|
||||
/// The fixed height, if any.
|
||||
pub height: Option<Linear>,
|
||||
/// The child node whose size to fix.
|
||||
pub child: LayoutNode,
|
||||
pub child: Node,
|
||||
}
|
||||
|
||||
impl Layout for Fixed {
|
||||
impl Layout for NodeFixed {
|
||||
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
|
||||
let Area { rem, full } = areas.current;
|
||||
let size = Size::new(
|
||||
@ -25,8 +25,8 @@ impl Layout for Fixed {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Fixed> for LayoutNode {
|
||||
fn from(fixed: Fixed) -> Self {
|
||||
Self::dynamic(fixed)
|
||||
impl From<NodeFixed> for Node {
|
||||
fn from(fixed: NodeFixed) -> Self {
|
||||
Self::any(fixed)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! Layouting of documents.
|
||||
//! Layouting.
|
||||
|
||||
mod fixed;
|
||||
mod node;
|
||||
@ -20,10 +20,48 @@ pub use spacing::*;
|
||||
pub use stack::*;
|
||||
pub use text::*;
|
||||
|
||||
/// Layout a document and return the produced layouts.
|
||||
pub fn layout(document: &Document, env: SharedEnv) -> Vec<BoxLayout> {
|
||||
let mut ctx = LayoutContext { env };
|
||||
document.layout(&mut ctx)
|
||||
/// Layout a tree into a collection of frames.
|
||||
pub fn layout(tree: &Tree, env: SharedEnv) -> Vec<Frame> {
|
||||
tree.layout(&mut LayoutContext { env })
|
||||
}
|
||||
|
||||
/// A tree of layout nodes.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Tree {
|
||||
/// Runs of pages with the same properties.
|
||||
pub runs: Vec<NodePages>,
|
||||
}
|
||||
|
||||
impl Tree {
|
||||
/// Layout the tree into a collection of frames.
|
||||
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Frame> {
|
||||
self.runs.iter().flat_map(|run| run.layout(ctx)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// A run of pages that all have the same properties.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct NodePages {
|
||||
/// The size of each page.
|
||||
pub size: Size,
|
||||
/// The layout node that produces the actual pages (typically a
|
||||
/// [`NodeStack`]).
|
||||
pub child: Node,
|
||||
}
|
||||
|
||||
impl NodePages {
|
||||
/// Layout the page run.
|
||||
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Frame> {
|
||||
let areas = Areas::repeat(self.size);
|
||||
let layouted = self.child.layout(ctx, &areas);
|
||||
layouted.frames()
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout a node.
|
||||
pub trait Layout {
|
||||
/// Layout the node into the given areas.
|
||||
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted;
|
||||
}
|
||||
|
||||
/// The context for layouting.
|
||||
@ -33,22 +71,30 @@ pub struct LayoutContext {
|
||||
pub env: SharedEnv,
|
||||
}
|
||||
|
||||
/// Layout a node.
|
||||
pub trait Layout {
|
||||
/// Layout the node into the given areas.
|
||||
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted;
|
||||
/// An area into which content can be laid out.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Area {
|
||||
/// The remaining size of this area.
|
||||
pub rem: Size,
|
||||
/// The full size this area once had (used for relative sizing).
|
||||
pub full: Size,
|
||||
}
|
||||
|
||||
/// A sequence of areas to layout into.
|
||||
impl Area {
|
||||
/// Create a new area.
|
||||
pub fn new(size: Size) -> Self {
|
||||
Self { rem: size, full: size }
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of areas to layout into.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Areas {
|
||||
/// The current area.
|
||||
pub current: Area,
|
||||
/// The backlog of followup areas.
|
||||
///
|
||||
/// _Note_: This works stack-like and not queue-like!
|
||||
/// A stack of followup areas (the next area is the last element).
|
||||
pub backlog: Vec<Size>,
|
||||
/// The last area that is repeated when the backlog is empty.
|
||||
/// The final area that is repeated when the backlog is empty.
|
||||
pub last: Option<Size>,
|
||||
}
|
||||
|
||||
@ -86,23 +132,30 @@ impl Areas {
|
||||
}
|
||||
}
|
||||
|
||||
/// The area into which content can be laid out.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Area {
|
||||
/// The remaining size of this area.
|
||||
pub rem: Size,
|
||||
/// The full size this area once had (used for relative sizing).
|
||||
pub full: Size,
|
||||
/// The result of layouting a node.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Layouted {
|
||||
/// Spacing that should be added to the parent.
|
||||
Spacing(Length),
|
||||
/// A layout that should be added to and aligned in the parent.
|
||||
Frame(Frame, ChildAlign),
|
||||
/// Multiple layouts.
|
||||
Frames(Vec<Frame>, ChildAlign),
|
||||
}
|
||||
|
||||
impl Area {
|
||||
/// Create a new area.
|
||||
pub fn new(size: Size) -> Self {
|
||||
Self { rem: size, full: size }
|
||||
impl Layouted {
|
||||
/// Return all frames contained in this variant (zero, one or arbitrarily
|
||||
/// many).
|
||||
pub fn frames(self) -> Vec<Frame> {
|
||||
match self {
|
||||
Self::Spacing(_) => vec![],
|
||||
Self::Frame(frame, _) => vec![frame],
|
||||
Self::Frames(frames, _) => frames,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// How to determine a container's size along an axis.
|
||||
/// Whether to expand or shrink a node along an axis.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Expansion {
|
||||
/// Fit the content.
|
||||
@ -111,111 +164,49 @@ pub enum Expansion {
|
||||
Fill,
|
||||
}
|
||||
|
||||
impl Expansion {
|
||||
/// Returns `Fill` if the condition is true and `Fit` otherwise.
|
||||
pub fn fill_if(condition: bool) -> Self {
|
||||
if condition { Self::Fill } else { Self::Fit }
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of [layouting](Layout::layout) a node.
|
||||
/// A finished layout with elements at fixed positions.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Layouted {
|
||||
/// Spacing that should be added to the parent.
|
||||
Spacing(Length),
|
||||
/// A layout that should be added to and aligned in the parent.
|
||||
Layout(BoxLayout, BoxAlign),
|
||||
/// Multiple layouts.
|
||||
Layouts(Vec<BoxLayout>, BoxAlign),
|
||||
}
|
||||
|
||||
impl Layouted {
|
||||
/// Return all layouts contained in this variant (zero, one or arbitrarily
|
||||
/// many).
|
||||
pub fn into_layouts(self) -> Vec<BoxLayout> {
|
||||
match self {
|
||||
Self::Spacing(_) => vec![],
|
||||
Self::Layout(layout, _) => vec![layout],
|
||||
Self::Layouts(layouts, _) => layouts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A finished box with content at fixed positions.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct BoxLayout {
|
||||
/// The size of the box.
|
||||
pub struct Frame {
|
||||
/// The size of the frame.
|
||||
pub size: Size,
|
||||
/// The elements composing this layout.
|
||||
pub elements: Vec<(Point, LayoutElement)>,
|
||||
pub elements: Vec<(Point, Element)>,
|
||||
}
|
||||
|
||||
impl BoxLayout {
|
||||
/// Create a new empty collection.
|
||||
impl Frame {
|
||||
/// Create a new, empty frame.
|
||||
pub fn new(size: Size) -> Self {
|
||||
Self { size, elements: vec![] }
|
||||
}
|
||||
|
||||
/// Add an element at a position.
|
||||
pub fn push(&mut self, pos: Point, element: LayoutElement) {
|
||||
pub fn push(&mut self, pos: Point, element: Element) {
|
||||
self.elements.push((pos, element));
|
||||
}
|
||||
|
||||
/// Add all elements of another collection, placing them relative to the
|
||||
/// given position.
|
||||
pub fn push_layout(&mut self, pos: Point, more: Self) {
|
||||
for (subpos, element) in more.elements {
|
||||
/// Add all elements of another frame, placing them relative to the given
|
||||
/// position.
|
||||
pub fn push_frame(&mut self, pos: Point, subframe: Self) {
|
||||
for (subpos, element) in subframe.elements {
|
||||
self.push(pos + subpos, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A layout element, the basic building block layouts are composed of.
|
||||
/// The building block frames are composed of.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum LayoutElement {
|
||||
pub enum Element {
|
||||
/// Shaped text.
|
||||
Text(Shaped),
|
||||
/// An image.
|
||||
Image(ImageElement),
|
||||
Image(Image),
|
||||
}
|
||||
|
||||
/// An image.
|
||||
/// An image element.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ImageElement {
|
||||
/// The image.
|
||||
pub struct Image {
|
||||
/// The image resource.
|
||||
pub res: ResourceId,
|
||||
/// The document size of the image.
|
||||
/// The size of the image in the document.
|
||||
pub size: Size,
|
||||
}
|
||||
|
||||
/// The top-level layout node.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Document {
|
||||
/// The runs of pages with same properties.
|
||||
pub runs: Vec<Pages>,
|
||||
}
|
||||
|
||||
impl Document {
|
||||
/// Layout the document.
|
||||
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> {
|
||||
self.runs.iter().flat_map(|run| run.layout(ctx)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// A variable-length run of pages that all have the same properties.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Pages {
|
||||
/// The size of each page.
|
||||
pub size: Size,
|
||||
/// The layout node that produces the actual pages (typically a [`Stack`]).
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
impl Pages {
|
||||
/// Layout the page run.
|
||||
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> {
|
||||
let areas = Areas::repeat(self.size);
|
||||
let layouted = self.child.layout(ctx, &areas);
|
||||
layouted.into_layouts()
|
||||
}
|
||||
}
|
||||
|
@ -1,91 +1,89 @@
|
||||
//! Layout nodes.
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A self-contained, styled layout node.
|
||||
/// A self-contained layout node.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum LayoutNode {
|
||||
/// A spacing node.
|
||||
Spacing(Spacing),
|
||||
pub enum Node {
|
||||
/// A text node.
|
||||
Text(Text),
|
||||
/// A dynamic that can implement custom layouting behaviour.
|
||||
Dyn(Dynamic),
|
||||
Text(NodeText),
|
||||
/// A spacing node.
|
||||
Spacing(NodeSpacing),
|
||||
/// A dynamic node that can implement custom layouting behaviour.
|
||||
Any(NodeAny),
|
||||
}
|
||||
|
||||
impl LayoutNode {
|
||||
impl Node {
|
||||
/// Create a new dynamic node.
|
||||
pub fn dynamic<T>(inner: T) -> Self
|
||||
pub fn any<T>(any: T) -> Self
|
||||
where
|
||||
T: Layout + Debug + Clone + PartialEq + 'static,
|
||||
{
|
||||
Self::Dyn(Dynamic::new(inner))
|
||||
Self::Any(NodeAny::new(any))
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for LayoutNode {
|
||||
impl Layout for Node {
|
||||
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
|
||||
match self {
|
||||
Self::Spacing(spacing) => spacing.layout(ctx, areas),
|
||||
Self::Text(text) => text.layout(ctx, areas),
|
||||
Self::Dyn(dynamic) => dynamic.layout(ctx, areas),
|
||||
Self::Any(any) => any.layout(ctx, areas),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for LayoutNode {
|
||||
impl Debug for Node {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Spacing(spacing) => spacing.fmt(f),
|
||||
Self::Text(text) => text.fmt(f),
|
||||
Self::Dyn(dynamic) => dynamic.fmt(f),
|
||||
Self::Any(any) => any.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a dynamic layouting node.
|
||||
pub struct Dynamic(Box<dyn Bounds>);
|
||||
pub struct NodeAny(Box<dyn Bounds>);
|
||||
|
||||
impl Dynamic {
|
||||
impl NodeAny {
|
||||
/// Create a new instance from any node that satisifies the required bounds.
|
||||
pub fn new<T>(inner: T) -> Self
|
||||
pub fn new<T>(any: T) -> Self
|
||||
where
|
||||
T: Layout + Debug + Clone + PartialEq + 'static,
|
||||
{
|
||||
Self(Box::new(inner))
|
||||
Self(Box::new(any))
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for Dynamic {
|
||||
impl Layout for NodeAny {
|
||||
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
|
||||
self.0.layout(ctx, areas)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Dynamic {
|
||||
impl Clone for NodeAny {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.dyn_clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Dynamic {
|
||||
impl PartialEq for NodeAny {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.dyn_eq(other.0.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Dynamic {
|
||||
impl Debug for NodeAny {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Dynamic> for LayoutNode {
|
||||
fn from(dynamic: Dynamic) -> Self {
|
||||
Self::Dyn(dynamic)
|
||||
impl From<NodeAny> for Node {
|
||||
fn from(dynamic: NodeAny) -> Self {
|
||||
Self::Any(dynamic)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,26 +1,26 @@
|
||||
use super::*;
|
||||
use crate::geom::Linear;
|
||||
|
||||
/// A node that pads its child at the sides.
|
||||
/// A node that adds padding to its child.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Pad {
|
||||
pub struct NodePad {
|
||||
/// The amount of padding.
|
||||
pub padding: Sides<Linear>,
|
||||
/// The child node whose sides to pad.
|
||||
pub child: LayoutNode,
|
||||
pub child: Node,
|
||||
}
|
||||
|
||||
impl Layout for Pad {
|
||||
impl Layout for NodePad {
|
||||
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
|
||||
let areas = shrink_areas(areas, self.padding);
|
||||
let areas = shrink(areas, self.padding);
|
||||
|
||||
let mut layouted = self.child.layout(ctx, &areas);
|
||||
match &mut layouted {
|
||||
Layouted::Spacing(_) => {}
|
||||
Layouted::Layout(layout, _) => pad_layout(layout, self.padding),
|
||||
Layouted::Layouts(layouts, _) => {
|
||||
for layout in layouts {
|
||||
pad_layout(layout, self.padding);
|
||||
Layouted::Frame(frame, _) => pad(frame, self.padding),
|
||||
Layouted::Frames(frames, _) => {
|
||||
for frame in frames {
|
||||
pad(frame, self.padding);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -29,14 +29,14 @@ impl Layout for Pad {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pad> for LayoutNode {
|
||||
fn from(pad: Pad) -> Self {
|
||||
Self::dynamic(pad)
|
||||
impl From<NodePad> for Node {
|
||||
fn from(pad: NodePad) -> Self {
|
||||
Self::any(pad)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shrink all areas by the padding.
|
||||
fn shrink_areas(areas: &Areas, padding: Sides<Linear>) -> Areas {
|
||||
fn shrink(areas: &Areas, padding: Sides<Linear>) -> Areas {
|
||||
let shrink = |size| size - padding.resolve(size).size();
|
||||
Areas {
|
||||
current: Area {
|
||||
@ -49,12 +49,12 @@ fn shrink_areas(areas: &Areas, padding: Sides<Linear>) -> Areas {
|
||||
}
|
||||
|
||||
/// Enlarge the box and move all elements inwards.
|
||||
fn pad_layout(layout: &mut BoxLayout, padding: Sides<Linear>) {
|
||||
let padding = padding.resolve(layout.size);
|
||||
fn pad(frame: &mut Frame, padding: Sides<Linear>) {
|
||||
let padding = padding.resolve(frame.size);
|
||||
let origin = Point::new(padding.left, padding.top);
|
||||
|
||||
layout.size += padding.size();
|
||||
for (point, _) in &mut layout.elements {
|
||||
frame.size += padding.size();
|
||||
for (point, _) in &mut frame.elements {
|
||||
*point += origin;
|
||||
}
|
||||
}
|
||||
|
@ -2,69 +2,67 @@ use super::*;
|
||||
|
||||
/// A node that arranges its children into a paragraph.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Par {
|
||||
pub struct NodePar {
|
||||
/// The `main` and `cross` directions of this paragraph.
|
||||
///
|
||||
/// The children are placed in lines along the `cross` direction. The lines
|
||||
/// are stacked along the `main` direction.
|
||||
pub flow: Flow,
|
||||
pub dirs: LayoutDirs,
|
||||
/// Whether to expand the cross axis to fill the area or to fit the content.
|
||||
pub cross_expansion: Expansion,
|
||||
/// The spacing to insert after each line.
|
||||
pub line_spacing: Length,
|
||||
/// The nodes to be arranged in a paragraph.
|
||||
pub children: Vec<LayoutNode>,
|
||||
pub children: Vec<Node>,
|
||||
/// How to align this paragraph in _its_ parent.
|
||||
pub align: BoxAlign,
|
||||
pub align: ChildAlign,
|
||||
}
|
||||
|
||||
impl Layout for Par {
|
||||
impl Layout for NodePar {
|
||||
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
|
||||
let mut layouter = ParLayouter::new(self, areas.clone());
|
||||
for child in &self.children {
|
||||
match child.layout(ctx, &layouter.areas) {
|
||||
Layouted::Spacing(spacing) => layouter.push_spacing(spacing),
|
||||
Layouted::Layout(layout, align) => {
|
||||
layouter.push_layout(layout, align.cross)
|
||||
}
|
||||
Layouted::Layouts(layouts, align) => {
|
||||
for layout in layouts {
|
||||
layouter.push_layout(layout, align.cross);
|
||||
Layouted::Frame(frame, align) => layouter.push_frame(frame, align.cross),
|
||||
Layouted::Frames(frames, align) => {
|
||||
for frame in frames {
|
||||
layouter.push_frame(frame, align.cross);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Layouted::Layouts(layouter.finish(), self.align)
|
||||
Layouted::Frames(layouter.finish(), self.align)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Par> for LayoutNode {
|
||||
fn from(par: Par) -> Self {
|
||||
Self::dynamic(par)
|
||||
impl From<NodePar> for Node {
|
||||
fn from(par: NodePar) -> Self {
|
||||
Self::any(par)
|
||||
}
|
||||
}
|
||||
|
||||
struct ParLayouter<'a> {
|
||||
par: &'a Par,
|
||||
par: &'a NodePar,
|
||||
main: SpecAxis,
|
||||
cross: SpecAxis,
|
||||
flow: Flow,
|
||||
dirs: LayoutDirs,
|
||||
areas: Areas,
|
||||
finished: Vec<BoxLayout>,
|
||||
lines: Vec<(Length, BoxLayout, Align)>,
|
||||
finished: Vec<Frame>,
|
||||
lines: Vec<(Length, Frame, Align)>,
|
||||
lines_size: Gen<Length>,
|
||||
run: Vec<(Length, BoxLayout, Align)>,
|
||||
run: Vec<(Length, Frame, Align)>,
|
||||
run_size: Gen<Length>,
|
||||
run_ruler: Align,
|
||||
}
|
||||
|
||||
impl<'a> ParLayouter<'a> {
|
||||
fn new(par: &'a Par, areas: Areas) -> Self {
|
||||
fn new(par: &'a NodePar, areas: Areas) -> Self {
|
||||
Self {
|
||||
par,
|
||||
main: par.flow.main.axis(),
|
||||
cross: par.flow.cross.axis(),
|
||||
flow: par.flow,
|
||||
main: par.dirs.main.axis(),
|
||||
cross: par.dirs.cross.axis(),
|
||||
dirs: par.dirs,
|
||||
areas,
|
||||
finished: vec![],
|
||||
lines: vec![],
|
||||
@ -80,7 +78,7 @@ impl<'a> ParLayouter<'a> {
|
||||
self.run_size.cross = (self.run_size.cross + amount).min(cross_max);
|
||||
}
|
||||
|
||||
fn push_layout(&mut self, layout: BoxLayout, align: Align) {
|
||||
fn push_frame(&mut self, frame: Frame, align: Align) {
|
||||
if self.run_ruler > align {
|
||||
self.finish_run();
|
||||
}
|
||||
@ -88,16 +86,16 @@ impl<'a> ParLayouter<'a> {
|
||||
let fits = {
|
||||
let mut usable = self.areas.current.rem;
|
||||
*usable.get_mut(self.cross) -= self.run_size.cross;
|
||||
usable.fits(layout.size)
|
||||
usable.fits(frame.size)
|
||||
};
|
||||
|
||||
if !fits {
|
||||
self.finish_run();
|
||||
|
||||
while !self.areas.current.rem.fits(layout.size) {
|
||||
while !self.areas.current.rem.fits(frame.size) {
|
||||
if self.areas.in_full_last() {
|
||||
// TODO: Diagnose once the necessary spans exist.
|
||||
let _ = warning!("cannot fit box into any area");
|
||||
let _ = warning!("cannot fit frame into any area");
|
||||
break;
|
||||
} else {
|
||||
self.finish_area();
|
||||
@ -105,8 +103,8 @@ impl<'a> ParLayouter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let size = layout.size.switch(self.flow);
|
||||
self.run.push((self.run_size.cross, layout, align));
|
||||
let size = frame.size.switch(self.dirs);
|
||||
self.run.push((self.run_size.cross, frame, align));
|
||||
|
||||
self.run_size.cross += size.cross;
|
||||
self.run_size.main = self.run_size.main.max(size.main);
|
||||
@ -119,13 +117,13 @@ impl<'a> ParLayouter<'a> {
|
||||
Expansion::Fit => self.run_size.cross,
|
||||
});
|
||||
|
||||
let mut output = BoxLayout::new(full_size.switch(self.flow).to_size());
|
||||
let mut output = Frame::new(full_size.switch(self.dirs).to_size());
|
||||
|
||||
for (before, layout, align) in std::mem::take(&mut self.run) {
|
||||
let child_cross_size = layout.size.get(self.cross);
|
||||
for (before, frame, align) in std::mem::take(&mut self.run) {
|
||||
let child_cross_size = frame.size.get(self.cross);
|
||||
|
||||
// Position along the cross axis.
|
||||
let cross = align.resolve(if self.flow.cross.is_positive() {
|
||||
let cross = align.resolve(if self.dirs.cross.is_positive() {
|
||||
let after_with_self = self.run_size.cross - before;
|
||||
before .. full_size.cross - after_with_self
|
||||
} else {
|
||||
@ -134,8 +132,8 @@ impl<'a> ParLayouter<'a> {
|
||||
full_size.cross - before_with_self .. after
|
||||
});
|
||||
|
||||
let pos = Gen::new(Length::ZERO, cross).switch(self.flow).to_point();
|
||||
output.push_layout(pos, layout);
|
||||
let pos = Gen::new(Length::ZERO, cross).switch(self.dirs).to_point();
|
||||
output.push_frame(pos, frame);
|
||||
}
|
||||
|
||||
self.lines.push((self.lines_size.main, output, self.run_ruler));
|
||||
@ -151,27 +149,27 @@ impl<'a> ParLayouter<'a> {
|
||||
|
||||
fn finish_area(&mut self) {
|
||||
let size = self.lines_size;
|
||||
let mut output = BoxLayout::new(size.switch(self.flow).to_size());
|
||||
let mut output = Frame::new(size.switch(self.dirs).to_size());
|
||||
|
||||
for (before, run, cross_align) in std::mem::take(&mut self.lines) {
|
||||
let child_size = run.size.switch(self.flow);
|
||||
let child_size = run.size.switch(self.dirs);
|
||||
|
||||
// Position along the main axis.
|
||||
let main = if self.flow.main.is_positive() {
|
||||
let main = if self.dirs.main.is_positive() {
|
||||
before
|
||||
} else {
|
||||
size.main - (before + child_size.main)
|
||||
};
|
||||
|
||||
// Align along the cross axis.
|
||||
let cross = cross_align.resolve(if self.flow.cross.is_positive() {
|
||||
let cross = cross_align.resolve(if self.dirs.cross.is_positive() {
|
||||
Length::ZERO .. size.cross - child_size.cross
|
||||
} else {
|
||||
size.cross - child_size.cross .. Length::ZERO
|
||||
});
|
||||
|
||||
let pos = Gen::new(main, cross).switch(self.flow).to_point();
|
||||
output.push_layout(pos, run);
|
||||
let pos = Gen::new(main, cross).switch(self.dirs).to_point();
|
||||
output.push_frame(pos, run);
|
||||
}
|
||||
|
||||
self.finished.push(output);
|
||||
@ -180,7 +178,7 @@ impl<'a> ParLayouter<'a> {
|
||||
self.lines_size = Gen::ZERO;
|
||||
}
|
||||
|
||||
fn finish(mut self) -> Vec<BoxLayout> {
|
||||
fn finish(mut self) -> Vec<Frame> {
|
||||
self.finish_run();
|
||||
self.finish_area();
|
||||
self.finished
|
||||
|
@ -5,7 +5,7 @@ use crate::eval::Softness;
|
||||
|
||||
/// A spacing node.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct Spacing {
|
||||
pub struct NodeSpacing {
|
||||
/// The amount of spacing to insert.
|
||||
pub amount: Length,
|
||||
/// Defines how spacing interacts with surrounding spacing.
|
||||
@ -19,13 +19,13 @@ pub struct Spacing {
|
||||
pub softness: Softness,
|
||||
}
|
||||
|
||||
impl Layout for Spacing {
|
||||
impl Layout for NodeSpacing {
|
||||
fn layout(&self, _: &mut LayoutContext, _: &Areas) -> Layouted {
|
||||
Layouted::Spacing(self.amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Spacing {
|
||||
impl Debug for NodeSpacing {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self.softness {
|
||||
Softness::Soft => write!(f, "Soft({})", self.amount),
|
||||
@ -34,8 +34,8 @@ impl Debug for Spacing {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Spacing> for LayoutNode {
|
||||
fn from(spacing: Spacing) -> Self {
|
||||
impl From<NodeSpacing> for Node {
|
||||
fn from(spacing: NodeSpacing) -> Self {
|
||||
Self::Spacing(spacing)
|
||||
}
|
||||
}
|
||||
|
@ -1,65 +1,65 @@
|
||||
use super::*;
|
||||
|
||||
/// A node that stacks and align its children.
|
||||
/// A node that stacks its children.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Stack {
|
||||
pub struct NodeStack {
|
||||
/// The `main` and `cross` directions of this stack.
|
||||
///
|
||||
/// The children are stacked along the `main` direction. The `cross`
|
||||
/// direction is required for aligning the children.
|
||||
pub flow: Flow,
|
||||
pub dirs: LayoutDirs,
|
||||
/// How to align this stack in _its_ parent.
|
||||
pub align: BoxAlign,
|
||||
pub align: ChildAlign,
|
||||
/// Whether to expand the axes to fill the area or to fit the content.
|
||||
pub expansion: Gen<Expansion>,
|
||||
/// The nodes to be stacked.
|
||||
pub children: Vec<LayoutNode>,
|
||||
pub children: Vec<Node>,
|
||||
}
|
||||
|
||||
impl Layout for Stack {
|
||||
impl Layout for NodeStack {
|
||||
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
|
||||
let mut layouter = StackLayouter::new(self, areas.clone());
|
||||
for child in &self.children {
|
||||
match child.layout(ctx, &layouter.areas) {
|
||||
Layouted::Spacing(spacing) => layouter.push_spacing(spacing),
|
||||
Layouted::Layout(layout, align) => layouter.push_layout(layout, align),
|
||||
Layouted::Layouts(layouts, align) => {
|
||||
for layout in layouts {
|
||||
layouter.push_layout(layout, align);
|
||||
Layouted::Frame(frame, align) => layouter.push_frame(frame, align),
|
||||
Layouted::Frames(frames, align) => {
|
||||
for frame in frames {
|
||||
layouter.push_frame(frame, align);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Layouted::Layouts(layouter.finish(), self.align)
|
||||
Layouted::Frames(layouter.finish(), self.align)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Stack> for LayoutNode {
|
||||
fn from(stack: Stack) -> Self {
|
||||
Self::dynamic(stack)
|
||||
impl From<NodeStack> for Node {
|
||||
fn from(stack: NodeStack) -> Self {
|
||||
Self::any(stack)
|
||||
}
|
||||
}
|
||||
|
||||
struct StackLayouter<'a> {
|
||||
stack: &'a Stack,
|
||||
stack: &'a NodeStack,
|
||||
main: SpecAxis,
|
||||
flow: Flow,
|
||||
dirs: LayoutDirs,
|
||||
areas: Areas,
|
||||
finished: Vec<BoxLayout>,
|
||||
layouts: Vec<(Length, BoxLayout, BoxAlign)>,
|
||||
finished: Vec<Frame>,
|
||||
frames: Vec<(Length, Frame, ChildAlign)>,
|
||||
used: Gen<Length>,
|
||||
ruler: Align,
|
||||
}
|
||||
|
||||
impl<'a> StackLayouter<'a> {
|
||||
fn new(stack: &'a Stack, areas: Areas) -> Self {
|
||||
fn new(stack: &'a NodeStack, areas: Areas) -> Self {
|
||||
Self {
|
||||
stack,
|
||||
main: stack.flow.main.axis(),
|
||||
flow: stack.flow,
|
||||
main: stack.dirs.main.axis(),
|
||||
dirs: stack.dirs,
|
||||
areas,
|
||||
finished: vec![],
|
||||
layouts: vec![],
|
||||
frames: vec![],
|
||||
used: Gen::ZERO,
|
||||
ruler: Align::Start,
|
||||
}
|
||||
@ -72,23 +72,23 @@ impl<'a> StackLayouter<'a> {
|
||||
self.used.main += capped;
|
||||
}
|
||||
|
||||
fn push_layout(&mut self, layout: BoxLayout, align: BoxAlign) {
|
||||
fn push_frame(&mut self, frame: Frame, align: ChildAlign) {
|
||||
if self.ruler > align.main {
|
||||
self.finish_area();
|
||||
}
|
||||
|
||||
while !self.areas.current.rem.fits(layout.size) {
|
||||
while !self.areas.current.rem.fits(frame.size) {
|
||||
if self.areas.in_full_last() {
|
||||
// TODO: Diagnose once the necessary spans exist.
|
||||
let _ = warning!("cannot fit box into any area");
|
||||
let _ = warning!("cannot fit frame into any area");
|
||||
break;
|
||||
} else {
|
||||
self.finish_area();
|
||||
}
|
||||
}
|
||||
|
||||
let size = layout.size.switch(self.flow);
|
||||
self.layouts.push((self.used.main, layout, align));
|
||||
let size = frame.size.switch(self.dirs);
|
||||
self.frames.push((self.used.main, frame, align));
|
||||
|
||||
*self.areas.current.rem.get_mut(self.main) -= size.main;
|
||||
self.used.main += size.main;
|
||||
@ -98,7 +98,7 @@ impl<'a> StackLayouter<'a> {
|
||||
|
||||
fn finish_area(&mut self) {
|
||||
let full_size = {
|
||||
let full = self.areas.current.full.switch(self.flow);
|
||||
let full = self.areas.current.full.switch(self.dirs);
|
||||
Gen::new(
|
||||
match self.stack.expansion.main {
|
||||
Expansion::Fill => full.main,
|
||||
@ -111,13 +111,13 @@ impl<'a> StackLayouter<'a> {
|
||||
)
|
||||
};
|
||||
|
||||
let mut output = BoxLayout::new(full_size.switch(self.flow).to_size());
|
||||
let mut output = Frame::new(full_size.switch(self.dirs).to_size());
|
||||
|
||||
for (before, layout, align) in std::mem::take(&mut self.layouts) {
|
||||
let child_size = layout.size.switch(self.flow);
|
||||
for (before, frame, align) in std::mem::take(&mut self.frames) {
|
||||
let child_size = frame.size.switch(self.dirs);
|
||||
|
||||
// Align along the main axis.
|
||||
let main = align.main.resolve(if self.flow.main.is_positive() {
|
||||
let main = align.main.resolve(if self.dirs.main.is_positive() {
|
||||
let after_with_self = self.used.main - before;
|
||||
before .. full_size.main - after_with_self
|
||||
} else {
|
||||
@ -127,14 +127,14 @@ impl<'a> StackLayouter<'a> {
|
||||
});
|
||||
|
||||
// Align along the cross axis.
|
||||
let cross = align.cross.resolve(if self.flow.cross.is_positive() {
|
||||
let cross = align.cross.resolve(if self.dirs.cross.is_positive() {
|
||||
Length::ZERO .. full_size.cross - child_size.cross
|
||||
} else {
|
||||
full_size.cross - child_size.cross .. Length::ZERO
|
||||
});
|
||||
|
||||
let pos = Gen::new(main, cross).switch(self.flow).to_point();
|
||||
output.push_layout(pos, layout);
|
||||
let pos = Gen::new(main, cross).switch(self.dirs).to_point();
|
||||
output.push_frame(pos, frame);
|
||||
}
|
||||
|
||||
self.finished.push(output);
|
||||
@ -144,7 +144,7 @@ impl<'a> StackLayouter<'a> {
|
||||
self.ruler = Align::Start;
|
||||
}
|
||||
|
||||
fn finish(mut self) -> Vec<BoxLayout> {
|
||||
fn finish(mut self) -> Vec<Frame> {
|
||||
self.finish_area();
|
||||
self.finished
|
||||
}
|
||||
|
@ -8,11 +8,11 @@ use crate::shaping;
|
||||
|
||||
/// A text node.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Text {
|
||||
pub struct NodeText {
|
||||
/// The text.
|
||||
pub text: String,
|
||||
/// How to align this text node in its parent.
|
||||
pub align: BoxAlign,
|
||||
pub align: ChildAlign,
|
||||
/// The text direction.
|
||||
pub dir: Dir,
|
||||
/// The font size.
|
||||
@ -23,15 +23,15 @@ pub struct Text {
|
||||
pub variant: FontVariant,
|
||||
}
|
||||
|
||||
impl Layout for Text {
|
||||
impl Layout for NodeText {
|
||||
fn layout(&self, ctx: &mut LayoutContext, _: &Areas) -> Layouted {
|
||||
let mut env = ctx.env.borrow_mut();
|
||||
Layouted::Layout(
|
||||
Layouted::Frame(
|
||||
shaping::shape(
|
||||
&mut env.fonts,
|
||||
&self.text,
|
||||
self.dir,
|
||||
self.font_size,
|
||||
&mut env.fonts,
|
||||
&self.families,
|
||||
self.variant,
|
||||
),
|
||||
@ -40,14 +40,14 @@ impl Layout for Text {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Text {
|
||||
impl Debug for NodeText {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "Text({})", self.text)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Text> for LayoutNode {
|
||||
fn from(text: Text) -> Self {
|
||||
impl From<NodeText> for Node {
|
||||
fn from(text: NodeText) -> Self {
|
||||
Self::Text(text)
|
||||
}
|
||||
}
|
||||
|
34
src/lib.rs
34
src/lib.rs
@ -5,23 +5,23 @@
|
||||
//! [iterator of tokens][tokens]. This token stream is [parsed] into a [syntax
|
||||
//! tree]. The structures describing the tree can be found in the [syntax]
|
||||
//! module.
|
||||
//! - **Evaluation:** The next step is to [evaluate] the parsed "script" to a
|
||||
//! [document], a high-level, fully styled representation. The nodes of the
|
||||
//! document tree are fully self-contained and order-independent and thus much
|
||||
//! - **Evaluation:** The next step is to [evaluate] the parsed "script" into a
|
||||
//! [layout tree], a high-level, fully styled representation. The nodes of
|
||||
//! this tree are fully self-contained and order-independent and thus much
|
||||
//! better suited for layouting than the syntax tree.
|
||||
//! - **Layouting:** The next step is to [layout] the document into a portable
|
||||
//! version of the typeset document. The output of this is a vector of
|
||||
//! [`BoxLayout`]s (corresponding to pages), ready for exporting.
|
||||
//! - **Layouting:** Next, the tree is to [layouted] into a portable version of
|
||||
//! the typeset document. The output of this is a vector of [`Frame`]s
|
||||
//! (corresponding to pages), ready for exporting.
|
||||
//! - **Exporting:** The finished layout can be exported into a supported
|
||||
//! format. Submodules for these formats are located in the [export] module.
|
||||
//! Currently, the only supported output format is [_PDF_].
|
||||
//!
|
||||
//! [tokens]: parse::Tokens
|
||||
//! [parsed]: parse::parse
|
||||
//! [syntax tree]: syntax::SynTree
|
||||
//! [syntax tree]: syntax::Tree
|
||||
//! [evaluate]: eval::eval
|
||||
//! [document]: layout::Document
|
||||
//! [layout]: layout::layout
|
||||
//! [layout tree]: layout::Tree
|
||||
//! [layouted]: layout::layout
|
||||
//! [_PDF_]: export::pdf
|
||||
|
||||
#[macro_use]
|
||||
@ -46,13 +46,13 @@ use std::rc::Rc;
|
||||
use crate::diag::{Feedback, Pass};
|
||||
use crate::env::SharedEnv;
|
||||
use crate::eval::State;
|
||||
use crate::layout::BoxLayout;
|
||||
use crate::layout::Frame;
|
||||
|
||||
/// Process _Typst_ source code directly into a collection of layouts.
|
||||
pub fn typeset(src: &str, env: SharedEnv, state: State) -> Pass<Vec<BoxLayout>> {
|
||||
let Pass { output: tree, feedback: f1 } = parse::parse(src);
|
||||
let Pass { output: document, feedback: f2 } =
|
||||
eval::eval(&tree, Rc::clone(&env), state);
|
||||
let layouts = layout::layout(&document, env);
|
||||
Pass::new(layouts, Feedback::join(f1, f2))
|
||||
/// Process _Typst_ source code directly into a collection of frames.
|
||||
pub fn typeset(src: &str, env: SharedEnv, state: State) -> Pass<Vec<Frame>> {
|
||||
let Pass { output: syntax_tree, feedback: f1 } = parse::parse(src);
|
||||
let Pass { output: layout_tree, feedback: f2 } =
|
||||
eval::eval(&syntax_tree, Rc::clone(&env), state);
|
||||
let frames = layout::layout(&layout_tree, env);
|
||||
Pass::new(frames, Feedback::join(f1, f2))
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
||||
if let Some((res, img)) = loaded {
|
||||
let dimensions = img.buf.dimensions();
|
||||
drop(env);
|
||||
ctx.push(Image {
|
||||
ctx.push(NodeImage {
|
||||
res,
|
||||
dimensions,
|
||||
width,
|
||||
@ -40,7 +40,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
||||
|
||||
/// An image node.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
struct Image {
|
||||
struct NodeImage {
|
||||
/// The resource id of the image file.
|
||||
res: ResourceId,
|
||||
/// The pixel dimensions of the image.
|
||||
@ -50,10 +50,10 @@ struct Image {
|
||||
/// The fixed height, if any.
|
||||
height: Option<Linear>,
|
||||
/// How to align this image node in its parent.
|
||||
align: BoxAlign,
|
||||
align: ChildAlign,
|
||||
}
|
||||
|
||||
impl Layout for Image {
|
||||
impl Layout for NodeImage {
|
||||
fn layout(&self, _: &mut LayoutContext, areas: &Areas) -> Layouted {
|
||||
let Area { rem, full } = areas.current;
|
||||
let pixel_ratio = (self.dimensions.0 as f64) / (self.dimensions.1 as f64);
|
||||
@ -76,18 +76,15 @@ impl Layout for Image {
|
||||
}
|
||||
};
|
||||
|
||||
let mut boxed = BoxLayout::new(size);
|
||||
boxed.push(
|
||||
Point::ZERO,
|
||||
LayoutElement::Image(ImageElement { res: self.res, size }),
|
||||
);
|
||||
let mut frame = Frame::new(size);
|
||||
frame.push(Point::ZERO, Element::Image(Image { res: self.res, size }));
|
||||
|
||||
Layouted::Layout(boxed, self.align)
|
||||
Layouted::Frame(frame, self.align)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Image> for LayoutNode {
|
||||
fn from(image: Image) -> Self {
|
||||
Self::dynamic(image)
|
||||
impl From<NodeImage> for Node {
|
||||
fn from(image: NodeImage) -> Self {
|
||||
Self::any(image)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::eval::Softness;
|
||||
use crate::layout::{Expansion, Fixed, Spacing, Stack};
|
||||
use crate::layout::{Expansion, NodeFixed, NodeSpacing, NodeStack};
|
||||
use crate::paper::{Paper, PaperClass};
|
||||
use crate::prelude::*;
|
||||
|
||||
@ -45,8 +45,8 @@ pub fn align(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
||||
// Check whether we know which axis this alignment belongs to.
|
||||
if let Some(axis) = axis {
|
||||
// We know the axis.
|
||||
let gen_axis = axis.switch(ctx.state.flow);
|
||||
let gen_align = arg.switch(ctx.state.flow);
|
||||
let gen_axis = axis.switch(ctx.state.dirs);
|
||||
let gen_align = arg.switch(ctx.state.dirs);
|
||||
|
||||
if arg.axis().map_or(false, |a| a != axis) {
|
||||
ctx.diag(error!(span, "invalid alignment for {} axis", axis));
|
||||
@ -132,7 +132,7 @@ impl Alignment {
|
||||
impl Switch for Alignment {
|
||||
type Other = Align;
|
||||
|
||||
fn switch(self, flow: Flow) -> Self::Other {
|
||||
fn switch(self, dirs: LayoutDirs) -> Self::Other {
|
||||
let get = |dir: Dir, at_positive_start| {
|
||||
if dir.is_positive() == at_positive_start {
|
||||
Align::Start
|
||||
@ -141,12 +141,12 @@ impl Switch for Alignment {
|
||||
}
|
||||
};
|
||||
|
||||
let flow = flow.switch(flow);
|
||||
let dirs = dirs.switch(dirs);
|
||||
match self {
|
||||
Self::Left => get(flow.horizontal, true),
|
||||
Self::Right => get(flow.horizontal, false),
|
||||
Self::Top => get(flow.vertical, true),
|
||||
Self::Bottom => get(flow.vertical, false),
|
||||
Self::Left => get(dirs.horizontal, true),
|
||||
Self::Right => get(dirs.horizontal, false),
|
||||
Self::Top => get(dirs.vertical, true),
|
||||
Self::Bottom => get(dirs.vertical, false),
|
||||
Self::Center => Align::Center,
|
||||
}
|
||||
}
|
||||
@ -169,9 +169,9 @@ pub fn boxed(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
||||
let main = args.get(ctx, "main-dir");
|
||||
let cross = args.get(ctx, "cross-dir");
|
||||
|
||||
ctx.set_flow(Gen::new(main, cross));
|
||||
ctx.set_dirs(Gen::new(main, cross));
|
||||
|
||||
let flow = ctx.state.flow;
|
||||
let dirs = ctx.state.dirs;
|
||||
let align = ctx.state.align;
|
||||
|
||||
ctx.start_content_group();
|
||||
@ -182,19 +182,14 @@ pub fn boxed(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
||||
|
||||
let children = ctx.end_content_group();
|
||||
|
||||
ctx.push(Fixed {
|
||||
let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit };
|
||||
let expansion =
|
||||
Spec::new(fill_if(width.is_some()), fill_if(height.is_some())).switch(dirs);
|
||||
|
||||
ctx.push(NodeFixed {
|
||||
width,
|
||||
height,
|
||||
child: LayoutNode::dynamic(Stack {
|
||||
flow,
|
||||
align,
|
||||
expansion: Spec::new(
|
||||
Expansion::fill_if(width.is_some()),
|
||||
Expansion::fill_if(height.is_some()),
|
||||
)
|
||||
.switch(flow),
|
||||
children,
|
||||
}),
|
||||
child: Node::any(NodeStack { dirs, align, expansion, children }),
|
||||
});
|
||||
|
||||
ctx.state = snapshot;
|
||||
@ -227,8 +222,8 @@ fn spacing(ctx: &mut EvalContext, args: &mut Args, axis: SpecAxis) -> Value {
|
||||
|
||||
if let Some(linear) = spacing {
|
||||
let amount = linear.resolve(ctx.state.font.font_size());
|
||||
let spacing = Spacing { amount, softness: Softness::Hard };
|
||||
if axis == ctx.state.flow.main.axis() {
|
||||
let spacing = NodeSpacing { amount, softness: Softness::Hard };
|
||||
if axis == ctx.state.dirs.main.axis() {
|
||||
ctx.end_par_group();
|
||||
ctx.push(spacing);
|
||||
ctx.start_par_group();
|
||||
@ -305,7 +300,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
||||
let main = args.get(ctx, "main-dir");
|
||||
let cross = args.get(ctx, "cross-dir");
|
||||
|
||||
ctx.set_flow(Gen::new(main, cross));
|
||||
ctx.set_dirs(Gen::new(main, cross));
|
||||
|
||||
let mut softness = ctx.end_page_group(|_| false);
|
||||
if let Some(body) = args.find::<ValueContent>(ctx) {
|
||||
|
@ -49,7 +49,7 @@ fn main() -> anyhow::Result<()> {
|
||||
|
||||
let state = State::default();
|
||||
let Pass {
|
||||
output: layouts,
|
||||
output: frames,
|
||||
feedback: Feedback { mut diags, .. },
|
||||
} = typeset(&src, Rc::clone(&env), state);
|
||||
|
||||
@ -72,7 +72,7 @@ fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
let pdf_data = pdf::export(&layouts, &env.borrow());
|
||||
let pdf_data = pdf::export(&frames, &env.borrow());
|
||||
fs::write(&dest_path, pdf_data).context("Failed to write PDF file.")?;
|
||||
|
||||
Ok(())
|
||||
|
@ -5,7 +5,7 @@ use crate::geom::{Length, Linear, Relative, Sides, Size};
|
||||
/// Specification of a paper.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Paper {
|
||||
/// The kind of paper, which defines the default margins.
|
||||
/// The broad class this paper belongs to.
|
||||
pub class: PaperClass,
|
||||
/// The width of the paper in millimeters.
|
||||
pub width: f64,
|
||||
@ -25,7 +25,7 @@ impl Paper {
|
||||
}
|
||||
}
|
||||
|
||||
/// Paper classes define default margins for a class of related papers.
|
||||
/// Defines default margins for a class of related papers.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum PaperClass {
|
||||
Custom,
|
||||
|
@ -2,7 +2,7 @@ use super::*;
|
||||
use crate::diag::Deco;
|
||||
|
||||
/// Parse the arguments to a function call.
|
||||
pub fn arguments(p: &mut Parser) -> Arguments {
|
||||
pub fn arguments(p: &mut Parser) -> ExprArgs {
|
||||
collection(p, vec![])
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ trait Collection {
|
||||
fn push_comma(&mut self) {}
|
||||
}
|
||||
|
||||
impl Collection for Arguments {
|
||||
impl Collection for ExprArgs {
|
||||
fn push_arg(&mut self, _: &mut Parser, arg: Spanned<Argument>) {
|
||||
self.push(arg.v);
|
||||
}
|
||||
@ -85,17 +85,17 @@ impl Collection for Arguments {
|
||||
enum State {
|
||||
Unknown,
|
||||
Expr(Spanned<Expr>),
|
||||
Array(Array),
|
||||
Dict(Dict),
|
||||
Array(ExprArray),
|
||||
Dict(ExprDict),
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn into_expr(self) -> Expr {
|
||||
match self {
|
||||
Self::Unknown => Expr::Lit(Lit::Array(vec![])),
|
||||
Self::Unknown => Expr::Array(vec![]),
|
||||
Self::Expr(expr) => expr.v,
|
||||
Self::Array(array) => Expr::Lit(Lit::Array(array)),
|
||||
Self::Dict(dict) => Expr::Lit(Lit::Dict(dict)),
|
||||
Self::Array(array) => Expr::Array(array),
|
||||
Self::Dict(dict) => Expr::Dict(dict),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
//! Conversion of byte positions to line/column locations.
|
||||
|
||||
use super::Scanner;
|
||||
use crate::syntax::{Location, Offset, Pos};
|
||||
|
||||
|
@ -22,13 +22,13 @@ use crate::syntax::*;
|
||||
use collection::{arguments, parenthesized};
|
||||
|
||||
/// Parse a string of source code.
|
||||
pub fn parse(src: &str) -> Pass<SynTree> {
|
||||
pub fn parse(src: &str) -> Pass<Tree> {
|
||||
let mut p = Parser::new(src);
|
||||
Pass::new(tree(&mut p), p.finish())
|
||||
}
|
||||
|
||||
/// Parse a syntax tree.
|
||||
fn tree(p: &mut Parser) -> SynTree {
|
||||
fn tree(p: &mut Parser) -> Tree {
|
||||
// We keep track of whether we are at the start of a block or paragraph
|
||||
// to know whether headings are allowed.
|
||||
let mut at_start = true;
|
||||
@ -36,8 +36,8 @@ fn tree(p: &mut Parser) -> SynTree {
|
||||
while !p.eof() {
|
||||
if let Some(node) = p.span_if(|p| node(p, at_start)) {
|
||||
match node.v {
|
||||
SynNode::Parbreak => at_start = true,
|
||||
SynNode::Space => {}
|
||||
Node::Parbreak => at_start = true,
|
||||
Node::Space => {}
|
||||
_ => at_start = false,
|
||||
}
|
||||
tree.push(node);
|
||||
@ -47,42 +47,42 @@ fn tree(p: &mut Parser) -> SynTree {
|
||||
}
|
||||
|
||||
/// Parse a syntax node.
|
||||
fn node(p: &mut Parser, at_start: bool) -> Option<SynNode> {
|
||||
fn node(p: &mut Parser, at_start: bool) -> Option<Node> {
|
||||
let node = match p.peek()? {
|
||||
Token::Space(newlines) => {
|
||||
if newlines < 2 {
|
||||
SynNode::Space
|
||||
Node::Space
|
||||
} else {
|
||||
SynNode::Parbreak
|
||||
Node::Parbreak
|
||||
}
|
||||
}
|
||||
Token::Text(text) => SynNode::Text(text.into()),
|
||||
Token::Text(text) => Node::Text(text.into()),
|
||||
|
||||
Token::LineComment(_) | Token::BlockComment(_) => {
|
||||
p.eat();
|
||||
return None;
|
||||
}
|
||||
|
||||
Token::Star => SynNode::Strong,
|
||||
Token::Underscore => SynNode::Emph,
|
||||
Token::Tilde => SynNode::Text("\u{00A0}".into()),
|
||||
Token::Backslash => SynNode::Linebreak,
|
||||
Token::Star => Node::Strong,
|
||||
Token::Underscore => Node::Emph,
|
||||
Token::Tilde => Node::Text("\u{00A0}".into()),
|
||||
Token::Backslash => Node::Linebreak,
|
||||
Token::Hashtag => {
|
||||
if at_start {
|
||||
return Some(SynNode::Heading(heading(p)));
|
||||
return Some(Node::Heading(heading(p)));
|
||||
} else {
|
||||
SynNode::Text(p.get(p.peek_span()).into())
|
||||
Node::Text(p.get(p.peek_span()).into())
|
||||
}
|
||||
}
|
||||
Token::Raw(t) => SynNode::Raw(raw(p, t)),
|
||||
Token::UnicodeEscape(t) => SynNode::Text(unicode_escape(p, t)),
|
||||
Token::Raw(t) => Node::Raw(raw(p, t)),
|
||||
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
|
||||
|
||||
Token::LeftBracket => {
|
||||
return Some(SynNode::Expr(Expr::Call(bracket_call(p))));
|
||||
return Some(Node::Expr(Expr::Call(bracket_call(p))));
|
||||
}
|
||||
|
||||
Token::LeftBrace => {
|
||||
return Some(SynNode::Expr(block_expr(p)?));
|
||||
return Some(Node::Expr(block_expr(p)?));
|
||||
}
|
||||
|
||||
_ => {
|
||||
@ -189,15 +189,15 @@ fn bracket_call(p: &mut Parser) -> ExprCall {
|
||||
p.end_group();
|
||||
|
||||
if p.peek() == Some(Token::LeftBracket) {
|
||||
let body = p.span(|p| Expr::Lit(Lit::Content(bracket_body(p))));
|
||||
let body = p.span(|p| Expr::Content(bracket_body(p)));
|
||||
inner.span.expand(body.span);
|
||||
inner.v.args.v.push(Argument::Pos(body));
|
||||
}
|
||||
|
||||
while let Some(mut top) = outer.pop() {
|
||||
let span = inner.span;
|
||||
let node = inner.map(|c| SynNode::Expr(Expr::Call(c)));
|
||||
let expr = Expr::Lit(Lit::Content(vec![node])).with_span(span);
|
||||
let node = inner.map(|c| Node::Expr(Expr::Call(c)));
|
||||
let expr = Expr::Content(vec![node]).with_span(span);
|
||||
top.v.args.v.push(Argument::Pos(expr));
|
||||
inner = top;
|
||||
}
|
||||
@ -227,7 +227,7 @@ fn bracket_subheader(p: &mut Parser) -> ExprCall {
|
||||
}
|
||||
|
||||
/// Parse the body of a bracketed function call.
|
||||
fn bracket_body(p: &mut Parser) -> SynTree {
|
||||
fn bracket_body(p: &mut Parser) -> Tree {
|
||||
p.push_mode(TokenMode::Body);
|
||||
p.start_group(Group::Bracket);
|
||||
let tree = tree(p);
|
||||
@ -299,13 +299,13 @@ fn value(p: &mut Parser) -> Option<Expr> {
|
||||
let expr = match p.peek() {
|
||||
// Bracketed function call.
|
||||
Some(Token::LeftBracket) => {
|
||||
let node = p.span(|p| SynNode::Expr(Expr::Call(bracket_call(p))));
|
||||
return Some(Expr::Lit(Lit::Content(vec![node])));
|
||||
let node = p.span(|p| Node::Expr(Expr::Call(bracket_call(p))));
|
||||
return Some(Expr::Content(vec![node]));
|
||||
}
|
||||
|
||||
// Content expression.
|
||||
Some(Token::LeftBrace) => {
|
||||
return Some(Expr::Lit(Lit::Content(content(p))));
|
||||
return Some(Expr::Content(content(p)));
|
||||
}
|
||||
|
||||
// Dictionary or just a parenthesized expression.
|
||||
@ -345,7 +345,7 @@ fn value(p: &mut Parser) -> Option<Expr> {
|
||||
}
|
||||
|
||||
// Parse a content value: `{...}`.
|
||||
fn content(p: &mut Parser) -> SynTree {
|
||||
fn content(p: &mut Parser) -> Tree {
|
||||
p.push_mode(TokenMode::Body);
|
||||
p.start_group(Group::Brace);
|
||||
let tree = tree(p);
|
||||
|
@ -1,5 +1,3 @@
|
||||
//! Resolve strings and raw blocks.
|
||||
|
||||
use super::{is_newline, Scanner};
|
||||
use crate::syntax::{Ident, NodeRaw};
|
||||
|
||||
|
@ -1,9 +1,7 @@
|
||||
//! Low-level char-based scanner.
|
||||
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::slice::SliceIndex;
|
||||
|
||||
/// A low-level featureful char-based scanner.
|
||||
/// A featureful char-based scanner.
|
||||
#[derive(Clone)]
|
||||
pub struct Scanner<'s> {
|
||||
src: &'s str,
|
||||
|
@ -9,7 +9,7 @@ use crate::geom::Unit;
|
||||
use crate::syntax::*;
|
||||
|
||||
use BinOp::*;
|
||||
use SynNode::{Emph, Linebreak, Parbreak, Space, Strong};
|
||||
use Node::{Emph, Linebreak, Parbreak, Space, Strong};
|
||||
use UnOp::*;
|
||||
|
||||
macro_rules! t {
|
||||
@ -82,16 +82,16 @@ macro_rules! into {
|
||||
};
|
||||
}
|
||||
|
||||
fn Text(text: &str) -> SynNode {
|
||||
SynNode::Text(text.into())
|
||||
fn Text(text: &str) -> Node {
|
||||
Node::Text(text.into())
|
||||
}
|
||||
|
||||
fn Heading(level: impl Into<Spanned<u8>>, contents: SynTree) -> SynNode {
|
||||
SynNode::Heading(NodeHeading { level: level.into(), contents })
|
||||
fn Heading(level: impl Into<Spanned<u8>>, contents: Tree) -> Node {
|
||||
Node::Heading(NodeHeading { level: level.into(), contents })
|
||||
}
|
||||
|
||||
fn Raw(lang: Option<&str>, lines: &[&str], inline: bool) -> SynNode {
|
||||
SynNode::Raw(NodeRaw {
|
||||
fn Raw(lang: Option<&str>, lines: &[&str], inline: bool) -> Node {
|
||||
Node::Raw(NodeRaw {
|
||||
lang: lang.map(|id| Ident(id.into())),
|
||||
lines: lines.iter().map(ToString::to_string).collect(),
|
||||
inline,
|
||||
@ -130,8 +130,8 @@ fn Str(string: &str) -> Expr {
|
||||
Expr::Lit(Lit::Str(string.to_string()))
|
||||
}
|
||||
|
||||
fn Block(expr: Expr) -> SynNode {
|
||||
SynNode::Expr(expr)
|
||||
fn Block(expr: Expr) -> Node {
|
||||
Node::Expr(expr)
|
||||
}
|
||||
|
||||
fn Binary(
|
||||
@ -157,7 +157,7 @@ macro_rules! Array {
|
||||
(@$($expr:expr),* $(,)?) => {
|
||||
vec![$(into!($expr)),*]
|
||||
};
|
||||
($($tts:tt)*) => (Expr::Lit(Lit::Array(Array![@$($tts)*])));
|
||||
($($tts:tt)*) => (Expr::Array(Array![@$($tts)*]));
|
||||
}
|
||||
|
||||
macro_rules! Dict {
|
||||
@ -167,7 +167,7 @@ macro_rules! Dict {
|
||||
expr: into!($expr)
|
||||
}),*]
|
||||
};
|
||||
($($tts:tt)*) => (Expr::Lit(Lit::Dict(Dict![@$($tts)*])));
|
||||
($($tts:tt)*) => (Expr::Dict(Dict![@$($tts)*]));
|
||||
}
|
||||
|
||||
macro_rules! Args {
|
||||
@ -187,7 +187,7 @@ macro_rules! Args {
|
||||
|
||||
macro_rules! Content {
|
||||
(@$($node:expr),* $(,)?) => (vec![$(into!($node)),*]);
|
||||
($($tts:tt)*) => (Expr::Lit(Lit::Content(Content![@$($tts)*])));
|
||||
($($tts:tt)*) => (Expr::Content(Content![@$($tts)*]));
|
||||
}
|
||||
|
||||
macro_rules! Call {
|
||||
@ -201,7 +201,7 @@ macro_rules! Call {
|
||||
}
|
||||
};
|
||||
(@$($tts:tt)*) => (Expr::Call(Call!(@@$($tts)*)));
|
||||
($($tts:tt)*) => (SynNode::Expr(Call!(@$($tts)*)));
|
||||
($($tts:tt)*) => (Node::Expr(Call!(@$($tts)*)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1,5 +1,3 @@
|
||||
//! Tokenization.
|
||||
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use super::{is_newline, Scanner};
|
||||
|
@ -8,7 +8,7 @@ pub use crate::eval::{
|
||||
};
|
||||
pub use crate::geom::*;
|
||||
#[doc(no_inline)]
|
||||
pub use crate::layout::LayoutNode;
|
||||
pub use crate::layout::Node;
|
||||
#[doc(no_inline)]
|
||||
pub use crate::syntax::{Span, Spanned, SynTree, WithSpan};
|
||||
pub use crate::{error, warning};
|
||||
pub use crate::syntax::{Span, Spanned, WithSpan};
|
||||
pub use crate::{error, impl_type, warning};
|
||||
|
@ -11,7 +11,7 @@ use ttf_parser::{Face, GlyphId};
|
||||
|
||||
use crate::font::FontLoader;
|
||||
use crate::geom::{Dir, Length, Point, Size};
|
||||
use crate::layout::{BoxLayout, LayoutElement};
|
||||
use crate::layout::{Element, Frame};
|
||||
|
||||
/// A shaped run of text.
|
||||
#[derive(Clone, PartialEq)]
|
||||
@ -31,13 +31,13 @@ pub struct Shaped {
|
||||
|
||||
impl Shaped {
|
||||
/// Create a new shape run with empty `text`, `glyphs` and `offsets`.
|
||||
pub fn new(face: FaceId, size: Length) -> Self {
|
||||
pub fn new(face: FaceId, font_size: Length) -> Self {
|
||||
Self {
|
||||
text: String::new(),
|
||||
face,
|
||||
glyphs: vec![],
|
||||
offsets: vec![],
|
||||
font_size: size,
|
||||
font_size,
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,16 +58,16 @@ impl Debug for Shaped {
|
||||
}
|
||||
}
|
||||
|
||||
/// Shape text into a box containing [`Shaped`] runs.
|
||||
/// Shape text into a frame containing [`Shaped`] runs.
|
||||
pub fn shape(
|
||||
loader: &mut FontLoader,
|
||||
text: &str,
|
||||
dir: Dir,
|
||||
font_size: Length,
|
||||
loader: &mut FontLoader,
|
||||
fallback: &FallbackTree,
|
||||
variant: FontVariant,
|
||||
) -> BoxLayout {
|
||||
let mut layout = BoxLayout::new(Size::new(Length::ZERO, font_size));
|
||||
) -> Frame {
|
||||
let mut frame = Frame::new(Size::new(Length::ZERO, font_size));
|
||||
let mut shaped = Shaped::new(FaceId::MAX, font_size);
|
||||
let mut offset = Length::ZERO;
|
||||
|
||||
@ -91,9 +91,9 @@ pub fn shape(
|
||||
|
||||
// Flush the buffer if we change the font face.
|
||||
if shaped.face != id && !shaped.text.is_empty() {
|
||||
let pos = Point::new(layout.size.width, Length::ZERO);
|
||||
layout.push(pos, LayoutElement::Text(shaped));
|
||||
layout.size.width += offset;
|
||||
let pos = Point::new(frame.size.width, Length::ZERO);
|
||||
frame.push(pos, Element::Text(shaped));
|
||||
frame.size.width += offset;
|
||||
shaped = Shaped::new(FaceId::MAX, font_size);
|
||||
offset = Length::ZERO;
|
||||
}
|
||||
@ -108,12 +108,12 @@ pub fn shape(
|
||||
|
||||
// Flush the last buffered parts of the word.
|
||||
if !shaped.text.is_empty() {
|
||||
let pos = Point::new(layout.size.width, Length::ZERO);
|
||||
layout.push(pos, LayoutElement::Text(shaped));
|
||||
layout.size.width += offset;
|
||||
let pos = Point::new(frame.size.width, Length::ZERO);
|
||||
frame.push(pos, Element::Text(shaped));
|
||||
frame.size.width += offset;
|
||||
}
|
||||
|
||||
layout
|
||||
frame
|
||||
}
|
||||
|
||||
/// Looks up the glyph for `c` and returns its index alongside its width at the
|
||||
|
@ -1,5 +1,3 @@
|
||||
//! Expressions.
|
||||
|
||||
use super::*;
|
||||
use crate::color::RgbaColor;
|
||||
use crate::geom::Unit;
|
||||
@ -7,7 +5,7 @@ use crate::geom::Unit;
|
||||
/// An expression.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Expr {
|
||||
/// A literal: `true`, `1cm`, `"hi"`, `{_Hey!_}`.
|
||||
/// A literal: `true`, `1cm`, `"hi"`.
|
||||
Lit(Lit),
|
||||
/// An invocation of a function: `[foo ...]`, `foo(...)`.
|
||||
Call(ExprCall),
|
||||
@ -15,6 +13,12 @@ pub enum Expr {
|
||||
Unary(ExprUnary),
|
||||
/// A binary operation: `a + b`, `a / b`.
|
||||
Binary(ExprBinary),
|
||||
/// An array expression: `(1, "hi", 12cm)`.
|
||||
Array(ExprArray),
|
||||
/// A dictionary expression: `(color: #f79143, pattern: dashed)`.
|
||||
Dict(ExprDict),
|
||||
/// A content expression: `{*Hello* there!}`.
|
||||
Content(ExprContent),
|
||||
}
|
||||
|
||||
/// An invocation of a function: `[foo ...]`, `foo(...)`.
|
||||
@ -23,14 +27,14 @@ pub struct ExprCall {
|
||||
/// The name of the function.
|
||||
pub name: Spanned<Ident>,
|
||||
/// The arguments to the function.
|
||||
pub args: Spanned<Arguments>,
|
||||
pub args: Spanned<ExprArgs>,
|
||||
}
|
||||
|
||||
/// The arguments to a function: `12, draw: false`.
|
||||
///
|
||||
/// In case of a bracketed invocation with a body, the body is _not_
|
||||
/// included in the span for the sake of clearer error messages.
|
||||
pub type Arguments = Vec<Argument>;
|
||||
pub type ExprArgs = Vec<Argument>;
|
||||
|
||||
/// An argument to a function call: `12` or `draw: false`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@ -41,6 +45,15 @@ pub enum Argument {
|
||||
Named(Named),
|
||||
}
|
||||
|
||||
/// A pair of a name and an expression: `pattern: dashed`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Named {
|
||||
/// The name: `pattern`.
|
||||
pub name: Spanned<Ident>,
|
||||
/// The right-hand side of the pair: `dashed`.
|
||||
pub expr: Spanned<Expr>,
|
||||
}
|
||||
|
||||
/// A unary operation: `-x`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ExprUnary {
|
||||
@ -81,6 +94,15 @@ pub enum BinOp {
|
||||
Div,
|
||||
}
|
||||
|
||||
/// An array expression: `(1, "hi", 12cm)`.
|
||||
pub type ExprArray = SpanVec<Expr>;
|
||||
|
||||
/// A dictionary expression: `(color: #f79143, pattern: dashed)`.
|
||||
pub type ExprDict = Vec<Named>;
|
||||
|
||||
/// A content expression: `{*Hello* there!}`.
|
||||
pub type ExprContent = Tree;
|
||||
|
||||
/// A literal.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Lit {
|
||||
@ -103,25 +125,4 @@ pub enum Lit {
|
||||
Color(RgbaColor),
|
||||
/// A string literal: `"hello!"`.
|
||||
Str(String),
|
||||
/// An array literal: `(1, "hi", 12cm)`.
|
||||
Array(Array),
|
||||
/// A dictionary literal: `(color: #f79143, pattern: dashed)`.
|
||||
Dict(Dict),
|
||||
/// A content literal: `{*Hello* there!}`.
|
||||
Content(SynTree),
|
||||
}
|
||||
|
||||
/// An array literal: `(1, "hi", 12cm)`.
|
||||
pub type Array = SpanVec<Expr>;
|
||||
|
||||
/// A dictionary literal: `(color: #f79143, pattern: dashed)`.
|
||||
pub type Dict = Vec<Named>;
|
||||
|
||||
/// A pair of a name and an expression: `pattern: dashed`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Named {
|
||||
/// The name: `pattern`.
|
||||
pub name: Spanned<Ident>,
|
||||
/// The right-hand side of the pair: `dashed`.
|
||||
pub expr: Spanned<Expr>,
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
//! Unicode identifiers.
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
use unicode_xid::UnicodeXID;
|
||||
@ -44,7 +42,7 @@ impl Deref for Ident {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the string is a valid identifier.
|
||||
/// Whether a string is a valid identifier.
|
||||
pub fn is_ident(string: &str) -> bool {
|
||||
let mut chars = string.chars();
|
||||
chars
|
||||
@ -52,12 +50,12 @@ pub fn is_ident(string: &str) -> bool {
|
||||
.map_or(false, |c| is_id_start(c) && chars.all(is_id_continue))
|
||||
}
|
||||
|
||||
/// Whether the character can start an identifier.
|
||||
/// Whether a character can start an identifier.
|
||||
pub fn is_id_start(c: char) -> bool {
|
||||
c.is_xid_start() || c == '_'
|
||||
}
|
||||
|
||||
/// Whether the character can continue an identifier.
|
||||
/// Whether a character can continue an identifier.
|
||||
pub fn is_id_continue(c: char) -> bool {
|
||||
c.is_xid_continue() || c == '_' || c == '-'
|
||||
}
|
||||
|
@ -12,5 +12,5 @@ pub use node::*;
|
||||
pub use span::*;
|
||||
pub use token::*;
|
||||
|
||||
/// A collection of nodes which form a tree together with the nodes' children.
|
||||
pub type SynTree = SpanVec<SynNode>;
|
||||
/// A collection of nodes which form a tree together with their children.
|
||||
pub type Tree = SpanVec<Node>;
|
||||
|
@ -1,11 +1,8 @@
|
||||
//! Syntax tree nodes.
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A syntax node, which encompasses a single logical entity of parsed source
|
||||
/// code.
|
||||
/// A syntax node, encompassing a single logical entity of parsed source code.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum SynNode {
|
||||
pub enum Node {
|
||||
/// Plain text.
|
||||
Text(String),
|
||||
|
||||
@ -36,7 +33,7 @@ pub struct NodeHeading {
|
||||
/// The section depth (numer of hashtags minus 1).
|
||||
pub level: Spanned<u8>,
|
||||
/// The contents of the heading.
|
||||
pub contents: SynTree,
|
||||
pub contents: Tree,
|
||||
}
|
||||
|
||||
/// A raw block with optional syntax highlighting: `` `raw` ``.
|
||||
|
@ -1,5 +1,3 @@
|
||||
//! Mapping of values to the locations they originate from in source code.
|
||||
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::ops::Range;
|
||||
|
||||
@ -66,12 +64,18 @@ impl<T> Spanned<T> {
|
||||
}
|
||||
|
||||
/// Map the value using a function while keeping the span.
|
||||
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U> {
|
||||
pub fn map<F, U>(self, f: F) -> Spanned<U>
|
||||
where
|
||||
F: FnOnce(T) -> U,
|
||||
{
|
||||
Spanned { v: f(self.v), span: self.span }
|
||||
}
|
||||
|
||||
/// Maps the span while keeping the value.
|
||||
pub fn map_span(mut self, f: impl FnOnce(Span) -> Span) -> Self {
|
||||
pub fn map_span<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(Span) -> Span,
|
||||
{
|
||||
self.span = f(self.span);
|
||||
self
|
||||
}
|
||||
@ -102,7 +106,7 @@ impl<T: Debug> Debug for Spanned<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Locates a slice of source code.
|
||||
/// Bounds of a slice of source code.
|
||||
#[derive(Copy, Clone, Ord, PartialOrd)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct Span {
|
||||
|
@ -1,5 +1,3 @@
|
||||
//! Token definition.
|
||||
|
||||
use crate::geom::Unit;
|
||||
|
||||
/// A minimal semantic entity of source code.
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Test integration of syntax, page setup, box layout and alignment.
|
||||
// Test integration of syntax, library and layouting.
|
||||
|
||||
[page width: 450pt, height: 300pt, margins: 1cm]
|
||||
|
||||
|
@ -19,7 +19,7 @@ use typst::eval::State;
|
||||
use typst::export::pdf;
|
||||
use typst::font::FontLoader;
|
||||
use typst::geom::{Length, Point, Sides, Size};
|
||||
use typst::layout::{BoxLayout, ImageElement, LayoutElement};
|
||||
use typst::layout::{Element, Frame, Image};
|
||||
use typst::parse::{LineMap, Scanner};
|
||||
use typst::shaping::Shaped;
|
||||
use typst::syntax::{Location, Pos, SpanVec, Spanned, WithSpan};
|
||||
@ -134,16 +134,16 @@ fn test(
|
||||
state.page.margins = Sides::uniform(Some(Length::pt(10.0).into()));
|
||||
|
||||
let Pass {
|
||||
output: layouts,
|
||||
output: frames,
|
||||
feedback: Feedback { mut diags, .. },
|
||||
} = typeset(&src, Rc::clone(env), state);
|
||||
diags.sort();
|
||||
|
||||
let env = env.borrow();
|
||||
let canvas = draw(&layouts, &env, 2.0);
|
||||
let canvas = draw(&frames, &env, 2.0);
|
||||
canvas.pixmap.save_png(png_path).unwrap();
|
||||
|
||||
let pdf_data = pdf::export(&layouts, &env);
|
||||
let pdf_data = pdf::export(&frames, &env);
|
||||
fs::write(pdf_path, pdf_data).unwrap();
|
||||
|
||||
let mut ok = true;
|
||||
@ -226,12 +226,12 @@ fn print_diag(diag: &Spanned<Diag>, map: &LineMap) {
|
||||
println!("{}: {}-{}: {}", diag.v.level, start, end, diag.v.message);
|
||||
}
|
||||
|
||||
fn draw(layouts: &[BoxLayout], env: &Env, pixel_per_pt: f32) -> Canvas {
|
||||
fn draw(frames: &[Frame], env: &Env, pixel_per_pt: f32) -> Canvas {
|
||||
let pad = Length::pt(5.0);
|
||||
|
||||
let height = pad + layouts.iter().map(|l| l.size.height + pad).sum::<Length>();
|
||||
let height = pad + frames.iter().map(|l| l.size.height + pad).sum::<Length>();
|
||||
let width = 2.0 * pad
|
||||
+ layouts
|
||||
+ frames
|
||||
.iter()
|
||||
.map(|l| l.size.width)
|
||||
.max_by(|a, b| a.partial_cmp(&b).unwrap())
|
||||
@ -244,7 +244,7 @@ fn draw(layouts: &[BoxLayout], env: &Env, pixel_per_pt: f32) -> Canvas {
|
||||
canvas.pixmap.fill(Color::BLACK);
|
||||
|
||||
let mut origin = Point::new(pad, pad);
|
||||
for layout in layouts {
|
||||
for frame in frames {
|
||||
let mut paint = Paint::default();
|
||||
paint.set_color(Color::WHITE);
|
||||
|
||||
@ -252,26 +252,26 @@ fn draw(layouts: &[BoxLayout], env: &Env, pixel_per_pt: f32) -> Canvas {
|
||||
Rect::from_xywh(
|
||||
origin.x.to_pt() as f32,
|
||||
origin.y.to_pt() as f32,
|
||||
layout.size.width.to_pt() as f32,
|
||||
layout.size.height.to_pt() as f32,
|
||||
frame.size.width.to_pt() as f32,
|
||||
frame.size.height.to_pt() as f32,
|
||||
)
|
||||
.unwrap(),
|
||||
&paint,
|
||||
);
|
||||
|
||||
for &(pos, ref element) in &layout.elements {
|
||||
for &(pos, ref element) in &frame.elements {
|
||||
let pos = origin + pos;
|
||||
match element {
|
||||
LayoutElement::Text(shaped) => {
|
||||
Element::Text(shaped) => {
|
||||
draw_text(&mut canvas, pos, env, shaped);
|
||||
}
|
||||
LayoutElement::Image(image) => {
|
||||
Element::Image(image) => {
|
||||
draw_image(&mut canvas, pos, env, image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
origin.y += layout.size.height + pad;
|
||||
origin.y += frame.size.height + pad;
|
||||
}
|
||||
|
||||
canvas
|
||||
@ -303,7 +303,7 @@ fn draw_text(canvas: &mut Canvas, pos: Point, env: &Env, shaped: &Shaped) {
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, element: &ImageElement) {
|
||||
fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, element: &Image) {
|
||||
let img = &env.resources.loaded::<ImageResource>(element.res);
|
||||
|
||||
let mut pixmap = Pixmap::new(img.buf.width(), img.buf.height()).unwrap();
|
||||
|
Loading…
x
Reference in New Issue
Block a user