Merge some modules 🥞

This commit is contained in:
Laurenz 2020-11-25 16:56:29 +01:00
parent 761931405c
commit 11e44516fa
14 changed files with 475 additions and 501 deletions

View File

@ -1,37 +0,0 @@
use super::*;
/// 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> {
let mut layouts = vec![];
for run in &self.runs {
layouts.extend(run.layout(ctx));
}
layouts
}
}
/// A variable-length run of pages that all have the same properties.
#[derive(Debug, Clone, PartialEq)]
pub struct Pages {
/// The size of the pages.
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()
}
}

View File

@ -1,8 +1,6 @@
//! Layouting of documents. //! Layouting of documents.
mod document;
mod fixed; mod fixed;
mod graphics;
mod node; mod node;
mod pad; mod pad;
mod par; mod par;
@ -16,9 +14,7 @@ use crate::font::SharedFontLoader;
use crate::geom::*; use crate::geom::*;
use crate::shaping::Shaped; use crate::shaping::Shaped;
pub use document::*;
pub use fixed::*; pub use fixed::*;
pub use graphics::*;
pub use node::*; pub use node::*;
pub use pad::*; pub use pad::*;
pub use par::*; pub use par::*;
@ -193,3 +189,35 @@ pub struct ImageElement {
/// The document size of the image. /// The document size of the image.
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()
}
}

View File

@ -74,12 +74,6 @@ impl Debug for Dynamic {
} }
} }
impl From<Dynamic> for LayoutNode {
fn from(dynamic: Dynamic) -> Self {
Self::Dyn(dynamic)
}
}
impl Clone for Dynamic { impl Clone for Dynamic {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self(self.0.dyn_clone()) Self(self.0.dyn_clone())
@ -92,6 +86,12 @@ impl PartialEq for Dynamic {
} }
} }
impl From<Dynamic> for LayoutNode {
fn from(dynamic: Dynamic) -> Self {
Self::Dyn(dynamic)
}
}
/// A dynamic node, which can implement custom layouting behaviour. /// A dynamic node, which can implement custom layouting behaviour.
/// ///
/// This trait just combines the requirements for types to qualify as dynamic /// This trait just combines the requirements for types to qualify as dynamic

View File

@ -29,6 +29,12 @@ impl Layout for Pad {
} }
} }
impl From<Pad> for LayoutNode {
fn from(pad: Pad) -> Self {
Self::dynamic(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: &Areas, padding: Sides<Linear>) -> Areas {
let shrink = |size| size - padding.resolve(size).size(); let shrink = |size| size - padding.resolve(size).size();
@ -52,9 +58,3 @@ fn pad_layout(layout: &mut BoxLayout, padding: Sides<Linear>) {
*point += origin; *point += origin;
} }
} }
impl From<Pad> for LayoutNode {
fn from(pad: Pad) -> Self {
Self::dynamic(pad)
}
}

View File

