Move around library types 🚚

This commit is contained in:
Laurenz 2021-03-10 17:22:44 +01:00
parent bbb9ed07ff
commit b0446cbdd1
20 changed files with 499 additions and 483 deletions

170
src/library/align.rs Normal file
View File

@ -0,0 +1,170 @@
use super::*;
/// `align`: Align content along the layouting axes.
///
/// Which axis an alignment should apply to (main or cross) is inferred from
/// either the argument itself (for anything other than `center`) or from the
/// second argument if present, defaulting to the cross axis for a single
/// `center` alignment.
///
/// # Positional arguments
/// - Alignments: variadic, of type `alignment`.
/// - Body: optional, of type `template`.
///
/// # Named arguments
/// - Horizontal alignment: `horizontal`, of type `alignment`.
/// - Vertical alignment: `vertical`, of type `alignment`.
///
/// # Relevant types and constants
/// - Type `alignment`
/// - `left`
/// - `right`
/// - `top`
/// - `bottom`
/// - `center`
pub fn align(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
let first = args.find(ctx);
let second = args.find(ctx);
let hor = args.get(ctx, "horizontal");
let ver = args.get(ctx, "vertical");
let body = args.find::<ValueTemplate>(ctx);
Value::template("align", move |ctx| {
let snapshot = ctx.state.clone();
let mut had = Gen::uniform(false);
let mut had_center = false;
// Infer the axes alignments belong to.
for (axis, Spanned { v: arg, span }) in first
.into_iter()
.chain(second.into_iter())
.map(|arg: Spanned<Alignment>| (arg.v.axis(), arg))
.chain(hor.into_iter().map(|arg| (Some(SpecAxis::Horizontal), arg)))
.chain(ver.into_iter().map(|arg| (Some(SpecAxis::Vertical), arg)))
{
// Check whether we know which axis this alignment belongs to.
if let Some(axis) = axis {
// We know the axis.
let gen_axis = axis.switch(ctx.state.dirs);
let gen_align = arg.switch(ctx.state.dirs);
if arg.axis().map_or(false, |a| a != axis) {
ctx.diag(error!(span, "invalid alignment for {} axis", axis));
} else if had.get(gen_axis) {
ctx.diag(error!(span, "duplicate alignment for {} axis", axis));
} else {
*ctx.state.align.get_mut(gen_axis) = gen_align;
*had.get_mut(gen_axis) = true;
}
} else {
// We don't know the axis: This has to be a `center` alignment for a
// positional argument.
debug_assert_eq!(arg, Alignment::Center);
if had.main && had.cross {
ctx.diag(error!(span, "duplicate alignment"));
} else if had_center {
// Both this and the previous one are unspecified `center`
// alignments. Both axes should be centered.
ctx.state.align.main = Align::Center;
ctx.state.align.cross = Align::Center;
had = Gen::uniform(true);
} else {
had_center = true;
}
}
// If we we know the other alignment, we can handle the unspecified
// `center` alignment.
if had_center && (had.main || had.cross) {
if had.main {
ctx.state.align.cross = Align::Center;
had.cross = true;
} else {
ctx.state.align.main = Align::Center;
had.main = true;
}
had_center = false;
}
}
// If `had_center` wasn't flushed by now, it's the only argument and then we
// default to applying it to the cross axis.
if had_center {
ctx.state.align.cross = Align::Center;
}
if ctx.state.align.main != snapshot.align.main {
ctx.end_par_group();
ctx.start_par_group();
}
if let Some(body) = &body {
body.exec(ctx);
ctx.state = snapshot;
}
})
}
/// An alignment argument.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(super) enum Alignment {
Left,
Center,
Right,
Top,
Bottom,
}
impl Alignment {
/// The specific axis this alignment refers to.
fn axis(self) -> Option<SpecAxis> {
match self {
Self::Left => Some(SpecAxis::Horizontal),
Self::Right => Some(SpecAxis::Horizontal),
Self::Top => Some(SpecAxis::Vertical),
Self::Bottom => Some(SpecAxis::Vertical),
Self::Center => None,
}
}
}
impl Switch for Alignment {
type Other = Align;
fn switch(self, dirs: LayoutDirs) -> Self::Other {
let get = |dir: Dir, at_positive_start| {
if dir.is_positive() == at_positive_start {
Align::Start
} else {
Align::End
}
};
let dirs = dirs.switch(dirs);
match self {
Self::Left => get(dirs.horizontal, true),
Self::Right => get(dirs.horizontal, false),
Self::Top => get(dirs.vertical, true),
Self::Bottom => get(dirs.vertical, false),
Self::Center => Align::Center,
}
}
}
impl Display for Alignment {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Left => "left",
Self::Center => "center",
Self::Right => "right",
Self::Top => "top",
Self::Bottom => "bottom",
})
}
}
typify! {
Alignment: "alignment",
}

