Cleanse library

- Remove doc-comments for Typst functions from library
- Reduce number of library source files
This commit is contained in:
Laurenz 2021-06-26 13:06:37 +02:00
parent 63cf361496
commit 285c2f617b
18 changed files with 532 additions and 935 deletions

View File

@ -1,62 +1,23 @@
use ::image::GenericImageView;
use super::*;
use crate::image::ImageId;
use crate::layout::{
AnyNode, Constrained, Constraints, Element, Frame, Layout, LayoutContext, Regions,
};
/// `image`: An image.
///
/// Supports PNG and JPEG files.
///
/// # Positional parameters
/// - Path to image file: of type `string`.
///
/// # Return value
/// A template that inserts an image.
pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let path = args.expect::<Spanned<String>>(ctx, "path to image file");
let width = args.named(ctx, "width");
let height = args.named(ctx, "height");
let mut node = None;
if let Some(path) = &path {
if let Some((resolved, _)) = ctx.resolve(&path.v, path.span) {
if let Some(id) = ctx.cache.image.load(ctx.loader, &resolved) {
let img = ctx.cache.image.get(id);
let dimensions = img.buf.dimensions();
node = Some(ImageNode { id, dimensions, width, height });
} else {
ctx.diag(error!(path.span, "failed to load image"));
}
}
}
Value::template("image", move |ctx| {
if let Some(node) = node {
ctx.push_into_par(node);
}
})
}
use ::image::GenericImageView;
/// An image node.
#[derive(Debug, Copy, Clone, PartialEq, Hash)]
struct ImageNode {
pub struct ImageNode {
/// The id of the image file.
id: ImageId,
/// The pixel dimensions of the image.
dimensions: (u32, u32),
pub id: ImageId,
/// The fixed width, if any.
width: Option<Linear>,
pub width: Option<Linear>,
/// The fixed height, if any.
height: Option<Linear>,
pub height: Option<Linear>,
}
impl Layout for ImageNode {
fn layout(
&self,
_: &mut LayoutContext,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> {
let Regions { current, base, .. } = regions;
@ -66,8 +27,9 @@ impl Layout for ImageNode {
let width = self.width.map(|w| w.resolve(base.width));
let height = self.height.map(|w| w.resolve(base.height));
let pixel_width = self.dimensions.0 as f64;
let pixel_height = self.dimensions.1 as f64;
let dimensions = ctx.cache.image.get(self.id).buf.dimensions();
let pixel_width = dimensions.0 as f64;
let pixel_height = dimensions.1 as f64;
let pixel_ratio = pixel_width / pixel_height;
let size = match (width, height) {

View File

@ -4,12 +4,14 @@ mod background;
mod fixed;
mod frame;
mod grid;
mod image;
mod incremental;
mod pad;
mod par;
mod shaping;
mod stack;
pub use self::image::*;
pub use background::*;
pub use fixed::*;
pub use frame::*;

View File

@ -1,128 +0,0 @@
use super::*;
/// `align`: Configure the alignment along the layouting axes.
///
/// # Positional parameters
/// - Alignments: variadic, of type `alignment`.
/// - Body: optional, of type `template`.
///
/// # Named parameters
/// - Horizontal alignment: `horizontal`, of type `alignment`.
/// - Vertical alignment: `vertical`, of type `alignment`.
///
/// # Return value
/// A template that changes the alignment along the layouting axes. The effect
/// is scoped to the body if present.
///
/// # Relevant types and constants
/// - Type `alignment`
/// - `start`
/// - `center`
/// - `end`
/// - `left`
/// - `right`
/// - `top`
/// - `bottom`
pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let first = args.eat::<AlignValue>(ctx);
let second = args.eat::<AlignValue>(ctx);
let mut horizontal = args.named::<AlignValue>(ctx, "horizontal");
let mut vertical = args.named::<AlignValue>(ctx, "vertical");
let body = args.eat::<TemplateValue>(ctx);
for value in first.into_iter().chain(second) {
match value.axis() {
Some(SpecAxis::Horizontal) | None if horizontal.is_none() => {
horizontal = Some(value);
}
Some(SpecAxis::Vertical) | None if vertical.is_none() => {
vertical = Some(value);
}
_ => {}
}
}
Value::template("align", move |ctx| {
let snapshot = ctx.state.clone();
if let Some(horizontal) = horizontal {
ctx.state.aligns.cross = horizontal.to_align(ctx.state.lang.dir);
}
if let Some(vertical) = vertical {
ctx.state.aligns.main = vertical.to_align(Dir::TTB);
if ctx.state.aligns.main != snapshot.aligns.main {
ctx.parbreak();
}
}
if let Some(body) = &body {
body.exec(ctx);
ctx.state = snapshot;
}
})
}
/// An alignment specifier passed to `align`.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(super) enum AlignValue {
Start,
Center,
End,
Left,
Right,
Top,
Bottom,
}
impl AlignValue {
fn axis(self) -> Option<SpecAxis> {
match self {
Self::Start => None,
Self::Center => None,
Self::End => None,
Self::Left => Some(SpecAxis::Horizontal),
Self::Right => Some(SpecAxis::Horizontal),
Self::Top => Some(SpecAxis::Vertical),
Self::Bottom => Some(SpecAxis::Vertical),
}
}
fn to_align(self, dir: Dir) -> Align {
let side = |is_at_positive_start| {
if dir.is_positive() == is_at_positive_start {
Align::Start
} else {
Align::End
}
};
match self {
Self::Start => Align::Start,
Self::Center => Align::Center,
Self::End => Align::End,
Self::Left => side(true),
Self::Right => side(false),
Self::Top => side(true),
Self::Bottom => side(false),
}
}
}
impl Display for AlignValue {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Start => "start",
Self::Center => "center",
Self::End => "end",
Self::Left => "left",
Self::Right => "right",
Self::Top => "top",
Self::Bottom => "bottom",
})
}
}
castable! {
AlignValue: "alignment",
}

View File

@ -1,86 +0,0 @@
use crate::exec::{FontState, LineState};
use crate::layout::Fill;
use super::*;
/// `strike`: Enable striken-through text.
///
/// # Named parameters
/// - Color: `color`, of type `color`.
/// - Baseline offset: `position`, of type `linear`.
/// - Strength: `strength`, of type `linear`.
/// - Extent that is applied on either end of the line: `extent`, of type
/// `linear`.
///
/// # Return value
/// A template that enables striken-through text. The effect is scoped to the
/// body if present.
pub fn strike(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
line_impl("strike", ctx, args, |font| &mut font.strikethrough)
}
/// `underline`: Enable underlined text.
///
/// # Named parameters
/// - Color: `color`, of type `color`.
/// - Baseline offset: `position`, of type `linear`.
/// - Strength: `strength`, of type `linear`.
/// - Extent that is applied on either end of the line: `extent`, of type
/// `linear`.
///
/// # Return value
/// A template that enables underlined text. The effect is scoped to the body if
/// present.
pub fn underline(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
line_impl("underline", ctx, args, |font| &mut font.underline)
}
/// `overline`: Add an overline above text.
///
/// # Named parameters
/// - Color: `color`, of type `color`.
/// - Baseline offset: `position`, of type `linear`.
/// - Strength: `strength`, of type `linear`.
/// - Extent that is applied on either end of the line: `extent`, of type
/// `linear`.
///
/// # Return value
/// A template that adds an overline above text. The effect is scoped to the
/// body if present.
pub fn overline(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
line_impl("overline", ctx, args, |font| &mut font.overline)
}
fn line_impl(
name: &str,
ctx: &mut EvalContext,
args: &mut FuncArgs,
substate: fn(&mut FontState) -> &mut Option<Rc<LineState>>,
) -> Value {
let color = args.named(ctx, "color");
let position = args.named(ctx, "position");
let strength = args.named::<Linear>(ctx, "strength");
let extent = args.named(ctx, "extent").unwrap_or_default();
let body = args.eat::<TemplateValue>(ctx);
// Suppress any existing strikethrough if strength is explicitly zero.
let state = strength.map_or(true, |s| !s.is_zero()).then(|| {
Rc::new(LineState {
strength,
position,
extent,
fill: color.map(Fill::Color),
})
});
Value::template(name, move |ctx| {
let snapshot = ctx.state.clone();
*substate(ctx.state.font_mut()) = state.clone();
if let Some(body) = &body {
body.exec(ctx);
ctx.state = snapshot;
}
})
}

View File

@ -4,20 +4,35 @@ use decorum::N64;
use super::*;
use crate::color::Color;
use crate::layout::{BackgroundNode, BackgroundShape, Fill, FixedNode, PadNode};
use crate::layout::{
BackgroundNode, BackgroundShape, Fill, FixedNode, ImageNode, PadNode,
};
/// `image`: An image.
pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let path = args.expect::<Spanned<String>>(ctx, "path to image file");
let width = args.named(ctx, "width");
let height = args.named(ctx, "height");
let mut node = None;
if let Some(path) = &path {
if let Some((resolved, _)) = ctx.resolve(&path.v, path.span) {
if let Some(id) = ctx.cache.image.load(ctx.loader, &resolved) {
node = Some(ImageNode { id, width, height });
} else {
ctx.diag(error!(path.span, "failed to load image"));
}
}
}
Value::template("image", move |ctx| {
if let Some(node) = node {
ctx.push_into_par(node);
}
})
}
/// `rect`: A rectangle with optional content.
///
/// # Positional parameters
/// - Body: optional, of type `template`.
///
/// # Named parameters
/// - Width: `width`, of type `linear` relative to parent width.
/// - Height: `height`, of type `linear` relative to parent height.
/// - Fill color: `fill`, of type `color`.
///
/// # Return value
/// A template that inserts a rectangle and sets the body into it.
pub fn rect(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let width = args.named(ctx, "width");
let height = args.named(ctx, "height");
@ -27,22 +42,6 @@ pub fn rect(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
}
/// `square`: A square with optional content.
///
/// # Positional parameters
/// - Body: optional, of type `template`.
///
/// # Named parameters
/// - Side length: `length`, of type `length`.
/// - Width: `width`, of type `linear` relative to parent width.
/// - Height: `height`, of type `linear` relative to parent height.
/// - Fill color: `fill`, of type `color`.
///
/// Note that you can specify only one of `length`, `width` and `height`. The
/// width and height parameters exist so that you can size the square relative
/// to its parent's size, which isn't possible by setting the side length.
///
/// # Return value
/// A template that inserts a square and sets the body into it.
pub fn square(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let length = args.named::<Length>(ctx, "length").map(Linear::from);
let width = length.or_else(|| args.named(ctx, "width"));
@ -79,17 +78,6 @@ fn rect_impl(
}
/// `ellipse`: An ellipse with optional content.
///
/// # Positional parameters
/// - Body: optional, of type `template`.
///
/// # Named parameters
/// - Width: `width`, of type `linear` relative to parent width.
/// - Height: `height`, of type `linear` relative to parent height.
/// - Fill color: `fill`, of type `color`.
///
/// # Return value
/// A template that inserts an ellipse and sets the body into it.
pub fn ellipse(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let width = args.named(ctx, "width");
let height = args.named(ctx, "height");
@ -99,22 +87,6 @@ pub fn ellipse(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
}
/// `circle`: A circle with optional content.
///
/// # Positional parameters
/// - Body: optional, of type `template`.
///
/// # Named parameters
/// - Radius: `radius`, of type `length`.
/// - Width: `width`, of type `linear` relative to parent width.
/// - Height: `height`, of type `linear` relative to parent height.
/// - Fill color: `fill`, of type `color`.
///
/// Note that you can specify only one of `radius`, `width` and `height`. The
/// width and height parameters exist so that you can size the circle relative
/// to its parent's size, which isn't possible by setting the radius.
///
/// # Return value
/// A template that inserts a circle and sets the body into it.
pub fn circle(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let radius = args.named::<Length>(ctx, "radius").map(|r| 2.0 * Linear::from(r));
let width = radius.or_else(|| args.named(ctx, "width"));

View File

@ -1,89 +0,0 @@
use crate::layout::{GridNode, TrackSizing};
use super::*;
/// `grid`: Arrange children into a grid.
///
/// # Positional parameters
/// - Children: variadic, of type `template`.
///
/// # Named parameters
/// - Column sizing: `columns`, of type `tracks`.
/// - Row sizing: `rows`, of type `tracks`.
/// - Gutter: `gutter`, shorthand for equal gutter everywhere, of type `length`.
/// - Gutter for rows: `gutter-rows`, of type `tracks`.
/// - Gutter for columns: `gutter-columns`, of type `tracks`.
/// - Column direction: `column-dir`, of type `direction`.
/// - Row direction: `row-dir`, of type `direction`.
///
/// # Return value
/// A template that arranges its children along the specified grid cells.
///
/// # Relevant types and constants
/// - Type `tracks`
/// - coerces from `array` of `track-sizing`
/// - Type `track-sizing`
/// - `auto`
// - coerces from `length`
// - coerces from `relative`
// - coerces from `linear`
// - coerces from `fractional`
/// - Type `direction`
/// - `ltr`
/// - `rtl`
/// - `ttb`
/// - `btt`
pub fn grid(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let columns = args.named::<Tracks>(ctx, "columns").unwrap_or_default();
let rows = args.named::<Tracks>(ctx, "rows").unwrap_or_default();
let gutter = args
.named::<Linear>(ctx, "gutter")
.map(|v| vec![TrackSizing::Linear(v)])
.unwrap_or_default();
let gutter_columns = args.named::<Tracks>(ctx, "gutter-columns");
let gutter_rows = args.named::<Tracks>(ctx, "gutter-rows");
let column_dir = args.named(ctx, "column-dir");
let row_dir = args.named(ctx, "row-dir");
let children = args.all::<TemplateValue>(ctx);
Value::template("grid", move |ctx| {
let children = children
.iter()
.map(|child| ctx.exec_template_stack(child).into())
.collect();
let cross_dir = column_dir.unwrap_or(ctx.state.lang.dir);
let main_dir = row_dir.unwrap_or(cross_dir.axis().other().dir(true));
ctx.push_into_stack(GridNode {
dirs: Gen::new(cross_dir, main_dir),
tracks: Gen::new(columns.clone(), rows.clone()),
gutter: Gen::new(
gutter_columns.as_ref().unwrap_or(&gutter).clone(),
gutter_rows.as_ref().unwrap_or(&gutter).clone(),
),
children,
})
})
}
/// Defines size of rows and columns in a grid.
type Tracks = Vec<TrackSizing>;
castable! {
Tracks: "array of `auto`s, linears, and fractionals",
Value::Int(count) => vec![TrackSizing::Auto; count.max(0) as usize],
Value::Array(values) => values
.into_iter()
.filter_map(|v| v.cast().ok())
.collect(),
}
castable! {
TrackSizing: "`auto`, linear, or fractional",
Value::Auto => TrackSizing::Auto,
Value::Length(v) => TrackSizing::Linear(v.into()),
Value::Relative(v) => TrackSizing::Linear(v.into()),
Value::Linear(v) => TrackSizing::Linear(v),
Value::Fractional(v) => TrackSizing::Fractional(v),
}

View File

@ -1,44 +0,0 @@
use super::*;
/// `lang`: Configure the language.
///
/// # Positional parameters
/// - Language: of type `string`. Has to be a valid ISO 639-1 code.
///
/// # Named parameters
/// - Text direction: `dir`, of type `direction`, must be horizontal.
///
/// # Return value
/// A template that configures language properties.
///
/// # Relevant types and constants
/// - Type `direction`
/// - `ltr`
/// - `rtl`
pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let iso = args.eat::<String>(ctx).map(|s| lang_dir(&s));
let dir = match args.named::<Spanned<Dir>>(ctx, "dir") {
Some(dir) if dir.v.axis() == SpecAxis::Horizontal => Some(dir.v),
Some(dir) => {
ctx.diag(error!(dir.span, "must be horizontal"));
None
}
None => None,
};
Value::template("lang", move |ctx| {
if let Some(dir) = dir.or(iso) {
ctx.state.lang.dir = dir;
}
ctx.parbreak();
})
}
/// The default direction for the language identified by `iso`.
fn lang_dir(iso: &str) -> Dir {
match iso.to_ascii_lowercase().as_str() {
"ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL,
"en" | "fr" | "de" | _ => Dir::LTR,
}
}

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

@ -0,0 +1,316 @@
use super::*;
use crate::layout::{GridNode, PadNode, StackChild, StackNode, TrackSizing};
use crate::paper::{Paper, PaperClass};
/// `page`: Configure pages.
pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let paper = args.eat::<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.named(ctx, "width");
let height = args.named(ctx, "height");
let margins = args.named(ctx, "margins");
let left = args.named(ctx, "left");
let top = args.named(ctx, "top");
let right = args.named(ctx, "right");
let bottom = args.named(ctx, "bottom");
let flip = args.named(ctx, "flip");
let body = args.eat::<TemplateValue>(ctx);
let span = args.span;
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();
}
if let Some(width) = width {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.width = width;
}
if let Some(height) = height {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.height = height;
}
if let Some(margins) = margins {
ctx.state.page.margins = Sides::splat(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);
}
ctx.pagebreak(false, true, span);
if let Some(body) = &body {
// TODO: Restrict body to a single page?
body.exec(ctx);
ctx.state = snapshot;
ctx.pagebreak(true, false, span);
}
})
}
/// `pagebreak`: Start a new page.
pub fn pagebreak(_: &mut EvalContext, args: &mut FuncArgs) -> Value {
let span = args.span;
Value::template("pagebreak", move |ctx| {
ctx.pagebreak(true, true, span);
})
}
/// `h`: Horizontal spacing.
pub fn h(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
spacing_impl("h", ctx, args, GenAxis::Cross)
}
/// `v`: Vertical spacing.
pub fn v(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
spacing_impl("v", ctx, args, GenAxis::Main)
}
fn spacing_impl(
name: &str,
ctx: &mut EvalContext,
args: &mut FuncArgs,
axis: GenAxis,
) -> Value {
let spacing: Option<Linear> = args.expect(ctx, "spacing");
Value::template(name, move |ctx| {
if let Some(linear) = spacing {
// TODO: Should this really always be font-size relative?
let amount = linear.resolve(ctx.state.font.size);
ctx.push_spacing(axis, amount);
}
})
}
/// `align`: Configure the alignment along the layouting axes.
pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let first = args.eat::<AlignValue>(ctx);
let second = args.eat::<AlignValue>(ctx);
let mut horizontal = args.named::<AlignValue>(ctx, "horizontal");
let mut vertical = args.named::<AlignValue>(ctx, "vertical");
let body = args.eat::<TemplateValue>(ctx);
for value in first.into_iter().chain(second) {
match value.axis() {
Some(SpecAxis::Horizontal) | None if horizontal.is_none() => {
horizontal = Some(value);
}
Some(SpecAxis::Vertical) | None if vertical.is_none() => {
vertical = Some(value);
}
_ => {}
}
}
Value::template("align", move |ctx| {
let snapshot = ctx.state.clone();
if let Some(horizontal) = horizontal {
ctx.state.aligns.cross = horizontal.to_align(ctx.state.lang.dir);
}
if let Some(vertical) = vertical {
ctx.state.aligns.main = vertical.to_align(Dir::TTB);
if ctx.state.aligns.main != snapshot.aligns.main {
ctx.parbreak();
}
}
if let Some(body) = &body {
body.exec(ctx);
ctx.state = snapshot;
}
})
}
/// An alignment specifier passed to `align`.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(super) enum AlignValue {
Start,
Center,
End,
Left,
Right,
Top,
Bottom,
}
impl AlignValue {
fn axis(self) -> Option<SpecAxis> {
match self {
Self::Start => None,
Self::Center => None,
Self::End => None,
Self::Left => Some(SpecAxis::Horizontal),
Self::Right => Some(SpecAxis::Horizontal),
Self::Top => Some(SpecAxis::Vertical),
Self::Bottom => Some(SpecAxis::Vertical),
}
}
fn to_align(self, dir: Dir) -> Align {
let side = |is_at_positive_start| {
if dir.is_positive() == is_at_positive_start {
Align::Start
} else {
Align::End
}
};
match self {
Self::Start => Align::Start,
Self::Center => Align::Center,
Self::End => Align::End,
Self::Left => side(true),
Self::Right => side(false),
Self::Top => side(true),
Self::Bottom => side(false),
}
}
}
impl Display for AlignValue {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Start => "start",
Self::Center => "center",
Self::End => "end",
Self::Left => "left",
Self::Right => "right",
Self::Top => "top",
Self::Bottom => "bottom",
})
}
}
castable! {
AlignValue: "alignment",
}
/// `pad`: Pad content at the sides.
pub fn pad(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let all = args.eat(ctx);
let left = args.named(ctx, "left");
let top = args.named(ctx, "top");
let right = args.named(ctx, "right");
let bottom = args.named(ctx, "bottom");
let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default();
let padding = Sides::new(
left.or(all).unwrap_or_default(),
top.or(all).unwrap_or_default(),
right.or(all).unwrap_or_default(),
bottom.or(all).unwrap_or_default(),
);
Value::template("pad", move |ctx| {
let child = ctx.exec_template_stack(&body).into();
ctx.push_into_stack(PadNode { padding, child });
})
}
/// `stack`: Stack children along an axis.
pub fn stack(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let dir = args.named::<Dir>(ctx, "dir").unwrap_or(Dir::TTB);
let children = args.all::<TemplateValue>(ctx);
Value::template("stack", move |ctx| {
let children = children
.iter()
.map(|child| {
let child = ctx.exec_template_stack(child).into();
StackChild::Any(child, ctx.state.aligns)
})
.collect();
ctx.push_into_stack(StackNode {
dirs: Gen::new(ctx.state.lang.dir, dir),
aspect: None,
children,
});
})
}
/// `grid`: Arrange children into a grid.
pub fn grid(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let columns = args.named::<Tracks>(ctx, "columns").unwrap_or_default();
let rows = args.named::<Tracks>(ctx, "rows").unwrap_or_default();
let gutter = args
.named::<Linear>(ctx, "gutter")
.map(|v| vec![TrackSizing::Linear(v)])
.unwrap_or_default();
let gutter_columns = args.named::<Tracks>(ctx, "gutter-columns");
let gutter_rows = args.named::<Tracks>(ctx, "gutter-rows");
let column_dir = args.named(ctx, "column-dir");
let row_dir = args.named(ctx, "row-dir");
let children = args.all::<TemplateValue>(ctx);
Value::template("grid", move |ctx| {
let children = children
.iter()
.map(|child| ctx.exec_template_stack(child).into())
.collect();
let cross_dir = column_dir.unwrap_or(ctx.state.lang.dir);
let main_dir = row_dir.unwrap_or(cross_dir.axis().other().dir(true));
ctx.push_into_stack(GridNode {
dirs: Gen::new(cross_dir, main_dir),
tracks: Gen::new(columns.clone(), rows.clone()),
gutter: Gen::new(
gutter_columns.as_ref().unwrap_or(&gutter).clone(),
gutter_rows.as_ref().unwrap_or(&gutter).clone(),
),
children,
})
})
}
/// Defines size of rows and columns in a grid.
type Tracks = Vec<TrackSizing>;
castable! {
Tracks: "array of `auto`s, linears, and fractionals",
Value::Int(count) => vec![TrackSizing::Auto; count.max(0) as usize],
Value::Array(values) => values
.into_iter()
.filter_map(|v| v.cast().ok())
.collect(),
}
castable! {
TrackSizing: "`auto`, linear, or fractional",
Value::Auto => TrackSizing::Auto,
Value::Length(v) => TrackSizing::Linear(v.into()),
Value::Relative(v) => TrackSizing::Linear(v.into()),
Value::Linear(v) => TrackSizing::Linear(v),
Value::Fractional(v) => TrackSizing::Fractional(v),
}