@ -1,185 +0,0 @@
use crate::prelude::*;
use std::fmt::{self, Display, Formatter};
/// `align`: Align content along the layouting axes.
///
/// # Positional arguments
/// - At most two of `left`, `right`, `top`, `bottom`, `center`.
///
/// When `center` is used as a positional argument, it is automatically inferred
/// which axis it should apply to depending on further arguments, defaulting
/// to the axis, text is set along.
///
/// # Keyword arguments
/// - `horizontal`: Any of `left`, `right` or `center`.
/// - `vertical`: Any of `top`, `bottom` or `center`.
///
/// There may not be two alignment specifications for the same axis.
pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
let snapshot = ctx.state.clone();
let body = args.find::<SynTree>();
let first = args.get::<_, Spanned<AlignArg>>(ctx, 0);
let second = args.get::<_, Spanned<AlignArg>>(ctx, 1);
let hor = args.get::<_, Spanned<AlignArg>>(ctx, "horizontal");
let ver = args.get::<_, Spanned<AlignArg>>(ctx, "vertical");
args.done(ctx);
let iter = first
.into_iter()
.chain(second.into_iter())
.map(|align| (align.v.axis(), align))
.chain(hor.into_iter().map(|align| (Some(SpecAxis::Horizontal), align)))
.chain(ver.into_iter().map(|align| (Some(SpecAxis::Vertical), align)));
let align = dedup_aligns(ctx, iter);
let ends_par = align.main != ctx.state.align.main;
ctx.state.align = align;
if ends_par {
ctx.end_par_group();
ctx.start_par_group();
}
if let Some(body) = body {
body.eval(ctx);
ctx.state = snapshot;
}
Value::None
}
/// Deduplicate alignments and deduce to which axes they apply.
fn dedup_aligns(
ctx: &mut EvalContext,
iter: impl Iterator<Item = (Option<SpecAxis>, Spanned<AlignArg>)>,
) -> BoxAlign {
let mut alignments = ctx.state.align;
let mut had = Gen::uniform(false);
let mut had_center = false;
for (axis, Spanned { v: align, span }) in iter {
// Check whether we know which axis this alignment belongs to.
if let Some(axis) = axis {
// We know the axis.
let gen_axis = axis.switch(ctx.state.flow);
let gen_align = align.switch(ctx.state.flow);
if align.axis().map_or(false, |a| a != axis) {
ctx.diag(error!(
span,
"invalid alignment `{}` for {} axis", align, axis,
));
} else if had.get(gen_axis) {
ctx.diag(error!(span, "duplicate alignment for {} axis", axis));
} else {
*alignments.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!(align, AlignArg::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.
alignments = BoxAlign::new(Align::Center, Align::Center);
had = Gen::uniform(true);
} else {
had_center = true;
}
}
// If we we know one alignment, we can handle the unspecified `center`
// alignment.
if had_center && (had.main || had.cross) {
if had.main {
alignments.cross = Align::Center;
had.cross = true;
} else {
alignments.main = Align::Center;
had.main = true;
}
had_center = false;
}
}
// If center has not been flushed by now, it is the only argument and then
// we default to applying it to the cross axis.
if had_center {
alignments.cross = Align::Center;
}
alignments
}
/// An alignment argument.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
enum AlignArg {
Left,
Right,
Top,
Bottom,
Center,
}
impl AlignArg {
/// The specific axis this alignment refers to.
///
/// Returns `None` if this is `Center` since the axis is unknown.
pub 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 AlignArg {
type Other = Align;
fn switch(self, flow: Flow) -> Self::Other {
let get = |dir: Dir, at_positive_start| {
if dir.is_positive() == at_positive_start {
Align::Start
} else {
Align::End
}
};
let flow = flow.switch(flow);
match self {
Self::Left => get(flow.horizontal, true),
Self::Right => get(flow.horizontal, false),
Self::Top => get(flow.vertical, true),
Self::Bottom => get(flow.vertical, false),
Self::Center => Align::Center,
}
}
}
convert_ident!(AlignArg, "alignment", |v| match v {
"left" => Some(Self::Left),
"right" => Some(Self::Right),
"top" => Some(Self::Top),
"bottom" => Some(Self::Bottom),
"center" => Some(Self::Center),
_ => None,
});
impl Display for AlignArg {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Left => "left",
Self::Right => "right",
Self::Top => "top",
Self::Bottom => "bottom",
Self::Center => "center",
})
}
}

View File

@ -1,44 +0,0 @@
use crate::geom::Linear;
use crate::layout::{Expansion, Fixed, Stack};
use crate::prelude::*;
/// `box`: Layouts its contents into a box.
///
/// # Keyword arguments
/// - `width`: The width of the box (length or relative to parent's width).
/// - `height`: The height of the box (length or relative to parent's height).
pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
let snapshot = ctx.state.clone();
let body = args.find::<SynTree>().unwrap_or_default();
let width = args.get::<_, Linear>(ctx, "width");
let height = args.get::<_, Linear>(ctx, "height");
let main = args.get::<_, Spanned<Dir>>(ctx, "main");
let cross = args.get::<_, Spanned<Dir>>(ctx, "cross");
ctx.set_flow(Gen::new(main, cross));
args.done(ctx);
let flow = ctx.state.flow;
let align = ctx.state.align;
ctx.start_content_group();
body.eval(ctx);
let children = ctx.end_content_group();
ctx.push(Fixed {
width,
height,
child: LayoutNode::dynamic(Stack {
flow,
align,
expansion: Spec::new(
Expansion::fill_if(width.is_some()),
Expansion::fill_if(height.is_some()),
)
.switch(flow),
children,
}),
});
ctx.state = snapshot;
Value::None
}

View File

