This commit is contained in:
Laurenz 2021-12-28 13:37:02 +01:00
parent 9624ad635b
commit bd304b99e5
21 changed files with 312 additions and 285 deletions

View File

@ -94,10 +94,6 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
if let (Some(&a), Some(&b)) = if let (Some(&a), Some(&b)) =
(a.downcast::<Align>(), b.downcast::<Align>()) (a.downcast::<Align>(), b.downcast::<Align>())
{ {
dynamic! {
Spec<Align>: "2d alignment",
}
return if a.axis() != b.axis() { return if a.axis() != b.axis() {
Ok(Dyn(Dynamic::new(match a.axis() { Ok(Dyn(Dynamic::new(match a.axis() {
SpecAxis::Horizontal => Spec { x: a, y: b }, SpecAxis::Horizontal => Spec { x: a, y: b },

View File

@ -1,9 +1,11 @@
//! Aligning nodes in their parent container.
use super::prelude::*; use super::prelude::*;
use super::ParNode; use super::ParNode;
/// `align`: Configure the alignment along the layouting axes. /// `align`: Configure the alignment along the layouting axes.
pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let aligns: Spec<_> = args.expect("alignment")?; let aligns: Spec<_> = args.find().unwrap_or_default();
let body: Node = args.expect("body")?; let body: Node = args.expect("body")?;
let mut styles = Styles::new(); let mut styles = Styles::new();
@ -16,6 +18,26 @@ pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
)) ))
} }
dynamic! {
Align: "alignment",
}
dynamic! {
Spec<Align>: "2d alignment",
}
castable! {
Spec<Option<Align>>,
Expected: "1d or 2d alignment",
@align: Align => {
let mut aligns = Spec::default();
aligns.set(align.axis(), Some(*align));
aligns
},
@aligns: Spec<Align> => aligns.map(Some),
}
/// A node that aligns its child. /// A node that aligns its child.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct AlignNode { pub struct AlignNode {
@ -57,19 +79,3 @@ impl Layout for AlignNode {
frames frames
} }
} }
dynamic! {
Align: "alignment",
}
castable! {
Spec<Option<Align>>,
Expected: "1d or 2d alignment",
@align: Align => {
let mut aligns = Spec::default();
aligns.set(align.axis(), Some(*align));
aligns
},
@aligns: Spec<Align> => aligns.map(Some),
}

View File

@ -1,7 +1,9 @@
//! Multi-column layouts.
use super::prelude::*; use super::prelude::*;
use super::ParNode; use super::ParNode;
/// `columns`: Stack children along an axis. /// `columns`: Set content into multiple columns.
pub fn columns(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub fn columns(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let columns = args.expect("column count")?; let columns = args.expect("column count")?;
let gutter = args.named("gutter")?.unwrap_or(Relative::new(0.04).into()); let gutter = args.named("gutter")?.unwrap_or(Relative::new(0.04).into());
@ -36,6 +38,8 @@ impl Layout for ColumnsNode {
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
regions: &Regions, regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> { ) -> Vec<Constrained<Rc<Frame>>> {
let columns = self.columns.get();
// Separating the infinite space into infinite columns does not make // Separating the infinite space into infinite columns does not make
// much sense. Note that this line assumes that no infinitely wide // much sense. Note that this line assumes that no infinitely wide
// region will follow if the first region's width is finite. // region will follow if the first region's width is finite.
@ -51,21 +55,15 @@ impl Layout for ColumnsNode {
// `region.backlog` and `regions.last`. // `region.backlog` and `regions.last`.
let mut sizes = vec![]; let mut sizes = vec![];
let columns = self.columns.get();
for (current, base) in regions for (current, base) in regions
.iter() .iter()
.take(1 + regions.backlog.len() + if regions.last.is_some() { 1 } else { 0 }) .take(1 + regions.backlog.len() + regions.last.iter().len())
{ {
let gutter = self.gutter.resolve(base.x); let gutter = self.gutter.resolve(base.x);
let width = (current.x - gutter * (columns - 1) as f64) / columns as f64;
let size = Size::new(width, current.y);
gutters.push(gutter); gutters.push(gutter);
let size = Size::new( sizes.extend(std::iter::repeat(size).take(columns));
(current.x - gutter * (columns - 1) as f64) / columns as f64,
current.y,
);
for _ in 0 .. columns {
sizes.push(size);
}
} }
let first = sizes.remove(0); let first = sizes.remove(0);
@ -76,24 +74,22 @@ impl Layout for ColumnsNode {
); );
// Retrieve elements for the last region from the vectors. // Retrieve elements for the last region from the vectors.
let last_gutter = if regions.last.is_some() { let last_gutter = regions.last.map(|_| {
let gutter = gutters.pop().unwrap(); let gutter = gutters.pop().unwrap();
let size = sizes.drain(sizes.len() - columns ..).next().unwrap(); let size = sizes.drain(sizes.len() - columns ..).next().unwrap();
pod.last = Some(size); pod.last = Some(size);
Some(gutter) gutter
} else { });
None
};
pod.backlog = sizes.into_iter(); pod.backlog = sizes.into_iter();
let mut finished = vec![];
let mut frames = self.child.layout(ctx, &pod).into_iter(); let mut frames = self.child.layout(ctx, &pod).into_iter();
let dir = ctx.styles.get(ParNode::DIR); let dir = ctx.styles.get(ParNode::DIR);
let mut finished = vec![];
let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize; let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize;
// Stitch together the columns for each region.
for ((current, base), gutter) in regions for ((current, base), gutter) in regions
.iter() .iter()
.take(total_regions) .take(total_regions)
@ -103,41 +99,35 @@ impl Layout for ColumnsNode {
// Otherwise its the maximum column height for the frame. In that // Otherwise its the maximum column height for the frame. In that
// case, the frame is first created with zero height and then // case, the frame is first created with zero height and then
// resized. // resized.
let mut height = if regions.expand.y { current.y } else { Length::zero() }; let height = if regions.expand.y { current.y } else { Length::zero() };
let mut frame = Frame::new(Spec::new(regions.current.x, height)); let mut output = Frame::new(Spec::new(regions.current.x, height));
let mut cursor = Length::zero(); let mut cursor = Length::zero();
for _ in 0 .. columns { for _ in 0 .. columns {
let child_frame = match frames.next() { let frame = match frames.next() {
Some(frame) => frame.item, Some(frame) => frame.item,
None => break, None => break,
}; };
let width = child_frame.size.x;
if !regions.expand.y { if !regions.expand.y {
height.set_max(child_frame.size.y); output.size.y.set_max(frame.size.y);
} }
frame.push_frame( let width = frame.size.x;
Point::with_x(if dir.is_positive() { let x = if dir.is_positive() {
cursor cursor
} else { } else {
regions.current.x - cursor - width regions.current.x - cursor - width
}), };
child_frame,
);
output.push_frame(Point::with_x(x), frame);
cursor += width + gutter; cursor += width + gutter;
} }
frame.size.y = height;
let mut cts = Constraints::new(regions.expand); let mut cts = Constraints::new(regions.expand);
cts.base = base.map(Some); cts.base = base.map(Some);
cts.exact = current.map(Some); cts.exact = current.map(Some);
finished.push(frame.constrain(cts)); finished.push(output.constrain(cts));
} }
finished finished

View File

@ -1,3 +1,5 @@
//! A flow of paragraphs and other block-level nodes.
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use super::prelude::*; use super::prelude::*;

View File

@ -1,47 +1,20 @@
//! Layout along a row and column raster.
use super::prelude::*; use super::prelude::*;
/// `grid`: Arrange children into a grid. /// `grid`: Arrange children into a grid.
pub fn grid(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub fn grid(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
castable! {
Vec<TrackSizing>,
Expected: "integer or (auto, linear, fractional, or array thereof)",
Value::Auto => vec![TrackSizing::Auto],
Value::Length(v) => vec![TrackSizing::Linear(v.into())],
Value::Relative(v) => vec![TrackSizing::Linear(v.into())],
Value::Linear(v) => vec![TrackSizing::Linear(v)],
Value::Fractional(v) => vec![TrackSizing::Fractional(v)],
Value::Int(v) => vec![TrackSizing::Auto; Value::Int(v).cast()?],
Value::Array(values) => values
.into_iter()
.filter_map(|v| v.cast().ok())
.collect(),
}
castable! {
TrackSizing,
Expected: "auto, linear, or fractional",
Value::Auto => Self::Auto,
Value::Length(v) => Self::Linear(v.into()),
Value::Relative(v) => Self::Linear(v.into()),
Value::Linear(v) => Self::Linear(v),
Value::Fractional(v) => Self::Fractional(v),
}
let columns = args.named("columns")?.unwrap_or_default(); let columns = args.named("columns")?.unwrap_or_default();
let rows = args.named("rows")?.unwrap_or_default(); let rows = args.named("rows")?.unwrap_or_default();
let tracks = Spec::new(columns, rows);
let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default(); let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
let column_gutter = args.named("column-gutter")?; let column_gutter = args.named("column-gutter")?;
let row_gutter = args.named("row-gutter")?; let row_gutter = args.named("row-gutter")?;
let gutter = Spec::new(
column_gutter.unwrap_or_else(|| base_gutter.clone()),
row_gutter.unwrap_or(base_gutter),
);
Ok(Value::block(GridNode { Ok(Value::block(GridNode {
tracks, tracks: Spec::new(columns, rows),
gutter, gutter: Spec::new(
column_gutter.unwrap_or_else(|| base_gutter.clone()),
row_gutter.unwrap_or(base_gutter),
),
children: args.all().map(Node::into_block).collect(), children: args.all().map(Node::into_block).collect(),
})) }))
} }
@ -57,17 +30,6 @@ pub struct GridNode {
pub children: Vec<PackedNode>, pub children: Vec<PackedNode>,
} }
/// Defines how to size a grid cell along an axis.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum TrackSizing {
/// Fit the cell to its contents.
Auto,
/// A length stated in absolute values and/or relative to the parent's size.
Linear(Linear),
/// A length that is the fraction of the remaining free space in the parent.
Fractional(Fractional),
}
impl Layout for GridNode { impl Layout for GridNode {
fn layout( fn layout(
&self, &self,
@ -85,6 +47,42 @@ impl Layout for GridNode {
} }
} }
/// Defines how to size a grid cell along an axis.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum TrackSizing {
/// Fit the cell to its contents.
Auto,
/// A length stated in absolute values and/or relative to the parent's size.
Linear(Linear),
/// A length that is the fraction of the remaining free space in the parent.
Fractional(Fractional),
}
castable! {
Vec<TrackSizing>,
Expected: "integer or (auto, linear, fractional, or array thereof)",
Value::Auto => vec![TrackSizing::Auto],
Value::Length(v) => vec![TrackSizing::Linear(v.into())],
Value::Relative(v) => vec![TrackSizing::Linear(v.into())],
Value::Linear(v) => vec![TrackSizing::Linear(v)],
Value::Fractional(v) => vec![TrackSizing::Fractional(v)],
Value::Int(v) => vec![TrackSizing::Auto; Value::Int(v).cast()?],
Value::Array(values) => values
.into_iter()
.filter_map(|v| v.cast().ok())
.collect(),
}
castable! {
TrackSizing,
Expected: "auto, linear, or fractional",
Value::Auto => Self::Auto,
Value::Length(v) => Self::Linear(v.into()),
Value::Relative(v) => Self::Linear(v.into()),
Value::Linear(v) => Self::Linear(v),
Value::Fractional(v) => Self::Fractional(v),
}
/// Performs grid layout. /// Performs grid layout.
struct GridLayouter<'a> { struct GridLayouter<'a> {
/// The children of the grid. /// The children of the grid.

View File

@ -1,3 +1,5 @@
//! Document-structuring section headings.
use super::prelude::*; use super::prelude::*;
use super::{FontFamily, TextNode}; use super::{FontFamily, TextNode};

View File

@ -1,3 +1,5 @@
//! Raster and vector graphics.
use std::io; use std::io;
use super::prelude::*; use super::prelude::*;
@ -106,6 +108,12 @@ pub enum ImageFit {
Stretch, Stretch,
} }
impl Default for ImageFit {
fn default() -> Self {
Self::Cover
}
}
castable! { castable! {
ImageFit, ImageFit,
Expected: "string", Expected: "string",
@ -116,9 +124,3 @@ castable! {
_ => Err(r#"expected "cover", "contain" or "stretch""#)?, _ => Err(r#"expected "cover", "contain" or "stretch""#)?,
}, },
} }
impl Default for ImageFit {
fn default() -> Self {
Self::Cover
}
}

View File

@ -1,3 +1,5 @@
//! Hyperlinking.
use super::prelude::*; use super::prelude::*;
use crate::util::EcoString; use crate::util::EcoString;

View File

@ -1,3 +1,5 @@
//! Unordered (bulleted) and ordered (numbered) lists.
use std::hash::Hash; use std::hash::Hash;
use super::prelude::*; use super::prelude::*;

View File

@ -3,44 +3,25 @@
//! Call [`new`] to obtain a [`Scope`] containing all standard library //! Call [`new`] to obtain a [`Scope`] containing all standard library
//! definitions. //! definitions.
mod align; pub mod align;
mod columns; pub mod columns;
mod flow; pub mod flow;
mod grid; pub mod grid;
mod heading; pub mod heading;
mod image; pub mod image;
mod link; pub mod link;
mod list; pub mod list;
mod pad; pub mod pad;
mod page; pub mod page;
mod par; pub mod par;
mod placed; pub mod placed;
mod shape; pub mod shape;
mod sized; pub mod sized;
mod spacing; pub mod spacing;
mod stack; pub mod stack;
mod text; pub mod text;
mod transform; pub mod transform;
mod utility; pub mod utility;
/// Helpful imports for creating library functionality.
mod prelude {
pub use std::fmt::{self, Debug, Formatter};
pub use std::num::NonZeroUsize;
pub use std::rc::Rc;
pub use typst_macros::properties;
pub use crate::diag::{At, TypResult};
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::*;
pub use crate::syntax::{Span, Spanned};
pub use crate::util::{EcoString, OptionExt};
}
pub use self::image::*; pub use self::image::*;
pub use align::*; pub use align::*;
@ -62,8 +43,37 @@ pub use text::*;
pub use transform::*; pub use transform::*;
pub use utility::*; pub use utility::*;
use crate::eval::{Scope, Value}; macro_rules! prelude {
use crate::geom::*; ($($reexport:item)*) => {
/// Helpful imports for creating library functionality.
pub mod prelude {
$(#[doc(no_inline)] $reexport)*
}
};
}
prelude! {
pub use std::fmt::{self, Debug, Formatter};
pub use std::num::NonZeroUsize;
pub use std::rc::Rc;
pub use typst_macros::properties;
pub use crate::diag::{At, TypResult};
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::{
Constrain, Constrained, Constraints, Layout, LayoutContext, PackedNode, Regions,
};
pub use crate::syntax::{Span, Spanned};
pub use crate::util::{EcoString, OptionExt};
}
use crate::eval::Scope;
use prelude::*;
/// Construct a scope containing all standard library definitions. /// Construct a scope containing all standard library definitions.
pub fn new() -> Scope { pub fn new() -> Scope {
@ -78,9 +88,8 @@ pub fn new() -> Scope {
std.def_class::<ListNode<Ordered>>("enum"); std.def_class::<ListNode<Ordered>>("enum");
// Text functions. // Text functions.
// TODO(style): These should be classes, once that works for inline nodes.
std.def_func("strike", strike);
std.def_func("underline", underline); std.def_func("underline", underline);
std.def_func("strike", strike);
std.def_func("overline", overline); std.def_func("overline", overline);
std.def_func("link", link); std.def_func("link", link);
@ -93,8 +102,6 @@ pub fn new() -> Scope {
std.def_func("v", v); std.def_func("v", v);
// Layout functions. // Layout functions.
// TODO(style): Decide which of these should be classes
// (and which of their properties should be settable).
std.def_func("box", box_); std.def_func("box", box_);
std.def_func("block", block); std.def_func("block", block);
std.def_func("stack", stack); std.def_func("stack", stack);
@ -160,12 +167,6 @@ dynamic! {
Dir: "direction", Dir: "direction",
} }
castable! {
Paint,
Expected: "color",
Value::Color(color) => Paint::Solid(color),
}
castable! { castable! {
usize, usize,
Expected: "non-negative integer", Expected: "non-negative integer",
@ -173,14 +174,20 @@ castable! {
} }
castable! { castable! {
prelude::NonZeroUsize, NonZeroUsize,
Expected: "positive integer", Expected: "positive integer",
Value::Int(int) => int Value::Int(int) => int
.try_into() .try_into()
.and_then(|n: usize| n.try_into()) .and_then(usize::try_into)
.map_err(|_| "must be positive")?, .map_err(|_| "must be positive")?,
} }
castable! {
Paint,
Expected: "color",
Value::Color(color) => Paint::Solid(color),
}
castable! { castable! {
String, String,
Expected: "string", Expected: "string",

View File

@ -1,3 +1,5 @@
//! Surrounding nodes with extra space.
use super::prelude::*; use super::prelude::*;
/// `pad`: Pad content at the sides. /// `pad`: Pad content at the sides.

View File

@ -1,4 +1,4 @@
#![allow(unused)] //! Pages of paper.
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::str::FromStr; use std::str::FromStr;
@ -6,11 +6,6 @@ use std::str::FromStr;
use super::prelude::*; use super::prelude::*;
use super::{ColumnsNode, PadNode}; use super::{ColumnsNode, PadNode};
/// `pagebreak`: Start a new page.
pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
Ok(Value::Node(Node::Pagebreak))
}
/// Layouts its child onto one or multiple pages. /// Layouts its child onto one or multiple pages.
#[derive(Clone, PartialEq, Hash)] #[derive(Clone, PartialEq, Hash)]
pub struct PageNode { pub struct PageNode {
@ -42,7 +37,7 @@ impl PageNode {
pub const FILL: Option<Paint> = None; pub const FILL: Option<Paint> = None;
/// How many columns the page has. /// How many columns the page has.
pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap(); pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap();
/// How many columns the page has. /// How much space is between the page's columns.
pub const COLUMN_GUTTER: Linear = Relative::new(0.04).into(); pub const COLUMN_GUTTER: Linear = Relative::new(0.04).into();
} }
@ -74,11 +69,12 @@ impl Set for PageNode {
} }
let margins = args.named("margins")?; let margins = args.named("margins")?;
styles.set_opt(Self::FLIPPED, args.named("flipped")?);
styles.set_opt(Self::LEFT, args.named("left")?.or(margins)); styles.set_opt(Self::LEFT, args.named("left")?.or(margins));
styles.set_opt(Self::TOP, args.named("top")?.or(margins)); styles.set_opt(Self::TOP, args.named("top")?.or(margins));
styles.set_opt(Self::RIGHT, args.named("right")?.or(margins)); styles.set_opt(Self::RIGHT, args.named("right")?.or(margins));
styles.set_opt(Self::BOTTOM, args.named("bottom")?.or(margins)); styles.set_opt(Self::BOTTOM, args.named("bottom")?.or(margins));
styles.set_opt(Self::FLIPPED, args.named("flipped")?);
styles.set_opt(Self::FILL, args.named("fill")?); styles.set_opt(Self::FILL, args.named("fill")?);
styles.set_opt(Self::COLUMNS, args.named("columns")?); styles.set_opt(Self::COLUMNS, args.named("columns")?);
styles.set_opt(Self::COLUMN_GUTTER, args.named("column-gutter")?); styles.set_opt(Self::COLUMN_GUTTER, args.named("column-gutter")?);
@ -163,6 +159,36 @@ impl Debug for PageNode {
} }
} }
/// `pagebreak`: Start a new page.
pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
Ok(Value::Node(Node::Pagebreak))
}
/// Defines default margins for a class of related papers.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum PaperClass {
Custom,
Base,
US,
Newspaper,
Book,
}
impl PaperClass {
/// The default margins for this page class.
fn default_margins(self) -> Sides<Linear> {
let f = |r| Relative::new(r).into();
let s = |l, t, r, b| Sides::new(f(l), f(t), f(r), f(b));
match self {
Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842),
Self::Base => s(0.1190, 0.0842, 0.1190, 0.0842),
Self::US => s(0.1760, 0.1092, 0.1760, 0.0910),
Self::Newspaper => s(0.0455, 0.0587, 0.0455, 0.0294),
Self::Book => s(0.1200, 0.0852, 0.1500, 0.0965),
}
}
}
/// Specification of a paper. /// Specification of a paper.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Paper { pub struct Paper {
@ -197,37 +223,6 @@ 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 {
Custom,
Base,
US,
Newspaper,
Book,
}
impl PaperClass {
/// The default margins for this page class.
fn default_margins(self) -> Sides<Linear> {
let f = |r| Relative::new(r).into();
let s = |l, t, r, b| Sides::new(f(l), f(t), f(r), f(b));
match self {
Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842),
Self::Base => s(0.1190, 0.0842, 0.1190, 0.0842),
Self::US => s(0.1760, 0.1092, 0.1760, 0.0910),
Self::Newspaper => s(0.0455, 0.0587, 0.0455, 0.0294),
Self::Book => s(0.1200, 0.0852, 0.1500, 0.0965),
}
}
}
/// Defines paper constants and a paper parsing implementation. /// Defines paper constants and a paper parsing implementation.
macro_rules! papers { macro_rules! papers {
($(($var:ident: $class:ident, $width:expr, $height: expr, $($pats:tt)*))*) => { ($(($var:ident: $class:ident, $width:expr, $height: expr, $($pats:tt)*))*) => {
@ -252,18 +247,6 @@ macro_rules! papers {
} }
} }
} }
/// The error when parsing a [`Paper`] from a string fails.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct ParsePaperError;
impl Display for ParsePaperError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad("invalid paper name")
}
}
impl std::error::Error for ParsePaperError {}
}; };
} }
@ -421,3 +404,21 @@ papers! {
(PRESENTATION_16_9: Base, 297.0, 167.0625, "presentation-16-9") (PRESENTATION_16_9: Base, 297.0, 167.0625, "presentation-16-9")
(PRESENTATION_4_3: Base, 280.0, 210.0, "presentation-4-3") (PRESENTATION_4_3: Base, 280.0, 210.0, "presentation-4-3")
} }
castable! {
Paper,
Expected: "string",
Value::Str(string) => Paper::from_str(&string).map_err(|e| e.to_string())?,
}
/// The error when parsing a [`Paper`] from a string fails.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct ParsePaperError;
impl Display for ParsePaperError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad("invalid paper name")
}
}
impl std::error::Error for ParsePaperError {}