62
src/library/base.rs Normal file
View File

@ -0,0 +1,62 @@
use crate::color::{Color, RgbaColor};
use crate::pretty::pretty;
use super::*;
/// `repr`: Get the string representation of a value.
///
/// # Positional arguments
/// - Any value.
///
/// # Return value
/// The string representation of the value.
pub fn repr(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
match args.require::<Value>(ctx, "value") {
Some(value) => pretty(&value).into(),
None => Value::Error,
}
}
/// `rgb`: Create an RGB(A) color.
///
/// # Positional arguments
/// - Red component: of type `float`, between 0.0 and 1.0.
/// - Green component: of type `float`, between 0.0 and 1.0.
/// - Blue component: of type `float`, between 0.0 and 1.0.
/// - Alpha component: optional, of type `float`, between 0.0 and 1.0.
pub fn rgb(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
let r = args.require(ctx, "red component");
let g = args.require(ctx, "green component");
let b = args.require(ctx, "blue component");
let a = args.find(ctx);
let mut clamp = |component: Option<Spanned<f64>>, default| {
component.map_or(default, |c| {
if c.v < 0.0 || c.v > 1.0 {
ctx.diag(warning!(c.span, "should be between 0.0 and 1.0"));
}
(c.v.max(0.0).min(1.0) * 255.0).round() as u8
})
};
Value::Color(Color::Rgba(RgbaColor::new(
clamp(r, 0),
clamp(g, 0),
clamp(b, 0),
clamp(a, 255),
)))
}
/// `type`: Find out the name of a value's type.
///
/// # Positional arguments
/// - Any value.
///
/// # Return value
/// The name of the value's type as a string.
pub fn type_(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
match args.require::<Value>(ctx, "value") {
Some(value) => value.type_name().into(),
None => Value::Error,
}
}

View File

@ -1,30 +0,0 @@
use crate::prelude::*;
use crate::pretty::pretty;
/// `type`: Find out the name of a value's type.
///
/// # Positional arguments
/// - Any value.
///
/// # Return value
/// The name of the value's type as a string.
pub fn type_(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
match args.require::<Value>(ctx, "value") {
Some(value) => value.type_name().into(),
None => Value::Error,
}
}
/// `repr`: Get the string representation of a value.
///
/// # Positional arguments
/// - Any value.
///
/// # Return value
/// The string representation of the value.
pub fn repr(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
match args.require::<Value>(ctx, "value") {
Some(value) => pretty(&value).into(),
None => Value::Error,
}
}

View File

