Support fractional width for box

This commit is contained in:
Laurenz 2023-02-12 22:04:27 +01:00
parent d99359dede
commit fd90736fb6
19 changed files with 255 additions and 185 deletions

View File

@ -44,7 +44,7 @@ pub struct BoxNode {
/// The content to be sized. /// The content to be sized.
pub body: Content, pub body: Content,
/// The box's width. /// The box's width.
pub width: Smart<Rel<Length>>, pub width: Sizing,
/// The box's height. /// The box's height.
pub height: Smart<Rel<Length>>, pub height: Smart<Rel<Length>>,
/// The box's baseline shift. /// The box's baseline shift.
@ -76,8 +76,14 @@ impl Layout for BoxNode {
styles: StyleChain, styles: StyleChain,
regions: Regions, regions: Regions,
) -> SourceResult<Fragment> { ) -> 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. // 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 let size = sizing
.resolve(styles) .resolve(styles)
.zip(regions.base()) .zip(regions.base())
@ -196,3 +202,50 @@ impl Layout for BlockNode {
self.0.layout(vt, styles, regions) 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),
}
}
}

View File

@ -1,7 +1,7 @@
use std::str::FromStr; use std::str::FromStr;
use crate::compute::{Numbering, NumberingPattern}; use crate::compute::{Numbering, NumberingPattern};
use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing}; use crate::layout::{BlockNode, GridNode, ParNode, Sizing, Spacing};
use crate::prelude::*; use crate::prelude::*;
/// # Numbered List /// # Numbered List
@ -193,10 +193,10 @@ impl Layout for EnumNode {
GridNode { GridNode {
tracks: Axes::with_x(vec![ tracks: Axes::with_x(vec![
TrackSizing::Relative(indent.into()), Sizing::Rel(indent.into()),
TrackSizing::Auto, Sizing::Auto,
TrackSizing::Relative(body_indent.into()), Sizing::Rel(body_indent.into()),
TrackSizing::Auto, Sizing::Auto,
]), ]),
gutter: Axes::with_y(vec![gutter.into()]), gutter: Axes::with_y(vec![gutter.into()]),
cells, cells,

View File

@ -113,11 +113,11 @@ impl<'a> FlowLayouter<'a> {
/// Layout vertical spacing. /// Layout vertical spacing.
fn layout_spacing(&mut self, node: VNode, styles: StyleChain) { fn layout_spacing(&mut self, node: VNode, styles: StyleChain) {
self.layout_item(match node.amount { self.layout_item(match node.amount {
Spacing::Relative(v) => FlowItem::Absolute( Spacing::Rel(v) => FlowItem::Absolute(
v.resolve(styles).relative_to(self.full.y), v.resolve(styles).relative_to(self.full.y),
node.weakness > 0, node.weakness > 0,
), ),
Spacing::Fractional(v) => FlowItem::Fractional(v), Spacing::Fr(v) => FlowItem::Fractional(v),
}); });
} }

View File

@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextNode;
use super::Spacing; use super::Sizing;
/// # Grid /// # Grid
/// Arrange content in a grid. /// Arrange content in a grid.
@ -94,9 +94,9 @@ use super::Spacing;
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct GridNode { pub struct GridNode {
/// Defines sizing for content rows and columns. /// 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. /// 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. /// The content to be arranged in a grid.
pub cells: Vec<Content>, pub cells: Vec<Content>,
} }
@ -122,10 +122,10 @@ impl GridNode {
fn field(&self, name: &str) -> Option<Value> { fn field(&self, name: &str) -> Option<Value> {
match name { match name {
"columns" => Some(TrackSizing::encode_slice(&self.tracks.x)), "columns" => Some(Sizing::encode_slice(&self.tracks.x)),
"rows" => Some(TrackSizing::encode_slice(&self.tracks.y)), "rows" => Some(Sizing::encode_slice(&self.tracks.y)),
"column-gutter" => Some(TrackSizing::encode_slice(&self.gutter.x)), "column-gutter" => Some(Sizing::encode_slice(&self.gutter.x)),
"row-gutter" => Some(TrackSizing::encode_slice(&self.gutter.y)), "row-gutter" => Some(Sizing::encode_slice(&self.gutter.y)),
"cells" => Some(Value::Array( "cells" => Some(Value::Array(
self.cells.iter().cloned().map(Value::Content).collect(), 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. /// Track sizing definitions.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct TrackSizings(pub Vec<TrackSizing>); pub struct TrackSizings(pub Vec<Sizing>);
castable! { castable! {
TrackSizings, TrackSizings,
sizing: TrackSizing => Self(vec![sizing]), sizing: Sizing => Self(vec![sizing]),
count: NonZeroUsize => Self(vec![TrackSizing::Auto; count.get()]), count: NonZeroUsize => Self(vec![Sizing::Auto; count.get()]),
values: Array => Self(values values: Array => Self(values
.into_iter() .into_iter()
.filter_map(|v| v.cast().ok()) .filter_map(|v| v.cast().ok())
@ -207,10 +171,10 @@ castable! {
} }
castable! { castable! {
TrackSizing, Sizing,
_: AutoValue => Self::Auto, _: AutoValue => Self::Auto,
v: Rel<Length> => Self::Relative(v), v: Rel<Length> => Self::Rel(v),
v: Fr => Self::Fractional(v), v: Fr => Self::Fr(v),
} }
/// Performs grid layout. /// Performs grid layout.
@ -224,9 +188,9 @@ struct GridLayouter<'a, 'v> {
/// Whether this grid has gutters. /// Whether this grid has gutters.
has_gutter: bool, has_gutter: bool,
/// The column tracks including gutter tracks. /// The column tracks including gutter tracks.
cols: Vec<TrackSizing>, cols: Vec<Sizing>,
/// The row tracks including gutter tracks. /// The row tracks including gutter tracks.
rows: Vec<TrackSizing>, rows: Vec<Sizing>,
/// The regions to layout children into. /// The regions to layout children into.
regions: Regions<'a>, regions: Regions<'a>,
/// The inherited styles. /// The inherited styles.
@ -259,8 +223,8 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
/// This prepares grid layout by unifying content and gutter tracks. /// This prepares grid layout by unifying content and gutter tracks.
fn new( fn new(
vt: &'a mut Vt<'v>, vt: &'a mut Vt<'v>,
tracks: Axes<&[TrackSizing]>, tracks: Axes<&[Sizing]>,
gutter: Axes<&[TrackSizing]>, gutter: Axes<&[Sizing]>,
cells: &'a [Content], cells: &'a [Content],
regions: Regions<'a>, regions: Regions<'a>,
styles: StyleChain<'a>, styles: StyleChain<'a>,
@ -281,8 +245,8 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
}; };
let has_gutter = gutter.any(|tracks| !tracks.is_empty()); let has_gutter = gutter.any(|tracks| !tracks.is_empty());
let auto = TrackSizing::Auto; let auto = Sizing::Auto;
let zero = TrackSizing::Relative(Rel::zero()); let zero = Sizing::Rel(Rel::zero());
let get_or = |tracks: &[_], idx, default| { let get_or = |tracks: &[_], idx, default| {
tracks.get(idx).or(tracks.last()).copied().unwrap_or(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] { match self.rows[y] {
TrackSizing::Auto => self.layout_auto_row(y)?, Sizing::Auto => self.layout_auto_row(y)?,
TrackSizing::Relative(v) => self.layout_relative_row(v, y)?, Sizing::Rel(v) => self.layout_relative_row(v, y)?,
TrackSizing::Fractional(v) => { Sizing::Fr(v) => {
self.lrows.push(Row::Fr(v, y)); self.lrows.push(Row::Fr(v, y));
self.fr += v; self.fr += v;
} }
@ -377,14 +341,14 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
// fractional tracks. // fractional tracks.
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
match col { match col {
TrackSizing::Auto => {} Sizing::Auto => {}
TrackSizing::Relative(v) => { Sizing::Rel(v) => {
let resolved = let resolved =
v.resolve(self.styles).relative_to(self.regions.base().x); v.resolve(self.styles).relative_to(self.regions.base().x);
*rcol = resolved; *rcol = resolved;
rel += 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 // Determine size of auto columns by laying out all cells in those
// columns, measuring them and finding the largest one. // columns, measuring them and finding the largest one.
for (x, &col) in self.cols.iter().enumerate() { for (x, &col) in self.cols.iter().enumerate() {
if col != TrackSizing::Auto { if col != Sizing::Auto {
continue; continue;
} }
@ -428,7 +392,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
// For relative rows, we can already resolve the correct // For relative rows, we can already resolve the correct
// base and for auto and fr we could only guess anyway. // base and for auto and fr we could only guess anyway.
let height = match self.rows[y] { let height = match self.rows[y] {
TrackSizing::Relative(v) => { Sizing::Rel(v) => {
v.resolve(self.styles).relative_to(self.regions.base().y) v.resolve(self.styles).relative_to(self.regions.base().y)
} }
_ => 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) { 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); *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) { for (&col, &rcol) in self.cols.iter().zip(&self.rcols) {
// Remove an auto column if it is not overlarge (rcol <= fair), // Remove an auto column if it is not overlarge (rcol <= fair),
// but also hasn't already been removed (rcol > last). // 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; redistribute -= rcol;
overlarge -= 1; overlarge -= 1;
changed = true; changed = true;
@ -489,7 +453,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
// Redistribute space fairly among overlarge columns. // Redistribute space fairly among overlarge columns.
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { 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; *rcol = fair;
} }
} }
@ -597,7 +561,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
if let Some(cell) = self.cell(x, y) { if let Some(cell) = self.cell(x, y) {
let size = Size::new(rcol, height); let size = Size::new(rcol, height);
let mut pod = Regions::one(size, Axes::splat(true)); 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; pod.full = self.regions.full;
} }
let frame = cell.layout(self.vt, self.styles, pod)?.into_frame(); let frame = cell.layout(self.vt, self.styles, pod)?.into_frame();

View File

@ -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::prelude::*;
use crate::text::TextNode; use crate::text::TextNode;
@ -147,10 +147,10 @@ impl Layout for ListNode {
GridNode { GridNode {
tracks: Axes::with_x(vec![ tracks: Axes::with_x(vec![
TrackSizing::Relative(indent.into()), Sizing::Rel(indent.into()),
TrackSizing::Auto, Sizing::Auto,
TrackSizing::Relative(body_indent.into()), Sizing::Rel(body_indent.into()),
TrackSizing::Auto, Sizing::Auto,
]), ]),
gutter: Axes::with_y(vec![gutter.into()]), gutter: Axes::with_y(vec![gutter.into()]),
cells, cells,

View File

@ -227,9 +227,16 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
fn accept( fn accept(
&mut self, &mut self,
content: &'a Content, mut content: &'a Content,
styles: StyleChain<'a>, styles: StyleChain<'a>,
) -> SourceResult<()> { ) -> 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. // Prepare only if this is the first application for this node.
if let Some(node) = content.with::<dyn Prepare>() { if let Some(node) = content.with::<dyn Prepare>() {
if !content.is_prepared() { if !content.is_prepared() {
@ -470,22 +477,15 @@ impl<'a> ParBuilder<'a> {
if content.is::<SpaceNode>() if content.is::<SpaceNode>()
|| content.is::<TextNode>() || content.is::<TextNode>()
|| content.is::<HNode>() || content.is::<HNode>()
|| content.is::<SmartQuoteNode>()
|| content.is::<LinebreakNode>() || content.is::<LinebreakNode>()
|| content.is::<BoxNode>() || content.is::<SmartQuoteNode>()
|| content.is::<RepeatNode>()
|| content.to::<FormulaNode>().map_or(false, |node| !node.block) || content.to::<FormulaNode>().map_or(false, |node| !node.block)
|| content.is::<BoxNode>()
{ {
self.0.push(content.clone(), styles); self.0.push(content.clone(), styles);
return true; 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 false
} }

View File

@ -4,8 +4,9 @@ use xi_unicode::LineBreakIterator;
use typst::model::Key; use typst::model::Key;
use super::{HNode, RepeatNode, Spacing}; use super::{BoxNode, HNode, Sizing, Spacing};
use crate::layout::AlignNode; use crate::layout::AlignNode;
use crate::math::FormulaNode;
use crate::prelude::*; use crate::prelude::*;
use crate::text::{ use crate::text::{
shape, LinebreakNode, Quoter, Quotes, ShapedText, SmartQuoteNode, SpaceNode, TextNode, shape, LinebreakNode, Quoter, Quotes, ShapedText, SmartQuoteNode, SpaceNode, TextNode,
@ -330,8 +331,10 @@ enum Segment<'a> {
Text(usize), Text(usize),
/// Horizontal spacing between other segments. /// Horizontal spacing between other segments.
Spacing(Spacing), Spacing(Spacing),
/// Arbitrary inline-level content. /// A math formula.
Inline(&'a Content), Formula(&'a FormulaNode),
/// A box with arbitrary content.
Box(&'a BoxNode),
} }
impl Segment<'_> { impl Segment<'_> {
@ -340,7 +343,8 @@ impl Segment<'_> {
match *self { match *self {
Self::Text(len) => len, Self::Text(len) => len,
Self::Spacing(_) => SPACING_REPLACE.len_utf8(), 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 spacing between other items.
Absolute(Abs), Absolute(Abs),
/// Fractional spacing between other items. /// Fractional spacing between other items.
Fractional(Fr), Fractional(Fr, Option<(&'a BoxNode, StyleChain<'a>)>),
/// Layouted inline-level content. /// Layouted inline-level content.
Frame(Frame), Frame(Frame),
/// A repeating node that fills the remaining space in a line.
Repeat(&'a RepeatNode, StyleChain<'a>),
} }
impl<'a> Item<'a> { impl<'a> Item<'a> {
@ -373,8 +375,8 @@ impl<'a> Item<'a> {
fn len(&self) -> usize { fn len(&self) -> usize {
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(_) | Self::Repeat(_, _) => NODE_REPLACE.len_utf8(), Self::Frame(_) => NODE_REPLACE.len_utf8(),
} }
} }
@ -384,7 +386,7 @@ impl<'a> Item<'a> {
Self::Text(shaped) => shaped.width, Self::Text(shaped) => shaped.width,
Self::Absolute(v) => *v, Self::Absolute(v) => *v,
Self::Frame(frame) => frame.width(), 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 { fn fr(&self) -> Fr {
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(Fr::one()),
_ => None, _ => None,
}) })
.sum() .sum()
@ -530,6 +531,9 @@ fn collect<'a>(
full.push_str(&node.0); full.push_str(&node.0);
} }
Segment::Text(full.len() - prev) 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>() { } else if let Some(node) = child.to::<LinebreakNode>() {
let c = if node.justify { '\u{2028}' } else { '\n' }; let c = if node.justify { '\u{2028}' } else { '\n' };
full.push(c); full.push(c);
@ -557,12 +561,18 @@ fn collect<'a>(
full.push(if node.double { '"' } else { '\'' }); full.push(if node.double { '"' } else { '\'' });
} }
Segment::Text(full.len() - prev) Segment::Text(full.len() - prev)
} else if let Some(&node) = child.to::<HNode>() { } else if let Some(node) = child.to::<FormulaNode>() {
full.push(SPACING_REPLACE);
Segment::Spacing(node.amount)
} else {
full.push(NODE_REPLACE); 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() { if let Some(last) = full.chars().last() {
@ -614,20 +624,26 @@ fn prepare<'a>(
shape_range(&mut items, vt, &bidi, cursor..end, styles); shape_range(&mut items, vt, &bidi, cursor..end, styles);
} }
Segment::Spacing(spacing) => match spacing { Segment::Spacing(spacing) => match spacing {
Spacing::Relative(v) => { Spacing::Rel(v) => {
let resolved = v.resolve(styles).relative_to(region.x); let resolved = v.resolve(styles).relative_to(region.x);
items.push(Item::Absolute(resolved)); items.push(Item::Absolute(resolved));
} }
Spacing::Fractional(v) => { Spacing::Fr(v) => {
items.push(Item::Fractional(v)); items.push(Item::Fractional(v, None));
} }
}, },
Segment::Inline(inline) => { Segment::Formula(formula) => {
if let Some(repeat) = inline.to::<RepeatNode>() { let pod = Regions::one(region, Axes::splat(false));
items.push(Item::Repeat(repeat, styles)); 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 { } else {
let pod = Regions::one(region, Axes::splat(false)); 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))); frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
items.push(Item::Frame(frame)); items.push(Item::Frame(frame));
} }
@ -1111,20 +1127,23 @@ fn finalize(
vt: &mut Vt, vt: &mut Vt,
p: &Preparation, p: &Preparation,
lines: &[Line], lines: &[Line],
mut region: Size, region: Size,
expand: bool, expand: bool,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
// Determine the paragraph's width: Full width of the region if we // Determine the paragraph's width: Full width of the region if we
// should expand or there's fractional spacing, fit-to-width otherwise. // 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. // Stack the lines into one frame per region.
let mut frames: Vec<Frame> = lines let mut frames: Vec<Frame> = lines
.iter() .iter()
.map(|line| commit(vt, p, line, region)) .map(|line| commit(vt, p, line, width, region.y))
.collect::<SourceResult<_>>()?; .collect::<SourceResult<_>>()?;
// Prevent orphans. // Prevent orphans.
@ -1159,9 +1178,10 @@ fn commit(
vt: &mut Vt, vt: &mut Vt,
p: &Preparation, p: &Preparation,
line: &Line, line: &Line,
region: Size, width: Abs,
full: Abs,
) -> SourceResult<Frame> { ) -> SourceResult<Frame> {
let mut remaining = region.x - line.width; let mut remaining = width - line.width;
let mut offset = Abs::zero(); let mut offset = Abs::zero();
// Reorder the line from logical to visual order. // Reorder the line from logical to visual order.
@ -1223,8 +1243,17 @@ fn commit(
Item::Absolute(v) => { Item::Absolute(v) => {
offset += *v; offset += *v;
} }
Item::Fractional(v) => { Item::Fractional(v, node) => {
offset += v.share(fr, remaining); 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) => { Item::Text(shaped) => {
let frame = shaped.build(vt, justification); let frame = shaped.build(vt, justification);
@ -1233,27 +1262,6 @@ fn commit(
Item::Frame(frame) => { Item::Frame(frame) => {
push(&mut offset, frame.clone()); 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(); remaining = Abs::zero();
} }
let size = Size::new(region.x, top + bottom); let size = Size::new(width, top + bottom);
let mut output = Frame::new(size); let mut output = Frame::new(size);
output.set_baseline(top); output.set_baseline(top);

View File

@ -1,7 +1,9 @@
use crate::prelude::*; use crate::prelude::*;
use super::AlignNode;
/// # Repeat /// # 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. /// This can be useful when implementing a custom index, reference, or outline.
/// ///
@ -10,7 +12,8 @@ use crate::prelude::*;
/// ///
/// ## Example /// ## Example
/// ```example /// ```example
/// Sign on the dotted line: #repeat[.] /// Sign on the dotted line:
/// #box(width: 1fr, repeat[.])
/// ///
/// #set text(10pt) /// #set text(10pt)
/// #v(8pt, weak: true) /// #v(8pt, weak: true)
@ -51,6 +54,34 @@ impl Layout for RepeatNode {
styles: StyleChain, styles: StyleChain,
regions: Regions, regions: Regions,
) -> SourceResult<Fragment> { ) -> 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))
} }
} }

View File

@ -222,22 +222,22 @@ impl Behave for VNode {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Spacing { pub enum Spacing {
/// Spacing specified in absolute terms and relative to the parent's size. /// 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 /// Spacing specified as a fraction of the remaining free space in the
/// parent. /// parent.
Fractional(Fr), Fr(Fr),
} }
impl Spacing { impl Spacing {
/// Whether this is fractional spacing. /// Whether this is fractional spacing.
pub fn is_fractional(self) -> bool { pub fn is_fractional(self) -> bool {
matches!(self, Self::Fractional(_)) matches!(self, Self::Fr(_))
} }
/// Encode into a value. /// Encode into a value.
pub fn encode(self) -> Value { pub fn encode(self) -> Value {
match self { match self {
Self::Relative(rel) => { Self::Rel(rel) => {
if rel.rel.is_zero() { if rel.rel.is_zero() {
Value::Length(rel.abs) Value::Length(rel.abs)
} else if rel.abs.is_zero() { } else if rel.abs.is_zero() {
@ -246,28 +246,28 @@ impl Spacing {
Value::Relative(rel) Value::Relative(rel)
} }
} }
Self::Fractional(fr) => Value::Fraction(fr), Self::Fr(fr) => Value::Fraction(fr),
} }
} }
} }
impl From<Abs> for Spacing { impl From<Abs> for Spacing {
fn from(abs: Abs) -> Self { fn from(abs: Abs) -> Self {
Self::Relative(abs.into()) Self::Rel(abs.into())
} }
} }
impl From<Em> for Spacing { impl From<Em> for Spacing {
fn from(em: Em) -> Self { 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 { impl PartialOrd for Spacing {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) { match (self, other) {
(Self::Relative(a), Self::Relative(b)) => a.partial_cmp(b), (Self::Rel(a), Self::Rel(b)) => a.partial_cmp(b),
(Self::Fractional(a), Self::Fractional(b)) => a.partial_cmp(b), (Self::Fr(a), Self::Fr(b)) => a.partial_cmp(b),
_ => None, _ => None,
} }
} }
@ -275,6 +275,6 @@ impl PartialOrd for Spacing {
castable! { castable! {
Spacing, Spacing,
v: Rel<Length> => Self::Relative(v), v: Rel<Length> => Self::Rel(v),
v: Fr => Self::Fractional(v), v: Fr => Self::Fr(v),
} }

View File

@ -200,7 +200,7 @@ impl<'a> StackLayouter<'a> {
/// Add spacing along the spacing direction. /// Add spacing along the spacing direction.
fn layout_spacing(&mut self, spacing: Spacing) { fn layout_spacing(&mut self, spacing: Spacing) {
match spacing { match spacing {
Spacing::Relative(v) => { Spacing::Rel(v) => {
// Resolve the spacing and limit it to the remaining space. // Resolve the spacing and limit it to the remaining space.
let resolved = v let resolved = v
.resolve(self.styles) .resolve(self.styles)
@ -213,7 +213,7 @@ impl<'a> StackLayouter<'a> {
self.used.main += limited; self.used.main += limited;
self.items.push(StackItem::Absolute(resolved)); self.items.push(StackItem::Absolute(resolved));
} }
Spacing::Fractional(v) => { Spacing::Fr(v) => {
self.fr += v; self.fr += v;
self.items.push(StackItem::Fractional(v)); self.items.push(StackItem::Fractional(v));
} }

View File

@ -1,4 +1,4 @@
use crate::layout::{AlignNode, GridNode, TrackSizing, TrackSizings}; use crate::layout::{AlignNode, GridNode, Sizing, TrackSizings};
use crate::prelude::*; use crate::prelude::*;
/// # Table /// # Table
@ -63,9 +63,9 @@ use crate::prelude::*;
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct TableNode { pub struct TableNode {
/// Defines sizing for content rows and columns. /// 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. /// 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. /// The content to be arranged in the table.
pub cells: Vec<Content>, pub cells: Vec<Content>,
} }
@ -134,10 +134,10 @@ impl TableNode {
fn field(&self, name: &str) -> Option<Value> { fn field(&self, name: &str) -> Option<Value> {
match name { match name {
"columns" => Some(TrackSizing::encode_slice(&self.tracks.x)), "columns" => Some(Sizing::encode_slice(&self.tracks.x)),
"rows" => Some(TrackSizing::encode_slice(&self.tracks.y)), "rows" => Some(Sizing::encode_slice(&self.tracks.y)),
"column-gutter" => Some(TrackSizing::encode_slice(&self.gutter.x)), "column-gutter" => Some(Sizing::encode_slice(&self.gutter.x)),
"row-gutter" => Some(TrackSizing::encode_slice(&self.gutter.y)), "row-gutter" => Some(Sizing::encode_slice(&self.gutter.y)),
"cells" => Some(Value::Array( "cells" => Some(Value::Array(
self.cells.iter().cloned().map(Value::Content).collect(), self.cells.iter().cloned().map(Value::Content).collect(),
)), )),

View File

@ -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::prelude::*;
use crate::text::{SpaceNode, TextNode}; use crate::text::{SpaceNode, TextNode};
@ -136,8 +136,8 @@ impl Layout for TermsNode {
GridNode { GridNode {
tracks: Axes::with_x(vec![ tracks: Axes::with_x(vec![
TrackSizing::Relative((indent + body_indent).into()), Sizing::Rel((indent + body_indent).into()),
TrackSizing::Auto, Sizing::Auto,
]), ]),
gutter: Axes::with_y(vec![gutter.into()]), gutter: Axes::with_y(vec![gutter.into()]),
cells, cells,

View File

@ -277,7 +277,7 @@ impl LayoutMath for Content {
} }
if let Some(node) = self.to::<HNode>() { 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() { if rel.rel.is_zero() {
ctx.push(MathFragment::Spacing(rel.abs.resolve(ctx.styles()))); ctx.push(MathFragment::Spacing(rel.abs.resolve(ctx.styles())));
} }

View File

@ -1,5 +1,7 @@
use super::HeadingNode; 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::prelude::*;
use crate::text::{LinebreakNode, SpaceNode, TextNode}; use crate::text::{LinebreakNode, SpaceNode, TextNode};
@ -180,10 +182,18 @@ impl Show for OutlineNode {
// Add filler symbols between the section name and page number. // Add filler symbols between the section name and page number.
if let Some(filler) = styles.get(Self::FILL) { if let Some(filler) = styles.get(Self::FILL) {
seq.push(SpaceNode.pack()); 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()); seq.push(SpaceNode.pack());
} else { } else {
let amount = Spacing::Fractional(Fr::one()); let amount = Spacing::Fr(Fr::one());
seq.push(HNode { amount, weak: false }.pack()); seq.push(HNode { amount, weak: false }.pack());
} }

View File

@ -86,7 +86,7 @@ impl Frame {
} }
/// Whether the frame has a non-default baseline. /// 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() self.baseline.is_some()
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -9,6 +9,10 @@ Spaced \
#box(height: 0.5cm) \ #box(height: 0.5cm) \
Apart Apart
---
// Test fr box.
Hello #box(width: 1fr, rect(height: 0.7em, width: 100%)) World
--- ---
// Test block over multiple pages. // Test block over multiple pages.

View File

@ -12,28 +12,28 @@
) )
#for section in sections [ #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. // Test dots with RTL.
#set text(lang: "ar") #set text(lang: "ar")
مقدمة #repeat[.] 15 مقدمة #box(width: 1fr, repeat[.]) 15
--- ---
// Test empty repeat. // Test empty repeat.
A #repeat[] B A #box(width: 1fr, repeat[]) B
--- ---
// Test spaceless repeat. // Test unboxed repeat.
A#repeat(rect(width: 2.5em, height: 1em))B #repeat(rect(width: 2em, height: 1em))
--- ---
// Test single repeat in both directions. // 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) #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) #set text(dir: rtl)
ريجين#repeat(rect(width: 4em, height: 0.7em))سون ريجين#box(width: 1fr, repeat(rect(width: 4em, height: 0.7em)))سون