View File

@ -1,3 +1,5 @@
//! Paragraph layout.
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::rc::Rc; use std::rc::Rc;
@ -9,16 +11,6 @@ use super::prelude::*;
use super::{shape, ShapedText, SpacingKind, SpacingNode, TextNode}; use super::{shape, ShapedText, SpacingKind, SpacingNode, TextNode};
use crate::util::{EcoString, RangeExt, RcExt, SliceExt}; use crate::util::{EcoString, RangeExt, RcExt, SliceExt};
/// `parbreak`: Start a new paragraph.
pub fn parbreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
Ok(Value::Node(Node::Parbreak))
}
/// `linebreak`: Start a new line.
pub fn linebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
Ok(Value::Node(Node::Linebreak))
}
/// A node that arranges its children into a paragraph. /// A node that arranges its children into a paragraph.
#[derive(Hash)] #[derive(Hash)]
pub struct ParNode(pub Vec<ParChild>); pub struct ParNode(pub Vec<ParChild>);
@ -62,17 +54,17 @@ impl Set for ParNode {
dir = Some(v); dir = Some(v);
} }
let mut align = None; let align =
if let Some(Spanned { v, span }) = args.named::<Spanned<Align>>("align")? { if let Some(Spanned { v, span }) = args.named::<Spanned<Align>>("align")? {
if v.axis() != SpecAxis::Horizontal { if v.axis() != SpecAxis::Horizontal {
bail!(span, "must be horizontal"); bail!(span, "must be horizontal");
} }
align = Some(v); Some(v)
} } else if let Some(dir) = dir {
Some(if dir == Dir::LTR { Align::Left } else { Align::Right })
if let (Some(dir), None) = (dir, align) { } else {
align = Some(if dir == Dir::LTR { Align::Left } else { Align::Right }); None
} };
styles.set_opt(Self::DIR, dir); styles.set_opt(Self::DIR, dir);
styles.set_opt(Self::ALIGN, align); styles.set_opt(Self::ALIGN, align);
@ -107,8 +99,7 @@ impl Layout for ParNode {
impl ParNode { impl ParNode {
/// Concatenate all text in the paragraph into one string, replacing spacing /// Concatenate all text in the paragraph into one string, replacing spacing
/// with a space character and other non-text nodes with the object /// with a space character and other non-text nodes with the object
/// replacement character. Returns the full text alongside the range each /// replacement character.
/// child spans in the text.
fn collect_text(&self) -> String { fn collect_text(&self) -> String {
let mut text = String::new(); let mut text = String::new();
for string in self.strings() { for string in self.strings() {
@ -190,6 +181,16 @@ impl Debug for ParChild {
} }
} }
/// `parbreak`: Start a new paragraph.
pub fn parbreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
Ok(Value::Node(Node::Parbreak))
}
/// `linebreak`: Start a new line.
pub fn linebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
Ok(Value::Node(Node::Linebreak))
}
/// A paragraph representation in which children are already layouted and text /// A paragraph representation in which children are already layouted and text
/// is separated into shapable runs. /// is separated into shapable runs.
struct ParLayouter<'a> { struct ParLayouter<'a> {

View File

@ -1,3 +1,5 @@
//! Absolute placement of nodes.
use super::prelude::*; use super::prelude::*;
use super::AlignNode; use super::AlignNode;

View File

@ -1,3 +1,5 @@
//! Colorable geometrical shapes.
use std::f64::consts::SQRT_2; use std::f64::consts::SQRT_2;
use super::prelude::*; use super::prelude::*;

View File

@ -1,3 +1,5 @@
//! Horizontal and vertical sizing of nodes.
use super::prelude::*; use super::prelude::*;
/// `box`: Size content and place it into a paragraph. /// `box`: Size content and place it into a paragraph.

View File

@ -1,3 +1,5 @@
//! Horizontal and vertical spacing between nodes.
use super::prelude::*; use super::prelude::*;
/// `h`: Horizontal spacing. /// `h`: Horizontal spacing.

View File

@ -1,3 +1,5 @@
//! Side-by-side layout of nodes along an axis.
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use super::prelude::*; use super::prelude::*;
@ -215,17 +217,17 @@ impl<'a> StackLayouter<'a> {
} }
let mut output = Frame::new(size); let mut output = Frame::new(size);
let mut before = Length::zero(); let mut cursor = Length::zero();
let mut ruler: Align = self.stack.dir.start().into(); let mut ruler: Align = self.stack.dir.start().into();
// Place all frames. // Place all frames.
for item in self.items.drain(..) { for item in self.items.drain(..) {
match item { match item {
StackItem::Absolute(v) => { StackItem::Absolute(v) => {
before += v; cursor += v;
} }
StackItem::Fractional(v) => { StackItem::Fractional(v) => {
before += v.resolve(self.fr, remaining); cursor += v.resolve(self.fr, remaining);
} }
StackItem::Frame(frame, align) => { StackItem::Frame(frame, align) => {
if self.stack.dir.is_positive() { if self.stack.dir.is_positive() {
@ -239,13 +241,13 @@ impl<'a> StackLayouter<'a> {
let child = frame.size.get(self.axis); let child = frame.size.get(self.axis);
let block = ruler.resolve(parent - self.used.main) let block = ruler.resolve(parent - self.used.main)
+ if self.stack.dir.is_positive() { + if self.stack.dir.is_positive() {
before cursor
} else { } else {
self.used.main - child - before self.used.main - child - cursor
}; };
let pos = Gen::new(Length::zero(), block).to_point(self.axis); let pos = Gen::new(Length::zero(), block).to_point(self.axis);
before += child; cursor += child;
output.push_frame(pos, frame); output.push_frame(pos, frame);
} }
} }

View File

@ -1,3 +1,5 @@
//! Text shaping and styling.
use std::borrow::Cow; use std::borrow::Cow;
use std::convert::TryInto; use std::convert::TryInto;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
@ -15,33 +17,6 @@ use crate::font::{
use crate::geom::{Dir, Em, Length, Point, Size}; use crate::geom::{Dir, Em, Length, Point, Size};
use crate::util::{EcoString, SliceExt}; use crate::util::{EcoString, SliceExt};
/// `strike`: Typeset striken-through text.
pub fn strike(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
line_impl(args, LineKind::Strikethrough)
}
/// `underline`: Typeset underlined text.
pub fn underline(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
line_impl(args, LineKind::Underline)
}
/// `overline`: Typeset text with an overline.
pub fn overline(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
line_impl(args, LineKind::Overline)
}
fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> {
let stroke = args.named("stroke")?.or_else(|| args.find());
let thickness = args.named::<Linear>("thickness")?.or_else(|| args.find());
let offset = args.named("offset")?;
let extent = args.named("extent")?.unwrap_or_default();
let body: Node = args.expect("body")?;
let deco = LineDecoration { kind, stroke, thickness, offset, extent };
Ok(Value::Node(
body.styled(Styles::one(TextNode::LINES, vec![deco])),
))
}
/// A single run of text with the same style. /// A single run of text with the same style.
#[derive(Hash)] #[derive(Hash)]
pub struct TextNode { pub struct TextNode {
@ -216,6 +191,21 @@ impl Debug for FontFamily {
} }
} }
dynamic! {
FontFamily: "font family",
Value::Str(string) => Self::named(&string),
}
castable! {
Vec<FontFamily>,
Expected: "string, generic family or array thereof",
Value::Str(string) => vec![FontFamily::named(&string)],
Value::Array(values) => {
values.into_iter().filter_map(|v| v.cast().ok()).collect()
},
@family: FontFamily => vec![family.clone()],
}
/// A specific font family like "Arial". /// A specific font family like "Arial".
#[derive(Clone, Eq, PartialEq, Hash)] #[derive(Clone, Eq, PartialEq, Hash)]
pub struct NamedFamily(String); pub struct NamedFamily(String);
@ -238,21 +228,6 @@ impl Debug for NamedFamily {
} }
} }
dynamic! {
FontFamily: "font family",
Value::Str(string) => Self::named(&string),
}
castable! {
Vec<FontFamily>,
Expected: "string, generic family or array thereof",
Value::Str(string) => vec![FontFamily::named(&string)],
Value::Array(values) => {
values.into_iter().filter_map(|v| v.cast().ok()).collect()
},
@family: FontFamily => vec![family.clone()],
}
castable! { castable! {
Vec<NamedFamily>, Vec<NamedFamily>,
Expected: "string or array of strings", Expected: "string or array of strings",
@ -421,6 +396,33 @@ castable! {
.collect(), .collect(),
} }
/// `strike`: Typeset striken-through text.
pub fn strike(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
line_impl(args, LineKind::Strikethrough)
}
/// `underline`: Typeset underlined text.
pub fn underline(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
line_impl(args, LineKind::Underline)
}
/// `overline`: Typeset text with an overline.
pub fn overline(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
line_impl(args, LineKind::Overline)
}
fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> {
let stroke = args.named("stroke")?.or_else(|| args.find());
let thickness = args.named::<Linear>("thickness")?.or_else(|| args.find());
let offset = args.named("offset")?;
let extent = args.named("extent")?.unwrap_or_default();
let body: Node = args.expect("body")?;
let deco = LineDecoration { kind, stroke, thickness, offset, extent };
Ok(Value::Node(
body.styled(Styles::one(TextNode::LINES, vec![deco])),
))
}
/// Defines a line that is positioned over, under or on top of text. /// Defines a line that is positioned over, under or on top of text.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct LineDecoration { pub struct LineDecoration {

View File

@ -1,3 +1,5 @@
//! Affine transformations on nodes.
use super::prelude::*; use super::prelude::*;
use crate::geom::Transform; use crate::geom::Transform;
@ -20,7 +22,7 @@ pub fn scale(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
/// `rotate`: Rotate content without affecting layout. /// `rotate`: Rotate content without affecting layout.
pub fn rotate(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub fn rotate(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let angle = args.expect("angle")?; let angle = args.named("angle")?.or_else(|| args.find()).unwrap_or_default();
let transform = Transform::rotation(angle); let transform = Transform::rotation(angle);
transform_impl(args, transform) transform_impl(args, transform)
} }

View File

@ -1,3 +1,5 @@
//! Computational utility functions.
use std::cmp::Ordering; use std::cmp::Ordering;
use std::str::FromStr; use std::str::FromStr;
@ -44,7 +46,7 @@ pub fn join(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
Ok(result) Ok(result)
} }
/// `int`: Try to convert a value to a integer. /// `int`: Convert a value to a integer.
pub fn int(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub fn int(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let Spanned { v, span } = args.expect("value")?; let Spanned { v, span } = args.expect("value")?;
Ok(Value::Int(match v { Ok(Value::Int(match v {
@ -59,7 +61,7 @@ pub fn int(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
})) }))
} }
/// `float`: Try to convert a value to a float. /// `float`: Convert a value to a float.
pub fn float(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub fn float(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let Spanned { v, span } = args.expect("value")?; let Spanned { v, span } = args.expect("value")?;
Ok(Value::Float(match v { Ok(Value::Float(match v {