View File

@ -1,57 +0,0 @@
use std::cmp::Ordering;
use super::*;
/// `min`: The minimum of two values.
///
/// # Positional parameters
/// - Values: variadic, must be comparable.
///
/// # Return value
/// The minimum of the sequence of values. For equal elements, the first one is
/// returned.
pub fn min(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
minmax(ctx, args, Ordering::Less)
}
/// `max`: The maximum of two values.
///
/// # Positional parameters
/// - Values: variadic, must be comparable.
///
/// # Return value
/// The maximum of the sequence of values. For equal elements, the first one is
/// returned.
pub fn max(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
minmax(ctx, args, Ordering::Greater)
}
/// Find the minimum or maximum of a sequence of values.
fn minmax(ctx: &mut EvalContext, args: &mut FuncArgs, goal: Ordering) -> Value {
let mut extremum = None;
while let Some(value) = args.eat::<Value>(ctx) {
if let Some(prev) = &extremum {
match value.cmp(&prev) {
Some(ordering) if ordering == goal => extremum = Some(value),
Some(_) => {}
None => {
ctx.diag(error!(
args.span,
"cannot compare {} with {}",
prev.type_name(),
value.type_name(),
));
return Value::Error;
}
}
} else {
extremum = Some(value);
}
}
extremum.unwrap_or_else(|| {
args.expect::<Value>(ctx, "value");
Value::Error
})
}

