mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Typesafe conversions in stack & line layouters 🍮
This commit is contained in:
parent
985fe28166
commit
4252f959f7
65
src/geom.rs
65
src/geom.rs
@ -6,9 +6,9 @@ pub use kurbo::*;
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::ops::*;
|
use std::ops::*;
|
||||||
|
|
||||||
use crate::layout::primitive::{Dir, Gen2, GenAlign, Get, Side, SpecAxis};
|
use crate::layout::{Dir, Gen2, GenAlign, Get, Side, Spec2, SpecAxis, Switch};
|
||||||
|
|
||||||
macro_rules! impl_get_2d {
|
macro_rules! impl_2d {
|
||||||
($t:ty, $x:ident, $y:ident) => {
|
($t:ty, $x:ident, $y:ident) => {
|
||||||
impl Get<SpecAxis> for $t {
|
impl Get<SpecAxis> for $t {
|
||||||
type Component = f64;
|
type Component = f64;
|
||||||
@ -27,12 +27,20 @@ macro_rules! impl_get_2d {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Switch for $t {
|
||||||
|
type Other = Gen2<f64>;
|
||||||
|
|
||||||
|
fn switch(self, dirs: Gen2<Dir>) -> Self::Other {
|
||||||
|
Spec2::new(self.$x, self.$y).switch(dirs)
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_get_2d!(Point, x, y);
|
impl_2d!(Point, x, y);
|
||||||
impl_get_2d!(Vec2, x, y);
|
impl_2d!(Vec2, x, y);
|
||||||
impl_get_2d!(Size, width, height);
|
impl_2d!(Size, width, height);
|
||||||
|
|
||||||
impl Get<Side> for Rect {
|
impl Get<Side> for Rect {
|
||||||
type Component = f64;
|
type Component = f64;
|
||||||
@ -60,61 +68,36 @@ impl Get<Side> for Rect {
|
|||||||
///
|
///
|
||||||
/// [sizes]: ../../kurbo/struct.Size.html
|
/// [sizes]: ../../kurbo/struct.Size.html
|
||||||
pub trait SizeExt {
|
pub trait SizeExt {
|
||||||
/// Returns the generalized version of a `Size` based on the current
|
|
||||||
/// directions.
|
|
||||||
///
|
|
||||||
/// In the generalized version:
|
|
||||||
/// - `x` describes the cross axis instead of the horizontal one.
|
|
||||||
/// - `y` describes the main axis instead of the vertical one.
|
|
||||||
fn generalized(self, dirs: Gen2<Dir>) -> Self;
|
|
||||||
|
|
||||||
/// Returns the specialized version of this generalized `Size` (inverse to
|
|
||||||
/// `generalized`).
|
|
||||||
fn specialized(self, dirs: Gen2<Dir>) -> Self;
|
|
||||||
|
|
||||||
/// Whether the given size fits into this one, that is, both coordinate
|
/// Whether the given size fits into this one, that is, both coordinate
|
||||||
/// values are smaller or equal.
|
/// values are smaller or equal.
|
||||||
fn fits(self, other: Self) -> bool;
|
fn fits(self, other: Self) -> bool;
|
||||||
|
|
||||||
/// The anchor position for an object to be aligned in a container with this
|
/// The anchor position for an object to be aligned in a container with this
|
||||||
/// size and the given directions.
|
/// size and the given directions.
|
||||||
///
|
|
||||||
/// This assumes the size to be generalized such that `width` corresponds to
|
|
||||||
/// the cross and `height` to the main axis.
|
|
||||||
fn anchor(self, dirs: Gen2<Dir>, aligns: Gen2<GenAlign>) -> Point;
|
fn anchor(self, dirs: Gen2<Dir>, aligns: Gen2<GenAlign>) -> Point;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SizeExt for Size {
|
impl SizeExt for Size {
|
||||||
fn generalized(self, dirs: Gen2<Dir>) -> Self {
|
|
||||||
match dirs.main.axis() {
|
|
||||||
SpecAxis::Horizontal => Self::new(self.height, self.width),
|
|
||||||
SpecAxis::Vertical => self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn specialized(self, dirs: Gen2<Dir>) -> Self {
|
|
||||||
// Even though generalized is its own inverse, we still have this second
|
|
||||||
// function, for clarity at the call-site.
|
|
||||||
self.generalized(dirs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fits(self, other: Self) -> bool {
|
fn fits(self, other: Self) -> bool {
|
||||||
self.width >= other.width && self.height >= other.height
|
self.width >= other.width && self.height >= other.height
|
||||||
}
|
}
|
||||||
|
|
||||||
fn anchor(self, dirs: Gen2<Dir>, aligns: Gen2<GenAlign>) -> Point {
|
fn anchor(self, dirs: Gen2<Dir>, aligns: Gen2<GenAlign>) -> Point {
|
||||||
fn anchor(length: f64, dir: Dir, align: GenAlign) -> f64 {
|
fn anchor(length: f64, dir: Dir, align: GenAlign) -> f64 {
|
||||||
match (dir.is_positive(), align) {
|
match if dir.is_positive() { align } else { align.inv() } {
|
||||||
(true, GenAlign::Start) | (false, GenAlign::End) => 0.0,
|
GenAlign::Start => 0.0,
|
||||||
(_, GenAlign::Center) => length / 2.0,
|
GenAlign::Center => length / 2.0,
|
||||||
(true, GenAlign::End) | (false, GenAlign::Start) => length,
|
GenAlign::End => length,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Point::new(
|
let switched = self.switch(dirs);
|
||||||
anchor(self.width, dirs.cross, aligns.cross),
|
let generic = Gen2::new(
|
||||||
anchor(self.height, dirs.main, aligns.main),
|
anchor(switched.main, dirs.main, aligns.main),
|
||||||
)
|
anchor(switched.cross, dirs.cross, aligns.cross),
|
||||||
|
);
|
||||||
|
|
||||||
|
generic.switch(dirs).to_point()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,11 +49,12 @@ impl LineLayouter {
|
|||||||
|
|
||||||
/// Add a layout.
|
/// Add a layout.
|
||||||
pub fn add(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) {
|
pub fn add(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) {
|
||||||
|
let dirs = self.ctx.dirs;
|
||||||
if let Some(prev) = self.run.aligns {
|
if let Some(prev) = self.run.aligns {
|
||||||
if aligns.main != prev.main {
|
if aligns.main != prev.main {
|
||||||
// TODO: Issue warning for non-fitting alignment in
|
// TODO: Issue warning for non-fitting alignment in
|
||||||
// non-repeating context.
|
// non-repeating context.
|
||||||
let fitting = self.stack.is_fitting_alignment(aligns);
|
let fitting = aligns.main >= self.stack.space.allowed_align;
|
||||||
if !fitting && self.ctx.repeat {
|
if !fitting && self.ctx.repeat {
|
||||||
self.finish_space(true);
|
self.finish_space(true);
|
||||||
} else {
|
} else {
|
||||||
@ -62,20 +63,20 @@ impl LineLayouter {
|
|||||||
} else if aligns.cross < prev.cross {
|
} else if aligns.cross < prev.cross {
|
||||||
self.finish_line();
|
self.finish_line();
|
||||||
} else if aligns.cross > prev.cross {
|
} else if aligns.cross > prev.cross {
|
||||||
let mut rest_run = LineRun::new();
|
let usable = self.stack.usable().get(dirs.cross.axis());
|
||||||
|
|
||||||
let usable = self.stack.usable().get(self.ctx.dirs.cross.axis());
|
let mut rest_run = LineRun::new();
|
||||||
|
rest_run.size.main = self.run.size.main;
|
||||||
rest_run.usable = Some(match aligns.cross {
|
rest_run.usable = Some(match aligns.cross {
|
||||||
GenAlign::Start => unreachable!("start > x"),
|
GenAlign::Start => unreachable!("start > x"),
|
||||||
GenAlign::Center => usable - 2.0 * self.run.size.width,
|
GenAlign::Center => usable - 2.0 * self.run.size.cross,
|
||||||
GenAlign::End => usable - self.run.size.width,
|
GenAlign::End => usable - self.run.size.cross,
|
||||||
});
|
});
|
||||||
|
|
||||||
rest_run.size.height = self.run.size.height;
|
|
||||||
|
|
||||||
self.finish_line();
|
self.finish_line();
|
||||||
self.stack.add_spacing(-rest_run.size.height, SpacingKind::Hard);
|
|
||||||
|
|
||||||
|
// Move back up in the stack layouter.
|
||||||
|
self.stack.add_spacing(-rest_run.size.main, SpacingKind::Hard);
|
||||||
self.run = rest_run;
|
self.run = rest_run;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,24 +85,26 @@ impl LineLayouter {
|
|||||||
self.add_cross_spacing(spacing, SpacingKind::Hard);
|
self.add_cross_spacing(spacing, SpacingKind::Hard);
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = layout.size.generalized(self.ctx.dirs);
|
let size = layout.size.switch(dirs);
|
||||||
|
let usable = self.usable();
|
||||||
|
|
||||||
if !self.usable().fits(size) {
|
if usable.main < size.main || usable.cross < size.cross {
|
||||||
if !self.line_is_empty() {
|
if !self.line_is_empty() {
|
||||||
self.finish_line();
|
self.finish_line();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Issue warning about overflow if there is overflow.
|
// TODO: Issue warning about overflow if there is overflow.
|
||||||
if !self.usable().fits(size) {
|
let usable = self.usable();
|
||||||
|
if usable.main < size.main || usable.cross < size.cross {
|
||||||
self.stack.skip_to_fitting_space(layout.size);
|
self.stack.skip_to_fitting_space(layout.size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.run.aligns = Some(aligns);
|
self.run.aligns = Some(aligns);
|
||||||
self.run.layouts.push((self.run.size.width, layout));
|
self.run.layouts.push((self.run.size.cross, layout));
|
||||||
|
|
||||||
self.run.size.width += size.width;
|
self.run.size.cross += size.cross;
|
||||||
self.run.size.height = self.run.size.height.max(size.height);
|
self.run.size.main = self.run.size.main.max(size.main);
|
||||||
self.run.last_spacing = LastSpacing::None;
|
self.run.last_spacing = LastSpacing::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,16 +112,16 @@ impl LineLayouter {
|
|||||||
///
|
///
|
||||||
/// This specifies how much more would fit before a line break would be
|
/// This specifies how much more would fit before a line break would be
|
||||||
/// needed.
|
/// needed.
|
||||||
fn usable(&self) -> Size {
|
fn usable(&self) -> Gen2<f64> {
|
||||||
// The base is the usable space of the stack layouter.
|
// The base is the usable space of the stack layouter.
|
||||||
let mut usable = self.stack.usable().generalized(self.ctx.dirs);
|
let mut usable = self.stack.usable().switch(self.ctx.dirs);
|
||||||
|
|
||||||
// If there was another run already, override the stack's size.
|
// If there was another run already, override the stack's size.
|
||||||
if let Some(cross) = self.run.usable {
|
if let Some(cross) = self.run.usable {
|
||||||
usable.width = cross;
|
usable.cross = cross;
|
||||||
}
|
}
|
||||||
|
|
||||||
usable.width -= self.run.size.width;
|
usable.cross -= self.run.size.cross;
|
||||||
usable
|
usable
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,8 +135,8 @@ impl LineLayouter {
|
|||||||
pub fn add_cross_spacing(&mut self, mut spacing: f64, kind: SpacingKind) {
|
pub fn add_cross_spacing(&mut self, mut spacing: f64, kind: SpacingKind) {
|
||||||
match kind {
|
match kind {
|
||||||
SpacingKind::Hard => {
|
SpacingKind::Hard => {
|
||||||
spacing = spacing.min(self.usable().width);
|
spacing = spacing.min(self.usable().cross);
|
||||||
self.run.size.width += spacing;
|
self.run.size.cross += spacing;
|
||||||
self.run.last_spacing = LastSpacing::Hard;
|
self.run.last_spacing = LastSpacing::Hard;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,13 +174,13 @@ impl LineLayouter {
|
|||||||
/// it will fit into this layouter's underlying stack.
|
/// it will fit into this layouter's underlying stack.
|
||||||
pub fn remaining(&self) -> Vec<LayoutSpace> {
|
pub fn remaining(&self) -> Vec<LayoutSpace> {
|
||||||
let mut spaces = self.stack.remaining();
|
let mut spaces = self.stack.remaining();
|
||||||
*spaces[0].size.get_mut(self.ctx.dirs.main.axis()) -= self.run.size.height;
|
*spaces[0].size.get_mut(self.ctx.dirs.main.axis()) -= self.run.size.main;
|
||||||
spaces
|
spaces
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the currently set line is empty.
|
/// Whether the currently set line is empty.
|
||||||
pub fn line_is_empty(&self) -> bool {
|
pub fn line_is_empty(&self) -> bool {
|
||||||
self.run.size == Size::ZERO && self.run.layouts.is_empty()
|
self.run.size == Gen2::ZERO && self.run.layouts.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish everything up and return the final collection of boxes.
|
/// Finish everything up and return the final collection of boxes.
|
||||||
@ -196,19 +199,20 @@ impl LineLayouter {
|
|||||||
|
|
||||||
/// Finish the active line and start a new one.
|
/// Finish the active line and start a new one.
|
||||||
pub fn finish_line(&mut self) {
|
pub fn finish_line(&mut self) {
|
||||||
let mut layout = BoxLayout::new(self.run.size.specialized(self.ctx.dirs));
|
let dirs = self.ctx.dirs;
|
||||||
let aligns = self.run.aligns.unwrap_or_default();
|
|
||||||
let cross = self.ctx.dirs.cross;
|
|
||||||
|
|
||||||
let layouts = std::mem::take(&mut self.run.layouts);
|
let mut layout = BoxLayout::new(self.run.size.switch(dirs).to_size());
|
||||||
for (offset, child) in layouts {
|
let aligns = self.run.aligns.unwrap_or_default();
|
||||||
let mut pos = Point::ZERO;
|
|
||||||
*pos.get_mut(cross.axis()) = if cross.is_positive() {
|
let children = std::mem::take(&mut self.run.layouts);
|
||||||
|
for (offset, child) in children {
|
||||||
|
let cross = if dirs.cross.is_positive() {
|
||||||
offset
|
offset
|
||||||
} else {
|
} else {
|
||||||
self.run.size.width - offset - child.size.get(cross.axis())
|
self.run.size.cross - offset - child.size.get(dirs.cross.axis())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let pos = Gen2::new(0.0, cross).switch(dirs).to_point();
|
||||||
layout.push_layout(pos, child);
|
layout.push_layout(pos, child);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,15 +235,15 @@ struct LineRun {
|
|||||||
/// The so-far accumulated items of the run.
|
/// The so-far accumulated items of the run.
|
||||||
layouts: Vec<(f64, BoxLayout)>,
|
layouts: Vec<(f64, BoxLayout)>,
|
||||||
/// The summed width and maximal height of the run.
|
/// The summed width and maximal height of the run.
|
||||||
size: Size,
|
size: Gen2<f64>,
|
||||||
/// The alignment of all layouts in the line.
|
/// The alignment of all layouts in the line.
|
||||||
///
|
///
|
||||||
/// When a new run is created the alignment is yet to be determined and
|
/// When a new run is created the alignment is yet to be determined and
|
||||||
/// `None` as such. Once a layout is added, its alignment decides the
|
/// `None` as such. Once a layout is added, its alignment decides the
|
||||||
/// alignment for the whole run.
|
/// alignment for the whole run.
|
||||||
aligns: Option<Gen2<GenAlign>>,
|
aligns: Option<Gen2<GenAlign>>,
|
||||||
/// The amount of space left by another run on the same line or `None` if
|
/// The amount of cross-space left by another run on the same line or `None`
|
||||||
/// this is the only run so far.
|
/// if this is the only run so far.
|
||||||
usable: Option<f64>,
|
usable: Option<f64>,
|
||||||
/// The spacing state. This influences how new spacing is handled, e.g. hard
|
/// The spacing state. This influences how new spacing is handled, e.g. hard
|
||||||
/// spacing may override soft spacing.
|
/// spacing may override soft spacing.
|
||||||
@ -250,7 +254,7 @@ impl LineRun {
|
|||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
layouts: vec![],
|
layouts: vec![],
|
||||||
size: Size::ZERO,
|
size: Gen2::ZERO,
|
||||||
aligns: None,
|
aligns: None,
|
||||||
usable: None,
|
usable: None,
|
||||||
last_spacing: LastSpacing::Hard,
|
last_spacing: LastSpacing::Hard,
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
|
use crate::geom::{Point, Size, Vec2};
|
||||||
|
|
||||||
/// Generic access to a structure's components.
|
/// Generic access to a structure's components.
|
||||||
pub trait Get<Index> {
|
pub trait Get<Index> {
|
||||||
/// The structure's component type.
|
/// The structure's component type.
|
||||||
@ -14,36 +16,16 @@ pub trait Get<Index> {
|
|||||||
fn get_mut(&mut self, index: Index) -> &mut Self::Component;
|
fn get_mut(&mut self, index: Index) -> &mut Self::Component;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a type into its generic representation.
|
/// Switch between the specific and generic representations of a type.
|
||||||
///
|
///
|
||||||
/// The generic representation deals with main and cross axes while the specific
|
/// The generic representation deals with main and cross axes while the specific
|
||||||
/// representation deals with horizontal and vertical axes.
|
/// representation deals with horizontal and vertical axes.
|
||||||
///
|
pub trait Switch {
|
||||||
/// See also [`ToSpec`] for the inverse conversion.
|
/// The type of the other version.
|
||||||
///
|
type Other;
|
||||||
/// [`ToSpec`]: trait.ToSpec.html
|
|
||||||
pub trait ToGen {
|
|
||||||
/// The generic version of this type.
|
|
||||||
type Output;
|
|
||||||
|
|
||||||
/// The generic version of this type based on the current directions.
|
/// The other version of this type based on the current directions.
|
||||||
fn to_gen(self, dirs: Gen2<Dir>) -> Self::Output;
|
fn switch(self, dirs: Gen2<Dir>) -> Self::Other;
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert a type into its specific representation.
|
|
||||||
///
|
|
||||||
/// The specific representation deals with horizontal and vertical axes while
|
|
||||||
/// the generic representation deals with main and cross axes.
|
|
||||||
///
|
|
||||||
/// See also [`ToGen`] for the inverse conversion.
|
|
||||||
///
|
|
||||||
/// [`ToGen`]: trait.ToGen.html
|
|
||||||
pub trait ToSpec {
|
|
||||||
/// The specific version of this type.
|
|
||||||
type Output;
|
|
||||||
|
|
||||||
/// The specific version of this type based on the current directions.
|
|
||||||
fn to_spec(self, dirs: Gen2<Dir>) -> Self::Output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The four directions into which content can be laid out.
|
/// The four directions into which content can be laid out.
|
||||||
@ -68,6 +50,26 @@ impl Dir {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The side this direction starts at.
|
||||||
|
pub fn start(self) -> Side {
|
||||||
|
match self {
|
||||||
|
Self::LTR => Side::Left,
|
||||||
|
Self::RTL => Side::Right,
|
||||||
|
Self::TTB => Side::Top,
|
||||||
|
Self::BTT => Side::Bottom,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The side this direction ends at.
|
||||||
|
pub fn end(self) -> Side {
|
||||||
|
match self {
|
||||||
|
Self::LTR => Side::Right,
|
||||||
|
Self::RTL => Side::Left,
|
||||||
|
Self::TTB => Side::Bottom,
|
||||||
|
Self::BTT => Side::Top,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether this direction points into the positive coordinate direction.
|
/// Whether this direction points into the positive coordinate direction.
|
||||||
///
|
///
|
||||||
/// The positive directions are left-to-right and top-to-bottom.
|
/// The positive directions are left-to-right and top-to-bottom.
|
||||||
@ -86,23 +88,6 @@ impl Dir {
|
|||||||
if self.is_positive() { 1.0 } else { -1.0 }
|
if self.is_positive() { 1.0 } else { -1.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The side of this direction the alignment identifies.
|
|
||||||
///
|
|
||||||
/// `Center` alignment is treated the same as `Start` alignment.
|
|
||||||
pub fn side(self, align: GenAlign) -> Side {
|
|
||||||
let start = match self {
|
|
||||||
Self::LTR => Side::Left,
|
|
||||||
Self::RTL => Side::Right,
|
|
||||||
Self::TTB => Side::Top,
|
|
||||||
Self::BTT => Side::Bottom,
|
|
||||||
};
|
|
||||||
|
|
||||||
match align {
|
|
||||||
GenAlign::Start | GenAlign::Center => start,
|
|
||||||
GenAlign::End => start.inv(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The inverse direction.
|
/// The inverse direction.
|
||||||
pub fn inv(self) -> Self {
|
pub fn inv(self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
@ -159,10 +144,10 @@ impl<T> Get<GenAxis> for Gen2<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ToSpec for Gen2<T> {
|
impl<T> Switch for Gen2<T> {
|
||||||
type Output = Spec2<T>;
|
type Other = Spec2<T>;
|
||||||
|
|
||||||
fn to_spec(self, dirs: Gen2<Dir>) -> Self::Output {
|
fn switch(self, dirs: Gen2<Dir>) -> Self::Other {
|
||||||
match dirs.main.axis() {
|
match dirs.main.axis() {
|
||||||
SpecAxis::Horizontal => Spec2::new(self.main, self.cross),
|
SpecAxis::Horizontal => Spec2::new(self.main, self.cross),
|
||||||
SpecAxis::Vertical => Spec2::new(self.cross, self.main),
|
SpecAxis::Vertical => Spec2::new(self.cross, self.main),
|
||||||
@ -170,6 +155,11 @@ impl<T> ToSpec for Gen2<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Gen2<f64> {
|
||||||
|
/// The instance that has both components set to zero.
|
||||||
|
pub const ZERO: Self = Self { main: 0.0, cross: 0.0 };
|
||||||
|
}
|
||||||
|
|
||||||
/// A generic container with two components for the two specific axes.
|
/// A generic container with two components for the two specific axes.
|
||||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||||
pub struct Spec2<T> {
|
pub struct Spec2<T> {
|
||||||
@ -204,10 +194,10 @@ impl<T> Get<SpecAxis> for Spec2<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ToGen for Spec2<T> {
|
impl<T> Switch for Spec2<T> {
|
||||||
type Output = Gen2<T>;
|
type Other = Gen2<T>;
|
||||||
|
|
||||||
fn to_gen(self, dirs: Gen2<Dir>) -> Self::Output {
|
fn switch(self, dirs: Gen2<Dir>) -> Self::Other {
|
||||||
match dirs.main.axis() {
|
match dirs.main.axis() {
|
||||||
SpecAxis::Horizontal => Gen2::new(self.horizontal, self.vertical),
|
SpecAxis::Horizontal => Gen2::new(self.horizontal, self.vertical),
|
||||||
SpecAxis::Vertical => Gen2::new(self.vertical, self.horizontal),
|
SpecAxis::Vertical => Gen2::new(self.vertical, self.horizontal),
|
||||||
@ -215,6 +205,26 @@ impl<T> ToGen for Spec2<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Spec2<f64> {
|
||||||
|
/// The instance that has both components set to zero.
|
||||||
|
pub const ZERO: Self = Self { horizontal: 0.0, vertical: 0.0 };
|
||||||
|
|
||||||
|
/// Convert to a 2D vector.
|
||||||
|
pub fn to_vec2(self) -> Vec2 {
|
||||||
|
Vec2::new(self.horizontal, self.vertical)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert to a point.
|
||||||
|
pub fn to_point(self) -> Point {
|
||||||
|
Point::new(self.horizontal, self.vertical)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert to a size.
|
||||||
|
pub fn to_size(self) -> Size {
|
||||||
|
Size::new(self.horizontal, self.vertical)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The two generic layouting axes.
|
/// The two generic layouting axes.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum GenAxis {
|
pub enum GenAxis {
|
||||||
@ -234,10 +244,10 @@ impl GenAxis {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToSpec for GenAxis {
|
impl Switch for GenAxis {
|
||||||
type Output = SpecAxis;
|
type Other = SpecAxis;
|
||||||
|
|
||||||
fn to_spec(self, dirs: Gen2<Dir>) -> Self::Output {
|
fn switch(self, dirs: Gen2<Dir>) -> Self::Other {
|
||||||
match self {
|
match self {
|
||||||
Self::Main => dirs.main.axis(),
|
Self::Main => dirs.main.axis(),
|
||||||
Self::Cross => dirs.cross.axis(),
|
Self::Cross => dirs.cross.axis(),
|
||||||
@ -273,10 +283,10 @@ impl SpecAxis {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToGen for SpecAxis {
|
impl Switch for SpecAxis {
|
||||||
type Output = GenAxis;
|
type Other = GenAxis;
|
||||||
|
|
||||||
fn to_gen(self, dirs: Gen2<Dir>) -> Self::Output {
|
fn switch(self, dirs: Gen2<Dir>) -> Self::Other {
|
||||||
if self == dirs.main.axis() {
|
if self == dirs.main.axis() {
|
||||||
GenAxis::Main
|
GenAxis::Main
|
||||||
} else {
|
} else {
|
||||||
@ -366,11 +376,10 @@ impl SpecAlign {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToGen for SpecAlign {
|
impl Switch for SpecAlign {
|
||||||
type Output = GenAlign;
|
type Other = GenAlign;
|
||||||
|
|
||||||
fn to_gen(self, dirs: Gen2<Dir>) -> Self::Output {
|
fn switch(self, dirs: Gen2<Dir>) -> Self::Other {
|
||||||
let dirs = dirs.to_spec(dirs);
|
|
||||||
let get = |dir: Dir, at_positive_start| {
|
let get = |dir: Dir, at_positive_start| {
|
||||||
if dir.is_positive() == at_positive_start {
|
if dir.is_positive() == at_positive_start {
|
||||||
GenAlign::Start
|
GenAlign::Start
|
||||||
@ -379,6 +388,7 @@ impl ToGen for SpecAlign {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let dirs = dirs.switch(dirs);
|
||||||
match self {
|
match self {
|
||||||
Self::Left => get(dirs.horizontal, true),
|
Self::Left => get(dirs.horizontal, true),
|
||||||
Self::Right => get(dirs.horizontal, false),
|
Self::Right => get(dirs.horizontal, false),
|
||||||
|
@ -28,7 +28,7 @@ pub struct StackLayouter {
|
|||||||
/// The finished layouts.
|
/// The finished layouts.
|
||||||
layouts: Vec<BoxLayout>,
|
layouts: Vec<BoxLayout>,
|
||||||
/// The in-progress space.
|
/// The in-progress space.
|
||||||
space: Space,
|
pub(super) space: Space,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The context for stack layouting.
|
/// The context for stack layouting.
|
||||||
@ -57,30 +57,31 @@ impl StackLayouter {
|
|||||||
/// Add a layout to the stack.
|
/// Add a layout to the stack.
|
||||||
pub fn add(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) {
|
pub fn add(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) {
|
||||||
// If the alignment cannot be fitted in this space, finish it.
|
// If the alignment cannot be fitted in this space, finish it.
|
||||||
|
//
|
||||||
// TODO: Issue warning for non-fitting alignment in non-repeating
|
// TODO: Issue warning for non-fitting alignment in non-repeating
|
||||||
// context.
|
// context.
|
||||||
if !self.update_rulers(aligns) && self.ctx.repeat {
|
if aligns.main < self.space.allowed_align && self.ctx.repeat {
|
||||||
self.finish_space(true);
|
self.finish_space(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, we add a possibly cached soft space. If the main alignment
|
// Add a possibly cached soft spacing.
|
||||||
// changed before, a possibly cached space would have already been
|
|
||||||
// discarded.
|
|
||||||
if let LastSpacing::Soft(spacing, _) = self.space.last_spacing {
|
if let LastSpacing::Soft(spacing, _) = self.space.last_spacing {
|
||||||
self.add_spacing(spacing, SpacingKind::Hard);
|
self.add_spacing(spacing, SpacingKind::Hard);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Issue warning about overflow if there is overflow.
|
// TODO: Issue warning about overflow if there is overflow in a
|
||||||
|
// non-repeating context.
|
||||||
if !self.space.usable.fits(layout.size) && self.ctx.repeat {
|
if !self.space.usable.fits(layout.size) && self.ctx.repeat {
|
||||||
self.skip_to_fitting_space(layout.size);
|
self.skip_to_fitting_space(layout.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change the usable space and size of the space.
|
// Change the usable space and size of the space.
|
||||||
self.update_metrics(layout.size.generalized(self.ctx.dirs));
|
self.update_metrics(layout.size.switch(self.ctx.dirs));
|
||||||
|
|
||||||
// Add the box to the vector and remember that spacings are allowed
|
// Add the box to the vector and remember that spacings are allowed
|
||||||
// again.
|
// again.
|
||||||
self.space.layouts.push((layout, aligns));
|
self.space.layouts.push((layout, aligns));
|
||||||
|
self.space.allowed_align = aligns.main;
|
||||||
self.space.last_spacing = LastSpacing::None;
|
self.space.last_spacing = LastSpacing::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,10 +94,10 @@ impl StackLayouter {
|
|||||||
let axis = self.ctx.dirs.main.axis();
|
let axis = self.ctx.dirs.main.axis();
|
||||||
spacing = spacing.min(self.space.usable.get(axis));
|
spacing = spacing.min(self.space.usable.get(axis));
|
||||||
|
|
||||||
let size = Size::new(0.0, spacing);
|
let size = Gen2::new(spacing, 0.0);
|
||||||
self.update_metrics(size);
|
self.update_metrics(size);
|
||||||
self.space.layouts.push((
|
self.space.layouts.push((
|
||||||
BoxLayout::new(size.specialized(self.ctx.dirs)),
|
BoxLayout::new(size.switch(self.ctx.dirs).to_size()),
|
||||||
Gen2::default(),
|
Gen2::default(),
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -119,40 +120,18 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_metrics(&mut self, added: Size) {
|
fn update_metrics(&mut self, added: Gen2<f64>) {
|
||||||
let mut size = self.space.size.generalized(self.ctx.dirs);
|
let mut size = self.space.size.switch(self.ctx.dirs);
|
||||||
let mut extra = self.space.extra.generalized(self.ctx.dirs);
|
let mut extra = self.space.extra.switch(self.ctx.dirs);
|
||||||
|
|
||||||
size.width += (added.width - extra.width).max(0.0);
|
size.cross += (added.cross - extra.cross).max(0.0);
|
||||||
size.height += (added.height - extra.height).max(0.0);
|
size.main += (added.main - extra.main).max(0.0);
|
||||||
extra.width = extra.width.max(added.width);
|
extra.cross = extra.cross.max(added.cross);
|
||||||
extra.height = (extra.height - added.height).max(0.0);
|
extra.main = (extra.main - added.main).max(0.0);
|
||||||
|
|
||||||
self.space.size = size.specialized(self.ctx.dirs);
|
self.space.size = size.switch(self.ctx.dirs).to_size();
|
||||||
self.space.extra = extra.specialized(self.ctx.dirs);
|
self.space.extra = extra.switch(self.ctx.dirs).to_size();
|
||||||
*self.space.usable.get_mut(self.ctx.dirs.main.axis()) -= added.height;
|
*self.space.usable.get_mut(self.ctx.dirs.main.axis()) -= added.main;
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if a space break is necessary.
|
|
||||||
fn update_rulers(&mut self, aligns: Gen2<GenAlign>) -> bool {
|
|
||||||
let allowed = self.is_fitting_alignment(aligns);
|
|
||||||
if allowed {
|
|
||||||
let side = self.ctx.dirs.main.side(GenAlign::Start);
|
|
||||||
*self.space.rulers.get_mut(side) = aligns.main;
|
|
||||||
}
|
|
||||||
allowed
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether a layout with the given alignment can still be layouted into the
|
|
||||||
/// active space or a space break is necessary.
|
|
||||||
pub(crate) fn is_fitting_alignment(&self, aligns: Gen2<GenAlign>) -> bool {
|
|
||||||
self.is_fitting_axis(self.ctx.dirs.main, aligns.main)
|
|
||||||
&& self.is_fitting_axis(self.ctx.dirs.cross, aligns.cross)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_fitting_axis(&self, dir: Dir, align: GenAlign) -> bool {
|
|
||||||
align >= self.space.rulers.get(dir.side(GenAlign::Start))
|
|
||||||
&& align <= self.space.rulers.get(dir.side(GenAlign::End)).inv()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the layouting spaces.
|
/// Update the layouting spaces.
|
||||||
@ -202,8 +181,9 @@ impl StackLayouter {
|
|||||||
/// The remaining usable size.
|
/// The remaining usable size.
|
||||||
pub fn usable(&self) -> Size {
|
pub fn usable(&self) -> Size {
|
||||||
self.space.usable
|
self.space.usable
|
||||||
- Size::new(0.0, self.space.last_spacing.soft_or_zero())
|
- Gen2::new(self.space.last_spacing.soft_or_zero(), 0.0)
|
||||||
.specialized(self.ctx.dirs)
|
.switch(self.ctx.dirs)
|
||||||
|
.to_size()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the current layout space is empty.
|
/// Whether the current layout space is empty.
|
||||||
@ -227,30 +207,36 @@ impl StackLayouter {
|
|||||||
/// Finish active current space and start a new one.
|
/// Finish active current space and start a new one.
|
||||||
pub fn finish_space(&mut self, hard: bool) {
|
pub fn finish_space(&mut self, hard: bool) {
|
||||||
let dirs = self.ctx.dirs;
|
let dirs = self.ctx.dirs;
|
||||||
let space = self.ctx.spaces[self.space.index];
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
// Step 1: Determine the full size of the space.
|
// Step 1: Determine the full size of the space.
|
||||||
// (Mostly done already while collecting the boxes, but here we
|
// (Mostly done already while collecting the boxes, but here we
|
||||||
// expand if necessary.)
|
// expand if necessary.)
|
||||||
|
|
||||||
let usable = space.usable();
|
let space = self.ctx.spaces[self.space.index];
|
||||||
if space.expansion.horizontal {
|
let start = space.start();
|
||||||
self.space.size.width = usable.width;
|
let padded_size = {
|
||||||
}
|
let mut used_size = self.space.size;
|
||||||
if space.expansion.vertical {
|
|
||||||
self.space.size.height = usable.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
let size = self.space.size - space.insets.size();
|
let usable = space.usable();
|
||||||
|
if space.expansion.horizontal {
|
||||||
|
used_size.width = usable.width;
|
||||||
|
}
|
||||||
|
if space.expansion.vertical {
|
||||||
|
used_size.height = usable.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
used_size
|
||||||
|
};
|
||||||
|
|
||||||
|
let unpadded_size = padded_size - space.insets.size();
|
||||||
|
let mut layout = BoxLayout::new(unpadded_size);
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
// Step 2: Forward pass. Create a bounding box for each layout in which
|
// Step 2: Forward pass. Create a bounding box for each layout in which
|
||||||
// it will be aligned. Then, go forwards through the boxes and remove
|
// it will be aligned. Then, go forwards through the boxes and remove
|
||||||
// what is taken by previous layouts from the following layouts.
|
// what is taken by previous layouts from the following layouts.
|
||||||
|
|
||||||
let start = space.start();
|
|
||||||
|
|
||||||
let mut bounds = vec![];
|
let mut bounds = vec![];
|
||||||
let mut bound = Rect {
|
let mut bound = Rect {
|
||||||
x0: start.x,
|
x0: start.x,
|
||||||
@ -260,16 +246,16 @@ impl StackLayouter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (layout, _) in &self.space.layouts {
|
for (layout, _) in &self.space.layouts {
|
||||||
// First, we store the bounds calculated so far (which were reduced
|
// First, store the bounds calculated so far (which were reduced
|
||||||
// by the predecessors of this layout) as the initial bounding box
|
// by the predecessors of this layout) as the initial bounding box
|
||||||
// of this layout.
|
// of this layout.
|
||||||
bounds.push(bound);
|
bounds.push(bound);
|
||||||
|
|
||||||
// Then, we reduce the bounding box for the following layouts. This
|
// Then, reduce the bounding box for the following layouts. This
|
||||||
// layout uses up space from the origin to the end. Thus, it reduces
|
// layout uses up space from the origin to the end. Thus, it reduces
|
||||||
// the usable space for following layouts at its origin by its
|
// the usable space for following layouts at its origin by its
|
||||||
// main-axis extent.
|
// main-axis extent.
|
||||||
*bound.get_mut(dirs.main.side(GenAlign::Start)) +=
|
*bound.get_mut(dirs.main.start()) +=
|
||||||
dirs.main.factor() * layout.size.get(dirs.main.axis());
|
dirs.main.factor() * layout.size.get(dirs.main.axis());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,37 +263,30 @@ impl StackLayouter {
|
|||||||
// Step 3: Backward pass. Reduce the bounding boxes from the previous
|
// Step 3: Backward pass. Reduce the bounding boxes from the previous
|
||||||
// layouts by what is taken by the following ones.
|
// layouts by what is taken by the following ones.
|
||||||
|
|
||||||
// The `x` field stores the maximal cross-axis extent in one
|
|
||||||
// axis-aligned run, while the `y` fields stores the accumulated
|
|
||||||
// main-axis extent.
|
|
||||||
let mut main_extent = 0.0;
|
let mut main_extent = 0.0;
|
||||||
for (child, bound) in self.space.layouts.iter().zip(&mut bounds).rev() {
|
for (child, bound) in self.space.layouts.iter().zip(&mut bounds).rev() {
|
||||||
// We reduce the bounding box of this layout at its end by the
|
let (layout, _) = child;
|
||||||
// accumulated main-axis extent of all layouts we have seen so far
|
|
||||||
// (which are the layouts after this one since we iterate reversed).
|
|
||||||
*bound.get_mut(dirs.main.side(GenAlign::End)) -=
|
|
||||||
dirs.main.factor() * main_extent;
|
|
||||||
|
|
||||||
// And then, we include this layout's main-axis extent.
|
// Reduce the bounding box of this layout by the following one's
|
||||||
main_extent += child.0.size.get(dirs.main.axis());
|
// main-axis extents.
|
||||||
|
*bound.get_mut(dirs.main.end()) -= dirs.main.factor() * main_extent;
|
||||||
|
|
||||||
|
// And then, include this layout's main-axis extent.
|
||||||
|
main_extent += layout.size.get(dirs.main.axis());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
// Step 4: Align each layout in its bounding box and collect everything
|
// Step 4: Align each layout in its bounding box and collect everything
|
||||||
// into a single finished layout.
|
// into a single finished layout.
|
||||||
|
|
||||||
let mut layout = BoxLayout::new(size);
|
|
||||||
|
|
||||||
let children = std::mem::take(&mut self.space.layouts);
|
let children = std::mem::take(&mut self.space.layouts);
|
||||||
for ((child, aligns), bound) in children.into_iter().zip(bounds) {
|
for ((child, aligns), bound) in children.into_iter().zip(bounds) {
|
||||||
let size = child.size.specialized(dirs);
|
// Align the child in its own bounds.
|
||||||
|
let local =
|
||||||
// The space in which this layout is aligned is given by the
|
bound.size().anchor(dirs, aligns) - child.size.anchor(dirs, aligns);
|
||||||
// distances between the borders of its bounding box.
|
|
||||||
let usable = bound.size().generalized(dirs);
|
|
||||||
let local = usable.anchor(dirs, aligns) - size.anchor(dirs, aligns);
|
|
||||||
let pos = bound.origin() + local.to_size().specialized(dirs).to_vec2();
|
|
||||||
|
|
||||||
|
// Make the local position in the bounds global.
|
||||||
|
let pos = bound.origin() + local;
|
||||||
layout.push_layout(pos, child);
|
layout.push_layout(pos, child);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,23 +310,21 @@ impl StackLayouter {
|
|||||||
|
|
||||||
/// A layout space composed of subspaces which can have different directions and
|
/// A layout space composed of subspaces which can have different directions and
|
||||||
/// alignments.
|
/// alignments.
|
||||||
struct Space {
|
pub(super) struct Space {
|
||||||
/// The index of this space in `ctx.spaces`.
|
/// The index of this space in `ctx.spaces`.
|
||||||
index: usize,
|
index: usize,
|
||||||
/// Whether to include a layout for this space even if it would be empty.
|
/// Whether to include a layout for this space even if it would be empty.
|
||||||
hard: bool,
|
hard: bool,
|
||||||
/// The so-far accumulated layouts.
|
/// The so-far accumulated layouts.
|
||||||
layouts: Vec<(BoxLayout, Gen2<GenAlign>)>,
|
layouts: Vec<(BoxLayout, Gen2<GenAlign>)>,
|
||||||
/// The specialized size of this space.
|
/// The size of this space.
|
||||||
size: Size,
|
size: Size,
|
||||||
/// The specialized remaining space.
|
/// The remaining space.
|
||||||
usable: Size,
|
usable: Size,
|
||||||
/// The specialized extra-needed size to affect the size at all.
|
/// The extra-needed size to affect the size at all.
|
||||||
extra: Size,
|
extra: Size,
|
||||||
/// Dictate which alignments for new boxes are still allowed and which
|
/// Which alignments for new boxes are still allowed.
|
||||||
/// require a new space to be started. For example, after an `End`-aligned
|
pub(super) allowed_align: GenAlign,
|
||||||
/// item, no `Start`-aligned one can follow.
|
|
||||||
rulers: Sides<GenAlign>,
|
|
||||||
/// The spacing state. This influences how new spacing is handled, e.g. hard
|
/// The spacing state. This influences how new spacing is handled, e.g. hard
|
||||||
/// spacing may override soft spacing.
|
/// spacing may override soft spacing.
|
||||||
last_spacing: LastSpacing,
|
last_spacing: LastSpacing,
|
||||||
@ -362,7 +339,7 @@ impl Space {
|
|||||||
size: Size::ZERO,
|
size: Size::ZERO,
|
||||||
usable,
|
usable,
|
||||||
extra: Size::ZERO,
|
extra: Size::ZERO,
|
||||||
rulers: Sides::uniform(GenAlign::Start),
|
allowed_align: GenAlign::Start,
|
||||||
last_spacing: LastSpacing::Hard,
|
last_spacing: LastSpacing::Hard,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,8 @@ fn dedup_aligns(
|
|||||||
// Check whether we know which axis this alignment belongs to.
|
// Check whether we know which axis this alignment belongs to.
|
||||||
if let Some(axis) = axis {
|
if let Some(axis) = axis {
|
||||||
// We know the axis.
|
// We know the axis.
|
||||||
let gen_axis = axis.to_gen(ctx.state.dirs);
|
let gen_axis = axis.switch(ctx.state.dirs);
|
||||||
let gen_align = align.to_gen(ctx.state.dirs);
|
let gen_align = align.switch(ctx.state.dirs);
|
||||||
|
|
||||||
if align.axis().map_or(false, |a| a != axis) {
|
if align.axis().map_or(false, |a| a != axis) {
|
||||||
ctx.diag(error!(
|
ctx.diag(error!(
|
||||||
|
@ -24,7 +24,7 @@ fn spacing(mut args: Args, ctx: &mut LayoutContext, axis: SpecAxis) -> Value {
|
|||||||
|
|
||||||
Value::Commands(if let Some(spacing) = spacing {
|
Value::Commands(if let Some(spacing) = spacing {
|
||||||
let spacing = spacing.eval(ctx.state.text.font_size());
|
let spacing = spacing.eval(ctx.state.text.font_size());
|
||||||
let axis = axis.to_gen(ctx.state.dirs);
|
let axis = axis.switch(ctx.state.dirs);
|
||||||
vec![AddSpacing(spacing, SpacingKind::Hard, axis)]
|
vec![AddSpacing(spacing, SpacingKind::Hard, axis)]
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user