@ -1,27 +0,0 @@
use crate::color::RgbaColor;
use crate::prelude::*;
/// `rgb`: Create an RGB(A) color.
pub fn rgb(mut args: Args, ctx: &mut EvalContext) -> Value {
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);
let mut clamp = |component: Option<Spanned<i64>>, default| {
component.map_or(default, |c| {
if c.v < 0 || c.v > 255 {
ctx.diag(error!(c.span, "should be between 0 and 255"));
}
c.v.max(0).min(255) as u8
})
};
Value::Color(RgbaColor::new(
clamp(r, 0),
clamp(g, 0),
clamp(b, 0),
clamp(a, 255),
))
}

View File

@ -1,42 +0,0 @@
use std::fs::File;
use std::io::BufReader;
use image::io::Reader;
use crate::layout::Image;
use crate::prelude::*;
/// `image`: Include an image.
///
/// # Positional arguments
/// - The path to the image (string)
pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value {
let path = args.need::<_, Spanned<String>>(ctx, 0, "path");
let width = args.get::<_, Linear>(ctx, "width");
let height = args.get::<_, Linear>(ctx, "height");
if let Some(path) = path {
if let Ok(file) = File::open(path.v) {
match Reader::new(BufReader::new(file))
.with_guessed_format()
.map_err(|err| err.into())
.and_then(|reader| reader.decode())
.map(|img| img.into_rgba8())
{
Ok(buf) => {
ctx.push(Image {
buf,
width,
height,
align: ctx.state.align,
});
}
Err(err) => ctx.diag(error!(path.span, "invalid image: {}", err)),
}
} else {
ctx.diag(error!(path.span, "failed to open image file"));
}
}
Value::None
}

View File

