summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/eval/class.rs2
-rw-r--r--src/eval/layout.rs4
-rw-r--r--src/eval/mod.rs16
-rw-r--r--src/eval/styles.rs3
-rw-r--r--src/eval/template.rs7
-rw-r--r--src/library/deco.rs74
-rw-r--r--src/library/elements/heading.rs (renamed from src/library/heading.rs)6
-rw-r--r--src/library/elements/image.rs (renamed from src/library/image.rs)6
-rw-r--r--src/library/elements/list.rs (renamed from src/library/list.rs)16
-rw-r--r--src/library/elements/math.rs (renamed from src/library/math.rs)4
-rw-r--r--src/library/elements/mod.rs15
-rw-r--r--src/library/elements/shape.rs (renamed from src/library/shape.rs)26
-rw-r--r--src/library/elements/table.rs (renamed from src/library/table.rs)6
-rw-r--r--src/library/layout/align.rs (renamed from src/library/align.rs)6
-rw-r--r--src/library/layout/columns.rs (renamed from src/library/columns.rs)6
-rw-r--r--src/library/layout/container.rs (renamed from src/library/container.rs)8
-rw-r--r--src/library/layout/flow.rs (renamed from src/library/flow.rs)7
-rw-r--r--src/library/layout/grid.rs (renamed from src/library/grid.rs)4
-rw-r--r--src/library/layout/hide.rs (renamed from src/library/hide.rs)4
-rw-r--r--src/library/layout/mod.rs27
-rw-r--r--src/library/layout/pad.rs (renamed from src/library/pad.rs)4
-rw-r--r--src/library/layout/page.rs (renamed from src/library/page.rs)4
-rw-r--r--src/library/layout/place.rs (renamed from src/library/place.rs)4
-rw-r--r--src/library/layout/spacing.rs (renamed from src/library/spacing.rs)4
-rw-r--r--src/library/layout/stack.rs (renamed from src/library/stack.rs)4
-rw-r--r--src/library/layout/transform.rs (renamed from src/library/transform.rs)19
-rw-r--r--src/library/mod.rs206
-rw-r--r--src/library/prelude.rs20
-rw-r--r--src/library/text/deco.rs250
-rw-r--r--src/library/text/link.rs (renamed from src/library/link.rs)4
-rw-r--r--src/library/text/mod.rs409
-rw-r--r--src/library/text/par.rs (renamed from src/library/par.rs)219
-rw-r--r--src/library/text/raw.rs (renamed from src/library/raw.rs)6
-rw-r--r--src/library/text/shaping.rs (renamed from src/library/text.rs)578
-rw-r--r--src/library/utility/math.rs114
-rw-r--r--src/library/utility/mod.rs (renamed from src/library/utility.rs)149
-rw-r--r--src/library/utility/numbering.rs (renamed from src/library/numbering.rs)86
-rw-r--r--tests/ref/elements/enum.png (renamed from tests/ref/markup/enums.png)bin26118 -> 26118 bytes
-rw-r--r--tests/ref/elements/heading.png (renamed from tests/ref/markup/heading.png)bin25291 -> 25291 bytes
-rw-r--r--tests/ref/elements/image.png (renamed from tests/ref/layout/image.png)bin183386 -> 183386 bytes
-rw-r--r--tests/ref/elements/list.png (renamed from tests/ref/markup/lists.png)bin21396 -> 21396 bytes
-rw-r--r--tests/ref/elements/math.png (renamed from tests/ref/markup/math.png)bin3444 -> 3444 bytes
-rw-r--r--tests/ref/elements/shape-aspect.png (renamed from tests/ref/layout/shape-aspect.png)bin4663 -> 4663 bytes
-rw-r--r--tests/ref/elements/shape-circle.png (renamed from tests/ref/layout/shape-circle.png)bin40653 -> 40653 bytes
-rw-r--r--tests/ref/elements/shape-ellipse.png (renamed from tests/ref/layout/shape-ellipse.png)bin22046 -> 22046 bytes
-rw-r--r--tests/ref/elements/shape-fill-stroke.png (renamed from tests/ref/layout/shape-fill-stroke.png)bin2893 -> 2893 bytes
-rw-r--r--tests/ref/elements/shape-rect.png (renamed from tests/ref/layout/shape-rect.png)bin7261 -> 7261 bytes
-rw-r--r--tests/ref/elements/shape-square.png (renamed from tests/ref/layout/shape-square.png)bin18777 -> 18777 bytes
-rw-r--r--tests/ref/elements/table.png (renamed from tests/ref/layout/table.png)bin1566 -> 1566 bytes
-rw-r--r--tests/ref/layout/container.png (renamed from tests/ref/layout/box-block.png)bin7628 -> 7628 bytes
-rw-r--r--tests/ref/markup/linebreak.pngbin641 -> 0 bytes
-rw-r--r--tests/ref/text/deco.png (renamed from tests/ref/text/decorations.png)bin29421 -> 29421 bytes
-rw-r--r--tests/ref/text/emph-strong.png (renamed from tests/ref/markup/emph-strong.png)bin6988 -> 6988 bytes
-rw-r--r--tests/ref/text/escape.png (renamed from tests/ref/markup/escape.png)bin13675 -> 13675 bytes
-rw-r--r--tests/ref/text/linebreak.png (renamed from tests/ref/text/linebreaks.png)bin14308 -> 14308 bytes
-rw-r--r--tests/ref/text/link.png (renamed from tests/ref/text/links.png)bin35262 -> 35262 bytes
-rw-r--r--tests/ref/text/raw.png (renamed from tests/ref/markup/raw.png)bin22255 -> 22255 bytes
-rw-r--r--tests/ref/text/shorthands.png (renamed from tests/ref/markup/shorthands.png)bin3838 -> 3838 bytes
-rw-r--r--tests/typ/elements/enum.typ (renamed from tests/typ/markup/enums.typ)0
-rw-r--r--tests/typ/elements/heading.typ (renamed from tests/typ/markup/heading.typ)0
-rw-r--r--tests/typ/elements/image.typ (renamed from tests/typ/layout/image.typ)0
-rw-r--r--tests/typ/elements/list.typ (renamed from tests/typ/markup/lists.typ)0
-rw-r--r--tests/typ/elements/math.typ (renamed from tests/typ/markup/math.typ)0
-rw-r--r--tests/typ/elements/shape-aspect.typ (renamed from tests/typ/layout/shape-aspect.typ)0
-rw-r--r--tests/typ/elements/shape-circle.typ (renamed from tests/typ/layout/shape-circle.typ)0
-rw-r--r--tests/typ/elements/shape-ellipse.typ (renamed from tests/typ/layout/shape-ellipse.typ)0
-rw-r--r--tests/typ/elements/shape-fill-stroke.typ (renamed from tests/typ/layout/shape-fill-stroke.typ)0
-rw-r--r--tests/typ/elements/shape-rect.typ (renamed from tests/typ/layout/shape-rect.typ)0
-rw-r--r--tests/typ/elements/shape-square.typ (renamed from tests/typ/layout/shape-square.typ)0
-rw-r--r--tests/typ/elements/table.typ (renamed from tests/typ/layout/table.typ)0
-rw-r--r--tests/typ/layout/container.typ (renamed from tests/typ/layout/box-block.typ)0
-rw-r--r--tests/typ/markup/linebreak.typ4
-rw-r--r--tests/typ/text/deco.typ (renamed from tests/typ/text/decorations.typ)0
-rw-r--r--tests/typ/text/emph-strong.typ (renamed from tests/typ/markup/emph-strong.typ)0
-rw-r--r--tests/typ/text/escape.typ (renamed from tests/typ/markup/escape.typ)0
-rw-r--r--tests/typ/text/linebreak.typ (renamed from tests/typ/text/linebreaks.typ)2
-rw-r--r--tests/typ/text/link.typ (renamed from tests/typ/text/links.typ)0
-rw-r--r--tests/typ/text/raw.typ (renamed from tests/typ/markup/raw.typ)0
-rw-r--r--tests/typ/text/shorthands.typ (renamed from tests/typ/markup/shorthands.typ)0
-rw-r--r--tests/typ/utility/collection.typ6
-rw-r--r--tests/typ/utility/strings.typ12
-rw-r--r--tests/typeset.rs3
82 files changed, 1174 insertions, 1180 deletions
diff --git a/src/eval/class.rs b/src/eval/class.rs
index 5e1857d7..2cced74d 100644
--- a/src/eval/class.rs
+++ b/src/eval/class.rs
@@ -33,7 +33,7 @@ use crate::Context;
/// ```
///
/// [construct]: Self::construct
-/// [`TextNode`]: crate::library::TextNode
+/// [`TextNode`]: crate::library::text::TextNode
/// [`set`]: Self::set
#[derive(Clone)]
pub struct Class {
diff --git a/src/eval/layout.rs b/src/eval/layout.rs
index 38ad3977..02912544 100644
--- a/src/eval/layout.rs
+++ b/src/eval/layout.rs
@@ -9,7 +9,7 @@ use crate::diag::TypResult;
use crate::eval::StyleChain;
use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
use crate::geom::{Align, Length, Linear, Paint, Point, Sides, Size, Spec, Transform};
-use crate::library::{AlignNode, PadNode, TransformNode, MOVE};
+use crate::library::layout::{AlignNode, MoveNode, PadNode};
use crate::util::Prehashed;
use crate::Context;
@@ -203,7 +203,7 @@ impl LayoutNode {
/// Transform this node's contents without affecting layout.
pub fn moved(self, offset: Point) -> Self {
if !offset.is_zero() {
- TransformNode::<MOVE> {
+ MoveNode {
transform: Transform::translation(offset.x, offset.y),
child: self,
}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 380e9e9d..f8b4b0f0 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -120,7 +120,7 @@ impl Eval for StrongNode {
type Output = Template;
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
- Ok(Template::show(library::StrongNode(
+ Ok(Template::show(library::text::StrongNode(
self.body().eval(ctx, scp)?,
)))
}
@@ -130,7 +130,7 @@ impl Eval for EmphNode {
type Output = Template;
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
- Ok(Template::show(library::EmphNode(
+ Ok(Template::show(library::text::EmphNode(
self.body().eval(ctx, scp)?,
)))
}
@@ -140,12 +140,12 @@ impl Eval for RawNode {
type Output = Template;
fn eval(&self, _: &mut Context, _: &mut Scopes) -> EvalResult<Self::Output> {
- let template = Template::show(library::RawNode {
+ let template = Template::show(library::text::RawNode {
text: self.text.clone(),
block: self.block,
});
Ok(match self.lang {
- Some(_) => template.styled(library::RawNode::LANG, self.lang.clone()),
+ Some(_) => template.styled(library::text::RawNode::LANG, self.lang.clone()),
None => template,
})
}
@@ -155,7 +155,7 @@ impl Eval for MathNode {
type Output = Template;
fn eval(&self, _: &mut Context, _: &mut Scopes) -> EvalResult<Self::Output> {
- Ok(Template::show(library::MathNode {
+ Ok(Template::show(library::elements::MathNode {
formula: self.formula.clone(),
display: self.display,
}))
@@ -166,7 +166,7 @@ impl Eval for HeadingNode {
type Output = Template;
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
- Ok(Template::show(library::HeadingNode {
+ Ok(Template::show(library::elements::HeadingNode {
body: self.body().eval(ctx, scp)?,
level: self.level(),
}))
@@ -177,7 +177,7 @@ impl Eval for ListNode {
type Output = Template;
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
- Ok(Template::List(library::ListItem {
+ Ok(Template::List(library::elements::ListItem {
number: None,
body: Box::new(self.body().eval(ctx, scp)?),
}))
@@ -188,7 +188,7 @@ impl Eval for EnumNode {
type Output = Template;
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
- Ok(Template::Enum(library::ListItem {
+ Ok(Template::Enum(library::elements::ListItem {
number: self.number(),
body: Box::new(self.body().eval(ctx, scp)?),
}))
diff --git a/src/eval/styles.rs b/src/eval/styles.rs
index 8e7bd4b6..5a8371a9 100644
--- a/src/eval/styles.rs
+++ b/src/eval/styles.rs
@@ -5,7 +5,8 @@ use std::sync::Arc;
use super::{Args, Func, Span, Template, Value};
use crate::diag::{At, TypResult};
-use crate::library::{PageNode, ParNode};
+use crate::library::layout::PageNode;
+use crate::library::text::ParNode;
use crate::Context;
/// A map of style properties.
diff --git a/src/eval/template.rs b/src/eval/template.rs
index 747e5d44..94cc0aff 100644
--- a/src/eval/template.rs
+++ b/src/eval/template.rs
@@ -10,11 +10,10 @@ use super::{
StyleMap, StyleVecBuilder,
};
use crate::diag::StrResult;
+use crate::library::elements::{ListItem, ListKind, ListNode, ORDERED, UNORDERED};
+use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, SpacingKind};
use crate::library::prelude::*;
-use crate::library::{
- DecoNode, FlowChild, FlowNode, ListItem, ListKind, ListNode, PageNode, ParChild,
- ParNode, PlaceNode, SpacingKind, TextNode, ORDERED, UNDERLINE, UNORDERED,
-};
+use crate::library::text::{DecoNode, ParChild, ParNode, TextNode, UNDERLINE};
use crate::util::EcoString;
/// Composable representation of styled content.
diff --git a/src/library/deco.rs b/src/library/deco.rs
deleted file mode 100644
index 8c2b5a9a..00000000
--- a/src/library/deco.rs
+++ /dev/null
@@ -1,74 +0,0 @@
-//! Text decorations.
-
-use super::prelude::*;
-use super::TextNode;
-
-/// Typeset underline, striken-through or overlined text.
-#[derive(Debug, Hash)]
-pub struct DecoNode<const L: DecoLine>(pub Template);
-
-#[class]
-impl<const L: DecoLine> DecoNode<L> {
- /// Stroke color of the line, defaults to the text color if `None`.
- #[shorthand]
- pub const STROKE: Option<Paint> = None;
- /// Thickness of the line's strokes (dependent on scaled font size), read
- /// from the font tables if `None`.
- #[shorthand]
- pub const THICKNESS: Option<Linear> = None;
- /// Position of the line relative to the baseline (dependent on scaled font
- /// size), read from the font tables if `None`.
- pub const OFFSET: Option<Linear> = None;
- /// Amount that the line will be longer or shorter than its associated text
- /// (dependent on scaled font size).
- pub const EXTENT: Linear = Linear::zero();
- /// Whether the line skips sections in which it would collide
- /// with the glyphs. Does not apply to strikethrough.
- pub const EVADE: bool = true;
-
- fn construct(_: &mut Context, args: &mut Args) -> TypResult<Template> {
- Ok(Template::show(Self(args.expect::<Template>("body")?)))
- }
-}
-
-impl<const L: DecoLine> Show for DecoNode<L> {
- fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Template> {
- Ok(styles
- .show(self, ctx, [Value::Template(self.0.clone())])?
- .unwrap_or_else(|| {
- self.0.clone().styled(TextNode::LINES, vec![Decoration {
- line: L,
- stroke: styles.get(Self::STROKE),
- thickness: styles.get(Self::THICKNESS),
- offset: styles.get(Self::OFFSET),
- extent: styles.get(Self::EXTENT),
- evade: styles.get(Self::EVADE),
- }])
- }))
- }
-}
-
-/// Defines a line that is positioned over, under or on top of text.
-///
-/// For more details, see [`DecoNode`].
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub struct Decoration {
- pub line: DecoLine,
- pub stroke: Option<Paint>,
- pub thickness: Option<Linear>,
- pub offset: Option<Linear>,
- pub extent: Linear,
- pub evade: bool,
-}
-
-/// A kind of decorative line.
-pub type DecoLine = usize;
-
-/// A line under text.
-pub const UNDERLINE: DecoLine = 0;
-
-/// A line through text.
-pub const STRIKETHROUGH: DecoLine = 1;
-
-/// A line over text.
-pub const OVERLINE: DecoLine = 2;
diff --git a/src/library/heading.rs b/src/library/elements/heading.rs
index 0c9c8e27..a67f4f24 100644
--- a/src/library/heading.rs
+++ b/src/library/elements/heading.rs
@@ -1,7 +1,5 @@
-//! Document-structuring section headings.
-
-use super::prelude::*;
-use super::{FontFamily, TextNode};
+use crate::library::prelude::*;
+use crate::library::text::{FontFamily, TextNode};
/// A section heading.
#[derive(Debug, Hash)]
diff --git a/src/library/image.rs b/src/library/elements/image.rs
index c1220734..66fb8f4b 100644
--- a/src/library/image.rs
+++ b/src/library/elements/image.rs
@@ -1,9 +1,7 @@
-//! Raster and vector graphics.
-
-use super::prelude::*;
-use super::TextNode;
use crate::diag::Error;
use crate::image::ImageId;
+use crate::library::prelude::*;
+use crate::library::text::TextNode;
/// Show a raster or vector graphic.
#[derive(Debug, Hash)]
diff --git a/src/library/list.rs b/src/library/elements/list.rs
index baa8a0c9..726a2834 100644
--- a/src/library/list.rs
+++ b/src/library/elements/list.rs
@@ -1,13 +1,12 @@
-//! Unordered (bulleted) and ordered (numbered) lists.
-
-use super::prelude::*;
-use super::{GridNode, Numbering, ParNode, TextNode, TrackSizing};
-
+use crate::library::layout::{GridNode, TrackSizing};
+use crate::library::prelude::*;
+use crate::library::text::{ParNode, TextNode};
+use crate::library::utility::Numbering;
use crate::parse::Scanner;
-/// An unordered or ordered list.
+/// An unordered (bulleted) or ordered (numbered) list.
#[derive(Debug, Hash)]
-pub struct ListNode<const L: ListKind> {
+pub struct ListNode<const L: ListKind = UNORDERED> {
/// Where the list starts.
pub start: usize,
/// If true, there is paragraph spacing between the items, if false
@@ -26,6 +25,9 @@ pub struct ListItem {
pub body: Box<Template>,
}
+/// An ordered list.
+pub type EnumNode = ListNode<ORDERED>;
+
#[class]
impl<const L: ListKind> ListNode<L> {
/// How the list is labelled.
diff --git a/src/library/math.rs b/src/library/elements/math.rs
index 5d0ae41b..761b4480 100644
--- a/src/library/math.rs
+++ b/src/library/elements/math.rs
@@ -1,6 +1,4 @@
-//! Mathematical formulas.
-
-use super::prelude::*;
+use crate::library::prelude::*;
/// A mathematical formula.
#[derive(Debug, Hash)]
diff --git a/src/library/elements/mod.rs b/src/library/elements/mod.rs
new file mode 100644
index 00000000..572010bb
--- /dev/null
+++ b/src/library/elements/mod.rs
@@ -0,0 +1,15 @@
+//! Primitive and semantic elements.
+
+mod heading;
+mod image;
+mod list;
+mod math;
+mod shape;
+mod table;
+
+pub use self::image::*;
+pub use heading::*;
+pub use list::*;
+pub use math::*;
+pub use shape::*;
+pub use table::*;
diff --git a/src/library/shape.rs b/src/library/elements/shape.rs
index bf159c52..8b967412 100644
--- a/src/library/shape.rs
+++ b/src/library/elements/shape.rs
@@ -1,14 +1,24 @@
-//! Colorable geometrical shapes.
-
use std::f64::consts::SQRT_2;
-use super::prelude::*;
-use super::TextNode;
+use crate::library::prelude::*;
+use crate::library::text::TextNode;
/// Place a node into a sizable and fillable shape.
#[derive(Debug, Hash)]
pub struct ShapeNode<const S: ShapeKind>(pub Option<LayoutNode>);
+/// Place a node into a square.
+pub type SquareNode = ShapeNode<SQUARE>;
+
+/// Place a node into a rectangle.
+pub type RectNode = ShapeNode<RECT>;
+
+/// Place a node into a circle.
+pub type CircleNode = ShapeNode<CIRCLE>;
+
+/// Place a node into an ellipse.
+pub type EllipseNode = ShapeNode<ELLIPSE>;
+
#[class]
impl<const S: ShapeKind> ShapeNode<S> {
/// How to fill the shape.
@@ -134,16 +144,16 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
pub type ShapeKind = usize;
/// A rectangle with equal side lengths.
-pub const SQUARE: ShapeKind = 0;
+const SQUARE: ShapeKind = 0;
/// A quadrilateral with four right angles.
-pub const RECT: ShapeKind = 1;
+const RECT: ShapeKind = 1;
/// An ellipse with coinciding foci.
-pub const CIRCLE: ShapeKind = 2;
+const CIRCLE: ShapeKind = 2;
/// A curve around two focal points.
-pub const ELLIPSE: ShapeKind = 3;
+const ELLIPSE: ShapeKind = 3;
/// Whether a shape kind is curvy.
fn is_round(kind: ShapeKind) -> bool {
diff --git a/src/library/table.rs b/src/library/elements/table.rs
index 07f28737..555dcc44 100644
--- a/src/library/table.rs
+++ b/src/library/elements/table.rs
@@ -1,7 +1,5 @@
-//! Tabular container.
-
-use super::prelude::*;
-use super::{GridNode, TrackSizing};
+use crate::library::layout::{GridNode, TrackSizing};
+use crate::library::prelude::*;
/// A table of items.
#[derive(Debug, Hash)]
diff --git a/src/library/align.rs b/src/library/layout/align.rs
index 2fd734d3..7fbe0d01 100644
--- a/src/library/align.rs
+++ b/src/library/layout/align.rs
@@ -1,7 +1,5 @@
-//! Aligning nodes in their parent container.
-
-use super::prelude::*;
-use super::ParNode;
+use crate::library::prelude::*;
+use crate::library::text::ParNode;
/// Align a node along the layouting axes.
#[derive(Debug, Hash)]
diff --git a/src/library/columns.rs b/src/library/layout/columns.rs
index 3ce9b6e5..167e7068 100644
--- a/src/library/columns.rs
+++ b/src/library/layout/columns.rs
@@ -1,7 +1,5 @@
-//! Multi-column layouts.
-
-use super::prelude::*;
-use super::ParNode;
+use crate::library::prelude::*;
+use crate::library::text::ParNode;
/// Separate a region into multiple equally sized columns.
#[derive(Debug, Hash)]
diff --git a/src/library/container.rs b/src/library/layout/container.rs
index 5517d927..55579878 100644
--- a/src/library/container.rs
+++ b/src/library/layout/container.rs
@@ -1,8 +1,6 @@
-//! Inline- and block-level containers.
+use crate::library::prelude::*;
-use super::prelude::*;
-
-/// Size content and place it into a paragraph.
+/// An inline-level container that sizes content and places it into a paragraph.
pub struct BoxNode;
#[class]
@@ -15,7 +13,7 @@ impl BoxNode {
}
}
-/// Place content into a separate flow.
+/// A block-level container that places content into a separate flow.
pub struct BlockNode;
#[class]
diff --git a/src/library/flow.rs b/src/library/layout/flow.rs
index 9e039d3a..f4b885b1 100644
--- a/src/library/flow.rs
+++ b/src/library/layout/flow.rs
@@ -1,7 +1,6 @@
-//! A flow of paragraphs and other block-level nodes.
-
-use super::prelude::*;
-use super::{AlignNode, ParNode, PlaceNode, SpacingKind, TextNode};
+use super::{AlignNode, PlaceNode, SpacingKind};
+use crate::library::prelude::*;
+use crate::library::text::{ParNode, TextNode};
/// Arrange spacing, paragraphs and other block-level nodes into a flow.
///
diff --git a/src/library/grid.rs b/src/library/layout/grid.rs
index 2d8cb462..63cd83b1 100644
--- a/src/library/grid.rs
+++ b/src/library/layout/grid.rs
@@ -1,6 +1,4 @@
-//! Layout along a row and column raster.
-
-use super::prelude::*;
+use crate::library::prelude::*;
/// Arrange nodes in a grid.
#[derive(Debug, Hash)]
diff --git a/src/library/hide.rs b/src/library/layout/hide.rs
index 89aea6d3..861a1208 100644
--- a/src/library/hide.rs
+++ b/src/library/layout/hide.rs
@@ -1,6 +1,4 @@
-//! Hiding of nodes without affecting layout.
-
-use super::prelude::*;
+use crate::library::prelude::*;
/// Hide a node without affecting layout.
#[derive(Debug, Hash)]
diff --git a/src/library/layout/mod.rs b/src/library/layout/mod.rs
new file mode 100644
index 00000000..944548ab
--- /dev/null
+++ b/src/library/layout/mod.rs
@@ -0,0 +1,27 @@
+//! Composable layouts.
+
+mod align;
+mod columns;
+mod container;
+mod flow;
+mod grid;
+mod hide;
+mod pad;
+mod page;
+mod place;
+mod spacing;
+mod stack;
+mod transform;
+
+pub use align::*;
+pub use columns::*;
+pub use container::*;
+pub use flow::*;
+pub use grid::*;
+pub use hide::*;
+pub use pad::*;
+pub use page::*;
+pub use place::*;
+pub use spacing::*;
+pub use stack::*;
+pub use transform::*;
diff --git a/src/library/pad.rs b/src/library/layout/pad.rs
index 174322e4..175a54f0 100644
--- a/src/library/pad.rs
+++ b/src/library/layout/pad.rs
@@ -1,6 +1,4 @@
-//! Surrounding nodes with extra space.
-
-use super::prelude::*;
+use crate::library::prelude::*;
/// Pad a node at the sides.
#[derive(Debug, Hash)]
diff --git a/src/library/page.rs b/src/library/layout/page.rs
index 5281c501..f5d766a5 100644
--- a/src/library/page.rs
+++ b/src/library/layout/page.rs
@@ -1,10 +1,8 @@
-//! Pages of paper.
-
use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
-use super::prelude::*;
use super::ColumnsNode;
+use crate::library::prelude::*;
/// Layouts its child onto one or multiple pages.
#[derive(Clone, PartialEq, Hash)]
diff --git a/src/library/place.rs b/src/library/layout/place.rs
index 00e72dfb..d65b3836 100644
--- a/src/library/place.rs
+++ b/src/library/layout/place.rs
@@ -1,7 +1,5 @@
-//! Absolute placement of nodes.
-
-use super::prelude::*;
use super::AlignNode;
+use crate::library::prelude::*;
/// Place a node at an absolute position.
#[derive(Debug, Hash)]
diff --git a/src/library/spacing.rs b/src/library/layout/spacing.rs
index 6f57c71c..3bebfb14 100644
--- a/src/library/spacing.rs
+++ b/src/library/layout/spacing.rs
@@ -1,6 +1,4 @@
-//! Horizontal and vertical spacing between nodes.
-
-use super::prelude::*;
+use crate::library::prelude::*;
/// Horizontal spacing.
pub struct HNode;
diff --git a/src/library/stack.rs b/src/library/layout/stack.rs
index 8e463f6a..414490ef 100644
--- a/src/library/stack.rs
+++ b/src/library/layout/stack.rs
@@ -1,7 +1,5 @@
-//! Side-by-side layout of nodes along an axis.
-
-use super::prelude::*;
use super::{AlignNode, SpacingKind};
+use crate::library::prelude::*;
/// Arrange nodes and spacing along an axis.
#[derive(Debug, Hash)]
diff --git a/src/library/transform.rs b/src/library/layout/transform.rs
index 897f3144..fafb37a4 100644
--- a/src/library/transform.rs
+++ b/src/library/layout/transform.rs
@@ -1,7 +1,5 @@
-//! Affine transformations on nodes.
-
-use super::prelude::*;
use crate::geom::Transform;
+use crate::library::prelude::*;
/// Transform a node without affecting layout.
#[derive(Debug, Hash)]
@@ -12,6 +10,15 @@ pub struct TransformNode<const T: TransformKind> {
pub child: LayoutNode,
}
+/// Transform a node by translating it without affecting layout.
+pub type MoveNode = TransformNode<MOVE>;
+
+/// Transform a node by rotating it without affecting layout.
+pub type RotateNode = TransformNode<ROTATE>;
+
+/// Transform a node by scaling it without affecting layout.
+pub type ScaleNode = TransformNode<SCALE>;
+
#[class]
impl<const T: TransformKind> TransformNode<T> {
/// The origin of the transformation.
@@ -70,10 +77,10 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
pub type TransformKind = usize;
/// A translation on the X and Y axes.
-pub const MOVE: TransformKind = 0;
+const MOVE: TransformKind = 0;
/// A rotational transformation.
-pub const ROTATE: TransformKind = 1;
+const ROTATE: TransformKind = 1;
/// A scale transformation.
-pub const SCALE: TransformKind = 2;
+const SCALE: TransformKind = 2;
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 590dd331..4a80ef43 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -3,79 +3,11 @@
//! Call [`new`] to obtain a [`Scope`] containing all standard library
//! definitions.
-pub mod align;
-pub mod columns;
-pub mod container;
-pub mod deco;
-pub mod flow;
-pub mod grid;
-pub mod heading;
-pub mod hide;
-pub mod image;
-pub mod link;
-pub mod list;
-pub mod math;
-pub mod numbering;
-pub mod pad;
-pub mod page;
-pub mod par;
-pub mod place;
-pub mod raw;
-pub mod shape;
-pub mod spacing;
-pub mod stack;
-pub mod table;
+pub mod elements;
+pub mod layout;
+pub mod prelude;
pub mod text;
-pub mod transform;
-
pub mod utility;
-pub use self::image::*;
-pub use align::*;
-pub use columns::*;
-pub use container::*;
-pub use deco::*;
-pub use flow::*;
-pub use grid::*;
-pub use heading::*;
-pub use hide::*;
-pub use link::*;
-pub use list::*;
-pub use math::*;
-pub use numbering::*;
-pub use pad::*;
-pub use page::*;
-pub use par::*;
-pub use place::*;
-pub use raw::*;
-pub use shape::*;
-pub use spacing::*;
-pub use stack::*;
-pub use table::*;
-pub use text::*;
-pub use transform::*;
-pub use utility::*;
-
-/// Helpful imports for creating library functionality.
-pub mod prelude {
- pub use std::fmt::{self, Debug, Formatter};
- pub use std::hash::Hash;
- pub use std::num::NonZeroUsize;
- pub use std::sync::Arc;
-
- pub use typst_macros::class;
-
- pub use crate::diag::{with_alternative, At, StrResult, TypResult};
- pub use crate::eval::{
- Arg, Args, Cast, Construct, Func, Layout, LayoutNode, Merge, Property, Regions,
- Scope, Set, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Template,
- Value,
- };
- pub use crate::frame::*;
- pub use crate::geom::*;
- pub use crate::syntax::{Span, Spanned};
- pub use crate::util::{EcoString, OptionExt};
- pub use crate::Context;
-}
use prelude::*;
@@ -83,72 +15,74 @@ use prelude::*;
pub fn new() -> Scope {
let mut std = Scope::new();
- // Structure and semantics.
- std.def_class::<PageNode>("page");
- std.def_class::<PagebreakNode>("pagebreak");
- std.def_class::<ParNode>("par");
- std.def_class::<ParbreakNode>("parbreak");
- std.def_class::<LinebreakNode>("linebreak");
- std.def_class::<TextNode>("text");
- std.def_class::<StrongNode>("strong");
- std.def_class::<EmphNode>("emph");
- std.def_class::<RawNode>("raw");
- std.def_class::<MathNode>("math");
- std.def_class::<DecoNode<UNDERLINE>>("underline");
- std.def_class::<DecoNode<STRIKETHROUGH>>("strike");
- std.def_class::<DecoNode<OVERLINE>>("overline");
- std.def_class::<LinkNode>("link");
- std.def_class::<HeadingNode>("heading");
- std.def_class::<ListNode<UNORDERED>>("list");
- std.def_class::<ListNode<ORDERED>>("enum");
- std.def_class::<TableNode>("table");
- std.def_class::<ImageNode>("image");
- std.def_class::<ShapeNode<RECT>>("rect");
- std.def_class::<ShapeNode<SQUARE>>("square");
- std.def_class::<ShapeNode<ELLIPSE>>("ellipse");
- std.def_class::<ShapeNode<CIRCLE>>("circle");
+ // Text.
+ std.def_class::<text::TextNode>("text");
+ std.def_class::<text::ParNode>("par");
+ std.def_class::<text::ParbreakNode>("parbreak");
+ std.def_class::<text::LinebreakNode>("linebreak");
+ std.def_class::<text::StrongNode>("strong");
+ std.def_class::<text::EmphNode>("emph");
+ std.def_class::<text::RawNode>("raw");
+ std.def_class::<text::UnderlineNode>("underline");
+ std.def_class::<text::StrikethroughNode>("strike");
+ std.def_class::<text::OverlineNode>("overline");
+ std.def_class::<text::LinkNode>("link");
+
+ // Elements.
+ std.def_class::<elements::MathNode>("math");
+ std.def_class::<elements::HeadingNode>("heading");
+ std.def_class::<elements::ListNode>("list");
+ std.def_class::<elements::EnumNode>("enum");
+ std.def_class::<elements::TableNode>("table");
+ std.def_class::<elements::ImageNode>("image");
+ std.def_class::<elements::RectNode>("rect");
+ std.def_class::<elements::SquareNode>("square");
+ std.def_class::<elements::EllipseNode>("ellipse");
+ std.def_class::<elements::CircleNode>("circle");
// Layout.
- std.def_class::<HNode>("h");
- std.def_class::<VNode>("v");
- std.def_class::<BoxNode>("box");
- std.def_class::<BlockNode>("block");
- std.def_class::<AlignNode>("align");
- std.def_class::<PadNode>("pad");
- std.def_class::<PlaceNode>("place");
- std.def_class::<TransformNode<MOVE>>("move");
- std.def_class::<TransformNode<SCALE>>("scale");
- std.def_class::<TransformNode<ROTATE>>("rotate");
- std.def_class::<HideNode>("hide");
- std.def_class::<StackNode>("stack");
- std.def_class::<GridNode>("grid");
- std.def_class::<ColumnsNode>("columns");
- std.def_class::<ColbreakNode>("colbreak");
+ std.def_class::<layout::PageNode>("page");
+ std.def_class::<layout::PagebreakNode>("pagebreak");
+ std.def_class::<layout::HNode>("h");
+ std.def_class::<layout::VNode>("v");
+ std.def_class::<layout::BoxNode>("box");
+ std.def_class::<layout::BlockNode>("block");
+ std.def_class::<layout::AlignNode>("align");
+ std.def_class::<layout::PadNode>("pad");
+ std.def_class::<layout::StackNode>("stack");
+ std.def_class::<layout::GridNode>("grid");
+ std.def_class::<layout::ColumnsNode>("columns");
+ std.def_class::<layout::ColbreakNode>("colbreak");
+ std.def_class::<layout::PlaceNode>("place");
+ std.def_class::<layout::MoveNode>("move");
+ std.def_class::<layout::ScaleNode>("scale");
+ std.def_class::<layout::RotateNode>("rotate");
+ std.def_class::<layout::HideNode>("hide");
// Utility functions.
- std.def_func("assert", assert);
- std.def_func("type", type_);
- std.def_func("repr", repr);
- std.def_func("join", join);
- std.def_func("int", int);
- std.def_func("float", float);
- std.def_func("str", str);
- std.def_func("abs", abs);
- std.def_func("min", min);
- std.def_func("max", max);
- std.def_func("even", even);
- std.def_func("odd", odd);
- std.def_func("mod", modulo);
- std.def_func("range", range);
- std.def_func("rgb", rgb);
- std.def_func("cmyk", cmyk);
- std.def_func("lower", lower);
- std.def_func("upper", upper);
- std.def_func("letter", letter);
- std.def_func("roman", roman);
- std.def_func("symbol", symbol);
- std.def_func("len", len);
- std.def_func("sorted", sorted);
+ std.def_func("assert", utility::assert);
+ std.def_func("type", utility::type_);
+ std.def_func("repr", utility::repr);
+ std.def_func("join", utility::join);
+ std.def_func("int", utility::int);
+ std.def_func("float", utility::float);
+ std.def_func("str", utility::str);
+ std.def_func("abs", utility::abs);
+ std.def_func("min", utility::min);
+ std.def_func("max", utility::max);
+ std.def_func("even", utility::even);
+ std.def_func("odd", utility::odd);
+ std.def_func("mod", utility::modulo);
+ std.def_func("range", utility::range);
+ std.def_func("rgb", utility::rgb);
+ std.def_func("cmyk", utility::cmyk);
+ std.def_func("lower", utility::lower);
+ std.def_func("upper", utility::upper);
+ std.def_func("letter", utility::letter);
+ std.def_func("roman", utility::roman);
+ std.def_func("symbol", utility::symbol);
+ std.def_func("len", utility::len);
+ std.def_func("sorted", utility::sorted);
// Predefined colors.
std.def_const("black", Color::BLACK);
@@ -181,9 +115,9 @@ pub fn new() -> Scope {
std.def_const("top", Align::Top);
std.def_const("horizon", Align::Horizon);
std.def_const("bottom", Align::Bottom);
- std.def_const("serif", FontFamily::Serif);
- std.def_const("sans-serif", FontFamily::SansSerif);
- std.def_const("monospace", FontFamily::Monospace);
+ std.def_const("serif", text::FontFamily::Serif);
+ std.def_const("sans-serif", text::FontFamily::SansSerif);
+ std.def_const("monospace", text::FontFamily::Monospace);
std
}
diff --git a/src/library/prelude.rs b/src/library/prelude.rs
new file mode 100644
index 00000000..4d13a655
--- /dev/null
+++ b/src/library/prelude.rs
@@ -0,0 +1,20 @@
+//! Helpful imports for creating library functionality.
+
+pub use std::fmt::{self, Debug, Formatter};
+pub use std::hash::Hash;
+pub use std::num::NonZeroUsize;
+pub use std::sync::Arc;
+
+pub use typst_macros::class;
+
+pub use crate::diag::{with_alternative, At, StrResult, TypResult};
+pub use crate::eval::{
+ Arg, Args, Array, Cast, Construct, Dict, Func, Layout, LayoutNode, Merge, Property,
+ Regions, Scope, Set, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Template,
+ Value,
+};
+pub use crate::frame::*;
+pub use crate::geom::*;
+pub use crate::syntax::{Span, Spanned};
+pub use crate::util::{EcoString, OptionExt};
+pub use crate::Context;
diff --git a/src/library/text/deco.rs b/src/library/text/deco.rs
new file mode 100644
index 00000000..a288c995
--- /dev/null
+++ b/src/library/text/deco.rs
@@ -0,0 +1,250 @@
+use kurbo::{BezPath, Line, ParamCurve};
+use ttf_parser::{GlyphId, OutlineBuilder};
+
+use super::TextNode;
+use crate::font::FontStore;
+use crate::library::prelude::*;
+
+/// Typeset underline, stricken-through or overlined text.
+#[derive(Debug, Hash)]
+pub struct DecoNode<const L: DecoLine>(pub Template);
+
+/// Typeset underlined text.
+pub type UnderlineNode = DecoNode<UNDERLINE>;
+
+/// Typeset stricken-through text.
+pub type StrikethroughNode = DecoNode<STRIKETHROUGH>;
+
+/// Typeset overlined text.
+pub type OverlineNode = DecoNode<OVERLINE>;
+
+#[class]
+impl<const L: DecoLine> DecoNode<L> {
+ /// Stroke color of the line, defaults to the text color if `None`.
+ #[shorthand]
+ pub const STROKE: Option<Paint> = None;
+ /// Thickness of the line's strokes (dependent on scaled font size), read
+ /// from the font tables if `None`.
+ #[shorthand]
+ pub const THICKNESS: Option<Linear> = None;
+ /// Position of the line relative to the baseline (dependent on scaled font
+ /// size), read from the font tables if `None`.
+ pub const OFFSET: Option<Linear> = None;
+ /// Amount that the line will be longer or shorter than its associated text
+ /// (dependent on scaled font size).
+ pub const EXTENT: Linear = Linear::zero();
+ /// Whether the line skips sections in which it would collide
+ /// with the glyphs. Does not apply to strikethrough.
+ pub const EVADE: bool = true;
+
+ fn construct(_: &mut Context, args: &mut Args) -> TypResult<Template> {
+ Ok(Template::show(Self(args.expect::<Template>("body")?)))
+ }
+}
+
+impl<const L: DecoLine> Show for DecoNode<L> {
+ fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Template> {
+ Ok(styles
+ .show(self, ctx, [Value::Template(self.0.clone())])?
+ .unwrap_or_else(|| {
+ self.0.clone().styled(TextNode::LINES, vec![Decoration {
+ line: L,
+ stroke: styles.get(Self::STROKE),
+ thickness: styles.get(Self::THICKNESS),
+ offset: styles.get(Self::OFFSET),
+ extent: styles.get(Self::EXTENT),
+ evade: styles.get(Self::EVADE),
+ }])
+ }))
+ }
+}
+
+/// Defines a line that is positioned over, under or on top of text.
+///
+/// For more details, see [`DecoNode`].
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct Decoration {
+ pub line: DecoLine,
+ pub stroke: Option<Paint>,
+ pub thickness: Option<Linear>,
+ pub offset: Option<Linear>,
+ pub extent: Linear,
+ pub evade: bool,
+}
+
+/// A kind of decorative line.
+pub type DecoLine = usize;
+
+/// A line under text.
+pub const UNDERLINE: DecoLine = 0;
+
+/// A line through text.
+pub const STRIKETHROUGH: DecoLine = 1;
+
+/// A line over text.
+pub const OVERLINE: DecoLine = 2;
+
+/// Add line decorations to a single run of shaped text.
+pub fn decorate(
+ frame: &mut Frame,
+ deco: &Decoration,
+ fonts: &FontStore,
+ text: &Text,
+ pos: Point,
+ width: Length,
+) {
+ let face = fonts.get(text.face_id);
+ let metrics = match deco.line {
+ STRIKETHROUGH => face.strikethrough,
+ OVERLINE => face.overline,
+ UNDERLINE | _ => face.underline,
+ };
+
+ let evade = deco.evade && deco.line != STRIKETHROUGH;
+ let extent = deco.extent.resolve(text.size);
+ let offset = deco
+ .offset
+ .map(|s| s.resolve(text.size))
+ .unwrap_or(-metrics.position.resolve(text.size));
+
+ let stroke = Stroke {
+ paint: deco.stroke.unwrap_or(text.fill),
+ thickness: deco
+ .thickness
+ .map(|s| s.resolve(text.size))
+ .unwrap_or(metrics.thickness.resolve(text.size)),
+ };
+
+ let gap_padding = 0.08 * text.size;
+ let min_width = 0.162 * text.size;
+
+ let mut start = pos.x - extent;
+ let end = pos.x + (width + 2.0 * extent);
+
+ let mut push_segment = |from: Length, to: Length| {
+ let origin = Point::new(from, pos.y + offset);
+ let target = Point::new(to - from, Length::zero());
+
+ if target.x >= min_width || !evade {
+ let shape = Shape::stroked(Geometry::Line(target), stroke);
+ frame.push(origin, Element::Shape(shape));
+ }
+ };
+
+ if !evade {
+ push_segment(start, end);
+ return;
+ }
+
+ let line = Line::new(
+ kurbo::Point::new(pos.x.to_raw(), offset.to_raw()),
+ kurbo::Point::new((pos.x + width).to_raw(), offset.to_raw()),
+ );
+
+ let mut x = pos.x;
+ let mut intersections = vec![];
+
+ for glyph in text.glyphs.iter() {
+ let dx = glyph.x_offset.resolve(text.size) + x;
+ let mut builder = BezPathBuilder::new(face.units_per_em, text.size, dx.to_raw());
+
+ let bbox = face.ttf().outline_glyph(GlyphId(glyph.id), &mut builder);
+ let path = builder.finish();
+
+ x += glyph.x_advance.resolve(text.size);
+
+ // Only do the costly segments intersection test if the line
+ // intersects the bounding box.
+ if bbox.map_or(false, |bbox| {
+ let y_min = -face.to_em(bbox.y_max).resolve(text.size);
+ let y_max = -face.to_em(bbox.y_min).resolve(text.size);
+
+ offset >= y_min && offset <= y_max
+ }) {
+ // Find all intersections of segments with the line.
+ intersections.extend(
+ path.segments()
+ .flat_map(|seg| seg.intersect_line(line))
+ .map(|is| Length::raw(line.eval(is.line_t).x)),
+ );
+ }
+ }
+
+ // When emitting the decorative line segments, we move from left to
+ // right. The intersections are not necessarily in this order, yet.
+ intersections.sort();
+
+ for gap in intersections.chunks_exact(2) {
+ let l = gap[0] - gap_padding;
+ let r = gap[1] + gap_padding;
+
+ if start >= end {
+ break;
+ }
+
+ if start >= l {
+ start = r;
+ continue;
+ }
+
+ push_segment(start, l);
+ start = r;
+ }
+
+ if start < end {
+ push_segment(start, end);
+ }
+}
+
+/// Builds a kurbo [`BezPath`] for a glyph.
+struct BezPathBuilder {
+ path: BezPath,
+ units_per_em: f64,
+ font_size: Length,
+ x_offset: f64,
+}
+
+impl BezPathBuilder {
+ fn new(units_per_em: f64, font_size: Length, x_offset: f64) -> Self {
+ Self {
+ path: BezPath::new(),
+ units_per_em,
+ font_size,
+ x_offset,
+ }
+ }
+
+ fn finish(self) -> BezPath {
+ self.path
+ }
+
+ fn p(&self, x: f32, y: f32) -> kurbo::Point {
+ kurbo::Point::new(self.s(x) + self.x_offset, -self.s(y))
+ }
+
+ fn s(&self, v: f32) -> f64 {
+ Em::from_units(v, self.units_per_em).resolve(self.font_size).to_raw()
+ }
+}
+
+impl OutlineBuilder for BezPathBuilder {
+ fn move_to(&mut self, x: f32, y: f32) {
+ self.path.move_to(self.p(x, y));
+ }
+
+ fn line_to(&mut self, x: f32, y: f32) {
+ self.path.line_to(self.p(x, y));
+ }
+
+ fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
+ self.path.quad_to(self.p(x1, y1), self.p(x, y));
+ }
+
+ fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
+ self.path.curve_to(self.p(x1, y1), self.p(x2, y2), self.p(x, y));
+ }
+
+ fn close(&mut self) {
+ self.path.close_path();
+ }
+}
diff --git a/src/library/link.rs b/src/library/text/link.rs
index 4a153d46..29f41927 100644
--- a/src/library/link.rs
+++ b/src/library/text/link.rs
@@ -1,7 +1,5 @@
-//! Hyperlinking.
-
-use super::prelude::*;
use super::TextNode;
+use crate::library::prelude::*;
use crate::util::EcoString;
/// Link text and other elements to an URL.
diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs
new file mode 100644
index 00000000..0df59007
--- /dev/null
+++ b/src/library/text/mod.rs
@@ -0,0 +1,409 @@
+mod deco;
+mod link;
+mod par;
+mod raw;
+mod shaping;
+
+pub use deco::*;
+pub use link::*;
+pub use par::*;
+pub use raw::*;
+pub use shaping::*;
+
+use std::borrow::Cow;
+use std::ops::BitXor;
+
+use ttf_parser::Tag;
+
+use crate::font::{Face, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
+use crate::library::prelude::*;
+use crate::util::EcoString;
+
+/// A single run of text with the same style.
+#[derive(Hash)]
+pub struct TextNode;
+
+#[class]
+impl TextNode {
+ /// A prioritized sequence of font families.
+ #[variadic]
+ pub const FAMILY: Vec<FontFamily> = vec![FontFamily::SansSerif];
+ /// The serif font family/families.
+ pub const SERIF: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Serif")];
+ /// The sans-serif font family/families.
+ pub const SANS_SERIF: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Sans")];
+ /// The monospace font family/families.
+ pub const MONOSPACE: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Mono")];
+ /// Whether to allow font fallback when the primary font list contains no
+ /// match.
+ pub const FALLBACK: bool = true;
+
+ /// How the font is styled.
+ pub const STYLE: FontStyle = FontStyle::Normal;
+ /// The boldness / thickness of the font's glyphs.
+ pub const WEIGHT: FontWeight = FontWeight::REGULAR;
+ /// The width of the glyphs.
+ pub const STRETCH: FontStretch = FontStretch::NORMAL;
+ /// The glyph fill color.
+ #[shorthand]
+ pub const FILL: Paint = Color::BLACK.into();
+
+ /// The size of the glyphs.
+ #[shorthand]
+ #[fold(Linear::compose)]
+ pub const SIZE: Linear = Length::pt(11.0).into();
+ /// The amount of space that should be added between characters.
+ pub const TRACKING: Em = Em::zero();
+ /// The top end of the text bounding box.
+ pub const TOP_EDGE: VerticalFontMetric = VerticalFontMetric::CapHeight;
+ /// The bottom end of the text bounding box.
+ pub const BOTTOM_EDGE: VerticalFontMetric = VerticalFontMetric::Baseline;
+
+ /// Whether to apply kerning ("kern").
+ pub const KERNING: bool = true;
+ /// Whether small capital glyphs should be used. ("smcp")
+ pub const SMALLCAPS: bool = false;
+ /// Whether to apply stylistic alternates. ("salt")
+ pub const ALTERNATES: bool = false;
+ /// Which stylistic set to apply. ("ss01" - "ss20")
+ pub const STYLISTIC_SET: Option<StylisticSet> = None;
+ /// Whether standard ligatures are active. ("liga", "clig")
+ pub const LIGATURES: bool = true;
+ /// Whether ligatures that should be used sparingly are active. ("dlig")
+ pub const DISCRETIONARY_LIGATURES: bool = false;
+ /// Whether historical ligatures are active. ("hlig")
+ pub const HISTORICAL_LIGATURES: bool = false;
+ /// Which kind of numbers / figures to select.
+ pub const NUMBER_TYPE: Smart<NumberType> = Smart::Auto;
+ /// The width of numbers / figures.
+ pub const NUMBER_WIDTH: Smart<NumberWidth> = Smart::Auto;
+ /// How to position numbers.
+ pub const NUMBER_POSITION: NumberPosition = NumberPosition::Normal;
+ /// Whether to have a slash through the zero glyph. ("zero")
+ pub const SLASHED_ZERO: bool = false;
+ /// Whether to convert fractions. ("frac")
+ pub const FRACTIONS: bool = false;
+ /// Raw OpenType features to apply.
+ pub const FEATURES: Vec<(Tag, u32)> = vec![];
+
+ /// Whether the font weight should be increased by 300.
+ #[skip]
+ #[fold(bool::bitxor)]
+ pub const STRONG: bool = false;
+ /// Whether the the font style should be inverted.
+ #[skip]
+ #[fold(bool::bitxor)]
+ pub const EMPH: bool = false;
+ /// Whether a monospace font should be preferred.
+ #[skip]
+ pub const MONOSPACED: bool = false;
+ /// The case transformation that should be applied to the next.
+ #[skip]
+ pub const CASE: Option<Case> = None;
+ /// Decorative lines.
+ #[skip]
+ #[fold(|a, b| a.into_iter().chain(b).collect())]
+ pub const LINES: Vec<Decoration> = vec![];
+ /// An URL the text should link to.
+ #[skip]
+ pub const LINK: Option<EcoString> = None;
+
+ fn construct(_: &mut Context, args: &mut Args) -> TypResult<Template> {
+ // The text constructor is special: It doesn't create a text node.
+ // Instead, it leaves the passed argument structurally unchanged, but
+ // styles all text in it.
+ args.expect("body")
+ }
+}
+
+/// Strong text, rendered in boldface.
+#[derive(Debug, Hash)]
+pub struct StrongNode(pub Template);
+
+#[class]
+impl StrongNode {
+ fn construct(_: &mut Context, args: &mut Args) -> TypResult<Template> {
+ Ok(Template::show(Self(args.expect("body")?)))
+ }
+}
+
+impl Show for StrongNode {
+ fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Template> {
+ Ok(styles
+ .show(self, ctx, [Value::Template(self.0.clone())])?
+ .unwrap_or_else(|| self.0.clone().styled(TextNode::STRONG, true)))
+ }
+}
+
+/// Emphasized text, rendered with an italic face.
+#[derive(Debug, Hash)]
+pub struct EmphNode(pub Template);
+
+#[class]
+impl EmphNode {
+ fn construct(_: &mut Context, args: &mut Args) -> TypResult<Template> {
+ Ok(Template::show(Self(args.expect("body")?)))
+ }
+}
+
+impl Show for EmphNode {
+ fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Template> {
+ Ok(styles
+ .show(self, ctx, [Value::Template(self.0.clone())])?
+ .unwrap_or_else(|| self.0.clone().styled(TextNode::EMPH, true)))
+ }
+}
+
+/// A generic or named font family.
+#[derive(Clone, Eq, PartialEq, Hash)]
+pub enum FontFamily {
+ /// A family that has "serifs", small strokes attached to letters.
+ Serif,
+ /// A family in which glyphs do not have "serifs", small attached strokes.
+ SansSerif,
+ /// A family in which (almost) all glyphs are of equal width.
+ Monospace,
+ /// A specific font family like "Arial".
+ Named(NamedFamily),
+}
+
+impl Debug for FontFamily {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ match self {
+ Self::Serif => f.pad("serif"),
+ Self::SansSerif => f.pad("sans-serif"),
+ Self::Monospace => f.pad("monospace"),
+ Self::Named(s) => s.fmt(f),
+ }
+ }
+}
+
+dynamic! {
+ FontFamily: "font family",
+ Value::Str(string) => Self::Named(NamedFamily::new(&string)),
+}
+
+castable! {
+ Vec<FontFamily>,
+ Expected: "string, generic family or array thereof",
+ Value::Str(string) => vec![FontFamily::Named(NamedFamily::new(&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(EcoString);
+
+impl NamedFamily {
+ /// Create a named font family variant.
+ pub fn new(string: &str) -> Self {
+ Self(string.to_lowercase().into())
+ }
+
+ /// The lowercased family name.
+ pub fn as_str(&self) -> &str {
+ &self.0
+ }
+}
+
+impl Debug for NamedFamily {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+castable! {
+ Vec<NamedFamily>,
+ Expected: "string or array of strings",
+ Value::Str(string) => vec![NamedFamily::new(&string)],
+ Value::Array(values) => values
+ .into_iter()
+ .filter_map(|v| v.cast().ok())
+ .map(|string: EcoString| NamedFamily::new(&string))
+ .collect(),
+}
+
+castable! {
+ FontStyle,
+ Expected: "string",
+ Value::Str(string) => match string.as_str() {
+ "normal" => Self::Normal,
+ "italic" => Self::Italic,
+ "oblique" => Self::Oblique,
+ _ => Err(r#"expected "normal", "italic" or "oblique""#)?,
+ },
+}
+
+castable! {
+ FontWeight,
+ Expected: "integer or string",
+ Value::Int(v) => Value::Int(v)
+ .cast::<usize>()?
+ .try_into()
+ .map_or(Self::BLACK, Self::from_number),
+ Value::Str(string) => match string.as_str() {
+ "thin" => Self::THIN,
+ "extralight" => Self::EXTRALIGHT,
+ "light" => Self::LIGHT,
+ "regular" => Self::REGULAR,
+ "medium" => Self::MEDIUM,
+ "semibold" => Self::SEMIBOLD,
+ "bold" => Self::BOLD,
+ "extrabold" => Self::EXTRABOLD,
+ "black" => Self::BLACK,
+ _ => Err("unknown font weight")?,
+ },
+}
+
+castable! {
+ FontStretch,
+ Expected: "relative",
+ Value::Relative(v) => Self::from_ratio(v.get() as f32),
+}
+
+castable! {
+ Em,
+ Expected: "float",
+ Value::Float(v) => Self::new(v),
+}
+
+castable! {
+ VerticalFontMetric,
+ Expected: "linear or string",
+ Value::Length(v) => Self::Linear(v.into()),
+ Value::Relative(v) => Self::Linear(v.into()),
+ Value::Linear(v) => Self::Linear(v),
+ Value::Str(string) => match string.as_str() {
+ "ascender" => Self::Ascender,
+ "cap-height" => Self::CapHeight,
+ "x-height" => Self::XHeight,
+ "baseline" => Self::Baseline,
+ "descender" => Self::Descender,
+ _ => Err("unknown font metric")?,
+ },
+}
+
+/// A stylistic set in a font face.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub struct StylisticSet(u8);
+
+impl StylisticSet {
+ /// Creates a new set, clamping to 1-20.
+ pub fn new(index: u8) -> Self {
+ Self(index.clamp(1, 20))
+ }
+
+ /// Get the value, guaranteed to be 1-20.
+ pub fn get(self) -> u8 {
+ self.0
+ }
+}
+
+castable! {
+ StylisticSet,
+ Expected: "integer",
+ Value::Int(v) => match v {
+ 1 ..= 20 => Self::new(v as u8),
+ _ => Err("must be between 1 and 20")?,
+ },
+}
+
+/// Which kind of numbers / figures to select.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum NumberType {
+ /// Numbers that fit well with capital text. ("lnum")
+ Lining,
+ /// Numbers that fit well into flow of upper- and lowercase text. ("onum")
+ OldStyle,
+}
+
+castable! {
+ NumberType,
+ Expected: "string",
+ Value::Str(string) => match string.as_str() {
+ "lining" => Self::Lining,
+ "old-style" => Self::OldStyle,
+ _ => Err(r#"expected "lining" or "old-style""#)?,
+ },
+}
+
+/// The width of numbers / figures.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum NumberWidth {
+ /// Number widths are glyph specific. ("pnum")
+ Proportional,
+ /// All numbers are of equal width / monospaced. ("tnum")
+ Tabular,
+}
+
+castable! {
+ NumberWidth,
+ Expected: "string",
+ Value::Str(string) => match string.as_str() {
+ "proportional" => Self::Proportional,
+ "tabular" => Self::Tabular,
+ _ => Err(r#"expected "proportional" or "tabular""#)?,
+ },
+}
+
+/// How to position numbers.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum NumberPosition {
+ /// Numbers are positioned on the same baseline as text.
+ Normal,
+ /// Numbers are smaller and placed at the bottom. ("subs")
+ Subscript,
+ /// Numbers are smaller and placed at the top. ("sups")
+ Superscript,
+}
+
+castable! {
+ NumberPosition,
+ Expected: "string",
+ Value::Str(string) => match string.as_str() {
+ "normal" => Self::Normal,
+ "subscript" => Self::Subscript,
+ "superscript" => Self::Superscript,
+ _ => Err(r#"expected "normal", "subscript" or "superscript""#)?,
+ },
+}
+
+castable! {
+ Vec<(Tag, u32)>,
+ Expected: "array of strings or dictionary mapping tags to integers",
+ Value::Array(values) => values
+ .into_iter()
+ .filter_map(|v| v.cast().ok())
+ .map(|string: EcoString| (Tag::from_bytes_lossy(string.as_bytes()), 1))
+ .collect(),
+ Value::Dict(values) => values
+ .into_iter()
+ .filter_map(|(k, v)| {
+ let tag = Tag::from_bytes_lossy(k.as_bytes());
+ let num = v.cast::<i64>().ok()?.try_into().ok()?;
+ Some((tag, num))
+ })
+ .collect(),
+}
+
+/// A case transformation on text.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum Case {
+ /// Everything is uppercased.
+ Upper,
+ /// Everything is lowercased.
+ Lower,
+}
+
+impl Case {
+ /// Apply the case to a string of text.
+ pub fn apply(self, text: &str) -> String {
+ match self {
+ Self::Upper => text.to_uppercase(),
+ Self::Lower => text.to_lowercase(),
+ }
+ }
+}
diff --git a/src/library/par.rs b/src/library/text/par.rs
index 6d5a0597..812231c2 100644
--- a/src/library/par.rs
+++ b/src/library/text/par.rs
@@ -1,14 +1,13 @@
-//! Paragraph layout.
-
use std::sync::Arc;
use either::Either;
use unicode_bidi::{BidiInfo, Level};
use xi_unicode::LineBreakIterator;
-use super::prelude::*;
-use super::{shape, ShapedText, SpacingKind, TextNode};
+use super::{shape, ShapedText, TextNode};
use crate::font::FontStore;
+use crate::library::layout::SpacingKind;
+use crate::library::prelude::*;
use crate::util::{ArcExt, EcoString, RangeExt, SliceExt};
/// Arrange text, spacing and inline-level nodes into a paragraph.
@@ -127,118 +126,24 @@ impl Layout for ParNode {
) -> TypResult<Vec<Arc<Frame>>> {
// Collect all text into one string and perform BiDi analysis.
let text = self.collect_text();
- let level = Level::from_dir(styles.get(Self::DIR));
- let bidi = BidiInfo::new(&text, level);
+ let bidi = BidiInfo::new(&text, match styles.get(Self::DIR) {
+ Dir::LTR => Some(Level::ltr()),
+ Dir::RTL => Some(Level::rtl()),
+ _ => None,
+ });
// Prepare paragraph layout by building a representation on which we can
// do line breaking without layouting each and every line from scratch.
let par = ParLayout::new(ctx, self, bidi, regions, &styles)?;
// Break the paragraph into lines.
- let lines = break_lines(&mut ctx.fonts, &par, regions.first.x);
+ let lines = break_into_lines(&mut ctx.fonts, &par, regions.first.x);
// Stack the lines into one frame per region.
Ok(stack_lines(&ctx.fonts, lines, regions, styles))
}
}
-/// Perform line breaking.
-fn break_lines<'a>(
- fonts: &mut FontStore,
- par: &'a ParLayout<'a>,
- width: Length,
-) -> Vec<LineLayout<'a>> {
- // The already determined lines and the current line attempt.
- let mut lines = vec![];
- let mut start = 0;
- let mut last = None;
-
- // Find suitable line breaks.
- for (end, mandatory) in LineBreakIterator::new(&par.bidi.text) {
- // Compute the line and its size.
- let mut line = par.line(fonts, start .. end, mandatory);
-
- // If the line doesn't fit anymore, we push the last fitting attempt
- // into the stack and rebuild the line from its end. The resulting
- // line cannot be broken up further.
- if !width.fits(line.size.x) {
- if let Some((last_line, last_end)) = last.take() {
- lines.push(last_line);
- start = last_end;
- line = par.line(fonts, start .. end, mandatory);
- }
- }
-
- // Finish the current line if there is a mandatory line break (i.e.
- // due to "\n") or if the line doesn't fit horizontally already
- // since then no shorter line will be possible.
- if mandatory || !width.fits(line.size.x) {
- lines.push(line);
- start = end;
- last = None;
- } else {
- last = Some((line, end));
- }
- }
-
- if let Some((line, _)) = last {
- lines.push(line);
- }
-
- lines
-}
-
-/// Combine the lines into one frame per region.
-fn stack_lines(
- fonts: &FontStore,
- lines: Vec<LineLayout>,
- regions: &Regions,
- styles: StyleChain,
-) -> Vec<Arc<Frame>> {
- let em = styles.get(TextNode::SIZE).abs;
- let leading = styles.get(ParNode::LEADING).resolve(em);
- let align = styles.get(ParNode::ALIGN);
- let justify = styles.get(ParNode::JUSTIFY);
-
- // Determine the paragraph's width: Full width of the region if we
- // should expand or there's fractional spacing, fit-to-width otherwise.
- let mut width = regions.first.x;
- if !regions.expand.x && lines.iter().all(|line| line.fr.is_zero()) {
- width = lines.iter().map(|line| line.size.x).max().unwrap_or_default();
- }
-
- // State for final frame building.
- let mut regions = regions.clone();
- let mut finished = vec![];
- let mut first = true;
- let mut output = Frame::new(Size::with_x(width));
-
- // Stack the lines into one frame per region.
- for line in lines {
- while !regions.first.y.fits(line.size.y) && !regions.in_last() {
- finished.push(Arc::new(output));
- output = Frame::new(Size::with_x(width));
- regions.next();
- first = true;
- }
-
- if !first {
- output.size.y += leading;
- }
-
- let frame = line.build(fonts, width, align, justify);
- let pos = Point::with_y(output.size.y);
- output.size.y += frame.size.y;
- output.merge_frame(pos, frame);
-
- regions.first.y -= line.size.y + leading;
- first = false;
- }
-
- finished.push(Arc::new(output));
- finished
-}
-
impl Debug for ParNode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("Par ")?;
@@ -337,7 +242,8 @@ impl<'a> ParLayout<'a> {
cursor += count;
let subrange = start .. cursor;
let text = &bidi.text[subrange.clone()];
- let shaped = shape(&mut ctx.fonts, text, styles, level.dir());
+ let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL };
+ let shaped = shape(&mut ctx.fonts, text, styles, dir);
items.push(ParItem::Text(shaped));
ranges.push(subrange);
}
@@ -613,22 +519,99 @@ impl<'a> LineLayout<'a> {
}
}
-/// Additional methods for BiDi levels.
-trait LevelExt: Sized {
- fn from_dir(dir: Dir) -> Option<Self>;
- fn dir(self) -> Dir;
-}
+/// Perform line breaking.
+fn break_into_lines<'a>(
+ fonts: &mut FontStore,
+ par: &'a ParLayout<'a>,
+ width: Length,
+) -> Vec<LineLayout<'a>> {
+ // The already determined lines and the current line attempt.
+ let mut lines = vec![];
+ let mut start = 0;
+ let mut last = None;
-impl LevelExt for Level {
- fn from_dir(dir: Dir) -> Option<Self> {
- match dir {
- Dir::LTR => Some(Level::ltr()),
- Dir::RTL => Some(Level::rtl()),
- _ => None,
+ // Find suitable line breaks.
+ for (end, mandatory) in LineBreakIterator::new(&par.bidi.text) {
+ // Compute the line and its size.
+ let mut line = par.line(fonts, start .. end, mandatory);
+
+ // If the line doesn't fit anymore, we push the last fitting attempt
+ // into the stack and rebuild the line from its end. The resulting
+ // line cannot be broken up further.
+ if !width.fits(line.size.x) {
+ if let Some((last_line, last_end)) = last.take() {
+ lines.push(last_line);
+ start = last_end;
+ line = par.line(fonts, start .. end, mandatory);
+ }
+ }
+
+ // Finish the current line if there is a mandatory line break (i.e.
+ // due to "\n") or if the line doesn't fit horizontally already
+ // since then no shorter line will be possible.
+ if mandatory || !width.fits(line.size.x) {
+ lines.push(line);
+ start = end;
+ last = None;
+ } else {
+ last = Some((line, end));
}
}
- fn dir(self) -> Dir {
- if self.is_ltr() { Dir::LTR } else { Dir::RTL }
+ if let Some((line, _)) = last {
+ lines.push(line);
+ }
+
+ lines
+}
+
+/// Combine the lines into one frame per region.
+fn stack_lines(
+ fonts: &FontStore,
+ lines: Vec<LineLayout>,
+ regions: &Regions,
+ styles: StyleChain,
+) -> Vec<Arc<Frame>> {
+ let em = styles.get(TextNode::SIZE).abs;
+ let leading = styles.get(ParNode::LEADING).resolve(em);
+ let align = styles.get(ParNode::ALIGN);
+ let justify = styles.get(ParNode::JUSTIFY);
+
+ // Determine the paragraph's width: Full width of the region if we
+ // should expand or there's fractional spacing, fit-to-width otherwise.
+ let mut width = regions.first.x;
+ if !regions.expand.x && lines.iter().all(|line| line.fr.is_zero()) {
+ width = lines.iter().map(|line| line.size.x).max().unwrap_or_default();
+ }
+
+ // State for final frame building.
+ let mut regions = regions.clone();
+ let mut finished = vec![];
+ let mut first = true;
+ let mut output = Frame::new(Size::with_x(width));
+
+ // Stack the lines into one frame per region.
+ for line in lines {
+ while !regions.first.y.fits(line.size.y) && !regions.in_last() {
+ finished.push(Arc::new(output));
+ output = Frame::new(Size::with_x(width));
+ regions.next();
+ first = true;
+ }
+
+ if !first {
+ output.size.y += leading;
+ }
+
+ let frame = line.build(fonts, width, align, justify);
+ let pos = Point::with_y(output.size.y);
+ output.size.y += frame.size.y;
+ output.merge_frame(pos, frame);
+
+ regions.first.y -= line.size.y + leading;
+ first = false;
}
+
+ finished.push(Arc::new(output));
+ finished
}
diff --git a/src/library/raw.rs b/src/library/text/raw.rs
index 785eeac5..97857f11 100644
--- a/src/library/raw.rs
+++ b/src/library/text/raw.rs
@@ -1,12 +1,10 @@
-//! Monospaced text and code.
-
use once_cell::sync::Lazy;
use syntect::easy::HighlightLines;
use syntect::highlighting::{FontStyle, Highlighter, Style, Theme, ThemeSet};
use syntect::parsing::SyntaxSet;
-use super::prelude::*;
-use crate::library::TextNode;
+use crate::library::prelude::*;
+use crate::library::text::TextNode;
use crate::source::SourceId;
use crate::syntax::{self, RedNode};
diff --git a/src/library/text.rs b/src/library/text/shaping.rs
index b76b60ee..26c8daf3 100644
--- a/src/library/text.rs
+++ b/src/library/text/shaping.rs
@@ -1,410 +1,11 @@
-//! Text shaping and styling.
+use std::ops::Range;
-use std::borrow::Cow;
-use std::fmt::{self, Debug, Formatter};
-use std::ops::{BitXor, Range};
-
-use kurbo::{BezPath, Line, ParamCurve};
use rustybuzz::{Feature, UnicodeBuffer};
-use ttf_parser::{GlyphId, OutlineBuilder, Tag};
-
-use super::prelude::*;
-use super::Decoration;
-use crate::font::{
- Face, FaceId, FontStore, FontStretch, FontStyle, FontVariant, FontWeight,
- VerticalFontMetric,
-};
-use crate::geom::{Dir, Em, Length, Point, Size};
-use crate::util::{EcoString, SliceExt};
-
-/// A single run of text with the same style.
-#[derive(Hash)]
-pub struct TextNode;
-
-#[class]
-impl TextNode {
- /// A prioritized sequence of font families.
- #[variadic]
- pub const FAMILY: Vec<FontFamily> = vec![FontFamily::SansSerif];
- /// The serif font family/families.
- pub const SERIF: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Serif")];
- /// The sans-serif font family/families.
- pub const SANS_SERIF: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Sans")];
- /// The monospace font family/families.
- pub const MONOSPACE: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Mono")];
- /// Whether to allow font fallback when the primary font list contains no
- /// match.
- pub const FALLBACK: bool = true;
-
- /// How the font is styled.
- pub const STYLE: FontStyle = FontStyle::Normal;
- /// The boldness / thickness of the font's glyphs.
- pub const WEIGHT: FontWeight = FontWeight::REGULAR;
- /// The width of the glyphs.
- pub const STRETCH: FontStretch = FontStretch::NORMAL;
- /// The glyph fill color.
- #[shorthand]
- pub const FILL: Paint = Color::BLACK.into();
-
- /// The size of the glyphs.
- #[shorthand]
- #[fold(Linear::compose)]
- pub const SIZE: Linear = Length::pt(11.0).into();
- /// The amount of space that should be added between characters.
- pub const TRACKING: Em = Em::zero();
- /// The top end of the text bounding box.
- pub const TOP_EDGE: VerticalFontMetric = VerticalFontMetric::CapHeight;
- /// The bottom end of the text bounding box.
- pub const BOTTOM_EDGE: VerticalFontMetric = VerticalFontMetric::Baseline;
-
- /// Whether to apply kerning ("kern").
- pub const KERNING: bool = true;
- /// Whether small capital glyphs should be used. ("smcp")
- pub const SMALLCAPS: bool = false;
- /// Whether to apply stylistic alternates. ("salt")
- pub const ALTERNATES: bool = false;
- /// Which stylistic set to apply. ("ss01" - "ss20")
- pub const STYLISTIC_SET: Option<StylisticSet> = None;
- /// Whether standard ligatures are active. ("liga", "clig")
- pub const LIGATURES: bool = true;
- /// Whether ligatures that should be used sparingly are active. ("dlig")
- pub const DISCRETIONARY_LIGATURES: bool = false;
- /// Whether historical ligatures are active. ("hlig")
- pub const HISTORICAL_LIGATURES: bool = false;
- /// Which kind of numbers / figures to select.
- pub const NUMBER_TYPE: Smart<NumberType> = Smart::Auto;
- /// The width of numbers / figures.
- pub const NUMBER_WIDTH: Smart<NumberWidth> = Smart::Auto;
- /// How to position numbers.
- pub const NUMBER_POSITION: NumberPosition = NumberPosition::Normal;
- /// Whether to have a slash through the zero glyph. ("zero")
- pub const SLASHED_ZERO: bool = false;
- /// Whether to convert fractions. ("frac")
- pub const FRACTIONS: bool = false;
- /// Raw OpenType features to apply.
- pub const FEATURES: Vec<(Tag, u32)> = vec![];
-
- /// Whether the font weight should be increased by 300.
- #[skip]
- #[fold(bool::bitxor)]
- pub const STRONG: bool = false;
- /// Whether the the font style should be inverted.
- #[skip]
- #[fold(bool::bitxor)]
- pub const EMPH: bool = false;
- /// Whether a monospace font should be preferred.
- #[skip]
- pub const MONOSPACED: bool = false;
- /// The case transformation that should be applied to the next.
- #[skip]
- pub const CASE: Option<Case> = None;
- /// Decorative lines.
- #[skip]
- #[fold(|a, b| a.into_iter().chain(b).collect())]
- pub const LINES: Vec<Decoration> = vec![];
- /// An URL the text should link to.
- #[skip]
- pub const LINK: Option<EcoString> = None;
-
- fn construct(_: &mut Context, args: &mut Args) -> TypResult<Template> {
- // The text constructor is special: It doesn't create a text node.
- // Instead, it leaves the passed argument structurally unchanged, but
- // styles all text in it.
- args.expect("body")
- }
-}
-
-/// Strong text, rendered in boldface.
-#[derive(Debug, Hash)]
-pub struct StrongNode(pub Template);
-
-#[class]
-impl StrongNode {
- fn construct(_: &mut Context, args: &mut Args) -> TypResult<Template> {
- Ok(Template::show(Self(args.expect("body")?)))
- }
-}
-
-impl Show for StrongNode {
- fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Template> {
- Ok(styles
- .show(self, ctx, [Value::Template(self.0.clone())])?
- .unwrap_or_else(|| self.0.clone().styled(TextNode::STRONG, true)))
- }
-}
-
-/// Emphasized text, rendered with an italic face.
-#[derive(Debug, Hash)]
-pub struct EmphNode(pub Template);
-
-#[class]
-impl EmphNode {
- fn construct(_: &mut Context, args: &mut Args) -> TypResult<Template> {
- Ok(Template::show(Self(args.expect("body")?)))
- }
-}
-
-impl Show for EmphNode {
- fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Template> {
- Ok(styles
- .show(self, ctx, [Value::Template(self.0.clone())])?
- .unwrap_or_else(|| self.0.clone().styled(TextNode::EMPH, true)))
- }
-}
-
-/// A generic or named font family.
-#[derive(Clone, Eq, PartialEq, Hash)]
-pub enum FontFamily {
- /// A family that has "serifs", small strokes attached to letters.
- Serif,
- /// A family in which glyphs do not have "serifs", small attached strokes.
- SansSerif,
- /// A family in which (almost) all glyphs are of equal width.
- Monospace,
- /// A specific font family like "Arial".
- Named(NamedFamily),
-}
-
-impl Debug for FontFamily {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::Serif => f.pad("serif"),
- Self::SansSerif => f.pad("sans-serif"),
- Self::Monospace => f.pad("monospace"),
- Self::Named(s) => s.fmt(f),
- }
- }
-}
-
-dynamic! {
- FontFamily: "font family",
- Value::Str(string) => Self::Named(NamedFamily::new(&string)),
-}
-
-castable! {
- Vec<FontFamily>,
- Expected: "string, generic family or array thereof",
- Value::Str(string) => vec![FontFamily::Named(NamedFamily::new(&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(EcoString);
-
-impl NamedFamily {
- /// Create a named font family variant.
- pub fn new(string: &str) -> Self {
- Self(string.to_lowercase().into())
- }
-
- /// The lowercased family name.
- pub fn as_str(&self) -> &str {
- &self.0
- }
-}
-
-impl Debug for NamedFamily {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- self.0.fmt(f)
- }
-}
-
-castable! {
- Vec<NamedFamily>,
- Expected: "string or array of strings",
- Value::Str(string) => vec![NamedFamily::new(&string)],
- Value::Array(values) => values
- .into_iter()
- .filter_map(|v| v.cast().ok())
- .map(|string: EcoString| NamedFamily::new(&string))
- .collect(),
-}
-
-castable! {
- FontStyle,
- Expected: "string",
- Value::Str(string) => match string.as_str() {
- "normal" => Self::Normal,
- "italic" => Self::Italic,
- "oblique" => Self::Oblique,
- _ => Err(r#"expected "normal", "italic" or "oblique""#)?,
- },
-}
-
-castable! {
- FontWeight,
- Expected: "integer or string",
- Value::Int(v) => Value::Int(v)
- .cast::<usize>()?
- .try_into()
- .map_or(Self::BLACK, Self::from_number),
- Value::Str(string) => match string.as_str() {
- "thin" => Self::THIN,
- "extralight" => Self::EXTRALIGHT,
- "light" => Self::LIGHT,
- "regular" => Self::REGULAR,
- "medium" => Self::MEDIUM,
- "semibold" => Self::SEMIBOLD,
- "bold" => Self::BOLD,
- "extrabold" => Self::EXTRABOLD,
- "black" => Self::BLACK,
- _ => Err("unknown font weight")?,
- },
-}
-
-castable! {
- FontStretch,
- Expected: "relative",
- Value::Relative(v) => Self::from_ratio(v.get() as f32),
-}
-
-castable! {
- Em,
- Expected: "float",
- Value::Float(v) => Self::new(v),
-}
-
-castable! {
- VerticalFontMetric,
- Expected: "linear or string",
- Value::Length(v) => Self::Linear(v.into()),
- Value::Relative(v) => Self::Linear(v.into()),
- Value::Linear(v) => Self::Linear(v),
- Value::Str(string) => match string.as_str() {
- "ascender" => Self::Ascender,
- "cap-height" => Self::CapHeight,
- "x-height" => Self::XHeight,
- "baseline" => Self::Baseline,
- "descender" => Self::Descender,
- _ => Err("unknown font metric")?,
- },
-}
-
-/// A stylistic set in a font face.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct StylisticSet(u8);
-
-impl StylisticSet {
- /// Creates a new set, clamping to 1-20.
- pub fn new(index: u8) -> Self {
- Self(index.clamp(1, 20))
- }
-
- /// Get the value, guaranteed to be 1-20.
- pub fn get(self) -> u8 {
- self.0
- }
-}
-
-castable! {
- StylisticSet,
- Expected: "integer",
- Value::Int(v) => match v {
- 1 ..= 20 => Self::new(v as u8),
- _ => Err("must be between 1 and 20")?,
- },
-}
-
-/// Which kind of numbers / figures to select.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum NumberType {
- /// Numbers that fit well with capital text. ("lnum")
- Lining,
- /// Numbers that fit well into flow of upper- and lowercase text. ("onum")
- OldStyle,
-}
-
-castable! {
- NumberType,
- Expected: "string",
- Value::Str(string) => match string.as_str() {
- "lining" => Self::Lining,
- "old-style" => Self::OldStyle,
- _ => Err(r#"expected "lining" or "old-style""#)?,
- },
-}
-
-/// The width of numbers / figures.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum NumberWidth {
- /// Number widths are glyph specific. ("pnum")
- Proportional,
- /// All numbers are of equal width / monospaced. ("tnum")
- Tabular,
-}
-
-castable! {
- NumberWidth,
- Expected: "string",
- Value::Str(string) => match string.as_str() {
- "proportional" => Self::Proportional,
- "tabular" => Self::Tabular,
- _ => Err(r#"expected "proportional" or "tabular""#)?,
- },
-}
-
-/// How to position numbers.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum NumberPosition {
- /// Numbers are positioned on the same baseline as text.
- Normal,
- /// Numbers are smaller and placed at the bottom. ("subs")
- Subscript,
- /// Numbers are smaller and placed at the top. ("sups")
- Superscript,
-}
-
-castable! {
- NumberPosition,
- Expected: "string",
- Value::Str(string) => match string.as_str() {
- "normal" => Self::Normal,
- "subscript" => Self::Subscript,
- "superscript" => Self::Superscript,
- _ => Err(r#"expected "normal", "subscript" or "superscript""#)?,
- },
-}
-castable! {
- Vec<(Tag, u32)>,
- Expected: "array of strings or dictionary mapping tags to integers",
- Value::Array(values) => values
- .into_iter()
- .filter_map(|v| v.cast().ok())
- .map(|string: EcoString| (Tag::from_bytes_lossy(string.as_bytes()), 1))
- .collect(),
- Value::Dict(values) => values
- .into_iter()
- .filter_map(|(k, v)| {
- let tag = Tag::from_bytes_lossy(k.as_bytes());
- let num = v.cast::<i64>().ok()?.try_into().ok()?;
- Some((tag, num))
- })
- .collect(),
-}
-
-/// A case transformation on text.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum Case {
- /// Everything is uppercased.
- Upper,
- /// Everything is lowercased.
- Lower,
-}
-
-impl Case {
- /// Apply the case to a string of text.
- pub fn apply(self, text: &str) -> String {
- match self {
- Self::Upper => text.to_uppercase(),
- Self::Lower => text.to_lowercase(),
- }
- }
-}
+use super::*;
+use crate::font::{FaceId, FontStore, FontVariant};
+use crate::library::prelude::*;
+use crate::util::SliceExt;
/// The result of shaping text.
///
@@ -448,9 +49,11 @@ pub struct ShapedGlyph {
pub is_space: bool,
}
-/// A visual side.
+/// A side you can go toward.
enum Side {
+ /// Go toward the west.
Left,
+ /// Go toward the east.
Right,
}
@@ -947,168 +550,3 @@ fn measure(
(Size::new(width, top + bottom), top)
}
-
-/// Add line decorations to a single run of shaped text.
-fn decorate(
- frame: &mut Frame,
- deco: &Decoration,
- fonts: &FontStore,
- text: &Text,
- pos: Point,
- width: Length,
-) {
- let face = fonts.get(text.face_id);
- let metrics = match deco.line {
- super::STRIKETHROUGH => face.strikethrough,
- super::OVERLINE => face.overline,
- super::UNDERLINE | _ => face.underline,
- };
-
- let evade = deco.evade && deco.line != super::STRIKETHROUGH;
- let extent = deco.extent.resolve(text.size);
- let offset = deco
- .offset
- .map(|s| s.resolve(text.size))
- .unwrap_or(-metrics.position.resolve(text.size));
-
- let stroke = Stroke {
- paint: deco.stroke.unwrap_or(text.fill),
- thickness: deco
- .thickness
- .map(|s| s.resolve(text.size))
- .unwrap_or(metrics.thickness.resolve(text.size)),
- };
-
- let gap_padding = 0.08 * text.size;
- let min_width = 0.162 * text.size;
-
- let mut start = pos.x - extent;
- let end = pos.x + (width + 2.0 * extent);
-
- let mut push_segment = |from: Length, to: Length| {
- let origin = Point::new(from, pos.y + offset);
- let target = Point::new(to - from, Length::zero());
-
- if target.x >= min_width || !evade {
- let shape = Shape::stroked(Geometry::Line(target), stroke);
- frame.push(origin, Element::Shape(shape));
- }
- };
-
- if !evade {
- push_segment(start, end);
- return;
- }
-
- let line = Line::new(
- kurbo::Point::new(pos.x.to_raw(), offset.to_raw()),
- kurbo::Point::new((pos.x + width).to_raw(), offset.to_raw()),
- );
-
- let mut x = pos.x;
- let mut intersections = vec![];
-
- for glyph in text.glyphs.iter() {
- let dx = glyph.x_offset.resolve(text.size) + x;
- let mut builder = BezPathBuilder::new(face.units_per_em, text.size, dx.to_raw());
-
- let bbox = face.ttf().outline_glyph(GlyphId(glyph.id), &mut builder);
- let path = builder.finish();
-
- x += glyph.x_advance.resolve(text.size);
-
- // Only do the costly segments intersection test if the line
- // intersects the bounding box.
- if bbox.map_or(false, |bbox| {
- let y_min = -face.to_em(bbox.y_max).resolve(text.size);
- let y_max = -face.to_em(bbox.y_min).resolve(text.size);
-
- offset >= y_min && offset <= y_max
- }) {
- // Find all intersections of segments with the line.
- intersections.extend(
- path.segments()
- .flat_map(|seg| seg.intersect_line(line))
- .map(|is| Length::raw(line.eval(is.line_t).x)),
- );
- }
- }
-
- // When emitting the decorative line segments, we move from left to
- // right. The intersections are not necessarily in this order, yet.
- intersections.sort();
-
- for gap in intersections.chunks_exact(2) {
- let l = gap[0] - gap_padding;
- let r = gap[1] + gap_padding;
-
- if start >= end {
- break;
- }
-
- if start >= l {
- start = r;
- continue;
- }
-
- push_segment(start, l);
- start = r;
- }
-
- if start < end {
- push_segment(start, end);
- }
-}
-
-/// Builds a kurbo [`BezPath`] for a glyph.
-struct BezPathBuilder {
- path: BezPath,
- units_per_em: f64,
- font_size: Length,
- x_offset: f64,
-}
-
-impl BezPathBuilder {
- fn new(units_per_em: f64, font_size: Length, x_offset: f64) -> Self {
- Self {
- path: BezPath::new(),
- units_per_em,
- font_size,
- x_offset,
- }
- }
-
- fn finish(self) -> BezPath {
- self.path
- }
-
- fn p(&self, x: f32, y: f32) -> kurbo::Point {
- kurbo::Point::new(self.s(x) + self.x_offset, -self.s(y))
- }
-
- fn s(&self, v: f32) -> f64 {
- Em::from_units(v, self.units_per_em).resolve(self.font_size).to_raw()
- }
-}
-
-impl OutlineBuilder for BezPathBuilder {
- fn move_to(&mut self, x: f32, y: f32) {
- self.path.move_to(self.p(x, y));
- }
-
- fn line_to(&mut self, x: f32, y: f32) {
- self.path.line_to(self.p(x, y));
- }
-
- fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
- self.path.quad_to(self.p(x1, y1), self.p(x, y));
- }
-
- fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
- self.path.curve_to(self.p(x1, y1), self.p(x2, y2), self.p(x, y));
- }
-
- fn close(&mut self) {
- self.path.close_path();
- }
-}
diff --git a/src/library/utility/math.rs b/src/library/utility/math.rs
new file mode 100644
index 00000000..795f39fd
--- /dev/null
+++ b/src/library/utility/math.rs
@@ -0,0 +1,114 @@
+use std::cmp::Ordering;
+
+use crate::library::prelude::*;
+
+/// The absolute value of a numeric value.
+pub fn abs(_: &mut Context, args: &mut Args) -> TypResult<Value> {
+ let Spanned { v, span } = args.expect("numeric value")?;
+ Ok(match v {
+ Value::Int(v) => Value::Int(v.abs()),
+ Value::Float(v) => Value::Float(v.abs()),
+ Value::Length(v) => Value::Length(v.abs()),
+ Value::Angle(v) => Value::Angle(v.abs()),
+ Value::Relative(v) => Value::Relative(v.abs()),
+ Value::Fractional(v) => Value::Fractional(v.abs()),
+ Value::Linear(_) => bail!(span, "cannot take absolute value of a linear"),
+ v => bail!(span, "expected numeric value, found {}", v.type_name()),
+ })
+}
+
+/// The minimum of a sequence of values.
+pub fn min(_: &mut Context, args: &mut Args) -> TypResult<Value> {
+ minmax(args, Ordering::Less)
+}
+
+/// The maximum of a sequence of values.
+pub fn max(_: &mut Context, args: &mut Args) -> TypResult<Value> {
+ minmax(args, Ordering::Greater)
+}
+
+/// Find the minimum or maximum of a sequence of values.
+fn minmax(args: &mut Args, goal: Ordering) -> TypResult<Value> {
+ let mut extremum = args.expect::<Value>("value")?;
+ for Spanned { v, span } in args.all::<Spanned<Value>>()? {
+ match v.partial_cmp(&extremum) {
+ Some(ordering) => {
+ if ordering == goal {
+ extremum = v;
+ }
+ }
+ None => bail!(
+ span,
+ "cannot compare {} with {}",
+ extremum.type_name(),
+ v.type_name(),
+ ),
+ }
+ }
+ Ok(extremum)
+}
+
+/// Whether an integer is even.
+pub fn even(_: &mut Context, args: &mut Args) -> TypResult<Value> {
+ Ok(Value::Bool(args.expect::<i64>("integer")? % 2 == 0))
+}
+
+/// Whether an integer is odd.
+pub fn odd(_: &mut Context, args: &mut Args) -> TypResult<Value> {
+ Ok(Value::Bool(args.expect::<i64>("integer")? % 2 != 0))
+}
+
+/// The modulo of two numbers.
+pub fn modulo(_: &mut Context, args: &mut Args) -> TypResult<Value> {
+ let Spanned { v: v1, span: span1 } = args.expect("integer or float")?;
+ let Spanned { v: v2, span: span2 } = args.expect("integer or float")?;
+
+ let (a, b) = match (v1, v2) {
+ (Value::Int(a), Value::Int(b)) => match a.checked_rem(b) {
+ Some(res) => return Ok(Value::Int(res)),
+ None => bail!(span2, "divisor must not be zero"),
+ },
+ (Value::Int(a), Value::Float(b)) => (a as f64, b),
+ (Value::Float(a), Value::Int(b)) => (a, b as f64),
+ (Value::Float(a), Value::Float(b)) => (a, b),
+ (Value::Int(_), b) | (Value::Float(_), b) => bail!(
+ span2,
+ format!("expected integer or float, found {}", b.type_name())
+ ),
+ (a, _) => bail!(
+ span1,
+ format!("expected integer or float, found {}", a.type_name())
+ ),
+ };
+
+ if b == 0.0 {
+ bail!(span2, "divisor must not be zero");
+ }
+
+ Ok(Value::Float(a % b))
+}
+
+/// Create a sequence of numbers.
+pub fn range(_: &mut Context, args: &mut Args) -> TypResult<Value> {
+ let first = args.expect::<i64>("end")?;
+ let (start, end) = match args.eat::<i64>()? {
+ Some(second) => (first, second),
+ None => (0, first),
+ };
+
+ let step: i64 = match args.named("step")? {
+ Some(Spanned { v: 0, span }) => bail!(span, "step must not be zero"),
+ Some(Spanned { v, .. }) => v,
+ None => 1,
+ };
+
+ let mut x = start;
+ let mut seq = vec![];
+
+ while x.cmp(&end) == 0.cmp(&step) {
+ seq.push(Value::Int(x));
+ x += step;
+ }
+
+ Ok(Value::Array(Array::from_vec(seq)))
+}
diff --git a/src/library/utility.rs b/src/library/utility/mod.rs
index ceae20bf..886cdc13 100644
--- a/src/library/utility.rs
+++ b/src/library/utility/mod.rs
@@ -1,11 +1,15 @@
//! Computational utility functions.
-use std::cmp::Ordering;
+mod math;
+mod numbering;
+
+pub use math::*;
+pub use numbering::*;
+
use std::str::FromStr;
-use super::prelude::*;
-use super::{Case, TextNode};
-use crate::eval::Array;
+use crate::library::prelude::*;
+use crate::library::text::{Case, TextNode};
/// Ensure that a condition is fulfilled.
pub fn assert(_: &mut Context, args: &mut Args) -> TypResult<Value> {
@@ -75,7 +79,7 @@ pub fn float(_: &mut Context, args: &mut Args) -> TypResult<Value> {
}))
}
-/// Try to convert a value to a string.
+/// Cconvert a value to a string.
pub fn str(_: &mut Context, args: &mut Args) -> TypResult<Value> {
let Spanned { v, span } = args.expect("value")?;
Ok(Value::Str(match v {
@@ -141,115 +145,19 @@ pub fn cmyk(_: &mut Context, args: &mut Args) -> TypResult<Value> {
Ok(Value::Color(CmykColor::new(c, m, y, k).into()))
}
-/// The absolute value of a numeric value.
-pub fn abs(_: &mut Context, args: &mut Args) -> TypResult<Value> {
- let Spanned { v, span } = args.expect("numeric value")?;
- Ok(match v {
- Value::Int(v) => Value::Int(v.abs()),
- Value::Float(v) => Value::Float(v.abs()),
- Value::Length(v) => Value::Length(v.abs()),
- Value::Angle(v) => Value::Angle(v.abs()),
- Value::Relative(v) => Value::Relative(v.abs()),
- Value::Fractional(v) => Value::Fractional(v.abs()),
- Value::Linear(_) => bail!(span, "cannot take absolute value of a linear"),
- v => bail!(span, "expected numeric value, found {}", v.type_name()),
- })
-}
-
-/// The minimum of a sequence of values.
-pub fn min(_: &mut Context, args: &mut Args) -> TypResult<Value> {
- minmax(args, Ordering::Less)
-}
-
-/// The maximum of a sequence of values.
-pub fn max(_: &mut Context, args: &mut Args) -> TypResult<Value> {
- minmax(args, Ordering::Greater)
-}
-
-/// Whether an integer is even.
-pub fn even(_: &mut Context, args: &mut Args) -> TypResult<Value> {
- Ok(Value::Bool(args.expect::<i64>("integer")? % 2 == 0))
-}
-
-/// Whether an integer is odd.
-pub fn odd(_: &mut Context, args: &mut Args) -> TypResult<Value> {
- Ok(Value::Bool(args.expect::<i64>("integer")? % 2 != 0))
-}
-
-/// The modulo of two numbers.
-pub fn modulo(_: &mut Context, args: &mut Args) -> TypResult<Value> {
- let Spanned { v: v1, span: span1 } = args.expect("integer or float")?;
- let Spanned { v: v2, span: span2 } = args.expect("integer or float")?;
-
- let (a, b) = match (v1, v2) {
- (Value::Int(a), Value::Int(b)) => match a.checked_rem(b) {
- Some(res) => return Ok(Value::Int(res)),
- None => bail!(span2, "divisor must not be zero"),
- },
- (Value::Int(a), Value::Float(b)) => (a as f64, b),
- (Value::Float(a), Value::Int(b)) => (a, b as f64),
- (Value::Float(a), Value::Float(b)) => (a, b),
- (Value::Int(_), b) | (Value::Float(_), b) => bail!(
- span2,
- format!("expected integer or float, found {}", b.type_name())
- ),
- (a, _) => bail!(
- span1,
- format!("expected integer or float, found {}", a.type_name())
+/// The length of a string, an array or a dictionary.
+pub fn len(_: &mut Context, args: &mut Args) -> TypResult<Value> {
+ let Spanned { v, span } = args.expect("collection")?;
+ Ok(Value::Int(match v {
+ Value::Str(v) => v.len() as i64,
+ Value::Array(v) => v.len(),
+ Value::Dict(v) => v.len(),
+ v => bail!(
+ span,
+ "expected string, array or dictionary, found {}",
+ v.type_name(),
),
- };
-
- if b == 0.0 {
- bail!(span2, "divisor must not be zero");
- }
-
- Ok(Value::Float(a % b))
-}
-
-/// Find the minimum or maximum of a sequence of values.
-fn minmax(args: &mut Args, goal: Ordering) -> TypResult<Value> {
- let mut extremum = args.expect::<Value>("value")?;
- for Spanned { v, span } in args.all::<Spanned<Value>>()? {
- match v.partial_cmp(&extremum) {
- Some(ordering) => {
- if ordering == goal {
- extremum = v;
- }
- }
- None => bail!(
- span,
- "cannot compare {} with {}",
- extremum.type_name(),
- v.type_name(),
- ),
- }
- }
- Ok(extremum)
-}
-
-/// Create a sequence of numbers.
-pub fn range(_: &mut Context, args: &mut Args) -> TypResult<Value> {
- let first = args.expect::<i64>("end")?;
- let (start, end) = match args.eat::<i64>()? {
- Some(second) => (first, second),
- None => (0, first),
- };
-
- let step: i64 = match args.named("step")? {
- Some(Spanned { v: 0, span }) => bail!(span, "step must not be zero"),
- Some(Spanned { v, .. }) => v,
- None => 1,
- };
-
- let mut x = start;
- let mut seq = vec![];
-
- while x.cmp(&end) == 0.cmp(&step) {
- seq.push(Value::Int(x));
- x += step;
- }
-
- Ok(Value::Array(Array::from_vec(seq)))
+ }))
}
/// Convert a string to lowercase.
@@ -272,21 +180,6 @@ fn case(case: Case, args: &mut Args) -> TypResult<Value> {
})
}
-/// The length of a string, an array or a dictionary.
-pub fn len(_: &mut Context, args: &mut Args) -> TypResult<Value> {
- let Spanned { v, span } = args.expect("collection")?;
- Ok(Value::Int(match v {
- Value::Str(v) => v.len() as i64,
- Value::Array(v) => v.len(),
- Value::Dict(v) => v.len(),
- v => bail!(
- span,
- "expected string, array or dictionary, found {}",
- v.type_name(),
- ),
- }))
-}
-
/// The sorted version of an array.
pub fn sorted(_: &mut Context, args: &mut Args) -> TypResult<Value> {
let Spanned { v, span } = args.expect::<Spanned<Array>>("array")?;
diff --git a/src/library/numbering.rs b/src/library/utility/numbering.rs
index 05a39e36..0070873f 100644
--- a/src/library/numbering.rs
+++ b/src/library/utility/numbering.rs
@@ -1,33 +1,26 @@
-//! Conversion of numbers into letters, roman numerals and symbols.
+use crate::library::prelude::*;
-use super::prelude::*;
+/// Converts an integer into one or multiple letters.
+pub fn letter(_: &mut Context, args: &mut Args) -> TypResult<Value> {
+ convert(Numbering::Letter, args)
+}
-static ROMANS: &'static [(&'static str, usize)] = &[
- ("M̅", 1000000),
- ("D̅", 500000),
- ("C̅", 100000),
- ("L̅", 50000),
- ("X̅", 10000),
- ("V̅", 5000),
- ("I̅V̅", 4000),
- ("M", 1000),
- ("CM", 900),
- ("D", 500),
- ("CD", 400),
- ("C", 100),
- ("XC", 90),
- ("L", 50),
- ("XL", 40),
- ("X", 10),
- ("IX", 9),
- ("V", 5),
- ("IV", 4),
- ("I", 1),
-];
+/// Converts an integer into a roman numeral.
+pub fn roman(_: &mut Context, args: &mut Args) -> TypResult<Value> {
+ convert(Numbering::Roman, args)
+}
-static SYMBOLS: &'static [char] = &['*', '†', '‡', '§', '‖', '¶'];
+/// Convert a number into a symbol.
+pub fn symbol(_: &mut Context, args: &mut Args) -> TypResult<Value> {
+ convert(Numbering::Symbol, args)
+}
+
+fn convert(numbering: Numbering, args: &mut Args) -> TypResult<Value> {
+ let n = args.expect::<usize>("non-negative integer")?;
+ Ok(Value::Str(numbering.apply(n)))
+}
-/// The different kinds of numberings.
+/// Allows to convert a number into letters, roman numerals and symbols.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Numbering {
Arabic,
@@ -92,22 +85,27 @@ impl Numbering {
}
}
-/// Converts an integer into one or multiple letters.
-pub fn letter(_: &mut Context, args: &mut Args) -> TypResult<Value> {
- convert(Numbering::Letter, args)
-}
-
-/// Converts an integer into a roman numeral.
-pub fn roman(_: &mut Context, args: &mut Args) -> TypResult<Value> {
- convert(Numbering::Roman, args)
-}
-
-/// Convert a number into a symbol.
-pub fn symbol(_: &mut Context, args: &mut Args) -> TypResult<Value> {
- convert(Numbering::Symbol, args)
-}
+static ROMANS: &'static [(&'static str, usize)] = &[
+ ("M̅", 1000000),
+ ("D̅", 500000),
+ ("C̅", 100000),
+ ("L̅", 50000),
+ ("X̅", 10000),
+ ("V̅", 5000),
+ ("I̅V̅", 4000),
+ ("M", 1000),
+ ("CM", 900),
+ ("D", 500),
+ ("CD", 400),
+ ("C", 100),
+ ("XC", 90),
+ ("L", 50),
+ ("XL", 40),
+ ("X", 10),
+ ("IX", 9),
+ ("V", 5),
+ ("IV", 4),
+ ("I", 1),
+];
-fn convert(numbering: Numbering, args: &mut Args) -> TypResult<Value> {
- let n = args.expect::<usize>("non-negative integer")?;
- Ok(Value::Str(numbering.apply(n)))
-}
+static SYMBOLS: &'static [char] = &['*', '†', '‡', '§', '‖', '¶'];
diff --git a/tests/ref/markup/enums.png b/tests/ref/elements/enum.png
index ecd1cf53..ecd1cf53 100644
--- a/tests/ref/markup/enums.png
+++ b/tests/ref/elements/enum.png
Binary files differ
diff --git a/tests/ref/markup/heading.png b/tests/ref/elements/heading.png
index 09f3d7c7..09f3d7c7 100644
--- a/tests/ref/markup/heading.png
+++ b/tests/ref/elements/heading.png
Binary files differ
diff --git a/tests/ref/layout/image.png b/tests/ref/elements/image.png
index 11a28980..11a28980 100644
--- a/tests/ref/layout/image.png
+++ b/tests/ref/elements/image.png
Binary files differ
diff --git a/tests/ref/markup/lists.png b/tests/ref/elements/list.png
index 503bfc9a..503bfc9a 100644
--- a/tests/ref/markup/lists.png
+++ b/tests/ref/elements/list.png
Binary files differ
diff --git a/tests/ref/markup/math.png b/tests/ref/elements/math.png
index 77ec8d51..77ec8d51 100644
--- a/tests/ref/markup/math.png
+++ b/tests/ref/elements/math.png
Binary files differ
diff --git a/tests/ref/layout/shape-aspect.png b/tests/ref/elements/shape-aspect.png
index 719c1e6e..719c1e6e 100644
--- a/tests/ref/layout/shape-aspect.png
+++ b/tests/ref/elements/shape-aspect.png
Binary files differ
diff --git a/tests/ref/layout/shape-circle.png b/tests/ref/elements/shape-circle.png
index 040c6f0b..040c6f0b 100644
--- a/tests/ref/layout/shape-circle.png
+++ b/tests/ref/elements/shape-circle.png
Binary files differ
diff --git a/tests/ref/layout/shape-ellipse.png b/tests/ref/elements/shape-ellipse.png
index 740f005f..740f005f 100644
--- a/tests/ref/layout/shape-ellipse.png
+++ b/tests/ref/elements/shape-ellipse.png
Binary files differ
diff --git a/tests/ref/layout/shape-fill-stroke.png b/tests/ref/elements/shape-fill-stroke.png
index 12fcbd55..12fcbd55 100644
--- a/tests/ref/layout/shape-fill-stroke.png
+++ b/tests/ref/elements/shape-fill-stroke.png
Binary files differ
diff --git a/tests/ref/layout/shape-rect.png b/tests/ref/elements/shape-rect.png
index 1fdb0dac..1fdb0dac 100644
--- a/tests/ref/layout/shape-rect.png
+++ b/tests/ref/elements/shape-rect.png
Binary files differ
diff --git a/tests/ref/layout/shape-square.png b/tests/ref/elements/shape-square.png
index 00a0c848..00a0c848 100644
--- a/tests/ref/layout/shape-square.png
+++ b/tests/ref/elements/shape-square.png
Binary files differ
diff --git a/tests/ref/layout/table.png b/tests/ref/elements/table.png
index bc70d548..bc70d548 100644
--- a/tests/ref/layout/table.png
+++ b/tests/ref/elements/table.png
Binary files differ
diff --git a/tests/ref/layout/box-block.png b/tests/ref/layout/container.png
index 87484c25..87484c25 100644
--- a/tests/ref/layout/box-block.png
+++ b/tests/ref/layout/container.png
Binary files differ
diff --git a/tests/ref/markup/linebreak.png b/tests/ref/markup/linebreak.png
deleted file mode 100644
index 2304ab96..00000000
--- a/tests/ref/markup/linebreak.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/text/decorations.png b/tests/ref/text/deco.png
index 684532a1..684532a1 100644
--- a/tests/ref/text/decorations.png
+++ b/tests/ref/text/deco.png
Binary files differ
diff --git a/tests/ref/markup/emph-strong.png b/tests/ref/text/emph-strong.png
index cce98812..cce98812 100644
--- a/tests/ref/markup/emph-strong.png
+++ b/tests/ref/text/emph-strong.png
Binary files differ
diff --git a/tests/ref/markup/escape.png b/tests/ref/text/escape.png
index 3434d6e0..3434d6e0 100644
--- a/tests/ref/markup/escape.png
+++ b/tests/ref/text/escape.png
Binary files differ
diff --git a/tests/ref/text/linebreaks.png b/tests/ref/text/linebreak.png
index 1498a845..1498a845 100644
--- a/tests/ref/text/linebreaks.png
+++ b/tests/ref/text/linebreak.png
Binary files differ
diff --git a/tests/ref/text/links.png b/tests/ref/text/link.png
index 75ea19e8..75ea19e8 100644
--- a/tests/ref/text/links.png
+++ b/tests/ref/text/link.png
Binary files differ
diff --git a/tests/ref/markup/raw.png b/tests/ref/text/raw.png
index 64402dae..64402dae 100644
--- a/tests/ref/markup/raw.png
+++ b/tests/ref/text/raw.png
Binary files differ
diff --git a/tests/ref/markup/shorthands.png b/tests/ref/text/shorthands.png
index ad09967d..ad09967d 100644
--- a/tests/ref/markup/shorthands.png
+++ b/tests/ref/text/shorthands.png
Binary files differ
diff --git a/tests/typ/markup/enums.typ b/tests/typ/elements/enum.typ
index 8ba3cea6..8ba3cea6 100644
--- a/tests/typ/markup/enums.typ
+++ b/tests/typ/elements/enum.typ
diff --git a/tests/typ/markup/heading.typ b/tests/typ/elements/heading.typ
index 2ae97aa8..2ae97aa8 100644
--- a/tests/typ/markup/heading.typ
+++ b/tests/typ/elements/heading.typ
diff --git a/tests/typ/layout/image.typ b/tests/typ/elements/image.typ
index 8817713f..8817713f 100644
--- a/tests/typ/layout/image.typ
+++ b/tests/typ/elements/image.typ
diff --git a/tests/typ/markup/lists.typ b/tests/typ/elements/list.typ
index 38fc2c63..38fc2c63 100644
--- a/tests/typ/markup/lists.typ
+++ b/tests/typ/elements/list.typ
diff --git a/tests/typ/markup/math.typ b/tests/typ/elements/math.typ
index cad01d10..cad01d10 100644
--- a/tests/typ/markup/math.typ
+++ b/tests/typ/elements/math.typ
diff --git a/tests/typ/layout/shape-aspect.typ b/tests/typ/elements/shape-aspect.typ
index 2c3e9b0c..2c3e9b0c 100644
--- a/tests/typ/layout/shape-aspect.typ
+++ b/tests/typ/elements/shape-aspect.typ
diff --git a/tests/typ/layout/shape-circle.typ b/tests/typ/elements/shape-circle.typ
index 4b978e86..4b978e86 100644
--- a/tests/typ/layout/shape-circle.typ
+++ b/tests/typ/elements/shape-circle.typ
diff --git a/tests/typ/layout/shape-ellipse.typ b/tests/typ/elements/shape-ellipse.typ
index 154144c4..154144c4 100644
--- a/tests/typ/layout/shape-ellipse.typ
+++ b/tests/typ/elements/shape-ellipse.typ
diff --git a/tests/typ/layout/shape-fill-stroke.typ b/tests/typ/elements/shape-fill-stroke.typ
index 935f3bc7..935f3bc7 100644
--- a/tests/typ/layout/shape-fill-stroke.typ
+++ b/tests/typ/elements/shape-fill-stroke.typ
diff --git a/tests/typ/layout/shape-rect.typ b/tests/typ/elements/shape-rect.typ
index add39b80..add39b80 100644
--- a/tests/typ/layout/shape-rect.typ
+++ b/tests/typ/elements/shape-rect.typ
diff --git a/tests/typ/layout/shape-square.typ b/tests/typ/elements/shape-square.typ
index c4ece778..c4ece778 100644
--- a/tests/typ/layout/shape-square.typ
+++ b/tests/typ/elements/shape-square.typ
diff --git a/tests/typ/layout/table.typ b/tests/typ/elements/table.typ
index 0372951c..0372951c 100644
--- a/tests/typ/layout/table.typ
+++ b/tests/typ/elements/table.typ
diff --git a/tests/typ/layout/box-block.typ b/tests/typ/layout/container.typ
index c6928074..c6928074 100644
--- a/tests/typ/layout/box-block.typ
+++ b/tests/typ/layout/container.typ
diff --git a/tests/typ/markup/linebreak.typ b/tests/typ/markup/linebreak.typ
deleted file mode 100644
index 7fe8a718..00000000
--- a/tests/typ/markup/linebreak.typ
+++ /dev/null
@@ -1,4 +0,0 @@
-// Test line breaks.
-
----
-A \ B \ C
diff --git a/tests/typ/text/decorations.typ b/tests/typ/text/deco.typ
index e0693ca3..e0693ca3 100644
--- a/tests/typ/text/decorations.typ
+++ b/tests/typ/text/deco.typ
diff --git a/tests/typ/markup/emph-strong.typ b/tests/typ/text/emph-strong.typ
index 9bdb5059..9bdb5059 100644
--- a/tests/typ/markup/emph-strong.typ
+++ b/tests/typ/text/emph-strong.typ
diff --git a/tests/typ/markup/escape.typ b/tests/typ/text/escape.typ
index 6ec469c1..6ec469c1 100644
--- a/tests/typ/markup/escape.typ
+++ b/tests/typ/text/escape.typ
diff --git a/tests/typ/text/linebreaks.typ b/tests/typ/text/linebreak.typ
index 25a8c5ab..ff8559d6 100644
--- a/tests/typ/text/linebreaks.typ
+++ b/tests/typ/text/linebreak.typ
@@ -1,4 +1,4 @@
-// Test line breaking special cases.
+// Test line breaks.
---
// Test overlong word that is not directly after a hard break.
diff --git a/tests/typ/text/links.typ b/tests/typ/text/link.typ
index 99037ee3..99037ee3 100644
--- a/tests/typ/text/links.typ
+++ b/tests/typ/text/link.typ
diff --git a/tests/typ/markup/raw.typ b/tests/typ/text/raw.typ
index 0e053a9b..0e053a9b 100644
--- a/tests/typ/markup/raw.typ
+++ b/tests/typ/text/raw.typ
diff --git a/tests/typ/markup/shorthands.typ b/tests/typ/text/shorthands.typ
index ef0bf866..ef0bf866 100644
--- a/tests/typ/markup/shorthands.typ
+++ b/tests/typ/text/shorthands.typ
diff --git a/tests/typ/utility/collection.typ b/tests/typ/utility/collection.typ
index 92ec2867..a4bc817e 100644
--- a/tests/typ/utility/collection.typ
+++ b/tests/typ/utility/collection.typ
@@ -2,12 +2,6 @@
// Ref: false
---
-#let memes = "ArE mEmEs gReAt?";
-#test(lower(memes), "are memes great?")
-#test(upper(memes), "ARE MEMES GREAT?")
-#test(upper("Ελλάδα"), "ΕΛΛΆΔΑ")
-
----
// Test the `len` function.
#test(len(()), 0)
#test(len(("A", "B", "C")), 3)
diff --git a/tests/typ/utility/strings.typ b/tests/typ/utility/strings.typ
index 91be0faa..4222266b 100644
--- a/tests/typ/utility/strings.typ
+++ b/tests/typ/utility/strings.typ
@@ -1,7 +1,17 @@
-// Test string functions.
+// Test string handling functions.
+// Ref: false
---
// Test the `upper`, `lower`, and number formatting functions.
+#let memes = "ArE mEmEs gReAt?";
+#test(lower(memes), "are memes great?")
+#test(upper(memes), "ARE MEMES GREAT?")
+#test(upper("Ελλάδα"), "ΕΛΛΆΔΑ")
+
+---
+// Test numbering formatting functions.
+// Ref: true
+
#upper("Abc 8")
#upper[def]
diff --git a/tests/typeset.rs b/tests/typeset.rs
index 1b45a975..388d4516 100644
--- a/tests/typeset.rs
+++ b/tests/typeset.rs
@@ -12,7 +12,8 @@ use typst::diag::Error;
use typst::eval::{Smart, StyleMap, Value};
use typst::frame::{Element, Frame};
use typst::geom::{Length, RgbaColor};
-use typst::library::{PageNode, TextNode};
+use typst::library::layout::PageNode;
+use typst::library::text::TextNode;
use typst::loading::FsLoader;
use typst::parse::Scanner;
use typst::source::SourceFile;