View File

@ -3,35 +3,15 @@
//! Call [`new`] to obtain a [`Scope`] containing all standard library
//! definitions.
mod align;
mod basic;
mod decorations;
mod font;
mod grid;
mod image;
mod lang;
mod math;
mod pad;
mod page;
mod par;
mod shapes;
mod spacing;
mod stack;
mod elements;
mod layout;
mod text;
mod utility;
pub use self::image::*;
pub use align::*;
pub use basic::*;
pub use decorations::*;
pub use font::*;
pub use grid::*;
pub use lang::*;
pub use math::*;
pub use pad::*;
pub use page::*;
pub use par::*;
pub use shapes::*;
pub use spacing::*;
pub use stack::*;
pub use elements::*;
pub use layout::*;
pub use text::*;
pub use utility::*;
use std::fmt::{self, Display, Formatter};
use std::rc::Rc;
@ -47,32 +27,38 @@ use crate::syntax::Spanned;
pub fn new() -> Scope {
let mut std = Scope::new();
// Library functions.
std.def_func("align", align);
std.def_func("circle", circle);
std.def_func("ellipse", ellipse);
// Text.
std.def_func("font", font);
std.def_func("grid", grid);
std.def_func("h", h);
std.def_func("image", image);
std.def_func("par", par);
std.def_func("lang", lang);
std.def_func("len", len);
std.def_func("max", max);
std.def_func("min", min);
std.def_func("strike", strike);
std.def_func("underline", underline);
std.def_func("overline", overline);
std.def_func("pad", pad);
// Layout.
std.def_func("page", page);
std.def_func("pagebreak", pagebreak);
std.def_func("par", par);
std.def_func("rect", rect);
std.def_func("repr", repr);
std.def_func("rgb", rgb);
std.def_func("square", square);
std.def_func("stack", stack);
std.def_func("strike", strike);
std.def_func("type", type_);
std.def_func("underline", underline);
std.def_func("h", h);
std.def_func("v", v);
std.def_func("align", align);
std.def_func("pad", pad);
std.def_func("stack", stack);
std.def_func("grid", grid);
// Elements.
std.def_func("image", image);
std.def_func("rect", rect);
std.def_func("square", square);
std.def_func("ellipse", ellipse);
std.def_func("circle", circle);
// Utility.
std.def_func("type", type_);
std.def_func("repr", repr);
std.def_func("len", len);
std.def_func("rgb", rgb);
std.def_func("min", min);
std.def_func("max", max);
// Colors.
std.def_const("white", RgbaColor::WHITE);

View File

@ -1,37 +0,0 @@
use super::*;
use crate::layout::PadNode;
/// `pad`: Pad content at the sides.
///
/// # Positional parameters
/// - Padding for all sides: `padding`, of type `linear` relative to sides.
/// - Body: of type `template`.
///
/// # Named parameters
/// - Left padding: `left`, of type `linear` relative to parent width.
/// - Right padding: `right`, of type `linear` relative to parent width.
/// - Top padding: `top`, of type `linear` relative to parent height.
/// - Bottom padding: `bottom`, of type `linear` relative to parent height.
///
/// # Return value
/// A template that pads its region and sets the body into it.
pub fn pad(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let all = args.eat(ctx);
let left = args.named(ctx, "left");
let top = args.named(ctx, "top");
let right = args.named(ctx, "right");
let bottom = args.named(ctx, "bottom");
let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default();
let padding = Sides::new(
left.or(all).unwrap_or_default(),
top.or(all).unwrap_or_default(),
right.or(all).unwrap_or_default(),
bottom.or(all).unwrap_or_default(),
);
Value::template("pad", move |ctx| {
let child = ctx.exec_template_stack(&body).into();
ctx.push_into_stack(PadNode { padding, child });
})
}

View File

@ -1,106 +0,0 @@
use super::*;
use crate::paper::{Paper, PaperClass};
/// `page`: Configure pages.
///
/// # Positional parameters
/// - Paper name: optional, of type `string`, see [here](crate::paper) for a
/// full list of all paper names.
/// - Body: optional, of type `template`.
///
/// # Named parameters
/// - 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`.
///
/// # Return value
/// A template that configures page properties. The effect is scoped to the body
/// if present.
pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let paper = args.eat::<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.named(ctx, "width");
let height = args.named(ctx, "height");
let margins = args.named(ctx, "margins");
let left = args.named(ctx, "left");
let top = args.named(ctx, "top");
let right = args.named(ctx, "right");
let bottom = args.named(ctx, "bottom");
let flip = args.named(ctx, "flip");
let body = args.eat::<TemplateValue>(ctx);
let span = args.span;
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();
}
if let Some(width) = width {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.width = width;
}
if let Some(height) = height {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.height = height;
}
if let Some(margins) = margins {
ctx.state.page.margins = Sides::splat(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);
}
ctx.pagebreak(false, true, span);
if let Some(body) = &body {
// TODO: Restrict body to a single page?
body.exec(ctx);
ctx.state = snapshot;
ctx.pagebreak(true, false, span);
}
})
}
/// `pagebreak`: Start a new page.
///
/// # Return value
/// A template that inserts a page break.
pub fn pagebreak(_: &mut EvalContext, args: &mut FuncArgs) -> Value {
let span = args.span;
Value::template("pagebreak", move |ctx| {
ctx.pagebreak(true, true, span);
})
}

