Small improvements 🍪

This commit is contained in:
Laurenz 2020-10-04 23:31:35 +02:00
parent 605ab104c5
commit 335fa2d118
21 changed files with 166 additions and 129 deletions

View File

@ -2,7 +2,9 @@
use std::mem;
use super::*;
use super::{Convert, RefKey, ValueDict};
use crate::layout::LayoutContext;
use crate::syntax::{SpanWith, Spanned};
/// A wrapper around a dictionary value that simplifies argument parsing in
/// functions.
@ -21,14 +23,44 @@ impl Args {
{
self.0.v.remove(key).and_then(|entry| {
let span = entry.value.span;
let (t, diag) = T::convert(entry.value);
let (result, diag) = T::convert(entry.value);
if let Some(diag) = diag {
ctx.f.diags.push(diag.span_with(span))
}
t.ok()
result.ok()
})
}
/// This is the same as [`get`], except that it generates an error about a
/// missing argument with the given `name` if the key does not exist.
///
/// [`get`]: #method.get
pub fn need<'a, K, T>(
&mut self,
ctx: &mut LayoutContext,
key: K,
name: &str,
) -> Option<T>
where
K: Into<RefKey<'a>>,
T: Convert,
{
match self.0.v.remove(key) {
Some(entry) => {
let span = entry.value.span;
let (result, diag) = T::convert(entry.value);
if let Some(diag) = diag {
ctx.f.diags.push(diag.span_with(span))
}
result.ok()
}
None => {
ctx.f.diags.push(error!(self.0.span, "missing argument: {}", name));
None
}
}
}
/// Retrieve and remove the first matching positional argument.
pub fn find<T>(&mut self) -> Option<T>
where
@ -104,6 +136,7 @@ impl Args {
#[cfg(test)]
mod tests {
use super::super::{Dict, SpannedEntry, Value};
use super::*;
fn entry(value: Value) -> SpannedEntry<Value> {

View File

@ -4,11 +4,12 @@ use std::ops::Deref;
use fontdock::{FontStretch, FontStyle, FontWeight};
use super::*;
use super::{Value, ValueDict, ValueFunc};
use crate::diag::Diag;
use crate::geom::Linear;
use crate::layout::{Dir, SpecAlign};
use crate::paper::Paper;
use crate::syntax::{Ident, SpanWith, Spanned, SynTree};
/// Types that values can be converted into.
pub trait Convert: Sized {
@ -96,9 +97,9 @@ macro_rules! impl_match {
}
impl_match!(Value, "value", v => v);
impl_match!(Ident, "ident", Value::Ident(v) => v);
impl_match!(Ident, "identifier", Value::Ident(v) => v);
impl_match!(bool, "bool", Value::Bool(v) => v);
impl_match!(i64, "int", Value::Int(v) => v);
impl_match!(i64, "integer", Value::Int(v) => v);
impl_match!(f64, "float",
Value::Int(v) => v as f64,
Value::Float(v) => v,
@ -112,9 +113,9 @@ impl_match!(Linear, "linear",
);
impl_match!(String, "string", Value::Str(v) => v);
impl_match!(SynTree, "tree", Value::Content(v) => v);
impl_match!(ValueDict, "dict", Value::Dict(v) => v);
impl_match!(ValueDict, "dictionary", Value::Dict(v) => v);
impl_match!(ValueFunc, "function", Value::Func(v) => v);
impl_match!(StringLike, "ident or string",
impl_match!(StringLike, "identifier or string",
Value::Ident(Ident(v)) => StringLike(v),
Value::Str(v) => StringLike(v),
);

View File

@ -1,4 +1,4 @@
//! State.
//! Evaluation state.
use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight};

View File

@ -1,4 +1,4 @@
//! Computational values: Syntactical expressions can be evaluated into these.
//! Computational values.
use std::fmt::{self, Debug, Formatter};
use std::ops::Deref;
@ -57,9 +57,9 @@ impl Value {
pub fn ty(&self) -> &'static str {
match self {
Self::None => "none",
Self::Ident(_) => "ident",
Self::Ident(_) => "identifier",
Self::Bool(_) => "bool",
Self::Int(_) => "int",
Self::Int(_) => "integer",
Self::Float(_) => "float",
Self::Relative(_) => "relative",
Self::Length(_) => "length",
@ -88,6 +88,9 @@ impl Spanned<Value> {
/// the value is represented as layoutable content in a reasonable way.
pub fn into_commands(self) -> Vec<Command> {
match self.v {
// Don't print out none values.
Value::None => vec![],
// Pass-through.
Value::Commands(commands) => commands,
Value::Content(tree) => vec![Command::LayoutSyntaxTree(tree)],
@ -109,9 +112,6 @@ impl Spanned<Value> {
commands
}
// Don't print out none values.
Value::None => vec![],
// Format with debug.
val => {
let fmt = format!("{:?}", val);
@ -144,6 +144,14 @@ impl Debug for Value {
}
}
/// A dictionary of values.
///
/// # Example
/// ```typst
/// (false, 12cm, greeting="hi")
/// ```
pub type ValueDict = Dict<SpannedEntry<Value>>;
/// An wrapper around a reference-counted executable function value.
///
/// The dynamic function object is wrapped in an `Rc` to keep [`Value`]
@ -192,11 +200,3 @@ impl Debug for ValueFunc {
f.pad("<function>")
}
}
/// A dictionary of values.
///
/// # Example
/// ```typst
/// (false, 12cm, greeting="hi")
/// ```
pub type ValueDict = Dict<SpannedEntry<Value>>;

View File

@ -26,11 +26,11 @@ use crate::length::Length;
/// The raw _PDF_ is written into the `target` writable, returning the number of
/// bytes written.
pub fn export<W: Write>(
layout: &[BoxLayout],
layouts: &[BoxLayout],
loader: &FontLoader,
target: W,
) -> io::Result<usize> {
PdfExporter::new(layout, loader, target)?.write()
PdfExporter::new(layouts, loader, target)?.write()
}
struct PdfExporter<'a, W: Write> {
@ -42,7 +42,7 @@ struct PdfExporter<'a, W: Write> {
/// tree and so on. These offsets are computed in the beginning and stored
/// here.
offsets: Offsets,
// Font remapping, see below at `remap_fonts`.
// Font remapping, for more information see `remap_fonts`.
to_pdf: HashMap<FaceId, usize>,
to_layout: Vec<FaceId>,
}

View File

@ -12,7 +12,9 @@ use super::*;
/// Performs the line layouting.
pub struct LineLayouter {
/// The context used for line layouting.
ctx: LineContext,
/// The underlying layouter that stacks the finished lines.
stack: StackLayouter,
/// The in-progress line.
run: LineRun,
@ -36,27 +38,6 @@ pub struct LineContext {
pub line_spacing: f64,
}
/// A sequence of boxes with the same alignment. A real line can consist of
/// multiple runs with different alignments.
struct LineRun {
/// The so-far accumulated items of the run.
layouts: Vec<(f64, BoxLayout)>,
/// The summed width and maximal height of the run.
size: Size,
/// The alignment of all layouts in the line.
///
/// When a new run is created the alignment is yet to be determined and
/// `None` as such. Once a layout is added, its alignment decides the
/// alignment for the whole run.
align: Option<LayoutAlign>,
/// The amount of space left by another run on the same line or `None` if
/// this is the only run so far.
usable: Option<f64>,
/// The spacing state. This influences how new spacing is handled, e.g. hard
/// spacing may override soft spacing.
last_spacing: LastSpacing,
}
impl LineLayouter {
/// Create a new line layouter.
pub fn new(ctx: LineContext) -> Self {
@ -259,6 +240,27 @@ impl LineLayouter {
}
}
/// A sequence of boxes with the same alignment. A real line can consist of
/// multiple runs with different alignments.
struct LineRun {
/// The so-far accumulated items of the run.
layouts: Vec<(f64, BoxLayout)>,
/// The summed width and maximal height of the run.
size: Size,
/// The alignment of all layouts in the line.
///
/// When a new run is created the alignment is yet to be determined and
/// `None` as such. Once a layout is added, its alignment decides the
/// alignment for the whole run.
align: Option<LayoutAlign>,
/// The amount of space left by another run on the same line or `None` if
/// this is the only run so far.
usable: Option<f64>,
/// The spacing state. This influences how new spacing is handled, e.g. hard
/// spacing may override soft spacing.
last_spacing: LastSpacing,
}
impl LineRun {
fn new() -> Self {
Self {

View File

@ -46,7 +46,6 @@ pub async fn layout(
};
let layouts = layout_tree(&tree, &mut ctx).await;
Pass::new(layouts, ctx.f)
}
@ -177,12 +176,11 @@ pub enum Command {
SetTextState(TextState),
/// Update the page style.
SetPageState(PageState),
/// Update the alignment for future boxes added to this layouting process.
SetAlignment(LayoutAlign),
/// Update the layouting system along which future boxes will be laid
/// out. This ends the current line.
SetSystem(LayoutSystem),
/// Update the alignment for future boxes added to this layouting process.
SetAlignment(LayoutAlign),
}
/// Defines how spacing interacts with surrounding spacing.
@ -211,26 +209,3 @@ impl SpacingKind {
/// The standard spacing kind used for word spacing.
pub const WORD: Self = Self::Soft(1);
}
/// The spacing kind of the most recently inserted item in a layouting process.
///
/// Since the last inserted item may not be spacing at all, this can be `None`.
#[derive(Debug, Copy, Clone, PartialEq)]
enum LastSpacing {
/// The last item was hard spacing.
Hard,
/// The last item was soft spacing with the given width and level.
Soft(f64, u32),
/// The last item wasn't spacing.
None,
}
impl LastSpacing {
/// The width of the soft space if this is a soft space or zero otherwise.
fn soft_or_zero(self) -> f64 {
match self {
LastSpacing::Soft(space, _) => space,
_ => 0.0,
}
}
}

View File

@ -23,7 +23,9 @@ use super::*;
/// Performs the stack layouting.
pub struct StackLayouter {
/// The context used for stack layouting.
ctx: StackContext,
/// The finished layouts.
layouts: Vec<BoxLayout>,
/// The in-progress space.
space: Space,
@ -45,30 +47,6 @@ pub struct StackContext {
pub repeat: bool,
}
/// A layout space composed of subspaces which can have different systems and
/// alignments.
struct Space {
/// The index of this space in `ctx.spaces`.
index: usize,
/// Whether to include a layout for this space even if it would be empty.
hard: bool,
/// The so-far accumulated layouts.
layouts: Vec<(LayoutSystem, BoxLayout)>,
/// The specialized size of this space.
size: Size,
/// The specialized remaining space.
usable: Size,
/// The specialized extra-needed size to affect the size at all.
extra: Size,
/// Dictate which alignments for new boxes are still allowed and which
/// require a new space to be started. For example, after an `End`-aligned
/// item, no `Start`-aligned one can follow.
rulers: Sides<GenAlign>,
/// The spacing state. This influences how new spacing is handled, e.g. hard
/// spacing may override soft spacing.
last_spacing: LastSpacing,
}
impl StackLayouter {
/// Create a new stack layouter.
pub fn new(ctx: StackContext) -> Self {
@ -387,6 +365,30 @@ impl StackLayouter {
}
}
/// A layout space composed of subspaces which can have different systems and
/// alignments.
struct Space {
/// The index of this space in `ctx.spaces`.
index: usize,
/// Whether to include a layout for this space even if it would be empty.
hard: bool,
/// The so-far accumulated layouts.
layouts: Vec<(LayoutSystem, BoxLayout)>,
/// The specialized size of this space.
size: Size,
/// The specialized remaining space.
usable: Size,
/// The specialized extra-needed size to affect the size at all.
extra: Size,
/// Dictate which alignments for new boxes are still allowed and which
/// require a new space to be started. For example, after an `End`-aligned
/// item, no `Start`-aligned one can follow.
rulers: Sides<GenAlign>,
/// The spacing state. This influences how new spacing is handled, e.g. hard
/// spacing may override soft spacing.
last_spacing: LastSpacing,
}
impl Space {
fn new(index: usize, hard: bool, usable: Size) -> Self {
Self {
@ -401,3 +403,26 @@ impl Space {
}
}
}
/// The spacing kind of the most recently inserted item in a layouting process.
///
/// Since the last inserted item may not be spacing at all, this can be `None`.
#[derive(Debug, Copy, Clone, PartialEq)]
pub(crate) enum LastSpacing {
/// The last item was hard spacing.
Hard,
/// The last item was soft spacing with the given width and level.
Soft(f64, u32),
/// The last item wasn't spacing.
None,
}
impl LastSpacing {
/// The width of the soft space if this is a soft space or zero otherwise.
fn soft_or_zero(self) -> f64 {
match self {
LastSpacing::Soft(space, _) => space,
_ => 0.0,
}
}
}

View File

@ -3,9 +3,7 @@
use super::*;
use crate::eval::Eval;
use crate::shaping;
use crate::syntax::{
Deco, Expr, NodeHeading, NodeRaw, Span, SpanWith, Spanned, SynNode, SynTree,
};
use crate::syntax::*;
use crate::DynFuture;
/// Layout a syntax tree in a given context.

View File

@ -2,24 +2,25 @@
//!
//! # Steps
//! - **Parsing:** The parsing step first transforms a plain string into an
//! [iterator of tokens][tokens]. Then, a [parser] constructs a syntax tree
//! from the token stream. The structures describing the tree can be found in
//! the [syntax] module.
//! [iterator of tokens][tokens]. This token stream is [parsed] into a [syntax
//! tree]. The structures describing the tree can be found in the [ast]
//! module.
//! - **Layouting:** The next step is to transform the syntax tree into a
//! portable representation of the typesetted document. Types for these can be
//! found in the [layout] module. A finished layout ready for exporting is a
//! [`MultiLayout`] consisting of multiple boxes (or pages).
//! portable representation of the typesetted document. The final output
//! consists of a vector of [`BoxLayouts`] (corresponding to pages), ready for
//! exporting.
//! - **Exporting:** The finished layout can then be exported into a supported
//! format. Submodules for these formats are located in the [export] module.
//! Currently, the only supported output format is [_PDF_].
//!
//! [tokens]: parse/struct.Tokens.html
//! [parser]: parse/fn.parse.html
//! [syntax]: syntax/index.html
//! [tokens]: parsing/struct.Tokens.html
//! [parsed]: parsing/fn.parse.html
//! [syntax tree]: syntax/ast/type.SynTree.html
//! [ast]: syntax/ast/index.html
//! [layout]: layout/index.html
//! [`BoxLayouts`]: layout/struct.BoxLayout.html
//! [export]: export/index.html
//! [_PDF_]: export/pdf/index.html
//! [`MultiLayout`]: layout/type.MultiLayout.html
#[macro_use]
pub mod diag;
@ -56,7 +57,7 @@ pub async fn typeset(
) -> Pass<Vec<BoxLayout>> {
let parsed = parse::parse(src);
let layouted = layout::layout(&parsed.output, state, loader).await;
let feedback = Feedback::merge(parsed.feedback, layouted.feedback);
let feedback = Feedback::join(parsed.feedback, layouted.feedback);
Pass::new(layouted.output, feedback)
}
@ -108,8 +109,8 @@ impl Feedback {
Self { diags: vec![], decos: vec![] }
}
/// Merged two feedbacks into one.
pub fn merge(mut a: Self, b: Self) -> Self {
/// Merge two feedbacks into one.
pub fn join(mut a: Self, b: Self) -> Self {
a.extend(b);
a
}
@ -120,7 +121,7 @@ impl Feedback {
self.decos.extend(more.decos);
}
/// Add more feedback whose spans are local and need to be offset by an
/// Add more feedback whose spans are local and need to be translated by an
/// `offset` to be correct in this feedback's context.
pub fn extend_offset(&mut self, more: Self, offset: Pos) {
self.diags.extend(more.diags.offset(offset));

View File

@ -1,4 +1,4 @@
use super::*;
use crate::prelude::*;
/// `align`: Align content along the layouting axes.
///
@ -16,7 +16,6 @@ use super::*;
/// There may not be two alignment specifications for the same axis.
pub async fn align(mut args: Args, ctx: &mut LayoutContext) -> Value {
let body = args.find::<SynTree>();
let h = args.get::<_, Spanned<SpecAlign>>(ctx, "horizontal");
let v = args.get::<_, Spanned<SpecAlign>>(ctx, "vertical");
let pos = args.find_all::<Spanned<SpecAlign>>();

View File

@ -1,5 +1,5 @@
use super::*;
use crate::geom::Linear;
use crate::prelude::*;
/// `box`: Layouts its contents into a box.
///

View File

@ -1,11 +1,11 @@
use super::*;
use crate::color::RgbaColor;
use crate::prelude::*;
/// `rgb`: Create an RGB(A) color.
pub async fn rgb(mut args: Args, ctx: &mut LayoutContext) -> Value {
let r = args.get::<_, Spanned<i64>>(ctx, 0);
let g = args.get::<_, Spanned<i64>>(ctx, 1);
let b = args.get::<_, Spanned<i64>>(ctx, 2);
let r = args.need::<_, Spanned<i64>>(ctx, 0, "red value");
let g = args.need::<_, Spanned<i64>>(ctx, 1, "green value");
let b = args.need::<_, Spanned<i64>>(ctx, 2, "blue value");
let a = args.get::<_, Spanned<i64>>(ctx, 3);
args.done(ctx);

View File

@ -1,8 +1,8 @@
use fontdock::{FontStretch, FontStyle, FontWeight};
use super::*;
use crate::eval::StringLike;
use crate::geom::Linear;
use crate::prelude::*;
/// `font`: Configure the font.
///

View File

@ -15,7 +15,6 @@ pub use page::*;
pub use spacing::*;
use crate::eval::{Scope, ValueFunc};
use crate::prelude::*;
macro_rules! std {
($($name:literal => $func:expr),* $(,)?) => {

View File

@ -1,9 +1,9 @@
use std::mem;
use super::*;
use crate::eval::Absolute;
use crate::geom::{Linear, Sides};
use crate::paper::{Paper, PaperClass};
use crate::prelude::*;
/// `page`: Configure pages.
///

View File

@ -1,6 +1,6 @@
use super::*;
use crate::geom::Linear;
use crate::layout::SpacingKind;
use crate::prelude::*;
/// `h`: Add horizontal spacing.
///
@ -19,7 +19,7 @@ pub async fn v(args: Args, ctx: &mut LayoutContext) -> Value {
}
fn spacing(mut args: Args, ctx: &mut LayoutContext, axis: SpecAxis) -> Value {
let spacing = args.get::<_, Linear>(ctx, 0);
let spacing = args.need::<_, Linear>(ctx, 0, "spacing");
args.done(ctx);
Value::Commands(if let Some(spacing) = spacing {

View File

@ -37,7 +37,7 @@ pub enum PaperClass {
}
impl PaperClass {
/// The default margin ratios for this page class.
/// The default margins for this page class.
pub fn default_margins(self) -> Sides<Linear> {
let f = Linear::rel;
let s = |l, r, t, b| Sides::new(f(l), f(r), f(t), f(b));

View File

@ -89,6 +89,7 @@ macro_rules! Call {
}};
($($tts:tt)*) => { Expr::Call(Call![@$($tts)*]) };
}
fn Unary(op: impl Into<Spanned<UnOp>>, expr: impl Into<Spanned<Expr>>) -> Expr {
Expr::Unary(ExprUnary {
op: op.into(),

View File

@ -21,6 +21,9 @@ pub struct ExprCall {
/// The name of the function.
pub name: Spanned<Ident>,
/// The arguments to the function.
///
/// In case of a bracketed invocation with a body, the body is _not_
/// included in the span for the sake of clearer error messages.
pub args: Spanned<LitDict>,
}

View File

@ -8,4 +8,4 @@ pub use expr::*;
pub use lit::*;
pub use tree::*;
use super::*;
use super::{Ident, SpanVec, Spanned};