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