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.
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),
}
}
}

View File

@ -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,

View File

@ -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),
});
}

View File

@ -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();

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::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,

View File

@ -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
}

View File

@ -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);

View File

@ -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))
}
}

View File

@ -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),
}

View File

@ -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));
}

View File

@ -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(),
)),

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::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,

View File

@ -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())));
}

View File

@ -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());
}

View File

@ -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()
}

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) \
Apart
---
// Test fr box.
Hello #box(width: 1fr, rect(height: 0.7em, width: 100%)) World
---
// Test block over multiple pages.

View File

@ -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)))سون