summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--library/src/layout/container.rs57
-rw-r--r--library/src/layout/enum.rs10
-rw-r--r--library/src/layout/flow.rs4
-rw-r--r--library/src/layout/grid.rs98
-rw-r--r--library/src/layout/list.rs10
-rw-r--r--library/src/layout/mod.rs20
-rw-r--r--library/src/layout/par.rs118
-rw-r--r--library/src/layout/repeat.rs37
-rw-r--r--library/src/layout/spacing.rs22
-rw-r--r--library/src/layout/stack.rs4
-rw-r--r--library/src/layout/table.rs14
-rw-r--r--library/src/layout/terms.rs6
-rw-r--r--library/src/math/mod.rs2
-rw-r--r--library/src/meta/outline.rs16
-rw-r--r--src/doc.rs2
-rw-r--r--tests/ref/layout/container.pngbin7512 -> 8332 bytes
-rw-r--r--tests/ref/layout/repeat.pngbin9344 -> 9260 bytes
-rw-r--r--tests/typ/layout/container.typ4
-rw-r--r--tests/typ/layout/repeat.typ16
19 files changed, 255 insertions, 185 deletions
diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs
index 09cfac8d..930a27e7 100644
--- a/library/src/layout/container.rs
+++ b/library/src/layout/container.rs
@@ -44,7 +44,7 @@ pub struct BoxNode {
/// The content to be sized.
pub body: Content,
/// The box's width.
- pub width: Smart<Rel<Length>>,
+ pub width: Sizing,
/// The box's height.
pub height: Smart<Rel<Length>>,
/// The box's baseline shift.
@@ -76,8 +76,14 @@ impl Layout for BoxNode {
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
+ let width = match self.width {
+ Sizing::Auto => Smart::Auto,
+ Sizing::Rel(rel) => Smart::Custom(rel),
+ Sizing::Fr(_) => Smart::Custom(Ratio::one().into()),
+ };
+
// Resolve the sizing to a concrete size.
- let sizing = Axes::new(self.width, self.height);
+ let sizing = Axes::new(width, self.height);
let size = sizing
.resolve(styles)
.zip(regions.base())
@@ -196,3 +202,50 @@ impl Layout for BlockNode {
self.0.layout(vt, styles, regions)
}
}
+
+/// Defines how to size a grid cell along an axis.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum Sizing {
+ /// A track that fits its cell's contents.
+ Auto,
+ /// A track size specified in absolute terms and relative to the parent's
+ /// size.
+ Rel(Rel<Length>),
+ /// A track size specified as a fraction of the remaining free space in the
+ /// parent.
+ Fr(Fr),
+}
+
+impl Sizing {
+ /// Whether this is fractional sizing.
+ pub fn is_fractional(self) -> bool {
+ matches!(self, Self::Fr(_))
+ }
+
+ pub fn encode(self) -> Value {
+ match self {
+ Self::Auto => Value::Auto,
+ Self::Rel(rel) => Spacing::Rel(rel).encode(),
+ Self::Fr(fr) => Spacing::Fr(fr).encode(),
+ }
+ }
+
+ pub fn encode_slice(vec: &[Sizing]) -> Value {
+ Value::Array(vec.iter().copied().map(Self::encode).collect())
+ }
+}
+
+impl Default for Sizing {
+ fn default() -> Self {
+ Self::Auto
+ }
+}
+
+impl From<Spacing> for Sizing {
+ fn from(spacing: Spacing) -> Self {
+ match spacing {
+ Spacing::Rel(rel) => Self::Rel(rel),
+ Spacing::Fr(fr) => Self::Fr(fr),
+ }
+ }
+}
diff --git a/library/src/layout/enum.rs b/library/src/layout/enum.rs
index b1b18680..9a83420c 100644
--- a/library/src/layout/enum.rs
+++ b/library/src/layout/enum.rs
@@ -1,7 +1,7 @@
use std::str::FromStr;
use crate::compute::{Numbering, NumberingPattern};
-use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing};
+use crate::layout::{BlockNode, GridNode, ParNode, Sizing, Spacing};
use crate::prelude::*;
/// # Numbered List
@@ -193,10 +193,10 @@ impl Layout for EnumNode {
GridNode {
tracks: Axes::with_x(vec![
- TrackSizing::Relative(indent.into()),
- TrackSizing::Auto,
- TrackSizing::Relative(body_indent.into()),
- TrackSizing::Auto,
+ Sizing::Rel(indent.into()),
+ Sizing::Auto,
+ Sizing::Rel(body_indent.into()),
+ Sizing::Auto,
]),
gutter: Axes::with_y(vec![gutter.into()]),
cells,
diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs
index 7b721c59..db9eed8d 100644
--- a/library/src/layout/flow.rs
+++ b/library/src/layout/flow.rs
@@ -113,11 +113,11 @@ impl<'a> FlowLayouter<'a> {
/// Layout vertical spacing.
fn layout_spacing(&mut self, node: VNode, styles: StyleChain) {
self.layout_item(match node.amount {
- Spacing::Relative(v) => FlowItem::Absolute(
+ Spacing::Rel(v) => FlowItem::Absolute(
v.resolve(styles).relative_to(self.full.y),
node.weakness > 0,
),
- Spacing::Fractional(v) => FlowItem::Fractional(v),
+ Spacing::Fr(v) => FlowItem::Fractional(v),
});
}
diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs
index 95e4ac8f..6fea5bbc 100644
--- a/library/src/layout/grid.rs
+++ b/library/src/layout/grid.rs
@@ -1,7 +1,7 @@
use crate::prelude::*;
use crate::text::TextNode;
-use super::Spacing;
+use super::Sizing;
/// # Grid
/// Arrange content in a grid.
@@ -94,9 +94,9 @@ use super::Spacing;
#[derive(Debug, Hash)]
pub struct GridNode {
/// Defines sizing for content rows and columns.
- pub tracks: Axes<Vec<TrackSizing>>,
+ pub tracks: Axes<Vec<Sizing>>,
/// Defines sizing of gutter rows and columns between content.
- pub gutter: Axes<Vec<TrackSizing>>,
+ pub gutter: Axes<Vec<Sizing>>,
/// The content to be arranged in a grid.
pub cells: Vec<Content>,
}
@@ -122,10 +122,10 @@ impl GridNode {
fn field(&self, name: &str) -> Option<Value> {
match name {
- "columns" => Some(TrackSizing::encode_slice(&self.tracks.x)),
- "rows" => Some(TrackSizing::encode_slice(&self.tracks.y)),
- "column-gutter" => Some(TrackSizing::encode_slice(&self.gutter.x)),
- "row-gutter" => Some(TrackSizing::encode_slice(&self.gutter.y)),
+ "columns" => Some(Sizing::encode_slice(&self.tracks.x)),
+ "rows" => Some(Sizing::encode_slice(&self.tracks.y)),
+ "column-gutter" => Some(Sizing::encode_slice(&self.gutter.x)),
+ "row-gutter" => Some(Sizing::encode_slice(&self.gutter.y)),
"cells" => Some(Value::Array(
self.cells.iter().cloned().map(Value::Content).collect(),
)),
@@ -156,50 +156,14 @@ impl Layout for GridNode {
}
}
-/// Defines how to size a grid cell along an axis.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum TrackSizing {
- /// A track that fits its cell's contents.
- Auto,
- /// A track size specified in absolute terms and relative to the parent's
- /// size.
- Relative(Rel<Length>),
- /// A track size specified as a fraction of the remaining free space in the
- /// parent.
- Fractional(Fr),
-}
-
-impl TrackSizing {
- pub fn encode(self) -> Value {
- match self {
- Self::Auto => Value::Auto,
- Self::Relative(rel) => Spacing::Relative(rel).encode(),
- Self::Fractional(fr) => Spacing::Fractional(fr).encode(),
- }
- }
-
- pub fn encode_slice(vec: &[TrackSizing]) -> Value {
- Value::Array(vec.iter().copied().map(Self::encode).collect())
- }
-}
-
-impl From<Spacing> for TrackSizing {
- fn from(spacing: Spacing) -> Self {
- match spacing {
- Spacing::Relative(rel) => Self::Relative(rel),
- Spacing::Fractional(fr) => Self::Fractional(fr),
- }
- }
-}
-
/// Track sizing definitions.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
-pub struct TrackSizings(pub Vec<TrackSizing>);
+pub struct TrackSizings(pub Vec<Sizing>);
castable! {
TrackSizings,
- sizing: TrackSizing => Self(vec![sizing]),
- count: NonZeroUsize => Self(vec![TrackSizing::Auto; count.get()]),
+ sizing: Sizing => Self(vec![sizing]),
+ count: NonZeroUsize => Self(vec![Sizing::Auto; count.get()]),
values: Array => Self(values
.into_iter()
.filter_map(|v| v.cast().ok())
@@ -207,10 +171,10 @@ castable! {
}
castable! {
- TrackSizing,
+ Sizing,
_: AutoValue => Self::Auto,
- v: Rel<Length> => Self::Relative(v),
- v: Fr => Self::Fractional(v),
+ v: Rel<Length> => Self::Rel(v),
+ v: Fr => Self::Fr(v),
}
/// Performs grid layout.
@@ -224,9 +188,9 @@ struct GridLayouter<'a, 'v> {
/// Whether this grid has gutters.
has_gutter: bool,
/// The column tracks including gutter tracks.
- cols: Vec<TrackSizing>,
+ cols: Vec<Sizing>,
/// The row tracks including gutter tracks.
- rows: Vec<TrackSizing>,
+ rows: Vec<Sizing>,
/// The regions to layout children into.
regions: Regions<'a>,
/// The inherited styles.
@@ -259,8 +223,8 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
/// This prepares grid layout by unifying content and gutter tracks.
fn new(
vt: &'a mut Vt<'v>,
- tracks: Axes<&[TrackSizing]>,
- gutter: Axes<&[TrackSizing]>,
+ tracks: Axes<&[Sizing]>,
+ gutter: Axes<&[Sizing]>,
cells: &'a [Content],
regions: Regions<'a>,
styles: StyleChain<'a>,
@@ -281,8 +245,8 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
};
let has_gutter = gutter.any(|tracks| !tracks.is_empty());
- let auto = TrackSizing::Auto;
- let zero = TrackSizing::Relative(Rel::zero());
+ let auto = Sizing::Auto;
+ let zero = Sizing::Rel(Rel::zero());
let get_or = |tracks: &[_], idx, default| {
tracks.get(idx).or(tracks.last()).copied().unwrap_or(default)
};
@@ -352,9 +316,9 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
}
match self.rows[y] {
- TrackSizing::Auto => self.layout_auto_row(y)?,
- TrackSizing::Relative(v) => self.layout_relative_row(v, y)?,
- TrackSizing::Fractional(v) => {
+ Sizing::Auto => self.layout_auto_row(y)?,
+ Sizing::Rel(v) => self.layout_relative_row(v, y)?,
+ Sizing::Fr(v) => {
self.lrows.push(Row::Fr(v, y));
self.fr += v;
}
@@ -377,14 +341,14 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
// fractional tracks.
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
match col {
- TrackSizing::Auto => {}
- TrackSizing::Relative(v) => {
+ Sizing::Auto => {}
+ Sizing::Rel(v) => {
let resolved =
v.resolve(self.styles).relative_to(self.regions.base().x);
*rcol = resolved;
rel += resolved;
}
- TrackSizing::Fractional(v) => fr += v,
+ Sizing::Fr(v) => fr += v,
}
}
@@ -418,7 +382,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
// Determine size of auto columns by laying out all cells in those
// columns, measuring them and finding the largest one.
for (x, &col) in self.cols.iter().enumerate() {
- if col != TrackSizing::Auto {
+ if col != Sizing::Auto {
continue;
}
@@ -428,7 +392,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
// For relative rows, we can already resolve the correct
// base and for auto and fr we could only guess anyway.
let height = match self.rows[y] {
- TrackSizing::Relative(v) => {
+ Sizing::Rel(v) => {
v.resolve(self.styles).relative_to(self.regions.base().y)
}
_ => self.regions.base().y,
@@ -456,7 +420,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
}
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
- if let TrackSizing::Fractional(v) = col {
+ if let Sizing::Fr(v) = col {
*rcol = v.share(fr, remaining);
}
}
@@ -479,7 +443,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
for (&col, &rcol) in self.cols.iter().zip(&self.rcols) {
// Remove an auto column if it is not overlarge (rcol <= fair),
// but also hasn't already been removed (rcol > last).
- if col == TrackSizing::Auto && rcol <= fair && rcol > last {
+ if col == Sizing::Auto && rcol <= fair && rcol > last {
redistribute -= rcol;
overlarge -= 1;
changed = true;
@@ -489,7 +453,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
// Redistribute space fairly among overlarge columns.
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
- if col == TrackSizing::Auto && *rcol > fair {
+ if col == Sizing::Auto && *rcol > fair {
*rcol = fair;
}
}
@@ -597,7 +561,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
if let Some(cell) = self.cell(x, y) {
let size = Size::new(rcol, height);
let mut pod = Regions::one(size, Axes::splat(true));
- if self.rows[y] == TrackSizing::Auto {
+ if self.rows[y] == Sizing::Auto {
pod.full = self.regions.full;
}
let frame = cell.layout(self.vt, self.styles, pod)?.into_frame();
diff --git a/library/src/layout/list.rs b/library/src/layout/list.rs
index 8bdbe737..eab835ca 100644
--- a/library/src/layout/list.rs
+++ b/library/src/layout/list.rs
@@ -1,4 +1,4 @@
-use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing};
+use crate::layout::{BlockNode, GridNode, ParNode, Sizing, Spacing};
use crate::prelude::*;
use crate::text::TextNode;
@@ -147,10 +147,10 @@ impl Layout for ListNode {
GridNode {
tracks: Axes::with_x(vec![
- TrackSizing::Relative(indent.into()),
- TrackSizing::Auto,
- TrackSizing::Relative(body_indent.into()),
- TrackSizing::Auto,
+ Sizing::Rel(indent.into()),
+ Sizing::Auto,
+ Sizing::Rel(body_indent.into()),
+ Sizing::Auto,
]),
gutter: Axes::with_y(vec![gutter.into()]),
cells,
diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs
index 3294a96c..9ee77a61 100644
--- a/library/src/layout/mod.rs
+++ b/library/src/layout/mod.rs
@@ -227,9 +227,16 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
fn accept(
&mut self,
- content: &'a Content,
+ mut content: &'a Content,
styles: StyleChain<'a>,
) -> SourceResult<()> {
+ if content.has::<dyn LayoutMath>() && !content.is::<FormulaNode>() {
+ content = self
+ .scratch
+ .content
+ .alloc(FormulaNode { body: content.clone(), block: false }.pack());
+ }
+
// Prepare only if this is the first application for this node.
if let Some(node) = content.with::<dyn Prepare>() {
if !content.is_prepared() {
@@ -470,22 +477,15 @@ impl<'a> ParBuilder<'a> {
if content.is::<SpaceNode>()
|| content.is::<TextNode>()
|| content.is::<HNode>()
- || content.is::<SmartQuoteNode>()
|| content.is::<LinebreakNode>()
- || content.is::<BoxNode>()
- || content.is::<RepeatNode>()
+ || content.is::<SmartQuoteNode>()
|| content.to::<FormulaNode>().map_or(false, |node| !node.block)
+ || content.is::<BoxNode>()
{
self.0.push(content.clone(), styles);
return true;
}
- if !content.is::<FormulaNode>() && content.has::<dyn LayoutMath>() {
- let formula = FormulaNode { body: content.clone(), block: false }.pack();
- self.0.push(formula, styles);
- return true;
- }
-
false
}
diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs
index b712d8b1..bd08b8a5 100644
--- a/library/src/layout/par.rs
+++ b/library/src/layout/par.rs
@@ -4,8 +4,9 @@ use xi_unicode::LineBreakIterator;
use typst::model::Key;
-use super::{HNode, RepeatNode, Spacing};
+use super::{BoxNode, HNode, Sizing, Spacing};
use crate::layout::AlignNode;
+use crate::math::FormulaNode;
use crate::prelude::*;
use crate::text::{
shape, LinebreakNode, Quoter, Quotes, ShapedText, SmartQuoteNode, SpaceNode, TextNode,
@@ -330,8 +331,10 @@ enum Segment<'a> {
Text(usize),
/// Horizontal spacing between other segments.
Spacing(Spacing),
- /// Arbitrary inline-level content.
- Inline(&'a Content),
+ /// A math formula.
+ Formula(&'a FormulaNode),
+ /// A box with arbitrary content.
+ Box(&'a BoxNode),
}
impl Segment<'_> {
@@ -340,7 +343,8 @@ impl Segment<'_> {
match *self {
Self::Text(len) => len,
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
- Self::Inline(_) => NODE_REPLACE.len_utf8(),
+ Self::Box(node) if node.width.is_fractional() => SPACING_REPLACE.len_utf8(),
+ Self::Formula(_) | Self::Box(_) => NODE_REPLACE.len_utf8(),
}
}
}
@@ -353,11 +357,9 @@ enum Item<'a> {
/// Absolute spacing between other items.
Absolute(Abs),
/// Fractional spacing between other items.
- Fractional(Fr),
+ Fractional(Fr, Option<(&'a BoxNode, StyleChain<'a>)>),
/// Layouted inline-level content.
Frame(Frame),
- /// A repeating node that fills the remaining space in a line.
- Repeat(&'a RepeatNode, StyleChain<'a>),
}
impl<'a> Item<'a> {
@@ -373,8 +375,8 @@ impl<'a> Item<'a> {
fn len(&self) -> usize {
match self {
Self::Text(shaped) => shaped.text.len(),
- Self::Absolute(_) | Self::Fractional(_) => SPACING_REPLACE.len_utf8(),
- Self::Frame(_) | Self::Repeat(_, _) => NODE_REPLACE.len_utf8(),
+ Self::Absolute(_) | Self::Fractional(_, _) => SPACING_REPLACE.len_utf8(),
+ Self::Frame(_) => NODE_REPLACE.len_utf8(),
}
}
@@ -384,7 +386,7 @@ impl<'a> Item<'a> {
Self::Text(shaped) => shaped.width,
Self::Absolute(v) => *v,
Self::Frame(frame) => frame.width(),
- Self::Fractional(_) | Self::Repeat(_, _) => Abs::zero(),
+ Self::Fractional(_, _) => Abs::zero(),
}
}
}
@@ -473,8 +475,7 @@ impl<'a> Line<'a> {
fn fr(&self) -> Fr {
self.items()
.filter_map(|item| match item {
- Item::Fractional(fr) => Some(*fr),
- Item::Repeat(_, _) => Some(Fr::one()),
+ Item::Fractional(fr, _) => Some(*fr),
_ => None,
})
.sum()
@@ -530,6 +531,9 @@ fn collect<'a>(
full.push_str(&node.0);
}
Segment::Text(full.len() - prev)
+ } else if let Some(&node) = child.to::<HNode>() {
+ full.push(SPACING_REPLACE);
+ Segment::Spacing(node.amount)
} else if let Some(node) = child.to::<LinebreakNode>() {
let c = if node.justify { '\u{2028}' } else { '\n' };
full.push(c);
@@ -557,12 +561,18 @@ fn collect<'a>(
full.push(if node.double { '"' } else { '\'' });
}
Segment::Text(full.len() - prev)
- } else if let Some(&node) = child.to::<HNode>() {
- full.push(SPACING_REPLACE);
- Segment::Spacing(node.amount)
- } else {
+ } else if let Some(node) = child.to::<FormulaNode>() {
full.push(NODE_REPLACE);
- Segment::Inline(child)
+ Segment::Formula(node)
+ } else if let Some(node) = child.to::<BoxNode>() {
+ full.push(if node.width.is_fractional() {
+ SPACING_REPLACE
+ } else {
+ NODE_REPLACE
+ });
+ Segment::Box(node)
+ } else {
+ panic!("unexpected par child: {child:?}");
};
if let Some(last) = full.chars().last() {
@@ -614,20 +624,26 @@ fn prepare<'a>(
shape_range(&mut items, vt, &bidi, cursor..end, styles);
}
Segment::Spacing(spacing) => match spacing {
- Spacing::Relative(v) => {
+ Spacing::Rel(v) => {
let resolved = v.resolve(styles).relative_to(region.x);
items.push(Item::Absolute(resolved));
}
- Spacing::Fractional(v) => {
- items.push(Item::Fractional(v));
+ Spacing::Fr(v) => {
+ items.push(Item::Fractional(v, None));
}
},
- Segment::Inline(inline) => {
- if let Some(repeat) = inline.to::<RepeatNode>() {
- items.push(Item::Repeat(repeat, styles));
+ Segment::Formula(formula) => {
+ let pod = Regions::one(region, Axes::splat(false));
+ let mut frame = formula.layout(vt, styles, pod)?.into_frame();
+ frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
+ items.push(Item::Frame(frame));
+ }
+ Segment::Box(node) => {
+ if let Sizing::Fr(v) = node.width {
+ items.push(Item::Fractional(v, Some((node, styles))));
} else {
let pod = Regions::one(region, Axes::splat(false));
- let mut frame = inline.layout(vt, styles, pod)?.into_frame();
+ let mut frame = node.layout(vt, styles, pod)?.into_frame();
frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
items.push(Item::Frame(frame));
}
@@ -1111,20 +1127,23 @@ fn finalize(
vt: &mut Vt,
p: &Preparation,
lines: &[Line],
- mut region: Size,
+ region: Size,
expand: bool,
) -> SourceResult<Fragment> {
// Determine the paragraph's width: Full width of the region if we
// should expand or there's fractional spacing, fit-to-width otherwise.
- if !region.x.is_finite() || (!expand && lines.iter().all(|line| line.fr().is_zero()))
+ let width = if !region.x.is_finite()
+ || (!expand && lines.iter().all(|line| line.fr().is_zero()))
{
- region.x = lines.iter().map(|line| line.width).max().unwrap_or_default();
- }
+ lines.iter().map(|line| line.width).max().unwrap_or_default()
+ } else {
+ region.x
+ };
// Stack the lines into one frame per region.
let mut frames: Vec<Frame> = lines
.iter()
- .map(|line| commit(vt, p, line, region))
+ .map(|line| commit(vt, p, line, width, region.y))
.collect::<SourceResult<_>>()?;
// Prevent orphans.
@@ -1159,9 +1178,10 @@ fn commit(
vt: &mut Vt,
p: &Preparation,
line: &Line,
- region: Size,
+ width: Abs,
+ full: Abs,
) -> SourceResult<Frame> {
- let mut remaining = region.x - line.width;
+ let mut remaining = width - line.width;
let mut offset = Abs::zero();
// Reorder the line from logical to visual order.
@@ -1223,8 +1243,17 @@ fn commit(
Item::Absolute(v) => {
offset += *v;
}
- Item::Fractional(v) => {
- offset += v.share(fr, remaining);
+ Item::Fractional(v, node) => {
+ let amount = v.share(fr, remaining);
+ if let Some((node, styles)) = node {
+ let region = Size::new(amount, full);
+ let pod = Regions::one(region, Axes::new(true, false));
+ let mut frame = node.layout(vt, *styles, pod)?.into_frame();
+ frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
+ push(&mut offset, frame);
+ } else {
+ offset += amount;
+ }
}
Item::Text(shaped) => {
let frame = shaped.build(vt, justification);
@@ -1233,27 +1262,6 @@ fn commit(
Item::Frame(frame) => {
push(&mut offset, frame.clone());
}
- Item::Repeat(repeat, styles) => {
- let before = offset;
- let fill = Fr::one().share(fr, remaining);
- let size = Size::new(fill, region.y);
- let pod = Regions::one(size, Axes::new(false, false));
- let frame = repeat.layout(vt, *styles, pod)?.into_frame();
- let width = frame.width();
- let count = (fill / width).floor();
- let remaining = fill % width;
- let apart = remaining / (count - 1.0);
- if count == 1.0 {
- offset += p.align.position(remaining);
- }
- if width > Abs::zero() {
- for _ in 0..(count as usize).min(1000) {
- push(&mut offset, frame.clone());
- offset += apart;
- }
- }
- offset = before + fill;
- }
}
}
@@ -1262,7 +1270,7 @@ fn commit(
remaining = Abs::zero();
}
- let size = Size::new(region.x, top + bottom);
+ let size = Size::new(width, top + bottom);
let mut output = Frame::new(size);
output.set_baseline(top);
diff --git a/library/src/layout/repeat.rs b/library/src/layout/repeat.rs
index 10cd1d25..ef630cf2 100644
--- a/library/src/layout/repeat.rs
+++ b/library/src/layout/repeat.rs
@@ -1,7 +1,9 @@
use crate::prelude::*;
+use super::AlignNode;
+
/// # Repeat
-/// Repeats content to fill a line.
+/// Repeats content to the available space.
///
/// This can be useful when implementing a custom index, reference, or outline.
///
@@ -10,7 +12,8 @@ use crate::prelude::*;
///
/// ## Example
/// ```example
-/// Sign on the dotted line: #repeat[.]
+/// Sign on the dotted line:
+/// #box(width: 1fr, repeat[.])
///
/// #set text(10pt)
/// #v(8pt, weak: true)
@@ -51,6 +54,34 @@ impl Layout for RepeatNode {
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
- self.0.layout(vt, styles, regions)
+ let pod = Regions::one(regions.size, Axes::new(false, false));
+ let piece = self.0.layout(vt, styles, pod)?.into_frame();
+ let align = styles.get(AlignNode::ALIGNS).x.resolve(styles);
+
+ let fill = regions.size.x;
+ let width = piece.width();
+ let count = (fill / width).floor();
+ let remaining = fill % width;
+ let apart = remaining / (count - 1.0);
+
+ let size = Size::new(regions.size.x, piece.height());
+ let mut frame = Frame::new(size);
+ if piece.has_baseline() {
+ frame.set_baseline(piece.baseline());
+ }
+
+ let mut offset = Abs::zero();
+ if count == 1.0 {
+ offset += align.position(remaining);
+ }
+
+ if width > Abs::zero() {
+ for _ in 0..(count as usize).min(1000) {
+ frame.push_frame(Point::with_x(offset), piece.clone());
+ offset += piece.width() + apart;
+ }
+ }
+
+ Ok(Fragment::frame(frame))
}
}
diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs
index 17f6b5be..295d7f2f 100644
--- a/library/src/layout/spacing.rs
+++ b/library/src/layout/spacing.rs
@@ -222,22 +222,22 @@ impl Behave for VNode {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Spacing {
/// Spacing specified in absolute terms and relative to the parent's size.
- Relative(Rel<Length>),
+ Rel(Rel<Length>),
/// Spacing specified as a fraction of the remaining free space in the
/// parent.
- Fractional(Fr),
+ Fr(Fr),
}
impl Spacing {
/// Whether this is fractional spacing.
pub fn is_fractional(self) -> bool {
- matches!(self, Self::Fractional(_))
+ matches!(self, Self::Fr(_))
}
/// Encode into a value.
pub fn encode(self) -> Value {
match self {
- Self::Relative(rel) => {
+ Self::Rel(rel) => {
if rel.rel.is_zero() {
Value::Length(rel.abs)
} else if rel.abs.is_zero() {
@@ -246,28 +246,28 @@ impl Spacing {
Value::Relative(rel)
}
}
- Self::Fractional(fr) => Value::Fraction(fr),
+ Self::Fr(fr) => Value::Fraction(fr),
}
}
}
impl From<Abs> for Spacing {
fn from(abs: Abs) -> Self {
- Self::Relative(abs.into())
+ Self::Rel(abs.into())
}
}
impl From<Em> for Spacing {
fn from(em: Em) -> Self {
- Self::Relative(Rel::new(Ratio::zero(), em.into()))
+ Self::Rel(Rel::new(Ratio::zero(), em.into()))
}
}
impl PartialOrd for Spacing {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
- (Self::Relative(a), Self::Relative(b)) => a.partial_cmp(b),
- (Self::Fractional(a), Self::Fractional(b)) => a.partial_cmp(b),
+ (Self::Rel(a), Self::Rel(b)) => a.partial_cmp(b),
+ (Self::Fr(a), Self::Fr(b)) => a.partial_cmp(b),
_ => None,
}
}
@@ -275,6 +275,6 @@ impl PartialOrd for Spacing {
castable! {
Spacing,
- v: Rel<Length> => Self::Relative(v),
- v: Fr => Self::Fractional(v),
+ v: Rel<Length> => Self::Rel(v),
+ v: Fr => Self::Fr(v),
}
diff --git a/library/src/layout/stack.rs b/library/src/layout/stack.rs
index 35a0ff6f..afcb3696 100644
--- a/library/src/layout/stack.rs
+++ b/library/src/layout/stack.rs
@@ -200,7 +200,7 @@ impl<'a> StackLayouter<'a> {
/// Add spacing along the spacing direction.
fn layout_spacing(&mut self, spacing: Spacing) {
match spacing {
- Spacing::Relative(v) => {
+ Spacing::Rel(v) => {
// Resolve the spacing and limit it to the remaining space.
let resolved = v
.resolve(self.styles)
@@ -213,7 +213,7 @@ impl<'a> StackLayouter<'a> {
self.used.main += limited;
self.items.push(StackItem::Absolute(resolved));
}
- Spacing::Fractional(v) => {
+ Spacing::Fr(v) => {
self.fr += v;
self.items.push(StackItem::Fractional(v));
}
diff --git a/library/src/layout/table.rs b/library/src/layout/table.rs
index 12d2455f..1ceea9b9 100644
--- a/library/src/layout/table.rs
+++ b/library/src/layout/table.rs
@@ -1,4 +1,4 @@
-use crate::layout::{AlignNode, GridNode, TrackSizing, TrackSizings};
+use crate::layout::{AlignNode, GridNode, Sizing, TrackSizings};
use crate::prelude::*;
/// # Table
@@ -63,9 +63,9 @@ use crate::prelude::*;
#[derive(Debug, Hash)]
pub struct TableNode {
/// Defines sizing for content rows and columns.
- pub tracks: Axes<Vec<TrackSizing>>,
+ pub tracks: Axes<Vec<Sizing>>,
/// Defines sizing of gutter rows and columns between content.
- pub gutter: Axes<Vec<TrackSizing>>,
+ pub gutter: Axes<Vec<Sizing>>,
/// The content to be arranged in the table.
pub cells: Vec<Content>,
}
@@ -134,10 +134,10 @@ impl TableNode {
fn field(&self, name: &str) -> Option<Value> {
match name {
- "columns" => Some(TrackSizing::encode_slice(&self.tracks.x)),
- "rows" => Some(TrackSizing::encode_slice(&self.tracks.y)),
- "column-gutter" => Some(TrackSizing::encode_slice(&self.gutter.x)),
- "row-gutter" => Some(TrackSizing::encode_slice(&self.gutter.y)),
+ "columns" => Some(Sizing::encode_slice(&self.tracks.x)),
+ "rows" => Some(Sizing::encode_slice(&self.tracks.y)),
+ "column-gutter" => Some(Sizing::encode_slice(&self.gutter.x)),
+ "row-gutter" => Some(Sizing::encode_slice(&self.gutter.y)),
"cells" => Some(Value::Array(
self.cells.iter().cloned().map(Value::Content).collect(),
)),
diff --git a/library/src/layout/terms.rs b/library/src/layout/terms.rs
index cf214084..b1d399db 100644
--- a/library/src/layout/terms.rs
+++ b/library/src/layout/terms.rs
@@ -1,4 +1,4 @@
-use crate::layout::{BlockNode, GridNode, HNode, ParNode, Spacing, TrackSizing};
+use crate::layout::{BlockNode, GridNode, HNode, ParNode, Sizing, Spacing};
use crate::prelude::*;
use crate::text::{SpaceNode, TextNode};
@@ -136,8 +136,8 @@ impl Layout for TermsNode {
GridNode {
tracks: Axes::with_x(vec![
- TrackSizing::Relative((indent + body_indent).into()),
- TrackSizing::Auto,
+ Sizing::Rel((indent + body_indent).into()),
+ Sizing::Auto,
]),
gutter: Axes::with_y(vec![gutter.into()]),
cells,
diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs
index 76dcdc2e..84af15cb 100644
--- a/library/src/math/mod.rs
+++ b/library/src/math/mod.rs
@@ -277,7 +277,7 @@ impl LayoutMath for Content {
}
if let Some(node) = self.to::<HNode>() {
- if let Spacing::Relative(rel) = node.amount {
+ if let Spacing::Rel(rel) = node.amount {
if rel.rel.is_zero() {
ctx.push(MathFragment::Spacing(rel.abs.resolve(ctx.styles())));
}
diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs
index 388021bb..d28a0f08 100644
--- a/library/src/meta/outline.rs
+++ b/library/src/meta/outline.rs
@@ -1,5 +1,7 @@
use super::HeadingNode;
-use crate::layout::{HNode, HideNode, ParbreakNode, RepeatNode, Spacing};
+use crate::layout::{
+ BoxNode, HNode, HideNode, ParbreakNode, RepeatNode, Sizing, Spacing,
+};
use crate::prelude::*;
use crate::text::{LinebreakNode, SpaceNode, TextNode};
@@ -180,10 +182,18 @@ impl Show for OutlineNode {
// Add filler symbols between the section name and page number.
if let Some(filler) = styles.get(Self::FILL) {
seq.push(SpaceNode.pack());
- seq.push(RepeatNode(filler.clone()).pack());
+ seq.push(
+ BoxNode {
+ body: RepeatNode(filler.clone()).pack(),
+ width: Sizing::Fr(Fr::one()),
+ height: Smart::Auto,
+ baseline: Rel::zero(),
+ }
+ .pack(),
+ );
seq.push(SpaceNode.pack());
} else {
- let amount = Spacing::Fractional(Fr::one());
+ let amount = Spacing::Fr(Fr::one());
seq.push(HNode { amount, weak: false }.pack());
}
diff --git a/src/doc.rs b/src/doc.rs
index 9fbd9436..64f7ae91 100644
--- a/src/doc.rs
+++ b/src/doc.rs
@@ -86,7 +86,7 @@ impl Frame {
}
/// Whether the frame has a non-default baseline.
- pub fn has_baseline(&mut self) -> bool {
+ pub fn has_baseline(&self) -> bool {
self.baseline.is_some()
}
diff --git a/tests/ref/layout/container.png b/tests/ref/layout/container.png
index f82df108..b825471c 100644
--- a/tests/ref/layout/container.png
+++ b/tests/ref/layout/container.png
Binary files differ
diff --git a/tests/ref/layout/repeat.png b/tests/ref/layout/repeat.png
index fcd52987..e6a27ad9 100644
--- a/tests/ref/layout/repeat.png
+++ b/tests/ref/layout/repeat.png
Binary files differ
diff --git a/tests/typ/layout/container.typ b/tests/typ/layout/container.typ
index c6928074..0b30c4e1 100644
--- a/tests/typ/layout/container.typ
+++ b/tests/typ/layout/container.typ
@@ -10,6 +10,10 @@ Spaced \
Apart
---
+// Test fr box.
+Hello #box(width: 1fr, rect(height: 0.7em, width: 100%)) World
+
+---
// Test block over multiple pages.
#set page(height: 60pt)
diff --git a/tests/typ/layout/repeat.typ b/tests/typ/layout/repeat.typ
index 05b055af..03642164 100644
--- a/tests/typ/layout/repeat.typ
+++ b/tests/typ/layout/repeat.typ
@@ -12,28 +12,28 @@
)
#for section in sections [
- #section.at(0) #repeat[.] #section.at(1) \
+ #section.at(0) #box(width: 1fr, repeat[.]) #section.at(1) \
]
---
// Test dots with RTL.
#set text(lang: "ar")
-مقدمة #repeat[.] 15
+مقدمة #box(width: 1fr, repeat[.]) 15
---
// Test empty repeat.
-A #repeat[] B
+A #box(width: 1fr, repeat[]) B
---
-// Test spaceless repeat.
-A#repeat(rect(width: 2.5em, height: 1em))B
+// Test unboxed repeat.
+#repeat(rect(width: 2em, height: 1em))
---
// Test single repeat in both directions.
-A#repeat(rect(width: 6em, height: 0.7em))B
+A#box(width: 1fr, repeat(rect(width: 6em, height: 0.7em)))B
#set align(center)
-A#repeat(rect(width: 6em, height: 0.7em))B
+A#box(width: 1fr, repeat(rect(width: 6em, height: 0.7em)))B
#set text(dir: rtl)
-ريجين#repeat(rect(width: 4em, height: 0.7em))سون
+ريجين#box(width: 1fr, repeat(rect(width: 4em, height: 0.7em)))سون