summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-01-05 14:49:14 +0100
committerLaurenz <laurmaedje@gmail.com>2022-01-05 15:06:42 +0100
commitf7e8624b4cf31744d600167dd7f3a9d9d1626014 (patch)
tree0941c8db497befc47a666d3d1384db4ab9fc5133 /src
parent4c81a5d43eabd959dbb500a8076f99f21bd037bd (diff)
Refactor
Diffstat (limited to 'src')
-rw-r--r--src/eval/class.rs2
-rw-r--r--src/eval/styles.rs52
-rw-r--r--src/geom/spec.rs17
-rw-r--r--src/library/align.rs56
-rw-r--r--src/library/columns.rs2
-rw-r--r--src/library/heading.rs18
-rw-r--r--src/library/image.rs19
-rw-r--r--src/library/link.rs21
-rw-r--r--src/library/list.rs35
-rw-r--r--src/library/page.rs50
-rw-r--r--src/library/placed.rs2
-rw-r--r--src/library/shape.rs9
-rw-r--r--src/library/text.rs5
13 files changed, 149 insertions, 139 deletions
diff --git a/src/eval/class.rs b/src/eval/class.rs
index 15e1f249..5682cb4d 100644
--- a/src/eval/class.rs
+++ b/src/eval/class.rs
@@ -84,7 +84,7 @@ impl Class {
impl Debug for Class {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("<class ")?;
- f.write_str(&self.0.name)?;
+ f.write_str(self.name())?;
f.write_char('>')
}
}
diff --git a/src/eval/styles.rs b/src/eval/styles.rs
index f8413feb..d7c17b92 100644
--- a/src/eval/styles.rs
+++ b/src/eval/styles.rs
@@ -70,8 +70,8 @@ impl StyleMap {
/// `outer`. The ones from `self` take precedence over the ones from
/// `outer`. For folded properties `self` contributes the inner value.
pub fn chain<'a>(&'a self, outer: &'a StyleChain<'a>) -> StyleChain<'a> {
+ // No need to chain an empty map.
if self.is_empty() {
- // No need to chain an empty map.
*outer
} else {
StyleChain { inner: self, outer: Some(outer) }
@@ -86,12 +86,12 @@ impl StyleMap {
/// style maps, whereas `chain` would be used during layouting to combine
/// immutable style maps from different levels of the hierarchy.
pub fn apply(&mut self, outer: &Self) {
- 'outer: for outer in &outer.0 {
- for inner in &mut self.0 {
- if inner.style_id() == outer.style_id() {
- inner.fold(outer);
- continue 'outer;
- }
+ for outer in &outer.0 {
+ if let Some(inner) =
+ self.0.iter_mut().find(|inner| inner.style_id() == outer.style_id())
+ {
+ *inner = inner.fold(outer);
+ continue;
}
self.0.push(outer.clone());
@@ -158,16 +158,16 @@ impl<'a> StyleChain<'a> {
/// Get the (folded) value of a copyable style property.
///
+ /// This is the method you should reach for first. If it doesn't work
+ /// because your property is not copyable, use `get_ref`. If that doesn't
+ /// work either because your property needs folding, use `get_cloned`.
+ ///
/// Returns the property's default value if no map in the chain contains an
/// entry for it.
- pub fn get<P>(self, key: P) -> P::Value
+ pub fn get<P: Property>(self, key: P) -> P::Value
where
- P: Property,
P::Value: Copy,
{
- // This exists separately to `get_cloned` for `Copy` types so that
- // people don't just naively use `get` / `get_cloned` where they should
- // use `get_ref`.
self.get_cloned(key)
}
@@ -177,13 +177,13 @@ impl<'a> StyleChain<'a> {
/// Prefer `get` if possible or resort to `get_cloned` for non-`Copy`
/// properties that need folding.
///
- /// Returns a reference to the property's default value if no map in the
- /// chain contains an entry for it.
- pub fn get_ref<P>(self, key: P) -> &'a P::Value
+ /// Returns a lazily-initialized reference to the property's default value
+ /// if no map in the chain contains an entry for it.
+ pub fn get_ref<P: Property>(self, key: P) -> &'a P::Value
where
- P: Property + Nonfolding,
+ P: Nonfolding,
{
- if let Some(value) = self.get_locally(key) {
+ if let Some(value) = self.find(key) {
value
} else if let Some(outer) = self.outer {
outer.get_ref(key)
@@ -197,11 +197,11 @@ impl<'a> StyleChain<'a> {
/// While this works for all properties, you should prefer `get` or
/// `get_ref` where possible. This is only needed for non-`Copy` properties
/// that need folding.
- pub fn get_cloned<P>(self, key: P) -> P::Value
- where
- P: Property,
- {
- if let Some(value) = self.get_locally(key).cloned() {
+ ///
+ /// Returns the property's default value if no map in the chain contains an
+ /// entry for it.
+ pub fn get_cloned<P: Property>(self, key: P) -> P::Value {
+ if let Some(value) = self.find(key).cloned() {
if P::FOLDABLE {
if let Some(outer) = self.outer {
P::fold(value, outer.get_cloned(key))
@@ -218,8 +218,8 @@ impl<'a> StyleChain<'a> {
}
}
- /// Find a property directly in the most local map.
- fn get_locally<P: Property>(&self, _: P) -> Option<&'a P::Value> {
+ /// Find a property directly in the localmost map.
+ fn find<P: Property>(self, _: P) -> Option<&'a P::Value> {
self.inner
.0
.iter()
@@ -309,8 +309,8 @@ impl Entry {
self.0.as_any().downcast_ref()
}
- fn fold(&mut self, outer: &Self) {
- *self = self.0.fold(outer);
+ fn fold(&self, outer: &Self) -> Self {
+ self.0.fold(outer)
}
}
diff --git a/src/geom/spec.rs b/src/geom/spec.rs
index 40d7386b..cf75f42d 100644
--- a/src/geom/spec.rs
+++ b/src/geom/spec.rs
@@ -85,10 +85,19 @@ impl<T> Spec<T> {
}
}
-impl<T> Spec<T>
-where
- T: Ord,
-{
+impl<T: Default> Spec<T> {
+ /// Create a new instance with y set to its default value.
+ pub fn with_x(x: T) -> Self {
+ Self { x, y: T::default() }
+ }
+
+ /// Create a new instance with x set to its default value.
+ pub fn with_y(y: T) -> Self {
+ Self { x: T::default(), y }
+ }
+}
+
+impl<T: Ord> Spec<T> {
/// The component-wise minimum of this and another instance.
pub fn min(self, other: Self) -> Self {
Self {
diff --git a/src/library/align.rs b/src/library/align.rs
index 32735244..e8dfabb1 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -7,33 +7,7 @@ use super::ParNode;
pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let aligns: Spec<_> = args.find().unwrap_or_default();
let body: PackedNode = args.expect("body")?;
-
- let mut styles = StyleMap::new();
- if let Some(align) = aligns.x {
- styles.set(ParNode::ALIGN, align);
- }
-
- Ok(Value::block(body.styled(styles).aligned(aligns)))
-}
-
-dynamic! {
- Align: "alignment",
-}
-
-dynamic! {
- Spec<Align>: "2d alignment",
-}
-
-castable! {
- Spec<Option<Align>>,
- Expected: "1d or 2d alignment",
- @align: Align => {
- let mut aligns = Spec::default();
- aligns.set(align.axis(), Some(*align));
- aligns
- },
- @aligns: Spec<Align> => aligns.map(Some),
-
+ Ok(Value::block(body.aligned(aligns)))
}
/// A node that aligns its child.
@@ -56,8 +30,14 @@ impl Layout for AlignNode {
let mut pod = regions.clone();
pod.expand &= self.aligns.map_is_none();
+ // Align paragraphs inside the child.
+ let mut passed = StyleMap::new();
+ if let Some(align) = self.aligns.x {
+ passed.set(ParNode::ALIGN, align);
+ }
+
// Layout the child.
- let mut frames = self.child.layout(ctx, &pod, styles);
+ let mut frames = self.child.layout(ctx, &pod, passed.chain(&styles));
for ((current, base), Constrained { item: frame, cts }) in
regions.iter().zip(&mut frames)
@@ -78,3 +58,23 @@ impl Layout for AlignNode {
frames
}
}
+
+dynamic! {
+ Align: "alignment",
+}
+
+dynamic! {
+ Spec<Align>: "2d alignment",
+}
+
+castable! {
+ Spec<Option<Align>>,
+ Expected: "1d or 2d alignment",
+ @align: Align => {
+ let mut aligns = Spec::default();
+ aligns.set(align.axis(), Some(*align));
+ aligns
+ },
+ @aligns: Spec<Align> => aligns.map(Some),
+
+}
diff --git a/src/library/columns.rs b/src/library/columns.rs
index 17ae6058..ce02b508 100644
--- a/src/library/columns.rs
+++ b/src/library/columns.rs
@@ -98,7 +98,7 @@ impl Layout for ColumnsNode {
// case, the frame is first created with zero height and then
// resized.
let height = if regions.expand.y { current.y } else { Length::zero() };
- let mut output = Frame::new(Spec::new(regions.current.x, height));
+ let mut output = Frame::new(Size::new(regions.current.x, height));
let mut cursor = Length::zero();
for _ in 0 .. columns {
diff --git a/src/library/heading.rs b/src/library/heading.rs
index 111472f4..3591ea0c 100644
--- a/src/library/heading.rs
+++ b/src/library/heading.rs
@@ -6,18 +6,18 @@ use super::{FontFamily, TextNode};
/// A section heading.
#[derive(Debug, Hash)]
pub struct HeadingNode {
- /// The node that produces the heading's contents.
- pub child: PackedNode,
/// The logical nesting depth of the section, starting from one. In the
/// default style, this controls the text size of the heading.
pub level: usize,
+ /// The node that produces the heading's contents.
+ pub child: PackedNode,
}
#[properties]
impl HeadingNode {
/// The heading's font family.
pub const FAMILY: Smart<FontFamily> = Smart::Auto;
- /// The fill color of heading in the text. Just the surrounding text color
+ /// The fill color of text in the heading. Just the surrounding text color
/// if `auto`.
pub const FILL: Smart<Paint> = Smart::Auto;
}
@@ -48,12 +48,12 @@ impl Layout for HeadingNode {
) -> Vec<Constrained<Rc<Frame>>> {
let upscale = (1.6 - 0.1 * self.level as f64).max(0.75);
- let mut local = StyleMap::new();
- local.set(TextNode::STRONG, true);
- local.set(TextNode::SIZE, Relative::new(upscale).into());
+ let mut passed = StyleMap::new();
+ passed.set(TextNode::STRONG, true);
+ passed.set(TextNode::SIZE, Relative::new(upscale).into());
if let Smart::Custom(family) = styles.get_ref(Self::FAMILY) {
- local.set(
+ passed.set(
TextNode::FAMILY_LIST,
std::iter::once(family)
.chain(styles.get_ref(TextNode::FAMILY_LIST))
@@ -63,9 +63,9 @@ impl Layout for HeadingNode {
}
if let Smart::Custom(fill) = styles.get(Self::FILL) {
- local.set(TextNode::FILL, fill);
+ passed.set(TextNode::FILL, fill);
}
- self.child.layout(ctx, regions, local.chain(&styles))
+ self.child.layout(ctx, regions, passed.chain(&styles))
}
}
diff --git a/src/library/image.rs b/src/library/image.rs
index 2b91b2fc..c5cb9aeb 100644
--- a/src/library/image.rs
+++ b/src/library/image.rs
@@ -3,18 +3,13 @@
use std::io;
use super::prelude::*;
-use super::LinkNode;
use crate::diag::Error;
use crate::image::ImageId;
/// `image`: An image.
pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
- let path = args.expect::<Spanned<EcoString>>("path to image file")?;
- let width = args.named("width")?;
- let height = args.named("height")?;
- let fit = args.named("fit")?.unwrap_or_default();
-
// Load the image.
+ let path = args.expect::<Spanned<EcoString>>("path to image file")?;
let full = ctx.make_path(&path.v);
let id = ctx.images.load(&full).map_err(|err| {
Error::boxed(path.span, match err.kind() {
@@ -23,6 +18,10 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
})
})?;
+ let width = args.named("width")?;
+ let height = args.named("height")?;
+ let fit = args.named("fit")?.unwrap_or_default();
+
Ok(Value::inline(
ImageNode { id, fit }.pack().sized(Spec::new(width, height)),
))
@@ -37,6 +36,12 @@ pub struct ImageNode {
pub fit: ImageFit,
}
+#[properties]
+impl ImageNode {
+ /// An URL the image should link to.
+ pub const LINK: Option<String> = None;
+}
+
impl Layout for ImageNode {
fn layout(
&self,
@@ -90,7 +95,7 @@ impl Layout for ImageNode {
}
// Apply link if it exists.
- if let Some(url) = styles.get_ref(LinkNode::URL) {
+ if let Some(url) = styles.get_ref(Self::LINK) {
frame.link(url);
}
diff --git a/src/library/link.rs b/src/library/link.rs
index d1d7842d..1050963c 100644
--- a/src/library/link.rs
+++ b/src/library/link.rs
@@ -1,9 +1,10 @@
//! Hyperlinking.
use super::prelude::*;
+use super::{ImageNode, ShapeNode, TextNode};
use crate::util::EcoString;
-/// `link`: Link text or other elements.
+/// `link`: Link text and other elements to an URL.
pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let url: String = args.expect::<EcoString>("url")?.into();
let body = args.find().unwrap_or_else(|| {
@@ -14,17 +15,9 @@ pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
Node::Text(text.into())
});
- Ok(Value::Node(
- body.styled(StyleMap::with(LinkNode::URL, Some(url))),
- ))
-}
-
-/// Host for link styles.
-#[derive(Debug, Hash)]
-pub struct LinkNode;
-
-#[properties]
-impl LinkNode {
- /// An URL to link to.
- pub const URL: Option<String> = None;
+ let mut passed = StyleMap::new();
+ passed.set(TextNode::LINK, Some(url.clone()));
+ passed.set(ImageNode::LINK, Some(url.clone()));
+ passed.set(ShapeNode::LINK, Some(url));
+ Ok(Value::Node(body.styled(passed)))
}
diff --git a/src/library/list.rs b/src/library/list.rs
index a9dfbec5..bbdc400a 100644
--- a/src/library/list.rs
+++ b/src/library/list.rs
@@ -50,26 +50,23 @@ impl<L: Labelling> Layout for ListNode<L> {
let label_indent = styles.get(Self::LABEL_INDENT).resolve(em);
let body_indent = styles.get(Self::BODY_INDENT).resolve(em);
- let columns = vec![
- TrackSizing::Linear(label_indent.into()),
- TrackSizing::Auto,
- TrackSizing::Linear(body_indent.into()),
- TrackSizing::Auto,
- ];
-
- let children = vec![
- PackedNode::default(),
- Node::Text(self.labelling.label()).into_block(),
- PackedNode::default(),
- self.child.clone(),
- ];
-
- GridNode {
- tracks: Spec::new(columns, vec![]),
+ let grid = GridNode {
+ tracks: Spec::with_x(vec![
+ TrackSizing::Linear(label_indent.into()),
+ TrackSizing::Auto,
+ TrackSizing::Linear(body_indent.into()),
+ TrackSizing::Auto,
+ ]),
gutter: Spec::default(),
- children,
- }
- .layout(ctx, regions, styles)
+ children: vec![
+ PackedNode::default(),
+ Node::Text(self.labelling.label()).into_block(),
+ PackedNode::default(),
+ self.child.clone(),
+ ],
+ };
+
+ grid.layout(ctx, regions, styles)
}
}
diff --git a/src/library/page.rs b/src/library/page.rs
index 69be2bb7..fa9345f5 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -166,31 +166,6 @@ pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
Ok(Value::Node(Node::Pagebreak))
}
-/// Defines default margins for a class of related papers.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum PaperClass {
- Custom,
- Base,
- US,
- Newspaper,
- Book,
-}
-
-impl PaperClass {
- /// The default margins for this page class.
- fn default_margins(self) -> Sides<Linear> {
- let f = |r| Relative::new(r).into();
- let s = |l, t, r, b| Sides::new(f(l), f(t), f(r), f(b));
- match self {
- Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842),
- Self::Base => s(0.1190, 0.0842, 0.1190, 0.0842),
- Self::US => s(0.1760, 0.1092, 0.1760, 0.0910),
- Self::Newspaper => s(0.0455, 0.0587, 0.0455, 0.0294),
- Self::Book => s(0.1200, 0.0852, 0.1500, 0.0965),
- }
- }
-}
-
/// Specification of a paper.
#[derive(Debug, Copy, Clone)]
pub struct Paper {
@@ -413,6 +388,31 @@ castable! {
Value::Str(string) => Paper::from_str(&string).map_err(|e| e.to_string())?,
}
+/// Defines default margins for a class of related papers.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum PaperClass {
+ Custom,
+ Base,
+ US,
+ Newspaper,
+ Book,
+}
+
+impl PaperClass {
+ /// The default margins for this page class.
+ fn default_margins(self) -> Sides<Linear> {
+ let f = |r| Relative::new(r).into();
+ let s = |l, t, r, b| Sides::new(f(l), f(t), f(r), f(b));
+ match self {
+ Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842),
+ Self::Base => s(0.1190, 0.0842, 0.1190, 0.0842),
+ Self::US => s(0.1760, 0.1092, 0.1760, 0.0910),
+ Self::Newspaper => s(0.0455, 0.0587, 0.0455, 0.0294),
+ Self::Book => s(0.1200, 0.0852, 0.1500, 0.0965),
+ }
+ }
+}
+
/// The error when parsing a [`Paper`] from a string fails.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct PaperError;
diff --git a/src/library/placed.rs b/src/library/placed.rs
index 8a4f46af..e7b17325 100644
--- a/src/library/placed.rs
+++ b/src/library/placed.rs
@@ -5,7 +5,7 @@ use super::AlignNode;
/// `place`: Place content at an absolute position.
pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
- let aligns = args.find().unwrap_or(Spec::new(Some(Align::Left), None));
+ let aligns = args.find().unwrap_or(Spec::with_x(Some(Align::Left)));
let tx = args.named("dx")?.unwrap_or_default();
let ty = args.named("dy")?.unwrap_or_default();
let body: PackedNode = args.expect("body")?;
diff --git a/src/library/shape.rs b/src/library/shape.rs
index 76c03edb..c47885d2 100644
--- a/src/library/shape.rs
+++ b/src/library/shape.rs
@@ -3,7 +3,6 @@
use std::f64::consts::SQRT_2;
use super::prelude::*;
-use super::LinkNode;
/// `rect`: A rectangle with optional content.
pub fn rect(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
@@ -101,6 +100,12 @@ pub struct ShapeNode {
pub child: Option<PackedNode>,
}
+#[properties]
+impl ShapeNode {
+ /// An URL the shape should link to.
+ pub const LINK: Option<String> = None;
+}
+
impl Layout for ShapeNode {
fn layout(
&self,
@@ -170,7 +175,7 @@ impl Layout for ShapeNode {
}
// Apply link if it exists.
- if let Some(url) = styles.get_ref(LinkNode::URL) {
+ if let Some(url) = styles.get_ref(Self::LINK) {
frame.link(url);
}
diff --git a/src/library/text.rs b/src/library/text.rs
index 5fa19cb9..486cb77f 100644
--- a/src/library/text.rs
+++ b/src/library/text.rs
@@ -9,7 +9,6 @@ use rustybuzz::{Feature, UnicodeBuffer};
use ttf_parser::Tag;
use super::prelude::*;
-use super::LinkNode;
use crate::font::{
Face, FaceId, FontStore, FontStretch, FontStyle, FontVariant, FontWeight,
VerticalFontMetric,
@@ -59,6 +58,8 @@ impl TextNode {
/// Decorative lines.
#[fold(|a, b| a.into_iter().chain(b).collect())]
pub const LINES: Vec<LineDecoration> = vec![];
+ /// An URL the text should link to.
+ pub const LINK: Option<String> = None;
/// The size of the glyphs.
#[fold(Linear::compose)]
@@ -889,7 +890,7 @@ impl<'a> ShapedText<'a> {
}
// Apply link if it exists.
- if let Some(url) = self.styles.get_ref(LinkNode::URL) {
+ if let Some(url) = self.styles.get_ref(TextNode::LINK) {
frame.link(url);
}