@ -1,15 +1,13 @@
use std::fmt::{self, Display, Formatter};
use fontdock::{FontStretch, FontStyle, FontWeight}; use fontdock::{FontStretch, FontStyle, FontWeight};
use crate::color::{Color, RgbaColor}; use super::*;
use crate::prelude::*;
/// `font`: Configure the font. /// `font`: Configure the font.
/// ///
/// # Positional arguments /// # Positional arguments
/// - Font size: optional, of type `linear` relative to current font size. /// - Font size: optional, of type `linear` relative to current font size.
/// - Font families: variadic, of type `font-family`. /// - Font families: variadic, of type `font-family`.
/// - Body: optional, of type `template`.
/// ///
/// # Named arguments /// # Named arguments
/// - Font Style: `style`, of type `font-style`. /// - Font Style: `style`, of type `font-style`.
@ -121,7 +119,7 @@ struct FontFamilies(Vec<FontFamily>);
/// A single font family. /// A single font family.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum FontFamily { pub(super) enum FontFamily {
Serif, Serif,
SansSerif, SansSerif,
Monospace, Monospace,
@ -183,33 +181,3 @@ typify! {
typify! { typify! {
FontStretch: "font stretch" FontStretch: "font stretch"
} }
/// `rgb`: Create an RGB(A) color.
///
/// # Positional arguments
/// - Red component: of type `float`, between 0.0 and 1.0.
/// - Green component: of type `float`, between 0.0 and 1.0.
/// - Blue component: of type `float`, between 0.0 and 1.0.
/// - Alpha component: optional, of type `float`, between 0.0 and 1.0.
pub fn rgb(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
let r = args.require(ctx, "red component");
let g = args.require(ctx, "green component");
let b = args.require(ctx, "blue component");
let a = args.find(ctx);
let mut clamp = |component: Option<Spanned<f64>>, default| {
component.map_or(default, |c| {
if c.v < 0.0 || c.v > 1.0 {
ctx.diag(warning!(c.span, "should be between 0.0 and 1.0"));
}
(c.v.max(0.0).min(1.0) * 255.0).round() as u8
})
};
Value::Color(Color::Rgba(RgbaColor::new(
clamp(r, 0),
clamp(g, 0),
clamp(b, 0),
clamp(a, 255),
)))
}

View File

@ -1,8 +1,8 @@
use image::GenericImageView; use ::image::GenericImageView;
use super::*;
use crate::env::{ImageResource, ResourceId}; use crate::env::{ImageResource, ResourceId};
use crate::layout::*; use crate::layout::*;
use crate::prelude::*;
/// `image`: Insert an image. /// `image`: Insert an image.
/// ///

View File

@ -1,378 +0,0 @@
use std::fmt::{self, Display, Formatter};
use crate::exec::Softness;
use crate::layout::{Expansion, Fill, NodeBackground, NodeFixed, NodeSpacing, NodeStack};
use crate::paper::{Paper, PaperClass};
use crate::prelude::*;
/// `align`: Align content along the layouting axes.
///
/// Which axis an alignment should apply to (main or cross) is inferred from
/// either the argument itself (for anything other than `center`) or from the
/// second argument if present, defaulting to the cross axis for a single
/// `center` alignment.
///
/// # Positional arguments
/// - Alignments: variadic, of type `alignment`.
///
/// # Named arguments
/// - Horizontal alignment: `horizontal`, of type `alignment`.
/// - Vertical alignment: `vertical`, of type `alignment`.
///
/// # Relevant types and constants
/// - Type `alignment`
/// - `left`
/// - `right`
/// - `top`
/// - `bottom`
/// - `center`
pub fn align(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
let first = args.find(ctx);
let second = args.find(ctx);
let hor = args.get(ctx, "horizontal");
let ver = args.get(ctx, "vertical");
let body = args.find::<ValueTemplate>(ctx);
Value::template("align", move |ctx| {
let snapshot = ctx.state.clone();
let mut had = Gen::uniform(false);
let mut had_center = false;
// Infer the axes alignments belong to.
for (axis, Spanned { v: arg, span }) in first
.into_iter()
.chain(second.into_iter())
.map(|arg: Spanned<Alignment>| (arg.v.axis(), arg))
.chain(hor.into_iter().map(|arg| (Some(SpecAxis::Horizontal), arg)))
.chain(ver.into_iter().map(|arg| (Some(SpecAxis::Vertical), arg)))
{
// Check whether we know which axis this alignment belongs to.
if let Some(axis) = axis {
// We know the axis.
let gen_axis = axis.switch(ctx.state.dirs);
let gen_align = arg.switch(ctx.state.dirs);
if arg.axis().map_or(false, |a| a != axis) {
ctx.diag(error!(span, "invalid alignment for {} axis", axis));
} else if had.get(gen_axis) {
ctx.diag(error!(span, "duplicate alignment for {} axis", axis));
} else {
*ctx.state.align.get_mut(gen_axis) = gen_align;
*had.get_mut(gen_axis) = true;
}
} else {
// We don't know the axis: This has to be a `center` alignment for a
// positional argument.
debug_assert_eq!(arg, Alignment::Center);
if had.main && had.cross {
ctx.diag(error!(span, "duplicate alignment"));
} else if had_center {
// Both this and the previous one are unspecified `center`
// alignments. Both axes should be centered.
ctx.state.align.main = Align::Center;
ctx.state.align.cross = Align::Center;
had = Gen::uniform(true);
} else {
had_center = true;
}
}
// If we we know the other alignment, we can handle the unspecified
// `center` alignment.
if had_center && (had.main || had.cross) {
if had.main {
ctx.state.align.cross = Align::Center;
had.cross = true;
} else {
ctx.state.align.main = Align::Center;
had.main = true;
}
had_center = false;
}
}
// If `had_center` wasn't flushed by now, it's the only argument and then we
// default to applying it to the cross axis.
if had_center {
ctx.state.align.cross = Align::Center;
}
if ctx.state.align.main != snapshot.align.main {
ctx.end_par_group();
ctx.start_par_group();
}
if let Some(body) = &body {
body.exec(ctx);
ctx.state = snapshot;
}
})
}
/// An alignment argument.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum Alignment {
Left,
Center,
Right,
Top,
Bottom,
}
impl Alignment {
/// The specific axis this alignment refers to.
fn axis(self) -> Option<SpecAxis> {
match self {
Self::Left => Some(SpecAxis::Horizontal),
Self::Right => Some(SpecAxis::Horizontal),
Self::Top => Some(SpecAxis::Vertical),
Self::Bottom => Some(SpecAxis::Vertical),
Self::Center => None,
}
}
}
impl Switch for Alignment {
type Other = Align;
fn switch(self, dirs: LayoutDirs) -> Self::Other {
let get = |dir: Dir, at_positive_start| {
if dir.is_positive() == at_positive_start {
Align::Start
} else {
Align::End
}
};
let dirs = dirs.switch(dirs);
match self {
Self::Left => get(dirs.horizontal, true),
Self::Right => get(dirs.horizontal, false),
Self::Top => get(dirs.vertical, true),
Self::Bottom => get(dirs.vertical, false),
Self::Center => Align::Center,
}
}
}
typify! {
Alignment: "alignment",
}
impl Display for Alignment {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Left => "left",
Self::Center => "center",
Self::Right => "right",
Self::Top => "top",
Self::Bottom => "bottom",
})
}
}
/// `box`: Layout content into a box.
///
/// # Named arguments
/// - Width of the box: `width`, of type `linear` relative to parent width.
/// - Height of the box: `height`, of type `linear` relative to parent height.
/// - Main layouting direction: `main-dir`, of type `direction`.
/// - Cross layouting direction: `cross-dir`, of type `direction`.
/// - Background color of the box: `color`, of type `color`.
///
/// # Relevant types and constants
/// - Type `direction`
/// - `ltr` (left to right)
/// - `rtl` (right to left)
/// - `ttb` (top to bottom)
/// - `btt` (bottom to top)
pub fn box_(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
let width = args.get(ctx, "width");
let height = args.get(ctx, "height");
let main = args.get(ctx, "main-dir");
let cross = args.get(ctx, "cross-dir");
let color = args.get(ctx, "color");
let body = args.find::<ValueTemplate>(ctx);
Value::template("box", move |ctx| {
let snapshot = ctx.state.clone();
ctx.set_dirs(Gen::new(main, cross));
let dirs = ctx.state.dirs;
let align = ctx.state.align;
ctx.start_content_group();
if let Some(body) = &body {
body.exec(ctx);
}
let children = ctx.end_content_group();
let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit };
let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some()));
let fixed = NodeFixed {
width,
height,
child: NodeStack { dirs, align, expand, children }.into(),
};
if let Some(color) = color {
ctx.push(NodeBackground {
fill: Fill::Color(color),
child: fixed.into(),
});
} else {
ctx.push(fixed);
}
ctx.state = snapshot;
})
}
typify! {
Dir: "direction"
}
/// `h`: Add horizontal spacing.
///
/// # Positional arguments
/// - Amount of spacing: of type `linear` relative to current font size.
pub fn h(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
spacing(ctx, args, SpecAxis::Horizontal)
}
/// `v`: Add vertical spacing.
///
/// # Positional arguments
/// - Amount of spacing: of type `linear` relative to current font size.
pub fn v(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
spacing(ctx, args, SpecAxis::Vertical)
}
/// Apply spacing along a specific axis.
fn spacing(ctx: &mut EvalContext, args: &mut ValueArgs, axis: SpecAxis) -> Value {
let spacing: Option<Linear> = args.require(ctx, "spacing");
Value::template("spacing", move |ctx| {
if let Some(linear) = spacing {
let amount = linear.resolve(ctx.state.font.font_size());
let spacing = NodeSpacing { amount, softness: Softness::Hard };
if axis == ctx.state.dirs.main.axis() {
ctx.end_par_group();
ctx.push(spacing);
ctx.start_par_group();
} else {
ctx.push(spacing);
}
}
})
}
/// `page`: Configure pages.
///
/// # Positional arguments
/// - Paper name: optional, of type `string`, see [here](crate::paper) for a
/// full list of all paper names.
///
/// # Named arguments
/// - Width of the page: `width`, of type `length`.
/// - Height of the page: `height`, of type `length`.
/// - Margins for all sides: `margins`, of type `linear` relative to sides.
/// - Left margin: `left`, of type `linear` relative to width.
/// - Right margin: `right`, of type `linear` relative to width.
/// - Top margin: `top`, of type `linear` relative to height.
/// - Bottom margin: `bottom`, of type `linear` relative to height.
/// - Flip width and height: `flip`, of type `bool`.
/// - Main layouting direction: `main-dir`, of type `direction`.
/// - Cross layouting direction: `cross-dir`, of type `direction`.
pub fn page(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
let paper = args.find::<Spanned<String>>(ctx).and_then(|name| {
Paper::from_name(&name.v).or_else(|| {
ctx.diag(error!(name.span, "invalid paper name"));
None
})
});
let width = args.get(ctx, "width");
let height = args.get(ctx, "height");
let margins = args.get(ctx, "margins");
let left = args.get(ctx, "left");
let top = args.get(ctx, "top");
let right = args.get(ctx, "right");
let bottom = args.get(ctx, "bottom");
let flip = args.get(ctx, "flip");
let main = args.get(ctx, "main-dir");
let cross = args.get(ctx, "cross-dir");
let body = args.find::<ValueTemplate>(ctx);
Value::template("page", move |ctx| {
let snapshot = ctx.state.clone();
if let Some(paper) = paper {
ctx.state.page.class = paper.class;
ctx.state.page.size = paper.size();
ctx.state.page.expand = Spec::uniform(Expansion::Fill);
}
if let Some(width) = width {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.width = width;
ctx.state.page.expand.horizontal = Expansion::Fill;
}
if let Some(height) = height {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.height = height;
ctx.state.page.expand.vertical = Expansion::Fill;
}
if let Some(margins) = margins {
ctx.state.page.margins = Sides::uniform(Some(margins));
}
if let Some(left) = left {
ctx.state.page.margins.left = Some(left);
}
if let Some(top) = top {
ctx.state.page.margins.top = Some(top);
}
if let Some(right) = right {
ctx.state.page.margins.right = Some(right);
}
if let Some(bottom) = bottom {
ctx.state.page.margins.bottom = Some(bottom);
}
if flip.unwrap_or(false) {
let page = &mut ctx.state.page;
std::mem::swap(&mut page.size.width, &mut page.size.height);
std::mem::swap(&mut page.expand.horizontal, &mut page.expand.vertical);
}
ctx.set_dirs(Gen::new(main, cross));
let mut softness = ctx.end_page_group(|_| false);
if let Some(body) = &body {
// TODO: Restrict body to a single page?
ctx.start_page_group(Softness::Hard);
body.exec(ctx);
ctx.end_page_group(|s| s == Softness::Hard);
softness = Softness::Soft;
ctx.state = snapshot;
}
ctx.start_page_group(softness);
})
}
/// `pagebreak`: Start a new page.
pub fn pagebreak(_: &mut EvalContext, _: &mut ValueArgs) -> Value {
Value::template("pagebreak", move |ctx| {
ctx.end_page_group(|_| true);
ctx.start_page_group(Softness::Hard);
})
}