View File

@ -1,32 +0,0 @@
use super::*;
/// `par`: Configure paragraphs.
///
/// # Named parameters
/// - Paragraph spacing: `spacing`, of type `linear` relative to current font size.
/// - Line leading: `leading`, of type `linear` relative to current font size.
/// - Word spacing: `word-spacing`, of type `linear` relative to current font size.
///
/// # Return value
/// A template that configures paragraph properties.
pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let spacing = args.named(ctx, "spacing");
let leading = args.named(ctx, "leading");
let word_spacing = args.named(ctx, "word-spacing");
Value::template("par", move |ctx| {
if let Some(spacing) = spacing {
ctx.state.par.spacing = spacing;
}
if let Some(leading) = leading {
ctx.state.par.leading = leading;
}
if let Some(word_spacing) = word_spacing {
ctx.state.par.word_spacing = word_spacing;
}
ctx.parbreak();
})
}

View File

@ -1,39 +0,0 @@
use super::*;
/// `h`: Horizontal spacing.
///
/// # Positional parameters
/// - Amount of spacing: of type `linear` relative to current font size.
///
/// # Return value
/// A template that inserts horizontal spacing.
pub fn h(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
spacing_impl("h", ctx, args, GenAxis::Cross)
}
/// `v`: Vertical spacing.
///
/// # Positional parameters
/// - Amount of spacing: of type `linear` relative to current font size.
///
/// # Return value
/// A template that inserts vertical spacing.
pub fn v(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
spacing_impl("v", ctx, args, GenAxis::Main)
}
fn spacing_impl(
name: &str,
ctx: &mut EvalContext,
args: &mut FuncArgs,
axis: GenAxis,
) -> Value {
let spacing: Option<Linear> = args.expect(ctx, "spacing");
Value::template(name, move |ctx| {
if let Some(linear) = spacing {
// TODO: Should this really always be font-size relative?
let amount = linear.resolve(ctx.state.font.size);
ctx.push_spacing(axis, amount);
}
})
}

