summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/geom/length.rs8
-rw-r--r--src/geom/scalar.rs14
-rw-r--r--src/library/mod.rs1
-rw-r--r--src/library/text/mod.rs2
-rw-r--r--src/library/text/par.rs83
-rw-r--r--src/library/text/repeat.rs24
-rw-r--r--tests/ref/text/repeat.pngbin0 -> 6637 bytes
-rw-r--r--tests/typ/text/repeat.typ15
8 files changed, 120 insertions, 27 deletions
diff --git a/src/geom/length.rs b/src/geom/length.rs
index 838d33c0..96888764 100644
--- a/src/geom/length.rs
+++ b/src/geom/length.rs
@@ -193,6 +193,14 @@ assign_impl!(Length -= Length);
assign_impl!(Length *= f64);
assign_impl!(Length /= f64);
+impl Rem for Length {
+ type Output = Self;
+
+ fn rem(self, other: Self) -> Self::Output {
+ Self(self.0 % other.0)
+ }
+}
+
impl Sum for Length {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
Self(iter.map(|s| s.0).sum())
diff --git a/src/geom/scalar.rs b/src/geom/scalar.rs
index 91225a2b..b45ae60a 100644
--- a/src/geom/scalar.rs
+++ b/src/geom/scalar.rs
@@ -148,6 +148,20 @@ impl<T: Into<Self>> DivAssign<T> for Scalar {
}
}
+impl<T: Into<Self>> Rem<T> for Scalar {
+ type Output = Self;
+
+ fn rem(self, rhs: T) -> Self::Output {
+ Self(self.0 % rhs.into().0)
+ }
+}
+
+impl<T: Into<Self>> RemAssign<T> for Scalar {
+ fn rem_assign(&mut self, rhs: T) {
+ self.0 %= rhs.into().0;
+ }
+}
+
impl Sum for Scalar {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
Self(iter.map(|s| s.0).sum())
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 0034b581..d3ed98da 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -29,6 +29,7 @@ pub fn new() -> Scope {
std.def_node::<text::StrikethroughNode>("strike");
std.def_node::<text::OverlineNode>("overline");
std.def_node::<text::LinkNode>("link");
+ std.def_node::<text::RepeatNode>("repeat");
// Structure.
std.def_node::<structure::HeadingNode>("heading");
diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs
index 0eb57339..bde553e2 100644
--- a/src/library/text/mod.rs
+++ b/src/library/text/mod.rs
@@ -6,6 +6,7 @@ mod link;
mod par;
mod quotes;
mod raw;
+mod repeat;
mod shaping;
pub use deco::*;
@@ -14,6 +15,7 @@ pub use link::*;
pub use par::*;
pub use quotes::*;
pub use raw::*;
+pub use repeat::*;
pub use shaping::*;
use std::borrow::Cow;
diff --git a/src/library/text/par.rs b/src/library/text/par.rs
index fc978357..4717c3af 100644
--- a/src/library/text/par.rs
+++ b/src/library/text/par.rs
@@ -4,7 +4,7 @@ use unicode_bidi::{BidiInfo, Level};
use unicode_script::{Script, UnicodeScript};
use xi_unicode::LineBreakIterator;
-use super::{shape, Lang, Quoter, Quotes, ShapedText, TextNode};
+use super::{shape, Lang, Quoter, Quotes, RepeatNode, ShapedText, TextNode};
use crate::font::FontStore;
use crate::library::layout::Spacing;
use crate::library::prelude::*;
@@ -76,7 +76,7 @@ impl Layout for ParNode {
let lines = linebreak(&p, &mut ctx.fonts, regions.first.x);
// Stack the lines into one frame per region.
- Ok(stack(&lines, &mut ctx.fonts, regions, styles))
+ stack(ctx, &lines, regions, styles)
}
}
@@ -262,6 +262,8 @@ enum Item<'a> {
Fractional(Fraction),
/// A layouted child node.
Frame(Frame),
+ /// A repeating node.
+ Repeat(&'a RepeatNode),
}
impl<'a> Item<'a> {
@@ -278,7 +280,7 @@ impl<'a> Item<'a> {
match self {
Self::Text(shaped) => shaped.text.len(),
Self::Absolute(_) | Self::Fractional(_) => SPACING_REPLACE.len_utf8(),
- Self::Frame(_) => NODE_REPLACE.len_utf8(),
+ Self::Frame(_) | Self::Repeat(_) => NODE_REPLACE.len_utf8(),
}
}
@@ -287,7 +289,7 @@ impl<'a> Item<'a> {
match self {
Item::Text(shaped) => shaped.width,
Item::Absolute(v) => *v,
- Item::Fractional(_) => Length::zero(),
+ Item::Fractional(_) | Self::Repeat(_) => Length::zero(),
Item::Frame(frame) => frame.size.x,
}
}
@@ -374,6 +376,7 @@ impl<'a> Line<'a> {
self.items()
.filter_map(|item| match item {
Item::Fractional(fr) => Some(*fr),
+ Item::Repeat(_) => Some(Fraction::one()),
_ => None,
})
.sum()
@@ -518,10 +521,14 @@ fn prepare<'a>(
}
},
Segment::Node(node) => {
- let size = Size::new(regions.first.x, regions.base.y);
- let pod = Regions::one(size, regions.base, Spec::splat(false));
- let frame = node.layout(ctx, &pod, styles)?.remove(0);
- items.push(Item::Frame(Arc::take(frame)));
+ if let Some(repeat) = node.downcast() {
+ items.push(Item::Repeat(repeat));
+ } else {
+ let size = Size::new(regions.first.x, regions.base.y);
+ let pod = Regions::one(size, regions.base, Spec::splat(false));
+ let frame = node.layout(ctx, &pod, styles)?.remove(0);
+ items.push(Item::Frame(Arc::take(frame)));
+ }
}
}
@@ -954,11 +961,11 @@ fn line<'a>(
/// Combine layouted lines into one frame per region.
fn stack(
+ ctx: &mut Context,
lines: &[Line],
- fonts: &mut FontStore,
regions: &Regions,
styles: StyleChain,
-) -> Vec<Arc<Frame>> {
+) -> TypResult<Vec<Arc<Frame>>> {
let leading = styles.get(ParNode::LEADING);
let align = styles.get(ParNode::ALIGN);
let justify = styles.get(ParNode::JUSTIFY);
@@ -978,7 +985,7 @@ fn stack(
// Stack the lines into one frame per region.
for line in lines {
- let frame = commit(line, fonts, width, align, justify);
+ let frame = commit(ctx, line, &regions, width, styles, align, justify)?;
let height = frame.size.y;
while !regions.first.y.fits(height) && !regions.in_last() {
@@ -1001,17 +1008,19 @@ fn stack(
}
finished.push(Arc::new(output));
- finished
+ Ok(finished)
}
/// Commit to a line and build its frame.
fn commit(
+ ctx: &mut Context,
line: &Line,
- fonts: &mut FontStore,
+ regions: &Regions,
width: Length,
+ styles: StyleChain,
align: Align,
justify: bool,
-) -> Frame {
+) -> TypResult<Frame> {
let mut remaining = width - line.width;
let mut offset = Length::zero();
@@ -1067,24 +1076,44 @@ fn commit(
// Build the frames and determine the height and baseline.
let mut frames = vec![];
for item in reordered {
- let frame = match item {
+ let mut push = |offset: &mut Length, frame: Frame| {
+ let width = frame.size.x;
+ top.set_max(frame.baseline());
+ bottom.set_max(frame.size.y - frame.baseline());
+ frames.push((*offset, frame));
+ *offset += width;
+ };
+
+ match item {
Item::Absolute(v) => {
offset += *v;
- continue;
}
Item::Fractional(v) => {
offset += v.share(fr, remaining);
- continue;
}
- Item::Text(shaped) => shaped.build(fonts, justification),
- Item::Frame(frame) => frame.clone(),
- };
-
- let width = frame.size.x;
- top.set_max(frame.baseline());
- bottom.set_max(frame.size.y - frame.baseline());
- frames.push((offset, frame));
- offset += width;
+ Item::Text(shaped) => {
+ push(&mut offset, shaped.build(&mut ctx.fonts, justification));
+ }
+ Item::Frame(frame) => {
+ push(&mut offset, frame.clone());
+ }
+ Item::Repeat(node) => {
+ let before = offset;
+ let width = Fraction::one().share(fr, remaining);
+ let size = Size::new(width, regions.base.y);
+ let pod = Regions::one(size, regions.base, Spec::new(false, false));
+ let frame = node.layout(ctx, &pod, styles)?.remove(0);
+ let count = (width / frame.size.x).floor();
+ let apart = (width % frame.size.x) / (count - 1.0);
+ if frame.size.x > Length::zero() {
+ for _ in 0 .. (count as usize).min(1000) {
+ push(&mut offset, frame.as_ref().clone());
+ offset += apart;
+ }
+ }
+ offset = before + width;
+ }
+ }
}
let size = Size::new(width, top + bottom);
@@ -1098,7 +1127,7 @@ fn commit(
output.merge_frame(Point::new(x, y), frame);
}
- output
+ Ok(output)
}
/// Return a line's items in visual order.
diff --git a/src/library/text/repeat.rs b/src/library/text/repeat.rs
new file mode 100644
index 00000000..68036be7
--- /dev/null
+++ b/src/library/text/repeat.rs
@@ -0,0 +1,24 @@
+use crate::library::prelude::*;
+
+/// Fill space by repeating something horizontally.
+#[derive(Debug, Hash)]
+pub struct RepeatNode(pub LayoutNode);
+
+#[node]
+impl RepeatNode {
+ fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
+ Ok(Content::inline(Self(args.expect("body")?)))
+ }
+}
+
+impl Layout for RepeatNode {
+ fn layout(
+ &self,
+ ctx: &mut Context,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>> {
+ // The actual repeating happens directly in the paragraph.
+ self.0.layout(ctx, regions, styles)
+ }
+}
diff --git a/tests/ref/text/repeat.png b/tests/ref/text/repeat.png
new file mode 100644
index 00000000..898de96f
--- /dev/null
+++ b/tests/ref/text/repeat.png
Binary files differ
diff --git a/tests/typ/text/repeat.typ b/tests/typ/text/repeat.typ
new file mode 100644
index 00000000..0036999a
--- /dev/null
+++ b/tests/typ/text/repeat.typ
@@ -0,0 +1,15 @@
+// Test the `repeat` function.
+
+---
+#let sections = (
+ ("Introduction", 1),
+ ("Approach", 1),
+ ("Evaluation", 3),
+ ("Discussion", 15),
+ ("Related Work", 16),
+ ("Conclusion", 253),
+)
+
+#for section in sections [
+ #section(0) #repeat[.] #section(1) \
+]