mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-27 11:17:01 -06:00
M7.3: Type → Type on a Path effects (Rainbow, Skew, 3D Ribbon, Stair Step, Gravity): TextObject::path_effect, type.pathOptions {effect}, checked menu items
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8434ef4404
commit
0ae572be93
@@ -35,7 +35,7 @@ pub use live::{BlendOrientation, BlendSpacing, BlendSpec, EnvelopeKind, Gradient
|
||||
pub use node::{ImageObject, LAYER_COLORS, LayerColor, LiveShape, Node, NodeId, NodeKind, OpacityMask};
|
||||
pub use pattern::{Overlap, PatternDef, PatternEdit, RepeatKind, RepeatSpec, TileType};
|
||||
pub use selection::{AnchorRef, Selection};
|
||||
pub use text::{AreaOptions, CharStyle, FirstBaseline, Justify, ParaStyle, TextKind, TextObject, TextRun, TextStyleDef};
|
||||
pub use text::{AreaOptions, CharStyle, FirstBaseline, Justify, ParaStyle, PathEffect, TextKind, TextObject, TextRun, TextStyleDef};
|
||||
pub use vectorcraft_color as color;
|
||||
pub use vectorcraft_geom as geom;
|
||||
|
||||
|
||||
@@ -136,6 +136,41 @@ pub enum FirstBaseline {
|
||||
Fixed,
|
||||
}
|
||||
|
||||
/// Type on a Path effect: how each glyph is oriented on the path (Type → Type on a Path).
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum PathEffect {
|
||||
/// Glyphs rotate with the path (the default).
|
||||
#[default]
|
||||
Rainbow,
|
||||
/// Vertical edges stay vertical; the baseline follows the path.
|
||||
Skew,
|
||||
/// Horizontal edges stay horizontal; vertical edges are perpendicular to the path.
|
||||
#[serde(rename = "3dRibbon")]
|
||||
Ribbon3d,
|
||||
/// No rotation: the left end of each glyph's baseline sits on the path.
|
||||
StairStep,
|
||||
/// The baseline centre sits on the path and glyphs point away from the path's centre.
|
||||
Gravity,
|
||||
}
|
||||
|
||||
impl PathEffect {
|
||||
pub const ALL: [PathEffect; 5] = [PathEffect::Rainbow, PathEffect::Skew, PathEffect::Ribbon3d, PathEffect::StairStep, PathEffect::Gravity];
|
||||
pub fn id(self) -> &'static str {
|
||||
match self {
|
||||
PathEffect::Rainbow => "rainbow",
|
||||
PathEffect::Skew => "skew",
|
||||
PathEffect::Ribbon3d => "3dRibbon",
|
||||
PathEffect::StairStep => "stairStep",
|
||||
PathEffect::Gravity => "gravity",
|
||||
}
|
||||
}
|
||||
pub fn parse(s: &str) -> Option<Self> {
|
||||
let k = s.to_ascii_lowercase().replace([' ', '-', '_'], "");
|
||||
Self::ALL.into_iter().find(|e| e.id().to_ascii_lowercase() == k || (k == "ribbon3d" && *e == PathEffect::Ribbon3d))
|
||||
}
|
||||
}
|
||||
|
||||
/// Area Type Options: rows and columns, gutters, inset and first baseline of area type.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
@@ -196,6 +231,9 @@ pub struct TextObject {
|
||||
/// Area Type Options (area type only).
|
||||
#[serde(default, skip_serializing_if = "crate::skip::is_default")]
|
||||
pub area: AreaOptions,
|
||||
/// Type on a Path effect (type on a path only).
|
||||
#[serde(default, skip_serializing_if = "crate::skip::is_default")]
|
||||
pub path_effect: PathEffect,
|
||||
/// Cached layout bounds in text space, filled in by the layout engine (not serialized).
|
||||
#[serde(skip)]
|
||||
pub cached_bounds: Option<Rect>,
|
||||
@@ -209,6 +247,7 @@ impl TextObject {
|
||||
runs: vec![TextRun { text: text.into(), style }],
|
||||
para: ParaStyle::default(),
|
||||
area: AreaOptions::default(),
|
||||
path_effect: PathEffect::default(),
|
||||
cached_bounds: None,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user