diff options
Diffstat (limited to 'src/library')
| -rw-r--r-- | src/library/graphics/image.rs | 2 | ||||
| -rw-r--r-- | src/library/graphics/line.rs | 43 | ||||
| -rw-r--r-- | src/library/graphics/shape.rs | 9 | ||||
| -rw-r--r-- | src/library/graphics/transform.rs | 55 | ||||
| -rw-r--r-- | src/library/layout/columns.rs | 5 | ||||
| -rw-r--r-- | src/library/layout/flow.rs | 20 | ||||
| -rw-r--r-- | src/library/layout/grid.rs | 16 | ||||
| -rw-r--r-- | src/library/layout/pad.rs | 11 | ||||
| -rw-r--r-- | src/library/layout/page.rs | 24 | ||||
| -rw-r--r-- | src/library/layout/place.rs | 6 | ||||
| -rw-r--r-- | src/library/layout/spacing.rs | 2 | ||||
| -rw-r--r-- | src/library/layout/stack.rs | 16 | ||||
| -rw-r--r-- | src/library/mod.rs | 12 | ||||
| -rw-r--r-- | src/library/prelude.rs | 4 | ||||
| -rw-r--r-- | src/library/structure/heading.rs | 14 | ||||
| -rw-r--r-- | src/library/structure/list.rs | 24 | ||||
| -rw-r--r-- | src/library/structure/table.rs | 5 | ||||
| -rw-r--r-- | src/library/text/deco.rs | 51 | ||||
| -rw-r--r-- | src/library/text/mod.rs | 77 | ||||
| -rw-r--r-- | src/library/text/par.rs | 24 | ||||
| -rw-r--r-- | src/library/text/shaping.rs | 18 | ||||
| -rw-r--r-- | src/library/utility/math.rs | 5 |
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, ®ions, 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()), }) |
