Repeat function

This commit is contained in:
Laurenz 2022-04-17 12:11:00 +02:00
parent 4494b443bb
commit db820ae9aa
8 changed files with 120 additions and 27 deletions

View File

@ -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())

View File

@ -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())

View File

@ -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");

View File

@ -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;

View File

@ -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,12 +521,16 @@ fn prepare<'a>(
}
},
Segment::Node(node) => {
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)));
}
}
}
cursor = end;
}
@ -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 {
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 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;
frames.push((*offset, frame));
*offset += width;
};
match item {
Item::Absolute(v) => {
offset += *v;
}
Item::Fractional(v) => {
offset += v.share(fr, remaining);
}
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.

View File

@ -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)
}
}

BIN
tests/ref/text/repeat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

15
tests/typ/text/repeat.typ Normal file
View File

@ -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) \
]