mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +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);
|
||||||
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())
|
||||||
|
@ -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())
|
||||||
|
@ -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");
|
||||||
|
@ -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;
|
||||||
|
@ -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, ®ions, 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.
|
||||||
|
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