mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Tidying
This commit is contained in:
parent
9624ad635b
commit
bd304b99e5
@ -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 },
|
||||||
|
@ -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),
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -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
|
||||||
|
@ -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::*;
|
||||||
|
@ -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.
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Document-structuring section headings.
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::{FontFamily, TextNode};
|
use super::{FontFamily, TextNode};
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Hyperlinking.
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Unordered (bulleted) and ordered (numbered) lists.
|
||||||
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
@ -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",
|
||||||
|
@ -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.
|
||||||
|
@ -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 {}
|
||||||
|
@ -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> {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Absolute placement of nodes.
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::AlignNode;
|
use super::AlignNode;
|
||||||
|
|
||||||
|
@ -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::*;
|
||||||
|
@ -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.
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Horizontal and vertical spacing between nodes.
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
|
||||||
/// `h`: Horizontal spacing.
|
/// `h`: Horizontal spacing.
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user