View File

@ -3,20 +3,30 @@
//! Call [`new`] to obtain a [`Scope`] containing all standard library //! Call [`new`] to obtain a [`Scope`] containing all standard library
//! definitions. //! definitions.
mod extend; mod align;
mod insert; mod base;
mod layout; mod font;
mod style; mod image;
mod page;
mod shapes;
mod spacing;
pub use extend::*; pub use self::image::*;
pub use insert::*; pub use align::*;
pub use layout::*; pub use base::*;
pub use style::*; pub use font::*;
pub use page::*;
pub use shapes::*;
pub use spacing::*;
use std::fmt::{self, Display, Formatter};
use fontdock::{FontStretch, FontStyle, FontWeight}; use fontdock::{FontStretch, FontStyle, FontWeight};
use crate::eval::{Scope, ValueAny, ValueFunc}; use crate::eval::{Scope, ValueAny, ValueFunc};
use crate::geom::Dir; use crate::exec::Softness;
use crate::layout::*;
use crate::prelude::*;
/// Construct a scope containing all standard library definitions. /// Construct a scope containing all standard library definitions.
pub fn new() -> Scope { pub fn new() -> Scope {
@ -80,3 +90,7 @@ pub fn new() -> Scope {
std std
} }
typify! {
Dir: "direction"
}

111
src/library/page.rs Normal file
View File

@ -0,0 +1,111 @@
use super::*;
use crate::paper::{Paper, PaperClass};
/// `page`: Configure pages.
///
/// # Positional arguments
/// - Paper name: optional, of type `string`, see [here](crate::paper) for a
/// full list of all paper names.
/// - Body: optional, of type `template`.
///
/// # Named arguments
/// - Width of the page: `width`, of type `length`.
/// - Height of the page: `height`, of type `length`.
/// - Margins for all sides: `margins`, of type `linear` relative to sides.
/// - Left margin: `left`, of type `linear` relative to width.
/// - Right margin: `right`, of type `linear` relative to width.
/// - Top margin: `top`, of type `linear` relative to height.
/// - Bottom margin: `bottom`, of type `linear` relative to height.
/// - Flip width and height: `flip`, of type `bool`.
/// - Main layouting direction: `main-dir`, of type `direction`.
/// - Cross layouting direction: `cross-dir`, of type `direction`.
pub fn page(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
let paper = args.find::<Spanned<String>>(ctx).and_then(|name| {
Paper::from_name(&name.v).or_else(|| {
ctx.diag(error!(name.span, "invalid paper name"));
None
})
});
let width = args.get(ctx, "width");
let height = args.get(ctx, "height");
let margins = args.get(ctx, "margins");
let left = args.get(ctx, "left");
let top = args.get(ctx, "top");
let right = args.get(ctx, "right");
let bottom = args.get(ctx, "bottom");
let flip = args.get(ctx, "flip");
let main = args.get(ctx, "main-dir");
let cross = args.get(ctx, "cross-dir");
let body = args.find::<ValueTemplate>(ctx);
Value::template("page", move |ctx| {
let snapshot = ctx.state.clone();
if let Some(paper) = paper {
ctx.state.page.class = paper.class;
ctx.state.page.size = paper.size();
ctx.state.page.expand = Spec::uniform(Expansion::Fill);
}
if let Some(width) = width {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.width = width;
ctx.state.page.expand.horizontal = Expansion::Fill;
}
if let Some(height) = height {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.height = height;
ctx.state.page.expand.vertical = Expansion::Fill;
}
if let Some(margins) = margins {
ctx.state.page.margins = Sides::uniform(Some(margins));
}
if let Some(left) = left {
ctx.state.page.margins.left = Some(left);
}
if let Some(top) = top {
ctx.state.page.margins.top = Some(top);
}
if let Some(right) = right {
ctx.state.page.margins.right = Some(right);
}
if let Some(bottom) = bottom {
ctx.state.page.margins.bottom = Some(bottom);
}
if flip.unwrap_or(false) {
let page = &mut ctx.state.page;
std::mem::swap(&mut page.size.width, &mut page.size.height);
std::mem::swap(&mut page.expand.horizontal, &mut page.expand.vertical);
}
ctx.set_dirs(Gen::new(main, cross));
let mut softness = ctx.end_page_group(|_| false);
if let Some(body) = &body {
// TODO: Restrict body to a single page?
ctx.start_page_group(Softness::Hard);
body.exec(ctx);
ctx.end_page_group(|s| s == Softness::Hard);
softness = Softness::Soft;
ctx.state = snapshot;
}
ctx.start_page_group(softness);
})
}
/// `pagebreak`: Start a new page.
pub fn pagebreak(_: &mut EvalContext, _: &mut ValueArgs) -> Value {
Value::template("pagebreak", move |ctx| {
ctx.end_page_group(|_| true);
ctx.start_page_group(Softness::Hard);
})
}

61
src/library/shapes.rs Normal file
View File

@ -0,0 +1,61 @@
use super::*;
/// `box`: Create a rectangular box.
///
/// # Positional arguments
/// - Body: optional, of type `template`.
///
/// # Named arguments
/// - Width of the box: `width`, of type `linear` relative to parent width.
/// - Height of the box: `height`, of type `linear` relative to parent height.
/// - Main layouting direction: `main-dir`, of type `direction`.
/// - Cross layouting direction: `cross-dir`, of type `direction`.
/// - Background color of the box: `color`, of type `color`.
///
/// # Relevant types and constants
/// - Type `direction`
/// - `ltr` (left to right)
/// - `rtl` (right to left)
/// - `ttb` (top to bottom)
/// - `btt` (bottom to top)
pub fn box_(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
let width = args.get(ctx, "width");
let height = args.get(ctx, "height");
let main = args.get(ctx, "main-dir");
let cross = args.get(ctx, "cross-dir");
let color = args.get(ctx, "color");
let body = args.find::<ValueTemplate>(ctx);
Value::template("box", move |ctx| {
let snapshot = ctx.state.clone();
ctx.set_dirs(Gen::new(main, cross));
let dirs = ctx.state.dirs;
let align = ctx.state.align;
ctx.start_content_group();
if let Some(body) = &body {
body.exec(ctx);
}
let children = ctx.end_content_group();
let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit };
let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some()));
let fixed = NodeFixed {
width,
height,
child: NodeStack { dirs, align, expand, children }.into(),
};
if let Some(color) = color {
ctx.push(NodeBackground {
fill: Fill::Color(color),
child: fixed.into(),
});
} else {
ctx.push(fixed);
}
ctx.state = snapshot;
})
}

