mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +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 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) {
|
@ -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::*;
|
||||
|
@ -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 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"));
|
@ -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
|
||||
//! 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);
|
||||
|
@ -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::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;
|
||||
}
|
||||
})
|
||||
}
|
@ -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
|
||||
})
|
||||
}
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user