summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-04-08 15:08:26 +0200
committerLaurenz <laurmaedje@gmail.com>2022-04-08 15:45:14 +0200
commit712c00ecb72b67da2c0788e5d3eb4dcc6366b2a7 (patch)
treef5d7ef4341a4728c980d020cc173fa6bb70feaff /src/library
parent977ac77e6a3298be2644a8231e93acbef9f7f396 (diff)
Em units
Diffstat (limited to 'src/library')
-rw-r--r--src/library/graphics/image.rs2
-rw-r--r--src/library/graphics/line.rs43
-rw-r--r--src/library/graphics/shape.rs9
-rw-r--r--src/library/graphics/transform.rs55
-rw-r--r--src/library/layout/columns.rs5
-rw-r--r--src/library/layout/flow.rs20
-rw-r--r--src/library/layout/grid.rs16
-rw-r--r--src/library/layout/pad.rs11
-rw-r--r--src/library/layout/page.rs24
-rw-r--r--src/library/layout/place.rs6
-rw-r--r--src/library/layout/spacing.rs2
-rw-r--r--src/library/layout/stack.rs16
-rw-r--r--src/library/mod.rs12
-rw-r--r--src/library/prelude.rs4
-rw-r--r--src/library/structure/heading.rs14
-rw-r--r--src/library/structure/list.rs24
-rw-r--r--src/library/structure/table.rs5
-rw-r--r--src/library/text/deco.rs51
-rw-r--r--src/library/text/mod.rs77
-rw-r--r--src/library/text/par.rs24
-rw-r--r--src/library/text/shaping.rs18
-rw-r--r--src/library/utility/math.rs5
22 files changed, 252 insertions, 191 deletions
diff --git a/src/library/graphics/image.rs b/src/library/graphics/image.rs
index 23ad52ab..193dc60e 100644
--- a/src/library/graphics/image.rs
+++ b/src/library/graphics/image.rs
@@ -13,7 +13,7 @@ impl ImageNode {
fn construct(ctx: &mut Context, args: &mut Args) -> TypResult<Content> {
let path = args.expect::<Spanned<EcoString>>("path to image file")?;
- let full = ctx.resolve(&path.v);
+ let full = ctx.complete_path(&path.v);
let id = ctx.images.load(&full).map_err(|err| match err.kind() {
std::io::ErrorKind::NotFound => error!(path.span, "file not found"),
_ => error!(path.span, "failed to load image ({})", err),
diff --git a/src/library/graphics/line.rs b/src/library/graphics/line.rs
index 571506c1..1dd138e6 100644
--- a/src/library/graphics/line.rs
+++ b/src/library/graphics/line.rs
@@ -4,9 +4,9 @@ use crate::library::prelude::*;
#[derive(Debug, Hash)]
pub struct LineNode {
/// Where the line starts.
- origin: Spec<Relative<Length>>,
+ origin: Spec<Relative<RawLength>>,
/// The offset from the `origin` where the line ends.
- delta: Spec<Relative<Length>>,
+ delta: Spec<Relative<RawLength>>,
}
#[node]
@@ -14,15 +14,17 @@ impl LineNode {
/// How to stroke the line.
pub const STROKE: Paint = Color::BLACK.into();
/// The line's thickness.
- pub const THICKNESS: Length = Length::pt(1.0);
+ #[property(resolve)]
+ pub const THICKNESS: RawLength = Length::pt(1.0).into();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
let origin = args.named("origin")?.unwrap_or_default();
- let delta = match args.named::<Spec<Relative<Length>>>("to")? {
+
+ let delta = match args.named::<Spec<Relative<RawLength>>>("to")? {
Some(to) => to.zip(origin).map(|(to, from)| to - from),
None => {
let length = args
- .named::<Relative<Length>>("length")?
+ .named::<Relative<RawLength>>("length")?
.unwrap_or(Length::cm(1.0).into());
let angle = args.named::<Angle>("angle")?.unwrap_or_default();
@@ -50,18 +52,37 @@ impl Layout for LineNode {
thickness,
});
- let resolved_origin =
- self.origin.zip(regions.base).map(|(l, b)| Relative::resolve(l, b));
- let resolved_delta =
- self.delta.zip(regions.base).map(|(l, b)| Relative::resolve(l, b));
+ let origin = self
+ .origin
+ .resolve(styles)
+ .zip(regions.base)
+ .map(|(l, b)| l.relative_to(b));
+
+ let delta = self
+ .delta
+ .resolve(styles)
+ .zip(regions.base)
+ .map(|(l, b)| l.relative_to(b));
- let geometry = Geometry::Line(resolved_delta.to_point());
+ let geometry = Geometry::Line(delta.to_point());
let shape = Shape { geometry, fill: None, stroke };
let target = regions.expand.select(regions.first, Size::zero());
let mut frame = Frame::new(target);
- frame.push(resolved_origin.to_point(), Element::Shape(shape));
+ frame.push(origin.to_point(), Element::Shape(shape));
Ok(vec![Arc::new(frame)])
}
}
+
+castable! {
+ Spec<Relative<RawLength>>,
+ Expected: "array of two relative lengths",
+ Value::Array(array) => {
+ let mut iter = array.into_iter();
+ match (iter.next(), iter.next(), iter.next()) {
+ (Some(a), Some(b), None) => Spec::new(a.cast()?, b.cast()?),
+ _ => Err("point array must contain exactly two entries")?,
+ }
+ },
+}
diff --git a/src/library/graphics/shape.rs b/src/library/graphics/shape.rs
index 9faa4c52..ec6f735b 100644
--- a/src/library/graphics/shape.rs
+++ b/src/library/graphics/shape.rs
@@ -26,14 +26,15 @@ impl<const S: ShapeKind> ShapeNode<S> {
/// How to stroke the shape.
pub const STROKE: Smart<Option<Paint>> = Smart::Auto;
/// The stroke's thickness.
- pub const THICKNESS: Length = Length::pt(1.0);
+ #[property(resolve)]
+ pub const THICKNESS: RawLength = Length::pt(1.0).into();
/// How much to pad the shape's content.
- pub const PADDING: Relative<Length> = Relative::zero();
+ pub const PADDING: Relative<RawLength> = Relative::zero();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
let size = match S {
- SQUARE => args.named::<Length>("size")?.map(Relative::from),
- CIRCLE => args.named::<Length>("radius")?.map(|r| 2.0 * Relative::from(r)),
+ SQUARE => args.named::<RawLength>("size")?.map(Relative::from),
+ CIRCLE => args.named::<RawLength>("radius")?.map(|r| 2.0 * Relative::from(r)),
_ => None,
};
diff --git a/src/library/graphics/transform.rs b/src/library/graphics/transform.rs
index 67f9cad9..ea021cc1 100644
--- a/src/library/graphics/transform.rs
+++ b/src/library/graphics/transform.rs
@@ -1,6 +1,46 @@
use crate::geom::Transform;
use crate::library::prelude::*;
+/// Move a node without affecting layout.
+#[derive(Debug, Hash)]
+pub struct MoveNode {
+ /// The offset by which to move the node.
+ pub delta: Spec<Relative<RawLength>>,
+ /// The node whose contents should be moved.
+ pub child: LayoutNode,
+}
+
+#[node]
+impl MoveNode {
+ fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
+ let dx = args.named("x")?.unwrap_or_default();
+ let dy = args.named("y")?.unwrap_or_default();
+ Ok(Content::inline(Self {
+ delta: Spec::new(dx, dy),
+ child: args.expect("body")?,
+ }))
+ }
+}
+
+impl Layout for MoveNode {
+ fn layout(
+ &self,
+ ctx: &mut Context,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>> {
+ let mut frames = self.child.layout(ctx, regions, styles)?;
+
+ let delta = self.delta.resolve(styles);
+ for frame in &mut frames {
+ let delta = delta.zip(frame.size).map(|(d, s)| d.relative_to(s));
+ Arc::make_mut(frame).translate(delta.to_point());
+ }
+
+ Ok(frames)
+ }
+}
+
/// Transform a node without affecting layout.
#[derive(Debug, Hash)]
pub struct TransformNode<const T: TransformKind> {
@@ -10,13 +50,10 @@ 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.
+/// Rotate a node without affecting layout.
pub type RotateNode = TransformNode<ROTATE>;
-/// Transform a node by scaling it without affecting layout.
+/// Scale a node without affecting layout.
pub type ScaleNode = TransformNode<SCALE>;
#[node]
@@ -27,11 +64,6 @@ impl<const T: TransformKind> TransformNode<T> {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
let transform = match T {
- MOVE => {
- let tx = args.named("x")?.unwrap_or_default();
- let ty = args.named("y")?.unwrap_or_default();
- Transform::translate(tx, ty)
- }
ROTATE => {
let angle = args.named_or_find("angle")?.unwrap_or_default();
Transform::rotate(angle)
@@ -77,9 +109,6 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
/// Kinds of transformations.
pub type TransformKind = usize;
-/// A translation on the X and Y axes.
-const MOVE: TransformKind = 0;
-
/// A rotational transformation.
const ROTATE: TransformKind = 1;
diff --git a/src/library/layout/columns.rs b/src/library/layout/columns.rs
index 1cb45c37..3ef66b40 100644
--- a/src/library/layout/columns.rs
+++ b/src/library/layout/columns.rs
@@ -14,7 +14,8 @@ pub struct ColumnsNode {
#[node]
impl ColumnsNode {
/// The size of the gutter space between each column.
- pub const GUTTER: Relative<Length> = Ratio::new(0.04).into();
+ #[property(resolve)]
+ pub const GUTTER: Relative<RawLength> = Ratio::new(0.04).into();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::block(Self {
@@ -39,7 +40,7 @@ impl Layout for ColumnsNode {
// Determine the width of the gutter and each column.
let columns = self.columns.get();
- let gutter = styles.get(Self::GUTTER).resolve(regions.base.x);
+ let gutter = styles.get(Self::GUTTER).relative_to(regions.base.x);
let width = (regions.first.x - gutter * (columns - 1) as f64) / columns as f64;
// Create the pod regions.
diff --git a/src/library/layout/flow.rs b/src/library/layout/flow.rs
index a53b0304..a3947e34 100644
--- a/src/library/layout/flow.rs
+++ b/src/library/layout/flow.rs
@@ -1,6 +1,6 @@
use super::{AlignNode, PlaceNode, Spacing};
use crate::library::prelude::*;
-use crate::library::text::{ParNode, TextNode};
+use crate::library::text::ParNode;
/// Arrange spacing, paragraphs and other block-level nodes into a flow.
///
@@ -37,22 +37,20 @@ impl Layout for FlowNode {
let styles = map.chain(&styles);
match child {
FlowChild::Leading => {
- let em = styles.get(TextNode::SIZE);
- let amount = styles.get(ParNode::LEADING).resolve(em);
- layouter.layout_spacing(amount.into());
+ let amount = styles.get(ParNode::LEADING);
+ layouter.layout_spacing(amount.into(), styles);
}
FlowChild::Parbreak => {
- let em = styles.get(TextNode::SIZE);
let leading = styles.get(ParNode::LEADING);
let spacing = styles.get(ParNode::SPACING);
- let amount = (leading + spacing).resolve(em);
- layouter.layout_spacing(amount.into());
+ let amount = leading + spacing;
+ layouter.layout_spacing(amount.into(), styles);
}
FlowChild::Colbreak => {
layouter.finish_region();
}
FlowChild::Spacing(kind) => {
- layouter.layout_spacing(*kind);
+ layouter.layout_spacing(*kind, styles);
}
FlowChild::Node(ref node) => {
layouter.layout_node(ctx, node, styles)?;
@@ -142,11 +140,11 @@ impl FlowLayouter {
}
/// Layout spacing.
- pub fn layout_spacing(&mut self, spacing: Spacing) {
+ pub fn layout_spacing(&mut self, spacing: Spacing, styles: StyleChain) {
match spacing {
Spacing::Relative(v) => {
// Resolve the spacing and limit it to the remaining space.
- let resolved = v.resolve(self.full.y);
+ let resolved = v.resolve(styles).relative_to(self.full.y);
let limited = resolved.min(self.regions.first.y);
self.regions.first.y -= limited;
self.used.y += limited;
@@ -235,7 +233,7 @@ impl FlowLayouter {
offset += v;
}
FlowItem::Fractional(v) => {
- offset += v.resolve(self.fr, remaining);
+ offset += v.share(self.fr, remaining);
}
FlowItem::Frame(frame, aligns) => {
ruler = ruler.max(aligns.y);
diff --git a/src/library/layout/grid.rs b/src/library/layout/grid.rs
index b1e5e54c..ad6323d5 100644
--- a/src/library/layout/grid.rs
+++ b/src/library/layout/grid.rs
@@ -58,7 +58,7 @@ pub enum TrackSizing {
Auto,
/// A track size specified in absolute terms and relative to the parent's
/// size.
- Relative(Relative<Length>),
+ Relative(Relative<RawLength>),
/// A track size specified as a fraction of the remaining free space in the
/// parent.
Fractional(Fraction),
@@ -236,7 +236,8 @@ impl<'a> GridLayouter<'a> {
match col {
TrackSizing::Auto => {}
TrackSizing::Relative(v) => {
- let resolved = v.resolve(self.regions.base.x);
+ let resolved =
+ v.resolve(self.styles).relative_to(self.regions.base.x);
*rcol = resolved;
rel += resolved;
}
@@ -295,7 +296,8 @@ impl<'a> GridLayouter<'a> {
// base, for auto it's already correct and for fr we could
// only guess anyway.
if let TrackSizing::Relative(v) = self.rows[y] {
- pod.base.y = v.resolve(self.regions.base.y);
+ pod.base.y =
+ v.resolve(self.styles).relative_to(self.regions.base.y);
}
let frame = node.layout(ctx, &pod, self.styles)?.remove(0);
@@ -315,7 +317,7 @@ impl<'a> GridLayouter<'a> {
fn grow_fractional_columns(&mut self, remaining: Length, fr: Fraction) {
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
if let TrackSizing::Fractional(v) = col {
- *rcol = v.resolve(fr, remaining);
+ *rcol = v.share(fr, remaining);
}
}
}
@@ -422,10 +424,10 @@ impl<'a> GridLayouter<'a> {
fn layout_relative_row(
&mut self,
ctx: &mut Context,
- v: Relative<Length>,
+ v: Relative<RawLength>,
y: usize,
) -> TypResult<()> {
- let resolved = v.resolve(self.regions.base.y);
+ let resolved = v.resolve(self.styles).relative_to(self.regions.base.y);
let frame = self.layout_single_row(ctx, resolved, y)?;
// Skip to fitting region.
@@ -543,7 +545,7 @@ impl<'a> GridLayouter<'a> {
Row::Frame(frame) => frame,
Row::Fr(v, y) => {
let remaining = self.full - self.used.y;
- let height = v.resolve(self.fr, remaining);
+ let height = v.share(self.fr, remaining);
self.layout_single_row(ctx, height, y)?
}
};
diff --git a/src/library/layout/pad.rs b/src/library/layout/pad.rs
index b7470540..e688e423 100644
--- a/src/library/layout/pad.rs
+++ b/src/library/layout/pad.rs
@@ -4,7 +4,7 @@ use crate::library::prelude::*;
#[derive(Debug, Hash)]
pub struct PadNode {
/// The amount of padding.
- pub padding: Sides<Relative<Length>>,
+ pub padding: Sides<Relative<RawLength>>,
/// The child node whose sides to pad.
pub child: LayoutNode,
}
@@ -33,14 +33,15 @@ impl Layout for PadNode {
styles: StyleChain,
) -> TypResult<Vec<Arc<Frame>>> {
// Layout child into padded regions.
- let pod = regions.map(|size| shrink(size, self.padding));
+ let padding = self.padding.resolve(styles);
+ let pod = regions.map(|size| shrink(size, padding));
let mut frames = self.child.layout(ctx, &pod, styles)?;
for frame in &mut frames {
// Apply the padding inversely such that the grown size padded
// yields the frame's size.
- let padded = grow(frame.size, self.padding);
- let padding = self.padding.resolve(padded);
+ let padded = grow(frame.size, padding);
+ let padding = padding.relative_to(padded);
let offset = Point::new(padding.left, padding.top);
// Grow the frame and translate everything in the frame inwards.
@@ -55,7 +56,7 @@ impl Layout for PadNode {
/// Shrink a size by padding relative to the size itself.
fn shrink(size: Size, padding: Sides<Relative<Length>>) -> Size {
- size - padding.resolve(size).sum_by_axis()
+ size - padding.relative_to(size).sum_by_axis()
}
/// Grow a size by padding relative to the grown size.
diff --git a/src/library/layout/page.rs b/src/library/layout/page.rs
index 37a87ae2..7aa53b23 100644
--- a/src/library/layout/page.rs
+++ b/src/library/layout/page.rs
@@ -10,19 +10,21 @@ pub struct PageNode(pub LayoutNode);
#[node]
impl PageNode {
/// The unflipped width of the page.
- pub const WIDTH: Smart<Length> = Smart::Custom(Paper::A4.width());
+ #[property(resolve)]
+ pub const WIDTH: Smart<RawLength> = Smart::Custom(Paper::A4.width().into());
/// The unflipped height of the page.
- pub const HEIGHT: Smart<Length> = Smart::Custom(Paper::A4.height());
+ #[property(resolve)]
+ pub const HEIGHT: Smart<RawLength> = Smart::Custom(Paper::A4.height().into());
/// Whether the page is flipped into landscape orientation.
pub const FLIPPED: bool = false;
/// The left margin.
- pub const LEFT: Smart<Relative<Length>> = Smart::Auto;
+ pub const LEFT: Smart<Relative<RawLength>> = Smart::Auto;
/// The right margin.
- pub const RIGHT: Smart<Relative<Length>> = Smart::Auto;
+ pub const RIGHT: Smart<Relative<RawLength>> = Smart::Auto;
/// The top margin.
- pub const TOP: Smart<Relative<Length>> = Smart::Auto;
+ pub const TOP: Smart<Relative<RawLength>> = Smart::Auto;
/// The bottom margin.
- pub const BOTTOM: Smart<Relative<Length>> = Smart::Auto;
+ pub const BOTTOM: Smart<Relative<RawLength>> = Smart::Auto;
/// The page's background color.
pub const FILL: Option<Paint> = None;
/// How many columns the page has.
@@ -42,8 +44,8 @@ impl PageNode {
let mut styles = StyleMap::new();
if let Some(paper) = args.named_or_find::<Paper>("paper")? {
- styles.set(Self::WIDTH, Smart::Custom(paper.width()));
- styles.set(Self::HEIGHT, Smart::Custom(paper.height()));
+ styles.set(Self::WIDTH, Smart::Custom(paper.width().into()));
+ styles.set(Self::HEIGHT, Smart::Custom(paper.height().into()));
}
styles.set_opt(Self::WIDTH, args.named("width")?);
@@ -115,7 +117,7 @@ impl PageNode {
}
// Layout the child.
- let regions = Regions::repeat(size, size, size.map(Numeric::is_finite));
+ let regions = Regions::repeat(size, size, size.map(Length::is_finite));
let mut frames = child.layout(ctx, &regions, styles)?;
let header = styles.get(Self::HEADER);
@@ -124,7 +126,7 @@ impl PageNode {
// Realize header and footer.
for frame in &mut frames {
let size = frame.size;
- let padding = padding.resolve(size);
+ let padding = padding.resolve(styles).relative_to(size);
for (y, h, marginal) in [
(Length::zero(), padding.top, header),
(size.y - padding.bottom, padding.bottom, footer),
@@ -133,7 +135,7 @@ impl PageNode {
let pos = Point::new(padding.left, y);
let w = size.x - padding.left - padding.right;
let area = Size::new(w, h);
- let pod = Regions::one(area, area, area.map(Numeric::is_finite));
+ let pod = Regions::one(area, area, area.map(Length::is_finite));
let sub = Layout::layout(&content, ctx, &pod, styles)?.remove(0);
Arc::make_mut(frame).push_frame(pos, sub);
}
diff --git a/src/library/layout/place.rs b/src/library/layout/place.rs
index eefa6a9b..e74776db 100644
--- a/src/library/layout/place.rs
+++ b/src/library/layout/place.rs
@@ -8,12 +8,12 @@ pub struct PlaceNode(pub LayoutNode);
#[node]
impl PlaceNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
- let tx = args.named("dx")?.unwrap_or_default();
- let ty = args.named("dy")?.unwrap_or_default();
let aligns = args.find()?.unwrap_or(Spec::with_x(Some(RawAlign::Start)));
+ let dx = args.named("dx")?.unwrap_or_default();
+ let dy = args.named("dy")?.unwrap_or_default();
let body: LayoutNode = args.expect("body")?;
Ok(Content::block(Self(
- body.moved(Point::new(tx, ty)).aligned(aligns),
+ body.moved(Spec::new(dx, dy)).aligned(aligns),
)))
}
}
diff --git a/src/library/layout/spacing.rs b/src/library/layout/spacing.rs
index e9837ef5..3468af5e 100644
--- a/src/library/layout/spacing.rs
+++ b/src/library/layout/spacing.rs
@@ -24,7 +24,7 @@ impl VNode {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Spacing {
/// Spacing specified in absolute terms and relative to the parent's size.
- Relative(Relative<Length>),
+ Relative(Relative<RawLength>),
/// Spacing specified as a fraction of the remaining free space in the parent.
Fractional(Fraction),
}
diff --git a/src/library/layout/stack.rs b/src/library/layout/stack.rs
index 312757f3..f915c215 100644
--- a/src/library/layout/stack.rs
+++ b/src/library/layout/stack.rs
@@ -30,7 +30,7 @@ impl Layout for StackNode {
regions: &Regions,
styles: StyleChain,
) -> TypResult<Vec<Arc<Frame>>> {
- let mut layouter = StackLayouter::new(self.dir, regions);
+ let mut layouter = StackLayouter::new(self.dir, regions, styles);
// Spacing to insert before the next node.
let mut deferred = None;
@@ -85,13 +85,15 @@ castable! {
}
/// Performs stack layout.
-pub struct StackLayouter {
+pub struct StackLayouter<'a> {
/// The stacking direction.
dir: Dir,
/// The axis of the stacking direction.
axis: SpecAxis,
/// The regions to layout children into.
regions: Regions,
+ /// The inherited styles.
+ styles: StyleChain<'a>,
/// Whether the stack itself should expand to fill the region.
expand: Spec<bool>,
/// The full size of the current region that was available at the start.
@@ -117,9 +119,9 @@ enum StackItem {
Frame(Arc<Frame>, Align),
}
-impl StackLayouter {
+impl<'a> StackLayouter<'a> {
/// Create a new stack layouter.
- pub fn new(dir: Dir, regions: &Regions) -> Self {
+ pub fn new(dir: Dir, regions: &Regions, styles: StyleChain<'a>) -> Self {
let axis = dir.axis();
let expand = regions.expand;
let full = regions.first;
@@ -132,6 +134,7 @@ impl StackLayouter {
dir,
axis,
regions,
+ styles,
expand,
full,
used: Gen::zero(),
@@ -146,7 +149,8 @@ impl StackLayouter {
match spacing {
Spacing::Relative(v) => {
// Resolve the spacing and limit it to the remaining space.
- let resolved = v.resolve(self.regions.base.get(self.axis));
+ let resolved =
+ v.resolve(self.styles).relative_to(self.regions.base.get(self.axis));
let remaining = self.regions.first.get_mut(self.axis);
let limited = resolved.min(*remaining);
*remaining -= limited;
@@ -219,7 +223,7 @@ impl StackLayouter {
for item in self.items.drain(..) {
match item {
StackItem::Absolute(v) => cursor += v,
- StackItem::Fractional(v) => cursor += v.resolve(self.fr, remaining),
+ StackItem::Fractional(v) => cursor += v.share(self.fr, remaining),
StackItem::Frame(frame, align) => {
if self.dir.is_positive() {
ruler = ruler.max(align);
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 358c2204..a5f0b50c 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -186,15 +186,3 @@ castable! {
Expected: "content",
Value::Content(content) => content.pack(),
}
-
-castable! {
- Spec<Relative<Length>>,
- Expected: "array of two relative lengths",
- Value::Array(array) => {
- let mut iter = array.into_iter();
- match (iter.next(), iter.next(), iter.next()) {
- (Some(a), Some(b), None) => Spec::new(a.cast()?, b.cast()?),
- _ => Err("point array must contain exactly two entries")?,
- }
- },
-}
diff --git a/src/library/prelude.rs b/src/library/prelude.rs
index f052a43a..d74a5d85 100644
--- a/src/library/prelude.rs
+++ b/src/library/prelude.rs
@@ -10,8 +10,8 @@ pub use typst_macros::node;
pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult};
pub use crate::eval::{
Arg, Args, Array, Cast, Content, Dict, Fold, Func, Key, Layout, LayoutNode, Merge,
- Node, RawAlign, Regions, Resolve, Scope, Show, ShowNode, Smart, StyleChain, StyleMap,
- StyleVec, Value,
+ Node, RawAlign, RawLength, Regions, Resolve, Scope, Show, ShowNode, Smart,
+ StyleChain, StyleMap, StyleVec, Value,
};
pub use crate::frame::*;
pub use crate::geom::*;
diff --git a/src/library/structure/heading.rs b/src/library/structure/heading.rs
index 8b143865..dcf87f90 100644
--- a/src/library/structure/heading.rs
+++ b/src/library/structure/heading.rs
@@ -1,5 +1,5 @@
use crate::library::prelude::*;
-use crate::library::text::{FontFamily, FontSize, TextNode, Toggle};
+use crate::library::text::{FontFamily, TextNode, TextSize, Toggle};
/// A section heading.
#[derive(Debug, Hash)]
@@ -21,9 +21,9 @@ impl HeadingNode {
pub const FILL: Leveled<Smart<Paint>> = Leveled::Value(Smart::Auto);
/// The size of text in the heading.
#[property(referenced)]
- pub const SIZE: Leveled<FontSize> = Leveled::Mapping(|level| {
+ pub const SIZE: Leveled<TextSize> = Leveled::Mapping(|level| {
let upscale = (1.6 - 0.1 * level as f64).max(0.75);
- FontSize(Ratio::new(upscale).into())
+ TextSize(Em::new(upscale).into())
});
/// Whether text in the heading is strengthend.
#[property(referenced)]
@@ -36,10 +36,10 @@ impl HeadingNode {
pub const UNDERLINE: Leveled<bool> = Leveled::Value(false);
/// The extra padding above the heading.
#[property(referenced)]
- pub const ABOVE: Leveled<Length> = Leveled::Value(Length::zero());
+ pub const ABOVE: Leveled<RawLength> = Leveled::Value(Length::zero().into());
/// The extra padding below the heading.
#[property(referenced)]
- pub const BELOW: Leveled<Length> = Leveled::Value(Length::zero());
+ pub const BELOW: Leveled<RawLength> = Leveled::Value(Length::zero().into());
/// Whether the heading is block-level.
#[property(referenced)]
pub const BLOCK: Leveled<bool> = Leveled::Value(true);
@@ -95,14 +95,14 @@ impl Show for HeadingNode {
let above = resolve!(Self::ABOVE);
if !above.is_zero() {
- seq.push(Content::Vertical(above.into()));
+ seq.push(Content::Vertical(above.resolve(styles).into()));
}
seq.push(body);
let below = resolve!(Self::BELOW);
if !below.is_zero() {
- seq.push(Content::Vertical(below.into()));
+ seq.push(Content::Vertical(below.resolve(styles).into()));
}
let mut content = Content::sequence(seq).styled_with_map(map);
diff --git a/src/library/structure/list.rs b/src/library/structure/list.rs
index c58e8648..c3eae1af 100644
--- a/src/library/structure/list.rs
+++ b/src/library/structure/list.rs
@@ -1,6 +1,6 @@
use crate::library::layout::{GridNode, TrackSizing};
use crate::library::prelude::*;
-use crate::library::text::{ParNode, TextNode};
+use crate::library::text::ParNode;
use crate::library::utility::Numbering;
use crate::parse::Scanner;
@@ -34,15 +34,20 @@ impl<const L: ListKind> ListNode<L> {
#[property(referenced)]
pub const LABEL: Label = Label::Default;
/// The spacing between the list items of a non-wide list.
- pub const SPACING: Relative<Length> = Relative::zero();
+ #[property(resolve)]
+ pub const SPACING: RawLength = RawLength::zero();
/// The indentation of each item's label.
- pub const INDENT: Relative<Length> = Ratio::new(0.0).into();
+ #[property(resolve)]
+ pub const INDENT: RawLength = RawLength::zero();
/// The space between the label and the body of each item.
- pub const BODY_INDENT: Relative<Length> = Ratio::new(0.5).into();
+ #[property(resolve)]
+ pub const BODY_INDENT: RawLength = Em::new(0.5).into();
/// The extra padding above the list.
- pub const ABOVE: Length = Length::zero();
+ #[property(resolve)]
+ pub const ABOVE: RawLength = RawLength::zero();
/// The extra padding below the list.
- pub const BELOW: Length = Length::zero();
+ #[property(resolve)]
+ pub const BELOW: RawLength = RawLength::zero();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::show(Self {
@@ -77,7 +82,6 @@ impl<const L: ListKind> Show for ListNode<L> {
number += 1;
}
- let em = styles.get(TextNode::SIZE);
let leading = styles.get(ParNode::LEADING);
let spacing = if self.wide {
styles.get(ParNode::SPACING)
@@ -85,9 +89,9 @@ impl<const L: ListKind> Show for ListNode<L> {
styles.get(Self::SPACING)
};
- let gutter = (leading + spacing).resolve(em);
- let indent = styles.get(Self::INDENT).resolve(em);
- let body_indent = styles.get(Self::BODY_INDENT).resolve(em);
+ let gutter = leading + spacing;
+ let indent = styles.get(Self::INDENT);
+ let body_indent = styles.get(Self::BODY_INDENT);
Content::block(GridNode {
tracks: Spec::with_x(vec![
diff --git a/src/library/structure/table.rs b/src/library/structure/table.rs
index e01ae908..d0ab0716 100644
--- a/src/library/structure/table.rs
+++ b/src/library/structure/table.rs
@@ -21,9 +21,10 @@ impl TableNode {
/// How to stroke the cells.
pub const STROKE: Option<Paint> = Some(Color::BLACK.into());
/// The stroke's thickness.
- pub const THICKNESS: Length = Length::pt(1.0);
+ #[property(resolve)]
+ pub const THICKNESS: RawLength = Length::pt(1.0).into();
/// How much to pad the cells's content.
- pub const PADDING: Relative<Length> = Length::pt(5.0).into();
+ pub const PADDING: Relative<RawLength> = Length::pt(5.0).into();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
let columns = args.named("columns")?.unwrap_or_default();
diff --git a/src/library/text/deco.rs b/src/library/text/deco.rs
index da1a1141..f5ed4744 100644
--- a/src/library/text/deco.rs
+++ b/src/library/text/deco.rs
@@ -23,16 +23,16 @@ impl<const L: DecoLine> DecoNode<L> {
/// Stroke color of the line, defaults to the text color if `None`.
#[property(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`.
- #[property(shorthand)]
- pub const THICKNESS: Option<Relative<Length>> = 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<Relative<Length>> = None;
- /// Amount that the line will be longer or shorter than its associated text
- /// (dependent on scaled font size).
- pub const EXTENT: Relative<Length> = Relative::zero();
+ /// Thickness of the line's strokes, read from the font tables if `auto`.
+ #[property(shorthand, resolve)]
+ pub const THICKNESS: Smart<RawLength> = Smart::Auto;
+ /// Position of the line relative to the baseline, read from the font tables
+ /// if `auto`.
+ #[property(resolve)]
+ pub const OFFSET: Smart<RawLength> = Smart::Auto;
+ /// Amount that the line will be longer or shorter than its associated text.
+ #[property(resolve)]
+ pub const EXTENT: RawLength = RawLength::zero();
/// Whether the line skips sections in which it would collide
/// with the glyphs. Does not apply to strikethrough.
pub const EVADE: bool = true;
@@ -66,9 +66,9 @@ impl<const L: DecoLine> Show for DecoNode<L> {
pub struct Decoration {
pub line: DecoLine,
pub stroke: Option<Paint>,
- pub thickness: Option<Relative<Length>>,
- pub offset: Option<Relative<Length>>,
- pub extent: Relative<Length>,
+ pub thickness: Smart<Length>,
+ pub offset: Smart<Length>,
+ pub extent: Length,
pub evade: bool,
}
@@ -102,25 +102,18 @@ pub fn decorate(
};
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 offset = deco.offset.unwrap_or(-metrics.position.at(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)),
+ thickness: deco.thickness.unwrap_or(metrics.thickness.at(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 start = pos.x - deco.extent;
+ let end = pos.x + (width + 2.0 * deco.extent);
let mut push_segment = |from: Length, to: Length| {
let origin = Point::new(from, pos.y + offset);
@@ -146,20 +139,20 @@ pub fn decorate(
let mut intersections = vec![];
for glyph in text.glyphs.iter() {
- let dx = glyph.x_offset.resolve(text.size) + x;
+ let dx = glyph.x_offset.at(text.size) + x;
let mut builder =
BezPathBuilder::new(face_metrics.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);
+ x += glyph.x_advance.at(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);
+ let y_min = -face.to_em(bbox.y_max).at(text.size);
+ let y_max = -face.to_em(bbox.y_min).at(text.size);
offset >= y_min && offset <= y_max
}) {
@@ -225,7 +218,7 @@ impl BezPathBuilder {
}
fn s(&self, v: f32) -> f64 {
- Em::from_units(v, self.units_per_em).resolve(self.font_size).to_raw()
+ Em::from_units(v, self.units_per_em).at(self.font_size).to_raw()
}
}
diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs
index 4a139fb3..b5ccc636 100644
--- a/src/library/text/mod.rs
+++ b/src/library/text/mod.rs
@@ -16,7 +16,9 @@ use std::borrow::Cow;
use ttf_parser::Tag;
-use crate::font::{Face, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
+use crate::font::{
+ Face, FaceMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric,
+};
use crate::library::prelude::*;
use crate::util::EcoString;
@@ -39,23 +41,25 @@ impl TextNode {
pub const WEIGHT: FontWeight = FontWeight::REGULAR;
/// The width of the glyphs.
pub const STRETCH: FontStretch = FontStretch::NORMAL;
+
/// The size of the glyphs.
#[property(shorthand, fold)]
- pub const SIZE: FontSize = Length::pt(11.0);
+ pub const SIZE: TextSize = Length::pt(11.0);
/// The glyph fill color.
#[property(shorthand)]
pub const FILL: Paint = Color::BLACK.into();
-
/// The amount of space that should be added between characters.
- pub const TRACKING: Em = Em::zero();
- /// The ratio by which spaces should be stretched.
- pub const SPACING: Ratio = Ratio::one();
+ #[property(resolve)]
+ pub const TRACKING: RawLength = RawLength::zero();
+ /// The width of spaces relative to the default space width.
+ #[property(resolve)]
+ pub const SPACING: Relative<RawLength> = Relative::one();
/// Whether glyphs can hang over into the margin.
pub const OVERHANG: bool = true;
/// The top end of the text bounding box.
- pub const TOP_EDGE: VerticalFontMetric = VerticalFontMetric::CapHeight;
+ pub const TOP_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::CapHeight);
/// The bottom end of the text bounding box.
- pub const BOTTOM_EDGE: VerticalFontMetric = VerticalFontMetric::Baseline;
+ pub const BOTTOM_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::Baseline);
/// Whether to apply kerning ("kern").
pub const KERNING: bool = true;
@@ -188,44 +192,53 @@ castable! {
/// The size of text.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct FontSize(pub Relative<Length>);
+pub struct TextSize(pub RawLength);
-impl Fold for FontSize {
+impl Fold for TextSize {
type Output = Length;
fn fold(self, outer: Self::Output) -> Self::Output {
- self.0.rel.resolve(outer) + self.0.abs
+ self.0.em.at(outer) + self.0.length
}
}
castable! {
- FontSize,
- Expected: "relative length",
- Value::Length(v) => Self(v.into()),
- Value::Ratio(v) => Self(v.into()),
- Value::Relative(v) => Self(v),
+ TextSize,
+ Expected: "length",
+ Value::Length(v) => Self(v),
}
-castable! {
- Em,
- Expected: "float",
- Value::Float(v) => Self::new(v),
+/// Specifies the bottom or top edge of text.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum TextEdge {
+ /// An edge specified using one of the well-known font metrics.
+ Metric(VerticalFontMetric),
+ /// An edge specified as a length.
+ Length(RawLength),
+}
+
+impl TextEdge {
+ /// Resolve the value of the text edge given a font face.
+ pub fn resolve(self, styles: StyleChain, metrics: &FaceMetrics) -> Length {
+ match self {
+ Self::Metric(metric) => metrics.vertical(metric).resolve(styles),
+ Self::Length(length) => length.resolve(styles),
+ }
+ }
}
castable! {
- VerticalFontMetric,
- Expected: "string or relative length",
- Value::Length(v) => Self::Relative(v.into()),
- Value::Ratio(v) => Self::Relative(v.into()),
- Value::Relative(v) => Self::Relative(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,
+ TextEdge,
+ Expected: "string or length",
+ Value::Length(v) => Self::Length(v),
+ Value::Str(string) => Self::Metric(match string.as_str() {
+ "ascender" => VerticalFontMetric::Ascender,
+ "cap-height" => VerticalFontMetric::CapHeight,
+ "x-height" => VerticalFontMetric::XHeight,
+ "baseline" => VerticalFontMetric::Baseline,
+ "descender" => VerticalFontMetric::Descender,
_ => Err("unknown font metric")?,
- },
+ }),
}
/// A stylistic set in a font face.
diff --git a/src/library/text/par.rs b/src/library/text/par.rs
index dc7c9dcf..57e2b45d 100644
--- a/src/library/text/par.rs
+++ b/src/library/text/par.rs
@@ -42,12 +42,15 @@ impl ParNode {
/// Whether to hyphenate text to improve line breaking. When `auto`, words
/// will will be hyphenated if and only if justification is enabled.
pub const HYPHENATE: Smart<bool> = Smart::Auto;
- /// The spacing between lines (dependent on scaled font size).
- pub const LEADING: Relative<Length> = Ratio::new(0.65).into();
- /// The extra spacing between paragraphs (dependent on scaled font size).
- pub const SPACING: Relative<Length> = Ratio::new(0.55).into();
+ /// The spacing between lines.
+ #[property(resolve)]
+ pub const LEADING: RawLength = Em::new(0.65).into();
+ /// The extra spacing between paragraphs.
+ #[property(resolve)]
+ pub const SPACING: RawLength = Em::new(0.55).into();
/// The indent the first line of a consecutive paragraph should have.
- pub const INDENT: Relative<Length> = Relative::zero();
+ #[property(resolve)]
+ pub const INDENT: RawLength = RawLength::zero();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
// The paragraph constructor is special: It doesn't create a paragraph
@@ -370,7 +373,7 @@ fn prepare<'a>(
}
ParChild::Spacing(spacing) => match *spacing {
Spacing::Relative(v) => {
- let resolved = v.resolve(regions.base.x);
+ let resolved = v.resolve(styles).relative_to(regions.base.x);
items.push(ParItem::Absolute(resolved));
ranges.push(range);
}
@@ -772,8 +775,7 @@ fn stack(
regions: &Regions,
styles: StyleChain,
) -> Vec<Arc<Frame>> {
- let em = styles.get(TextNode::SIZE);
- let leading = styles.get(ParNode::LEADING).resolve(em);
+ let leading = styles.get(ParNode::LEADING);
let align = styles.get(ParNode::ALIGN);
let justify = styles.get(ParNode::JUSTIFY);
@@ -837,7 +839,7 @@ fn commit(
if text.styles.get(TextNode::OVERHANG) {
let start = text.dir.is_positive();
let em = text.styles.get(TextNode::SIZE);
- let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em);
+ let amount = overhang(glyph.c, start) * glyph.x_advance.at(em);
offset -= amount;
remaining += amount;
}
@@ -852,7 +854,7 @@ fn commit(
{
let start = !text.dir.is_positive();
let em = text.styles.get(TextNode::SIZE);
- let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em);
+ let amount = overhang(glyph.c, start) * glyph.x_advance.at(em);
remaining += amount;
}
}
@@ -887,7 +889,7 @@ fn commit(
match item {
ParItem::Absolute(v) => offset += *v,
- ParItem::Fractional(v) => offset += v.resolve(line.fr, remaining),
+ ParItem::Fractional(v) => offset += v.share(line.fr, remaining),
ParItem::Text(shaped) => position(shaped.build(fonts, justification)),
ParItem::Frame(frame) => position(frame.clone()),
}
diff --git a/src/library/text/shaping.rs b/src/library/text/shaping.rs
index d398e56d..32177f0a 100644
--- a/src/library/text/shaping.rs
+++ b/src/library/text/shaping.rs
@@ -132,7 +132,7 @@ impl<'a> ShapedText<'a> {
.filter(|g| g.is_justifiable())
.map(|g| g.x_advance)
.sum::<Em>()
- .resolve(self.styles.get(TextNode::SIZE))
+ .at(self.styles.get(TextNode::SIZE))
}
/// Reshape a range of the shaped text, reusing information from this
@@ -168,7 +168,7 @@ impl<'a> ShapedText<'a> {
let glyph_id = ttf.glyph_index('-')?;
let x_advance = face.to_em(ttf.glyph_hor_advance(glyph_id)?);
let cluster = self.glyphs.last().map(|g| g.cluster).unwrap_or_default();
- self.size.x += x_advance.resolve(size);
+ self.size.x += x_advance.at(size);
self.glyphs.to_mut().push(ShapedGlyph {
face_id,
glyph_id: glyph_id.0,
@@ -443,8 +443,10 @@ fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, face_id: FaceI
/// Apply tracking and spacing to a slice of shaped glyphs.
fn track_and_space(ctx: &mut ShapingContext) {
- let tracking = ctx.styles.get(TextNode::TRACKING);
- let spacing = ctx.styles.get(TextNode::SPACING);
+ let em = ctx.styles.get(TextNode::SIZE);
+ let tracking = Em::from_length(ctx.styles.get(TextNode::TRACKING), em);
+ let spacing = ctx.styles.get(TextNode::SPACING).map(|abs| Em::from_length(abs, em));
+
if tracking.is_zero() && spacing.is_one() {
return;
}
@@ -452,7 +454,7 @@ fn track_and_space(ctx: &mut ShapingContext) {
let mut glyphs = ctx.glyphs.iter_mut().peekable();
while let Some(glyph) = glyphs.next() {
if glyph.is_space() {
- glyph.x_advance *= spacing.get();
+ glyph.x_advance = spacing.relative_to(glyph.x_advance);
}
if glyphs.peek().map_or(false, |next| glyph.cluster != next.cluster) {
@@ -479,8 +481,8 @@ fn measure(
// Expand top and bottom by reading the face's vertical metrics.
let mut expand = |face: &Face| {
let metrics = face.metrics();
- top.set_max(metrics.vertical(top_edge, size));
- bottom.set_max(-metrics.vertical(bottom_edge, size));
+ top.set_max(top_edge.resolve(styles, metrics));
+ bottom.set_max(-bottom_edge.resolve(styles, metrics));
};
if glyphs.is_empty() {
@@ -499,7 +501,7 @@ fn measure(
expand(face);
for glyph in group {
- width += glyph.x_advance.resolve(size);
+ width += glyph.x_advance.at(size);
}
}
}
diff --git a/src/library/utility/math.rs b/src/library/utility/math.rs
index 272ececa..63ec5e55 100644
--- a/src/library/utility/math.rs
+++ b/src/library/utility/math.rs
@@ -37,12 +37,11 @@ pub fn abs(_: &mut Context, args: &mut Args) -> TypResult<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::Ratio(v) => Value::Ratio(v.abs()),
Value::Fraction(v) => Value::Fraction(v.abs()),
- Value::Relative(_) => {
- bail!(span, "cannot take absolute value of a relative length")
+ Value::Length(_) | Value::Relative(_) => {
+ bail!(span, "cannot take absolute value of a length")
}
v => bail!(span, "expected numeric value, found {}", v.type_name()),
})