mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Refactor grid row layout
This commit is contained in:
parent
a61ee46ed2
commit
e2cdda67dc
@ -18,6 +18,11 @@ impl Length {
|
|||||||
Self { raw: N64::from(0.0) }
|
Self { raw: N64::from(0.0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The inifinite length.
|
||||||
|
pub fn inf() -> Self {
|
||||||
|
Self { raw: N64::from(f64::INFINITY) }
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a length from a number of points.
|
/// Create a length from a number of points.
|
||||||
pub fn pt(pt: f64) -> Self {
|
pub fn pt(pt: f64) -> Self {
|
||||||
Self::with_unit(pt, LengthUnit::Pt)
|
Self::with_unit(pt, LengthUnit::Pt)
|
||||||
@ -203,6 +208,12 @@ impl Sum for Length {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Sum<&'a Length> for Length {
|
||||||
|
fn sum<I: Iterator<Item = &'a Length>>(iter: I) -> Self {
|
||||||
|
iter.copied().fold(Length::zero(), Add::add)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Different units of length measurement.
|
/// Different units of length measurement.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
pub enum LengthUnit {
|
pub enum LengthUnit {
|
||||||
|
@ -57,6 +57,13 @@ impl Size {
|
|||||||
SpecAxis::Vertical => Gen::new(self.width, self.height),
|
SpecAxis::Vertical => Gen::new(self.width, self.height),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find the largest contained size that satisfies the given `aspect` ratio.
|
||||||
|
pub fn with_aspect(self, aspect: f64) -> Self {
|
||||||
|
let width = self.width.min(aspect * self.height);
|
||||||
|
let height = width / aspect;
|
||||||
|
Size::new(width, height)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Get<SpecAxis> for Size {
|
impl Get<SpecAxis> for Size {
|
||||||
|
@ -6,7 +6,7 @@ use crate::image::ImageId;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// A finished layout with elements at fixed positions.
|
/// A finished layout with elements at fixed positions.
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Frame {
|
pub struct Frame {
|
||||||
/// The size of the frame.
|
/// The size of the frame.
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
@ -31,8 +31,12 @@ impl Frame {
|
|||||||
/// Add all elements of another frame, placing them relative to the given
|
/// Add all elements of another frame, placing them relative to the given
|
||||||
/// position.
|
/// position.
|
||||||
pub fn push_frame(&mut self, pos: Point, subframe: Self) {
|
pub fn push_frame(&mut self, pos: Point, subframe: Self) {
|
||||||
for (subpos, element) in subframe.elements {
|
if pos == Point::zero() && self.elements.is_empty() {
|
||||||
self.push(pos + subpos, element);
|
self.elements = subframe.elements;
|
||||||
|
} else {
|
||||||
|
for (subpos, element) in subframe.elements {
|
||||||
|
self.push(pos + subpos, element);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,27 @@ pub struct GridNode {
|
|||||||
pub children: Vec<AnyNode>,
|
pub children: Vec<AnyNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines how to size a grid cell along an axis.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Hash)]
|
||||||
|
pub enum TrackSizing {
|
||||||
|
/// Fit the cell to its contents.
|
||||||
|
Auto,
|
||||||
|
/// A length stated in absolute values and fractions of the parent's size.
|
||||||
|
Linear(Linear),
|
||||||
|
/// A length that is the fraction of the remaining free space in the parent.
|
||||||
|
Fractional(Fractional),
|
||||||
|
}
|
||||||
|
|
||||||
impl Layout for GridNode {
|
impl Layout for GridNode {
|
||||||
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> {
|
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> {
|
||||||
GridLayouter::new(self, regions.clone()).layout(ctx)
|
// Prepare grid layout by unifying content and gutter tracks.
|
||||||
|
let mut layouter = GridLayouter::new(self, regions.clone());
|
||||||
|
|
||||||
|
// Determine all column sizes.
|
||||||
|
layouter.measure_columns(ctx);
|
||||||
|
|
||||||
|
// Layout the grid row-by-row.
|
||||||
|
layouter.layout(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,125 +46,123 @@ impl From<GridNode> for AnyNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
/// Performs grid layout.
|
||||||
struct GridLayouter<'a> {
|
struct GridLayouter<'a> {
|
||||||
|
/// The axis of the cross direction.
|
||||||
cross: SpecAxis,
|
cross: SpecAxis,
|
||||||
|
/// The axis of the main direction.
|
||||||
main: SpecAxis,
|
main: SpecAxis,
|
||||||
|
/// The column tracks including gutter tracks.
|
||||||
cols: Vec<TrackSizing>,
|
cols: Vec<TrackSizing>,
|
||||||
|
/// The row tracks including gutter tracks.
|
||||||
rows: Vec<TrackSizing>,
|
rows: Vec<TrackSizing>,
|
||||||
cells: Vec<Cell<'a>>,
|
/// The children of the grid.
|
||||||
|
children: &'a [AnyNode],
|
||||||
|
/// The region to layout into.
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
|
/// Resolved column sizes.
|
||||||
rcols: Vec<Length>,
|
rcols: Vec<Length>,
|
||||||
rrows: Vec<(usize, Option<Length>, Option<Vec<Option<Frame>>>)>,
|
/// The full main size of the current region.
|
||||||
|
full: Length,
|
||||||
|
/// The used-up size of the current region. The cross size is determined
|
||||||
|
/// once after columns are resolved and not touched again.
|
||||||
|
used: Gen<Length>,
|
||||||
|
/// The sum of fractional ratios in the current region.
|
||||||
|
fr: Fractional,
|
||||||
|
/// Rows in the current region.
|
||||||
|
lrows: Vec<Row>,
|
||||||
|
/// Frames for finished regions.
|
||||||
finished: Vec<Frame>,
|
finished: Vec<Frame>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
/// Produced by initial row layout, auto and linear rows are already finished,
|
||||||
enum Cell<'a> {
|
/// fractional rows not yet.
|
||||||
Node(&'a AnyNode),
|
enum Row {
|
||||||
Gutter,
|
/// Finished row frame of auto or linear row.
|
||||||
|
Frame(Frame),
|
||||||
|
/// Ratio of a fractional row and y index of the track.
|
||||||
|
Fr(Fractional, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GridLayouter<'a> {
|
impl<'a> GridLayouter<'a> {
|
||||||
fn new(grid: &'a GridNode, regions: Regions) -> Self {
|
/// Prepare grid layout by unifying content and gutter tracks.
|
||||||
let cross = grid.dirs.cross.axis();
|
fn new(grid: &'a GridNode, mut regions: Regions) -> Self {
|
||||||
let main = grid.dirs.main.axis();
|
|
||||||
|
|
||||||
let mut cols = vec![];
|
let mut cols = vec![];
|
||||||
let mut rows = vec![];
|
let mut rows = vec![];
|
||||||
let mut cells = vec![];
|
|
||||||
|
|
||||||
// A grid always needs to have at least one column.
|
// Number of content columns: Always at least one.
|
||||||
let content_cols = grid.tracks.cross.len().max(1);
|
let c = grid.tracks.cross.len().max(1);
|
||||||
|
|
||||||
// Create at least as many rows as specified and also at least as many
|
// Number of content rows: At least as many as given, but also at least
|
||||||
// as necessary to place each item.
|
// as many as needed to place each item.
|
||||||
let content_rows = {
|
let r = {
|
||||||
let len = grid.children.len();
|
let len = grid.children.len();
|
||||||
let specified = grid.tracks.main.len();
|
let given = grid.tracks.main.len();
|
||||||
let necessary = len / content_cols + (len % content_cols).clamp(0, 1);
|
let needed = len / c + (len % c).clamp(0, 1);
|
||||||
specified.max(necessary)
|
given.max(needed)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Collect the track sizing for all columns, including gutter columns.
|
let auto = TrackSizing::Auto;
|
||||||
for i in 0 .. content_cols {
|
let zero = TrackSizing::Linear(Linear::zero());
|
||||||
cols.push(grid.tracks.cross.get_or_last(i));
|
let get_or = |tracks: &[_], idx, default| {
|
||||||
if i < content_cols - 1 {
|
tracks.get(idx).or(tracks.last()).copied().unwrap_or(default)
|
||||||
cols.push(grid.gutter.cross.get_or_last(i));
|
};
|
||||||
}
|
|
||||||
|
// Collect content and gutter columns.
|
||||||
|
for x in 0 .. c {
|
||||||
|
cols.push(get_or(&grid.tracks.cross, x, auto));
|
||||||
|
cols.push(get_or(&grid.gutter.cross, x, zero));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect the track sizing for all rows, including gutter rows.
|
// Collect content and gutter rows.
|
||||||
for i in 0 .. content_rows {
|
for y in 0 .. r {
|
||||||
rows.push(grid.tracks.main.get_or_last(i));
|
rows.push(get_or(&grid.tracks.main, y, auto));
|
||||||
if i < content_rows - 1 {
|
rows.push(get_or(&grid.gutter.main, y, zero));
|
||||||
rows.push(grid.gutter.main.get_or_last(i));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build up the matrix of cells, including gutter cells.
|
// Remove superfluous gutter tracks.
|
||||||
for (i, item) in grid.children.iter().enumerate() {
|
cols.pop();
|
||||||
cells.push(Cell::Node(item));
|
rows.pop();
|
||||||
|
|
||||||
let row = i / content_cols;
|
let cross = grid.dirs.cross.axis();
|
||||||
let col = i % content_cols;
|
let main = grid.dirs.main.axis();
|
||||||
|
let full = regions.current.get(main);
|
||||||
|
let rcols = vec![Length::zero(); cols.len()];
|
||||||
|
|
||||||
if col < content_cols - 1 {
|
// We use the regions only for auto row measurement.
|
||||||
// Push gutter after each child.
|
regions.expand = Gen::new(true, false).to_spec(main);
|
||||||
cells.push(Cell::Gutter);
|
|
||||||
} else if row < content_rows - 1 {
|
|
||||||
// Except for the last child of each row.
|
|
||||||
// There we push a gutter row.
|
|
||||||
for _ in 0 .. cols.len() {
|
|
||||||
cells.push(Cell::Gutter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill the thing up.
|
|
||||||
while cells.len() < cols.len() * rows.len() {
|
|
||||||
cells.push(Cell::Gutter);
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
cross,
|
cross,
|
||||||
main,
|
main,
|
||||||
cols,
|
cols,
|
||||||
rows,
|
rows,
|
||||||
cells,
|
children: &grid.children,
|
||||||
regions,
|
regions,
|
||||||
rcols: vec![],
|
rcols,
|
||||||
rrows: vec![],
|
lrows: vec![],
|
||||||
|
full,
|
||||||
|
used: Gen::zero(),
|
||||||
|
fr: Fractional::zero(),
|
||||||
finished: vec![],
|
finished: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Frame> {
|
/// Determine all column sizes.
|
||||||
self.rcols = self.resolve_columns(ctx);
|
fn measure_columns(&mut self, ctx: &mut LayoutContext) {
|
||||||
self.layout_rows(ctx);
|
// Sum of sizes of resolved linear tracks.
|
||||||
self.finished
|
let mut linear = Length::zero();
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine the size of all columns.
|
// Sum of fractions of all fractional tracks.
|
||||||
fn resolve_columns(&self, ctx: &mut LayoutContext) -> Vec<Length> {
|
let mut fr = Fractional::zero();
|
||||||
|
|
||||||
|
// Generic version of current and base size.
|
||||||
let current = self.regions.current.to_gen(self.main);
|
let current = self.regions.current.to_gen(self.main);
|
||||||
let base = self.regions.base.to_gen(self.main);
|
let base = self.regions.base.to_gen(self.main);
|
||||||
|
|
||||||
// Prepare vector for resolved column lengths.
|
// Resolve the size of all linear columns and compute the sum of all
|
||||||
let mut rcols = vec![Length::zero(); self.cols.len()];
|
// fractional tracks.
|
||||||
|
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
|
||||||
// - Sum of sizes of resolved linear tracks,
|
|
||||||
// - Sum of fractions of all fractional tracks,
|
|
||||||
// - Sum of sizes of resolved (through layouting) auto tracks,
|
|
||||||
// - Number of auto tracks.
|
|
||||||
let mut linear = Length::zero();
|
|
||||||
let mut fr = Fractional::zero();
|
|
||||||
let mut auto = Length::zero();
|
|
||||||
let mut auto_count = 0;
|
|
||||||
|
|
||||||
// Resolve size of linear columns and compute the sum of all fractional
|
|
||||||
// tracks.
|
|
||||||
for (&col, rcol) in self.cols.iter().zip(&mut rcols) {
|
|
||||||
match col {
|
match col {
|
||||||
TrackSizing::Auto => {}
|
TrackSizing::Auto => {}
|
||||||
TrackSizing::Linear(v) => {
|
TrackSizing::Linear(v) => {
|
||||||
@ -158,379 +174,303 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size available to auto columns (not used by fixed-size columns).
|
// Size that is not used by fixed-size columns.
|
||||||
let available = current.cross - linear;
|
let available = current.cross - linear;
|
||||||
if available <= Length::zero() {
|
if available >= Length::zero() {
|
||||||
return rcols;
|
// Determine size of auto columns.
|
||||||
}
|
let (auto, count) = self.measure_auto_columns(ctx, available);
|
||||||
|
|
||||||
// Resolve size of auto columns by laying out all cells in those
|
// If there is remaining space, distribute it to fractional columns,
|
||||||
// columns, measuring them and finding the largest one.
|
// otherwise shrink auto columns.
|
||||||
for (x, (&col, rcol)) in self.cols.iter().zip(&mut rcols).enumerate() {
|
let remaining = available - auto;
|
||||||
if col == TrackSizing::Auto {
|
if remaining >= Length::zero() {
|
||||||
let mut resolved = Length::zero();
|
self.grow_fractional_columns(remaining, fr);
|
||||||
|
} else {
|
||||||
for (y, &row) in self.rows.iter().enumerate() {
|
self.shrink_auto_columns(available, count);
|
||||||
if let Cell::Node(node) = self.get(x, y) {
|
|
||||||
// Set the correct main size if the row is fixed-size.
|
|
||||||
let main = match row {
|
|
||||||
TrackSizing::Linear(v) => v.resolve(base.main),
|
|
||||||
_ => current.main,
|
|
||||||
};
|
|
||||||
|
|
||||||
let size = Gen::new(available, main).to_size(self.main);
|
|
||||||
let regions = Regions::one(size, Spec::splat(false));
|
|
||||||
let frame = node.layout(ctx, ®ions).remove(0);
|
|
||||||
resolved = resolved.max(frame.size.get(self.cross))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*rcol = resolved;
|
|
||||||
auto += resolved;
|
|
||||||
auto_count += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is remaining space, distribute it to fractional columns,
|
self.used.cross = self.rcols.iter().sum();
|
||||||
// otherwise shrink auto columns.
|
|
||||||
let remaining = available - auto;
|
|
||||||
if remaining >= Length::zero() {
|
|
||||||
for (&col, rcol) in self.cols.iter().zip(&mut rcols) {
|
|
||||||
if let TrackSizing::Fractional(v) = col {
|
|
||||||
let ratio = v / fr;
|
|
||||||
if ratio.is_finite() {
|
|
||||||
*rcol = ratio * remaining;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// The fair share each auto column may have.
|
|
||||||
let fair = available / auto_count as f64;
|
|
||||||
|
|
||||||
// The number of overlarge auto columns and the space that will be
|
|
||||||
// equally redistributed to them.
|
|
||||||
let mut overlarge: usize = 0;
|
|
||||||
let mut redistribute = available;
|
|
||||||
|
|
||||||
for (&col, rcol) in self.cols.iter().zip(&mut rcols) {
|
|
||||||
if col == TrackSizing::Auto {
|
|
||||||
if *rcol > fair {
|
|
||||||
overlarge += 1;
|
|
||||||
} else {
|
|
||||||
redistribute -= *rcol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redistribute the space equally.
|
|
||||||
let share = redistribute / overlarge as f64;
|
|
||||||
if overlarge > 0 {
|
|
||||||
for (&col, rcol) in self.cols.iter().zip(&mut rcols) {
|
|
||||||
if col == TrackSizing::Auto && *rcol > fair {
|
|
||||||
*rcol = share;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rcols
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_rows(&mut self, ctx: &mut LayoutContext) {
|
/// Measure the size that is available to auto columns.
|
||||||
// Determine non-`fr` row heights
|
fn measure_auto_columns(
|
||||||
let mut total_frs = 0.0;
|
|
||||||
let mut current = self.regions.current.get(self.main);
|
|
||||||
|
|
||||||
for y in 0 .. self.rows.len() {
|
|
||||||
let resolved = match self.rows[y] {
|
|
||||||
TrackSizing::Linear(l) => {
|
|
||||||
(Some(l.resolve(self.regions.base.get(self.main))), None)
|
|
||||||
}
|
|
||||||
TrackSizing::Auto => {
|
|
||||||
let mut max = Length::zero();
|
|
||||||
let mut local_max = max;
|
|
||||||
let mut multi_region = false;
|
|
||||||
let mut last_size = vec![];
|
|
||||||
for (x, &col_size) in self.rcols.iter().enumerate() {
|
|
||||||
if let Cell::Node(node) = self.get(x, y) {
|
|
||||||
let colsize = Gen::new(col_size, current).to_size(self.main);
|
|
||||||
|
|
||||||
let mut regions = self.regions.map(|mut s| {
|
|
||||||
*s.get_mut(self.cross) = col_size;
|
|
||||||
s
|
|
||||||
});
|
|
||||||
|
|
||||||
regions.base = colsize;
|
|
||||||
regions.current = colsize;
|
|
||||||
regions.expand = Spec::splat(false);
|
|
||||||
|
|
||||||
let mut frames = node.layout(ctx, ®ions);
|
|
||||||
multi_region |= frames.len() > 1;
|
|
||||||
last_size.push((
|
|
||||||
frames.len() - 1,
|
|
||||||
frames.last().unwrap().size.get(self.main),
|
|
||||||
));
|
|
||||||
let frame = frames.remove(0);
|
|
||||||
local_max = local_max.max(frame.size.get(self.main));
|
|
||||||
|
|
||||||
if !multi_region {
|
|
||||||
max = local_max;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
last_size.push((0, Length::zero()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let overshoot = if multi_region {
|
|
||||||
self.rrows.push((y, Some(local_max), None));
|
|
||||||
let res = self.finish_region(ctx, total_frs, Some(last_size));
|
|
||||||
max = if let Some(overflow) = res.as_ref() {
|
|
||||||
overflow
|
|
||||||
.iter()
|
|
||||||
.filter_map(|x| x.as_ref())
|
|
||||||
.map(|x| x.size.get(self.main))
|
|
||||||
.max()
|
|
||||||
.unwrap_or(Length::zero())
|
|
||||||
} else {
|
|
||||||
local_max
|
|
||||||
};
|
|
||||||
|
|
||||||
current = self.regions.current.get(self.main);
|
|
||||||
total_frs = 0.0;
|
|
||||||
if res.is_none() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
res
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// If multi-region results: finish_regions, returning
|
|
||||||
// the last non-set frames.
|
|
||||||
(Some(max), overshoot)
|
|
||||||
}
|
|
||||||
TrackSizing::Fractional(f) => {
|
|
||||||
total_frs += f.get();
|
|
||||||
(None, None)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let (Some(resolved), _) = resolved {
|
|
||||||
while !current.fits(resolved) && !self.regions.in_full_last() {
|
|
||||||
self.finish_region(ctx, total_frs, None);
|
|
||||||
current = self.regions.current.get(self.main);
|
|
||||||
total_frs = 0.0;
|
|
||||||
}
|
|
||||||
current -= resolved;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.rrows.push((y, resolved.0, resolved.1));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.finish_region(ctx, total_frs, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish_region(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
total_frs: f64,
|
available: Length,
|
||||||
multiregion_sizing: Option<Vec<(usize, Length)>>,
|
) -> (Length, usize) {
|
||||||
) -> Option<Vec<Option<Frame>>> {
|
let mut auto = Length::zero();
|
||||||
if self.rrows.is_empty() {
|
let mut count = 0;
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut pos = Gen::splat(Length::zero());
|
// Determine size of auto columns by laying out all cells in those
|
||||||
let frame = Frame::new(Size::zero(), Length::zero());
|
// columns, measuring them and finding the largest one.
|
||||||
let mut total_cross = Length::zero();
|
for (x, &col) in self.cols.iter().enumerate() {
|
||||||
let mut total_main = Length::zero();
|
if col != TrackSizing::Auto {
|
||||||
let mut max_regions = 0;
|
continue;
|
||||||
let mut collected_frames = if multiregion_sizing.is_some() {
|
|
||||||
Some(vec![None; self.rcols.len()])
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
self.finished.push(frame);
|
|
||||||
|
|
||||||
let frame_len = self.finished.len();
|
|
||||||
|
|
||||||
let total_row_height: Length = self.rrows.iter().filter_map(|(_, x, _)| *x).sum();
|
|
||||||
|
|
||||||
for &(y, h, ref layouted) in self.rrows.iter().as_ref() {
|
|
||||||
let last = self.rrows.last().map_or(false, |(o, _, _)| &y == o);
|
|
||||||
let available = self.regions.current.get(self.main) - total_row_height;
|
|
||||||
let h = if let Some(len) = h {
|
|
||||||
len
|
|
||||||
} else if let TrackSizing::Fractional(f) = self.rows[y] {
|
|
||||||
if total_frs > 0.0 {
|
|
||||||
let res = available * (f.get() / total_frs);
|
|
||||||
if res.is_finite() { res } else { Length::zero() }
|
|
||||||
} else {
|
|
||||||
Length::zero()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable!("non-fractional tracks are already resolved");
|
|
||||||
};
|
|
||||||
total_main += h;
|
|
||||||
|
|
||||||
if let Some(layouted) = layouted {
|
|
||||||
for (col_index, frame) in layouted.into_iter().enumerate() {
|
|
||||||
if let Some(frame) = frame {
|
|
||||||
self.finished
|
|
||||||
.get_mut(frame_len - 1)
|
|
||||||
.unwrap()
|
|
||||||
.push_frame(pos.to_point(self.main), frame.clone());
|
|
||||||
}
|
|
||||||
pos.cross += self.rcols[col_index];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut overshoot_columns = vec![];
|
|
||||||
for (x, &w) in self.rcols.iter().enumerate() {
|
|
||||||
let element = self.get(x, y);
|
|
||||||
|
|
||||||
if y == 0 {
|
|
||||||
total_cross += w;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Cell::Node(n) = element {
|
|
||||||
let region_size = Gen::new(w, h).to_size(self.main);
|
|
||||||
let regions = if last {
|
|
||||||
if let Some(last_sizes) = multiregion_sizing.as_ref() {
|
|
||||||
let mut regions = self.regions.map(|mut s| {
|
|
||||||
*s.get_mut(self.cross) = w;
|
|
||||||
s
|
|
||||||
});
|
|
||||||
|
|
||||||
regions.base = region_size;
|
|
||||||
regions.current = region_size;
|
|
||||||
regions.expand = Spec::splat(true);
|
|
||||||
|
|
||||||
let (last_region, last_size) = last_sizes[x];
|
|
||||||
regions.unique_regions(last_region + 1);
|
|
||||||
*regions
|
|
||||||
.nth_mut(last_region)
|
|
||||||
.unwrap()
|
|
||||||
.get_mut(self.main) = last_size;
|
|
||||||
regions
|
|
||||||
} else {
|
|
||||||
Regions::one(region_size, Spec::splat(true))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Regions::one(region_size, Spec::splat(true))
|
|
||||||
};
|
|
||||||
let mut items = n.layout(ctx, ®ions);
|
|
||||||
let item = items.remove(0);
|
|
||||||
|
|
||||||
if last && multiregion_sizing.is_some() {
|
|
||||||
max_regions = max_regions.max(items.len());
|
|
||||||
overshoot_columns.push((x, items));
|
|
||||||
} else {
|
|
||||||
assert_eq!(items.len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.finished
|
|
||||||
.get_mut(frame_len - 1)
|
|
||||||
.unwrap()
|
|
||||||
.push_frame(pos.to_point(self.main), item);
|
|
||||||
}
|
|
||||||
|
|
||||||
pos.cross += w;
|
|
||||||
}
|
|
||||||
|
|
||||||
if overshoot_columns.iter().any(|(_, items)| !items.is_empty()) {
|
|
||||||
for (x, col) in overshoot_columns {
|
|
||||||
let mut cross_offset = Length::zero();
|
|
||||||
for col in 0 .. x {
|
|
||||||
cross_offset += self.rcols[col];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let collected_frames = collected_frames.as_mut().unwrap();
|
|
||||||
*collected_frames.get_mut(x).unwrap() =
|
|
||||||
col.get(max_regions - 1).cloned();
|
|
||||||
|
|
||||||
for (cell_index, subcell) in col.into_iter().enumerate() {
|
|
||||||
if cell_index >= max_regions - 1 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let frame = if let Some(frame) =
|
|
||||||
self.finished.get_mut(frame_len + cell_index)
|
|
||||||
{
|
|
||||||
frame
|
|
||||||
} else {
|
|
||||||
let frame = Frame::new(Size::zero(), Length::zero());
|
|
||||||
// The previous frame always exists: either the
|
|
||||||
// last iteration created it or it is the normal
|
|
||||||
// frame.
|
|
||||||
self.finished.push(frame);
|
|
||||||
self.finished.last_mut().unwrap()
|
|
||||||
};
|
|
||||||
let pos = Gen::new(cross_offset, Length::zero());
|
|
||||||
frame
|
|
||||||
.size
|
|
||||||
.get_mut(self.cross)
|
|
||||||
.set_max(pos.cross + subcell.size.get(self.cross));
|
|
||||||
frame
|
|
||||||
.size
|
|
||||||
.get_mut(self.main)
|
|
||||||
.set_max(subcell.size.get(self.main));
|
|
||||||
frame.baseline = frame.size.height;
|
|
||||||
frame.push_frame(pos.to_point(self.main), subcell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pos.cross = Length::zero();
|
let mut resolved = Length::zero();
|
||||||
pos.main += h;
|
for node in (0 .. self.rows.len()).filter_map(|y| self.cell(x, y)) {
|
||||||
}
|
let size = Gen::new(available, Length::inf()).to_size(self.main);
|
||||||
|
let regions = Regions::one(size, Spec::splat(false));
|
||||||
let frame = self.finished.get_mut(frame_len - 1).unwrap();
|
let frame = node.layout(ctx, ®ions).remove(0);
|
||||||
frame.size = Gen::new(total_cross, total_main).to_size(self.main);
|
resolved.set_max(frame.size.get(self.cross));
|
||||||
frame.baseline = frame.size.height;
|
|
||||||
|
|
||||||
self.rrows.clear();
|
|
||||||
for _ in 0 .. (max_regions.max(1)) {
|
|
||||||
self.regions.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(frames) = collected_frames.as_ref() {
|
|
||||||
if frames.iter().all(|i| i.is_none()) {
|
|
||||||
collected_frames = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.rcols[x] = resolved;
|
||||||
|
auto += resolved;
|
||||||
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
collected_frames
|
(auto, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, x: usize, y: usize) -> &Cell<'a> {
|
/// Distribute remaining space to fractional columns.
|
||||||
|
fn grow_fractional_columns(&mut self, remaining: Length, fr: Fractional) {
|
||||||
|
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
|
||||||
|
if let TrackSizing::Fractional(v) = col {
|
||||||
|
let ratio = v / fr;
|
||||||
|
if ratio.is_finite() {
|
||||||
|
*rcol = ratio * remaining;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Redistribute space to auto columns so that each gets a fair share.
|
||||||
|
fn shrink_auto_columns(&mut self, available: Length, count: usize) {
|
||||||
|
// The fair share each auto column may have.
|
||||||
|
let fair = available / count as f64;
|
||||||
|
|
||||||
|
// The number of overlarge auto columns and the space that will be
|
||||||
|
// equally redistributed to them.
|
||||||
|
let mut overlarge: usize = 0;
|
||||||
|
let mut redistribute = available;
|
||||||
|
|
||||||
|
// Find out the number of and space used by overlarge auto columns.
|
||||||
|
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
|
||||||
|
if col == TrackSizing::Auto {
|
||||||
|
if *rcol > fair {
|
||||||
|
overlarge += 1;
|
||||||
|
} else {
|
||||||
|
redistribute -= *rcol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redistribute the space equally.
|
||||||
|
let share = redistribute / overlarge as f64;
|
||||||
|
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
|
||||||
|
if col == TrackSizing::Auto && *rcol > fair {
|
||||||
|
*rcol = share;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout the grid row-by-row.
|
||||||
|
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Frame> {
|
||||||
|
for y in 0 .. self.rows.len() {
|
||||||
|
match self.rows[y] {
|
||||||
|
TrackSizing::Auto => {
|
||||||
|
self.layout_auto_row(ctx, y);
|
||||||
|
}
|
||||||
|
TrackSizing::Linear(v) => {
|
||||||
|
let base = self.regions.base.get(self.main);
|
||||||
|
let resolved = v.resolve(base);
|
||||||
|
let frame = self.layout_single_row(ctx, resolved, y);
|
||||||
|
self.push_row(ctx, frame);
|
||||||
|
}
|
||||||
|
TrackSizing::Fractional(v) => {
|
||||||
|
self.fr += v;
|
||||||
|
self.lrows.push(Row::Fr(v, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.finish_region(ctx);
|
||||||
|
self.finished
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout a row with automatic size along the main axis. Such a row may
|
||||||
|
/// break across multiple regions.
|
||||||
|
fn layout_auto_row(&mut self, ctx: &mut LayoutContext, y: usize) {
|
||||||
|
let mut first = Length::zero();
|
||||||
|
let mut rest: Vec<Length> = vec![];
|
||||||
|
|
||||||
|
// Determine the size for each region of the row.
|
||||||
|
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||||
|
if let Some(node) = self.cell(x, y) {
|
||||||
|
let cross = self.cross;
|
||||||
|
self.regions.mutate(|size| *size.get_mut(cross) = rcol);
|
||||||
|
|
||||||
|
let mut sizes = node
|
||||||
|
.layout(ctx, &self.regions)
|
||||||
|
.into_iter()
|
||||||
|
.map(|frame| frame.size.get(self.main));
|
||||||
|
|
||||||
|
if let Some(size) = sizes.next() {
|
||||||
|
first.set_max(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (resolved, size) in rest.iter_mut().zip(&mut sizes) {
|
||||||
|
resolved.set_max(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
rest.extend(sizes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layout the row.
|
||||||
|
if rest.is_empty() {
|
||||||
|
let frame = self.layout_single_row(ctx, first, y);
|
||||||
|
self.push_row(ctx, frame);
|
||||||
|
} else {
|
||||||
|
let frames = self.layout_multi_row(ctx, first, &rest, y);
|
||||||
|
for frame in frames {
|
||||||
|
self.push_row(ctx, frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout a row with a fixed size along the main axis.
|
||||||
|
fn layout_single_row(
|
||||||
|
&self,
|
||||||
|
ctx: &mut LayoutContext,
|
||||||
|
length: Length,
|
||||||
|
y: usize,
|
||||||
|
) -> Frame {
|
||||||
|
let size = self.to_size(length);
|
||||||
|
let mut output = Frame::new(size, size.height);
|
||||||
|
let mut pos = Gen::zero();
|
||||||
|
|
||||||
|
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||||
|
if let Some(node) = self.cell(x, y) {
|
||||||
|
let size = Gen::new(rcol, length).to_size(self.main);
|
||||||
|
let regions = Regions::one(size, Spec::splat(true));
|
||||||
|
let frame = node.layout(ctx, ®ions).remove(0);
|
||||||
|
output.push_frame(pos.to_point(self.main), frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
pos.cross += rcol;
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout a row spanning multiple regions.
|
||||||
|
fn layout_multi_row(
|
||||||
|
&self,
|
||||||
|
ctx: &mut LayoutContext,
|
||||||
|
first: Length,
|
||||||
|
rest: &[Length],
|
||||||
|
y: usize,
|
||||||
|
) -> Vec<Frame> {
|
||||||
|
// Prepare frames.
|
||||||
|
let mut outputs: Vec<_> = std::iter::once(first)
|
||||||
|
.chain(rest.iter().copied())
|
||||||
|
.map(|v| self.to_size(v))
|
||||||
|
.map(|size| Frame::new(size, size.height))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Prepare regions.
|
||||||
|
let mut regions = Regions::one(self.to_size(first), Spec::splat(true));
|
||||||
|
regions.backlog = rest.iter().rev().map(|&v| self.to_size(v)).collect();
|
||||||
|
|
||||||
|
// Layout the row.
|
||||||
|
let mut pos = Gen::zero();
|
||||||
|
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||||
|
if let Some(node) = self.cell(x, y) {
|
||||||
|
regions.mutate(|size| *size.get_mut(self.cross) = rcol);
|
||||||
|
|
||||||
|
// Push the layouted frames into the individual output frames.
|
||||||
|
let frames = node.layout(ctx, ®ions);
|
||||||
|
for (output, frame) in outputs.iter_mut().zip(frames) {
|
||||||
|
output.push_frame(pos.to_point(self.main), frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos.cross += rcol;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a row frame into the current or next fitting region, finishing
|
||||||
|
/// regions (including layouting fractional rows) if necessary.
|
||||||
|
fn push_row(&mut self, ctx: &mut LayoutContext, frame: Frame) {
|
||||||
|
let length = frame.size.get(self.main);
|
||||||
|
|
||||||
|
// Skip to fitting region.
|
||||||
|
while !self.regions.current.get(self.main).fits(length)
|
||||||
|
&& !self.regions.in_full_last()
|
||||||
|
{
|
||||||
|
self.finish_region(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
*self.regions.current.get_mut(self.main) -= length;
|
||||||
|
self.used.main += length;
|
||||||
|
self.lrows.push(Row::Frame(frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish rows for one region.
|
||||||
|
fn finish_region(&mut self, ctx: &mut LayoutContext) {
|
||||||
|
// Determine the size of the region.
|
||||||
|
let length = if self.fr.is_zero() { self.used.main } else { self.full };
|
||||||
|
let size = self.to_size(length);
|
||||||
|
|
||||||
|
// The frame for the region.
|
||||||
|
let mut output = Frame::new(size, size.height);
|
||||||
|
let mut pos = Gen::zero();
|
||||||
|
|
||||||
|
// Determine the remaining size for fractional rows.
|
||||||
|
let remaining = self.full - self.used.main;
|
||||||
|
|
||||||
|
// Place finished rows and layout fractional rows.
|
||||||
|
for row in std::mem::take(&mut self.lrows) {
|
||||||
|
let frame = match row {
|
||||||
|
Row::Frame(frame) => frame,
|
||||||
|
Row::Fr(v, y) => {
|
||||||
|
let ratio = v / self.fr;
|
||||||
|
if remaining > Length::zero() && ratio.is_finite() {
|
||||||
|
let resolved = ratio * remaining;
|
||||||
|
self.layout_single_row(ctx, resolved, y)
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let main = frame.size.get(self.main);
|
||||||
|
output.push_frame(pos.to_point(self.main), frame);
|
||||||
|
pos.main += main;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.regions.next();
|
||||||
|
self.full = self.regions.current.get(self.main);
|
||||||
|
self.used.main = Length::zero();
|
||||||
|
self.fr = Fractional::zero();
|
||||||
|
self.finished.push(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the node in the cell in column `x` and row `y`.
|
||||||
|
///
|
||||||
|
/// Returns `None` if it's a gutter cell.
|
||||||
|
fn cell(&self, x: usize, y: usize) -> Option<&'a AnyNode> {
|
||||||
assert!(x < self.cols.len());
|
assert!(x < self.cols.len());
|
||||||
assert!(y < self.rows.len());
|
assert!(y < self.rows.len());
|
||||||
self.cells.get(y * self.cols.len() + x).unwrap()
|
|
||||||
|
// Even columns and rows are children, odd ones are gutter.
|
||||||
|
if x % 2 == 0 && y % 2 == 0 {
|
||||||
|
let c = 1 + self.cols.len() / 2;
|
||||||
|
self.children.get((y / 2) * c + x / 2)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a size where the cross axis spans the whole grid and the main
|
||||||
|
/// axis the given length.
|
||||||
|
fn to_size(&self, main_size: Length) -> Size {
|
||||||
|
Gen::new(self.used.cross, main_size).to_size(self.main)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait TracksExt {
|
|
||||||
/// Get the sizing for the track at the given `idx` or fallback to the
|
|
||||||
/// last defined track or `auto`.
|
|
||||||
fn get_or_last(&self, idx: usize) -> TrackSizing;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TracksExt for Vec<TrackSizing> {
|
|
||||||
fn get_or_last(&self, idx: usize) -> TrackSizing {
|
|
||||||
self.get(idx).or(self.last()).copied().unwrap_or(TrackSizing::Auto)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Defines how to size a grid cell along an axis.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Hash)]
|
|
||||||
pub enum TrackSizing {
|
|
||||||
/// Fit the cell to its contents.
|
|
||||||
Auto,
|
|
||||||
/// A length stated in absolute values and fractions of the parent's size.
|
|
||||||
Linear(Linear),
|
|
||||||
/// A length that is the fraction of the remaining free space in the parent.
|
|
||||||
Fractional(Fractional),
|
|
||||||
}
|
|
||||||
|
@ -23,8 +23,6 @@ use std::collections::HashMap;
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
use decorum::N64;
|
|
||||||
|
|
||||||
use crate::cache::Cache;
|
use crate::cache::Cache;
|
||||||
use crate::geom::*;
|
use crate::geom::*;
|
||||||
use crate::loading::Loader;
|
use crate::loading::Loader;
|
||||||
@ -245,18 +243,14 @@ impl Regions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map the size of all regions.
|
/// Create new regions where all sizes are mapped with `f`.
|
||||||
pub fn map<F>(&self, mut f: F) -> Self
|
pub fn map<F>(&self, mut f: F) -> Self
|
||||||
where
|
where
|
||||||
F: FnMut(Size) -> Size,
|
F: FnMut(Size) -> Size,
|
||||||
{
|
{
|
||||||
Self {
|
let mut regions = self.clone();
|
||||||
current: f(self.current),
|
regions.mutate(|s| *s = f(*s));
|
||||||
base: f(self.base),
|
regions
|
||||||
backlog: self.backlog.iter().copied().map(|s| f(s)).collect(),
|
|
||||||
last: self.last.map(f),
|
|
||||||
expand: self.expand,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether `current` is a fully sized (untouched) copy of the last region.
|
/// Whether `current` is a fully sized (untouched) copy of the last region.
|
||||||
@ -274,38 +268,14 @@ impl Regions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shrink `current` to ensure that the aspect ratio can be satisfied.
|
/// Mutate all contained sizes in place.
|
||||||
pub fn apply_aspect_ratio(&mut self, aspect: N64) {
|
pub fn mutate<F>(&mut self, mut f: F)
|
||||||
let width = self.current.width.min(aspect.into_inner() * self.current.height);
|
where
|
||||||
let height = width / aspect.into_inner();
|
F: FnMut(&mut Size),
|
||||||
self.current = Size::new(width, height);
|
{
|
||||||
}
|
f(&mut self.current);
|
||||||
|
f(&mut self.base);
|
||||||
/// Appends new elements to the backlog such that the behavior of `next`
|
self.last.as_mut().map(|x| f(x));
|
||||||
/// does not change. Panics when used with finite regions.
|
self.backlog.iter_mut().for_each(f);
|
||||||
pub fn backlogify(&mut self, count: usize) {
|
|
||||||
for _ in 0 .. count {
|
|
||||||
self.backlog.push(self.last.unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensures that enough unique regions are present, including the current
|
|
||||||
/// and last ones. Panics when used with finite regions.
|
|
||||||
pub fn unique_regions(&mut self, count: usize) {
|
|
||||||
if self.backlog.len() < count.max(2) - 2 {
|
|
||||||
self.backlogify((count - 2) - self.backlog.len());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mutable reference to the size of the `n`th region.
|
|
||||||
pub fn nth_mut(&mut self, n: usize) -> Option<&mut Size> {
|
|
||||||
let backlog_len = self.backlog.len();
|
|
||||||
if n == 0 {
|
|
||||||
Some(&mut self.current)
|
|
||||||
} else if n <= backlog_len {
|
|
||||||
self.backlog.get_mut(backlog_len - n)
|
|
||||||
} else {
|
|
||||||
self.last.as_mut()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,12 +40,12 @@ impl Layout for ParNode {
|
|||||||
// Find out the BiDi embedding levels.
|
// Find out the BiDi embedding levels.
|
||||||
let bidi = BidiInfo::new(&text, Level::from_dir(self.dir));
|
let bidi = BidiInfo::new(&text, Level::from_dir(self.dir));
|
||||||
|
|
||||||
// Build a representation of the paragraph on which we can do
|
// Prepare paragraph layout by bulding a representation on which we can
|
||||||
// linebreaking without layouting each and every line from scratch.
|
// do line breaking without layouting each and every line from scratch.
|
||||||
let layout = ParLayout::new(ctx, regions, self, bidi);
|
let layouter = ParLayouter::new(self, ctx, regions, bidi);
|
||||||
|
|
||||||
// Find suitable linebreaks.
|
// Find suitable linebreaks.
|
||||||
layout.build(ctx, regions.clone(), self)
|
layouter.layout(ctx, regions.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,9 +102,11 @@ impl Debug for ParChild {
|
|||||||
|
|
||||||
/// A paragraph representation in which children are already layouted and text
|
/// A paragraph representation in which children are already layouted and text
|
||||||
/// is separated into shapable runs.
|
/// is separated into shapable runs.
|
||||||
struct ParLayout<'a> {
|
struct ParLayouter<'a> {
|
||||||
/// The top-level direction.
|
/// The top-level direction.
|
||||||
dir: Dir,
|
dir: Dir,
|
||||||
|
/// The line spacing.
|
||||||
|
line_spacing: Length,
|
||||||
/// Bidirectional text embedding levels for the paragraph.
|
/// Bidirectional text embedding levels for the paragraph.
|
||||||
bidi: BidiInfo<'a>,
|
bidi: BidiInfo<'a>,
|
||||||
/// Layouted children and separated text runs.
|
/// Layouted children and separated text runs.
|
||||||
@ -113,12 +115,12 @@ struct ParLayout<'a> {
|
|||||||
ranges: Vec<Range>,
|
ranges: Vec<Range>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ParLayout<'a> {
|
impl<'a> ParLayouter<'a> {
|
||||||
/// Build a paragraph layout for the given node.
|
/// Prepare initial shaped text and layouted children.
|
||||||
fn new(
|
fn new(
|
||||||
|
par: &'a ParNode,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
par: &'a ParNode,
|
|
||||||
bidi: BidiInfo<'a>,
|
bidi: BidiInfo<'a>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Prepare an iterator over each child an the range it spans.
|
// Prepare an iterator over each child an the range it spans.
|
||||||
@ -142,26 +144,25 @@ impl<'a> ParLayout<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ParChild::Any(ref node, align) => {
|
ParChild::Any(ref node, align) => {
|
||||||
let mut frames = node.layout(ctx, regions).into_iter();
|
let frame = node.layout(ctx, regions).remove(0);
|
||||||
let frame = frames.next().unwrap();
|
|
||||||
assert!(frames.next().is_none());
|
|
||||||
items.push(ParItem::Frame(frame, align));
|
items.push(ParItem::Frame(frame, align));
|
||||||
ranges.push(range);
|
ranges.push(range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self { dir: par.dir, bidi, items, ranges }
|
Self {
|
||||||
|
dir: par.dir,
|
||||||
|
line_spacing: par.line_spacing,
|
||||||
|
bidi,
|
||||||
|
items,
|
||||||
|
ranges,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find first-fit line breaks and build the paragraph.
|
/// Find first-fit line breaks and build the paragraph.
|
||||||
fn build(
|
fn layout(self, ctx: &mut LayoutContext, regions: Regions) -> Vec<Frame> {
|
||||||
self,
|
let mut stack = LineStack::new(self.line_spacing, regions);
|
||||||
ctx: &mut LayoutContext,
|
|
||||||
regions: Regions,
|
|
||||||
par: &ParNode,
|
|
||||||
) -> Vec<Frame> {
|
|
||||||
let mut stack = LineStack::new(par.line_spacing, regions);
|
|
||||||
|
|
||||||
// The current line attempt.
|
// The current line attempt.
|
||||||
// Invariant: Always fits into `stack.regions.current`.
|
// Invariant: Always fits into `stack.regions.current`.
|
||||||
@ -272,37 +273,41 @@ impl ParItem<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple layouter that stacks lines into regions.
|
/// Stacks lines on top of each other.
|
||||||
struct LineStack<'a> {
|
struct LineStack<'a> {
|
||||||
line_spacing: Length,
|
line_spacing: Length,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
finished: Vec<Frame>,
|
|
||||||
lines: Vec<LineLayout<'a>>,
|
|
||||||
size: Size,
|
size: Size,
|
||||||
|
lines: Vec<LineLayout<'a>>,
|
||||||
|
finished: Vec<Frame>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> LineStack<'a> {
|
impl<'a> LineStack<'a> {
|
||||||
|
/// Create an empty line stack.
|
||||||
fn new(line_spacing: Length, regions: Regions) -> Self {
|
fn new(line_spacing: Length, regions: Regions) -> Self {
|
||||||
Self {
|
Self {
|
||||||
line_spacing,
|
line_spacing,
|
||||||
regions,
|
regions,
|
||||||
finished: vec![],
|
|
||||||
lines: vec![],
|
|
||||||
size: Size::zero(),
|
size: Size::zero(),
|
||||||
|
lines: vec![],
|
||||||
|
finished: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push a new line into the stack.
|
||||||
fn push(&mut self, line: LineLayout<'a>) {
|
fn push(&mut self, line: LineLayout<'a>) {
|
||||||
self.size.height += line.size.height;
|
self.regions.current.height -= line.size.height + self.line_spacing;
|
||||||
|
|
||||||
self.size.width.set_max(line.size.width);
|
self.size.width.set_max(line.size.width);
|
||||||
|
self.size.height += line.size.height;
|
||||||
if !self.lines.is_empty() {
|
if !self.lines.is_empty() {
|
||||||
self.size.height += self.line_spacing;
|
self.size.height += self.line_spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.regions.current.height -= line.size.height + self.line_spacing;
|
|
||||||
self.lines.push(line);
|
self.lines.push(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finish the frame for one region.
|
||||||
fn finish_region(&mut self, ctx: &LayoutContext) {
|
fn finish_region(&mut self, ctx: &LayoutContext) {
|
||||||
if self.regions.expand.horizontal {
|
if self.regions.expand.horizontal {
|
||||||
self.size.width = self.regions.current.width;
|
self.size.width = self.regions.current.width;
|
||||||
@ -312,7 +317,7 @@ impl<'a> LineStack<'a> {
|
|||||||
let mut offset = Length::zero();
|
let mut offset = Length::zero();
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
|
|
||||||
for line in std::mem::take(&mut self.lines) {
|
for line in self.lines.drain(..) {
|
||||||
let frame = line.build(ctx, self.size.width);
|
let frame = line.build(ctx, self.size.width);
|
||||||
|
|
||||||
let pos = Point::new(Length::zero(), offset);
|
let pos = Point::new(Length::zero(), offset);
|
||||||
@ -325,11 +330,12 @@ impl<'a> LineStack<'a> {
|
|||||||
output.push_frame(pos, frame);
|
output.push_frame(pos, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.finished.push(output);
|
|
||||||
self.regions.next();
|
self.regions.next();
|
||||||
self.size = Size::zero();
|
self.size = Size::zero();
|
||||||
|
self.finished.push(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finish the last region and return the built frames.
|
||||||
fn finish(mut self, ctx: &LayoutContext) -> Vec<Frame> {
|
fn finish(mut self, ctx: &LayoutContext) -> Vec<Frame> {
|
||||||
self.finish_region(ctx);
|
self.finish_region(ctx);
|
||||||
self.finished
|
self.finished
|
||||||
@ -340,8 +346,10 @@ impl<'a> LineStack<'a> {
|
|||||||
/// paragraph's text. This type enables you to cheaply measure the size of a
|
/// paragraph's text. This type enables you to cheaply measure the size of a
|
||||||
/// line in a range before comitting to building the line's frame.
|
/// line in a range before comitting to building the line's frame.
|
||||||
struct LineLayout<'a> {
|
struct LineLayout<'a> {
|
||||||
/// The paragraph the line was created in.
|
/// The direction of the line.
|
||||||
par: &'a ParLayout<'a>,
|
dir: Dir,
|
||||||
|
/// Bidi information about the paragraph.
|
||||||
|
bidi: &'a BidiInfo<'a>,
|
||||||
/// The range the line spans in the paragraph.
|
/// The range the line spans in the paragraph.
|
||||||
line: Range,
|
line: Range,
|
||||||
/// A reshaped text item if the line sliced up a text item at the start.
|
/// A reshaped text item if the line sliced up a text item at the start.
|
||||||
@ -363,7 +371,7 @@ struct LineLayout<'a> {
|
|||||||
|
|
||||||
impl<'a> LineLayout<'a> {
|
impl<'a> LineLayout<'a> {
|
||||||
/// Create a line which spans the given range.
|
/// Create a line which spans the given range.
|
||||||
fn new(ctx: &mut LayoutContext, par: &'a ParLayout<'a>, mut line: Range) -> Self {
|
fn new(ctx: &mut LayoutContext, par: &'a ParLayouter<'a>, mut line: Range) -> Self {
|
||||||
// Find the items which bound the text range.
|
// Find the items which bound the text range.
|
||||||
let last_idx = par.find(line.end.saturating_sub(1)).unwrap();
|
let last_idx = par.find(line.end.saturating_sub(1)).unwrap();
|
||||||
let first_idx = if line.is_empty() {
|
let first_idx = if line.is_empty() {
|
||||||
@ -436,7 +444,8 @@ impl<'a> LineLayout<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
par,
|
dir: par.dir,
|
||||||
|
bidi: &par.bidi,
|
||||||
line,
|
line,
|
||||||
first,
|
first,
|
||||||
items,
|
items,
|
||||||
@ -474,7 +483,7 @@ impl<'a> LineLayout<'a> {
|
|||||||
|
|
||||||
// FIXME: Ruler alignment for RTL.
|
// FIXME: Ruler alignment for RTL.
|
||||||
let pos = Point::new(
|
let pos = Point::new(
|
||||||
ruler.resolve(self.par.dir, offset .. free + offset),
|
ruler.resolve(self.dir, offset .. free + offset),
|
||||||
self.baseline - frame.baseline,
|
self.baseline - frame.baseline,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -494,7 +503,6 @@ impl<'a> LineLayout<'a> {
|
|||||||
|
|
||||||
// Find the paragraph that contains the line.
|
// Find the paragraph that contains the line.
|
||||||
let para = self
|
let para = self
|
||||||
.par
|
|
||||||
.bidi
|
.bidi
|
||||||
.paragraphs
|
.paragraphs
|
||||||
.iter()
|
.iter()
|
||||||
@ -502,7 +510,7 @@ impl<'a> LineLayout<'a> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Compute the reordered ranges in visual order (left to right).
|
// Compute the reordered ranges in visual order (left to right).
|
||||||
let (levels, runs) = self.par.bidi.visual_runs(para, self.line.clone());
|
let (levels, runs) = self.bidi.visual_runs(para, self.line.clone());
|
||||||
|
|
||||||
// Find the items for each run.
|
// Find the items for each run.
|
||||||
for run in runs {
|
for run in runs {
|
||||||
|
@ -39,7 +39,7 @@ impl From<StackNode> for AnyNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
/// Performs stack layout.
|
||||||
struct StackLayouter<'a> {
|
struct StackLayouter<'a> {
|
||||||
/// The stack node to layout.
|
/// The stack node to layout.
|
||||||
stack: &'a StackNode,
|
stack: &'a StackNode,
|
||||||
@ -49,9 +49,6 @@ struct StackLayouter<'a> {
|
|||||||
expand: Spec<bool>,
|
expand: Spec<bool>,
|
||||||
/// The region to layout into.
|
/// The region to layout into.
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
/// Offset, alignment and frame for all children that fit into the current
|
|
||||||
/// region. The exact positions are not known yet.
|
|
||||||
frames: Vec<(Length, Gen<Align>, Frame)>,
|
|
||||||
/// The full size of `regions.current` that was available before we started
|
/// The full size of `regions.current` that was available before we started
|
||||||
/// subtracting.
|
/// subtracting.
|
||||||
full: Size,
|
full: Size,
|
||||||
@ -59,11 +56,15 @@ struct StackLayouter<'a> {
|
|||||||
used: Gen<Length>,
|
used: Gen<Length>,
|
||||||
/// The alignment ruler for the current region.
|
/// The alignment ruler for the current region.
|
||||||
ruler: Align,
|
ruler: Align,
|
||||||
|
/// Offset, alignment and frame for all children that fit into the current
|
||||||
|
/// region. The exact positions are not known yet.
|
||||||
|
frames: Vec<(Length, Gen<Align>, Frame)>,
|
||||||
/// Finished frames for previous regions.
|
/// Finished frames for previous regions.
|
||||||
finished: Vec<Frame>,
|
finished: Vec<Frame>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> StackLayouter<'a> {
|
impl<'a> StackLayouter<'a> {
|
||||||
|
/// Create a new stack layouter.
|
||||||
fn new(stack: &'a StackNode, mut regions: Regions) -> Self {
|
fn new(stack: &'a StackNode, mut regions: Regions) -> Self {
|
||||||
let main = stack.dirs.main.axis();
|
let main = stack.dirs.main.axis();
|
||||||
let full = regions.current;
|
let full = regions.current;
|
||||||
@ -73,7 +74,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
*regions.expand.get_mut(main) = false;
|
*regions.expand.get_mut(main) = false;
|
||||||
|
|
||||||
if let Some(aspect) = stack.aspect {
|
if let Some(aspect) = stack.aspect {
|
||||||
regions.apply_aspect_ratio(aspect);
|
regions.current = regions.current.with_aspect(aspect.into_inner());
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -81,26 +82,21 @@ impl<'a> StackLayouter<'a> {
|
|||||||
main,
|
main,
|
||||||
expand,
|
expand,
|
||||||
regions,
|
regions,
|
||||||
finished: vec![],
|
|
||||||
frames: vec![],
|
|
||||||
full,
|
full,
|
||||||
used: Gen::zero(),
|
used: Gen::zero(),
|
||||||
ruler: Align::Start,
|
ruler: Align::Start,
|
||||||
|
frames: vec![],
|
||||||
|
finished: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Layout all children.
|
||||||
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Frame> {
|
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Frame> {
|
||||||
for child in &self.stack.children {
|
for child in &self.stack.children {
|
||||||
match *child {
|
match *child {
|
||||||
StackChild::Spacing(amount) => self.push_spacing(amount),
|
StackChild::Spacing(amount) => self.space(amount),
|
||||||
StackChild::Any(ref node, aligns) => {
|
StackChild::Any(ref node, aligns) => {
|
||||||
let mut frames = node.layout(ctx, &self.regions).into_iter();
|
for frame in node.layout(ctx, &self.regions) {
|
||||||
if let Some(frame) = frames.next() {
|
|
||||||
self.push_frame(frame, aligns);
|
|
||||||
}
|
|
||||||
|
|
||||||
for frame in frames {
|
|
||||||
self.finish_region();
|
|
||||||
self.push_frame(frame, aligns);
|
self.push_frame(frame, aligns);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,7 +107,8 @@ impl<'a> StackLayouter<'a> {
|
|||||||
self.finished
|
self.finished
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_spacing(&mut self, amount: Length) {
|
/// Add main-axis spacing into the current region.
|
||||||
|
fn space(&mut self, amount: Length) {
|
||||||
// Cap the spacing to the remaining available space.
|
// Cap the spacing to the remaining available space.
|
||||||
let remaining = self.regions.current.get_mut(self.main);
|
let remaining = self.regions.current.get_mut(self.main);
|
||||||
let capped = amount.min(*remaining);
|
let capped = amount.min(*remaining);
|
||||||
@ -121,60 +118,65 @@ impl<'a> StackLayouter<'a> {
|
|||||||
*remaining -= capped;
|
*remaining -= capped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push a frame into the current or next fitting region, finishing regions
|
||||||
|
/// if necessary.
|
||||||
fn push_frame(&mut self, frame: Frame, aligns: Gen<Align>) {
|
fn push_frame(&mut self, frame: Frame, aligns: Gen<Align>) {
|
||||||
let size = frame.size;
|
let size = frame.size.to_gen(self.main);
|
||||||
|
|
||||||
// Don't allow `Start` after `End` in the same region.
|
// Don't allow `Start` after `End` in the same region.
|
||||||
if self.ruler > aligns.main {
|
if aligns.main < self.ruler {
|
||||||
self.finish_region();
|
self.finish_region();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust the ruler.
|
|
||||||
self.ruler = aligns.main;
|
|
||||||
|
|
||||||
// Find a fitting region.
|
// Find a fitting region.
|
||||||
while !self.regions.current.fits(size) && !self.regions.in_full_last() {
|
while !self.regions.current.get(self.main).fits(size.main)
|
||||||
|
&& !self.regions.in_full_last()
|
||||||
|
{
|
||||||
self.finish_region();
|
self.finish_region();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remember the frame with offset and alignment.
|
// Shrink available space in the region.
|
||||||
self.frames.push((self.used.main, aligns, frame));
|
*self.regions.current.get_mut(self.main) -= size.main;
|
||||||
|
|
||||||
// Grow our size and shrink available space in the region.
|
// Grow our size.
|
||||||
let gen = size.to_gen(self.main);
|
let offset = self.used.main;
|
||||||
self.used.main += gen.main;
|
self.used.main += size.main;
|
||||||
self.used.cross.set_max(gen.cross);
|
self.used.cross.set_max(size.cross);
|
||||||
*self.regions.current.get_mut(self.main) -= gen.main;
|
self.ruler = aligns.main;
|
||||||
|
|
||||||
|
// Remember the frame with offset and alignment.
|
||||||
|
self.frames.push((offset, aligns, frame));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finish the frame for one region.
|
||||||
fn finish_region(&mut self) {
|
fn finish_region(&mut self) {
|
||||||
let used = self.used.to_size(self.main);
|
|
||||||
let expand = self.expand;
|
let expand = self.expand;
|
||||||
|
let used = self.used.to_size(self.main);
|
||||||
|
|
||||||
// Determine the stack's size dependening on whether the region is
|
// Determine the stack's size dependening on whether the region is
|
||||||
// fixed.
|
// fixed.
|
||||||
let mut stack_size = Size::new(
|
let mut size = Size::new(
|
||||||
if expand.horizontal { self.full.width } else { used.width },
|
if expand.horizontal { self.full.width } else { used.width },
|
||||||
if expand.vertical { self.full.height } else { used.height },
|
if expand.vertical { self.full.height } else { used.height },
|
||||||
);
|
);
|
||||||
|
|
||||||
// Make sure the stack's size satisfies the aspect ratio.
|
// Make sure the stack's size satisfies the aspect ratio.
|
||||||
if let Some(aspect) = self.stack.aspect {
|
if let Some(aspect) = self.stack.aspect {
|
||||||
let width = stack_size
|
let width = size
|
||||||
.width
|
.width
|
||||||
.max(aspect.into_inner() * stack_size.height)
|
.max(aspect.into_inner() * size.height)
|
||||||
.min(self.full.width)
|
.min(self.full.width)
|
||||||
.min(aspect.into_inner() * self.full.height);
|
.min(aspect.into_inner() * self.full.height);
|
||||||
|
|
||||||
stack_size = Size::new(width, width / aspect.into_inner());
|
size = Size::new(width, width / aspect.into_inner());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut output = Frame::new(stack_size, stack_size.height);
|
let mut output = Frame::new(size, size.height);
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
|
|
||||||
// Place all frames.
|
// Place all frames.
|
||||||
for (offset, aligns, frame) in std::mem::take(&mut self.frames) {
|
for (offset, aligns, frame) in self.frames.drain(..) {
|
||||||
let stack_size = stack_size.to_gen(self.main);
|
let stack_size = size.to_gen(self.main);
|
||||||
let child_size = frame.size.to_gen(self.main);
|
let child_size = frame.size.to_gen(self.main);
|
||||||
|
|
||||||
// Align along the cross axis.
|
// Align along the cross axis.
|
||||||
@ -206,10 +208,9 @@ impl<'a> StackLayouter<'a> {
|
|||||||
output.push_frame(pos, frame);
|
output.push_frame(pos, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move on to the next region.
|
|
||||||
self.regions.next();
|
self.regions.next();
|
||||||
if let Some(aspect) = self.stack.aspect {
|
if let Some(aspect) = self.stack.aspect {
|
||||||
self.regions.apply_aspect_ratio(aspect);
|
self.regions.current = self.regions.current.with_aspect(aspect.into_inner());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.full = self.regions.current;
|
self.full = self.regions.current;
|
||||||
|
@ -221,7 +221,7 @@ fn test_part(
|
|||||||
// We want to have "unbounded" pages, so we allow them to be infinitely
|
// We want to have "unbounded" pages, so we allow them to be infinitely
|
||||||
// large and fit them to match their content.
|
// large and fit them to match their content.
|
||||||
let mut state = State::default();
|
let mut state = State::default();
|
||||||
state.page.size = Size::new(Length::pt(120.0), Length::raw(f64::INFINITY));
|
state.page.size = Size::new(Length::pt(120.0), Length::inf());
|
||||||
state.page.margins = Sides::splat(Some(Length::pt(10.0).into()));
|
state.page.margins = Sides::splat(Some(Length::pt(10.0).into()));
|
||||||
|
|
||||||
let mut pass = typst::typeset(loader, cache, Some(src_path), &src, &scope, state);
|
let mut pass = typst::typeset(loader, cache, Some(src_path), &src, &scope, state);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user