View File

@ -1,40 +0,0 @@
use super::*;
use crate::layout::{StackChild, StackNode};
/// `stack`: Stack children along an axis.
///
/// # Positional parameters
/// - Children: variadic, of type `template`.
///
/// # Named parameters
/// - Stacking direction: `dir`, of type `direction`.
///
/// # Return value
/// A template that places its children along the specified layouting axis.
///
/// # Relevant types and constants
/// - Type `direction`
/// - `ltr`
/// - `rtl`
/// - `ttb`
/// - `btt`
pub fn stack(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let dir = args.named::<Dir>(ctx, "dir").unwrap_or(Dir::TTB);
let children = args.all::<TemplateValue>(ctx);
Value::template("stack", move |ctx| {
let children = children
.iter()
.map(|child| {
let child = ctx.exec_template_stack(child).into();
StackChild::Any(child, ctx.state.aligns)
})
.collect();
ctx.push_into_stack(StackNode {
dirs: Gen::new(ctx.state.lang.dir, dir),
aspect: None,
children,
});
})
}

View File

@ -1,53 +1,10 @@
use crate::exec::{FontState, LineState};
use crate::font::{FontStretch, FontStyle, FontWeight};
use crate::layout::Fill;
use super::*;
/// `font`: Configure the font.
///
/// # Positional parameters
/// - Body: optional, of type `template`.
///
/// # Named parameters
/// - Font size: `size`, of type `linear` relative to current font size.
/// - Font families: `family`, `font-family`, `string` or `array`.
/// - Font Style: `style`, of type `font-style`.
/// - Font Weight: `weight`, of type `font-weight`.
/// - Font Stretch: `stretch`, of type `relative`, between 0.5 and 2.0.
/// - Top edge of the font: `top-edge`, of type `vertical-font-metric`.
/// - Bottom edge of the font: `bottom-edge`, of type `vertical-font-metric`.
/// - Color the glyphs: `color`, of type `color`.
/// - Serif family definition: `serif`, of type `family-def`.
/// - Sans-serif family definition: `sans-serif`, of type `family-def`.
/// - Monospace family definition: `monospace`, of type `family-def`.
///
/// # Return value
/// A template that configures font properties. The effect is scoped to the body
/// if present.
///
/// # Relevant types and constants
/// - Type `font-family`
/// - `serif`
/// - `sans-serif`
/// - `monospace`
/// - coerces from `string`
/// - Type `family-def`
/// - coerces from `string`
/// - coerces from `array` of `string`
/// - Type `font-style`
/// - `normal`
/// - `italic`
/// - `oblique`
/// - Type `font-weight`
/// - `regular` (400)
/// - `bold` (700)
/// - coerces from `integer`, between 100 and 900
/// - Type `vertical-font-metric`
/// - `ascender`
/// - `cap-height`
/// - `x-height`
/// - `baseline`
/// - `descender`
pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let list = args.named(ctx, "family");
let size = args.named::<Linear>(ctx, "size");
@ -198,3 +155,104 @@ castable! {
castable! {
VerticalFontMetric: "vertical font metric",
}
/// `par`: Configure paragraphs.
pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let spacing = args.named(ctx, "spacing");
let leading = args.named(ctx, "leading");
let word_spacing = args.named(ctx, "word-spacing");
Value::template("par", move |ctx| {
if let Some(spacing) = spacing {
ctx.state.par.spacing = spacing;
}
if let Some(leading) = leading {
ctx.state.par.leading = leading;
}
if let Some(word_spacing) = word_spacing {
ctx.state.par.word_spacing = word_spacing;
}
ctx.parbreak();
})
}
/// `lang`: Configure the language.
pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let iso = args.eat::<String>(ctx).map(|s| lang_dir(&s));
let dir = match args.named::<Spanned<Dir>>(ctx, "dir") {
Some(dir) if dir.v.axis() == SpecAxis::Horizontal => Some(dir.v),
Some(dir) => {
ctx.diag(error!(dir.span, "must be horizontal"));
None
}
None => None,
};
Value::template("lang", move |ctx| {
if let Some(dir) = dir.or(iso) {
ctx.state.lang.dir = dir;
}
ctx.parbreak();
})
}
/// The default direction for the language identified by `iso`.
fn lang_dir(iso: &str) -> Dir {
match iso.to_ascii_lowercase().as_str() {
"ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL,
"en" | "fr" | "de" | _ => Dir::LTR,
}
}
/// `strike`: Enable striken-through text.
pub fn strike(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
line_impl("strike", ctx, args, |font| &mut font.strikethrough)
}
/// `underline`: Enable underlined text.
pub fn underline(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
line_impl("underline", ctx, args, |font| &mut font.underline)
}
/// `overline`: Add an overline above text.
pub fn overline(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
line_impl("overline", ctx, args, |font| &mut font.overline)
}
fn line_impl(
name: &str,
ctx: &mut EvalContext,
args: &mut FuncArgs,
substate: fn(&mut FontState) -> &mut Option<Rc<LineState>>,
) -> Value {
let color = args.named(ctx, "color");
let position = args.named(ctx, "position");
let strength = args.named::<Linear>(ctx, "strength");
let extent = args.named(ctx, "extent").unwrap_or_default();
let body = args.eat::<TemplateValue>(ctx);
// Suppress any existing strikethrough if strength is explicitly zero.
let state = strength.map_or(true, |s| !s.is_zero()).then(|| {
Rc::new(LineState {
strength,
position,
extent,
fill: color.map(Fill::Color),
})
});
Value::template(name, move |ctx| {
let snapshot = ctx.state.clone();
*substate(ctx.state.font_mut()) = state.clone();
if let Some(body) = &body {
body.exec(ctx);
ctx.state = snapshot;
}
})
}

