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)) =
(a.downcast::<Align>(), b.downcast::<Align>())
{
dynamic! {
Spec<Align>: "2d alignment",
}
return if a.axis() != b.axis() {
Ok(Dyn(Dynamic::new(match a.axis() {
SpecAxis::Horizontal => Spec { x: a, y: b },

View File

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

View File

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

View File

@ -1,47 +1,20 @@
//! Layout along a row and column raster.
use super::prelude::*;
/// `grid`: Arrange children into a grid.
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 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 column_gutter = args.named("column-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 {
tracks,
gutter,
tracks: Spec::new(columns, rows),
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(),
}))
}
@ -57,17 +30,6 @@ pub struct GridNode {
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 {
fn layout(
&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.
struct GridLayouter<'a> {
/// The children of the grid.

View File

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

View File

@ -1,3 +1,5 @@
//! Raster and vector graphics.
use std::io;
use super::prelude::*;
@ -106,6 +108,12 @@ pub enum ImageFit {
Stretch,
}
impl Default for ImageFit {
fn default() -> Self {
Self::Cover
}
}
castable! {
ImageFit,
Expected: "string",
@ -116,9 +124,3 @@ castable! {
_ => 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 crate::util::EcoString;

View File

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

View File

@ -3,44 +3,25 @@
//! Call [`new`] to obtain a [`Scope`] containing all standard library
//! definitions.
mod align;
mod columns;
mod flow;
mod grid;
mod heading;
mod image;
mod link;
mod list;
mod pad;
mod page;
mod par;
mod placed;
mod shape;
mod sized;
mod spacing;
mod stack;
mod text;
mod transform;
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 mod align;
pub mod columns;
pub mod flow;
pub mod grid;
pub mod heading;
pub mod image;
pub mod link;
pub mod list;
pub mod pad;
pub mod page;
pub mod par;
pub mod placed;
pub mod shape;
pub mod sized;
pub mod spacing;
pub mod stack;
pub mod text;
pub mod transform;
pub mod utility;
pub use self::image::*;
pub use align::*;
@ -62,8 +43,37 @@ pub use text::*;
pub use transform::*;
pub use utility::*;
use crate::eval::{Scope, Value};
use crate::geom::*;
macro_rules! prelude {
($($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.
pub fn new() -> Scope {
@ -78,9 +88,8 @@ pub fn new() -> Scope {
std.def_class::<ListNode<Ordered>>("enum");
// 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("strike", strike);
std.def_func("overline", overline);
std.def_func("link", link);
@ -93,8 +102,6 @@ pub fn new() -> Scope {
std.def_func("v", v);
// 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("block", block);
std.def_func("stack", stack);
@ -160,12 +167,6 @@ dynamic! {
Dir: "direction",
}
castable! {
Paint,
Expected: "color",
Value::Color(color) => Paint::Solid(color),
}
castable! {
usize,
Expected: "non-negative integer",
@ -173,14 +174,20 @@ castable! {
}
castable! {
prelude::NonZeroUsize,
NonZeroUsize,
Expected: "positive integer",
Value::Int(int) => int
.try_into()
.and_then(|n: usize| n.try_into())
.and_then(usize::try_into)
.map_err(|_| "must be positive")?,
}
castable! {
Paint,
Expected: "color",
Value::Color(color) => Paint::Solid(color),
}
castable! {
String,
Expected: "string",

View File

@ -1,3 +1,5 @@
//! Surrounding nodes with extra space.
use super::prelude::*;
/// `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::str::FromStr;
@ -6,11 +6,6 @@ use std::str::FromStr;
use super::prelude::*;
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.
#[derive(Clone, PartialEq, Hash)]
pub struct PageNode {
@ -42,7 +37,7 @@ impl PageNode {
pub const FILL: Option<Paint> = None;
/// How many columns the page has.
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();
}
@ -74,11 +69,12 @@ impl Set for PageNode {
}
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::TOP, args.named("top")?.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::FLIPPED, args.named("flipped")?);
styles.set_opt(Self::FILL, args.named("fill")?);
styles.set_opt(Self::COLUMNS, args.named("columns")?);
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.
#[derive(Debug, Copy, Clone)]
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.
macro_rules! papers {
($(($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_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::rc::Rc;
@ -9,16 +11,6 @@ use super::prelude::*;
use super::{shape, ShapedText, SpacingKind, SpacingNode, TextNode};
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.
#[derive(Hash)]
pub struct ParNode(pub Vec<ParChild>);
@ -62,17 +54,17 @@ impl Set for ParNode {
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 });
}
let align =
if let Some(Spanned { v, span }) = args.named::<Spanned<Align>>("align")? {
if v.axis() != SpecAxis::Horizontal {
bail!(span, "must be horizontal");
}
Some(v)
} else if let Some(dir) = dir {
Some(if dir == Dir::LTR { Align::Left } else { Align::Right })
} else {
None
};
styles.set_opt(Self::DIR, dir);
styles.set_opt(Self::ALIGN, align);
@ -107,8 +99,7 @@ impl Layout for ParNode {
impl ParNode {
/// Concatenate all text in the paragraph into one string, replacing spacing
/// with a space character and other non-text nodes with the object
/// replacement character. Returns the full text alongside the range each
/// child spans in the text.
/// replacement character.
fn collect_text(&self) -> String {
let mut text = String::new();
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
/// is separated into shapable runs.
struct ParLayouter<'a> {

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
//! Horizontal and vertical spacing between nodes.
use super::prelude::*;
/// `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 super::prelude::*;
@ -215,17 +217,17 @@ impl<'a> StackLayouter<'a> {
}
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();
// Place all frames.
for item in self.items.drain(..) {
match item {
StackItem::Absolute(v) => {
before += v;
cursor += v;
}
StackItem::Fractional(v) => {
before += v.resolve(self.fr, remaining);
cursor += v.resolve(self.fr, remaining);
}
StackItem::Frame(frame, align) => {
if self.stack.dir.is_positive() {
@ -239,13 +241,13 @@ impl<'a> StackLayouter<'a> {
let child = frame.size.get(self.axis);
let block = ruler.resolve(parent - self.used.main)
+ if self.stack.dir.is_positive() {
before
cursor
} else {
self.used.main - child - before
self.used.main - child - cursor
};
let pos = Gen::new(Length::zero(), block).to_point(self.axis);
before += child;
cursor += child;
output.push_frame(pos, frame);
}
}

View File

@ -1,3 +1,5 @@
//! Text shaping and styling.
use std::borrow::Cow;
use std::convert::TryInto;
use std::fmt::{self, Debug, Formatter};
@ -15,33 +17,6 @@ use crate::font::{
use crate::geom::{Dir, Em, Length, Point, Size};
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.
#[derive(Hash)]
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".
#[derive(Clone, Eq, PartialEq, Hash)]
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! {
Vec<NamedFamily>,
Expected: "string or array of strings",
@ -421,6 +396,33 @@ castable! {
.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.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct LineDecoration {

View File

@ -1,3 +1,5 @@
//! Affine transformations on nodes.
use super::prelude::*;
use crate::geom::Transform;
@ -20,7 +22,7 @@ pub fn scale(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
/// `rotate`: Rotate content without affecting layout.
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);
transform_impl(args, transform)
}

View File

@ -1,3 +1,5 @@
//! Computational utility functions.
use std::cmp::Ordering;
use std::str::FromStr;
@ -44,7 +46,7 @@ pub fn join(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
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> {
let Spanned { v, span } = args.expect("value")?;
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> {
let Spanned { v, span } = args.expect("value")?;
Ok(Value::Float(match v {