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>
Gradient tool handles (a drawn vector's start and end, the end handle and the moved start) snap to anchors, edges
and smart guides, with the guide overlays; Shift constrains to 45° steps from the Constrain Angle preference
(`ToolContext::constrain_angle`, `constrain_angle_from` in geom). Double-clicking a toolbar button runs the new UI
command `tool.options {tool}`: the Gradient tool opens the Gradient panel, the Eyedropper opens Eyedropper Options
(`eyedropper.setOptions {appearance?, transparency?}`, which `appearance.copyFrom` honours and its params override).
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>
A drag released on one of the dragged swatches no longer falls through to
the list's drop zone (which moved them to the end).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A CMYK document (file.new {colorMode: "cmyk"}, Document::new_with_mode)
starts with CMYK default swatches (default_swatches(model)), and RGB
colours and gradient stops applied to it are stored as CMYK (Gray stays
Gray) unless paint.setFill/setStroke get keepModel; the Color panel keeps
the model picked on its sliders. RGB documents keep colours as given.
Harmonies and Color Guide variations keep the base colour's model, Recolor
Artwork the replaced colour's, and Edit Colors blends between models take
the document's, through a shared Color::in_model / keep_model.
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>
The eyedropper (`appearance.copyFrom`), `graphicStyle.new` and `graphicStyle.apply` move placed gradients so they
sit at the same place relative to each target's bounds (type: its layout box in text space) instead of where the
source had them; the paint defaults take the gradient unplaced, so new art fits it. Model: `GradientGeom::rebase`
/ `GradientPaint::rebase` (color), `Appearance::rebase_gradients` (strokes relative to their inflated boxes) and
`GraphicStyle::unit_box`: new styles store placed gradients in unit-box space; styles from older files keep
document coordinates and apply as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both writers now ask the shared stroke geometry how to write each stroke
(effects::stroke::for_writer): a plain stroke where that is exact, else the
canvas's filled outlines (arrowheads, width profiles, dashes fitted to corners,
dots with round or projecting caps), grouped under the stroke opacity when the
line and its heads overlap. Brushed strokes are written as their brush art.
Inside/outside alignment only applies to closed paths, so open paths export as
centred strokes. The PDF outside-stroke clip frame and the SVG outside mask now
cover the whole stroke reach (miter spikes, projecting caps). SVG stroke
gradients use the same paint box as the canvas. The svg crate docs no longer
claim arrowheads, profiles, brushes and effects are dropped.
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>
New commands: swatch.addUsedColors {selection?, global?} adds a swatch for
each colour the art uses that no solid swatch has (optionally global, the
art linking to them); swatch.unused lists the swatches nothing uses (links,
colours, gradients and patterns in art, symbols, pattern tiles and graphic
styles count); swatch.merge keeps the first colour and relinks the others'
art; swatch.ungroup and swatch.sortByKind (process, spot, gradients,
patterns). Each edit is one undo step; the panel menu items are enabled.
Document::visit_paints walks every paint of a document.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Appearance panel names the style a linked object uses and its menu offers Redefine
Graphic Style "name", which updates every object still linked (edited objects keep their
look and are unlinked). The Graphic Styles panel breaks links (button and menu), selects
all unused styles, sorts by name, deletes several styles, and opens Graphic Style Options
(rename; links follow) on double-click; New Graphic Style from the menu or with Alt asks
for a name. Select > Same > Graphic Style and Appearance Attribute work, and Document Info
lists the style names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Engine: effect.move {from, to, fromItem?, toItem?, copy?}; appearance.showAllHidden;
appearance.duplicateItem takes to? and indices?; appearance.removeItem takes indices?;
Appearance::has_hidden/show_all. UI: effect.dialog takes index? (edit in place via
effect.setParams) and opens the effectExists question for an effect already in the list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Styles now store the object's opacity, blend mode, isolate and knockout and apply them.
New Graphic Style from a group without its own paint captures its topmost object's
appearance, and from type its characters' fill and stroke. Names are made unique.
graphicStyle.apply takes ids and add (Alt-click in the panel adds the style on top of the
existing appearance). graphicStyle.list reports every style. Applying links objects to a
stable style id (Node.graphic_style), which the management commands build on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Gradient panel gains the Fill/Stroke proxy, a thumbnail menu of the document's gradient swatches with Save to
Swatches, Angle and Aspect Ratio fields with preset menus (the shared spin field), Show/Hide Options, and a stop
colour row whose eyedropper samples the next colour clicked on art into the selected stop (Eyedropper tool option
`stop`, `paint.sampleColor {stop}`). On the slider a selected midpoint diamond shows and sets its midpoint in
Location, double-clicking a stop opens the shared stop popover, Alt-drag copies a stop and Alt-dropping it on
another swaps their colours (`swap_stop_colors`), and a dropped colour swatch adds or recolours a stop. Clicking a
stop no longer falls through to the add-stop strip. Swatch tiles and the gradient thumbnail drag onto art, which
paints the object under the pointer through `paint.setFill`/`paint.setStroke {ids}`. The panel, the popover and
the Color panel share one stop-writing helper.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clicking a colour group's folder selects the whole group. Show Find Field
filters the panel by name. Tiles and list rows drag to reorder swatches,
move them into or out of colour groups or reorder groups (new command
swatch.move, one undo step); swatches and Fill/Stroke proxies dropped on
art paint the object under the pointer (paint.setFill/setStroke with its
id), and a proxy dropped on the panel becomes a swatch. List rows show
kind and colour-mode icons and take double-clicks; spot colours get a dot
in the grid. The search field widget is shared with the Layers panel.
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>
The encoder kept its own copy of the renderer's pixel budget and checked it after
render::raster_size, so a WebP too wide for its 14-bit sizes reported the generic limit
(fileio test oversized_raster_exports_are_refused). Only the WebP side limit stays local.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The merged dasher clamped negative entries to zero, which turned [-5, 3] into dots instead of the
solid line SVG and PDF draw for an invalid pattern (render test invalid_dash_pattern_strokes_solid).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shift-sampling a type object's run gradient projected the document point onto a vector kept in
text space, so placed gradients on moved or turned type sampled the wrong colour. The eyedropper
now reads the paint through Node::proxy_paint and samples in the paint's own space and box (text
space for runs, the stroke-inflated box for strokes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lossless gradient params carried a saved gradient's vector (document coordinates of the art it
came from) into swatch applications and the default paint, so a swatch applied elsewhere, or the
next shape drawn, got a gradient placed off the object. Applying a gradient swatch now fits it to
each object (keeping its aspect, on type in text space), and the default for new art keeps all
but the placement. Saving a swatch stays lossless.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A group's own stack now stays empty after Clear (and keeps only the rows it had after Reduce to
Basic), so the proxies keep showing the defaults for it. The Layers target-dot helper no longer
takes over the thumbnail function's doc comment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The double-click that opens the popover lands outside it in the same frame, so the popover
closed as soon as it opened; only a later click elsewhere closes it now. The selected stop
remembers the document, first selected object and proxy it was picked on (Session::selected_stop),
so selecting other art or toggling Fill/Stroke no longer lets Delete or the arrows edit a stop
on a different gradient.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>