Outset; fix folding

This commit is contained in:
Martin Haug 2022-05-01 13:21:07 +02:00
parent 5f1499d380
commit 84a4961a5d
12 changed files with 93 additions and 54 deletions

View File

@ -478,7 +478,7 @@ pub fn rect_paths(
];
for (side, radius) in sides.into_iter().zip(radius.windows(2)) {
let stroke_continuity = strokes.get(side) == strokes.get(side.clockwise());
let stroke_continuity = strokes.get(side) == strokes.get(side.next_cw());
connection = connection.advance(stroke_continuity && side != Side::Left);
always_continuous &= stroke_continuity;

View File

@ -128,7 +128,7 @@ impl Side {
}
/// The next side, clockwise.
pub fn clockwise(self) -> Self {
pub fn next_cw(self) -> Self {
match self {
Self::Left => Self::Top,
Self::Top => Self::Right,
@ -138,7 +138,7 @@ impl Side {
}
/// The next side, counter-clockwise.
pub fn counter_clockwise(self) -> Self {
pub fn next_ccw(self) -> Self {
match self {
Self::Left => Self::Bottom,
Self::Top => Self::Left,

View File

@ -102,6 +102,7 @@ castable! {
}
},
Value::Length(l) => Sides::splat(Some(l.into())),
Value::Ratio(r) => Sides::splat(Some(r.into())),
Value::Relative(r) => Sides::splat(Some(r)),
}
@ -117,40 +118,44 @@ impl<const S: ShapeKind> Layout for AngularNode<S> {
let inset = styles.get(Self::INSET);
// Pad the child.
let child = child
.clone()
.padded(inset.map(|side| side.map(|abs| RawLength::from(abs))));
let child = child.clone().padded(inset.map(|side| side.map(RawLength::from)));
let mut pod = Regions::one(regions.first, regions.base, regions.expand);
frames = child.layout(ctx, &pod, styles)?;
// Relayout with full expansion into square region to make sure
// the result is really a square or circle.
let length = if regions.expand.x || regions.expand.y {
let target = regions.expand.select(regions.first, Size::zero());
target.x.max(target.y)
} else {
let size = frames[0].size;
let desired = size.x.max(size.y);
desired.min(regions.first.x).min(regions.first.y)
};
if is_quadratic(S) {
let length = if regions.expand.x || regions.expand.y {
let target = regions.expand.select(regions.first, Size::zero());
target.x.max(target.y)
} else {
let size = frames[0].size;
let desired = size.x.max(size.y);
desired.min(regions.first.x).min(regions.first.y)
};
pod.first = Size::splat(length);
pod.expand = Spec::splat(true);
frames = child.layout(ctx, &pod, styles)?;
pod.first = Size::splat(length);
pod.expand = Spec::splat(true);
frames = child.layout(ctx, &pod, styles)?;
}
} else {
// The default size that a shape takes on if it has no child and
// enough space.
let mut size =
Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.first);
let length = if regions.expand.x || regions.expand.y {
let target = regions.expand.select(regions.first, Size::zero());
target.x.max(target.y)
if is_quadratic(S) {
let length = if regions.expand.x || regions.expand.y {
let target = regions.expand.select(regions.first, Size::zero());
target.x.max(target.y)
} else {
size.x.min(size.y)
};
size = Size::splat(length);
} else {
size.x.min(size.y)
};
size = Size::splat(length);
size = regions.expand.select(regions.first, size);
}
frames = vec![Arc::new(Frame::new(size))];
}
@ -162,27 +167,38 @@ impl<const S: ShapeKind> Layout for AngularNode<S> {
let stroke = match styles.get(Self::STROKE) {
Smart::Auto if fill.is_none() => Sides::splat(Some(Stroke::default())),
Smart::Auto => Sides::splat(None),
Smart::Custom(strokes) => strokes.map(|s| Some(s.unwrap_or_default())),
Smart::Custom(strokes) => strokes.map(|s| s.map(|s| s.unwrap_or_default())),
};
let radius = {
let radius = styles.get(Self::RADIUS);
Sides {
left: radius.left.relative_to(frame.size.x / 2.0),
top: radius.top.relative_to(frame.size.y / 2.0),
right: radius.right.relative_to(frame.size.x / 2.0),
bottom: radius.bottom.relative_to(frame.size.y / 2.0),
}
let outset = styles.get(Self::OUTSET);
let outset = Sides {
left: outset.left.relative_to(frame.size.x),
top: outset.top.relative_to(frame.size.y),
right: outset.right.relative_to(frame.size.x),
bottom: outset.bottom.relative_to(frame.size.y),
};
let size = Spec::new(
frame.size.x + outset.left + outset.right,
frame.size.y + outset.top + outset.bottom,
);
let radius = styles.get(Self::RADIUS);
let radius = Sides {
left: radius.left.relative_to(size.x / 2.0),
top: radius.top.relative_to(size.y / 2.0),
right: radius.right.relative_to(size.x / 2.0),
bottom: radius.bottom.relative_to(size.y / 2.0),
};
if fill.is_some() || stroke.iter().any(Option::is_some) {
let shape = Shape {
geometry: Geometry::Rect(frame.size, radius),
geometry: Geometry::Rect(size, radius),
fill,
stroke,
};
frame.prepend(Point::zero(), Element::Shape(shape));
frame.prepend(Point::new(-outset.left, -outset.top), Element::Shape(shape));
}
// Apply link if it exists.
@ -208,3 +224,13 @@ const CIRCLE: ShapeKind = 2;
/// A curve around two focal points.
const ELLIPSE: ShapeKind = 3;
/// Whether a shape kind is curvy.
fn is_round(kind: ShapeKind) -> bool {
matches!(kind, CIRCLE | ELLIPSE)
}
/// Whether a shape kind has equal side length.
fn is_quadratic(kind: ShapeKind) -> bool {
matches!(kind, SQUARE | CIRCLE)
}

View File

@ -8,7 +8,7 @@ use std::sync::Arc;
use super::{Content, Show, ShowNode};
use crate::diag::{At, TypResult};
use crate::eval::{Args, Func, Node, Smart, Value};
use crate::geom::{Numeric, Relative, Sides, Spec};
use crate::geom::{Length, Numeric, Relative, Sides, Spec};
use crate::library::layout::PageNode;
use crate::library::structure::{EnumNode, ListNode};
use crate::library::text::{FontFamily, ParNode, TextNode};
@ -459,17 +459,30 @@ where
}
}
impl<T> Fold for Sides<Option<T>>
impl<T> Fold for Sides<T>
where
T: Default,
T: Fold,
{
type Output = Sides<T>;
type Output = Sides<T::Output>;
fn fold(self, outer: Self::Output) -> Self::Output {
Sides {
left: self.left.fold(outer.left),
top: self.top.fold(outer.top),
right: self.right.fold(outer.right),
bottom: self.bottom.fold(outer.bottom),
}
}
}
impl Fold for Sides<Option<Relative<Length>>> {
type Output = Sides<Relative<Length>>;
fn fold(self, outer: Self::Output) -> Self::Output {
Sides {
left: self.left.unwrap_or(outer.left),
right: self.right.unwrap_or(outer.right),
top: self.top.unwrap_or(outer.top),
right: self.right.unwrap_or(outer.right),
bottom: self.bottom.unwrap_or(outer.bottom),
}
}

View File

@ -11,7 +11,7 @@
// Syntax sugar for function definitions.
#let fill = conifer
#let rect(body) = rect(width: 2cm, fill: fill, padding: 5pt, body)
#let rect(body) = rect(width: 2cm, fill: fill, inset: 5pt, body)
#rect[Hi!]
---

View File

@ -7,6 +7,6 @@
#let d = 3
#let value = [hi]
#let item(a, b) = a + b
#let fn = rect.with(fill: conifer, padding: 5pt)
#let fn = rect.with(fill: conifer, inset: 5pt)
Some _includable_ text.

View File

@ -11,7 +11,7 @@
---
// Test alignment in automatically sized square and circle.
#set text(8pt)
#square(padding: 4pt)[
#square(inset: 4pt)[
Hey there, #align(center + bottom, rotate(180deg, [you!]))
]
#circle(align(center + horizon, [Hey.]))

View File

@ -16,13 +16,13 @@ Auto-sized circle. \
Center-aligned rect in auto-sized circle.
#circle(fill: forest, stroke: conifer,
align(center + horizon,
rect(fill: conifer, padding: 5pt)[But, soft!]
rect(fill: conifer, inset: 5pt)[But, soft!]
)
)
Rect in auto-sized circle. \
#circle(fill: forest,
rect(fill: conifer, stroke: white, padding: 4pt)[
rect(fill: conifer, stroke: white, inset: 4pt)[
#set text(8pt)
But, soft! what light through yonder window breaks?
]

View File

@ -8,7 +8,7 @@
#set page(width: 150pt)
// Fit to text.
#rect(fill: conifer, padding: 3pt)[Textbox]
#rect(fill: conifer, inset: 3pt)[Textbox]
// Empty with fixed width and height.
#block(rect(
@ -18,7 +18,7 @@
))
// Fixed width, text height.
#rect(width: 2cm, fill: rgb("9650d6"), padding: 5pt)[Fixed and padded]
#rect(width: 2cm, fill: rgb("9650d6"), inset: 5pt)[Fixed and padded]
// Page width, fixed height.
#rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft]

View File

@ -7,7 +7,7 @@
---
// Test auto-sized square.
#square(fill: eastern, padding: 5pt)[
#square(fill: eastern, inset: 5pt)[
#set text(fill: white, weight: "bold")
Typst
]

View File

@ -16,7 +16,7 @@
// Test the `columns` function.
#set page(width: auto)
#rect(width: 180pt, height: 100pt, padding: 8pt, columns(2, [
#rect(width: 180pt, height: 100pt, inset: 8pt, columns(2, [
A special plight has befallen our document.
Columns in text boxes reigned down unto the soil
to waste a year's crop of rich layouts.
@ -40,7 +40,7 @@ a page for a test but it does get the job done.
// Test the expansion behavior.
#set page(height: 2.5cm, width: 7.05cm)
#rect(padding: 6pt, columns(2, [
#rect(inset: 6pt, columns(2, [
ABC \
BCD
#colbreak()
@ -73,7 +73,7 @@ D
// Test an empty second column.
#set page(width: 7.05cm, columns: 2)
#rect(width: 100%, padding: 3pt)[So there isn't anything in the second column?]
#rect(width: 100%, inset: 3pt)[So there isn't anything in the second column?]
---
// Test columns when one of them is empty.

View File

@ -16,17 +16,17 @@
// but the B should be center-aligned.
#set par(align: center)
#par(align: right)[
A #rect(width: 2cm, fill: conifer, padding: 4pt)[B]
A #rect(width: 2cm, fill: conifer, inset: 4pt)[B]
]
---
// The inner rectangle should also be yellow here.
// (and therefore invisible)
[#set rect(fill: yellow);#text(1em, rect(padding: 5pt, rect()))]
[#set rect(fill: yellow);#text(1em, rect(inset: 5pt, rect()))]
---
// The inner rectangle should not be yellow here.
A #rect(fill: yellow, padding: 5pt, rect()) B
A #rect(fill: yellow, inset: 5pt, rect()) B
---
// The inner list should not be indented extra.