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);
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 { impl Sum for Length {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
Self(iter.map(|s| s.0).sum()) 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 { impl Sum for Scalar {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
Self(iter.map(|s| s.0).sum()) 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::StrikethroughNode>("strike");
std.def_node::<text::OverlineNode>("overline"); std.def_node::<text::OverlineNode>("overline");
std.def_node::<text::LinkNode>("link"); std.def_node::<text::LinkNode>("link");
std.def_node::<text::RepeatNode>("repeat");
// Structure. // Structure.
std.def_node::<structure::HeadingNode>("heading"); std.def_node::<structure::HeadingNode>("heading");

View File

@ -6,6 +6,7 @@ mod link;
mod par; mod par;
mod quotes; mod quotes;
mod raw; mod raw;
mod repeat;
mod shaping; mod shaping;
pub use deco::*; pub use deco::*;
@ -14,6 +15,7 @@ pub use link::*;
pub use par::*; pub use par::*;
pub use quotes::*; pub use quotes::*;
pub use raw::*; pub use raw::*;
pub use repeat::*;
pub use shaping::*; pub use shaping::*;
use std::borrow::Cow; use std::borrow::Cow;

View File

@ -4,7 +4,7 @@ use unicode_bidi::{BidiInfo, Level};
use unicode_script::{Script, UnicodeScript}; use unicode_script::{Script, UnicodeScript};
use xi_unicode::LineBreakIterator; 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::font::FontStore;
use crate::library::layout::Spacing; use crate::library::layout::Spacing;
use crate::library::prelude::*; use crate::library::prelude::*;
@ -76,7 +76,7 @@ impl Layout for ParNode {
let lines = linebreak(&p, &mut ctx.fonts, regions.first.x); let lines = linebreak(&p, &mut ctx.fonts, regions.first.x);
// Stack the lines into one frame per region. // 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), Fractional(Fraction),
/// A layouted child node. /// A layouted child node.
Frame(Frame), Frame(Frame),
/// A repeating node.
Repeat(&'a RepeatNode),
} }
impl<'a> Item<'a> { impl<'a> Item<'a> {
@ -278,7 +280,7 @@ impl<'a> Item<'a> {
match self { match self {
Self::Text(shaped) => shaped.text.len(), Self::Text(shaped) => shaped.text.len(),
Self::Absolute(_) | Self::Fractional(_) => SPACING_REPLACE.len_utf8(), 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 { match self {
Item::Text(shaped) => shaped.width, Item::Text(shaped) => shaped.width,
Item::Absolute(v) => *v, Item::Absolute(v) => *v,
Item::Fractional(_) => Length::zero(), Item::Fractional(_) | Self::Repeat(_) => Length::zero(),
Item::Frame(frame) => frame.size.x, Item::Frame(frame) => frame.size.x,
} }
} }
@ -374,6 +376,7 @@ impl<'a> Line<'a> {
self.items() self.items()
.filter_map(|item| match item { .filter_map(|item| match item {
Item::Fractional(fr) => Some(*fr), Item::Fractional(fr) => Some(*fr),
Item::Repeat(_) => Some(Fraction::one()),
_ => None, _ => None,
}) })
.sum() .sum()
@ -518,10 +521,14 @@ fn prepare<'a>(
} }
}, },
Segment::Node(node) => { Segment::Node(node) => {
let size = Size::new(regions.first.x, regions.base.y); if let Some(repeat) = node.downcast() {
let pod = Regions::one(size, regions.base, Spec::splat(false)); items.push(Item::Repeat(repeat));
let frame = node.layout(ctx, &pod, styles)?.remove(0); } else {
items.push(Item::Frame(Arc::take(frame))); 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. /// Combine layouted lines into one frame per region.
fn stack( fn stack(
ctx: &mut Context,
lines: &[Line], lines: &[Line],
fonts: &mut FontStore,
regions: &Regions, regions: &Regions,
styles: StyleChain, styles: StyleChain,
) -> Vec<Arc<Frame>> { ) -> TypResult<Vec<Arc<Frame>>> {
let leading = styles.get(ParNode::LEADING); let leading = styles.get(ParNode::LEADING);
let align = styles.get(ParNode::ALIGN); let align = styles.get(ParNode::ALIGN);
let justify = styles.get(ParNode::JUSTIFY); let justify = styles.get(ParNode::JUSTIFY);
@ -978,7 +985,7 @@ fn stack(
// Stack the lines into one frame per region. // Stack the lines into one frame per region.
for line in lines { 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; let height = frame.size.y;
while !regions.first.y.fits(height) && !regions.in_last() { while !regions.first.y.fits(height) && !regions.in_last() {
@ -1001,17 +1008,19 @@ fn stack(
} }
finished.push(Arc::new(output)); finished.push(Arc::new(output));
finished Ok(finished)
} }
/// Commit to a line and build its frame. /// Commit to a line and build its frame.
fn commit( fn commit(
ctx: &mut Context,
line: &Line, line: &Line,
fonts: &mut FontStore, regions: &Regions,
width: Length, width: Length,
styles: StyleChain,
align: Align, align: Align,
justify: bool, justify: bool,
) -> Frame { ) -> TypResult<Frame> {
let mut remaining = width - line.width; let mut remaining = width - line.width;
let mut offset = Length::zero(); let mut offset = Length::zero();
@ -1067,24 +1076,44 @@ fn commit(
// Build the frames and determine the height and baseline. // Build the frames and determine the height and baseline.
let mut frames = vec![]; let mut frames = vec![];
for item in reordered { 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) => { Item::Absolute(v) => {
offset += *v; offset += *v;
continue;
} }
Item::Fractional(v) => { Item::Fractional(v) => {
offset += v.share(fr, remaining); offset += v.share(fr, remaining);
continue;
} }
Item::Text(shaped) => shaped.build(fonts, justification), Item::Text(shaped) => {
Item::Frame(frame) => frame.clone(), push(&mut offset, shaped.build(&mut ctx.fonts, justification));
}; }
Item::Frame(frame) => {
let width = frame.size.x; push(&mut offset, frame.clone());
top.set_max(frame.baseline()); }
bottom.set_max(frame.size.y - frame.baseline()); Item::Repeat(node) => {
frames.push((offset, frame)); let before = offset;
offset += width; 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); let size = Size::new(width, top + bottom);
@ -1098,7 +1127,7 @@ fn commit(
output.merge_frame(Point::new(x, y), frame); output.merge_frame(Point::new(x, y), frame);
} }
output Ok(output)
} }
/// Return a line's items in visual order. /// 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) \
]