36
src/library/spacing.rs Normal file
View File

@ -0,0 +1,36 @@
use super::*;
/// `h`: Add horizontal spacing.
///
/// # Positional arguments
/// - Amount of spacing: of type `linear` relative to current font size.
pub fn h(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
spacing(ctx, args, SpecAxis::Horizontal)
}
/// `v`: Add vertical spacing.
///
/// # Positional arguments
/// - Amount of spacing: of type `linear` relative to current font size.
pub fn v(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
spacing(ctx, args, SpecAxis::Vertical)
}
/// Apply spacing along a specific axis.
fn spacing(ctx: &mut EvalContext, args: &mut ValueArgs, axis: SpecAxis) -> Value {
let spacing: Option<Linear> = args.require(ctx, "spacing");
Value::template("spacing", move |ctx| {
if let Some(linear) = spacing {
let amount = linear.resolve(ctx.state.font.font_size());
let spacing = NodeSpacing { amount, softness: Softness::Hard };
if axis == ctx.state.dirs.main.axis() {
ctx.end_par_group();
ctx.push(spacing);
ctx.start_par_group();
} else {
ctx.push(spacing);
}
}
})
}

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,25 @@
// Test the base functions.
// Ref: false
---
#test(type("hi"), "string")
#test(repr([Hi #box[there]]), "[Hi [<node box>]]")
---
// Check the output.
#test(rgb(0.0, 0.3, 0.7), #004db3)
// Alpha channel.
#test(rgb(1.0, 0.0, 0.0, 0.5), #ff000080)
// Warning: 2:11-2:14 should be between 0.0 and 1.0
// Warning: 1:16-1:20 should be between 0.0 and 1.0
#test(rgb(-30, 15.5, 0.5), #00ff80)
// Error: 11-15 missing argument: blue component
#test(rgb(0, 1), #00ff00)
// Error: 3:11-3:11 missing argument: red component
// Error: 2:11-2:11 missing argument: green component
// Error: 1:11-1:11 missing argument: blue component
#test(rgb(), #000000)

