mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Cleanse library
- Remove doc-comments for Typst functions from library - Reduce number of library source files
This commit is contained in:
parent
63cf361496
commit
285c2f617b
@ -1,62 +1,23 @@
|
|||||||
use ::image::GenericImageView;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::image::ImageId;
|
use crate::image::ImageId;
|
||||||
use crate::layout::{
|
|
||||||
AnyNode, Constrained, Constraints, Element, Frame, Layout, LayoutContext, Regions,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// `image`: An image.
|
use ::image::GenericImageView;
|
||||||
///
|
|
||||||
/// 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);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An image node.
|
/// An image node.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Hash)]
|
||||||
struct ImageNode {
|
pub struct ImageNode {
|
||||||
/// The id of the image file.
|
/// The id of the image file.
|
||||||
id: ImageId,
|
pub id: ImageId,
|
||||||
/// The pixel dimensions of the image.
|
|
||||||
dimensions: (u32, u32),
|
|
||||||
/// The fixed width, if any.
|
/// The fixed width, if any.
|
||||||
width: Option<Linear>,
|
pub width: Option<Linear>,
|
||||||
/// The fixed height, if any.
|
/// The fixed height, if any.
|
||||||
height: Option<Linear>,
|
pub height: Option<Linear>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ImageNode {
|
impl Layout for ImageNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
_: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let Regions { current, base, .. } = regions;
|
let Regions { current, base, .. } = regions;
|
||||||
@ -66,8 +27,9 @@ impl Layout for ImageNode {
|
|||||||
let width = self.width.map(|w| w.resolve(base.width));
|
let width = self.width.map(|w| w.resolve(base.width));
|
||||||
let height = self.height.map(|w| w.resolve(base.height));
|
let height = self.height.map(|w| w.resolve(base.height));
|
||||||
|
|
||||||
let pixel_width = self.dimensions.0 as f64;
|
let dimensions = ctx.cache.image.get(self.id).buf.dimensions();
|
||||||
let pixel_height = self.dimensions.1 as f64;
|
let pixel_width = dimensions.0 as f64;
|
||||||
|
let pixel_height = dimensions.1 as f64;
|
||||||
let pixel_ratio = pixel_width / pixel_height;
|
let pixel_ratio = pixel_width / pixel_height;
|
||||||
|
|
||||||
let size = match (width, height) {
|
let size = match (width, height) {
|
@ -4,12 +4,14 @@ mod background;
|
|||||||
mod fixed;
|
mod fixed;
|
||||||
mod frame;
|
mod frame;
|
||||||
mod grid;
|
mod grid;
|
||||||
|
mod image;
|
||||||
mod incremental;
|
mod incremental;
|
||||||
mod pad;
|
mod pad;
|
||||||
mod par;
|
mod par;
|
||||||
mod shaping;
|
mod shaping;
|
||||||
mod stack;
|
mod stack;
|
||||||
|
|
||||||
|
pub use self::image::*;
|
||||||
pub use background::*;
|
pub use background::*;
|
||||||
pub use fixed::*;
|
pub use fixed::*;
|
||||||
pub use frame::*;
|
pub use frame::*;
|
||||||
|
@ -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",
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
@ -4,20 +4,35 @@ use decorum::N64;
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::color::Color;
|
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.
|
/// `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 {
|
pub fn rect(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
let width = args.named(ctx, "width");
|
let width = args.named(ctx, "width");
|
||||||
let height = args.named(ctx, "height");
|
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.
|
/// `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 {
|
pub fn square(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
let length = args.named::<Length>(ctx, "length").map(Linear::from);
|
let length = args.named::<Length>(ctx, "length").map(Linear::from);
|
||||||
let width = length.or_else(|| args.named(ctx, "width"));
|
let width = length.or_else(|| args.named(ctx, "width"));
|
||||||
@ -79,17 +78,6 @@ fn rect_impl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `ellipse`: An ellipse with optional content.
|
/// `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 {
|
pub fn ellipse(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
let width = args.named(ctx, "width");
|
let width = args.named(ctx, "width");
|
||||||
let height = args.named(ctx, "height");
|
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.
|
/// `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 {
|
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 radius = args.named::<Length>(ctx, "radius").map(|r| 2.0 * Linear::from(r));
|
||||||
let width = radius.or_else(|| args.named(ctx, "width"));
|
let width = radius.or_else(|| args.named(ctx, "width"));
|
@ -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),
|
|
||||||
}
|
|
@ -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
316
src/library/layout.rs
Normal 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),
|
||||||
|
}
|
@ -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
|
|
||||||
})
|
|
||||||
}
|
|
@ -3,35 +3,15 @@
|
|||||||
//! Call [`new`] to obtain a [`Scope`] containing all standard library
|
//! Call [`new`] to obtain a [`Scope`] containing all standard library
|
||||||
//! definitions.
|
//! definitions.
|
||||||
|
|
||||||
mod align;
|
mod elements;
|
||||||
mod basic;
|
mod layout;
|
||||||
mod decorations;
|
mod text;
|
||||||
mod font;
|
mod utility;
|
||||||
mod grid;
|
|
||||||
mod image;
|
|
||||||
mod lang;
|
|
||||||
mod math;
|
|
||||||
mod pad;
|
|
||||||
mod page;
|
|
||||||
mod par;
|
|
||||||
mod shapes;
|
|
||||||
mod spacing;
|
|
||||||
mod stack;
|
|
||||||
|
|
||||||
pub use self::image::*;
|
pub use elements::*;
|
||||||
pub use align::*;
|
pub use layout::*;
|
||||||
pub use basic::*;
|
pub use text::*;
|
||||||
pub use decorations::*;
|
pub use utility::*;
|
||||||
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::*;
|
|
||||||
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@ -47,32 +27,38 @@ use crate::syntax::Spanned;
|
|||||||
pub fn new() -> Scope {
|
pub fn new() -> Scope {
|
||||||
let mut std = Scope::new();
|
let mut std = Scope::new();
|
||||||
|
|
||||||
// Library functions.
|
// Text.
|
||||||
std.def_func("align", align);
|
|
||||||
std.def_func("circle", circle);
|
|
||||||
std.def_func("ellipse", ellipse);
|
|
||||||
std.def_func("font", font);
|
std.def_func("font", font);
|
||||||
std.def_func("grid", grid);
|
std.def_func("par", par);
|
||||||
std.def_func("h", h);
|
|
||||||
std.def_func("image", image);
|
|
||||||
std.def_func("lang", lang);
|
std.def_func("lang", lang);
|
||||||
std.def_func("len", len);
|
std.def_func("strike", strike);
|
||||||
std.def_func("max", max);
|
std.def_func("underline", underline);
|
||||||
std.def_func("min", min);
|
|
||||||
std.def_func("overline", overline);
|
std.def_func("overline", overline);
|
||||||
std.def_func("pad", pad);
|
|
||||||
|
// Layout.
|
||||||
std.def_func("page", page);
|
std.def_func("page", page);
|
||||||
std.def_func("pagebreak", pagebreak);
|
std.def_func("pagebreak", pagebreak);
|
||||||
std.def_func("par", par);
|
std.def_func("h", h);
|
||||||
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("v", v);
|
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.
|
// Colors.
|
||||||
std.def_const("white", RgbaColor::WHITE);
|
std.def_const("white", RgbaColor::WHITE);
|
||||||
|
@ -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 });
|
|
||||||
})
|
|
||||||
}
|
|
@ -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);
|
|
||||||
})
|
|
||||||
}
|
|
@ -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();
|
|
||||||
})
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
@ -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,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,53 +1,10 @@
|
|||||||
|
use crate::exec::{FontState, LineState};
|
||||||
use crate::font::{FontStretch, FontStyle, FontWeight};
|
use crate::font::{FontStretch, FontStyle, FontWeight};
|
||||||
use crate::layout::Fill;
|
use crate::layout::Fill;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// `font`: Configure the font.
|
/// `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 {
|
pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
let list = args.named(ctx, "family");
|
let list = args.named(ctx, "family");
|
||||||
let size = args.named::<Linear>(ctx, "size");
|
let size = args.named::<Linear>(ctx, "size");
|
||||||
@ -198,3 +155,104 @@ castable! {
|
|||||||
castable! {
|
castable! {
|
||||||
VerticalFontMetric: "vertical font metric",
|
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;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -1,15 +1,11 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use crate::color::{Color, RgbaColor};
|
use crate::color::{Color, RgbaColor};
|
||||||
use crate::pretty::pretty;
|
use crate::pretty::pretty;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// `type`: The name of a value's type.
|
/// `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 {
|
pub fn type_(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
match args.expect::<Value>(ctx, "value") {
|
match args.expect::<Value>(ctx, "value") {
|
||||||
Some(value) => value.type_name().into(),
|
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.
|
/// `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 {
|
pub fn repr(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
match args.expect::<Value>(ctx, "value") {
|
match args.expect::<Value>(ctx, "value") {
|
||||||
Some(value) => pretty(&value).into(),
|
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.
|
/// `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 {
|
pub fn rgb(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
let r = args.expect(ctx, "red component");
|
let r = args.expect(ctx, "red component");
|
||||||
let g = args.expect(ctx, "green 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),
|
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
|
||||||
|
})
|
||||||
|
}
|
@ -30,68 +30,6 @@ pub enum Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A raw block with optional syntax highlighting: `` `...` ``.
|
/// 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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct RawNode {
|
pub struct RawNode {
|
||||||
/// The source code location.
|
/// The source code location.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user