@ -1,18 +1,59 @@
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::fs::File;
use std::io::BufReader;
use super::*; use image::io::Reader;
use image::RgbaImage;
use crate::layout::*;
use crate::prelude::*;
/// `image`: Insert an image.
///
/// # Positional arguments
/// - The path to the image (string)
pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value {
let path = args.need::<_, Spanned<String>>(ctx, 0, "path");
let width = args.get::<_, Linear>(ctx, "width");
let height = args.get::<_, Linear>(ctx, "height");
if let Some(path) = path {
if let Ok(file) = File::open(path.v) {
match Reader::new(BufReader::new(file))
.with_guessed_format()
.map_err(|err| err.into())
.and_then(|reader| reader.decode())
.map(|img| img.into_rgba8())
{
Ok(buf) => {
ctx.push(Image {
buf,
width,
height,
align: ctx.state.align,
});
}
Err(err) => ctx.diag(error!(path.span, "invalid image: {}", err)),
}
} else {
ctx.diag(error!(path.span, "failed to open image file"));
}
}
Value::None
}
/// An image node. /// An image node.
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct Image { struct Image {
/// The image. /// The image.
pub buf: RgbaImage, buf: RgbaImage,
/// The fixed width, if any. /// The fixed width, if any.
pub width: Option<Linear>, width: Option<Linear>,
/// The fixed height, if any. /// The fixed height, if any.
pub height: Option<Linear>, height: Option<Linear>,
/// How to align this image node in its parent. /// How to align this image node in its parent.
pub align: BoxAlign, align: BoxAlign,
} }
impl Layout for Image { impl Layout for Image {

337
src/library/layout.rs Normal file
View File

@ -0,0 +1,337 @@
use std::fmt::{self, Display, Formatter};
use crate::geom::{Length, Linear};
use crate::layout::{Expansion, Fixed, Softness, Spacing, Stack};
use crate::paper::{Paper, PaperClass};
use crate::prelude::*;
/// `align`: Align content along the layouting axes.
///
/// # Positional arguments
/// - At most two of `left`, `right`, `top`, `bottom`, `center`.
///
/// When `center` is used as a positional argument, it is automatically inferred
/// which axis it should apply to depending on further arguments, defaulting
/// to the cross axis.
///
/// # Keyword arguments
/// - `horizontal`: Any of `left`, `right` or `center`.
/// - `vertical`: Any of `top`, `bottom` or `center`.
pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
let snapshot = ctx.state.clone();
let body = args.find::<SynTree>();
let first = args.get::<_, Spanned<AlignArg>>(ctx, 0);
let second = args.get::<_, Spanned<AlignArg>>(ctx, 1);
let hor = args.get::<_, Spanned<AlignArg>>(ctx, "horizontal");
let ver = args.get::<_, Spanned<AlignArg>>(ctx, "vertical");
args.done(ctx);
let prev_main = ctx.state.align.main;
let mut had = Gen::uniform(false);
let mut had_center = false;
for (axis, Spanned { v: arg, span }) in first
.into_iter()
.chain(second.into_iter())
.map(|arg| (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.flow);
let gen_align = arg.switch(ctx.state.flow);
if arg.axis().map_or(false, |a| a != axis) {
ctx.diag(error!(
span,
"invalid alignment `{}` for {} axis", arg, 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, AlignArg::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 != prev_main {
ctx.end_par_group();
ctx.start_par_group();
}
if let Some(body) = body {
body.eval(ctx);
ctx.state = snapshot;
}
Value::None
}
/// An argument to `[align]`.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
enum AlignArg {
Left,
Right,
Top,
Bottom,
Center,
}
convert_ident!(AlignArg, "alignment", |v| match v {
"left" => Some(Self::Left),
"right" => Some(Self::Right),
"top" => Some(Self::Top),
"bottom" => Some(Self::Bottom),
"center" => Some(Self::Center),
_ => None,
});
impl AlignArg {
/// The specific axis this alignment refers to.
///
/// Returns `None` if this is `Center` since the axis is unknown.
pub 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 AlignArg {
type Other = Align;
fn switch(self, flow: Flow) -> Self::Other {
let get = |dir: Dir, at_positive_start| {
if dir.is_positive() == at_positive_start {
Align::Start
} else {
Align::End
}
};
let flow = flow.switch(flow);
match self {
Self::Left => get(flow.horizontal, true),
Self::Right => get(flow.horizontal, false),
Self::Top => get(flow.vertical, true),
Self::Bottom => get(flow.vertical, false),
Self::Center => Align::Center,
}
}
}
impl Display for AlignArg {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Left => "left",
Self::Right => "right",
Self::Top => "top",
Self::Bottom => "bottom",
Self::Center => "center",
})
}
}
/// `box`: Layout content into a box.
///
/// # Keyword arguments
/// - `width`: The width of the box (length or relative to parent's width).
/// - `height`: The height of the box (length or relative to parent's height).
pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
let snapshot = ctx.state.clone();
let body = args.find::<SynTree>().unwrap_or_default();
let width = args.get::<_, Linear>(ctx, "width");
let height = args.get::<_, Linear>(ctx, "height");
let main = args.get::<_, Spanned<Dir>>(ctx, "main");
let cross = args.get::<_, Spanned<Dir>>(ctx, "cross");
ctx.set_flow(Gen::new(main, cross));
args.done(ctx);
let flow = ctx.state.flow;
let align = ctx.state.align;
ctx.start_content_group();
body.eval(ctx);
let children = ctx.end_content_group();
ctx.push(Fixed {
width,
height,
child: LayoutNode::dynamic(Stack {
flow,
align,
expansion: Spec::new(
Expansion::fill_if(width.is_some()),
Expansion::fill_if(height.is_some()),
)
.switch(flow),
children,
}),
});
ctx.state = snapshot;
Value::None
}
/// `h`: Add horizontal spacing.
///
/// # Positional arguments
/// - The spacing (length or relative to font size).
pub fn h(args: Args, ctx: &mut EvalContext) -> Value {
spacing(args, ctx, SpecAxis::Horizontal)
}
/// `v`: Add vertical spacing.
///
/// # Positional arguments
/// - The spacing (length or relative to font size).
pub fn v(args: Args, ctx: &mut EvalContext) -> Value {
spacing(args, ctx, SpecAxis::Vertical)
}
/// Apply spacing along a specific axis.
fn spacing(mut args: Args, ctx: &mut EvalContext, axis: SpecAxis) -> Value {
let spacing = args.need::<_, Linear>(ctx, 0, "spacing");
args.done(ctx);
if let Some(linear) = spacing {
let amount = linear.resolve(ctx.state.font.font_size());
let spacing = Spacing { amount, softness: Softness::Hard };
if ctx.state.flow.main.axis() == axis {
ctx.end_par_group();
ctx.push(spacing);
ctx.start_par_group();
} else {
ctx.push(spacing);
}
}
Value::None
}
/// `page`: Configure pages.
///
/// # Positional arguments
/// - The name of a paper, e.g. `a4` (optional).
///
/// # Keyword arguments
/// - `width`: The width of pages (length).
/// - `height`: The height of pages (length).
/// - `margins`: The margins for all sides (length or relative to side lengths).
/// - `left`: The left margin (length or relative to width).
/// - `right`: The right margin (length or relative to width).
/// - `top`: The top margin (length or relative to height).
/// - `bottom`: The bottom margin (length or relative to height).
/// - `flip`: Flips custom or paper-defined width and height (boolean).
pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value {
let snapshot = ctx.state.clone();
let body = args.find::<SynTree>();
if let Some(paper) = args.find::<Paper>() {
ctx.state.page.class = paper.class;
ctx.state.page.size = paper.size();
}
if let Some(width) = args.get::<_, Length>(ctx, "width") {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.width = width;
}
if let Some(height) = args.get::<_, Length>(ctx, "height") {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.height = height;
}
if let Some(margins) = args.get::<_, Linear>(ctx, "margins") {
ctx.state.page.margins = Sides::uniform(Some(margins));
}
if let Some(left) = args.get::<_, Linear>(ctx, "left") {
ctx.state.page.margins.left = Some(left);
}
if let Some(top) = args.get::<_, Linear>(ctx, "top") {
ctx.state.page.margins.top = Some(top);
}
if let Some(right) = args.get::<_, Linear>(ctx, "right") {
ctx.state.page.margins.right = Some(right);
}
if let Some(bottom) = args.get::<_, Linear>(ctx, "bottom") {
ctx.state.page.margins.bottom = Some(bottom);
}
if args.get::<_, bool>(ctx, "flip").unwrap_or(false) {
let size = &mut ctx.state.page.size;
std::mem::swap(&mut size.width, &mut size.height);
}
let main = args.get::<_, Spanned<Dir>>(ctx, "main");
let cross = args.get::<_, Spanned<Dir>>(ctx, "cross");
ctx.set_flow(Gen::new(main, cross));
args.done(ctx);
if let Some(body) = body {
ctx.end_page_group();
ctx.start_page_group(true);
body.eval(ctx);
ctx.state = snapshot;
}
ctx.end_page_group();
ctx.start_page_group(false);
Value::None
}
/// `pagebreak`: Start a new page.
pub fn pagebreak(args: Args, ctx: &mut EvalContext) -> Value {
args.done(ctx);
ctx.end_page_group();
ctx.start_page_group(true);
Value::None
}

View File

@ -1,20 +1,12 @@
//! The standard library. //! The standard library.
mod align; mod insert;
mod boxed; mod layout;
mod color; mod style;
mod font;
mod graphics;
mod page;
mod spacing;
pub use align::*; pub use insert::*;
pub use boxed::*; pub use layout::*;
pub use color::*; pub use style::*;
pub use font::*;
pub use graphics::*;
pub use page::*;
pub use spacing::*;
use crate::eval::{Scope, ValueFunc}; use crate::eval::{Scope, ValueFunc};

View File

@ -1,88 +0,0 @@
use crate::geom::{Length, Linear};
use crate::paper::{Paper, PaperClass};
use crate::prelude::*;
/// `page`: Configure pages.
///
/// # Positional arguments
/// - The name of a paper, e.g. `a4` (optional).
///
/// # Keyword arguments
/// - `width`: The width of pages (length).
/// - `height`: The height of pages (length).
/// - `margins`: The margins for all sides (length or relative to side lengths).
/// - `left`: The left margin (length or relative to width).
/// - `right`: The right margin (length or relative to width).
/// - `top`: The top margin (length or relative to height).
/// - `bottom`: The bottom margin (length or relative to height).
/// - `flip`: Flips custom or paper-defined width and height (boolean).
pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value {
let snapshot = ctx.state.clone();
let body = args.find::<SynTree>();
if let Some(paper) = args.find::<Paper>() {
ctx.state.page.class = paper.class;
ctx.state.page.size = paper.size();
}
if let Some(width) = args.get::<_, Length>(ctx, "width") {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.width = width;
}
if let Some(height) = args.get::<_, Length>(ctx, "height") {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.height = height;
}
if let Some(margins) = args.get::<_, Linear>(ctx, "margins") {
ctx.state.page.margins = Sides::uniform(Some(margins));
}
if let Some(left) = args.get::<_, Linear>(ctx, "left") {
ctx.state.page.margins.left = Some(left);
}
if let Some(top) = args.get::<_, Linear>(ctx, "top") {
ctx.state.page.margins.top = Some(top);
}
if let Some(right) = args.get::<_, Linear>(ctx, "right") {
ctx.state.page.margins.right = Some(right);
}
if let Some(bottom) = args.get::<_, Linear>(ctx, "bottom") {
ctx.state.page.margins.bottom = Some(bottom);
}
if args.get::<_, bool>(ctx, "flip").unwrap_or(false) {
let size = &mut ctx.state.page.size;
std::mem::swap(&mut size.width, &mut size.height);
}
let main = args.get::<_, Spanned<Dir>>(ctx, "main");
let cross = args.get::<_, Spanned<Dir>>(ctx, "cross");
ctx.set_flow(Gen::new(main, cross));
args.done(ctx);
if let Some(body) = body {
ctx.end_page_group();
ctx.start_page_group(true);
body.eval(ctx);
ctx.state = snapshot;
}
ctx.end_page_group();
ctx.start_page_group(false);
Value::None
}
/// `pagebreak`: Starts a new page.
pub fn pagebreak(args: Args, ctx: &mut EvalContext) -> Value {
args.done(ctx);
ctx.end_page_group();
ctx.start_page_group(true);
Value::None
}

View File

@ -1,39 +0,0 @@
use crate::geom::Linear;
use crate::layout::{Softness, Spacing};
use crate::prelude::*;
/// `h`: Add horizontal spacing.
///
/// # Positional arguments
/// - The spacing (length or relative to font size).
pub fn h(args: Args, ctx: &mut EvalContext) -> Value {
spacing(args, ctx, SpecAxis::Horizontal)
}
/// `v`: Add vertical spacing.
///
/// # Positional arguments
/// - The spacing (length or relative to font size).
pub fn v(args: Args, ctx: &mut EvalContext) -> Value {
spacing(args, ctx, SpecAxis::Vertical)
}
/// Apply spacing along a specific axis.
fn spacing(mut args: Args, ctx: &mut EvalContext, axis: SpecAxis) -> Value {
let spacing = args.need::<_, Linear>(ctx, 0, "spacing");
args.done(ctx);
if let Some(linear) = spacing {
let amount = linear.resolve(ctx.state.font.font_size());
let spacing = Spacing { amount, softness: Softness::Hard };
if ctx.state.flow.main.axis() == axis {
ctx.end_par_group();
ctx.push(spacing);
ctx.start_par_group();
} else {
ctx.push(spacing);
}
}
Value::None
}

View File

@ -2,6 +2,7 @@ use std::rc::Rc;
use fontdock::{FontStretch, FontStyle, FontWeight}; use fontdock::{FontStretch, FontStyle, FontWeight};
use crate::color::RgbaColor;
use crate::eval::StringLike; use crate::eval::StringLike;
use crate::geom::Linear; use crate::geom::Linear;
use crate::prelude::*; use crate::prelude::*;
@ -9,8 +10,14 @@ use crate::prelude::*;
/// `font`: Configure the font. /// `font`: Configure the font.
/// ///
/// # Positional arguments /// # Positional arguments
/// - The font size (optional, length or relative to previous font size). /// - The font size (optional, length or relative to current font size).
/// - A font family fallback list (optional, identifiers or strings). /// - All identifier and string arguments are interpreted as an ordered list of
/// fallback font families.
///
/// An example invocation could look like this:
/// ```typst
/// [font: 12pt, Arial, "Noto Sans", sans-serif]
/// ```
/// ///
/// # Keyword arguments /// # Keyword arguments
/// - `style` /// - `style`
@ -28,7 +35,7 @@ use crate::prelude::*;
/// - `bold` (`700`) /// - `bold` (`700`)
/// - `extrabold` (`800`) /// - `extrabold` (`800`)
/// - `black` (`900`) /// - `black` (`900`)
/// - any integer from the range `100` - `900` (inclusive) /// - integer between `100` and `900`
/// ///
/// - `stretch` /// - `stretch`
/// - `ultra-condensed` /// - `ultra-condensed`
@ -106,3 +113,34 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value {
Value::None Value::None
} }
/// `rgb`: Create an RGB(A) color.
///
/// # Positional arguments
/// - The red component (integer between 0 and 255).
/// - The green component (integer between 0 and 255).
/// - The blue component (integer between 0 and 255).
/// - The alpha component (optional, integer between 0 and 255).
pub fn rgb(mut args: Args, ctx: &mut EvalContext) -> Value {
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);
let mut clamp = |component: Option<Spanned<i64>>, default| {
component.map_or(default, |c| {
if c.v < 0 || c.v > 255 {
ctx.diag(error!(c.span, "should be between 0 and 255"));
}
c.v.max(0).min(255) as u8
})
};
Value::Color(RgbaColor::new(
clamp(r, 0),
clamp(g, 0),
clamp(b, 0),
clamp(a, 255),
))
}