View File

@ -1,4 +1,4 @@
// Test the font function. // Test the `font` function.
--- ---
// Test configuring font properties. // Test configuring font properties.

View File

@ -1,4 +1,4 @@
// Test the image function. // Test the `image` function.
--- ---
// Test loading different image formats. // Test loading different image formats.

View File

@ -1,4 +1,4 @@
// Test the page function. // Test the `page` function.
--- ---
// Test configuring page sizes and margins. // Test configuring page sizes and margins.

View File

@ -1,4 +1,4 @@
// Test the pagebreak function. // Test the `pagebreak` function.
--- ---
First of two First of two

View File

@ -1,23 +0,0 @@
// Test the rgb function.
// Ref: false
---
{
// Check the output.
test(rgb(0.0, 0.3, 0.7), #004db3)
// Alpha channel.
test(rgb(1.0, 0.0, 0.0, 0.5), #ff000080)
// Warning: 2:14-2:17 should be between 0.0 and 1.0
// Warning: 1:19-1:23 should be between 0.0 and 1.0
test(rgb(-30, 15.5, 0.5), #00ff80)
// Error: 14-18 missing argument: blue component
test(rgb(0, 1), #00ff00)
// Error: 3:14-3:14 missing argument: red component
// Error: 2:14-2:14 missing argument: green component
// Error: 1:14-1:14 missing argument: blue component
test(rgb(), #000000)
}

View File

@ -1,4 +1,4 @@
// Test the box function. // Test shapes.
--- ---
#page("a8", flip: true) #page("a8", flip: true)

View File

@ -1,4 +1,4 @@
// Test the h and v functions. // Test the `h` and `v` functions.
--- ---
// Ends paragraphs. // Ends paragraphs.