mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46: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 super::{ops, Args, Array, Dict, Func, RawLength};
|
||||||
use crate::diag::{with_alternative, StrResult};
|
use crate::diag::{with_alternative, StrResult};
|
||||||
use crate::geom::{
|
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::library::text::RawNode;
|
||||||
use crate::model::{Content, Layout, LayoutNode};
|
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! {
|
dynamic! {
|
||||||
Dir: "direction",
|
Dir: "direction",
|
||||||
}
|
}
|
||||||
|
@ -5,26 +5,22 @@ use crate::library::text::TextNode;
|
|||||||
|
|
||||||
/// Place a node into a sizable and fillable shape.
|
/// Place a node into a sizable and fillable shape.
|
||||||
#[derive(Debug, Hash)]
|
#[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.
|
/// Place a node into a square.
|
||||||
pub type SquareNode = AngularNode<SQUARE>;
|
pub type SquareNode = ShapeNode<SQUARE>;
|
||||||
|
|
||||||
/// Place a node into a rectangle.
|
/// 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.
|
/// Place a node into a circle.
|
||||||
// #[derive(Debug, Hash)]
|
pub type CircleNode = ShapeNode<CIRCLE>;
|
||||||
// pub struct RoundNode<const S: ShapeKind>(pub Option<LayoutNode>);
|
|
||||||
|
|
||||||
// /// Place a node into a circle.
|
/// Place a node into an ellipse.
|
||||||
// pub type CircleNode = RoundNode<CIRCLE>;
|
pub type EllipseNode = ShapeNode<ELLIPSE>;
|
||||||
|
|
||||||
// /// Place a node into an ellipse.
|
|
||||||
// pub type EllipseNode = RoundNode<ELLIPSE>;
|
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl<const S: ShapeKind> AngularNode<S> {
|
impl<const S: ShapeKind> ShapeNode<S> {
|
||||||
/// How to fill the shape.
|
/// How to fill the shape.
|
||||||
pub const FILL: Option<Paint> = None;
|
pub const FILL: Option<Paint> = None;
|
||||||
/// How to stroke the shape.
|
/// 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());
|
pub const RADIUS: Sides<Option<Relative<RawLength>>> = Sides::splat(Relative::zero());
|
||||||
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
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 {
|
let width = match size {
|
||||||
None => args.named("width")?,
|
None => args.named("width")?,
|
||||||
@ -60,53 +60,33 @@ impl<const S: ShapeKind> AngularNode<S> {
|
|||||||
Self(args.find()?).pack().sized(Spec::new(width, height)),
|
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! {
|
styles.set_opt(Self::INSET, args.named("inset")?);
|
||||||
Sides<Option<RawStroke>>,
|
styles.set_opt(Self::OUTSET, args.named("outset")?);
|
||||||
Expected: "stroke, dictionary with strokes for each side",
|
|
||||||
Value::None => {
|
if S != CIRCLE {
|
||||||
Sides::splat(None)
|
styles.set_opt(Self::RADIUS, args.named("radius")?);
|
||||||
},
|
|
||||||
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)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
Ok(styles)
|
||||||
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"),
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
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(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
ctx: &mut Context,
|
||||||
@ -115,7 +95,13 @@ impl<const S: ShapeKind> Layout for AngularNode<S> {
|
|||||||
) -> TypResult<Vec<Arc<Frame>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let mut frames;
|
let mut frames;
|
||||||
if let Some(child) = &self.0 {
|
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.
|
// Pad the child.
|
||||||
let child = child.clone().padded(inset.map(|side| side.map(RawLength::from)));
|
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.
|
// Add fill and/or stroke.
|
||||||
let fill = styles.get(Self::FILL);
|
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 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| 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);
|
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),
|
bottom: radius.bottom.relative_to(size.y / 2.0),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if fill.is_some() || (stroke.iter().any(Option::is_some) && stroke.is_uniform()) {
|
||||||
if fill.is_some() || stroke.iter().any(Option::is_some) {
|
let geometry = if is_round(S) {
|
||||||
let shape = Shape {
|
Geometry::Ellipse(size)
|
||||||
geometry: Geometry::Rect(size, radius),
|
} else {
|
||||||
fill,
|
Geometry::Rect(size, radius)
|
||||||
stroke,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let shape = Shape { geometry, fill, stroke };
|
||||||
frame.prepend(Point::new(-outset.left, -outset.top), Element::Shape(shape));
|
frame.prepend(Point::new(-outset.left, -outset.top), Element::Shape(shape));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ Rect in ellipse in fixed rect. \
|
|||||||
)
|
)
|
||||||
|
|
||||||
Auto-sized ellipse. \
|
Auto-sized ellipse. \
|
||||||
#ellipse(fill: conifer, stroke: 3pt + forest, padding: 3pt)[
|
#ellipse(fill: conifer, stroke: 3pt + forest, inset: 3pt)[
|
||||||
#set text(8pt)
|
#set text(8pt)
|
||||||
But, soft! what light through yonder window breaks?
|
But, soft! what light through yonder window breaks?
|
||||||
]
|
]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user