mirror of
https://github.com/typst/typst
synced 2025-05-21 20:45:27 +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.
|
||||
|
||||
mod document;
|
||||
mod fixed;
|
||||
mod graphics;
|
||||
mod node;
|
||||
mod pad;
|
||||
mod par;
|
||||
@ -16,9 +14,7 @@ use crate::font::SharedFontLoader;
|
||||
use crate::geom::*;
|
||||
use crate::shaping::Shaped;
|
||||
|
||||
pub use document::*;
|
||||
pub use fixed::*;
|
||||
pub use graphics::*;
|
||||
pub use node::*;
|
||||
pub use pad::*;
|
||||
pub use par::*;
|
||||
@ -193,3 +189,35 @@ pub struct ImageElement {
|
||||
/// The document size of the image.
|
||||
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 {
|
||||
fn clone(&self) -> Self {
|
||||
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.
|
||||
///
|
||||
/// 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.
|
||||
fn shrink_areas(areas: &Areas, padding: Sides<Linear>) -> Areas {
|
||||
let shrink = |size| size - padding.resolve(size).size();
|
||||
@ -52,9 +58,3 @@ fn pad_layout(layout: &mut BoxLayout, padding: Sides<Linear>) {
|
||||
*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::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.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Image {
|
||||
struct Image {
|
||||
/// The image.
|
||||
pub buf: RgbaImage,
|
||||
buf: RgbaImage,
|
||||
/// The fixed width, if any.
|
||||
pub width: Option<Linear>,
|
||||
width: Option<Linear>,
|
||||
/// The fixed height, if any.
|
||||
pub height: Option<Linear>,
|
||||
height: Option<Linear>,
|
||||
/// How to align this image node in its parent.
|
||||
pub align: BoxAlign,
|
||||
align: BoxAlign,
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
mod align;
|
||||
mod boxed;
|
||||
mod color;
|
||||
mod font;
|
||||
mod graphics;
|
||||
mod page;
|
||||
mod spacing;
|
||||
mod insert;
|
||||
mod layout;
|
||||
mod style;
|
||||
|
||||
pub use align::*;
|
||||
pub use boxed::*;
|
||||
pub use color::*;
|
||||
pub use font::*;
|
||||
pub use graphics::*;
|
||||
pub use page::*;
|
||||
pub use spacing::*;
|
||||
pub use insert::*;
|
||||
pub use layout::*;
|
||||
pub use style::*;
|
||||
|
||||
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 crate::color::RgbaColor;
|
||||
use crate::eval::StringLike;
|
||||
use crate::geom::Linear;
|
||||
use crate::prelude::*;
|
||||
@ -9,8 +10,14 @@ use crate::prelude::*;
|
||||
/// `font`: Configure the font.
|
||||
///
|
||||
/// # Positional arguments
|
||||
/// - The font size (optional, length or relative to previous font size).
|
||||
/// - A font family fallback list (optional, identifiers or strings).
|
||||
/// - The font size (optional, length or relative to current font size).
|
||||
/// - 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
|
||||
/// - `style`
|
||||
@ -28,7 +35,7 @@ use crate::prelude::*;
|
||||
/// - `bold` (`700`)
|
||||
/// - `extrabold` (`800`)
|
||||
/// - `black` (`900`)
|
||||
/// - any integer from the range `100` - `900` (inclusive)
|
||||
/// - integer between `100` and `900`
|
||||
///
|
||||
/// - `stretch`
|
||||
/// - `ultra-condensed`
|
||||
@ -106,3 +113,34 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value {
|
||||
|
||||
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