summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-12-15 20:27:41 +0100
committerLaurenz <laurmaedje@gmail.com>2021-12-15 20:27:41 +0100
commit2a3d0f4b390457174ed09347dd29e97ff9a783e4 (patch)
tree0e0634bff6b7f64131267f4cbe05651c1c91d900 /src/library
parent244ad386ec271ff86a2101eb4cc38d37a55552b9 (diff)
Set Rules Episode VII: The Set Awakens
Diffstat (limited to 'src/library')
-rw-r--r--src/library/mod.rs28
-rw-r--r--src/library/page.rs92
-rw-r--r--src/library/par.rs88
-rw-r--r--src/library/text.rs95
4 files changed, 156 insertions, 147 deletions
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 9b6da6a9..1e2ee224 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -27,7 +27,9 @@ mod prelude {
pub use std::rc::Rc;
pub use crate::diag::{At, TypResult};
- pub use crate::eval::{Args, EvalContext, Node, Property, Smart, Styles, Value};
+ pub use crate::eval::{
+ Args, Construct, EvalContext, Node, Property, Set, Smart, Styles, Value,
+ };
pub use crate::frame::*;
pub use crate::geom::*;
pub use crate::layout::*;
@@ -60,22 +62,24 @@ use crate::geom::*;
pub fn new() -> Scope {
let mut std = Scope::new();
- // Text.
- std.def_func("font", font);
- std.def_func("par", par);
- std.def_func("parbreak", parbreak);
+ // Classes.
+ std.def_class::<PageNode>("page");
+ std.def_class::<ParNode>("par");
+ std.def_class::<TextNode>("text");
+
+ // Text functions.
std.def_func("strike", strike);
std.def_func("underline", underline);
std.def_func("overline", overline);
std.def_func("link", link);
- // Layout.
- std.def_func("page", page);
- std.def_func("pagebreak", pagebreak);
+ // Layout functions.
std.def_func("h", h);
std.def_func("v", v);
std.def_func("box", box_);
std.def_func("block", block);
+ std.def_func("pagebreak", pagebreak);
+ std.def_func("parbreak", parbreak);
std.def_func("stack", stack);
std.def_func("grid", grid);
std.def_func("pad", pad);
@@ -85,14 +89,14 @@ pub fn new() -> Scope {
std.def_func("scale", scale);
std.def_func("rotate", rotate);
- // Elements.
+ // Element functions.
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.
+ // Utility functions.
std.def_func("assert", assert);
std.def_func("type", type_);
std.def_func("repr", repr);
@@ -110,14 +114,14 @@ pub fn new() -> Scope {
std.def_func("len", len);
std.def_func("sorted", sorted);
- // Colors.
+ // Predefined colors.
std.def_const("white", RgbaColor::WHITE);
std.def_const("black", RgbaColor::BLACK);
std.def_const("eastern", RgbaColor::new(0x23, 0x9D, 0xAD, 0xFF));
std.def_const("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
std.def_const("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
- // Arbitrary constants.
+ // Other constants.
std.def_const("ltr", Dir::LTR);
std.def_const("rtl", Dir::RTL);
std.def_const("ttb", Dir::TTB);
diff --git a/src/library/page.rs b/src/library/page.rs
index 4a4fab03..0b0cc2d9 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -6,53 +6,6 @@ use std::str::FromStr;
use super::prelude::*;
use super::PadNode;
-/// `page`: Configure pages.
-pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
- castable! {
- Paper,
- Expected: "string",
- Value::Str(string) => Paper::from_str(&string).map_err(|e| e.to_string())?,
- }
-
- let body: Option<Node> = args.find();
-
- let mut map = Styles::new();
- let styles = match body {
- Some(_) => &mut map,
- None => &mut ctx.styles,
- };
-
- if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) {
- styles.set(PageNode::CLASS, paper.class());
- styles.set(PageNode::WIDTH, Smart::Custom(paper.width()));
- styles.set(PageNode::HEIGHT, Smart::Custom(paper.height()));
- }
-
- if let Some(width) = args.named("width")? {
- styles.set(PageNode::CLASS, PaperClass::Custom);
- styles.set(PageNode::WIDTH, width);
- }
-
- if let Some(height) = args.named("height")? {
- styles.set(PageNode::CLASS, PaperClass::Custom);
- styles.set(PageNode::HEIGHT, height);
- }
-
- let margins = args.named("margins")?;
-
- set!(styles, PageNode::FLIPPED => args.named("flipped")?);
- set!(styles, PageNode::LEFT => args.named("left")?.or(margins));
- set!(styles, PageNode::TOP => args.named("top")?.or(margins));
- set!(styles, PageNode::RIGHT => args.named("right")?.or(margins));
- set!(styles, PageNode::BOTTOM => args.named("bottom")?.or(margins));
- set!(styles, PageNode::FILL => args.named("fill")?);
-
- Ok(match body {
- Some(body) => Value::block(body.into_block().styled(map)),
- None => Value::None,
- })
-}
-
/// `pagebreak`: Start a new page.
pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
Ok(Value::Node(Node::Pagebreak))
@@ -90,6 +43,45 @@ properties! {
FILL: Option<Paint> = None,
}
+impl Construct for PageNode {
+ fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
+ // TODO(set): Make sure it's really a page so that it doesn't merge
+ // with adjacent pages.
+ Ok(Node::Page(args.expect::<Node>("body")?.into_block()))
+ }
+}
+
+impl Set for PageNode {
+ fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()> {
+ if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) {
+ styles.set(PageNode::CLASS, paper.class());
+ styles.set(PageNode::WIDTH, Smart::Custom(paper.width()));
+ styles.set(PageNode::HEIGHT, Smart::Custom(paper.height()));
+ }
+
+ if let Some(width) = args.named("width")? {
+ styles.set(PageNode::CLASS, PaperClass::Custom);
+ styles.set(PageNode::WIDTH, width);
+ }
+
+ if let Some(height) = args.named("height")? {
+ styles.set(PageNode::CLASS, PaperClass::Custom);
+ styles.set(PageNode::HEIGHT, height);
+ }
+
+ let margins = args.named("margins")?;
+
+ set!(styles, PageNode::FLIPPED => args.named("flipped")?);
+ set!(styles, PageNode::LEFT => args.named("left")?.or(margins));
+ set!(styles, PageNode::TOP => args.named("top")?.or(margins));
+ set!(styles, PageNode::RIGHT => args.named("right")?.or(margins));
+ set!(styles, PageNode::BOTTOM => args.named("bottom")?.or(margins));
+ set!(styles, PageNode::FILL => args.named("fill")?);
+
+ Ok(())
+ }
+}
+
impl PageNode {
/// Layout the page run into a sequence of frames, one per page.
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
@@ -182,6 +174,12 @@ impl Default for Paper {
}
}
+castable! {
+ Paper,
+ Expected: "string",
+ Value::Str(string) => Paper::from_str(&string).map_err(|e| e.to_string())?,
+}
+
/// Defines default margins for a class of related papers.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum PaperClass {
diff --git a/src/library/par.rs b/src/library/par.rs
index 6ea960bf..d63c2315 100644
--- a/src/library/par.rs
+++ b/src/library/par.rs
@@ -9,46 +9,6 @@ use super::prelude::*;
use super::{shape, ShapedText, SpacingKind, SpacingNode, TextNode};
use crate::util::{EcoString, RangeExt, RcExt, SliceExt};
-/// `par`: Configure paragraphs.
-pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
- let spacing = args.named("spacing")?;
- let leading = args.named("leading")?;
-
- let mut dir =
- args.named("lang")?
- .map(|iso: EcoString| match iso.to_lowercase().as_str() {
- "ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL,
- "en" | "fr" | "de" => Dir::LTR,
- _ => Dir::LTR,
- });
-
- if let Some(Spanned { v, span }) = args.named::<Spanned<Dir>>("dir")? {
- if v.axis() != SpecAxis::Horizontal {
- bail!(span, "must be horizontal");
- }
- dir = Some(v);
- }
-
- let mut align = None;
- if let Some(Spanned { v, span }) = args.named::<Spanned<Align>>("align")? {
- if v.axis() != SpecAxis::Horizontal {
- bail!(span, "must be horizontal");
- }
- align = Some(v);
- }
-
- if let (Some(dir), None) = (dir, align) {
- align = Some(if dir == Dir::LTR { Align::Left } else { Align::Right });
- }
-
- set!(ctx.styles, ParNode::DIR => dir);
- set!(ctx.styles, ParNode::ALIGN => align);
- set!(ctx.styles, ParNode::LEADING => leading);
- set!(ctx.styles, ParNode::SPACING => spacing);
-
- Ok(Value::None)
-}
-
/// `parbreak`: Start a new paragraph.
pub fn parbreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
Ok(Value::Node(Node::Parbreak))
@@ -71,6 +31,54 @@ properties! {
SPACING: Linear = Relative::new(1.2).into(),
}
+impl Construct for ParNode {
+ fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
+ // Lift to a block so that it doesn't merge with adjacent stuff.
+ Ok(Node::Block(args.expect::<Node>("body")?.into_block()))
+ }
+}
+
+impl Set for ParNode {
+ fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()> {
+ let spacing = args.named("spacing")?;
+ let leading = args.named("leading")?;
+
+ let mut dir =
+ args.named("lang")?
+ .map(|iso: EcoString| match iso.to_lowercase().as_str() {
+ "ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL,
+ "en" | "fr" | "de" => Dir::LTR,
+ _ => Dir::LTR,
+ });
+
+ if let Some(Spanned { v, span }) = args.named::<Spanned<Dir>>("dir")? {
+ if v.axis() != SpecAxis::Horizontal {
+ bail!(span, "must be horizontal");
+ }
+ dir = Some(v);
+ }
+
+ let mut align = None;
+ if let Some(Spanned { v, span }) = args.named::<Spanned<Align>>("align")? {
+ if v.axis() != SpecAxis::Horizontal {
+ bail!(span, "must be horizontal");
+ }
+ align = Some(v);
+ }
+
+ if let (Some(dir), None) = (dir, align) {
+ align = Some(if dir == Dir::LTR { Align::Left } else { Align::Right });
+ }
+
+ set!(styles, ParNode::DIR => dir);
+ set!(styles, ParNode::ALIGN => align);
+ set!(styles, ParNode::LEADING => leading);
+ set!(styles, ParNode::SPACING => spacing);
+
+ Ok(())
+ }
+}
+
impl Layout for ParNode {
fn layout(
&self,
diff --git a/src/library/text.rs b/src/library/text.rs
index 4ab378c2..4baf4bc5 100644
--- a/src/library/text.rs
+++ b/src/library/text.rs
@@ -15,54 +15,6 @@ use crate::font::{
use crate::geom::{Dir, Em, Length, Point, Size};
use crate::util::{EcoString, SliceExt};
-/// `font`: Configure the font.
-pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
- let body = args.find::<Node>();
-
- let mut map = Styles::new();
- let styles = match body {
- Some(_) => &mut map,
- None => &mut ctx.styles,
- };
-
- let list = args.named("family")?.or_else(|| {
- let families: Vec<_> = args.all().collect();
- (!families.is_empty()).then(|| families)
- });
-
- set!(styles, TextNode::FAMILY_LIST => list);
- set!(styles, TextNode::SERIF_LIST => args.named("serif")?);
- set!(styles, TextNode::SANS_SERIF_LIST => args.named("sans-serif")?);
- set!(styles, TextNode::MONOSPACE_LIST => args.named("monospace")?);
- set!(styles, TextNode::FALLBACK => args.named("fallback")?);
- set!(styles, TextNode::STYLE => args.named("style")?);
- set!(styles, TextNode::WEIGHT => args.named("weight")?);
- set!(styles, TextNode::STRETCH => args.named("stretch")?);
- set!(styles, TextNode::FILL => args.named("fill")?.or_else(|| args.find()));
- set!(styles, TextNode::SIZE => args.named("size")?.or_else(|| args.find()));
- set!(styles, TextNode::TRACKING => args.named("tracking")?.map(Em::new));
- set!(styles, TextNode::TOP_EDGE => args.named("top-edge")?);
- set!(styles, TextNode::BOTTOM_EDGE => args.named("bottom-edge")?);
- set!(styles, TextNode::KERNING => args.named("kerning")?);
- set!(styles, TextNode::SMALLCAPS => args.named("smallcaps")?);
- set!(styles, TextNode::ALTERNATES => args.named("alternates")?);
- set!(styles, TextNode::STYLISTIC_SET => args.named("stylistic-set")?);
- set!(styles, TextNode::LIGATURES => args.named("ligatures")?);
- set!(styles, TextNode::DISCRETIONARY_LIGATURES => args.named("discretionary-ligatures")?);
- set!(styles, TextNode::HISTORICAL_LIGATURES => args.named("historical-ligatures")?);
- set!(styles, TextNode::NUMBER_TYPE => args.named("number-type")?);
- set!(styles, TextNode::NUMBER_WIDTH => args.named("number-width")?);
- set!(styles, TextNode::NUMBER_POSITION => args.named("number-position")?);
- set!(styles, TextNode::SLASHED_ZERO => args.named("slashed-zero")?);
- set!(styles, TextNode::FRACTIONS => args.named("fractions")?);
- set!(styles, TextNode::FEATURES => args.named("features")?);
-
- Ok(match body {
- Some(body) => Value::Node(body.styled(map)),
- None => Value::None,
- })
-}
-
/// `strike`: Typeset striken-through text.
pub fn strike(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
line_impl(args, LineKind::Strikethrough)
@@ -172,6 +124,53 @@ properties! {
FEATURES: Vec<(Tag, u32)> = vec![],
}
+impl Construct for TextNode {
+ fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
+ // We don't need to do anything more here because the whole point of the
+ // text constructor is to apply the styles and that happens
+ // automatically during class construction.
+ args.expect::<Node>("body")
+ }
+}
+
+impl Set for TextNode {
+ fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()> {
+ let list = args.named("family")?.or_else(|| {
+ let families: Vec<_> = args.all().collect();
+ (!families.is_empty()).then(|| families)
+ });
+
+ set!(styles, TextNode::FAMILY_LIST => list);
+ set!(styles, TextNode::SERIF_LIST => args.named("serif")?);
+ set!(styles, TextNode::SANS_SERIF_LIST => args.named("sans-serif")?);
+ set!(styles, TextNode::MONOSPACE_LIST => args.named("monospace")?);
+ set!(styles, TextNode::FALLBACK => args.named("fallback")?);
+ set!(styles, TextNode::STYLE => args.named("style")?);
+ set!(styles, TextNode::WEIGHT => args.named("weight")?);
+ set!(styles, TextNode::STRETCH => args.named("stretch")?);
+ set!(styles, TextNode::FILL => args.named("fill")?.or_else(|| args.find()));
+ set!(styles, TextNode::SIZE => args.named("size")?.or_else(|| args.find()));
+ set!(styles, TextNode::TRACKING => args.named("tracking")?.map(Em::new));
+ set!(styles, TextNode::TOP_EDGE => args.named("top-edge")?);
+ set!(styles, TextNode::BOTTOM_EDGE => args.named("bottom-edge")?);
+ set!(styles, TextNode::KERNING => args.named("kerning")?);
+ set!(styles, TextNode::SMALLCAPS => args.named("smallcaps")?);
+ set!(styles, TextNode::ALTERNATES => args.named("alternates")?);
+ set!(styles, TextNode::STYLISTIC_SET => args.named("stylistic-set")?);
+ set!(styles, TextNode::LIGATURES => args.named("ligatures")?);
+ set!(styles, TextNode::DISCRETIONARY_LIGATURES => args.named("discretionary-ligatures")?);
+ set!(styles, TextNode::HISTORICAL_LIGATURES => args.named("historical-ligatures")?);
+ set!(styles, TextNode::NUMBER_TYPE => args.named("number-type")?);
+ set!(styles, TextNode::NUMBER_WIDTH => args.named("number-width")?);
+ set!(styles, TextNode::NUMBER_POSITION => args.named("number-position")?);
+ set!(styles, TextNode::SLASHED_ZERO => args.named("slashed-zero")?);
+ set!(styles, TextNode::FRACTIONS => args.named("fractions")?);
+ set!(styles, TextNode::FEATURES => args.named("features")?);
+
+ Ok(())
+ }
+}
+
impl Debug for TextNode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if f.alternate() {