mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Outset; fix folding
This commit is contained in:
parent
5f1499d380
commit
84a4961a5d
@ -478,7 +478,7 @@ pub fn rect_paths(
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (side, radius) in sides.into_iter().zip(radius.windows(2)) {
|
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);
|
connection = connection.advance(stroke_continuity && side != Side::Left);
|
||||||
always_continuous &= stroke_continuity;
|
always_continuous &= stroke_continuity;
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ impl Side {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The next side, clockwise.
|
/// The next side, clockwise.
|
||||||
pub fn clockwise(self) -> Self {
|
pub fn next_cw(self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Left => Self::Top,
|
Self::Left => Self::Top,
|
||||||
Self::Top => Self::Right,
|
Self::Top => Self::Right,
|
||||||
@ -138,7 +138,7 @@ impl Side {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The next side, counter-clockwise.
|
/// The next side, counter-clockwise.
|
||||||
pub fn counter_clockwise(self) -> Self {
|
pub fn next_ccw(self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Left => Self::Bottom,
|
Self::Left => Self::Bottom,
|
||||||
Self::Top => Self::Left,
|
Self::Top => Self::Left,
|
||||||
|
@ -102,6 +102,7 @@ castable! {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Value::Length(l) => Sides::splat(Some(l.into())),
|
Value::Length(l) => Sides::splat(Some(l.into())),
|
||||||
|
Value::Ratio(r) => Sides::splat(Some(r.into())),
|
||||||
Value::Relative(r) => Sides::splat(Some(r)),
|
Value::Relative(r) => Sides::splat(Some(r)),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,15 +118,14 @@ impl<const S: ShapeKind> Layout for AngularNode<S> {
|
|||||||
let inset = styles.get(Self::INSET);
|
let inset = styles.get(Self::INSET);
|
||||||
|
|
||||||
// Pad the child.
|
// Pad the child.
|
||||||
let child = child
|
let child = child.clone().padded(inset.map(|side| side.map(RawLength::from)));
|
||||||
.clone()
|
|
||||||
.padded(inset.map(|side| side.map(|abs| RawLength::from(abs))));
|
|
||||||
|
|
||||||
let mut pod = Regions::one(regions.first, regions.base, regions.expand);
|
let mut pod = Regions::one(regions.first, regions.base, regions.expand);
|
||||||
frames = child.layout(ctx, &pod, styles)?;
|
frames = child.layout(ctx, &pod, styles)?;
|
||||||
|
|
||||||
// Relayout with full expansion into square region to make sure
|
// Relayout with full expansion into square region to make sure
|
||||||
// the result is really a square or circle.
|
// the result is really a square or circle.
|
||||||
|
if is_quadratic(S) {
|
||||||
let length = if regions.expand.x || regions.expand.y {
|
let length = if regions.expand.x || regions.expand.y {
|
||||||
let target = regions.expand.select(regions.first, Size::zero());
|
let target = regions.expand.select(regions.first, Size::zero());
|
||||||
target.x.max(target.y)
|
target.x.max(target.y)
|
||||||
@ -138,12 +138,14 @@ impl<const S: ShapeKind> Layout for AngularNode<S> {
|
|||||||
pod.first = Size::splat(length);
|
pod.first = Size::splat(length);
|
||||||
pod.expand = Spec::splat(true);
|
pod.expand = Spec::splat(true);
|
||||||
frames = child.layout(ctx, &pod, styles)?;
|
frames = child.layout(ctx, &pod, styles)?;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// The default size that a shape takes on if it has no child and
|
// The default size that a shape takes on if it has no child and
|
||||||
// enough space.
|
// enough space.
|
||||||
let mut size =
|
let mut size =
|
||||||
Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.first);
|
Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.first);
|
||||||
|
|
||||||
|
if is_quadratic(S) {
|
||||||
let length = if regions.expand.x || regions.expand.y {
|
let length = if regions.expand.x || regions.expand.y {
|
||||||
let target = regions.expand.select(regions.first, Size::zero());
|
let target = regions.expand.select(regions.first, Size::zero());
|
||||||
target.x.max(target.y)
|
target.x.max(target.y)
|
||||||
@ -151,6 +153,9 @@ impl<const S: ShapeKind> Layout for AngularNode<S> {
|
|||||||
size.x.min(size.y)
|
size.x.min(size.y)
|
||||||
};
|
};
|
||||||
size = Size::splat(length);
|
size = Size::splat(length);
|
||||||
|
} else {
|
||||||
|
size = regions.expand.select(regions.first, size);
|
||||||
|
}
|
||||||
|
|
||||||
frames = vec![Arc::new(Frame::new(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) {
|
let stroke = match styles.get(Self::STROKE) {
|
||||||
Smart::Auto if fill.is_none() => Sides::splat(Some(Stroke::default())),
|
Smart::Auto if fill.is_none() => Sides::splat(Some(Stroke::default())),
|
||||||
Smart::Auto => Sides::splat(None),
|
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 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 = styles.get(Self::RADIUS);
|
||||||
|
let radius = Sides {
|
||||||
Sides {
|
left: radius.left.relative_to(size.x / 2.0),
|
||||||
left: radius.left.relative_to(frame.size.x / 2.0),
|
top: radius.top.relative_to(size.y / 2.0),
|
||||||
top: radius.top.relative_to(frame.size.y / 2.0),
|
right: radius.right.relative_to(size.x / 2.0),
|
||||||
right: radius.right.relative_to(frame.size.x / 2.0),
|
bottom: radius.bottom.relative_to(size.y / 2.0),
|
||||||
bottom: radius.bottom.relative_to(frame.size.y / 2.0),
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if fill.is_some() || stroke.iter().any(Option::is_some) {
|
if fill.is_some() || stroke.iter().any(Option::is_some) {
|
||||||
let shape = Shape {
|
let shape = Shape {
|
||||||
geometry: Geometry::Rect(frame.size, radius),
|
geometry: Geometry::Rect(size, radius),
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
};
|
};
|
||||||
frame.prepend(Point::zero(), Element::Shape(shape));
|
frame.prepend(Point::new(-outset.left, -outset.top), Element::Shape(shape));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply link if it exists.
|
// Apply link if it exists.
|
||||||
@ -208,3 +224,13 @@ const CIRCLE: ShapeKind = 2;
|
|||||||
|
|
||||||
/// A curve around two focal points.
|
/// A curve around two focal points.
|
||||||
const ELLIPSE: ShapeKind = 3;
|
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)
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ use std::sync::Arc;
|
|||||||
use super::{Content, Show, ShowNode};
|
use super::{Content, Show, ShowNode};
|
||||||
use crate::diag::{At, TypResult};
|
use crate::diag::{At, TypResult};
|
||||||
use crate::eval::{Args, Func, Node, Smart, Value};
|
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::layout::PageNode;
|
||||||
use crate::library::structure::{EnumNode, ListNode};
|
use crate::library::structure::{EnumNode, ListNode};
|
||||||
use crate::library::text::{FontFamily, ParNode, TextNode};
|
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
|
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 {
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
Sides {
|
Sides {
|
||||||
left: self.left.unwrap_or(outer.left),
|
left: self.left.unwrap_or(outer.left),
|
||||||
right: self.right.unwrap_or(outer.right),
|
|
||||||
top: self.top.unwrap_or(outer.top),
|
top: self.top.unwrap_or(outer.top),
|
||||||
|
right: self.right.unwrap_or(outer.right),
|
||||||
bottom: self.bottom.unwrap_or(outer.bottom),
|
bottom: self.bottom.unwrap_or(outer.bottom),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
// Syntax sugar for function definitions.
|
// Syntax sugar for function definitions.
|
||||||
#let fill = conifer
|
#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!]
|
#rect[Hi!]
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -7,6 +7,6 @@
|
|||||||
#let d = 3
|
#let d = 3
|
||||||
#let value = [hi]
|
#let value = [hi]
|
||||||
#let item(a, b) = a + b
|
#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.
|
Some _includable_ text.
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
---
|
---
|
||||||
// Test alignment in automatically sized square and circle.
|
// Test alignment in automatically sized square and circle.
|
||||||
#set text(8pt)
|
#set text(8pt)
|
||||||
#square(padding: 4pt)[
|
#square(inset: 4pt)[
|
||||||
Hey there, #align(center + bottom, rotate(180deg, [you!]))
|
Hey there, #align(center + bottom, rotate(180deg, [you!]))
|
||||||
]
|
]
|
||||||
#circle(align(center + horizon, [Hey.]))
|
#circle(align(center + horizon, [Hey.]))
|
||||||
|
@ -16,13 +16,13 @@ Auto-sized circle. \
|
|||||||
Center-aligned rect in auto-sized circle.
|
Center-aligned rect in auto-sized circle.
|
||||||
#circle(fill: forest, stroke: conifer,
|
#circle(fill: forest, stroke: conifer,
|
||||||
align(center + horizon,
|
align(center + horizon,
|
||||||
rect(fill: conifer, padding: 5pt)[But, soft!]
|
rect(fill: conifer, inset: 5pt)[But, soft!]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
Rect in auto-sized circle. \
|
Rect in auto-sized circle. \
|
||||||
#circle(fill: forest,
|
#circle(fill: forest,
|
||||||
rect(fill: conifer, stroke: white, padding: 4pt)[
|
rect(fill: conifer, stroke: white, inset: 4pt)[
|
||||||
#set text(8pt)
|
#set text(8pt)
|
||||||
But, soft! what light through yonder window breaks?
|
But, soft! what light through yonder window breaks?
|
||||||
]
|
]
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
#set page(width: 150pt)
|
#set page(width: 150pt)
|
||||||
|
|
||||||
// Fit to text.
|
// Fit to text.
|
||||||
#rect(fill: conifer, padding: 3pt)[Textbox]
|
#rect(fill: conifer, inset: 3pt)[Textbox]
|
||||||
|
|
||||||
// Empty with fixed width and height.
|
// Empty with fixed width and height.
|
||||||
#block(rect(
|
#block(rect(
|
||||||
@ -18,7 +18,7 @@
|
|||||||
))
|
))
|
||||||
|
|
||||||
// Fixed width, text height.
|
// 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.
|
// Page width, fixed height.
|
||||||
#rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft]
|
#rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft]
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test auto-sized square.
|
// Test auto-sized square.
|
||||||
#square(fill: eastern, padding: 5pt)[
|
#square(fill: eastern, inset: 5pt)[
|
||||||
#set text(fill: white, weight: "bold")
|
#set text(fill: white, weight: "bold")
|
||||||
Typst
|
Typst
|
||||||
]
|
]
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
// Test the `columns` function.
|
// Test the `columns` function.
|
||||||
#set page(width: auto)
|
#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.
|
A special plight has befallen our document.
|
||||||
Columns in text boxes reigned down unto the soil
|
Columns in text boxes reigned down unto the soil
|
||||||
to waste a year's crop of rich layouts.
|
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.
|
// Test the expansion behavior.
|
||||||
#set page(height: 2.5cm, width: 7.05cm)
|
#set page(height: 2.5cm, width: 7.05cm)
|
||||||
|
|
||||||
#rect(padding: 6pt, columns(2, [
|
#rect(inset: 6pt, columns(2, [
|
||||||
ABC \
|
ABC \
|
||||||
BCD
|
BCD
|
||||||
#colbreak()
|
#colbreak()
|
||||||
@ -73,7 +73,7 @@ D
|
|||||||
// Test an empty second column.
|
// Test an empty second column.
|
||||||
#set page(width: 7.05cm, columns: 2)
|
#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.
|
// Test columns when one of them is empty.
|
||||||
|
@ -16,17 +16,17 @@
|
|||||||
// but the B should be center-aligned.
|
// but the B should be center-aligned.
|
||||||
#set par(align: center)
|
#set par(align: center)
|
||||||
#par(align: right)[
|
#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.
|
// The inner rectangle should also be yellow here.
|
||||||
// (and therefore invisible)
|
// (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.
|
// 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.
|
// The inner list should not be indented extra.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user