mirror of
https://github.com/typst/typst
synced 2025-05-22 04:55:29 +08:00
Merge some modules 🥞
This commit is contained in:
parent
761931405c
commit
11e44516fa
@ -1,37 +0,0 @@
|
|||||||
use super::*;
|
|
||||||
|
|
||||||
/// The top-level layout node.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct Document {
|
|
||||||
/// The runs of pages with same properties.
|
|
||||||
pub runs: Vec<Pages>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Document {
|
|
||||||
/// Layout the document.
|
|
||||||
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> {
|
|
||||||
let mut layouts = vec![];
|
|
||||||
for run in &self.runs {
|
|
||||||
layouts.extend(run.layout(ctx));
|
|
||||||
}
|
|
||||||
layouts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A variable-length run of pages that all have the same properties.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct Pages {
|
|
||||||
/// The size of the pages.
|
|
||||||
pub size: Size,
|
|
||||||
/// The layout node that produces the actual pages (typically a [`Stack`]).
|
|
||||||
pub child: LayoutNode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pages {
|
|
||||||
/// Layout the page run.
|
|
||||||
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> {
|
|
||||||
let areas = Areas::repeat(self.size);
|
|
||||||
let layouted = self.child.layout(ctx, &areas);
|
|
||||||
layouted.into_layouts()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,6 @@
|
|||||||
//! Layouting of documents.
|
//! Layouting of documents.
|
||||||
|
|
||||||
mod document;
|
|
||||||
mod fixed;
|
mod fixed;
|
||||||
mod graphics;
|
|
||||||
mod node;
|
mod node;
|
||||||
mod pad;
|
mod pad;
|
||||||
mod par;
|
mod par;
|
||||||
@ -16,9 +14,7 @@ use crate::font::SharedFontLoader;
|
|||||||
use crate::geom::*;
|
use crate::geom::*;
|
||||||
use crate::shaping::Shaped;
|
use crate::shaping::Shaped;
|
||||||
|
|
||||||
pub use document::*;
|
|
||||||
pub use fixed::*;
|
pub use fixed::*;
|
||||||
pub use graphics::*;
|
|
||||||
pub use node::*;
|
pub use node::*;
|
||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
pub use par::*;
|
pub use par::*;
|
||||||
@ -193,3 +189,35 @@ pub struct ImageElement {
|
|||||||
/// The document size of the image.
|
/// The document size of the image.
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The top-level layout node.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Document {
|
||||||
|
/// The runs of pages with same properties.
|
||||||
|
pub runs: Vec<Pages>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Document {
|
||||||
|
/// Layout the document.
|
||||||
|
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> {
|
||||||
|
self.runs.iter().flat_map(|run| run.layout(ctx)).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A variable-length run of pages that all have the same properties.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Pages {
|
||||||
|
/// The size of each page.
|
||||||
|
pub size: Size,
|
||||||
|
/// The layout node that produces the actual pages (typically a [`Stack`]).
|
||||||
|
pub child: LayoutNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pages {
|
||||||
|
/// Layout the page run.
|
||||||
|
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> {
|
||||||
|
let areas = Areas::repeat(self.size);
|
||||||
|
let layouted = self.child.layout(ctx, &areas);
|
||||||
|
layouted.into_layouts()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -74,12 +74,6 @@ impl Debug for Dynamic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Dynamic> for LayoutNode {
|
|
||||||
fn from(dynamic: Dynamic) -> Self {
|
|
||||||
Self::Dyn(dynamic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Dynamic {
|
impl Clone for Dynamic {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self(self.0.dyn_clone())
|
Self(self.0.dyn_clone())
|
||||||
@ -92,6 +86,12 @@ impl PartialEq for Dynamic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Dynamic> for LayoutNode {
|
||||||
|
fn from(dynamic: Dynamic) -> Self {
|
||||||
|
Self::Dyn(dynamic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A dynamic node, which can implement custom layouting behaviour.
|
/// A dynamic node, which can implement custom layouting behaviour.
|
||||||
///
|
///
|
||||||
/// This trait just combines the requirements for types to qualify as dynamic
|
/// This trait just combines the requirements for types to qualify as dynamic
|
||||||
|
@ -29,6 +29,12 @@ impl Layout for Pad {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Pad> for LayoutNode {
|
||||||
|
fn from(pad: Pad) -> Self {
|
||||||
|
Self::dynamic(pad)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Shrink all areas by the padding.
|
/// Shrink all areas by the padding.
|
||||||
fn shrink_areas(areas: &Areas, padding: Sides<Linear>) -> Areas {
|
fn shrink_areas(areas: &Areas, padding: Sides<Linear>) -> Areas {
|
||||||
let shrink = |size| size - padding.resolve(size).size();
|
let shrink = |size| size - padding.resolve(size).size();
|
||||||
@ -52,9 +58,3 @@ fn pad_layout(layout: &mut BoxLayout, padding: Sides<Linear>) {
|
|||||||
*point += origin;
|
*point += origin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Pad> for LayoutNode {
|
|
||||||
fn from(pad: Pad) -> Self {
|
|
||||||
Self::dynamic(pad)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,185 +0,0 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
|
||||||
|
|
||||||
/// `align`: Align content along the layouting axes.
|
|
||||||
///
|
|
||||||
/// # Positional arguments
|
|
||||||
/// - At most two of `left`, `right`, `top`, `bottom`, `center`.
|
|
||||||
///
|
|
||||||
/// When `center` is used as a positional argument, it is automatically inferred
|
|
||||||
/// which axis it should apply to depending on further arguments, defaulting
|
|
||||||
/// to the axis, text is set along.
|
|
||||||
///
|
|
||||||
/// # Keyword arguments
|
|
||||||
/// - `horizontal`: Any of `left`, `right` or `center`.
|
|
||||||
/// - `vertical`: Any of `top`, `bottom` or `center`.
|
|
||||||
///
|
|
||||||
/// There may not be two alignment specifications for the same axis.
|
|
||||||
pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
|
|
||||||
let snapshot = ctx.state.clone();
|
|
||||||
let body = args.find::<SynTree>();
|
|
||||||
let first = args.get::<_, Spanned<AlignArg>>(ctx, 0);
|
|
||||||
let second = args.get::<_, Spanned<AlignArg>>(ctx, 1);
|
|
||||||
let hor = args.get::<_, Spanned<AlignArg>>(ctx, "horizontal");
|
|
||||||
let ver = args.get::<_, Spanned<AlignArg>>(ctx, "vertical");
|
|
||||||
args.done(ctx);
|
|
||||||
|
|
||||||
let iter = first
|
|
||||||
.into_iter()
|
|
||||||
.chain(second.into_iter())
|
|
||||||
.map(|align| (align.v.axis(), align))
|
|
||||||
.chain(hor.into_iter().map(|align| (Some(SpecAxis::Horizontal), align)))
|
|
||||||
.chain(ver.into_iter().map(|align| (Some(SpecAxis::Vertical), align)));
|
|
||||||
|
|
||||||
let align = dedup_aligns(ctx, iter);
|
|
||||||
let ends_par = align.main != ctx.state.align.main;
|
|
||||||
ctx.state.align = align;
|
|
||||||
|
|
||||||
if ends_par {
|
|
||||||
ctx.end_par_group();
|
|
||||||
ctx.start_par_group();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(body) = body {
|
|
||||||
body.eval(ctx);
|
|
||||||
ctx.state = snapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deduplicate alignments and deduce to which axes they apply.
|
|
||||||
fn dedup_aligns(
|
|
||||||
ctx: &mut EvalContext,
|
|
||||||
iter: impl Iterator<Item = (Option<SpecAxis>, Spanned<AlignArg>)>,
|
|
||||||
) -> BoxAlign {
|
|
||||||
let mut alignments = ctx.state.align;
|
|
||||||
let mut had = Gen::uniform(false);
|
|
||||||
let mut had_center = false;
|
|
||||||
|
|
||||||
for (axis, Spanned { v: align, span }) in iter {
|
|
||||||
// Check whether we know which axis this alignment belongs to.
|
|
||||||
if let Some(axis) = axis {
|
|
||||||
// We know the axis.
|
|
||||||
let gen_axis = axis.switch(ctx.state.flow);
|
|
||||||
let gen_align = align.switch(ctx.state.flow);
|
|
||||||
|
|
||||||
if align.axis().map_or(false, |a| a != axis) {
|
|
||||||
ctx.diag(error!(
|
|
||||||
span,
|
|
||||||
"invalid alignment `{}` for {} axis", align, axis,
|
|
||||||
));
|
|
||||||
} else if had.get(gen_axis) {
|
|
||||||
ctx.diag(error!(span, "duplicate alignment for {} axis", axis));
|
|
||||||
} else {
|
|
||||||
*alignments.get_mut(gen_axis) = gen_align;
|
|
||||||
*had.get_mut(gen_axis) = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We don't know the axis: This has to be a `center` alignment for a
|
|
||||||
// positional argument.
|
|
||||||
debug_assert_eq!(align, AlignArg::Center);
|
|
||||||
|
|
||||||
if had.main && had.cross {
|
|
||||||
ctx.diag(error!(span, "duplicate alignment"));
|
|
||||||
} else if had_center {
|
|
||||||
// Both this and the previous one are unspecified `center`
|
|
||||||
// alignments. Both axes should be centered.
|
|
||||||
alignments = BoxAlign::new(Align::Center, Align::Center);
|
|
||||||
had = Gen::uniform(true);
|
|
||||||
} else {
|
|
||||||
had_center = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we we know one alignment, we can handle the unspecified `center`
|
|
||||||
// alignment.
|
|
||||||
if had_center && (had.main || had.cross) {
|
|
||||||
if had.main {
|
|
||||||
alignments.cross = Align::Center;
|
|
||||||
had.cross = true;
|
|
||||||
} else {
|
|
||||||
alignments.main = Align::Center;
|
|
||||||
had.main = true;
|
|
||||||
}
|
|
||||||
had_center = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If center has not been flushed by now, it is the only argument and then
|
|
||||||
// we default to applying it to the cross axis.
|
|
||||||
if had_center {
|
|
||||||
alignments.cross = Align::Center;
|
|
||||||
}
|
|
||||||
|
|
||||||
alignments
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An alignment argument.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
|
||||||
enum AlignArg {
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Top,
|
|
||||||
Bottom,
|
|
||||||
Center,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AlignArg {
|
|
||||||
/// The specific axis this alignment refers to.
|
|
||||||
///
|
|
||||||
/// Returns `None` if this is `Center` since the axis is unknown.
|
|
||||||
pub fn axis(self) -> Option<SpecAxis> {
|
|
||||||
match self {
|
|
||||||
Self::Left => Some(SpecAxis::Horizontal),
|
|
||||||
Self::Right => Some(SpecAxis::Horizontal),
|
|
||||||
Self::Top => Some(SpecAxis::Vertical),
|
|
||||||
Self::Bottom => Some(SpecAxis::Vertical),
|
|
||||||
Self::Center => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Switch for AlignArg {
|
|
||||||
type Other = Align;
|
|
||||||
|
|
||||||
fn switch(self, flow: Flow) -> Self::Other {
|
|
||||||
let get = |dir: Dir, at_positive_start| {
|
|
||||||
if dir.is_positive() == at_positive_start {
|
|
||||||
Align::Start
|
|
||||||
} else {
|
|
||||||
Align::End
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let flow = flow.switch(flow);
|
|
||||||
match self {
|
|
||||||
Self::Left => get(flow.horizontal, true),
|
|
||||||
Self::Right => get(flow.horizontal, false),
|
|
||||||
Self::Top => get(flow.vertical, true),
|
|
||||||
Self::Bottom => get(flow.vertical, false),
|
|
||||||
Self::Center => Align::Center,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
convert_ident!(AlignArg, "alignment", |v| match v {
|
|
||||||
"left" => Some(Self::Left),
|
|
||||||
"right" => Some(Self::Right),
|
|
||||||
"top" => Some(Self::Top),
|
|
||||||
"bottom" => Some(Self::Bottom),
|
|
||||||
"center" => Some(Self::Center),
|
|
||||||
_ => None,
|
|
||||||
});
|
|
||||||
|
|
||||||
impl Display for AlignArg {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.pad(match self {
|
|
||||||
Self::Left => "left",
|
|
||||||
Self::Right => "right",
|
|
||||||
Self::Top => "top",
|
|
||||||
Self::Bottom => "bottom",
|
|
||||||
Self::Center => "center",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
use crate::geom::Linear;
|
|
||||||
use crate::layout::{Expansion, Fixed, Stack};
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
/// `box`: Layouts its contents into a box.
|
|
||||||
///
|
|
||||||
/// # Keyword arguments
|
|
||||||
/// - `width`: The width of the box (length or relative to parent's width).
|
|
||||||
/// - `height`: The height of the box (length or relative to parent's height).
|
|
||||||
pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
|
|
||||||
let snapshot = ctx.state.clone();
|
|
||||||
let body = args.find::<SynTree>().unwrap_or_default();
|
|
||||||
let width = args.get::<_, Linear>(ctx, "width");
|
|
||||||
let height = args.get::<_, Linear>(ctx, "height");
|
|
||||||
let main = args.get::<_, Spanned<Dir>>(ctx, "main");
|
|
||||||
let cross = args.get::<_, Spanned<Dir>>(ctx, "cross");
|
|
||||||
ctx.set_flow(Gen::new(main, cross));
|
|
||||||
args.done(ctx);
|
|
||||||
|
|
||||||
let flow = ctx.state.flow;
|
|
||||||
let align = ctx.state.align;
|
|
||||||
|
|
||||||
ctx.start_content_group();
|
|
||||||
body.eval(ctx);
|
|
||||||
let children = ctx.end_content_group();
|
|
||||||
|
|
||||||
ctx.push(Fixed {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
child: LayoutNode::dynamic(Stack {
|
|
||||||
flow,
|
|
||||||
align,
|
|
||||||
expansion: Spec::new(
|
|
||||||
Expansion::fill_if(width.is_some()),
|
|
||||||
Expansion::fill_if(height.is_some()),
|
|
||||||
)
|
|
||||||
.switch(flow),
|
|
||||||
children,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.state = snapshot;
|
|
||||||
Value::None
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
use crate::color::RgbaColor;
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
/// `rgb`: Create an RGB(A) color.
|
|
||||||
pub fn rgb(mut args: Args, ctx: &mut EvalContext) -> Value {
|
|
||||||
let r = args.need::<_, Spanned<i64>>(ctx, 0, "red value");
|
|
||||||
let g = args.need::<_, Spanned<i64>>(ctx, 1, "green value");
|
|
||||||
let b = args.need::<_, Spanned<i64>>(ctx, 2, "blue value");
|
|
||||||
let a = args.get::<_, Spanned<i64>>(ctx, 3);
|
|
||||||
args.done(ctx);
|
|
||||||
|
|
||||||
let mut clamp = |component: Option<Spanned<i64>>, default| {
|
|
||||||
component.map_or(default, |c| {
|
|
||||||
if c.v < 0 || c.v > 255 {
|
|
||||||
ctx.diag(error!(c.span, "should be between 0 and 255"));
|
|
||||||
}
|
|
||||||
c.v.max(0).min(255) as u8
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
Value::Color(RgbaColor::new(
|
|
||||||
clamp(r, 0),
|
|
||||||
clamp(g, 0),
|
|
||||||
clamp(b, 0),
|
|
||||||
clamp(a, 255),
|
|
||||||
))
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::io::BufReader;
|
|
||||||
|
|
||||||
use image::io::Reader;
|
|
||||||
|
|
||||||
use crate::layout::Image;
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
/// `image`: Include an image.
|
|
||||||
///
|
|
||||||
/// # Positional arguments
|
|
||||||
/// - The path to the image (string)
|
|
||||||
pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value {
|
|
||||||
let path = args.need::<_, Spanned<String>>(ctx, 0, "path");
|
|
||||||
let width = args.get::<_, Linear>(ctx, "width");
|
|
||||||
let height = args.get::<_, Linear>(ctx, "height");
|
|
||||||
|
|
||||||
if let Some(path) = path {
|
|
||||||
if let Ok(file) = File::open(path.v) {
|
|
||||||
match Reader::new(BufReader::new(file))
|
|
||||||
.with_guessed_format()
|
|
||||||
.map_err(|err| err.into())
|
|
||||||
.and_then(|reader| reader.decode())
|
|
||||||
.map(|img| img.into_rgba8())
|
|
||||||
{
|
|
||||||
Ok(buf) => {
|
|
||||||
ctx.push(Image {
|
|
||||||
buf,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
align: ctx.state.align,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(err) => ctx.diag(error!(path.span, "invalid image: {}", err)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx.diag(error!(path.span, "failed to open image file"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::None
|
|
||||||
}
|
|
@ -1,18 +1,59 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
|
||||||
use super::*;
|
use image::io::Reader;
|
||||||
|
use image::RgbaImage;
|
||||||
|
|
||||||
|
use crate::layout::*;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
/// `image`: Insert an image.
|
||||||
|
///
|
||||||
|
/// # Positional arguments
|
||||||
|
/// - The path to the image (string)
|
||||||
|
pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value {
|
||||||
|
let path = args.need::<_, Spanned<String>>(ctx, 0, "path");
|
||||||
|
let width = args.get::<_, Linear>(ctx, "width");
|
||||||
|
let height = args.get::<_, Linear>(ctx, "height");
|
||||||
|
|
||||||
|
if let Some(path) = path {
|
||||||
|
if let Ok(file) = File::open(path.v) {
|
||||||
|
match Reader::new(BufReader::new(file))
|
||||||
|
.with_guessed_format()
|
||||||
|
.map_err(|err| err.into())
|
||||||
|
.and_then(|reader| reader.decode())
|
||||||
|
.map(|img| img.into_rgba8())
|
||||||
|
{
|
||||||
|
Ok(buf) => {
|
||||||
|
ctx.push(Image {
|
||||||
|
buf,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
align: ctx.state.align,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(err) => ctx.diag(error!(path.span, "invalid image: {}", err)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.diag(error!(path.span, "failed to open image file"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::None
|
||||||
|
}
|
||||||
|
|
||||||
/// An image node.
|
/// An image node.
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct Image {
|
struct Image {
|
||||||
/// The image.
|
/// The image.
|
||||||
pub buf: RgbaImage,
|
buf: RgbaImage,
|
||||||
/// The fixed width, if any.
|
/// The fixed width, if any.
|
||||||
pub width: Option<Linear>,
|
width: Option<Linear>,
|
||||||
/// The fixed height, if any.
|
/// The fixed height, if any.
|
||||||
pub height: Option<Linear>,
|
height: Option<Linear>,
|
||||||
/// How to align this image node in its parent.
|
/// How to align this image node in its parent.
|
||||||
pub align: BoxAlign,
|
align: BoxAlign,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for Image {
|
impl Layout for Image {
|
337
src/library/layout.rs
Normal file
337
src/library/layout.rs
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
|
use crate::geom::{Length, Linear};
|
||||||
|
use crate::layout::{Expansion, Fixed, Softness, Spacing, Stack};
|
||||||
|
use crate::paper::{Paper, PaperClass};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
/// `align`: Align content along the layouting axes.
|
||||||
|
///
|
||||||
|
/// # Positional arguments
|
||||||
|
/// - At most two of `left`, `right`, `top`, `bottom`, `center`.
|
||||||
|
///
|
||||||
|
/// When `center` is used as a positional argument, it is automatically inferred
|
||||||
|
/// which axis it should apply to depending on further arguments, defaulting
|
||||||
|
/// to the cross axis.
|
||||||
|
///
|
||||||
|
/// # Keyword arguments
|
||||||
|
/// - `horizontal`: Any of `left`, `right` or `center`.
|
||||||
|
/// - `vertical`: Any of `top`, `bottom` or `center`.
|
||||||
|
pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
|
||||||
|
let snapshot = ctx.state.clone();
|
||||||
|
let body = args.find::<SynTree>();
|
||||||
|
let first = args.get::<_, Spanned<AlignArg>>(ctx, 0);
|
||||||
|
let second = args.get::<_, Spanned<AlignArg>>(ctx, 1);
|
||||||
|
let hor = args.get::<_, Spanned<AlignArg>>(ctx, "horizontal");
|
||||||
|
let ver = args.get::<_, Spanned<AlignArg>>(ctx, "vertical");
|
||||||
|
args.done(ctx);
|
||||||
|
|
||||||
|
let prev_main = ctx.state.align.main;
|
||||||
|
let mut had = Gen::uniform(false);
|
||||||
|
let mut had_center = false;
|
||||||
|
|
||||||
|
for (axis, Spanned { v: arg, span }) in first
|
||||||
|
.into_iter()
|
||||||
|
.chain(second.into_iter())
|
||||||
|
.map(|arg| (arg.v.axis(), arg))
|
||||||
|
.chain(hor.into_iter().map(|arg| (Some(SpecAxis::Horizontal), arg)))
|
||||||
|
.chain(ver.into_iter().map(|arg| (Some(SpecAxis::Vertical), arg)))
|
||||||
|
{
|
||||||
|
// Check whether we know which axis this alignment belongs to.
|
||||||
|
if let Some(axis) = axis {
|
||||||
|
// We know the axis.
|
||||||
|
let gen_axis = axis.switch(ctx.state.flow);
|
||||||
|
let gen_align = arg.switch(ctx.state.flow);
|
||||||
|
|
||||||
|
if arg.axis().map_or(false, |a| a != axis) {
|
||||||
|
ctx.diag(error!(
|
||||||
|
span,
|
||||||
|
"invalid alignment `{}` for {} axis", arg, axis,
|
||||||
|
));
|
||||||
|
} else if had.get(gen_axis) {
|
||||||
|
ctx.diag(error!(span, "duplicate alignment for {} axis", axis));
|
||||||
|
} else {
|
||||||
|
*ctx.state.align.get_mut(gen_axis) = gen_align;
|
||||||
|
*had.get_mut(gen_axis) = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We don't know the axis: This has to be a `center` alignment for a
|
||||||
|
// positional argument.
|
||||||
|
debug_assert_eq!(arg, AlignArg::Center);
|
||||||
|
|
||||||
|
if had.main && had.cross {
|
||||||
|
ctx.diag(error!(span, "duplicate alignment"));
|
||||||
|
} else if had_center {
|
||||||
|
// Both this and the previous one are unspecified `center`
|
||||||
|
// alignments. Both axes should be centered.
|
||||||
|
ctx.state.align.main = Align::Center;
|
||||||
|
ctx.state.align.cross = Align::Center;
|
||||||
|
had = Gen::uniform(true);
|
||||||
|
} else {
|
||||||
|
had_center = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we we know the other alignment, we can handle the unspecified
|
||||||
|
// `center` alignment.
|
||||||
|
if had_center && (had.main || had.cross) {
|
||||||
|
if had.main {
|
||||||
|
ctx.state.align.cross = Align::Center;
|
||||||
|
had.cross = true;
|
||||||
|
} else {
|
||||||
|
ctx.state.align.main = Align::Center;
|
||||||
|
had.main = true;
|
||||||
|
}
|
||||||
|
had_center = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `had_center` wasn't flushed by now, it's the only argument and then we
|
||||||
|
// default to applying it to the cross axis.
|
||||||
|
if had_center {
|
||||||
|
ctx.state.align.cross = Align::Center;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.state.align.main != prev_main {
|
||||||
|
ctx.end_par_group();
|
||||||
|
ctx.start_par_group();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(body) = body {
|
||||||
|
body.eval(ctx);
|
||||||
|
ctx.state = snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An argument to `[align]`.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
enum AlignArg {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
Center,
|
||||||
|
}
|
||||||
|
|
||||||
|
convert_ident!(AlignArg, "alignment", |v| match v {
|
||||||
|
"left" => Some(Self::Left),
|
||||||
|
"right" => Some(Self::Right),
|
||||||
|
"top" => Some(Self::Top),
|
||||||
|
"bottom" => Some(Self::Bottom),
|
||||||
|
"center" => Some(Self::Center),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
impl AlignArg {
|
||||||
|
/// The specific axis this alignment refers to.
|
||||||
|
///
|
||||||
|
/// Returns `None` if this is `Center` since the axis is unknown.
|
||||||
|
pub fn axis(self) -> Option<SpecAxis> {
|
||||||
|
match self {
|
||||||
|
Self::Left => Some(SpecAxis::Horizontal),
|
||||||
|
Self::Right => Some(SpecAxis::Horizontal),
|
||||||
|
Self::Top => Some(SpecAxis::Vertical),
|
||||||
|
Self::Bottom => Some(SpecAxis::Vertical),
|
||||||
|
Self::Center => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Switch for AlignArg {
|
||||||
|
type Other = Align;
|
||||||
|
|
||||||
|
fn switch(self, flow: Flow) -> Self::Other {
|
||||||
|
let get = |dir: Dir, at_positive_start| {
|
||||||
|
if dir.is_positive() == at_positive_start {
|
||||||
|
Align::Start
|
||||||
|
} else {
|
||||||
|
Align::End
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let flow = flow.switch(flow);
|
||||||
|
match self {
|
||||||
|
Self::Left => get(flow.horizontal, true),
|
||||||
|
Self::Right => get(flow.horizontal, false),
|
||||||
|
Self::Top => get(flow.vertical, true),
|
||||||
|
Self::Bottom => get(flow.vertical, false),
|
||||||
|
Self::Center => Align::Center,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for AlignArg {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.pad(match self {
|
||||||
|
Self::Left => "left",
|
||||||
|
Self::Right => "right",
|
||||||
|
Self::Top => "top",
|
||||||
|
Self::Bottom => "bottom",
|
||||||
|
Self::Center => "center",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `box`: Layout content into a box.
|
||||||
|
///
|
||||||
|
/// # Keyword arguments
|
||||||
|
/// - `width`: The width of the box (length or relative to parent's width).
|
||||||
|
/// - `height`: The height of the box (length or relative to parent's height).
|
||||||
|
pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
|
||||||
|
let snapshot = ctx.state.clone();
|
||||||
|
let body = args.find::<SynTree>().unwrap_or_default();
|
||||||
|
let width = args.get::<_, Linear>(ctx, "width");
|
||||||
|
let height = args.get::<_, Linear>(ctx, "height");
|
||||||
|
let main = args.get::<_, Spanned<Dir>>(ctx, "main");
|
||||||
|
let cross = args.get::<_, Spanned<Dir>>(ctx, "cross");
|
||||||
|
ctx.set_flow(Gen::new(main, cross));
|
||||||
|
args.done(ctx);
|
||||||
|
|
||||||
|
let flow = ctx.state.flow;
|
||||||
|
let align = ctx.state.align;
|
||||||
|
|
||||||
|
ctx.start_content_group();
|
||||||
|
body.eval(ctx);
|
||||||
|
let children = ctx.end_content_group();
|
||||||
|
|
||||||
|
ctx.push(Fixed {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
child: LayoutNode::dynamic(Stack {
|
||||||
|
flow,
|
||||||
|
align,
|
||||||
|
expansion: Spec::new(
|
||||||
|
Expansion::fill_if(width.is_some()),
|
||||||
|
Expansion::fill_if(height.is_some()),
|
||||||
|
)
|
||||||
|
.switch(flow),
|
||||||
|
children,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.state = snapshot;
|
||||||
|
Value::None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `h`: Add horizontal spacing.
|
||||||
|
///
|
||||||
|
/// # Positional arguments
|
||||||
|
/// - The spacing (length or relative to font size).
|
||||||
|
pub fn h(args: Args, ctx: &mut EvalContext) -> Value {
|
||||||
|
spacing(args, ctx, SpecAxis::Horizontal)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `v`: Add vertical spacing.
|
||||||
|
///
|
||||||
|
/// # Positional arguments
|
||||||
|
/// - The spacing (length or relative to font size).
|
||||||
|
pub fn v(args: Args, ctx: &mut EvalContext) -> Value {
|
||||||
|
spacing(args, ctx, SpecAxis::Vertical)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply spacing along a specific axis.
|
||||||
|
fn spacing(mut args: Args, ctx: &mut EvalContext, axis: SpecAxis) -> Value {
|
||||||
|
let spacing = args.need::<_, Linear>(ctx, 0, "spacing");
|
||||||
|
args.done(ctx);
|
||||||
|
|
||||||
|
if let Some(linear) = spacing {
|
||||||
|
let amount = linear.resolve(ctx.state.font.font_size());
|
||||||
|
let spacing = Spacing { amount, softness: Softness::Hard };
|
||||||
|
if ctx.state.flow.main.axis() == axis {
|
||||||
|
ctx.end_par_group();
|
||||||
|
ctx.push(spacing);
|
||||||
|
ctx.start_par_group();
|
||||||
|
} else {
|
||||||
|
ctx.push(spacing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `page`: Configure pages.
|
||||||
|
///
|
||||||
|
/// # Positional arguments
|
||||||
|
/// - The name of a paper, e.g. `a4` (optional).
|
||||||
|
///
|
||||||
|
/// # Keyword arguments
|
||||||
|
/// - `width`: The width of pages (length).
|
||||||
|
/// - `height`: The height of pages (length).
|
||||||
|
/// - `margins`: The margins for all sides (length or relative to side lengths).
|
||||||
|
/// - `left`: The left margin (length or relative to width).
|
||||||
|
/// - `right`: The right margin (length or relative to width).
|
||||||
|
/// - `top`: The top margin (length or relative to height).
|
||||||
|
/// - `bottom`: The bottom margin (length or relative to height).
|
||||||
|
/// - `flip`: Flips custom or paper-defined width and height (boolean).
|
||||||
|
pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value {
|
||||||
|
let snapshot = ctx.state.clone();
|
||||||
|
let body = args.find::<SynTree>();
|
||||||
|
|
||||||
|
if let Some(paper) = args.find::<Paper>() {
|
||||||
|
ctx.state.page.class = paper.class;
|
||||||
|
ctx.state.page.size = paper.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(width) = args.get::<_, Length>(ctx, "width") {
|
||||||
|
ctx.state.page.class = PaperClass::Custom;
|
||||||
|
ctx.state.page.size.width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(height) = args.get::<_, Length>(ctx, "height") {
|
||||||
|
ctx.state.page.class = PaperClass::Custom;
|
||||||
|
ctx.state.page.size.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(margins) = args.get::<_, Linear>(ctx, "margins") {
|
||||||
|
ctx.state.page.margins = Sides::uniform(Some(margins));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(left) = args.get::<_, Linear>(ctx, "left") {
|
||||||
|
ctx.state.page.margins.left = Some(left);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(top) = args.get::<_, Linear>(ctx, "top") {
|
||||||
|
ctx.state.page.margins.top = Some(top);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(right) = args.get::<_, Linear>(ctx, "right") {
|
||||||
|
ctx.state.page.margins.right = Some(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(bottom) = args.get::<_, Linear>(ctx, "bottom") {
|
||||||
|
ctx.state.page.margins.bottom = Some(bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.get::<_, bool>(ctx, "flip").unwrap_or(false) {
|
||||||
|
let size = &mut ctx.state.page.size;
|
||||||
|
std::mem::swap(&mut size.width, &mut size.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
let main = args.get::<_, Spanned<Dir>>(ctx, "main");
|
||||||
|
let cross = args.get::<_, Spanned<Dir>>(ctx, "cross");
|
||||||
|
ctx.set_flow(Gen::new(main, cross));
|
||||||
|
|
||||||
|
args.done(ctx);
|
||||||
|
|
||||||
|
if let Some(body) = body {
|
||||||
|
ctx.end_page_group();
|
||||||
|
ctx.start_page_group(true);
|
||||||
|
body.eval(ctx);
|
||||||
|
ctx.state = snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.end_page_group();
|
||||||
|
ctx.start_page_group(false);
|
||||||
|
|
||||||
|
Value::None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `pagebreak`: Start a new page.
|
||||||
|
pub fn pagebreak(args: Args, ctx: &mut EvalContext) -> Value {
|
||||||
|
args.done(ctx);
|
||||||
|
ctx.end_page_group();
|
||||||
|
ctx.start_page_group(true);
|
||||||
|
Value::None
|
||||||
|
}
|
@ -1,20 +1,12 @@
|
|||||||
//! The standard library.
|
//! The standard library.
|
||||||
|
|
||||||
mod align;
|
mod insert;
|
||||||
mod boxed;
|
mod layout;
|
||||||
mod color;
|
mod style;
|
||||||
mod font;
|
|
||||||
mod graphics;
|
|
||||||
mod page;
|
|
||||||
mod spacing;
|
|
||||||
|
|
||||||
pub use align::*;
|
pub use insert::*;
|
||||||
pub use boxed::*;
|
pub use layout::*;
|
||||||
pub use color::*;
|
pub use style::*;
|
||||||
pub use font::*;
|
|
||||||
pub use graphics::*;
|
|
||||||
pub use page::*;
|
|
||||||
pub use spacing::*;
|
|
||||||
|
|
||||||
use crate::eval::{Scope, ValueFunc};
|
use crate::eval::{Scope, ValueFunc};
|
||||||
|
|
||||||
|
@ -1,88 +0,0 @@
|
|||||||
use crate::geom::{Length, Linear};
|
|
||||||
use crate::paper::{Paper, PaperClass};
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
/// `page`: Configure pages.
|
|
||||||
///
|
|
||||||
/// # Positional arguments
|
|
||||||
/// - The name of a paper, e.g. `a4` (optional).
|
|
||||||
///
|
|
||||||
/// # Keyword arguments
|
|
||||||
/// - `width`: The width of pages (length).
|
|
||||||
/// - `height`: The height of pages (length).
|
|
||||||
/// - `margins`: The margins for all sides (length or relative to side lengths).
|
|
||||||
/// - `left`: The left margin (length or relative to width).
|
|
||||||
/// - `right`: The right margin (length or relative to width).
|
|
||||||
/// - `top`: The top margin (length or relative to height).
|
|
||||||
/// - `bottom`: The bottom margin (length or relative to height).
|
|
||||||
/// - `flip`: Flips custom or paper-defined width and height (boolean).
|
|
||||||
pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value {
|
|
||||||
let snapshot = ctx.state.clone();
|
|
||||||
let body = args.find::<SynTree>();
|
|
||||||
|
|
||||||
if let Some(paper) = args.find::<Paper>() {
|
|
||||||
ctx.state.page.class = paper.class;
|
|
||||||
ctx.state.page.size = paper.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(width) = args.get::<_, Length>(ctx, "width") {
|
|
||||||
ctx.state.page.class = PaperClass::Custom;
|
|
||||||
ctx.state.page.size.width = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(height) = args.get::<_, Length>(ctx, "height") {
|
|
||||||
ctx.state.page.class = PaperClass::Custom;
|
|
||||||
ctx.state.page.size.height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(margins) = args.get::<_, Linear>(ctx, "margins") {
|
|
||||||
ctx.state.page.margins = Sides::uniform(Some(margins));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(left) = args.get::<_, Linear>(ctx, "left") {
|
|
||||||
ctx.state.page.margins.left = Some(left);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(top) = args.get::<_, Linear>(ctx, "top") {
|
|
||||||
ctx.state.page.margins.top = Some(top);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(right) = args.get::<_, Linear>(ctx, "right") {
|
|
||||||
ctx.state.page.margins.right = Some(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(bottom) = args.get::<_, Linear>(ctx, "bottom") {
|
|
||||||
ctx.state.page.margins.bottom = Some(bottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.get::<_, bool>(ctx, "flip").unwrap_or(false) {
|
|
||||||
let size = &mut ctx.state.page.size;
|
|
||||||
std::mem::swap(&mut size.width, &mut size.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
let main = args.get::<_, Spanned<Dir>>(ctx, "main");
|
|
||||||
let cross = args.get::<_, Spanned<Dir>>(ctx, "cross");
|
|
||||||
ctx.set_flow(Gen::new(main, cross));
|
|
||||||
|
|
||||||
args.done(ctx);
|
|
||||||
|
|
||||||
if let Some(body) = body {
|
|
||||||
ctx.end_page_group();
|
|
||||||
ctx.start_page_group(true);
|
|
||||||
body.eval(ctx);
|
|
||||||
ctx.state = snapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.end_page_group();
|
|
||||||
ctx.start_page_group(false);
|
|
||||||
|
|
||||||
Value::None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `pagebreak`: Starts a new page.
|
|
||||||
pub fn pagebreak(args: Args, ctx: &mut EvalContext) -> Value {
|
|
||||||
args.done(ctx);
|
|
||||||
ctx.end_page_group();
|
|
||||||
ctx.start_page_group(true);
|
|
||||||
Value::None
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
use crate::geom::Linear;
|
|
||||||
use crate::layout::{Softness, Spacing};
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
/// `h`: Add horizontal spacing.
|
|
||||||
///
|
|
||||||
/// # Positional arguments
|
|
||||||
/// - The spacing (length or relative to font size).
|
|
||||||
pub fn h(args: Args, ctx: &mut EvalContext) -> Value {
|
|
||||||
spacing(args, ctx, SpecAxis::Horizontal)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `v`: Add vertical spacing.
|
|
||||||
///
|
|
||||||
/// # Positional arguments
|
|
||||||
/// - The spacing (length or relative to font size).
|
|
||||||
pub fn v(args: Args, ctx: &mut EvalContext) -> Value {
|
|
||||||
spacing(args, ctx, SpecAxis::Vertical)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply spacing along a specific axis.
|
|
||||||
fn spacing(mut args: Args, ctx: &mut EvalContext, axis: SpecAxis) -> Value {
|
|
||||||
let spacing = args.need::<_, Linear>(ctx, 0, "spacing");
|
|
||||||
args.done(ctx);
|
|
||||||
|
|
||||||
if let Some(linear) = spacing {
|
|
||||||
let amount = linear.resolve(ctx.state.font.font_size());
|
|
||||||
let spacing = Spacing { amount, softness: Softness::Hard };
|
|
||||||
if ctx.state.flow.main.axis() == axis {
|
|
||||||
ctx.end_par_group();
|
|
||||||
ctx.push(spacing);
|
|
||||||
ctx.start_par_group();
|
|
||||||
} else {
|
|
||||||
ctx.push(spacing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::None
|
|
||||||
}
|
|
@ -2,6 +2,7 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use fontdock::{FontStretch, FontStyle, FontWeight};
|
use fontdock::{FontStretch, FontStyle, FontWeight};
|
||||||
|
|
||||||
|
use crate::color::RgbaColor;
|
||||||
use crate::eval::StringLike;
|
use crate::eval::StringLike;
|
||||||
use crate::geom::Linear;
|
use crate::geom::Linear;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@ -9,8 +10,14 @@ use crate::prelude::*;
|
|||||||
/// `font`: Configure the font.
|
/// `font`: Configure the font.
|
||||||
///
|
///
|
||||||
/// # Positional arguments
|
/// # Positional arguments
|
||||||
/// - The font size (optional, length or relative to previous font size).
|
/// - The font size (optional, length or relative to current font size).
|
||||||
/// - A font family fallback list (optional, identifiers or strings).
|
/// - All identifier and string arguments are interpreted as an ordered list of
|
||||||
|
/// fallback font families.
|
||||||
|
///
|
||||||
|
/// An example invocation could look like this:
|
||||||
|
/// ```typst
|
||||||
|
/// [font: 12pt, Arial, "Noto Sans", sans-serif]
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Keyword arguments
|
/// # Keyword arguments
|
||||||
/// - `style`
|
/// - `style`
|
||||||
@ -28,7 +35,7 @@ use crate::prelude::*;
|
|||||||
/// - `bold` (`700`)
|
/// - `bold` (`700`)
|
||||||
/// - `extrabold` (`800`)
|
/// - `extrabold` (`800`)
|
||||||
/// - `black` (`900`)
|
/// - `black` (`900`)
|
||||||
/// - any integer from the range `100` - `900` (inclusive)
|
/// - integer between `100` and `900`
|
||||||
///
|
///
|
||||||
/// - `stretch`
|
/// - `stretch`
|
||||||
/// - `ultra-condensed`
|
/// - `ultra-condensed`
|
||||||
@ -106,3 +113,34 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value {
|
|||||||
|
|
||||||
Value::None
|
Value::None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `rgb`: Create an RGB(A) color.
|
||||||
|
///
|
||||||
|
/// # Positional arguments
|
||||||
|
/// - The red component (integer between 0 and 255).
|
||||||
|
/// - The green component (integer between 0 and 255).
|
||||||
|
/// - The blue component (integer between 0 and 255).
|
||||||
|
/// - The alpha component (optional, integer between 0 and 255).
|
||||||
|
pub fn rgb(mut args: Args, ctx: &mut EvalContext) -> Value {
|
||||||
|
let r = args.need::<_, Spanned<i64>>(ctx, 0, "red value");
|
||||||
|
let g = args.need::<_, Spanned<i64>>(ctx, 1, "green value");
|
||||||
|
let b = args.need::<_, Spanned<i64>>(ctx, 2, "blue value");
|
||||||
|
let a = args.get::<_, Spanned<i64>>(ctx, 3);
|
||||||
|
args.done(ctx);
|
||||||
|
|
||||||
|
let mut clamp = |component: Option<Spanned<i64>>, default| {
|
||||||
|
component.map_or(default, |c| {
|
||||||
|
if c.v < 0 || c.v > 255 {
|
||||||
|
ctx.diag(error!(c.span, "should be between 0 and 255"));
|
||||||
|
}
|
||||||
|
c.v.max(0).min(255) as u8
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
Value::Color(RgbaColor::new(
|
||||||
|
clamp(r, 0),
|
||||||
|
clamp(g, 0),
|
||||||
|
clamp(b, 0),
|
||||||
|
clamp(a, 255),
|
||||||
|
))
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user