mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 09:20:56 -06:00
An envelope mapped type from its laid-out box: ascent to descent for point type, the frame for area type. The distorted glyphs then filled less of the envelope than other art does, with room above the capitals and below the baseline. The envelope now maps type from the bounds of its glyph outlines, underlines and strikethroughs when the type is on its own, in groups and layers, or used as a clipping path. Inline graphics count by their boxes. Rotated type and type in perspective map from their outlines as they are drawn. Make with Mesh, Make with Top Object and Reset with Mesh without Maintain Shape use those bounds. Type that draws nothing keeps its laid-out box, and so does type inside any other object, such as a blend, a repeat or a symbol instance. Shrink Text to Fit type is measured as it is laid out on the page. vectorcraft-text's envelope_ink measures the type, and register_envelope_ink registers it with the document crate. The engine session, Renderer::new, live evaluation in vectorcraft-effects and the .ai reader register it. The document crate keeps the measured boxes per thread, by object and by a hash of the content, and measures again when the type, the fonts, the hyphenation exceptions or the transform's scale or rotation change. This is VectorCraft's own rule. Envelopes of type made in VectorCraft and in saved files map from the glyph outline bounds instead of the type's laid-out box, so they redraw differently.
299 lines
16 KiB
Rust
299 lines
16 KiB
Rust
use super::blend::{
|
||
blend_expand, blend_info, blend_make, blend_options, blend_release, blend_replace_spine, blend_reverse_spine, blend_reverse_stack,
|
||
spine_add_anchor, spine_convert_anchor, spine_move_anchor, spine_remove_anchor, spine_reshape_segment,
|
||
};
|
||
use super::common::{has_blend, has_envelope, has_mesh};
|
||
use super::envelope::{
|
||
env_edit_contents, env_expand, env_info, env_make_mesh, env_make_top, env_make_warp, env_options, env_release, env_reset_mesh, env_reset_warp,
|
||
env_set_mesh_point,
|
||
};
|
||
use super::mesh::{mesh_add_line, mesh_create, mesh_delete_point, mesh_expand, mesh_move_point, mesh_move_points, mesh_set_color};
|
||
use super::{CommandSpec, always, cmd, has_doc, has_multi, has_selection};
|
||
|
||
pub fn specs() -> Vec<CommandSpec> {
|
||
vec![
|
||
// ---------- Blend ----------
|
||
cmd!(
|
||
"object.blend.make",
|
||
"Make",
|
||
["Object", "Blend"],
|
||
Some("Cmd+Alt+B"),
|
||
"{ids?, steps?: n | distance?: pt | smooth?: bool, orientation?: page|path (default: the Blend Options set with nothing selected, else smooth colour and page), starts?: [anchor index | null, …] (per object of `ids`, else of the selection in paint order: the anchor of its first subpath the blend starts from; an open path's last anchor runs it the other way), spacing?: smooth|steps|distance (with value), value?: n, easingType?: easeIn|easeOut|easeInOut|easeOutIn, ramp?: pct (0..100), colorShift?: pct (-100..100; 0: even)} blend the selected objects (paint order) into a live blend; a blend among them takes the others in as more key objects (keeping its options, name and transparency) instead of nesting → {id}",
|
||
has_doc,
|
||
blend_make
|
||
),
|
||
cmd!(
|
||
"object.blend.release",
|
||
"Release",
|
||
["Object", "Blend"],
|
||
Some("Cmd+Alt+Shift+B"),
|
||
"{} release the selected blends: the key objects come back and the spine stays as a path with no fill or stroke, below them; a blend with a name, opacity, blend mode, isolation, opacity mask or appearance of its own comes back as a group keeping them (and its knockout) → {ids (the keys), spines: [ids], groups: [ids]}",
|
||
has_blend,
|
||
blend_release
|
||
),
|
||
cmd!(
|
||
"object.blend.options",
|
||
"Blend Options…",
|
||
["Object", "Blend"],
|
||
None,
|
||
"{spacing?: smooth|steps|distance, value?: n, steps?: n, distance?: pt, orientation?: page|path, smooth?: bool, easingType?: easeIn|easeOut|easeInOut|easeOutIn, ramp?: pct, colorShift?: pct} set the options of the selected blends; easingType chooses the distribution, ramp sets its strength (0..100; 0 = even), colorShift (-100..100; 0 = even) shifts colours independently of shape/position easing (omitted ones keep their value; out of range is an error); with no blend selected, the options new blends start with (spacing and orientation only; a tool setting, not an undo step) → {defaults?: true}",
|
||
always,
|
||
blend_options
|
||
),
|
||
cmd!(
|
||
"object.blend.expand",
|
||
"Expand",
|
||
["Object", "Blend"],
|
||
None,
|
||
"{} replace the selected blends by groups of their keys and steps, each keeping the blend's id, name, transparency (knockout and isolation included), opacity mask and appearance → {ids}",
|
||
has_blend,
|
||
blend_expand
|
||
),
|
||
cmd!(
|
||
"object.blend.replaceSpine",
|
||
"Replace Spine",
|
||
["Object", "Blend"],
|
||
None,
|
||
"{} use the selected path as the spine of the selected blend (the path is consumed)",
|
||
has_blend,
|
||
blend_replace_spine
|
||
),
|
||
cmd!(
|
||
"object.blend.reverseSpine",
|
||
"Reverse Spine",
|
||
["Object", "Blend"],
|
||
None,
|
||
"{} reverse the order of the key objects along the spine",
|
||
has_blend,
|
||
blend_reverse_spine
|
||
),
|
||
cmd!(
|
||
query "object.blend.info",
|
||
"Blend Info",
|
||
[],
|
||
None,
|
||
"{} the options of the first selected blend, else those new blends start with → {target: blend|defaults, id?, spacing: smooth|steps|distance, steps, distance, orientation: page|path, easingType?: easeIn|easeOut|easeInOut|easeOutIn, ramp?: pct, colorShift?: pct, keys?: [ids], starts?: [anchor|null], spine?: {anchors: [{x, y, in: [x, y], out: [x, y]}], closed, keyAnchors: [anchor each key sits on] | null (keys spread evenly: a spine from Replace Spine), explicit: false for the straight lines between the key centres}}",
|
||
always,
|
||
blend_info
|
||
),
|
||
cmd!(
|
||
"object.blend.spine.moveAnchor",
|
||
"Move Spine Point",
|
||
[],
|
||
None,
|
||
"{id? (default: the selected blend), anchor: index, x, y, with?: [index…], handle?: in|out, independent?: bool} move a point of the blend's spine (its handles along), and the points `with` names by as much, or with `handle` place that handle's end at (x, y) (a smooth point keeps the other handle in line unless `independent`). A key object sitting on a moved point moves with it. The first edit turns the straight spine into a path",
|
||
has_doc,
|
||
spine_move_anchor
|
||
),
|
||
cmd!(
|
||
"object.blend.spine.convertAnchor",
|
||
"Convert Anchor Point",
|
||
[],
|
||
None,
|
||
"{id? (default: the selected blend), anchor: index, to: smooth|corner, x?, y?} convert a blend spine point: dragging a corner creates symmetric smooth handles, clicking a point with handles retracts both. Without x/y, smooth uses the neighbouring spine points",
|
||
has_doc,
|
||
spine_convert_anchor
|
||
),
|
||
cmd!(
|
||
"object.blend.spine.reshapeSegment",
|
||
"Reshape Spine Segment",
|
||
[],
|
||
None,
|
||
"{id? (default: the selected blend), segment: index, t: 0..1, dx, dy, perpendicular?: bool} bend a segment of the blend's spine so its point at t moves by (dx, dy), as path.reshapeSegment bends a path's (perpendicular too): a straight spine turns into a curve (#1111)",
|
||
has_doc,
|
||
spine_reshape_segment
|
||
),
|
||
cmd!(
|
||
"object.blend.spine.addAnchor",
|
||
"Add Spine Point",
|
||
[],
|
||
None,
|
||
"{id? (default: the selected blend), x, y} add a point to the blend's spine where it passes nearest (x, y) → {anchor}",
|
||
has_doc,
|
||
spine_add_anchor
|
||
),
|
||
cmd!(
|
||
"object.blend.spine.removeAnchor",
|
||
"Delete Spine Point",
|
||
[],
|
||
None,
|
||
"{id? (default: the selected blend), anchor: index} delete a point of the blend's spine that no key object sits on",
|
||
has_doc,
|
||
spine_remove_anchor
|
||
),
|
||
cmd!(
|
||
"object.blend.reverseFrontToBack",
|
||
"Reverse Front to Back",
|
||
["Object", "Blend"],
|
||
None,
|
||
"{} reverse the stacking order of the selected blends",
|
||
has_blend,
|
||
blend_reverse_stack
|
||
),
|
||
// ---------- Envelope ----------
|
||
cmd!(
|
||
"object.envelope.makeWithWarp",
|
||
"Make with Warp…",
|
||
["Object", "Envelope Distort"],
|
||
Some("Cmd+Alt+Shift+W"),
|
||
"{ids?, style?: arc|arcLower|arcUpper|arch|bulge|shellLower|shellUpper|flag|wave|fish|rise|fisheye|inflate|squeeze|twist (arc), bend?: % -100..100 (50), h?: horizontal distortion % -100..100 (0), v?: vertical distortion % (0), horizontal?: bool (true) | orientation?: horizontal|vertical, horizontalDistortion?: as h, verticalDistortion?: as v} envelope the selected objects with a warp (Envelope Options from object.envelope.options with nothing selected; type maps from its glyph outline bounds) → {id}",
|
||
has_selection,
|
||
env_make_warp
|
||
),
|
||
cmd!(
|
||
"object.envelope.makeWithMesh",
|
||
"Make with Mesh…",
|
||
["Object", "Envelope Distort"],
|
||
Some("Cmd+Alt+M"),
|
||
"{ids?, rows?: 1..50 (4), cols?: 1..50 (4), columns?: as cols} envelope the selected objects with a mesh of rows × cols patches over their bounds (type measured by its glyph outlines) → {id}",
|
||
has_selection,
|
||
env_make_mesh
|
||
),
|
||
cmd!(
|
||
"object.envelope.makeWithTopObject",
|
||
"Make with Top Object",
|
||
["Object", "Envelope Distort"],
|
||
Some("Cmd+Alt+C"),
|
||
"{ids?} the topmost selected path becomes the envelope of the others (type maps from its glyph outline bounds) → {id}",
|
||
has_multi,
|
||
env_make_top
|
||
),
|
||
cmd!(
|
||
"object.envelope.resetWithWarp",
|
||
"Reset with Warp…",
|
||
["Object", "Envelope Distort"],
|
||
None,
|
||
"{style?, bend?, h?, v?, horizontal? | orientation? (as makeWithWarp; unset: the envelope's current warp, else arc 50 %), horizontalDistortion?: as h, verticalDistortion?: as v} make the selected envelopes warp envelopes, keeping their content",
|
||
has_envelope,
|
||
env_reset_warp
|
||
),
|
||
cmd!(
|
||
"object.envelope.resetWithMesh",
|
||
"Reset with Mesh…",
|
||
["Object", "Envelope Distort"],
|
||
None,
|
||
"{rows?: 1..50, cols?: 1..50 (unset: a mesh envelope's own, else 4), maintainShape?: bool (true: the mesh follows the current envelope shape; false: a flat grid over the content, type measured by its glyph outlines), columns?: as cols} make the selected envelopes mesh envelopes, keeping their content",
|
||
has_envelope,
|
||
env_reset_mesh
|
||
),
|
||
cmd!(
|
||
query "object.envelope.info",
|
||
"Envelope Info",
|
||
[],
|
||
None,
|
||
"{} the selected envelope's settings, or with none selected the defaults new envelopes get → {id: n|null, type: warp|mesh|topObject, style, bend, h, v, horizontal, rows, cols, fidelity, antiAlias, preserveShape: clippingMask|transparency, distortAppearance, distortLinearGradients, distortPatternFills, boundsMargin?: [top, bottom, left, right] (shares of the content's height and width, reported only when not zero: with distortAppearance, how far the box the content maps from reaches past its geometric bounds), editing}",
|
||
has_doc,
|
||
env_info
|
||
),
|
||
cmd!(
|
||
"object.envelope.release",
|
||
"Release",
|
||
["Object", "Envelope Distort"],
|
||
None,
|
||
"{} release the selected envelopes into their content and their shape (a grey gradient mesh for warp and mesh envelopes, the path for a top-object envelope; the content keeps the envelope's opacity, blend mode and opacity mask) → {ids}",
|
||
has_envelope,
|
||
env_release
|
||
),
|
||
cmd!(
|
||
"object.envelope.options",
|
||
"Envelope Options…",
|
||
["Object", "Envelope Distort"],
|
||
None,
|
||
"{fidelity?: 0..100, antiAlias?: bool, preserveShape?: clippingMask|transparency, distortAppearance?: bool, distortLinearGradients?: bool, distortPatternFills?: bool (the last two work with distortAppearance; on an envelope with boundsMargin, distortAppearance also switches the content between the box with the margins and its geometric box), style?, bend?, h?, v?, horizontal? (warp envelopes), horizontalDistortion?: as h, verticalDistortion?: as v, orientation?: horizontal|vertical} set the selected envelopes' options; with no envelope selected, the options new envelopes get (see object.envelope.info)",
|
||
has_doc,
|
||
env_options
|
||
),
|
||
cmd!(
|
||
"object.envelope.expand",
|
||
"Expand",
|
||
["Object", "Envelope Distort"],
|
||
None,
|
||
"{} replace the selected envelopes by groups of their distorted content (type outlined), keeping name, opacity, blend mode, isolation, knockout and opacity mask → {ids}",
|
||
has_envelope,
|
||
env_expand
|
||
),
|
||
cmd!(
|
||
"object.envelope.editContents",
|
||
"Edit Contents",
|
||
["Object", "Envelope Distort"],
|
||
None,
|
||
"{editing?: bool} toggle Edit Contents / Edit Envelope; leaving Edit Contents measures an envelope's boundsMargin again when the content's strokes reach differently than when Edit Contents began → {editing}",
|
||
has_envelope,
|
||
env_edit_contents
|
||
),
|
||
cmd!(
|
||
"object.envelope.setMeshPoint",
|
||
"Move Envelope Mesh Point",
|
||
[],
|
||
None,
|
||
"{id, index, x, y, handle?: 0 right|1 left|2 down|3 up} move a point of a mesh envelope (its handles follow), or with `handle` place that handle end at (x, y); object.mesh.movePoint, addLine and deletePoint edit mesh envelopes too",
|
||
has_doc,
|
||
env_set_mesh_point
|
||
),
|
||
// ---------- Gradient mesh ----------
|
||
cmd!(
|
||
"object.mesh.create",
|
||
"Create Gradient Mesh…",
|
||
["Object"],
|
||
None,
|
||
"{ids?, rows?: n (4), cols?: n (4), appearance?: flat|center|edge, highlight?: % (100), at?: [x,y], tolerance?: pt (0), columns?: as cols} convert filled paths into gradient meshes in their fill colour (a gradient fill: the colour it paints at each point), lightened per `appearance` (with `at`: 1×1 mesh plus lines through that point, or through the nearest point of the edge when `at` is outside within `tolerance`); an embedded image becomes a mesh over its rectangle (its transform kept) whose points take the average colour and opacity of the picture around them (a linked image is refused: embed it first) → {ids, index?}",
|
||
has_selection,
|
||
mesh_create
|
||
),
|
||
cmd!(
|
||
"object.mesh.setPointColor",
|
||
"Set Mesh Point Color",
|
||
[],
|
||
None,
|
||
"{id, index | points: [index…] | items: [{id, index | points}…], color? (#rrggbbaa and #rgba are refused), keepModel?: bool (as paint.setFill), opacity?: 0..1} colour and/or opacity of gradient mesh points (one undo step; an index out of range changes nothing)",
|
||
has_doc,
|
||
mesh_set_color
|
||
),
|
||
cmd!(
|
||
"object.mesh.movePoint",
|
||
"Move Mesh Point",
|
||
[],
|
||
None,
|
||
"{id, index, x, y, handle?: 0 right|1 left|2 down|3 up} move a point of a gradient mesh or mesh envelope (its handles follow), or with `handle` place that handle end at (x, y)",
|
||
has_doc,
|
||
mesh_move_point
|
||
),
|
||
cmd!(
|
||
"object.mesh.movePoints",
|
||
"Move Mesh Points",
|
||
[],
|
||
None,
|
||
"{id, points: [index…] | items: [{id, index | points}…], dx, dy} move points of gradient meshes or mesh envelopes by (dx, dy), their handles with them (one undo step; an index out of range changes nothing)",
|
||
has_doc,
|
||
mesh_move_points
|
||
),
|
||
cmd!(
|
||
"object.mesh.addLine",
|
||
"Add Mesh Line",
|
||
[],
|
||
None,
|
||
"{id, x, y, color? (#rrggbbaa and #rgba are refused), keepModel?: bool (as paint.setFill), tolerance?: pt (0)} add a row and a column through (x, y) to a gradient mesh (new point in `color`) or a mesh envelope; a point outside within `tolerance` of the edge adds them through the nearest point of the edge → {index}",
|
||
has_doc,
|
||
mesh_add_line
|
||
),
|
||
cmd!(
|
||
"object.mesh.deletePoint",
|
||
"Delete Mesh Point",
|
||
[],
|
||
None,
|
||
"{id, index} delete the mesh lines through a point of a gradient mesh or mesh envelope",
|
||
has_doc,
|
||
mesh_delete_point
|
||
),
|
||
cmd!(
|
||
"object.mesh.expand",
|
||
"Expand Gradient Mesh",
|
||
["Object"],
|
||
None,
|
||
"{} replace the selected meshes by groups of flat-coloured pieces → {ids}",
|
||
has_mesh,
|
||
mesh_expand
|
||
),
|
||
]
|
||
}
|