diff --git a/bench/Cargo.toml b/bench/Cargo.toml index 25a112a16..36f40b8c9 100644 --- a/bench/Cargo.toml +++ b/bench/Cargo.toml @@ -5,10 +5,10 @@ authors = ["The Typst Project Developers"] edition = "2018" publish = false -[dependencies] +[dev-dependencies] +criterion = "0.3" fontdock = { path = "../../fontdock" } typst = { path = ".." } -criterion = "0.3" [[bench]] name = "typst" diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 6d8f66ca6..bab93a058 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -25,7 +25,7 @@ use crate::diag::{Deco, Feedback, Pass}; use crate::env::SharedEnv; use crate::geom::{BoxAlign, Dir, Flow, Gen, Length, Linear, Relative, Sides, Size}; use crate::layout::{ - Document, Expansion, LayoutNode, Pad, Pages, Par, Softness, Spacing, Stack, Text, + Document, Expansion, LayoutNode, Pad, Pages, Par, Spacing, Stack, Text, }; use crate::syntax::*; @@ -35,9 +35,9 @@ use crate::syntax::*; /// evaluation. pub fn eval(tree: &SynTree, env: SharedEnv, state: State) -> Pass { let mut ctx = EvalContext::new(env, state); - ctx.start_page_group(false); + ctx.start_page_group(Softness::Hard); tree.eval(&mut ctx); - ctx.end_page_group(true); + ctx.end_page_group(|s| s == Softness::Hard); ctx.finish() } @@ -117,28 +117,35 @@ impl EvalContext { /// Start a page group based on the active page state. /// - /// If both this `hard` and the one in the matching call to `end_page_group` - /// are false, empty page runs will be omitted from the output. + /// 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, hard: bool) { + 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, - hard, + softness, }); self.start_par_group(); } - /// End a page group and push it to the finished page runs. + /// 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, hard: bool) { + pub fn end_page_group( + &mut self, + keep_empty: impl FnOnce(Softness) -> bool, + ) -> Softness { self.end_par_group(); let (group, children) = self.end_group::(); - if hard || group.hard || !children.is_empty() { + if !children.is_empty() || keep_empty(group.softness) { self.runs.push(Pages { size: group.size, child: LayoutNode::dynamic(Pad { @@ -152,6 +159,7 @@ impl EvalContext { }), }) } + group.softness } /// Start a content group. @@ -273,7 +281,7 @@ struct PageGroup { padding: Sides, flow: Flow, align: BoxAlign, - hard: bool, + softness: Softness, } /// A group for generic content. @@ -286,6 +294,15 @@ struct ParGroup { line_spacing: Length, } +/// Defines how items interact with surrounding items. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Softness { + /// Soft items can be skipped in some circumstances. + Soft, + /// Hard items are always retained. + Hard, +} + /// Evaluate an item. /// /// _Note_: Evaluation is not necessarily pure, it may change the active state. diff --git a/src/layout/spacing.rs b/src/layout/spacing.rs index 1ba3b54a2..c9a9c233e 100644 --- a/src/layout/spacing.rs +++ b/src/layout/spacing.rs @@ -1,14 +1,21 @@ use std::fmt::{self, Debug, Formatter}; use super::*; +use crate::eval::Softness; /// A spacing node. #[derive(Copy, Clone, PartialEq)] pub struct Spacing { /// The amount of spacing to insert. pub amount: Length, - /// Spacing interaction, see [`Softness`]'s documentation for more - /// information. + /// Defines how spacing interacts with surrounding spacing. + /// + /// Hard spacing assures that a fixed amount of spacing will always be + /// inserted. Soft spacing will be consumed by previous soft spacing or + /// neighbouring hard spacing and can be used to insert overridable spacing, + /// e.g. between words or paragraphs. + /// + /// This field is only used in evaluation, not in layouting. pub softness: Softness, } @@ -32,18 +39,3 @@ impl From for LayoutNode { Self::Spacing(spacing) } } - -/// Defines how spacing interacts with surrounding spacing. -/// -/// Hard spacing assures that a fixed amount of spacing will always be inserted. -/// Soft spacing will be consumed by previous soft spacing or neighbouring hard -/// spacing and can be used to insert overridable spacing, e.g. between words or -/// paragraphs. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum Softness { - /// Soft spacing is not laid out if it directly follows other soft spacing - /// or if it touches hard spacing. - Soft, - /// Hard spacing is always laid out and consumes surrounding soft spacing. - Hard, -} diff --git a/src/library/layout.rs b/src/library/layout.rs index 4a787f1ec..36a208219 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -1,7 +1,8 @@ use std::fmt::{self, Display, Formatter}; +use crate::eval::Softness; use crate::geom::{Length, Linear}; -use crate::layout::{Expansion, Fixed, Softness, Spacing, Stack}; +use crate::layout::{Expansion, Fixed, Spacing, Stack}; use crate::paper::{Paper, PaperClass}; use crate::prelude::*; @@ -184,8 +185,8 @@ pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value { let body = args.find::().unwrap_or_default(); let width = args.get::<_, Linear>(ctx, "width"); let height = args.get::<_, Linear>(ctx, "height"); - let main = args.get::<_, Spanned>(ctx, "main"); - let cross = args.get::<_, Spanned>(ctx, "cross"); + let main = args.get::<_, Spanned>(ctx, "main-dir"); + let cross = args.get::<_, Spanned>(ctx, "cross-dir"); ctx.set_flow(Gen::new(main, cross)); args.done(ctx); @@ -269,7 +270,7 @@ pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value { let snapshot = ctx.state.clone(); let body = args.find::(); - if let Some(paper) = args.find::() { + if let Some(paper) = args.get::<_, Paper>(ctx, 0) { ctx.state.page.class = paper.class; ctx.state.page.size = paper.size(); } @@ -309,21 +310,24 @@ pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value { std::mem::swap(&mut size.width, &mut size.height); } - let main = args.get::<_, Spanned>(ctx, "main"); - let cross = args.get::<_, Spanned>(ctx, "cross"); + let main = args.get::<_, Spanned>(ctx, "main-dir"); + let cross = args.get::<_, Spanned>(ctx, "cross-dir"); ctx.set_flow(Gen::new(main, cross)); args.done(ctx); + let mut softness = ctx.end_page_group(|_| false); + if let Some(body) = body { - ctx.end_page_group(false); - ctx.start_page_group(true); + // TODO: Restrict body to a single page? + ctx.start_page_group(Softness::Hard); body.eval(ctx); + ctx.end_page_group(|s| s == Softness::Hard); ctx.state = snapshot; + softness = Softness::Soft; } - ctx.end_page_group(false); - ctx.start_page_group(false); + ctx.start_page_group(softness); Value::None } @@ -331,7 +335,7 @@ pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value { /// `pagebreak`: Start a new page. pub fn pagebreak(args: Args, ctx: &mut EvalContext) -> Value { args.done(ctx); - ctx.end_page_group(false); - ctx.start_page_group(true); + ctx.end_page_group(|_| true); + ctx.start_page_group(Softness::Hard); Value::None } diff --git a/tests/ref/image-error.png b/tests/ref/image-error.png deleted file mode 100644 index 812a3758c..000000000 Binary files a/tests/ref/image-error.png and /dev/null differ diff --git a/tests/ref/image-formats.png b/tests/ref/image-formats.png new file mode 100644 index 000000000..45b23276b Binary files /dev/null and b/tests/ref/image-formats.png differ diff --git a/tests/ref/image-jpeg.png b/tests/ref/image-jpeg.png deleted file mode 100644 index ef9e74cbd..000000000 Binary files a/tests/ref/image-jpeg.png and /dev/null differ diff --git a/tests/ref/image-png.png b/tests/ref/image-png.png deleted file mode 100644 index 4e0818d21..000000000 Binary files a/tests/ref/image-png.png and /dev/null differ diff --git a/tests/ref/page-body.png b/tests/ref/page-body.png new file mode 100644 index 000000000..a3b3d7264 Binary files /dev/null and b/tests/ref/page-body.png differ diff --git a/tests/ref/page-dirs.png b/tests/ref/page-dirs.png new file mode 100644 index 000000000..2bf23ebcc Binary files /dev/null and b/tests/ref/page-dirs.png differ diff --git a/tests/ref/page-metrics.png b/tests/ref/page-metrics.png new file mode 100644 index 000000000..2e8d626db Binary files /dev/null and b/tests/ref/page-metrics.png differ diff --git a/tests/typ/example-coma.typ b/tests/typ/example-coma.typ index f841a1221..d41032c1f 100644 --- a/tests/typ/example-coma.typ +++ b/tests/typ/example-coma.typ @@ -1,4 +1,4 @@ -// Small integration test of syntax, page setup, box layout and alignment. +// Test integration of syntax, page setup, box layout and alignment. [page: width=450pt, height=300pt, margins=1cm] diff --git a/tests/typ/image-error.typ b/tests/typ/image-error.typ index 4fde4ab29..9a7f2c400 100644 --- a/tests/typ/image-error.typ +++ b/tests/typ/image-error.typ @@ -1,5 +1,8 @@ -// error: 5:9-5:30 failed to load image +// Test error cases of the `image` function. + +// compare-ref: false // error: 8:9-8:30 failed to load image +// error: 11:9-11:30 failed to load image // File does not exist. [image: "path/does/not/exist"] diff --git a/tests/typ/image-fit.typ b/tests/typ/image-fit.typ index b735f058e..eca7c1e45 100644 --- a/tests/typ/image-fit.typ +++ b/tests/typ/image-fit.typ @@ -1,3 +1,5 @@ +// Test configuring the size and fitting behaviour of images. + // Fit to width of page. [image: "res/rhino.png"] diff --git a/tests/typ/image-formats.typ b/tests/typ/image-formats.typ new file mode 100644 index 000000000..36b991d01 --- /dev/null +++ b/tests/typ/image-formats.typ @@ -0,0 +1,8 @@ +// Test loading different image formats. + +// Load an RGBA PNG image. +[image: "res/rhino.png"] +[pagebreak] + +// Load an RGB JPEG image. +[image: "res/tiger.jpg"] diff --git a/tests/typ/image-jpeg.typ b/tests/typ/image-jpeg.typ deleted file mode 100644 index 48cf1a0dc..000000000 --- a/tests/typ/image-jpeg.typ +++ /dev/null @@ -1,2 +0,0 @@ -// Load an RGB JPEG image. -[image: "res/tiger.jpg"] diff --git a/tests/typ/image-png.typ b/tests/typ/image-png.typ deleted file mode 100644 index 482591e92..000000000 --- a/tests/typ/image-png.typ +++ /dev/null @@ -1,2 +0,0 @@ -// Load an RGBA PNG image. -[image: "res/rhino.png"] diff --git a/tests/typ/page-body.typ b/tests/typ/page-body.typ new file mode 100644 index 000000000..6067dcfee --- /dev/null +++ b/tests/typ/page-body.typ @@ -0,0 +1,11 @@ +// Test a combination of pages with bodies and normal content. + +[page: height=50pt] + +[page][First] +[page][Second] +[pagebreak] +Fourth +[page][Fifth] +Sixth +[page][Seventh and last] diff --git a/tests/typ/page-dirs.typ b/tests/typ/page-dirs.typ new file mode 100644 index 000000000..09ba4e9e8 --- /dev/null +++ b/tests/typ/page-dirs.typ @@ -0,0 +1,5 @@ +// Test changing the layouting directions of pages. + +[page: main-dir=btt, cross-dir=rtl] + +Right to left! diff --git a/tests/typ/page-error.typ b/tests/typ/page-error.typ new file mode 100644 index 000000000..dee5c449d --- /dev/null +++ b/tests/typ/page-error.typ @@ -0,0 +1,11 @@ +// Test error cases of the `page` function. + +// compare-ref: false +// error: 8:8-8:19 invalid paper +// error: 11:17-11:20 aligned axis + +// Invalid paper. +[page: nonexistant] + +// Aligned axes. +[page: main-dir=ltr] diff --git a/tests/typ/page-metrics.typ b/tests/typ/page-metrics.typ new file mode 100644 index 000000000..6c7bd4611 --- /dev/null +++ b/tests/typ/page-metrics.typ @@ -0,0 +1,25 @@ +// Test configuring page sizes and margins. + +// Set width. +[page: width=50pt][High] + +// Set height. +[page: height=50pt][Wide] + +// Set all margins at once. +[page: margins=40pt][ + [align: top, left][TL] + [align: bottom, right][BR] +] + +// Set individual margins. +[page: left=0pt >> align: left][Left] +[page: right=0pt >> align: right][Right] +[page: top=0pt >> align: top][Top] +[page: bottom=0pt >> align: bottom][Bottom] + +// Ensure that specific margins override general margins. +[page: margins=0pt, left=40pt][Overriden] + +// Flip the page. +[page: a10, flip=true][Flipped] diff --git a/tests/typeset.rs b/tests/typeset.rs index 012aff637..5f1085858 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -127,7 +127,7 @@ fn test( let src = fs::read_to_string(src_path).unwrap(); let map = LineMap::new(&src); - let ref_diags = parse_diags(&src, &map); + let (ref_diags, compare_ref) = parse_metadata(&src, &map); let mut state = State::default(); state.page.size = Size::uniform(Length::pt(120.0)); @@ -167,14 +167,16 @@ fn test( } } - if let Ok(ref_pixmap) = Pixmap::load_png(&ref_path) { - if canvas.pixmap != ref_pixmap { - println!(" Does not match reference image. ❌"); + if compare_ref { + if let Ok(ref_pixmap) = Pixmap::load_png(&ref_path) { + if canvas.pixmap != ref_pixmap { + println!(" Does not match reference image. ❌"); + ok = false; + } + } else { + println!(" Failed to open reference image. ❌"); ok = false; } - } else { - println!(" Failed to open reference image. ❌"); - ok = false; } if ok { @@ -184,10 +186,13 @@ fn test( ok } -fn parse_diags(src: &str, map: &LineMap) -> SpanVec { +fn parse_metadata(src: &str, map: &LineMap) -> (SpanVec, bool) { let mut diags = vec![]; + let mut compare_ref = true; for line in src.lines() { + compare_ref &= !line.starts_with("// compare-ref: false"); + let (level, rest) = if let Some(rest) = line.strip_prefix("// error: ") { (Level::Error, rest) } else if let Some(rest) = line.strip_prefix("// warning: ") { @@ -211,7 +216,8 @@ fn parse_diags(src: &str, map: &LineMap) -> SpanVec { } diags.sort(); - diags + + (diags, compare_ref) } fn print_diag(diag: &Spanned, map: &LineMap) {