View File

@ -1,15 +1,11 @@
use std::cmp::Ordering;
use crate::color::{Color, RgbaColor};
use crate::pretty::pretty;
use super::*;
/// `type`: The name of a value's type.
///
/// # Positional parameters
/// - Any value.
///
/// # Return value
/// The name of the value's type as a string.
pub fn type_(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
match args.expect::<Value>(ctx, "value") {
Some(value) => value.type_name().into(),
@ -18,12 +14,6 @@ pub fn type_(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
}
/// `repr`: The string representation of a value.
///
/// # Positional parameters
/// - Any value.
///
/// # Return value
/// The string representation of the value.
pub fn repr(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
match args.expect::<Value>(ctx, "value") {
Some(value) => pretty(&value).into(),
@ -46,15 +36,6 @@ pub fn len(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
}
/// `rgb`: Create an RGB(A) color.
///
/// # Positional parameters
/// - 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.
///
/// # Return value
/// The color with the given components.
pub fn rgb(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let r = args.expect(ctx, "red component");
let g = args.expect(ctx, "green component");
@ -77,3 +58,43 @@ pub fn rgb(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
clamp(a, 255),
)))
}
/// `min`: The minimum of a sequence of values.
pub fn min(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
minmax(ctx, args, Ordering::Less)
}
/// `max`: The maximum of a sequence of values.
pub fn max(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
minmax(ctx, args, Ordering::Greater)
}
/// Find the minimum or maximum of a sequence of values.
fn minmax(ctx: &mut EvalContext, args: &mut FuncArgs, goal: Ordering) -> Value {
let mut extremum = None;
while let Some(value) = args.eat::<Value>(ctx) {
if let Some(prev) = &extremum {
match value.cmp(&prev) {
Some(ordering) if ordering == goal => extremum = Some(value),
Some(_) => {}
None => {
ctx.diag(error!(
args.span,
"cannot compare {} with {}",
prev.type_name(),
value.type_name(),
));
return Value::Error;
}
}
} else {
extremum = Some(value);
}
}
extremum.unwrap_or_else(|| {
args.expect::<Value>(ctx, "value");
Value::Error
})
}

View File

@ -30,68 +30,6 @@ pub enum Node {
}
/// A raw block with optional syntax highlighting: `` `...` ``.
///
/// Raw blocks start with 1 or 3+ backticks and end with the same number of
/// backticks.
///
/// When using at least three backticks, an optional language tag may follow
/// directly after the backticks. This tag defines which language to
/// syntax-highlight the text in. Apart from the language tag and some
/// whitespace trimming discussed below, everything inside a raw block is
/// rendered verbatim, in particular, there are no escape sequences.
///
/// # Examples
/// - Raw text is surrounded by backticks.
/// ```typst
/// `raw`
/// ```
/// - An optional language tag may follow directly at the start when the block
/// is surrounded by at least three backticks.
/// ````typst
/// ```rust println!("hello!")```;
/// ````
/// - Blocks can span multiple lines.
/// ````typst
/// ```rust
/// loop {
/// find_yak().shave();
/// }
/// ```
/// ````
/// - Start with a space to omit the language tag (the space will be trimmed
/// from the output).
/// `````typst
/// ```` This has no leading space.````
/// `````
/// - Use more backticks to allow backticks in the raw text.
/// `````typst
/// ```` This contains ```backticks```.````
/// `````
///
/// # Trimming
/// If we would always render the raw text between the backticks exactly as
/// given, some things would become cumbersome/impossible to write:
/// - Typical multiline code blocks (like in the example above) would have an
/// additional newline before and after the code.
/// - Multi-line blocks would need to start with a space since a word would be
/// interpreted as a language tag.
/// - Text ending with a backtick would be impossible since the backtick would
/// be interpreted as belonging to the closing backticks.
///
/// To fix these problems, we sometimes trim a bit of space from blocks with 3+
/// backticks:
/// - At the start, we trim a single space or a sequence of whitespace followed
/// by a newline.
/// - At the end, we trim
/// - a single space if the raw text ends with a backtick followed only by
/// whitespace,
/// - a newline followed by a sequence of whitespace.
///
/// You can thus produce a single backtick without surrounding spaces with the
/// sequence ```` ``` ` ``` ````.
///
/// Note that with these rules you can always force leading or trailing
/// whitespace simply by adding more spaces.
#[derive(Debug, Clone, PartialEq)]
pub struct RawNode {
/// The source code location.