Commit Graph

2805 Commits

Author SHA1 Message Date
sanyamsingh001 cfec15d8d9 G13: offer TIFF in Export for Screens and Asset Export
Co-authored-by: Codex <noreply@openai.com>
2026-10-14 05:16:22 +07:00
bflatastic a57aea8e94 DXF export: leave out the centerline option and the 3ds Max preset for now
The VPORT view direction fix and its test stay. The Export Strokes as
Centerlines option and the preset button are not in the documented DXF/DWG
export options, so they wait for the owner's decision.
2026-10-14 05:16:15 +07:00
Osama Amin 4a68f84282 DXF export: strokes as zero-width centerlines, a 3ds Max R12 linework preset, and one view direction in VPORT 2026-10-14 05:16:09 +07:00
bflatastic 301a99d91c SVG import: nested :not() selectors are bounded 2026-10-14 05:16:03 +07:00
Gert Pellin 6f9595542d SVG import: no slice indexing when writing a new style attribute 2026-10-14 05:16:00 +07:00
Gert Pellin 5f8c4e9ab5 SVG import: style sheet rules with :not() selectors apply (#1557)
usvg's style sheets (simplecss) drop rules whose selector has a :not(), so
rect:not(.keep) { fill: green } left the rect as it was. The text pass's own
cascade now reads :not() of a list of compound selectors (counted as its most
specific argument), and the import pre-pass writes what such rules set on an
element into its style attribute, with the value that wins the cascade there,
so usvg reads what CSS would: against other rules, the style attribute and
!important, inherited from groups, display: none and CSS transforms included.
Selector lists are split at top-level commas only.
2026-10-14 05:15:59 +07:00
sanyamsingh001 bb07d267aa G1: keep Artboard deletion consistent across controls
Co-authored-by: Codex <noreply@openai.com>
2026-10-14 05:15:42 +07:00
sanyamsingh001 5f4da3beb2 G4: capture character styles from selected text
Co-authored-by: Codex <noreply@openai.com>
2026-10-14 05:15:34 +07:00
sanyamsingh001 123b8ad8ea G4: propagate character-style Auto-leading resets
Co-authored-by: Codex <noreply@openai.com>
2026-10-14 05:15:30 +07:00
sanyamsingh001 0c2c98f6e7 G4: show mixed paragraph alignment without a selected button
Co-authored-by: Codex <noreply@openai.com>
2026-10-14 05:15:18 +07:00
sanyamsingh001 ff6aaaaeea G1: switch new ruler-guide orientation with Alt
Co-authored-by: Codex <noreply@openai.com>
2026-10-14 05:15:12 +07:00
sanyamsingh001 0e6b2e331d G1: create width points through Width Point Edit
Co-authored-by: Codex <noreply@openai.com>
2026-10-14 05:14:20 +07:00
sanyamsingh001 f7714c539f G1: honor Shift-double-click in Width Point Edit
Co-authored-by: Codex <noreply@openai.com>
2026-10-14 05:13:59 +07:00
bflatastic bc50c7fa56 Identify Objects By: rustfmt 2026-10-14 05:13:36 +07:00
Fabio William Conceição 958c1dffe8 Part of #394: Identify Objects By shows and edits Layers names as XML IDs 2026-10-14 05:13:15 +07:00
bflatastic cfa65a2148 Appearance of Black › Printing / Exporting: cover ink-plane documents and CMYK exports
Drops the documented richBlack parameter: commands don't list it.
2026-10-14 05:12:59 +07:00
Fabio William Conceição b6d14b1fd0 Part of #394: Appearance of Black › Printing / Exporting draws pure CMYK black as RGB black
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-14 05:11:55 +07:00
GameDirection b5bc8f0d19 Corner Radius: a field per corner of a live rectangle, with a link toggle
The Corner Radius field in the Properties and Transform panels has a
link toggle behind it (the "singleCornerRadius" preference, on by
default). When the link is on, one field sets every corner, as before.
When it is off, the panels show one field for each corner of a live
rectangle: Top Left, Top Right, Bottom Right and Bottom Left. Each field
sets its own corner through object.setLiveShape {corners, radius}.

This is the part of #1594 that does not change the corner widget drag
or its modifier keys, split out for review.

Tests: a Properties panel test (the link on shows one field; off shows
the four labelled fields with each corner's radius, in line and in
order). The new labels are translated in all 15 catalogs.
2026-10-14 05:11:22 +07:00
Ayberk A. 043b446b94 3D and Materials: the light takes the documented settings
The public 3D and Materials panel options give the Light Settings as
Intensity (0–100 %), Rotation (-180° to 180°), Height (0° to 90°),
Softness (0–100 %) and Ambient Light (0–200 %), with the light moved to
the back or the front of the object. The 3D effects (Revolve included)
had Direction and Elevation (an azimuth and elevation), Intensity and
Ambient (0–100 %).

- Rotation turns the light round the object on the page (our reading:
  0° on its right, counterclockwise); Height raises it from beside the
  object (0°) to straight in front (90°); Light behind the object puts
  it at the back. The light gizmo's ring and line are Rotation and
  Height.
- Softness wraps the light round the object: at 0 % it falls off with
  the angle as before, at 100 % it reaches nothing only straight away.
- Ambient Light runs to 200 %; faces stay no brighter than the art.
- lightRotation, lightHeight, lightBehind and lightSoftness replace
  lightAzimuth and lightElevation. An earlier light is read as the same
  direction, so it shades exactly as before (old and new code give the
  same colours for the four effects and five lights); effect.setParams
  turns a stored one into the new form before applying new values, and
  the parameter description names lightAzimuth and lightElevation as the
  earlier form, so effect.apply and effect.setParams still take them. The
  default light is the earlier default's: Rotation 125.26°, Height 30°.
- The dialog's Lighting tab shows the settings in the documented order;
  Softness, Ambient Light and Light behind the object are in every
  catalog, and the unused Direction, Elevation and Ambient are gone.

Light colour, presets, several lights and shadows are not built.
2026-10-14 05:10:48 +07:00
Ayberk A. 39db6abfce 3D and Materials: Perspective runs 0° to 160°, as documented
The public 3D and Materials panel options give Perspective as 0° to 160°
("how close or far the object appears"); it was a 0–100 % strength, for
Revolve, Extrude & Bevel, Inflate and Rotate alike.

- The eye stands where a sphere about the object's centre, √3 times its
  half size (the box around it fits inside), fills that angle of the
  view: 0° is none, wider brings the eye nearer, and at 160° it is just
  outside the sphere, never inside the object. The documentation doesn't
  say how the angle places the eye; docs/three-d.md says this is our
  reading.
- `perspectiveAngle` (degrees) replaces `perspective` (%). A strength from
  an earlier file or script is read as the angle that shows exactly the
  same view: sin(angle / 2) = √3 × strength / 4, 100 % about 51.3°. An
  angle given too wins. effect.setParams turns a stored strength into the
  angle before applying new values. The parameter description names
  `perspective` as the earlier form, so effect.apply and effect.setParams
  still take it under the key check of #1592. Old and new code draw the four effects identically at 0, 25,
  60 and 100 % (bounds equal to 1e-9).
- The dialogs' Perspective fields run 0–160°.

# Conflicts:
#	docs/target-app-parity.md
2026-10-14 05:10:09 +07:00
BFlat a3357121fb
Merge pull request #1792 from dotCooCoo/feat/thread-reflow-copies-only-changed-stories
Type: an edit copies a threaded story only when it changes one of its frames
2026-10-14 05:07:09 +07:00
BFlat 81d9e37e8f
Merge pull request #1767 from dotCooCoo/feat/open-resolves-links-off-ui-thread
Open: the opening thread also reads linked files and lays out type
2026-10-14 05:06:41 +07:00
BFlat bb70aa8359
Merge pull request #1744 from dotCooCoo/feat/symbol-instance-art-bounds
Symbols: instances of imported symbols are measured by their art
2026-10-14 05:06:05 +07:00
BFlat 9258c4ea57
Merge pull request #1690 from dotCooCoo/feat/svg-style-rule-index
SVG import: the style cascade looks up each element's rules by id, class and type
2026-10-14 05:05:34 +07:00
BFlat b0ec580828
Merge pull request #1755 from dotCooCoo/feat/cmyk-transparency-planes
Render: the ink planes of CMYK documents with transparency are composed on several threads
2026-10-14 05:04:55 +07:00
BFlat 03e6657e93
Merge pull request #1689 from dotCooCoo/feat/outline-mode-path-caches
Render: Outline view draws paths and clipped art from geometry kept between frames
2026-10-14 05:04:17 +07:00
BFlat aeda9de044
Merge pull request #1688 from dotCooCoo/feat/live-effects-follow-document-edits
Render: live objects are evaluated again when patterns, symbols, fonts or color settings change
2026-10-14 05:03:40 +07:00
BFlat 35ecdf2142
Merge pull request #1687 from dotCooCoo/feat/group-backdrop-region
Render: the canvas no longer draws its picture back for each non-isolated group that blends
2026-10-14 05:02:02 +07:00
BFlat 1b54bc335d
Merge pull request #1667 from dotCooCoo/feat/stroke-outlines-per-object
Render: objects that share an appearance keep their own stroke outlines between frames
2026-10-14 05:01:35 +07:00
dotCooCoo 4844d54fda Type: an edit copies a threaded story only when it changes one of its frames
After every edit of a document with threaded text, each frame of a
drag included, threads::reflow copied the runs and the text object of
every frame of every thread, and then dropped the copies for each
thread whose frames the edit had not changed. With a story of 50,000
characters threaded through 8 frames with a style change at every word
(9,618 runs), moving another object took 7.8 ms in a release build,
and a frame of a drag took 7.2 ms.

reflow now copies a thread's story only when one of its frames
changed. The same move takes 0.013 ms, and the drag frame takes
0.012 ms. Threads with a changed frame re-flow as before, and the
documents and undo steps after edits are the same as before.

Docs: docs/target-app-parity.md.
2026-10-14 04:56:30 +07:00
dotCooCoo 85cea7efea Open: the opening thread also reads linked files and lays out type
When the desktop app opened a document on a thread of its own, that
thread parsed it, but open_read, which the app ran on the UI thread,
read the document's linked files, decoding BMP, PSD and RGB TIFF files
and storing them again as PNG, and laid out the type. In release builds,
a document with 12 linked 3000 x 2000 TIFFs kept the UI thread busy for
1.04 s, and one with 300 Auto Size area type objects, every second one
in three columns, kept it busy for 0.85 s.

read_for_open now reads the linked files and lays out the type on the
opening thread, with the path and the Update Links preference that
opening::start passes it, and open_read only adds the finished document.
open_read now takes 0.02 ms and 0.1 ms on those documents. For the
measured documents, the opened documents, results and undo states are
the same as on main.

Docs: docs/target-app-parity.md.
2026-10-14 04:56:30 +07:00
bflatastic 37174ac306 Render tests: the symbol art bounds test holds the fonts steady (#1744) 2026-10-14 04:56:30 +07:00
dotCooCoo 21a386a6d4 SVG import: the style cascade looks up each element's rules by id, class and type
The SVG import's style cascade tested every <style> rule against each
element it was asked about. The import asks about the objects outside
<defs> and the like when the file contains `display`, about each shape
when the file contains `non-scaling-stroke`, and about each <text>, its
spans and its ancestors. On a file with 2,000 class rules and 100,000
rectangles, `vectorcraft-cli info` took 3.54 s with one `display`
attribute in the file and 2.03 s without it.

The rules are now filed under the id of their selector's rightmost
compound, else under its first class, else under its type. An element
is tested against the rules filed under its id, its classes and its
type, and the rules filed under none of these, in source order. It
matches the same rules in the same order and gets the same
properties. The file with the `display` attribute now takes 2.10 s.

Docs: docs/target-app-parity.md.
2026-10-14 04:56:30 +07:00
dotCooCoo f9cbcfb046 Render: the ink planes of CMYK documents with transparency are composed on several threads
A CMYK document that shows transparency is drawn as two ink planes,
and ink::compose turned them into screen colors one pixel at a time on
one thread. At 2880 x 1800 that took about as long as drawing one
plane of 50,000 paths. The renderer also searched a CMYK document for
transparency in every frame.

compose now splits the frame into bands, as many as the renderer has
threads and no more than one per 65,536 pixels. The calling thread
composes the first band and a scoped thread composes each other band,
with the same per-pixel code. The renderer keeps the answer of the
transparency test with the layers, symbol art and pattern art it was
found for, held weakly, and tests again only when the document holds
other art. With Overprint Preview on, a document whose layers or
symbols hold an overprinting fill or stroke is still tested in every
frame. FrameStats::ink_threads reports how many threads composed a
frame. On 8 threads at 2880 x 1800, a page of 300 shapes at 20% to 90%
opacity takes 30 ms a frame instead of 55 ms, and 50,000 paths with
one at 99% opacity take 258 ms instead of 361 ms, with the same
pixels.

Docs: docs/target-app-parity.md.
2026-10-14 04:56:30 +07:00
dotCooCoo 2ce6f90522 Render: Outline view draws paths and clipped art from geometry kept between frames
In Outline view the renderer made a new path from the path data of
each path and compound path it drew, in every frame. In Preview the
renderer draws plain paths from the path it keeps for each object
between frames. In Outline view the members of a clip group were
culled by bounds measured again in every frame, and the bounds of a
group inside a clip group were measured from every path in it. The
outlined shapes of an Image Trace object were culled and drawn the
same way.

In Outline view the renderer now draws paths and compound paths from
the paths it keeps for them, and culls the members of clip groups and
the outlined shapes of Image Trace objects by the bounds it keeps for
them. The live-effect geometry of those members is kept between frames
too. The frames have the same pixels as before.

With 50,000 paths at 2880x1800 on 8 threads, an Outline frame at 400%
took 47.1 ms on main and takes 16.7 ms when the paths are the members
of one clip group. It took 36.6 ms and takes 26.3 ms when the paths
are paired into compound paths.

Docs: docs/target-app-parity.md.
2026-10-14 04:56:30 +07:00
bflatastic caa80fb213 Render tests: the live object tests take the font locks (#1688) 2026-10-14 04:56:30 +07:00
dotCooCoo d9bd9b134d Symbols: instances of imported symbols are measured by their art
Every symbol instance was measured as its transform applied to the
20 × 20 square around the origin, which symbols made in the app keep
their art in. The SVG and DXF importers keep symbol art as drawn, so
the box of an instance of an imported symbol did not match its art.
The SVG Star instance at x 20, y 30 draws from (20, 30) to (40, 50)
and was measured from (10, 20) to (30, 40). Align moved such an
instance by the wrong amount, clicks and marquees beside its art
selected it, and the canvas, exports and prints left it out when its
art reached the view or the page and the square did not.

An instance is now measured by its symbol's art, which
Document::symbol_boxes looks up in the document each time
(Node::geometric_bounds_with, visual_bounds_with and
reach_bounds_with). With Use Preview Bounds on, the strokes in the art
count at their own weight, as the canvas draws them, and
object.setBounds counts them at that weight when it sizes an instance
or an object that holds one. When strokes do not scale and a width or
height of 0 or more is smaller than what a path's stroke adds,
object.setBounds now gives the path no width or height, as it does
for an instance whose art has strokes; before, it mirrored the path.
With Use Preview Bounds on, object.setBounds now measures the margins
that strokes add object by object, the members of groups and the
objects in symbol instances' art included, and solves for the width,
height and reference point asked for when the width and height are 0
or more. Before, it measured the margins of the whole selection and
could miss the size or the reference point asked for when strokes did
not scale and the objects' strokes added different margins, and when
strokes scaled and the width and height were out of proportion, even
for one path. A clip group, a Shaper Group, a compound path, type, and
a group or instance with effects or an opacity mask of its own are each
measured as one object, and so is an instance in perspective or one
turned or slanted relative to the bounding box, other than by quarter
turns. When the selection holds more than 4,096 objects to measure this
way, each selected object is measured as one. Instances inside symbol
art are measured by their own symbol's art. An instance is measured as
the square when its symbol is missing, when its symbol or a symbol
nested in its art holds an instance of itself, and when symbols nest 32
deep in its art.

Align and Distribute, the Transform panel, the bounding box and the
selection outline, clicks, the marquee, document.inspect and
document.node, Fit to Artwork Bounds and Fit to Selected Art, canvas
culling, thumbnails, the SVG filter region of an instance with a
raster effect, raster effect images, Export Selection, the SVG of an
artboard, printing, and the PDF, EPS, EMF/WMF and DXF writers use the
new box. The renderer also culls an instance by the effects and
brushed strokes in its art, and it culls a blend, envelope or repeat
by the brush art of what it holds and the effects in the art of the
instances in it. It culls an instance with geometry effects of its own
by the effects in its art after those geometry effects reshape the art.
It measures the effects in a symbol's art once for each symbol and
linear part of the transform that places it. For symbols whose art
holds instances, it measures at most 4,096 placements a frame, and at
most 4,096 more for each pattern tile it rasterizes and each placed
document it draws. Past that, it estimates the reach from the symbol's
reach with no transform, which it measures once. When the drawn
document's symbols change, the renderer drops the cached bounds of the
instances and of the objects that hold them.

The PDF and EPS writers, the SVG of an artboard and Skip Blank
Artboards grow the box of an instance whose art holds brushed strokes
by 16 times the widest stroke (at least 1 pt) of the objects in that
art that have a brushed stroke, plus 8 pt, as the renderer grows the
cull bounds of a brushed path. They keep an instance whose brush art
reaches the page. The SVG of an artboard also keeps an object that
holds an instance when what the canvas paints for it, the effects in
the symbol's art and the instance's own included, reaches the artboard.

Not yet: other commands that measure objects, such as Select › All on
Active Artboard, Transform Each and Convert to Artboards, still measure
an instance by the square.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-14 04:56:30 +07:00
dotCooCoo 78980c8190 Render: the canvas no longer draws its picture back for each non-isolated group that blends
A transparency group whose members blend with the art below it, and a
masked object with a blend mode, are drawn over a copy of that art. For
each such group in each frame, the renderer rendered everything drawn
so far into a new pixmap the size of its context (on the canvas, the
whole view), started that context again and drew that pixmap back into
it as an image, which the next copy and the frame rendered again. At
2880x1800, a document with 50 translucent groups, each holding a
Multiply rectangle, took 299 ms a frame on 8 threads.

Every render that composites in 8 bits, the canvas included, now keeps
that picture and renders what the context draws next over it. The
backdrop is still cut from the picture. The 50 groups take 65 ms a
frame, and the frames have the same pixels. Renders that composite in
floating point (raster exports, Save for Web, Object › Rasterize and
Crop Image) still draw the picture back.

Docs: docs/target-app-parity.md.
2026-10-14 04:56:29 +07:00
bflatastic 819e53fe92 Render tests: the stroke outline tests hold the fonts steady (#1667) 2026-10-14 04:56:29 +07:00
dotCooCoo 6504dc7da2 Render: live objects are evaluated again when patterns, symbols, fonts or color settings change
The renderer kept the evaluation of each blend, envelope and repeat
while the object and a hash stayed the same: the addresses of the art of
the symbols the object showed and of the patterns it painted with, and
the patterns' tile bounds. When anything else the evaluation reads
changed, such as a pattern's tile type, symbols in symbol art, the
fonts or the color settings, a renderer that had drawn the object went
on drawing the old evaluation. The quads of a gradient mesh were kept
the same way after a change to the color settings, and the art drawn
for a live object with an effect of its own was kept by the object's
address alone.

resources_key now also walks the opacity-mask, symbol and pattern art
it finds. It hashes what it finds, the font generation when it finds
type or a placed document, and the color settings. Mesh quads are made
again when the color settings change. The art drawn for an object's
effects is made again when the hash changes, if it was made from
evaluated live objects.

With other builds running, a temporary release test timed the hash for
400 live objects at 17 to 506 microseconds a frame, against 10 to 16
before.

Docs: docs/target-app-parity.md, docs/gaps.md.
2026-10-14 04:56:29 +07:00
dotCooCoo 9f0a9a362b Render: objects that share an appearance keep their own stroke outlines between frames
The renderer kept the outline of a stroke under the address of the
stroke in the object's appearance, the bits of the width and the
tolerance level, and used an entry only when the entry held the object
being drawn. The slices of a gradient along or across a stroke were
kept the same way. Objects can share one appearance until one of them
changes it. Copies made by Duplicate Selection share the original's
appearance, gradients included. For objects without a gradient fill or
stroke, so do the art the renderer places for unstained symbol
instances, the copies a live repeat draws and copies made by
edit.duplicate. In each frame, the objects that share an appearance
replaced each other's entries. The renderer therefore outlined their
strokes again in every frame.

The two caches now key each entry by the address of the object as
well, and each object keeps its own outlines from frame to frame. Art
that the renderer makes again in every frame keeps the old key: symbol
art that placed_art does not keep, and the evaluations of live objects
and the art of objects with effects that their caches do not keep. The
copies in such art replace each other's entries as before.

vectorcraft-cli bench of 2,000 unstained instances of a symbol of
eight paths took 45.7 ms a frame on main and 31.1 ms with this change,
at 2880x1800 on 8 threads, and a grid repeat of the same paths, which
draws 2,000 groups, took 45.0 ms on main and 27.2 ms with this change.
The peak working set of a bench run on the instances went from 88 MiB
to 122 MiB. In the tests, each frame draws what a new renderer draws.
2026-10-14 04:56:29 +07:00
BFlat cd89392ac9
Merge pull request #1739 from KnottyDyes/squad/remote-stale-connection
MCP: reconnect closed control sockets before sending requests
2026-10-14 04:43:14 +07:00
BFlat db1d586674
Merge pull request #1768 from fahrim/fix/1728-jsonrpc-version-and-reply-id
Fix #1728: require the JSON-RPC 2.0 envelope and correlate control replies
2026-10-14 04:42:45 +07:00
BFlat 7543ef966c
Merge pull request #1764 from fahrim/fix/1727-framing-limits
Fix #1727: cap MCP framing input, batches and kept command_batch results
2026-10-14 04:42:21 +07:00
BFlat 67f131d786
Merge pull request #1797 from DWAA1660/fix/1726-control-deadlines
Fix control sockets retaining connection slots indefinitely
2026-10-14 04:41:56 +07:00
bflatastic 2366fcfd80 docs/mcp.md: the server reconnects to the app before a call when the connection was closed 2026-10-14 04:31:44 +07:00
bflatastic 7e56066847 MCP: property test for the framing, batch and envelope limits; the refusal without an id is documented (#1727, #1728) 2026-10-14 04:31:44 +07:00
bflatastic 63b0dc4c32 MCP: a connection holding bytes nobody asked for is replaced before the request is sent 2026-10-14 04:31:44 +07:00
bflatastic d3327b17a1 MCP: a control refusal without an id reports the app's reason instead of an id mismatch (#1728) 2026-10-14 04:31:44 +07:00
KnottyDyes 16366ce422 MCP: reconnect closed control sockets before sending requests
After the app restarted, the remote backend wrote the next request to
the old, closed connection and reported that the operation may have
completed, although the restarted app received nothing. Reused
connections are now checked without blocking before writing, and a
closed one is replaced first. If the app can't be reached, the error
says so and that nothing was sent. Failures after the request was
written still report uncertainty and are never replayed.

Loopback tests cover a restart between calls (the edit is applied
exactly once) and a refused reconnect. The lost-reply test from #1246
still passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSB7fMQUccqFQekksDGWpo
2026-10-14 04:31:44 +07:00