Support fractional width for box
This commit is contained in:
parent
d99359dede
commit
fd90736fb6
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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(),
|
||||
)),
|
||||
|
@ -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,
|
||||
|
@ -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())));
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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 |
@ -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.
|
||||
|
||||
|
@ -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)))سون
|
||||
|
Loading…
x
Reference in New Issue
Block a user