mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Repeat function
This commit is contained in:
parent
4494b443bb
commit
db820ae9aa
@ -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())
|
||||
|
@ -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())
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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, ®ions, 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.
|
||||
|
24
src/library/text/repeat.rs
Normal file
24
src/library/text/repeat.rs
Normal 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
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
15
tests/typ/text/repeat.typ
Normal 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) \
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user