With type selected, stroke.set (without an item) gives every run the
weight, cap, join, miter limit and dashes instead of adding an object
stroke; while the Type tool edits text, the Stroke panel styles the
selected characters through text.setRangeStyle `strokeOptions`, and the
panel reads the first run (or the selection's style).
CharStyle gains stroke_cap, stroke_join, stroke_miter_limit and
stroke_dash (defaulted, skipped when default). Character strokes and
object strokes on type now draw through the shared stroke geometry on
the canvas, in PDF and in SVG (live text writes cap/join/miter/dash and
tspans reset what the first run sets; object strokes are drawn on the
glyph outlines), so every stroke option, opacity and blend applies.
Outlines, the Eyedropper and graphic styles keep the options.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A group's or layer's own fills and strokes paint every member's geometry, below or above the
members as the new contents slot (Appearance.contents_index) says; its geometry effects reshape
the members as one piece and its raster effects apply to the composite (one combined shadow),
respecting knockout groups. Type's own fills and strokes paint under or over its characters.
Render, SVG/PDF (via bake) and Expand Appearance evaluate them alike.
Commands: appearance.* / effect.* take target: "object"|"contents" and layer ids;
appearance.moveItem moves the Contents slot (from: "contents") or an item across it
(contents); appearance.targetContents selects a group's members. Graphic styles applied to a
group now style the group itself. The Appearance panel shows Contents/Characters rows that
drag, and double-click to target the members or edit the characters' paint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
layer.clippingMask.toggle {id?} -> {clip}: the top object of the current
layer (or of the one selected group, or of `id`) becomes the clipping path,
unpainted and moved to the bottom so art added later is clipped too; pressing
the button again releases the mask and leaves the path unpainted. One undo
step. The Layers panel button runs it and underlines clipping-path names.
Model: NodeKind::Layer.clip (skipped when false). Clipped layers render, hit
test, bound, and export to SVG and PDF like clip groups (Node::clips /
set_clips); the clip-path helpers are shared with Make/Release Clipping Mask.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On screen, a group (or object) with opacity, a blend mode, an opacity mask or
knockout whose content blends is drawn offscreen over a copy of its backdrop
and mixed back in by its opacity, mask and clip (a group blend mode then
applies to the backdrop-free result, as PDF defines it). Isolate Blending keeps
the content starting from transparency. Clip groups no longer isolate their
children (blending content is clipped per drawing), and non-isolated knockout
groups composite their elements against the art below the group. Groups
without blending inside still render as plain layers.
In PDF, such groups are written as non-isolated transparency groups (a
constant-alpha soft mask instead of the writer's isolated opacity group),
knockout groups included.
doc: Node::blends_through / children_blend.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
color::blend gains blend_rgb (the 16 blend functions, with the 0.3/0.59/0.11
luminance of the non-separable modes), composite (source-over with blending)
and the shared opacity-mask luminance weights. Exported SVG masks take
luminance from sRGB values (color-interpolation="sRGB"), as the canvas does.
Tests: a per-mode table on the canvas (object, fill and group blend modes,
within 1/255; Exclusion 2/255 in the rasteriser's 8-bit pipeline), the same
table through SVG export rendered by resvg, clip and invert masks against the
canvas, and every mode's PDF name read back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fill and stroke layers (and type characters' fill and stroke) carry an
overprint flag. object.setOverprint {fill?, stroke?, item?, ids?} sets it
(groups: their contents; type: its characters too; one undo step, none when
nothing changes) and attributes.info reads it back (null where mixed).
Overprint Preview and Separations Preview draw overprinting fills and strokes
with Multiply. The old Document.unknown["overprintBlack"] id list migrates to
the new flags on load; Overprint Black now sets the flags.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each dash and dot of a profile stroke is as wide as the profile where it sits
along the whole path (before, dashes dropped the profile). Corners of profile
strokes take the stroke's miter (within the miter limit), round or bevel join on
the outer side and stay clean on the inner side, instead of a miter clamped at
4x; smooth points of curves bend round. Canvas, SVG/PDF outlines and Outline
Stroke share the geometry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With alignDashes every run between corners and path ends holds a whole number of
periods, scaled to fit, with a dash (or dot) centred on each corner and on both
ends of an open path. It lives in the shared dash generator, so the canvas, SVG
and PDF export (as outlines) and Outline Stroke all draw it.
stroke.set: alignDashes and dashOffset edit the current pattern on their own, and
a new dash array keeps the offset and alignment it replaces. The Stroke panel
turns dashes on fitted to corners (or as last turned off), and its Exact/Fit
buttons send only the flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Geometry effects reach objects that aren't paths through their outlines (vectorcraft_effects::
reshape: glyph outlines, evaluated live art, the symbol's art, an image clipped by its reshaped
frame), applied to the whole art as one piece; the renderer caches that art per object and
paints raster effects around it in the same filter layers paths use (shared Renderer::raster_fx,
also used per fill/stroke). Exports bake the reshaped art; SVG writes per-item filters; PDF
warns about raster effects on strokes as well as fills and objects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New Document.page_isolate / page_knockout, set by
transparency.togglePageIsolatedBlending / togglePageKnockoutGroup {value?}
(one undo step each, saved with the document, reported by
transparency.info) and the Transparency panel menu's check items. The page
renders as an isolated group (top-level blend modes no longer blend with
what lies under the page) and/or a knockout group whose elements are the
layers (neutral layers pass their objects through). PDF and SVG wrap the
page content in an isolated group and write page knockout as soft masks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New Node.knockout_shape (transparency.set / object.setProps knockoutShape,
reported by transparency.info). In a knockout group such an object knocks
out only as much as its opacity and mask let through (a 50% object knocks
out 50%); otherwise its full coverage knocks out. The renderer, PDF and
SVG masks honour it, and the panel's checkbox is now live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Knockout Group is now Knockout {Neutral, Off, On} (old files' true loads as
On, false as Neutral). In a knockout group each child erases what the
children below it drew where it paints, then adds itself, so it composites
against the group's backdrop; neutral plain groups pass their children
through to the enclosing knockout group. transparency.set / object.setProps
take knockout "on"|"off"|"neutral"|bool; transparency.info reports the
state. PDF and SVG write knockout groups as nested soft masks with the same
look (PDF warns); the Transparency panel has a tri-state checkbox.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Outline Stroke (and the stroke part of Expand) now builds each stroke from the
shared canvas geometry: dash shapes, arrowheads, width profiles and inside/outside
alignment, united into one clean path (pathops::stroke_region). Every stroke item
is outlined in paint order (runs of fills stay on copies of the path), stroke
opacity, blend mode and effects move to the new fill, and brushed strokes become
their brush art. The live Effect > Path > Outline Stroke outlines the stroke it
sits on (on a fill or the object: the top stroke) with that stroke's cap, join,
dashes, alignment, profile, arrowheads and weight: GeomContext now carries the
stroke and the fill rule, built per item by the renderer, export baking and
Expand Appearance.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clip groups clip by compound paths (with their holes), even-odd paths, text
outlines and groups (the union of their members) on screen, in raster
export, in SVG and in PDF alike, through one shared region:
Node::clip_outline (the model walk) and effects::clip_outline (text
outlined, several shapes united), cached per clipping path in the
renderer. Nothing to clip by hides the clipped art everywhere, and clicks
hit a clip group only inside its clipping region. Make Clipping Mask takes
a compound path or text object on top (it loses its paint) and Release
gives it back unchanged in kind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clicking an Opacity row in the Appearance panel (a fill's or stroke's, or
the object's) opens a popup with the same blend-mode dropdown, opacity
field and slider as the Transparency panel (widgets::opacity_blend), and
edits that item through transparency.set. The blend-mode dropdown draws a
separator between the BlendMode::group() groups, also for the blend-mode
parameters of effect dialogs and inline effect editors. Fills and strokes
of type objects composite with their own opacity and blend mode.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hollow heads draw as rings (the open arrow as a chevron) and closed paths get
no heads. The stroke stops under each head, so it never shows through a hollow
head or past the tip; stroke.set {arrowAlign: "extend"|"tip"} chooses whether
the tip extends past the end point (default) or sits on it with the stroke
shortened. Line and heads share one paint box (a gradient runs on into the
head) and take the stroke and object opacity once, as one layer on the canvas
and one group in PDF. StrokeLayer.arrow_align is a new defaulted field, so
older files load unchanged. The Stroke panel's Align buttons get two generated
icons and are disabled when the stroke has no head.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared stroke geometry now has its own arc-length dash generator: a dash
of length 0 becomes a dot that a round cap paints as a disc and a projecting
cap as a square turned along the path (a butt cap paints nothing). Dashes
restart per subpath, join across the start of closed subpaths, and a pattern
too fine for the path draws solid. The canvas and raster export use it through
line_outline; the stroke.set params doc says how to get dots.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Arrowheads, width-profile outlines and dashed line outlines now come from
vectorcraft_effects::stroke (stroke_pieces, line_outline, width_outline), used by
the canvas and PDF export alike; the renderer caches profile outlines too. Width
profiles are a doc-level catalogue (WidthProfile::PRESETS, preset, preset_id,
id_of) shared by stroke.set and the Stroke panel; stroke.set rejects unknown
profile ids and its params doc lists the arrowhead names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PNG/JPEG/WebP export renders the artboard at the requested scale with no size check. The CPU
rasteriser addresses at most 65535 pixels a side (and panics at that edge), so a large artboard at
a print resolution, e.g. the maximum 16383 pt artboard at 300 ppi (~68 000 px), or a screenshot at
scale 16, panicked inside vello_common: the desktop app or the MCP server exited and unsaved work
was lost.
`vectorcraft_render::raster_size` checks the pixel size first (at most 32 768 px a side and 268
megapixels, like Rasterize already refuses oversized images) and the export paths return an error
that names the size: `document.export` / `exportSelection` / `serialize`, the MCP `export` and
`screenshot` tools, File ▸ Export and the control protocol's `ui.render`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dash pattern with a negative value (for example [-5, 3], from `stroke.set` or a PDF `d`
operator) was handed to the dasher whenever any value was positive. When the values sum to zero
or less the dasher never advances, so rendering the document hung forever (canvas, screenshot,
PNG/JPEG export, `vectorcraft-cli convert`).
PDF (32000-1 §8.4.3.6) and SVG (`stroke-dasharray`) both define such a pattern as invalid and the
stroke as solid. `Dash::is_dashed` now says whether a pattern draws dashes (finite, non-negative,
not all zero), and the renderer, the width-profile outline, the SVG and PDF exporters and the PDF
importer use it, so an invalid pattern is drawn, written and read back as a solid stroke everywhere
(the PDF exporter used to clamp negatives to 0, writing zero-length dashes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Document::swatches_iter/_mut, swatch_mut, swatch_group_of, remove_swatch and a copy-on-write
walk over linked solid paints (map_solid_paints) cover every colour group.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New original DrawCraft mark (gradient + pen nib) replaces the two-letter badge
- render: thumbnails and pattern tiles use a single-threaded context (vello's default is
multithreaded and panics on filters); per-item raster effects detected
- Type tool state no longer leaks across documents
- ui.set {status} for clean screenshots
- README: new hero, feature shots, Made in DrawCraft gallery; examples/neon-drive, ribbons,
feature-sheet; ASSETS.md updated; heading renamed to Crafting Apps
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>