Commit Graph

24 Commits

Author SHA1 Message Date
bflatastic c1dc860f1d M3.63: visual bounds and clicks take in the whole stroke
Paths and compound paths now measure their visual bounds along their shape:
the body (inside/outside alignment, the width profile's widest point), the
tips of the miter spikes the limit keeps, projecting caps' corners and the
arrowheads' reach. A rectangle's right-angle miters stay within half the
weight (Fit to Selected Art, Rasterize and all-art SVG export get tighter,
exact boxes); Appearance::outset is the conservative reach for anything else.
Clicks hit what the strokes paint: outside strokes outside the path (0.8 x the
weight out), inside ones inside, the profile's width where clicked, caps,
spikes and heads. Click culling keeps a cheap box (hit test 0.34 ms per click
on the 50k-path budget). The subpath/tangent helpers move from the stroke
geometry into vectorcraft_geom::bez so both share them; a cross-check test
holds every painted point of the real stroke geometry inside the bounds and
hittable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:55:23 +07:00
bflatastic 2877407111 M3.81: freeform gradients: colour points with spread, smooth lines, first points placed inside the shape
Freeform gradients carry their own points (colour, opacity, spread) and lines
(Catmull-Rom curves through points), blended by inverse-distance weighting
from each disc of pure colour. paint.editGradient {kind: "freeform", mode?}
places 4+ points inside the shape, coloured along the stops (which follow the
points' colours). The renderer samples the field on a cached colour grid
drawn as an image paint clipped by the fill or stroke. Moves, transforms,
box rebases and warps move the points; old files load unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:21:38 +07:00
bflatastic 323b178ea7 M3.40: stroke on type — the Stroke panel edits the characters' stroke
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>
2026-10-05 01:09:55 +07:00
bflatastic 8a837fd4b9 M3.68: groups, layers and type have their own fills, strokes and effects, with Contents and Characters rows
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>
2026-10-05 00:59:34 +07:00
bflatastic a1a5c79b03 M3.73: blend modes follow the reference formulas on screen, in SVG and in PDF
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>
2026-10-05 00:27:31 +07:00
bflatastic 3981acdb06 Merge wp/C3: dash alignment and variable-width dashes (M3.38, M3.39)
# Conflicts:
#	crates/pdf/src/lib.rs
#	crates/ui-egui/src/panels/mod.rs
2026-10-04 23:48:47 +07:00
bflatastic 43d7ac420d Merge wp/D2
# Conflicts:
#	crates/pdf/src/export.rs
#	crates/render/src/fx.rs
#	crates/render/src/lib.rs
#	crates/svg/src/export.rs
#	crates/ui-egui/src/canvas.rs
#	crates/ui-egui/src/dialogs/mod.rs
#	crates/ui-egui/src/panels/appearance.rs
#	docs/control-protocol.md
#	docs/mcp.md
2026-10-04 23:46:38 +07:00
bflatastic 42ad4540b7 M3.38: dashes fitted to corners and path ends, a flag of their own that keeps the offset
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>
2026-10-04 23:37:58 +07:00
bflatastic 0f1565aed2 M3.43: effects render on type, placed images, symbol instances, blends, envelopes, meshes and repeats; a fill or stroke's own raster effects render and export as SVG filters on that element
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>
2026-10-04 23:15:13 +07:00
bflatastic f363cdb4ee M3.36: SVG and PDF export draw arrowheads, width profiles, brushes and aligned strokes as the canvas does
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>
2026-10-04 23:07:33 +07:00
Brandon Thomas ead8f72208
Merge pull request #11 from storybored379/pr/svg-import-physical-units
SVG import: root sizes in mm/cm/in/pt open at their physical size
2026-10-04 10:16:45 -04:00
Brandon Thomas 3326b1aaac
Merge pull request #10 from storybored379/pr/svg-export-area-type-lines
SVG export: area type keeps its wrapped lines and alignment
2026-10-04 10:16:42 -04:00
Brandon Thomas 4be1e009ec
Merge pull request #8 from storybored379/pr/svg-import-tspan-line-gap
SVG import: tspan line breaks follow the baseline gap (none on the same baseline, bounded far gaps)
2026-10-04 10:16:35 -04:00
storybored379 6b159467a2 SVG import: tspan line breaks follow the baseline gap (none on the same baseline, bounded far gaps)
Text import turns the gap between an absolutely positioned <tspan y> and the previous line into
that many line breaks, with two problems:

- At least one break was added even when the tspan sits on the current baseline. Styled or
  kerned runs written with x and y on every tspan (as Illustrator writes them) were split into one
  line per run: "Hello <tspan y=same>world</tspan>" came back as "Hello \nworld".
- The count had no upper bound. A tspan with y="1e15" (or any y far below its text) asked for
  ~10^14 '\n' characters, and opening the file aborted the app, the MCP server or
  `vectorcraft-cli` with "memory allocation failed" after eating all memory.

A tspan less than half a line from the current baseline now adds no break, the gap is capped at
10 000 line breaks, and a non-finite gap counts as one; other gaps are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:32:48 -07:00
storybored379 ce5fdfd373 SVG import: root sizes in mm/cm/in/pt open at their physical size
VectorCraft writes and reads SVG at 1 user unit (CSS px) = 1 pt, but a root width/height in
absolute units was taken in CSS pixels (96 per inch). An A4 document from Inkscape
(width="210mm" height="297mm" viewBox="0 0 210 297") opened on a 793.7 × 1122.5 pt artboard,
i.e. 280 × 396 mm, with all art 4/3 too large, and its PDF export came out on that page size;
width="8.5in" gave an 11.3 inch artboard and width="300pt" a 400 pt one.

When the root width or height has an absolute unit (in, cm, mm, pt, pc) the import now scales by
72/96 along that axis, so the artboard and the art keep their physical size (595.3 × 841.9 pt for
A4); geometry, stroke widths and text follow through the same transform. Unitless, px, % and
viewBox-only files are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:25:18 -07:00
storybored379 cab5dffbcf SVG export: area type keeps its wrapped lines and alignment
Area type was written as one <tspan> per paragraph at the frame's top-left: SVG text never wraps,
so every SVG viewer (and VectorCraft's own import) showed each paragraph as a single line running
out of the frame. Centred and right-aligned area type also got text-anchor="middle"/"end" at the
frame's left edge, so it was centred on (or ended at) that edge instead of inside the frame.

The exporter now asks the text layout for the lines: each line is a <tspan x y> at its baseline,
at the line's left edge, centre or right edge to match the text-anchor, with hyphenated breaks
written with their hyphen and the spaces at soft breaks dropped. The <text> carries the first
line's position too, so importers that place text by its own x/y (ours included) start at the
first line. Re-importing an exported file now reproduces the area type pixel for pixel as point
type with the same line breaks (justified lines lose their extra word spacing).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:25:18 -07:00
storybored379 4ac7095601 SVG import: <textPath> comes back as type on a path
VectorCraft exports type on a path as <text><textPath href="#text-path-N"
startOffset="…%"> with the path in <defs>, but the XML text fallback read
every <text> as point type at the text element's x/y. Text on a path
therefore re-imported as a straight line at (0, 0), above the artboard
and mostly out of view, and lost its path.

Resolve the textPath's href (SVG 2 href or xlink:href) to the <path>,
bake the text's user-space transform (and the path's own transform) into
it, and read startOffset (a percentage, or a length along the path) into
TextKind::OnPath.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:17:34 -07:00
Brandon Thomas 9b0fe0ca5d M1.9: agent integrations — MCP tools add_text, apply_effect, pathfinder, transform, create_graph, text_wrap, export {selection, outlineText}; CLI convert (IN → OUT, effects kept) and info; SVG export Convert to Outlines (text as glyph paths); TextObject pathEffect key camelCase
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 07:50:20 -04:00
Brandon Thomas 09fc2eedd9 M4.3: live effects survive SVG, PDF and clipboard export (vectorcraft_effects::bake_document evaluates object, per-fill/stroke and Pathfinder effects on a copy; SVG raster effects become filters: drop shadow, outer/inner glow, Gaussian blur, feather)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 21:39:20 -04:00
Brandon Thomas 4878f1c5dd Rename DrawCraft → VectorCraft: crates (vectorcraft-*), apps, binaries, types, env vars, docs, examples (.vectorcraft); old .drawcraft files and preferences still load. Fix: text bounds exact after opening a file; selection no longer marks a document modified (dirty = content differs from the saved document). Screenshots and renders regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:30:09 -04:00
Brandon Thomas d5aa387367 M7.5: OpenType as character attributes — per-run features (ligatures, contextual alternates, discretionary ligatures, swash, small caps, ordinals, fractions, figure styles) drive shaping; OpenType panel; SVG font-feature-settings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 12:02:13 -04:00
Brandon Thomas 5e15994012 M3: opacity masks — model (linked transform), luminance mask rendering, transparency.* commands, SVG <mask> export/import, PDF luminosity soft masks, Transparency panel controls
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:30:38 -04:00
Brandon Thomas 42df20ca7f Panels to Illustrator parity (19 panels), ~70 menu commands, generic param dialogs, view rotation, property/model-based tests + MCP e2e; fix arrange overflow, compound grouping, double journaling, float round-trip, coordinate sanity, simplify growth, batch rollback of session state
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:32:20 -04:00
Brandon Thomas bfee80a22c M0: workspace skeleton — geom, color, doc, render, text, pathops, svg, tools, engine
Engine-first Cargo workspace for DrawCraft (clean-room Illustrator):
- geom: anchor-based path model, shapes, hit testing, snapping
- color: colours, paints, gradients, swatches, blend modes, harmonies
- doc: persistent Arc document tree, appearance stacks, text model, selection
- render: vello_cpu renderer (appearance, gradients, clip groups, blend modes)
- text: font DB, harfrust shaping, point/area/path layout
- pathops: curve booleans (linesweeper), Pathfinder, offset, simplify
- svg: import (usvg) and export
- tools: selection/direct/pen/shape tools emitting commands
- engine: Session, history with interactions, ~110 commands

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 05:53:29 -04:00