X/Y abstractions
This commit is contained in:
parent
393d74f9bb
commit
3a15922d2f
@ -226,7 +226,3 @@ impl Debug for Arg {
|
||||
Debug::fmt(&self.value.v, f)
|
||||
}
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
Args: "arguments",
|
||||
}
|
||||
|
@ -494,6 +494,10 @@ impl Eval for ClosureExpr {
|
||||
|
||||
// Put the remaining arguments into the sink.
|
||||
if let Some(sink) = &sink {
|
||||
dynamic! {
|
||||
Args: "arguments",
|
||||
}
|
||||
|
||||
ctx.scopes.def_mut(sink, args.take());
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ use std::convert::TryFrom;
|
||||
|
||||
use super::{Dynamic, Value};
|
||||
use crate::diag::StrResult;
|
||||
use crate::geom::{Align, Get, Spec};
|
||||
use crate::geom::{Align, Spec, SpecAxis};
|
||||
use crate::util::EcoString;
|
||||
use Value::*;
|
||||
|
||||
@ -94,14 +94,18 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
if let (Some(&a), Some(&b)) =
|
||||
(a.downcast::<Align>(), b.downcast::<Align>())
|
||||
{
|
||||
if a.axis() == b.axis() {
|
||||
return Err(format!("cannot add two {:?} alignments", a.axis()));
|
||||
dynamic! {
|
||||
Spec<Align>: "2d alignment",
|
||||
}
|
||||
|
||||
let mut aligns = Spec::default();
|
||||
aligns.set(a.axis(), Some(a));
|
||||
aligns.set(b.axis(), Some(b));
|
||||
return Ok(Dyn(Dynamic::new(aligns)));
|
||||
return if a.axis() != b.axis() {
|
||||
Ok(Dyn(Dynamic::new(match a.axis() {
|
||||
SpecAxis::Horizontal => Spec { x: a, y: b },
|
||||
SpecAxis::Vertical => Spec { x: b, y: a },
|
||||
})))
|
||||
} else {
|
||||
Err(format!("cannot add two {:?} alignments", a.axis()))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,8 +266,8 @@ impl<'a> PdfExporter<'a> {
|
||||
let mut page_writer = self.writer.page(page_id);
|
||||
page_writer.parent(page_tree_ref);
|
||||
|
||||
let w = page.size.w.to_f32();
|
||||
let h = page.size.h.to_f32();
|
||||
let w = page.size.x.to_f32();
|
||||
let h = page.size.y.to_f32();
|
||||
page_writer.media_box(Rect::new(0.0, 0.0, w, h));
|
||||
page_writer.contents(content_id);
|
||||
|
||||
@ -366,7 +366,7 @@ impl<'a> PageExporter<'a> {
|
||||
|
||||
fn export(mut self, frame: &Frame) -> Page {
|
||||
// Make the coordinate system start at the top-left.
|
||||
self.bottom = frame.size.h.to_f32();
|
||||
self.bottom = frame.size.y.to_f32();
|
||||
self.content.transform([1.0, 0.0, 0.0, -1.0, 0.0, self.bottom]);
|
||||
self.write_frame(&frame);
|
||||
Page {
|
||||
@ -397,8 +397,8 @@ impl<'a> PageExporter<'a> {
|
||||
self.transform(translation.pre_concat(group.transform));
|
||||
|
||||
if group.clips {
|
||||
let w = group.frame.size.w.to_f32();
|
||||
let h = group.frame.size.h.to_f32();
|
||||
let w = group.frame.size.x.to_f32();
|
||||
let h = group.frame.size.y.to_f32();
|
||||
self.content.move_to(0.0, 0.0);
|
||||
self.content.line_to(w, 0.0);
|
||||
self.content.line_to(w, h);
|
||||
@ -471,8 +471,8 @@ impl<'a> PageExporter<'a> {
|
||||
|
||||
match shape.geometry {
|
||||
Geometry::Rect(size) => {
|
||||
let w = size.w.to_f32();
|
||||
let h = size.h.to_f32();
|
||||
let w = size.x.to_f32();
|
||||
let h = size.y.to_f32();
|
||||
if w > 0.0 && h > 0.0 {
|
||||
self.content.rect(x, y, w, h);
|
||||
}
|
||||
@ -533,8 +533,8 @@ impl<'a> PageExporter<'a> {
|
||||
fn write_image(&mut self, x: f32, y: f32, id: ImageId, size: Size) {
|
||||
self.image_map.insert(id);
|
||||
let name = format_eco!("Im{}", self.image_map.map(id));
|
||||
let w = size.w.to_f32();
|
||||
let h = size.h.to_f32();
|
||||
let w = size.x.to_f32();
|
||||
let h = size.y.to_f32();
|
||||
self.content.save_state();
|
||||
self.content.transform([w, 0.0, 0.0, -h, x, y + h]);
|
||||
self.content.x_object(Name(name.as_bytes()));
|
||||
@ -550,8 +550,8 @@ impl<'a> PageExporter<'a> {
|
||||
// Compute the bounding box of the transformed link.
|
||||
for point in [
|
||||
pos,
|
||||
pos + Point::with_x(size.w),
|
||||
pos + Point::with_y(size.h),
|
||||
pos + Point::with_x(size.x),
|
||||
pos + Point::with_y(size.y),
|
||||
pos + size.to_point(),
|
||||
] {
|
||||
let t = point.transform(self.state.transform);
|
||||
|
18
src/frame.rs
18
src/frame.rs
@ -25,7 +25,7 @@ impl Frame {
|
||||
#[track_caller]
|
||||
pub fn new(size: Size) -> Self {
|
||||
assert!(size.is_finite());
|
||||
Self { size, baseline: size.h, elements: vec![] }
|
||||
Self { size, baseline: size.y, elements: vec![] }
|
||||
}
|
||||
|
||||
/// Add an element at a position in the background.
|
||||
@ -58,13 +58,15 @@ impl Frame {
|
||||
/// Resize the frame to a new size, distributing new space according to the
|
||||
/// given alignments.
|
||||
pub fn resize(&mut self, new: Size, aligns: Spec<Align>) {
|
||||
let offset = Point::new(
|
||||
aligns.x.resolve(new.w - self.size.w),
|
||||
aligns.y.resolve(new.h - self.size.h),
|
||||
);
|
||||
self.size = new;
|
||||
self.baseline += offset.y;
|
||||
self.translate(offset);
|
||||
if self.size != new {
|
||||
let offset = Point::new(
|
||||
aligns.x.resolve(new.x - self.size.x),
|
||||
aligns.y.resolve(new.y - self.size.y),
|
||||
);
|
||||
self.size = new;
|
||||
self.baseline += offset.y;
|
||||
self.translate(offset);
|
||||
}
|
||||
}
|
||||
|
||||
/// Move the contents of the frame by an offset.
|
||||
|
@ -53,21 +53,6 @@ impl Gen<Length> {
|
||||
pub fn to_point(self, main: SpecAxis) -> Point {
|
||||
self.to_spec(main).to_point()
|
||||
}
|
||||
|
||||
/// Convert to a size.
|
||||
pub fn to_size(self, main: SpecAxis) -> Size {
|
||||
self.to_spec(main).to_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Gen<Option<T>> {
|
||||
/// Unwrap the individual fields.
|
||||
pub fn unwrap_or(self, other: Gen<T>) -> Gen<T> {
|
||||
Gen {
|
||||
cross: self.cross.unwrap_or(other.cross),
|
||||
main: self.main.unwrap_or(other.main),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Get<GenAxis> for Gen<T> {
|
||||
|
@ -42,7 +42,7 @@ impl Linear {
|
||||
}
|
||||
|
||||
/// Whether there is a linear component.
|
||||
pub fn is_relative(&self) -> bool {
|
||||
pub fn is_relative(self) -> bool {
|
||||
!self.rel.is_zero()
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ mod point;
|
||||
mod relative;
|
||||
mod scalar;
|
||||
mod sides;
|
||||
mod size;
|
||||
mod spec;
|
||||
mod transform;
|
||||
|
||||
@ -34,7 +33,6 @@ pub use point::*;
|
||||
pub use relative::*;
|
||||
pub use scalar::*;
|
||||
pub use sides::*;
|
||||
pub use size::*;
|
||||
pub use spec::*;
|
||||
pub use transform::*;
|
||||
|
||||
|
@ -26,9 +26,9 @@ impl Path {
|
||||
let point = Point::new;
|
||||
let mut path = Self::new();
|
||||
path.move_to(point(z, z));
|
||||
path.line_to(point(size.w, z));
|
||||
path.line_to(point(size.w, size.h));
|
||||
path.line_to(point(z, size.h));
|
||||
path.line_to(point(size.x, z));
|
||||
path.line_to(point(size.x, size.y));
|
||||
path.line_to(point(z, size.y));
|
||||
path.close_path();
|
||||
path
|
||||
}
|
||||
@ -37,8 +37,8 @@ impl Path {
|
||||
pub fn ellipse(size: Size) -> Self {
|
||||
// https://stackoverflow.com/a/2007782
|
||||
let z = Length::zero();
|
||||
let rx = size.w / 2.0;
|
||||
let ry = size.h / 2.0;
|
||||
let rx = size.x / 2.0;
|
||||
let ry = size.y / 2.0;
|
||||
let m = 0.551784;
|
||||
let mx = m * rx;
|
||||
let my = m * ry;
|
||||
|
@ -2,7 +2,7 @@ use super::*;
|
||||
|
||||
/// A 64-bit float that implements `Eq`, `Ord` and `Hash`.
|
||||
///
|
||||
/// Panics if its `NaN` during any of those operations.
|
||||
/// Panics if it's `NaN` during any of those operations.
|
||||
#[derive(Default, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Scalar(pub f64);
|
||||
|
@ -33,11 +33,13 @@ impl<T> Sides<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Sides<Length> {
|
||||
/// A size with `left` and `right` summed into `width`, and `top` and
|
||||
/// `bottom` summed into `height`.
|
||||
pub fn size(self) -> Size {
|
||||
Size::new(self.left + self.right, self.top + self.bottom)
|
||||
impl<T> Sides<T>
|
||||
where
|
||||
T: Add,
|
||||
{
|
||||
/// Sums up `left` and `right` into `x`, and `top` and `bottom` into `y`.
|
||||
pub fn sum_by_axis(self) -> Spec<T::Output> {
|
||||
Spec::new(self.left + self.right, self.top + self.bottom)
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,10 +47,10 @@ impl Sides<Linear> {
|
||||
/// Resolve the linear sides relative to the given `size`.
|
||||
pub fn resolve(self, size: Size) -> Sides<Length> {
|
||||
Sides {
|
||||
left: self.left.resolve(size.w),
|
||||
top: self.top.resolve(size.h),
|
||||
right: self.right.resolve(size.w),
|
||||
bottom: self.bottom.resolve(size.h),
|
||||
left: self.left.resolve(size.x),
|
||||
top: self.top.resolve(size.y),
|
||||
right: self.right.resolve(size.x),
|
||||
bottom: self.bottom.resolve(size.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
131
src/geom/size.rs
131
src/geom/size.rs
@ -1,131 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
/// A size in 2D.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub struct Size {
|
||||
/// The width.
|
||||
pub w: Length,
|
||||
/// The height.
|
||||
pub h: Length,
|
||||
}
|
||||
|
||||
impl Size {
|
||||
/// The zero size.
|
||||
pub const fn zero() -> Self {
|
||||
Self { w: Length::zero(), h: Length::zero() }
|
||||
}
|
||||
|
||||
/// Create a new size from width and height.
|
||||
pub const fn new(w: Length, h: Length) -> Self {
|
||||
Self { w, h }
|
||||
}
|
||||
|
||||
/// Create an instance with two equal components.
|
||||
pub const fn splat(v: Length) -> Self {
|
||||
Self { w: v, h: v }
|
||||
}
|
||||
|
||||
/// Whether the other size fits into this one (smaller width and height).
|
||||
pub fn fits(self, other: Self) -> bool {
|
||||
self.w.fits(other.w) && self.h.fits(other.h)
|
||||
}
|
||||
|
||||
/// Whether both components are finite.
|
||||
pub fn is_finite(self) -> bool {
|
||||
self.w.is_finite() && self.h.is_finite()
|
||||
}
|
||||
|
||||
/// Whether any of the two components is infinite.
|
||||
pub fn is_infinite(self) -> bool {
|
||||
self.w.is_infinite() || self.h.is_infinite()
|
||||
}
|
||||
|
||||
/// Convert to a point.
|
||||
pub const fn to_point(self) -> Point {
|
||||
Point::new(self.w, self.h)
|
||||
}
|
||||
|
||||
/// Convert to a Spec.
|
||||
pub const fn to_spec(self) -> Spec<Length> {
|
||||
Spec::new(self.w, self.h)
|
||||
}
|
||||
|
||||
/// Convert to the generic representation.
|
||||
pub const fn to_gen(self, main: SpecAxis) -> Gen<Length> {
|
||||
match main {
|
||||
SpecAxis::Horizontal => Gen::new(self.h, self.w),
|
||||
SpecAxis::Vertical => Gen::new(self.w, self.h),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Get<SpecAxis> for Size {
|
||||
type Component = Length;
|
||||
|
||||
fn get(self, axis: SpecAxis) -> Length {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => self.w,
|
||||
SpecAxis::Vertical => self.h,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, axis: SpecAxis) -> &mut Length {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => &mut self.w,
|
||||
SpecAxis::Vertical => &mut self.h,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Size {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "Size({:?}, {:?})", self.w, self.h)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self { w: -self.w, h: -self.h }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self { w: self.w + other.w, h: self.h + other.h }
|
||||
}
|
||||
}
|
||||
|
||||
sub_impl!(Size - Size -> Size);
|
||||
|
||||
impl Mul<f64> for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
Self { w: self.w * other, h: self.h * other }
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Size> for f64 {
|
||||
type Output = Size;
|
||||
|
||||
fn mul(self, other: Size) -> Size {
|
||||
other * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
Self { w: self.w / other, h: self.h / other }
|
||||
}
|
||||
}
|
||||
|
||||
assign_impl!(Size -= Size);
|
||||
assign_impl!(Size += Size);
|
||||
assign_impl!(Size *= f64);
|
||||
assign_impl!(Size /= f64);
|
219
src/geom/spec.rs
219
src/geom/spec.rs
@ -1,7 +1,10 @@
|
||||
use std::any::Any;
|
||||
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A container with a horizontal and vertical component.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub struct Spec<T> {
|
||||
/// The horizontal component.
|
||||
pub x: T,
|
||||
@ -31,9 +34,18 @@ impl<T> Spec<T> {
|
||||
Spec { x: f(self.x), y: f(self.y) }
|
||||
}
|
||||
|
||||
/// Convert from `&Spec<T>` to `Spec<&T>`.
|
||||
pub fn as_ref(&self) -> Spec<&T> {
|
||||
Spec { x: &self.x, y: &self.y }
|
||||
}
|
||||
|
||||
/// Convert from `&mut Spec<T>` to `Spec<&mut T>`.
|
||||
pub fn as_mut(&mut self) -> Spec<&mut T> {
|
||||
Spec { x: &mut self.x, y: &mut self.y }
|
||||
}
|
||||
|
||||
/// Zip two instances into an instance over a tuple.
|
||||
pub fn zip<U>(self, other: impl Into<Spec<U>>) -> Spec<(T, U)> {
|
||||
let other = other.into();
|
||||
pub fn zip<U>(self, other: Spec<U>) -> Spec<(T, U)> {
|
||||
Spec {
|
||||
x: (self.x, other.x),
|
||||
y: (self.y, other.y),
|
||||
@ -56,6 +68,14 @@ impl<T> Spec<T> {
|
||||
f(&self.x) && f(&self.y)
|
||||
}
|
||||
|
||||
/// Filter the individual fields with a mask.
|
||||
pub fn filter(self, mask: Spec<bool>) -> Spec<Option<T>> {
|
||||
Spec {
|
||||
x: if mask.x { Some(self.x) } else { None },
|
||||
y: if mask.y { Some(self.y) } else { None },
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert to the generic representation.
|
||||
pub fn to_gen(self, main: SpecAxis) -> Gen<T> {
|
||||
match main {
|
||||
@ -65,39 +85,6 @@ impl<T> Spec<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size> for Spec<Length> {
|
||||
fn from(size: Size) -> Self {
|
||||
size.to_spec()
|
||||
}
|
||||
}
|
||||
|
||||
impl Spec<Length> {
|
||||
/// The zero value.
|
||||
pub fn zero() -> Self {
|
||||
Self { x: Length::zero(), y: Length::zero() }
|
||||
}
|
||||
|
||||
/// Convert to a point.
|
||||
pub fn to_point(self) -> Point {
|
||||
Point::new(self.x, self.y)
|
||||
}
|
||||
|
||||
/// Convert to a size.
|
||||
pub fn to_size(self) -> Size {
|
||||
Size::new(self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Spec<Option<T>> {
|
||||
/// Unwrap the individual fields.
|
||||
pub fn unwrap_or(self, other: Spec<T>) -> Spec<T> {
|
||||
Spec {
|
||||
x: self.x.unwrap_or(other.x),
|
||||
y: self.y.unwrap_or(other.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Get<SpecAxis> for Spec<T> {
|
||||
type Component = T;
|
||||
|
||||
@ -116,9 +103,20 @@ impl<T> Get<SpecAxis> for Spec<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for Spec<T> {
|
||||
impl<T> Debug for Spec<T>
|
||||
where
|
||||
T: Debug + 'static,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "Spec({:?}, {:?})", self.x, self.y)
|
||||
if let Spec { x: Some(x), y: Some(y) } =
|
||||
self.as_ref().map(|v| (v as &dyn Any).downcast_ref::<Align>())
|
||||
{
|
||||
write!(f, "{:?}-{:?}", x, y)
|
||||
} else if (&self.x as &dyn Any).is::<Length>() {
|
||||
write!(f, "Size({:?}, {:?})", self.x, self.y)
|
||||
} else {
|
||||
write!(f, "Spec({:?}, {:?})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,3 +157,148 @@ impl Debug for SpecAxis {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A size in 2D.
|
||||
pub type Size = Spec<Length>;
|
||||
|
||||
impl Size {
|
||||
/// The zero value.
|
||||
pub fn zero() -> Self {
|
||||
Self { x: Length::zero(), y: Length::zero() }
|
||||
}
|
||||
|
||||
/// Whether the other size fits into this one (smaller width and height).
|
||||
pub fn fits(self, other: Self) -> bool {
|
||||
self.x.fits(other.x) && self.y.fits(other.y)
|
||||
}
|
||||
|
||||
/// Whether both components are finite.
|
||||
pub fn is_finite(self) -> bool {
|
||||
self.x.is_finite() && self.y.is_finite()
|
||||
}
|
||||
|
||||
/// Whether any of the two components is infinite.
|
||||
pub fn is_infinite(self) -> bool {
|
||||
self.x.is_infinite() || self.y.is_infinite()
|
||||
}
|
||||
|
||||
/// Convert to a point.
|
||||
pub fn to_point(self) -> Point {
|
||||
Point::new(self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self { x: -self.x, y: -self.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self { x: self.x + other.x, y: self.y + other.y }
|
||||
}
|
||||
}
|
||||
|
||||
sub_impl!(Size - Size -> Size);
|
||||
|
||||
impl Mul<f64> for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
Self { x: self.x * other, y: self.y * other }
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Size> for f64 {
|
||||
type Output = Size;
|
||||
|
||||
fn mul(self, other: Size) -> Size {
|
||||
other * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
Self { x: self.x / other, y: self.y / other }
|
||||
}
|
||||
}
|
||||
|
||||
assign_impl!(Size -= Size);
|
||||
assign_impl!(Size += Size);
|
||||
assign_impl!(Size *= f64);
|
||||
assign_impl!(Size /= f64);
|
||||
|
||||
impl<T> Spec<Option<T>> {
|
||||
/// Whether the individual fields are some.
|
||||
pub fn map_is_some(&self) -> Spec<bool> {
|
||||
self.as_ref().map(Option::is_some)
|
||||
}
|
||||
|
||||
/// Whether the individual fields are none.
|
||||
pub fn map_is_none(&self) -> Spec<bool> {
|
||||
self.as_ref().map(Option::is_none)
|
||||
}
|
||||
|
||||
/// Unwrap the individual fields.
|
||||
pub fn unwrap_or(self, other: Spec<T>) -> Spec<T> {
|
||||
Spec {
|
||||
x: self.x.unwrap_or(other.x),
|
||||
y: self.y.unwrap_or(other.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Spec<bool> {
|
||||
/// Select `t.x` if `self.x` is true and `f.x` otherwise and same for `y`.
|
||||
pub fn select<T>(self, t: Spec<T>, f: Spec<T>) -> Spec<T> {
|
||||
Spec {
|
||||
x: if self.x { t.x } else { f.x },
|
||||
y: if self.y { t.y } else { f.y },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Not for Spec<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn not(self) -> Self::Output {
|
||||
Self { x: !self.x, y: !self.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for Spec<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
Self { x: self.x | rhs.x, y: self.y | rhs.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd for Spec<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: Self) -> Self::Output {
|
||||
Self { x: self.x & rhs.x, y: self.y & rhs.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOrAssign for Spec<bool> {
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
self.x |= rhs.x;
|
||||
self.y |= rhs.y;
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAndAssign for Spec<bool> {
|
||||
fn bitand_assign(&mut self, rhs: Self) {
|
||||
self.x &= rhs.x;
|
||||
self.y &= rhs.y;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use std::rc::Rc;
|
||||
|
||||
use super::Regions;
|
||||
use crate::frame::Frame;
|
||||
use crate::geom::{Length, Linear, Size, Spec};
|
||||
use crate::geom::{Length, Size, Spec};
|
||||
|
||||
/// Constrain a frame with constraints.
|
||||
pub trait Constrain {
|
||||
@ -65,8 +65,8 @@ impl Constraints {
|
||||
Self {
|
||||
min: Spec::default(),
|
||||
max: Spec::default(),
|
||||
exact: regions.current.to_spec().map(Some),
|
||||
base: regions.base.to_spec().map(Some),
|
||||
exact: regions.current.map(Some),
|
||||
base: regions.base.map(Some),
|
||||
expand: regions.expand,
|
||||
}
|
||||
}
|
||||
@ -80,18 +80,6 @@ impl Constraints {
|
||||
&& verify(self.exact, current, Length::approx_eq)
|
||||
&& verify(self.base, base, Length::approx_eq)
|
||||
}
|
||||
|
||||
/// Set the appropriate base constraints for linear width and height sizing.
|
||||
pub fn set_base_if_linear(&mut self, base: Size, sizing: Spec<Option<Linear>>) {
|
||||
// The full sizes need to be equal if there is a relative component in
|
||||
// the sizes.
|
||||
if sizing.x.map_or(false, |l| l.is_relative()) {
|
||||
self.base.x = Some(base.w);
|
||||
}
|
||||
if sizing.y.map_or(false, |l| l.is_relative()) {
|
||||
self.base.y = Some(base.h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify a single constraint.
|
||||
|
@ -51,7 +51,7 @@ impl Regions {
|
||||
|
||||
/// Whether the current region is full and a region break is called for.
|
||||
pub fn is_full(&self) -> bool {
|
||||
Length::zero().fits(self.current.h) && !self.in_last()
|
||||
Length::zero().fits(self.current.y) && !self.in_last()
|
||||
}
|
||||
|
||||
/// Whether `current` is the last usable region.
|
||||
|
@ -29,10 +29,9 @@ impl Layout for AlignNode {
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
) -> Vec<Constrained<Rc<Frame>>> {
|
||||
// Along axes with specified alignment, the child doesn't need to expand.
|
||||
// The child only needs to expand along an axis if there's no alignment.
|
||||
let mut pod = regions.clone();
|
||||
pod.expand.x &= self.aligns.x.is_none();
|
||||
pod.expand.y &= self.aligns.y.is_none();
|
||||
pod.expand &= self.aligns.map_is_none();
|
||||
|
||||
// Layout the child.
|
||||
let mut frames = self.child.layout(ctx, &pod);
|
||||
@ -40,23 +39,17 @@ impl Layout for AlignNode {
|
||||
for (Constrained { item: frame, cts }, (current, base)) in
|
||||
frames.iter_mut().zip(regions.iter())
|
||||
{
|
||||
// The possibly larger size in which we align the frame.
|
||||
let new = Size::new(
|
||||
if regions.expand.x { current.w } else { frame.size.w },
|
||||
if regions.expand.y { current.h } else { frame.size.h },
|
||||
);
|
||||
|
||||
let aligns = self.aligns.unwrap_or(Spec::new(Align::Left, Align::Top));
|
||||
Rc::make_mut(frame).resize(new, aligns);
|
||||
// Align in the target size. The target size depends on whether we
|
||||
// should expand.
|
||||
let target = regions.expand.select(current, frame.size);
|
||||
let default = Spec::new(Align::Left, Align::Top);
|
||||
let aligns = self.aligns.unwrap_or(default);
|
||||
Rc::make_mut(frame).resize(target, aligns);
|
||||
|
||||
// Set constraints.
|
||||
cts.expand = regions.expand;
|
||||
cts.base.x.and_set(Some(base.w));
|
||||
cts.base.y.and_set(Some(base.h));
|
||||
cts.exact = Spec::new(
|
||||
regions.expand.x.then(|| current.w),
|
||||
regions.expand.y.then(|| current.h),
|
||||
);
|
||||
cts.base = base.filter(cts.base.map_is_some());
|
||||
cts.exact = current.filter(regions.expand);
|
||||
}
|
||||
|
||||
frames
|
||||
|
@ -157,10 +157,10 @@ impl<'a> FlowLayouter<'a> {
|
||||
/// Layout absolute spacing.
|
||||
fn layout_absolute(&mut self, amount: Linear) {
|
||||
// Resolve the linear, limiting it to the remaining available space.
|
||||
let resolved = amount.resolve(self.full.h);
|
||||
let limited = resolved.min(self.regions.current.h);
|
||||
self.regions.current.h -= limited;
|
||||
self.used.h += limited;
|
||||
let resolved = amount.resolve(self.full.y);
|
||||
let limited = resolved.min(self.regions.current.y);
|
||||
self.regions.current.y -= limited;
|
||||
self.used.y += limited;
|
||||
self.items.push(FlowItem::Absolute(resolved));
|
||||
}
|
||||
|
||||
@ -195,9 +195,9 @@ impl<'a> FlowLayouter<'a> {
|
||||
for (i, frame) in frames.into_iter().enumerate() {
|
||||
// Grow our size, shrink the region and save the frame for later.
|
||||
let size = frame.item.size;
|
||||
self.used.h += size.h;
|
||||
self.used.w.set_max(size.w);
|
||||
self.regions.current.h -= size.h;
|
||||
self.used.y += size.y;
|
||||
self.used.x.set_max(size.x);
|
||||
self.regions.current.y -= size.y;
|
||||
self.items.push(FlowItem::Frame(frame.item, aligns));
|
||||
|
||||
if i + 1 < len {
|
||||
@ -210,16 +210,13 @@ impl<'a> FlowLayouter<'a> {
|
||||
fn finish_region(&mut self) {
|
||||
// Determine the size of the flow in this region dependening on whether
|
||||
// the region expands.
|
||||
let mut size = Size::new(
|
||||
if self.expand.x { self.full.w } else { self.used.w },
|
||||
if self.expand.y { self.full.h } else { self.used.h },
|
||||
);
|
||||
let mut size = self.expand.select(self.full, self.used);
|
||||
|
||||
// Account for fractional spacing in the size calculation.
|
||||
let remaining = self.full.h - self.used.h;
|
||||
if self.fr.get() > 0.0 && self.full.h.is_finite() {
|
||||
self.used.h = self.full.h;
|
||||
size.h = self.full.h;
|
||||
let remaining = self.full.y - self.used.y;
|
||||
if self.fr.get() > 0.0 && self.full.y.is_finite() {
|
||||
self.used.y = self.full.y;
|
||||
size.y = self.full.y;
|
||||
}
|
||||
|
||||
let mut output = Frame::new(size);
|
||||
@ -243,10 +240,10 @@ impl<'a> FlowLayouter<'a> {
|
||||
ruler = ruler.max(aligns.y);
|
||||
|
||||
// Align horizontally and vertically.
|
||||
let x = aligns.x.resolve(size.w - frame.size.w);
|
||||
let y = before + ruler.resolve(size.h - self.used.h);
|
||||
let x = aligns.x.resolve(size.x - frame.size.x);
|
||||
let y = before + ruler.resolve(size.y - self.used.y);
|
||||
let pos = Point::new(x, y);
|
||||
before += frame.size.h;
|
||||
before += frame.size.y;
|
||||
|
||||
// The baseline of the flow is that of the first frame.
|
||||
if first {
|
||||
@ -261,8 +258,8 @@ impl<'a> FlowLayouter<'a> {
|
||||
|
||||
// Generate tight constraints for now.
|
||||
let mut cts = Constraints::new(self.expand);
|
||||
cts.exact = self.full.to_spec().map(Some);
|
||||
cts.base = self.regions.base.to_spec().map(Some);
|
||||
cts.exact = self.full.map(Some);
|
||||
cts.base = self.regions.base.map(Some);
|
||||
|
||||
// Advance to the next region.
|
||||
self.regions.next();
|
||||
|
@ -178,7 +178,7 @@ impl<'a> GridLayouter<'a> {
|
||||
rcols: vec![Length::zero(); cols.len()],
|
||||
cols,
|
||||
rows,
|
||||
full: regions.current.h,
|
||||
full: regions.current.y,
|
||||
regions,
|
||||
used: Size::zero(),
|
||||
fr: Fractional::zero(),
|
||||
@ -220,7 +220,7 @@ impl<'a> GridLayouter<'a> {
|
||||
case = Case::Fitting;
|
||||
}
|
||||
TrackSizing::Linear(v) => {
|
||||
let resolved = v.resolve(self.regions.base.w);
|
||||
let resolved = v.resolve(self.regions.base.x);
|
||||
*rcol = resolved;
|
||||
linear += resolved;
|
||||
}
|
||||
@ -232,7 +232,7 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
// Size that is not used by fixed-size columns.
|
||||
let available = self.regions.current.w - linear;
|
||||
let available = self.regions.current.x - linear;
|
||||
if available >= Length::zero() {
|
||||
// Determine size of auto columns.
|
||||
let (auto, count) = self.measure_auto_columns(ctx, available);
|
||||
@ -254,18 +254,18 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
// Children could depend on base.
|
||||
self.cts.base = self.regions.base.to_spec().map(Some);
|
||||
self.cts.base = self.regions.base.map(Some);
|
||||
|
||||
// Set constraints depending on the case we hit.
|
||||
match case {
|
||||
Case::PurelyLinear => {}
|
||||
Case::Fitting => self.cts.min.x = Some(self.used.w),
|
||||
Case::Exact => self.cts.exact.x = Some(self.regions.current.w),
|
||||
Case::Fitting => self.cts.min.x = Some(self.used.x),
|
||||
Case::Exact => self.cts.exact.x = Some(self.regions.current.x),
|
||||
Case::Overflowing => self.cts.max.x = Some(linear),
|
||||
}
|
||||
|
||||
// Sum up the resolved column sizes once here.
|
||||
self.used.w = self.rcols.iter().sum();
|
||||
self.used.x = self.rcols.iter().sum();
|
||||
}
|
||||
|
||||
/// Measure the size that is available to auto columns.
|
||||
@ -287,7 +287,7 @@ impl<'a> GridLayouter<'a> {
|
||||
let mut resolved = Length::zero();
|
||||
for y in 0 .. self.rows.len() {
|
||||
if let Some(node) = self.cell(x, y) {
|
||||
let size = Size::new(available, self.regions.base.h);
|
||||
let size = Size::new(available, self.regions.base.y);
|
||||
let mut pod =
|
||||
Regions::one(size, self.regions.base, Spec::splat(false));
|
||||
|
||||
@ -295,11 +295,11 @@ impl<'a> GridLayouter<'a> {
|
||||
// base, for auto it's already correct and for fr we could
|
||||
// only guess anyway.
|
||||
if let TrackSizing::Linear(v) = self.rows[y] {
|
||||
pod.base.h = v.resolve(self.regions.base.h);
|
||||
pod.base.y = v.resolve(self.regions.base.y);
|
||||
}
|
||||
|
||||
let frame = node.layout(ctx, &pod).remove(0).item;
|
||||
resolved.set_max(frame.size.w);
|
||||
resolved.set_max(frame.size.x);
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,17 +382,15 @@ impl<'a> GridLayouter<'a> {
|
||||
// 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) {
|
||||
// All widths should be `rcol` except the base for auto columns.
|
||||
let mut pod = self.regions.clone();
|
||||
pod.mutate(|size| size.w = rcol);
|
||||
|
||||
// Set the horizontal base back to the parent region's base for
|
||||
// auto columns.
|
||||
pod.mutate(|size| size.x = rcol);
|
||||
if self.cols[x] == TrackSizing::Auto {
|
||||
pod.base.w = self.regions.base.w;
|
||||
pod.base.x = self.regions.base.x;
|
||||
}
|
||||
|
||||
let mut sizes =
|
||||
node.layout(ctx, &pod).into_iter().map(|frame| frame.item.size.h);
|
||||
node.layout(ctx, &pod).into_iter().map(|frame| frame.item.size.y);
|
||||
|
||||
// For each region, we want to know the maximum height any
|
||||
// column requires.
|
||||
@ -425,7 +423,7 @@ impl<'a> GridLayouter<'a> {
|
||||
for (target, (current, _)) in
|
||||
resolved[.. len - 1].iter_mut().zip(self.regions.iter())
|
||||
{
|
||||
target.set_max(current.h);
|
||||
target.set_max(current.y);
|
||||
}
|
||||
}
|
||||
|
||||
@ -444,13 +442,13 @@ impl<'a> GridLayouter<'a> {
|
||||
/// Layout a row with linear height. Such a row cannot break across multiple
|
||||
/// regions, but it may force a region break.
|
||||
fn layout_linear_row(&mut self, ctx: &mut LayoutContext, v: Linear, y: usize) {
|
||||
let resolved = v.resolve(self.regions.base.h);
|
||||
let resolved = v.resolve(self.regions.base.y);
|
||||
let frame = self.layout_single_row(ctx, resolved, y);
|
||||
|
||||
// Skip to fitting region.
|
||||
let height = frame.size.h;
|
||||
while !self.regions.current.h.fits(height) && !self.regions.in_last() {
|
||||
self.cts.max.y = Some(self.used.h + height);
|
||||
let height = frame.size.y;
|
||||
while !self.regions.current.y.fits(height) && !self.regions.in_last() {
|
||||
self.cts.max.y = Some(self.used.y + height);
|
||||
self.finish_region(ctx);
|
||||
|
||||
// Don't skip multiple regions for gutter and don't push a row.
|
||||
@ -469,21 +467,18 @@ impl<'a> GridLayouter<'a> {
|
||||
height: Length,
|
||||
y: usize,
|
||||
) -> Frame {
|
||||
let mut output = Frame::new(Size::new(self.used.w, height));
|
||||
let mut output = Frame::new(Size::new(self.used.x, height));
|
||||
let mut pos = Point::zero();
|
||||
|
||||
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||
if let Some(node) = self.cell(x, y) {
|
||||
let size = Size::new(rcol, height);
|
||||
|
||||
// Set the base to the size for non-auto rows.
|
||||
let mut base = self.regions.base;
|
||||
if self.cols[x] != TrackSizing::Auto {
|
||||
base.w = size.w;
|
||||
}
|
||||
if self.rows[y] != TrackSizing::Auto {
|
||||
base.h = size.h;
|
||||
}
|
||||
// Set the base to the region's base for auto rows and to the
|
||||
// size for linear and fractional rows.
|
||||
let base = Spec::new(self.cols[x], self.rows[y])
|
||||
.map(|s| s == TrackSizing::Auto)
|
||||
.select(self.regions.base, size);
|
||||
|
||||
let pod = Regions::one(size, base, Spec::splat(true));
|
||||
let frame = node.layout(ctx, &pod).remove(0);
|
||||
@ -506,15 +501,15 @@ impl<'a> GridLayouter<'a> {
|
||||
// Prepare frames.
|
||||
let mut outputs: Vec<_> = heights
|
||||
.iter()
|
||||
.map(|&h| Frame::new(Size::new(self.used.w, h)))
|
||||
.map(|&h| Frame::new(Size::new(self.used.x, h)))
|
||||
.collect();
|
||||
|
||||
// Prepare regions.
|
||||
let size = Size::new(self.used.w, heights[0]);
|
||||
let size = Size::new(self.used.x, heights[0]);
|
||||
let mut pod = Regions::one(size, self.regions.base, Spec::splat(true));
|
||||
pod.backlog = heights[1 ..]
|
||||
.iter()
|
||||
.map(|&h| Size::new(self.used.w, h))
|
||||
.map(|&h| Size::new(self.used.x, h))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter();
|
||||
|
||||
@ -522,12 +517,10 @@ impl<'a> GridLayouter<'a> {
|
||||
let mut pos = Point::zero();
|
||||
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||
if let Some(node) = self.cell(x, y) {
|
||||
pod.mutate(|size| size.w = rcol);
|
||||
|
||||
// Set the horizontal base back to the parent region's base for
|
||||
// auto columns.
|
||||
// All widths should be `rcol` except the base for auto columns.
|
||||
pod.mutate(|size| size.x = rcol);
|
||||
if self.cols[x] == TrackSizing::Auto {
|
||||
pod.base.w = self.regions.base.w;
|
||||
pod.base.x = self.regions.base.x;
|
||||
}
|
||||
|
||||
// Push the layouted frames into the individual output frames.
|
||||
@ -545,8 +538,8 @@ impl<'a> GridLayouter<'a> {
|
||||
|
||||
/// Push a row frame into the current region.
|
||||
fn push_row(&mut self, frame: Frame) {
|
||||
self.regions.current.h -= frame.size.h;
|
||||
self.used.h += frame.size.h;
|
||||
self.regions.current.y -= frame.size.y;
|
||||
self.used.y += frame.size.y;
|
||||
self.lrows.push(Row::Frame(frame));
|
||||
}
|
||||
|
||||
@ -556,10 +549,10 @@ impl<'a> GridLayouter<'a> {
|
||||
// there are fr rows.
|
||||
let mut size = self.used;
|
||||
if self.fr.get() > 0.0 && self.full.is_finite() {
|
||||
size.h = self.full;
|
||||
size.y = self.full;
|
||||
self.cts.exact.y = Some(self.full);
|
||||
} else {
|
||||
self.cts.min.y = Some(size.h);
|
||||
self.cts.min.y = Some(size.y);
|
||||
}
|
||||
|
||||
// The frame for the region.
|
||||
@ -571,20 +564,20 @@ impl<'a> GridLayouter<'a> {
|
||||
let frame = match row {
|
||||
Row::Frame(frame) => frame,
|
||||
Row::Fr(v, y) => {
|
||||
let remaining = self.full - self.used.h;
|
||||
let remaining = self.full - self.used.y;
|
||||
let height = v.resolve(self.fr, remaining);
|
||||
self.layout_single_row(ctx, height, y)
|
||||
}
|
||||
};
|
||||
|
||||
let height = frame.size.h;
|
||||
let height = frame.size.y;
|
||||
output.merge_frame(pos, frame);
|
||||
pos.y += height;
|
||||
}
|
||||
|
||||
self.regions.next();
|
||||
self.full = self.regions.current.h;
|
||||
self.used.h = Length::zero();
|
||||
self.full = self.regions.current.y;
|
||||
self.used.y = Length::zero();
|
||||
self.fr = Fractional::zero();
|
||||
self.finished.push(output.constrain(self.cts));
|
||||
self.cts = Constraints::new(self.expand);
|
||||
|
@ -47,16 +47,16 @@ impl Layout for ImageNode {
|
||||
let pxh = img.height() as f64;
|
||||
|
||||
let pixel_ratio = pxw / pxh;
|
||||
let current_ratio = current.w / current.h;
|
||||
let current_ratio = current.x / current.y;
|
||||
let wide = pixel_ratio > current_ratio;
|
||||
|
||||
// The space into which the image will be placed according to its fit.
|
||||
let canvas = if expand.x && expand.y {
|
||||
current
|
||||
} else if expand.x || (wide && current.w.is_finite()) {
|
||||
Size::new(current.w, current.h.min(current.w.safe_div(pixel_ratio)))
|
||||
} else if current.h.is_finite() {
|
||||
Size::new(current.w.min(current.h * pixel_ratio), current.h)
|
||||
} else if expand.x || (wide && current.x.is_finite()) {
|
||||
Size::new(current.x, current.y.min(current.x.safe_div(pixel_ratio)))
|
||||
} else if current.y.is_finite() {
|
||||
Size::new(current.x.min(current.y * pixel_ratio), current.y)
|
||||
} else {
|
||||
Size::new(Length::pt(pxw), Length::pt(pxh))
|
||||
};
|
||||
@ -65,9 +65,9 @@ impl Layout for ImageNode {
|
||||
let size = match self.fit {
|
||||
ImageFit::Contain | ImageFit::Cover => {
|
||||
if wide == (self.fit == ImageFit::Contain) {
|
||||
Size::new(canvas.w, canvas.w / pixel_ratio)
|
||||
Size::new(canvas.x, canvas.x / pixel_ratio)
|
||||
} else {
|
||||
Size::new(canvas.h * pixel_ratio, canvas.h)
|
||||
Size::new(canvas.y * pixel_ratio, canvas.y)
|
||||
}
|
||||
}
|
||||
ImageFit::Stretch => canvas,
|
||||
|
@ -143,15 +143,6 @@ dynamic! {
|
||||
Align: "alignment",
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
Spec<Option<Align>>: "2d alignment",
|
||||
@align: Align => {
|
||||
let mut aligns = Spec::default();
|
||||
aligns.set(align.axis(), Some(*align));
|
||||
aligns
|
||||
},
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
FontFamily: "font family",
|
||||
Value::Str(string) => Self::Named(string.to_lowercase()),
|
||||
@ -162,3 +153,15 @@ castable! {
|
||||
Expected: "color",
|
||||
Value::Color(color) => Paint::Solid(color),
|
||||
}
|
||||
|
||||
castable! {
|
||||
Spec<Option<Align>>,
|
||||
Expected: "1d or 2d alignment",
|
||||
@align: Align => {
|
||||
let mut aligns = Spec::default();
|
||||
aligns.set(align.axis(), Some(*align));
|
||||
aligns
|
||||
},
|
||||
@aligns: Spec<Align> => aligns.map(Some),
|
||||
|
||||
}
|
||||
|
@ -54,29 +54,17 @@ impl Layout for PadNode {
|
||||
frame.baseline += offset.y;
|
||||
frame.translate(offset);
|
||||
|
||||
// Set exact and base constraints if the child had them.
|
||||
cts.exact.x.and_set(Some(current.w));
|
||||
cts.exact.y.and_set(Some(current.h));
|
||||
cts.base.x.and_set(Some(base.w));
|
||||
cts.base.y.and_set(Some(base.h));
|
||||
|
||||
// Also set base constraints if the padding is relative.
|
||||
if self.padding.left.is_relative() || self.padding.right.is_relative() {
|
||||
cts.base.x = Some(base.w);
|
||||
}
|
||||
|
||||
if self.padding.top.is_relative() || self.padding.bottom.is_relative() {
|
||||
cts.base.y = Some(base.h);
|
||||
}
|
||||
// Set exact and base constraints if the child had them. Also set
|
||||
// base if our padding is relative.
|
||||
let is_rel = self.padding.sum_by_axis().map(Linear::is_relative);
|
||||
cts.exact = current.filter(cts.exact.map_is_some());
|
||||
cts.base = base.filter(is_rel | cts.base.map_is_some());
|
||||
|
||||
// Inflate min and max contraints by the padding.
|
||||
for spec in [&mut cts.min, &mut cts.max] {
|
||||
if let Some(x) = spec.x.as_mut() {
|
||||
*x += padding.size().w;
|
||||
}
|
||||
if let Some(y) = spec.y.as_mut() {
|
||||
*y += padding.size().h;
|
||||
}
|
||||
spec.as_mut()
|
||||
.zip(padding.sum_by_axis())
|
||||
.map(|(s, p)| s.as_mut().map(|v| *v += p));
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +74,7 @@ impl Layout for PadNode {
|
||||
|
||||
/// Shrink a size by padding relative to the size itself.
|
||||
fn shrink(size: Size, padding: Sides<Linear>) -> Size {
|
||||
size - padding.resolve(size).size()
|
||||
size - padding.resolve(size).sum_by_axis()
|
||||
}
|
||||
|
||||
/// Grow a size by padding relative to the grown size.
|
||||
@ -109,12 +97,6 @@ fn shrink(size: Size, padding: Sides<Linear>) -> Size {
|
||||
/// <=> (1 - p.rel) * w = s + p.abs
|
||||
/// <=> w = (s + p.abs) / (1 - p.rel)
|
||||
fn grow(size: Size, padding: Sides<Linear>) -> Size {
|
||||
fn solve_axis(length: Length, padding: Linear) -> Length {
|
||||
(length + padding.abs).safe_div(1.0 - padding.rel.get())
|
||||
}
|
||||
|
||||
Size::new(
|
||||
solve_axis(size.w, padding.left + padding.right),
|
||||
solve_axis(size.h, padding.top + padding.bottom),
|
||||
)
|
||||
size.zip(padding.sum_by_axis())
|
||||
.map(|(s, p)| (s + p.abs).safe_div(1.0 - p.rel.get()))
|
||||
}
|
||||
|
@ -30,16 +30,16 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
|
||||
if let Some(width) = width {
|
||||
page.class = PaperClass::Custom;
|
||||
page.size.w = width;
|
||||
page.size.x = width;
|
||||
}
|
||||
|
||||
if flip.unwrap_or(false) {
|
||||
std::mem::swap(&mut page.size.w, &mut page.size.h);
|
||||
std::mem::swap(&mut page.size.x, &mut page.size.y);
|
||||
}
|
||||
|
||||
if let Some(height) = height {
|
||||
page.class = PaperClass::Custom;
|
||||
page.size.h = height;
|
||||
page.size.y = height;
|
||||
}
|
||||
|
||||
if let Some(margins) = margins {
|
||||
@ -95,7 +95,7 @@ impl PageNode {
|
||||
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
|
||||
// When one of the lengths is infinite the page fits its content along
|
||||
// that axis.
|
||||
let expand = self.size.to_spec().map(Length::is_finite);
|
||||
let expand = self.size.map(Length::is_finite);
|
||||
let regions = Regions::repeat(self.size, self.size, expand);
|
||||
|
||||
// Layout the child.
|
||||
|
@ -208,7 +208,7 @@ impl<'a> ParLayouter<'a> {
|
||||
for (range, child) in par.ranges().zip(&par.children) {
|
||||
match *child {
|
||||
ParChild::Spacing(Spacing::Linear(v)) => {
|
||||
let resolved = v.resolve(regions.current.w);
|
||||
let resolved = v.resolve(regions.current.x);
|
||||
items.push(ParItem::Absolute(resolved));
|
||||
ranges.push(range);
|
||||
}
|
||||
@ -230,7 +230,7 @@ impl<'a> ParLayouter<'a> {
|
||||
}
|
||||
}
|
||||
ParChild::Node(ref node) => {
|
||||
let size = Size::new(regions.current.w, regions.base.h);
|
||||
let size = Size::new(regions.current.x, regions.base.y);
|
||||
let expand = Spec::splat(false);
|
||||
let pod = Regions::one(size, regions.base, expand);
|
||||
let frame = node.layout(ctx, &pod).remove(0);
|
||||
@ -292,26 +292,26 @@ impl<'a> ParLayouter<'a> {
|
||||
// fit the line will yield the same line break. Therefore,
|
||||
// the width of the region must not fit the width of the
|
||||
// tried line.
|
||||
if !stack.regions.current.w.fits(line.size.w) {
|
||||
stack.cts.max.x.set_min(line.size.w);
|
||||
if !stack.regions.current.x.fits(line.size.x) {
|
||||
stack.cts.max.x.set_min(line.size.x);
|
||||
}
|
||||
|
||||
// Same as above, but for height.
|
||||
if !stack.regions.current.h.fits(line.size.h) {
|
||||
let too_large = stack.size.h + self.leading + line.size.h;
|
||||
if !stack.regions.current.y.fits(line.size.y) {
|
||||
let too_large = stack.size.y + self.leading + line.size.y;
|
||||
stack.cts.max.y.set_min(too_large);
|
||||
}
|
||||
|
||||
stack.push(last_line);
|
||||
|
||||
stack.cts.min.y = Some(stack.size.h);
|
||||
stack.cts.min.y = Some(stack.size.y);
|
||||
start = last_end;
|
||||
line = LineLayout::new(ctx, &self, start .. end);
|
||||
}
|
||||
}
|
||||
|
||||
// If the line does not fit vertically, we start a new region.
|
||||
while !stack.regions.current.h.fits(line.size.h) {
|
||||
while !stack.regions.current.y.fits(line.size.y) {
|
||||
if stack.regions.in_last() {
|
||||
stack.overflowing = true;
|
||||
break;
|
||||
@ -320,7 +320,7 @@ impl<'a> ParLayouter<'a> {
|
||||
// Again, the line must not fit. It would if the space taken up
|
||||
// plus the line height would fit, therefore the constraint
|
||||
// below.
|
||||
let too_large = stack.size.h + self.leading + line.size.h;
|
||||
let too_large = stack.size.y + self.leading + line.size.y;
|
||||
stack.cts.max.y.set_min(too_large);
|
||||
|
||||
stack.finish_region(ctx);
|
||||
@ -329,7 +329,7 @@ impl<'a> ParLayouter<'a> {
|
||||
// If the line does not fit horizontally or we have a mandatory
|
||||
// line break (i.e. due to "\n"), we push the line into the
|
||||
// stack.
|
||||
if mandatory || !stack.regions.current.w.fits(line.size.w) {
|
||||
if mandatory || !stack.regions.current.x.fits(line.size.x) {
|
||||
start = end;
|
||||
last = None;
|
||||
|
||||
@ -339,23 +339,23 @@ impl<'a> ParLayouter<'a> {
|
||||
// paragraph, we want to force an empty line.
|
||||
if mandatory && end == self.bidi.text.len() {
|
||||
let line = LineLayout::new(ctx, &self, end .. end);
|
||||
if stack.regions.current.h.fits(line.size.h) {
|
||||
if stack.regions.current.y.fits(line.size.y) {
|
||||
stack.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
stack.cts.min.y = Some(stack.size.h);
|
||||
stack.cts.min.y = Some(stack.size.y);
|
||||
} else {
|
||||
// Otherwise, the line fits both horizontally and vertically
|
||||
// and we remember it.
|
||||
stack.cts.min.x.set_max(line.size.w);
|
||||
stack.cts.min.x.set_max(line.size.x);
|
||||
last = Some((line, end));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((line, _)) = last {
|
||||
stack.push(line);
|
||||
stack.cts.min.y = Some(stack.size.h);
|
||||
stack.cts.min.y = Some(stack.size.y);
|
||||
}
|
||||
|
||||
stack.finish(ctx)
|
||||
@ -467,9 +467,9 @@ impl<'a> LineLayout<'a> {
|
||||
ParItem::Fractional(v) => fr += v,
|
||||
ParItem::Text(ShapedText { size, baseline, .. })
|
||||
| ParItem::Frame(Frame { size, baseline, .. }) => {
|
||||
width += size.w;
|
||||
width += size.x;
|
||||
top.set_max(baseline);
|
||||
bottom.set_max(size.h - baseline);
|
||||
bottom.set_max(size.y - baseline);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -489,8 +489,8 @@ impl<'a> LineLayout<'a> {
|
||||
|
||||
/// Build the line's frame.
|
||||
fn build(&self, ctx: &LayoutContext, width: Length) -> Frame {
|
||||
let size = Size::new(self.size.w.max(width), self.size.h);
|
||||
let remaining = size.w - self.size.w;
|
||||
let size = Size::new(self.size.x.max(width), self.size.y);
|
||||
let remaining = size.x - self.size.x;
|
||||
|
||||
let mut output = Frame::new(size);
|
||||
let mut offset = Length::zero();
|
||||
@ -507,7 +507,7 @@ impl<'a> LineLayout<'a> {
|
||||
|
||||
let x = offset + self.par.align.resolve(remaining);
|
||||
let y = self.baseline - frame.baseline;
|
||||
offset += frame.size.w;
|
||||
offset += frame.size.x;
|
||||
|
||||
// Add to the line's frame.
|
||||
output.merge_frame(Point::new(x, y), frame);
|
||||
@ -602,12 +602,12 @@ impl<'a> LineStack<'a> {
|
||||
|
||||
/// Push a new line into the stack.
|
||||
fn push(&mut self, line: LineLayout<'a>) {
|
||||
self.regions.current.h -= line.size.h + self.leading;
|
||||
self.regions.current.y -= line.size.y + self.leading;
|
||||
|
||||
self.size.w.set_max(line.size.w);
|
||||
self.size.h += line.size.h;
|
||||
self.size.x.set_max(line.size.x);
|
||||
self.size.y += line.size.y;
|
||||
if !self.lines.is_empty() {
|
||||
self.size.h += self.leading;
|
||||
self.size.y += self.leading;
|
||||
}
|
||||
|
||||
self.fractional |= !line.fr.is_zero();
|
||||
@ -617,14 +617,14 @@ impl<'a> LineStack<'a> {
|
||||
/// Finish the frame for one region.
|
||||
fn finish_region(&mut self, ctx: &LayoutContext) {
|
||||
if self.regions.expand.x || self.fractional {
|
||||
self.size.w = self.regions.current.w;
|
||||
self.cts.exact.x = Some(self.regions.current.w);
|
||||
self.size.x = self.regions.current.x;
|
||||
self.cts.exact.x = Some(self.regions.current.x);
|
||||
}
|
||||
|
||||
if self.overflowing {
|
||||
self.cts.min.y = None;
|
||||
self.cts.max.y = None;
|
||||
self.cts.exact = self.full.to_spec().map(Some);
|
||||
self.cts.exact = self.full.map(Some);
|
||||
}
|
||||
|
||||
let mut output = Frame::new(self.size);
|
||||
@ -632,7 +632,7 @@ impl<'a> LineStack<'a> {
|
||||
let mut first = true;
|
||||
|
||||
for line in self.lines.drain(..) {
|
||||
let frame = line.build(ctx, self.size.w);
|
||||
let frame = line.build(ctx, self.size.x);
|
||||
|
||||
let pos = Point::with_y(offset);
|
||||
if first {
|
||||
@ -640,7 +640,7 @@ impl<'a> LineStack<'a> {
|
||||
first = false;
|
||||
}
|
||||
|
||||
offset += frame.size.h + self.leading;
|
||||
offset += frame.size.y + self.leading;
|
||||
output.merge_frame(pos, frame);
|
||||
}
|
||||
|
||||
@ -648,7 +648,7 @@ impl<'a> LineStack<'a> {
|
||||
self.regions.next();
|
||||
self.full = self.regions.current;
|
||||
self.cts = Constraints::new(self.regions.expand);
|
||||
self.cts.base = self.regions.base.to_spec().map(Some);
|
||||
self.cts.base = self.regions.base.map(Some);
|
||||
self.size = Size::zero();
|
||||
}
|
||||
|
||||
|
@ -138,8 +138,8 @@ impl Layout for ShapeNode {
|
||||
// the result is really a square or circle.
|
||||
let size = frames[0].item.size;
|
||||
let mut pod = regions.clone();
|
||||
pod.current.w = size.w.max(size.h).min(pod.current.w);
|
||||
pod.current.h = pod.current.w;
|
||||
pod.current.x = size.x.max(size.y).min(pod.current.x);
|
||||
pod.current.y = pod.current.x;
|
||||
pod.expand = Spec::splat(true);
|
||||
frames = node.layout(ctx, &pod);
|
||||
}
|
||||
@ -153,7 +153,7 @@ impl Layout for ShapeNode {
|
||||
let default = Length::pt(30.0);
|
||||
let mut size = Size::new(
|
||||
if regions.expand.x {
|
||||
regions.current.w
|
||||
regions.current.x
|
||||
} else {
|
||||
// For rectangle and ellipse, the default shape is a bit
|
||||
// wider than high.
|
||||
@ -162,16 +162,16 @@ impl Layout for ShapeNode {
|
||||
ShapeKind::Rect | ShapeKind::Ellipse => 1.5 * default,
|
||||
}
|
||||
},
|
||||
if regions.expand.y { regions.current.h } else { default },
|
||||
if regions.expand.y { regions.current.y } else { default },
|
||||
);
|
||||
|
||||
// Don't overflow the region.
|
||||
size.w = size.w.min(regions.current.w);
|
||||
size.h = size.h.min(regions.current.h);
|
||||
size.x = size.x.min(regions.current.x);
|
||||
size.y = size.y.min(regions.current.y);
|
||||
|
||||
if matches!(self.kind, ShapeKind::Square | ShapeKind::Circle) {
|
||||
size.w = size.w.min(size.h);
|
||||
size.h = size.w;
|
||||
size.x = size.x.min(size.y);
|
||||
size.y = size.x;
|
||||
}
|
||||
|
||||
Frame::new(size)
|
||||
@ -194,11 +194,7 @@ impl Layout for ShapeNode {
|
||||
}
|
||||
|
||||
// Ensure frame size matches regions size if expansion is on.
|
||||
let expand = regions.expand;
|
||||
frame.size = Size::new(
|
||||
if expand.x { regions.current.w } else { frame.size.w },
|
||||
if expand.y { regions.current.h } else { frame.size.h },
|
||||
);
|
||||
frame.size = regions.expand.select(regions.current, frame.size);
|
||||
|
||||
// Return tight constraints for now.
|
||||
vec![frame.constrain(Constraints::tight(regions))]
|
||||
|
@ -35,50 +35,36 @@ impl Layout for SizedNode {
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
) -> Vec<Constrained<Rc<Frame>>> {
|
||||
// Generate constraints.
|
||||
let mut cts = Constraints::new(regions.expand);
|
||||
cts.set_base_if_linear(regions.base, self.sizing);
|
||||
|
||||
// Set tight exact and base constraints if the child is
|
||||
// automatically sized since we don't know what the child might do.
|
||||
if self.sizing.x.is_none() {
|
||||
cts.exact.x = Some(regions.current.w);
|
||||
cts.base.x = Some(regions.base.w);
|
||||
}
|
||||
|
||||
// Same here.
|
||||
if self.sizing.y.is_none() {
|
||||
cts.exact.y = Some(regions.current.h);
|
||||
cts.base.y = Some(regions.base.h);
|
||||
}
|
||||
|
||||
// Resolve width and height relative to the region's base.
|
||||
let width = self.sizing.x.map(|w| w.resolve(regions.base.w));
|
||||
let height = self.sizing.y.map(|h| h.resolve(regions.base.h));
|
||||
let is_auto = self.sizing.map_is_none();
|
||||
let is_rel = self.sizing.map(|s| s.map_or(false, Linear::is_relative));
|
||||
|
||||
// The "pod" is the region into which the child will be layouted.
|
||||
let pod = {
|
||||
let size = Size::new(
|
||||
width.unwrap_or(regions.current.w),
|
||||
height.unwrap_or(regions.current.h),
|
||||
);
|
||||
// Resolve the sizing to a concrete size.
|
||||
let size = self
|
||||
.sizing
|
||||
.zip(regions.base)
|
||||
.map(|(s, b)| s.map(|v| v.resolve(b)))
|
||||
.unwrap_or(regions.current);
|
||||
|
||||
let base = Size::new(
|
||||
if width.is_some() { size.w } else { regions.base.w },
|
||||
if height.is_some() { size.h } else { regions.base.h },
|
||||
);
|
||||
// Select the appropriate base and expansion for the child depending
|
||||
// on whether it is automatically or linearly sized.
|
||||
let base = is_auto.select(regions.base, size);
|
||||
let expand = regions.expand | !is_auto;
|
||||
|
||||
let expand = Spec::new(
|
||||
width.is_some() || regions.expand.x,
|
||||
height.is_some() || regions.expand.y,
|
||||
);
|
||||
|
||||
// TODO: Allow multiple regions if only width is set.
|
||||
Regions::one(size, base, expand)
|
||||
};
|
||||
|
||||
let mut frames = self.child.layout(ctx, &pod);
|
||||
frames[0].cts = cts;
|
||||
|
||||
// Set base & exact constraints if the child is automatically sized
|
||||
// since we don't know what the child might do. Also set base if our
|
||||
// sizing is relative.
|
||||
let frame = &mut frames[0];
|
||||
frame.cts = Constraints::new(regions.expand);
|
||||
frame.cts.exact = regions.current.filter(is_auto);
|
||||
frame.cts.base = regions.base.filter(is_auto | is_rel);
|
||||
|
||||
frames
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +128,6 @@ impl<'a> StackLayouter<'a> {
|
||||
let expand = regions.expand;
|
||||
let full = regions.current;
|
||||
|
||||
|
||||
// Disable expansion along the block axis for children.
|
||||
let mut regions = regions.clone();
|
||||
regions.expand.set(axis, false);
|
||||
@ -210,11 +209,8 @@ impl<'a> StackLayouter<'a> {
|
||||
fn finish_region(&mut self) {
|
||||
// Determine the size of the stack in this region dependening on whether
|
||||
// the region expands.
|
||||
let used = self.used.to_size(self.axis);
|
||||
let mut size = Size::new(
|
||||
if self.expand.x { self.full.w } else { used.w },
|
||||
if self.expand.y { self.full.h } else { used.h },
|
||||
);
|
||||
let used = self.used.to_spec(self.axis);
|
||||
let mut size = self.expand.select(self.full, used);
|
||||
|
||||
// Expand fully if there are fr spacings.
|
||||
let full = self.full.get(self.axis);
|
||||
@ -263,8 +259,8 @@ impl<'a> StackLayouter<'a> {
|
||||
|
||||
// Generate tight constraints for now.
|
||||
let mut cts = Constraints::new(self.expand);
|
||||
cts.exact = self.full.to_spec().map(Some);
|
||||
cts.base = self.regions.base.to_spec().map(Some);
|
||||
cts.exact = self.full.map(Some);
|
||||
cts.base = self.regions.base.map(Some);
|
||||
|
||||
// Advance to the next region.
|
||||
self.regions.next();
|
||||
|
@ -57,8 +57,7 @@ impl Layout for TransformNode {
|
||||
let mut frames = self.child.layout(ctx, regions);
|
||||
|
||||
for Constrained { item: frame, .. } in frames.iter_mut() {
|
||||
let x = self.origin.x.resolve(frame.size.w);
|
||||
let y = self.origin.y.resolve(frame.size.h);
|
||||
let Spec { x, y } = self.origin.zip(frame.size).map(|(o, s)| o.resolve(s));
|
||||
let transform = Transform::translation(x, y)
|
||||
.pre_concat(self.transform)
|
||||
.pre_concat(Transform::translation(-x, -y));
|
||||
|
@ -27,9 +27,6 @@ impl BoolExt for bool {
|
||||
|
||||
/// Additional methods for options.
|
||||
pub trait OptionExt<T> {
|
||||
/// Replace `self` with `other` if `self` is `Some`.
|
||||
fn and_set(&mut self, other: Option<T>);
|
||||
|
||||
/// Sets `other` as the value if `self` is `None` or if it contains a value
|
||||
/// larger than `other`.
|
||||
fn set_min(&mut self, other: T)
|
||||
@ -44,12 +41,6 @@ pub trait OptionExt<T> {
|
||||
}
|
||||
|
||||
impl<T> OptionExt<T> for Option<T> {
|
||||
fn and_set(&mut self, other: Option<T>) {
|
||||
if self.is_some() {
|
||||
*self = other;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_min(&mut self, other: T)
|
||||
where
|
||||
T: Ord,
|
||||
|
@ -387,8 +387,8 @@ fn print_error(source: &SourceFile, line: usize, error: &Error) {
|
||||
|
||||
fn draw(ctx: &Context, frames: &[Rc<Frame>], dpp: f32) -> sk::Pixmap {
|
||||
let pad = Length::pt(5.0);
|
||||
let width = 2.0 * pad + frames.iter().map(|l| l.size.w).max().unwrap_or_default();
|
||||
let height = pad + frames.iter().map(|l| l.size.h + pad).sum::<Length>();
|
||||
let width = 2.0 * pad + frames.iter().map(|l| l.size.x).max().unwrap_or_default();
|
||||
let height = pad + frames.iter().map(|l| l.size.y + pad).sum::<Length>();
|
||||
|
||||
let pxw = (dpp * width.to_f32()) as u32;
|
||||
let pxh = (dpp * height.to_f32()) as u32;
|
||||
@ -414,13 +414,13 @@ fn draw(ctx: &Context, frames: &[Rc<Frame>], dpp: f32) -> sk::Pixmap {
|
||||
let mut background = sk::Paint::default();
|
||||
background.set_color(sk::Color::WHITE);
|
||||
|
||||
let w = frame.size.w.to_f32();
|
||||
let h = frame.size.h.to_f32();
|
||||
let w = frame.size.x.to_f32();
|
||||
let h = frame.size.y.to_f32();
|
||||
let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap();
|
||||
canvas.fill_rect(rect, &background, ts, None);
|
||||
|
||||
draw_frame(&mut canvas, ts, &mask, ctx, frame);
|
||||
ts = ts.pre_translate(0.0, (frame.size.h + pad).to_f32());
|
||||
ts = ts.pre_translate(0.0, (frame.size.y + pad).to_f32());
|
||||
}
|
||||
|
||||
canvas
|
||||
@ -469,8 +469,8 @@ fn draw_group(
|
||||
) {
|
||||
let ts = ts.pre_concat(convert_typst_transform(group.transform));
|
||||
if group.clips {
|
||||
let w = group.frame.size.w.to_f32();
|
||||
let h = group.frame.size.h.to_f32();
|
||||
let w = group.frame.size.x.to_f32();
|
||||
let h = group.frame.size.y.to_f32();
|
||||
let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap();
|
||||
let path = sk::PathBuilder::from_rect(rect).transform(ts).unwrap();
|
||||
let rule = sk::FillRule::default();
|
||||
@ -565,8 +565,8 @@ fn draw_shape(
|
||||
) {
|
||||
let path = match shape.geometry {
|
||||
Geometry::Rect(size) => {
|
||||
let w = size.w.to_f32();
|
||||
let h = size.h.to_f32();
|
||||
let w = size.x.to_f32();
|
||||
let h = size.y.to_f32();
|
||||
let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap();
|
||||
sk::PathBuilder::from_rect(rect)
|
||||
}
|
||||
@ -613,8 +613,8 @@ fn draw_image(
|
||||
*dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply();
|
||||
}
|
||||
|
||||
let view_width = size.w.to_f32();
|
||||
let view_height = size.h.to_f32();
|
||||
let view_width = size.x.to_f32();
|
||||
let view_height = size.y.to_f32();
|
||||
let scale_x = view_width as f32 / pixmap.width() as f32;
|
||||
let scale_y = view_height as f32 / pixmap.height() as f32;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user