mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
A new Cast
implementation for Sides
Reinstate circle
This commit is contained in:
parent
84a4961a5d
commit
7b6f3a0ab9
@ -8,7 +8,7 @@ use std::sync::Arc;
|
||||
use super::{ops, Args, Array, Dict, Func, RawLength};
|
||||
use crate::diag::{with_alternative, StrResult};
|
||||
use crate::geom::{
|
||||
Angle, Color, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor,
|
||||
Angle, Color, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor, Sides,
|
||||
};
|
||||
use crate::library::text::RawNode;
|
||||
use crate::model::{Content, Layout, LayoutNode};
|
||||
@ -596,6 +596,44 @@ impl<T: Cast> Cast for Smart<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Cast + Default + Clone> Cast for Sides<T> {
|
||||
fn is(value: &Value) -> bool {
|
||||
matches!(value, Value::Dict(_)) || T::is(value)
|
||||
}
|
||||
|
||||
fn cast(value: Value) -> StrResult<Self> {
|
||||
match value {
|
||||
Value::Dict(dict) => {
|
||||
for (key, _) in &dict {
|
||||
if !matches!(
|
||||
key.as_str(),
|
||||
"left" | "top" | "right" | "bottom" | "x" | "y" | "rest"
|
||||
) {
|
||||
return Err(format!("unexpected key {key:?}"));
|
||||
}
|
||||
}
|
||||
|
||||
let sides = Sides {
|
||||
left: dict.get("left".into()).or_else(|_| dict.get("x".into())),
|
||||
top: dict.get("top".into()).or_else(|_| dict.get("y".into())),
|
||||
right: dict.get("right".into()).or_else(|_| dict.get("x".into())),
|
||||
bottom: dict.get("bottom".into()).or_else(|_| dict.get("y".into())),
|
||||
}
|
||||
.map(|side| {
|
||||
side.or_else(|_| dict.get("rest".into()))
|
||||
.and_then(|v| T::cast(v.clone()))
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
Ok(sides)
|
||||
}
|
||||
v => T::cast(v)
|
||||
.map(Sides::splat)
|
||||
.map_err(|msg| with_alternative(msg, "dictionary")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
Dir: "direction",
|
||||
}
|
||||
|
@ -5,26 +5,22 @@ use crate::library::text::TextNode;
|
||||
|
||||
/// Place a node into a sizable and fillable shape.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct AngularNode<const S: ShapeKind>(pub Option<LayoutNode>);
|
||||
pub struct ShapeNode<const S: ShapeKind>(pub Option<LayoutNode>);
|
||||
|
||||
/// Place a node into a square.
|
||||
pub type SquareNode = AngularNode<SQUARE>;
|
||||
pub type SquareNode = ShapeNode<SQUARE>;
|
||||
|
||||
/// Place a node into a rectangle.
|
||||
pub type RectNode = AngularNode<RECT>;
|
||||
pub type RectNode = ShapeNode<RECT>;
|
||||
|
||||
// /// Place a node into a sizable and fillable shape.
|
||||
// #[derive(Debug, Hash)]
|
||||
// pub struct RoundNode<const S: ShapeKind>(pub Option<LayoutNode>);
|
||||
/// Place a node into a circle.
|
||||
pub type CircleNode = ShapeNode<CIRCLE>;
|
||||
|
||||
// /// Place a node into a circle.
|
||||
// pub type CircleNode = RoundNode<CIRCLE>;
|
||||
|
||||
// /// Place a node into an ellipse.
|
||||
// pub type EllipseNode = RoundNode<ELLIPSE>;
|
||||
/// Place a node into an ellipse.
|
||||
pub type EllipseNode = ShapeNode<ELLIPSE>;
|
||||
|
||||
#[node]
|
||||
impl<const S: ShapeKind> AngularNode<S> {
|
||||
impl<const S: ShapeKind> ShapeNode<S> {
|
||||
/// How to fill the shape.
|
||||
pub const FILL: Option<Paint> = None;
|
||||
/// How to stroke the shape.
|
||||
@ -44,7 +40,11 @@ impl<const S: ShapeKind> AngularNode<S> {
|
||||
pub const RADIUS: Sides<Option<Relative<RawLength>>> = Sides::splat(Relative::zero());
|
||||
|
||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||
let size = args.named::<RawLength>("size")?.map(Relative::from);
|
||||
let size = match S {
|
||||
SQUARE => args.named::<RawLength>("size")?.map(Relative::from),
|
||||
CIRCLE => args.named::<RawLength>("radius")?.map(|r| 2.0 * Relative::from(r)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let width = match size {
|
||||
None => args.named("width")?,
|
||||
@ -60,53 +60,33 @@ impl<const S: ShapeKind> AngularNode<S> {
|
||||
Self(args.find()?).pack().sized(Spec::new(width, height)),
|
||||
))
|
||||
}
|
||||
|
||||
fn set(args: &mut Args) -> TypResult<StyleMap> {
|
||||
let mut styles = StyleMap::new();
|
||||
styles.set_opt(Self::FILL, args.named("fill")?);
|
||||
|
||||
if is_round(S) {
|
||||
styles.set_opt(
|
||||
Self::STROKE,
|
||||
args.named::<Smart<Option<RawStroke>>>("stroke")?
|
||||
.map(|some| some.map(Sides::splat)),
|
||||
);
|
||||
} else {
|
||||
styles.set_opt(Self::STROKE, args.named("stroke")?);
|
||||
}
|
||||
|
||||
castable! {
|
||||
Sides<Option<RawStroke>>,
|
||||
Expected: "stroke, dictionary with strokes for each side",
|
||||
Value::None => {
|
||||
Sides::splat(None)
|
||||
},
|
||||
Value::Dict(values) => {
|
||||
let get = |name: &str| values.get(name.into()).and_then(|v| v.clone().cast()).unwrap_or(None);
|
||||
Sides {
|
||||
top: get("top"),
|
||||
right: get("right"),
|
||||
bottom: get("bottom"),
|
||||
left: get("left"),
|
||||
}
|
||||
},
|
||||
Value::Length(thickness) => Sides::splat(Some(RawStroke {
|
||||
paint: Smart::Auto,
|
||||
thickness: Smart::Custom(thickness),
|
||||
})),
|
||||
Value::Color(color) => Sides::splat(Some(RawStroke {
|
||||
paint: Smart::Custom(color.into()),
|
||||
thickness: Smart::Auto,
|
||||
})),
|
||||
@stroke: RawStroke => Sides::splat(Some(*stroke)),
|
||||
styles.set_opt(Self::INSET, args.named("inset")?);
|
||||
styles.set_opt(Self::OUTSET, args.named("outset")?);
|
||||
|
||||
if S != CIRCLE {
|
||||
styles.set_opt(Self::RADIUS, args.named("radius")?);
|
||||
}
|
||||
|
||||
castable! {
|
||||
Sides<Option<Relative<RawLength>>>,
|
||||
Expected: "length or dictionary of lengths for each side",
|
||||
Value::None => Sides::splat(None),
|
||||
Value::Dict(values) => {
|
||||
let get = |name: &str| values.get(name.into()).and_then(|v| v.clone().cast()).unwrap_or(None);
|
||||
Sides {
|
||||
top: get("top"),
|
||||
right: get("right"),
|
||||
bottom: get("bottom"),
|
||||
left: get("left"),
|
||||
Ok(styles)
|
||||
}
|
||||
},
|
||||
Value::Length(l) => Sides::splat(Some(l.into())),
|
||||
Value::Ratio(r) => Sides::splat(Some(r.into())),
|
||||
Value::Relative(r) => Sides::splat(Some(r)),
|
||||
}
|
||||
|
||||
impl<const S: ShapeKind> Layout for AngularNode<S> {
|
||||
impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut Context,
|
||||
@ -115,7 +95,13 @@ impl<const S: ShapeKind> Layout for AngularNode<S> {
|
||||
) -> TypResult<Vec<Arc<Frame>>> {
|
||||
let mut frames;
|
||||
if let Some(child) = &self.0 {
|
||||
let inset = styles.get(Self::INSET);
|
||||
let mut inset = styles.get(Self::INSET);
|
||||
if is_round(S) {
|
||||
inset = inset.map(|mut side| {
|
||||
side.rel += Ratio::new(0.5 - SQRT_2 / 4.0);
|
||||
side
|
||||
});
|
||||
}
|
||||
|
||||
// Pad the child.
|
||||
let child = child.clone().padded(inset.map(|side| side.map(RawLength::from)));
|
||||
@ -164,10 +150,12 @@ impl<const S: ShapeKind> Layout for AngularNode<S> {
|
||||
|
||||
// Add fill and/or stroke.
|
||||
let fill = styles.get(Self::FILL);
|
||||
let stroke = match styles.get(Self::STROKE) {
|
||||
let mut 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| s.map(|s| s.unwrap_or_default())),
|
||||
Smart::Custom(strokes) => {
|
||||
strokes.map(|s| s.map(RawStroke::unwrap_or_default))
|
||||
}
|
||||
};
|
||||
|
||||
let outset = styles.get(Self::OUTSET);
|
||||
@ -191,13 +179,14 @@ impl<const S: ShapeKind> Layout for AngularNode<S> {
|
||||
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(size, radius),
|
||||
fill,
|
||||
stroke,
|
||||
if fill.is_some() || (stroke.iter().any(Option::is_some) && stroke.is_uniform()) {
|
||||
let geometry = if is_round(S) {
|
||||
Geometry::Ellipse(size)
|
||||
} else {
|
||||
Geometry::Rect(size, radius)
|
||||
};
|
||||
|
||||
let shape = Shape { geometry, fill, stroke };
|
||||
frame.prepend(Point::new(-outset.left, -outset.top), Element::Shape(shape));
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ Rect in ellipse in fixed rect. \
|
||||
)
|
||||
|
||||
Auto-sized ellipse. \
|
||||
#ellipse(fill: conifer, stroke: 3pt + forest, padding: 3pt)[
|
||||
#ellipse(fill: conifer, stroke: 3pt + forest, inset: 3pt)[
|
||||
#set text(8pt)
|
||||
But, soft! what light through yonder window breaks?
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user