Commit Graph
2484 Commits
Author SHA1 Message Date
dotCooCoo 3405620151 Docs: update marquee feature status and revision history 2026-11-08 23:51:23 +07:00
dotCooCoo 6d87514055 Selection: show marquee rules and offer enclosed and directional modes 2026-11-08 23:51:12 +07:00
dotCooCoo 368f1c1f83 Document Info: Pattern Objects lists the objects painted with patterns
Squashed from #5220 onto main.
2026-11-08 23:50:38 +07:00
dotCooCoo 8c7e7041fd Title bar: the app bar shows no brand mark under the desktop's own frame
On Linux, when the desktop draws the window's frame (KDE, Sway,
Hyprland, COSMIC and X11 sessions), the app bar painted the brand mark
under the desktop's title bar unless System Title Bar was on. The mark
followed the live preference instead of the frame the window has.

The desktop app now records the frame it opened the window with
(VectorcraftApp::desktop_frame), and system_title_bar reads it. A
change to System Title Bar during a session no longer shows or hides
the mark; it changes with the frame at the next start. Embedders
without a window of their own keep the mark.
2026-11-08 23:50:37 +07:00
dotCooCoo 2049082ce7 Drawing tools: Escape on an arc, spiral or grid drag puts back the options it changed
Squashed from #5202 onto main.
2026-11-08 23:50:37 +07:00
dotCooCoo 023ee90657 Perspective grid: Snap to Grid moves objects at every depth by one plane offset
With Snap to Grid on, a move of objects on one perspective plane at
different depths applied the snap correction only to the objects at the
first object's depth, and every other object moved by the pointer's
offset read at its own depth. Those objects left the grid and lost their
spacing.

Every object on the first object's plane now moves by that object's
plane offset plus the correction, so the edge of the joint bounds nearer
a gridline lands on it and the objects keep their spacing. A selection
at one depth snaps as before, and objects on other planes keep their own
offset.
2026-11-08 23:50:37 +07:00
dotCooCoo eaa42374a3 Shortcuts: F2, F3 and F4 also cut, copy and paste on Windows and Linux
Squashed from #5041 onto main.
2026-11-08 23:50:36 +07:00
dotCooCoo 246b8ed8cd Type: typed +, # and > start lists with a hollow circle, square and dash
Typed list detection gave every bulleted trigger the filled dot. Now
`*` and a space start a list with the filled dot (•), `+` with the
hollow circle (◦), `#` with the filled square (▪) and `>` with the dash
(–), each one of the existing Bullets presets. `1.`, `1)`, `A.` and `A)`
start the same numbered lists as before.

Only the list a typed trigger starts changes. Lists already in a
document keep their bullets, pasted triggers start no list, and Undo
and Redo of a typed list work as before.

Docs: docs/mcp.md (typed list paragraph), docs/target-app-parity.md.
2026-11-08 23:50:35 +07:00
dotCooCoo c1164f271f Type: Start Lists From Typed Markers turns typed list detection off
Preferences > Type gets Start Lists From Typed Markers (`typedLists`, on
by default). With it off, a list marker and a space typed with the Type
tool at the start of a paragraph (`1. `, `1) `, `A. `, `A) `, `* `,
`# `, `> `, `+ `) stay as text and start no list. `text.startList`,
`text.setFormat {list}` and the Bullets and Numbered List buttons work
as before.

The preference reaches the Type tool through `ToolContext::typed_lists`.
The label has a row in every catalog.
2026-11-08 23:50:33 +07:00
dotCooCoo 3f4d1a2512 Render: the decoded-image cache can grow to a quarter of installed memory on every desktop OS
Squashed from #5034 onto main.
2026-11-08 23:50:32 +07:00
dotCooCoo be391e541a Type: Type Orientation is a setting of the whole thread
Squashed from #5027 onto main.
2026-11-08 23:50:32 +07:00
dotCooCoo 6cd4171fbd Paragraph: Reset Panel also resets the Mojikumi Set and tab stops
Squashed from #5025 onto main.
2026-11-08 23:50:31 +07:00
dotCooCoo a33d6a6ec8 Canvas: a General option makes Cmd/Ctrl-wheel scroll the canvas sideways
Squashed from #5023 onto main.
2026-11-08 23:50:31 +07:00
dotCooCoo 0d1a8b6ffa Actions: Batch Save and Close over data sets counts only saves that make the file the document's own
A recorded save in the template format writes a copy and leaves the
document's file as it was, so it no longer clears the warning or the
refusals. A recorded Save or Save As with its own path gives a never-saved,
converted or restored document its file before Save and Close, so the
batch runs and warns that every data set saves over that one file.
2026-11-08 23:50:30 +07:00
dotCooCoo 427029def6 Actions: Batch Save and Close over data sets follows a redirected Save step
With a destination folder and Override Save on, a recorded Save or Save As
step writes each data set to its own file in that folder and makes that
file the document's own, so Save and Close then saves each data set to its
own file. The batch no longer warns that every data set saves over one
file in that case, and no longer refuses a document that was never saved,
converted or restored, since the recorded step gives it a file first.
2026-11-08 23:50:30 +07:00
dotCooCoo 64cfa8b553 Actions: Batch over data sets warns about PDF, EPS, DXF and web exports and refuses Save and Close on converted documents
A recorded PDF, EPS, DXF, Save for Web or Save for Office Documents step
that gives a `path` writes the same file for every data set, because the
batch does not point these steps into the destination folder. The one-file
rule now counts them, so the dialog and `action.batch` warn.

Save and Close over the data sets of a document converted from an older
version or restored by Data Recovery is refused with "save the document
with Save As before Save and Close" before a data set is selected. Saving
such a document does not write its file, so the batch reported success
without saving anything. The message has a row in each of the 26 catalogs.
2026-11-08 23:50:29 +07:00
dotCooCoo 2ad0268388 Actions: Batch over data sets keeps Save and Close and warns when each data set saves over one file
Batch… with a Data Sets source offers None, Save and Close and Folder again.
Save and Close saves the open document over its own file after each data
set. When every data set saves over the same file, the dialog shows a
warning and `action.batch` returns `warning`: with Save and Close, or with
a recorded step that writes a file and that the batch does not point into
the destination folder. A Save step without a path counts when it writes
the document's own file; Save As, Save a Copy, Save as Template and Export
steps count when they give a path; Export for Screens counts when its
folder, given or from its preset, names a folder. Save and Close over the
data sets of a document that was never saved is refused with "the
document has never been saved" before a data set is selected.
2026-11-08 23:50:28 +07:00
dotCooCoo 7fd287238f Actions: Batch refuses Save and Close over data sets instead of saving each one over the open document
Data sets share one open document, so Save and Close saved every data set
over the same file and only the last one survived. action.batch now
returns an error for source datasets with destination saveAndClose before
it selects a data set or runs a step, and the Batch dialog offers None and
Folder for a Data Sets source, moving a Save and Close choice to Folder.
2026-11-08 23:50:27 +07:00
dotCooCoo f118cf6aa6 Image Trace: Expand can group the traced paths by color
Squashed from #4058 onto main.
2026-11-08 23:50:26 +07:00
dotCooCoo 7c813e7171 Document: document.compare measures how far an artboard's render is from an image
Squashed from #3995 onto main.
2026-11-08 23:50:25 +07:00
dotCooCoo 8888128822 Image Trace: Flat Logo can trace only the named colors, without a white of its own
Squashed from #3946 onto main.
2026-11-08 23:50:25 +07:00
dotCooCoo 4b4a97ce1e Variables: a second text variable on one type object is refused
Squashed from #2642 onto main.
2026-11-08 23:50:24 +07:00
FredApps 65adb7b211 Merge pull request #2480 from vangel76/fix/2414-svg-classes
SVG: keep CSS classes of imported elements on export (#2414)
2026-11-08 17:01:22 +01:00
dotCooCoo 9ebf258b7c Docs: list the bulk memory fuel line beside the exporter plug-ins entry 2026-11-08 13:23:35 +07:00
dotCooCoo 1bcd19e6b6 Plug-ins: a run keeps the fuel left in a slice at a bulk memory instruction
wasmi pauses a run before an instruction the store's fuel can't pay for
and charges nothing for it. A memory.fill or memory.copy is charged for
its whole length, so a pause can leave up to that cost in the store.
Plugin::call replaced the store's fuel with the next slice and compared
the instruction's cost with the rest of the budget alone, so a run whose
budget covered its work could end with Limit("instruction budget").

Each pause now tops the store up to the slice size, keeping what it
held, and ends the run only when the store's fuel and the rest of the
budget together can't pay for the next instruction.
2026-11-08 13:23:34 +07:00
dotCooCoo c85b286994 .ai import: Scatter brushes in Tints open in Tints, and brush art drops its frame
A Scatter brush in a .ai or EPS file's editing data always came into
the Brushes panel keeping its art's colors. A Scatter brush whose last
15 numbers start with 1 now opens in Tints, and one whose numbers start
with 0 keeps its art's colors. Other values are not read and keep the
art's colors. The field before the art's name does not decide it: an
object whose first field is 1 is read by its 15 numbers, and a Scatter
object with 8 numbers is still left out.

A brush's art usually comes in a group after its frame, an unpainted
rectangle around the art. The reader kept that rectangle as part of
the brush's art. It now leaves out a frame at the art's bounds (within
1/1000 pt): an unnamed, unpainted rectangle with its four corners in
order, closed, or open with its first corner again at the end, and no
settings of its own (shown, unlocked, at full opacity in Normal, with
no effects, note or other attributes). A wider frame stays in the art,
and so do a path that crosses the corners, an open path along three
sides and a rectangle with settings of its own. A group left with one
object becomes that object, unless it has a state or look of its own
(hidden, locked, isolated, knockout, an opacity, a blend mode, a name,
a mask, a note or another setting, or an appearance); then it stays a
group without the frame. The rule applies to every brush art section.

Existing files with such brushes now open with Tints and without the
extra frame path in the brush's art. With an exact frame, the pieces
that a Scatter or Art brush draws along a path stay where they were.

The Scatter, Art and Pattern brush colorization options, Tints among
them, are described on pages 281-282 of
https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf#page=281

Tests cover Scatter brushes whose numbers start with 1 and 0, with a
first field of 1 and 2, and an 8-number object. They cover frames kept
or left out (exact, wider, open, filled, named, crossing the corners,
along three sides, with a note, Show Center or hidden), a group's own
state and note kept, and the pieces a Scatter and an Art brush draw
along a path with and without the frame. The import fuzz test reads
brush art after frames of any corners and paint.
2026-11-08 13:23:23 +07:00
dotCooCoo 960bc64197 Plug-ins: recording a grant keeps the grants file entries it can't read
A grants.json entry this version can't use (a permission it doesn't know,
an invalid id) is now skipped on its own instead of dropping every grant in
the file, and recording or removing a grant writes it back unchanged,
except an entry for that plug-in. A grants.json that is too large, can't be
read or is not a JSON list is left as it is, and a rewrite that would pass
the 1 MiB read limit is written compactly, or not at all.
2026-11-08 13:21:16 +07:00
dotCooCoo b071fb109c Plug-ins: the host import fuzz test reaches vc_log's text checks
The fuzz test for hostile host import arguments drew vc_log's level from every i32, so almost
every call stopped at the level check and never read memory. Levels are now also drawn from -1
to 4. The module docs, a test doc comment and docs/plugins.md now say that with an unknown total
each change of done is passed on.
2026-11-08 13:21:16 +07:00
dotCooCoo 1c676fc372 Plug-ins: API level 2 lets a plug-in report progress, see Cancel and write log lines
A plug-in whose manifest gives "api": 2 and lists the new `progress`
permission may import three host functions from module `env`:
vc_progress(done, total), vc_cancelled() -> i32 and
vc_log(level, ptr, len) -> i32. Installing checks every import and
refuses, by name, one the host doesn't offer, one with another type and
one the manifest doesn't allow.

- Progress is clamped and passed on once per tenth of a percent (past
  2,000 reports, once per 1,000 changes). A heavy filter's progress
  window and the export's progress window show it as a percentage bar,
  and their Cancel makes vc_cancelled return 1. A live effect's progress
  is not shown.
- A run cancelled before it returns ends as cancelled, and one that
  returns after its time budget ran out ends at that budget, so what a
  plug-in returns after it sees vc_cancelled changes nothing. This also
  applies to a plug-in of API level 1 that finishes after a Cancel or
  past its time budget.
- Log text must lie in the module's memory and be UTF-8. Lines go to the
  app's log as "<id>: <text>" with hidden characters escaped, at most
  1,024 characters a line, 200 lines and 32 KiB a run, and the host reads
  at most 1 MiB of log text a run.
- plugin.list and plugin.info give the manifest's `api`; the install
  prompt names the new permission in plain words.

Docs: docs/plugins.md, docs/mcp.md, docs/target-app-parity.md.
2026-11-08 13:21:15 +07:00
dotCooCoo c8701a18cd .ai import: legacy binding punctuation opens as Burasagari's Force setting
The legacy reader skipped `bindrepeat Tg` (section 15.3.4 of the version 7
file format specification). It now reads the binding punctuation bit:
1 and 3 set the paragraph's Burasagari to Force, and 0 and 2 set it to
none, which is VectorCraft's own mapping. The value is modal, lasts into
the next text object, and an operand other than 0 to 3 leaves it as it
was. The repeat character, `TG`, `Xu` and `Tu` are still skipped.

docs/mcp.md and the `ParaStyle::burasagari` doc comment name the legacy
`.ai` case as the exception to imported text keeping `none`.
2026-11-08 13:21:08 +07:00
dotCooCoo b7203714be Links: Relink can replace all instances, the folder's missing links or each link separately
The Links panel's Relink... button and menu item open a Relink dialog
(ui.relinkDialog) with a Replace menu. Selected Instances relinks the
selected images and placed documents, as Relink did before. All
Instances also relinks every other object in the layers linked to the
same file. Missing Links from This Folder relinks the selection to the
chosen file, then every other missing link to the file of its name in
that file's folder, and the status bar names the files not there. Each
Link Separately asks for a file for each selected link in turn, one OK
each, and relinks them all at the last one: the dialog has one row per
linked file, and each selected embedded image gets a row of its own.
Every choice is one undo step.

links.relink takes scope: selected (default), all, missingInFolder
(with path) or each (with paths: [{ids, path}] in place of ids, path
and folder). A scope or paths that doesn't fit is an error and relinks
nothing. With copy, every file is read and checked before any is
copied, copies made before a failed copy are removed, and
missingInFolder looks for missing images only.

Docs: params text, docs/mcp.md, docs/control-protocol.md,
docs/target-app-parity.md. Catalog rows for the new labels and messages
in every catalog.
2026-11-08 13:21:01 +07:00
dotCooCoo cd95a8a6d1 Swatches: linked paints find their swatch in one name map
Add Used Colors, New Color Group from artwork and Select All Unused
looked up each linked paint's swatch by searching every swatch. They now
build one map of swatch names first, with the built-in Registration
swatch, and the first swatch of each name as before.

New tests and test cases: New Color Group from artwork with repeated,
near-equal and -0.0 colors, a Registration link and names that need
numbering, and New Swatch and Edit Color Group with near-equal and
-0.0 colors; each checks the names, their order and one undo step.
2026-11-08 13:20:41 +07:00
dotCooCoo 346f80dcc7 Swatches: New Swatch, New Color Group and Edit Color Group name swatches from one set of taken names
New Swatch with several colors, New Color Group and Edit Color Group's
extra colors searched every swatch and group for each candidate name
of each new swatch. They now build the set of taken names once
(FreeSwatchNames, with take_new for New Swatch's default names and
insert for the group name and the group's own swatches) and keep it up
to date as they add swatches. The names they give are unchanged.
2026-11-08 13:20:41 +07:00
dotCooCoo 39f36dc4ba Swatches: Add Used Colors and Select All Unused look colors up in hash sets
swatch.addUsedColors and swatch.unused searched lists inside loops over
colors and swatches, checking each color against every color or swatch
before it. They now keep the same ordered lists and use hash sets and
maps for membership and lookup. A color's key is its model and the bits
of its components with -0.0 read as 0.0, so the key agrees with `==`; a
color with a NaN component matches nothing, as before.

Add Used Colors names its new swatches through
Document::free_swatch_names, which collects the taken swatch and color
group names once and gives the same names free_swatch_name gives when the
swatches are added one at a time. The added swatches, their names, order
and links, the undo step, and the swatches Select All Unused lists are
unchanged.
2026-11-08 13:20:41 +07:00
dotCooCoo 874ae85fab Plug-ins: manifests list permissions, the user grants them at install, and the host enforces the grant
A manifest may list `permissions` (doc.read, doc.write, state,
effect.render, export.write) and an `api` level. doc.read, doc.write
and state follow the capability model proposed in
storytold/photocraft#662; the other names are VectorCraft's own. An
unknown, reserved, duplicate or wrong-kind name is refused by name. A
manifest without the key gets the permissions its kind implies and
installs as before.

- Install Plug-in... and File > Open show the name, id, version, file
  SHA-256 and each permission in plain words, with control and
  bidirectional characters escaped and each string cut to 64
  characters. OK installs the module only if it still has the SHA-256
  shown; Cancel installs nothing. The heavy-budget prompt and the
  status line show the escaped name too, and OK keeps the `replace` it
  was given.
- `plugin.install` needs `grant` listing every permission, and
  `plugin.inspect` reports `needsApproval`. `hash` pins the module:
  one with another SHA-256 is refused. The grant is kept with the
  module's hash in the store's `grants.json`, and only that store's
  grants count while it is attached; the same set does not ask again,
  more permissions do, and `plugin.remove` forgets it.
- The Additional Plug-ins Folder and `plugin.reload` install only
  plug-ins the attached store has granted and report the others as
  needing approval. At startup the store skips an ungranted plug-in.
- A filter run without doc.read is given no objects; without state it
  neither reads nor writes `documentData`; without doc.write an output
  that changes the document is refused and the document is unchanged.

Docs: docs/plugins.md, docs/mcp.md, docs/target-app-parity.md.
2026-11-08 13:20:32 +07:00
dotCooCoo c94aec3fff Envelopes: type maps from its glyph outline bounds
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.
2026-11-08 13:20:19 +07:00
dotCooCoo 97c31e076f Shortcuts: holding a tool's single-key shortcut uses the tool until the key is let go
Holding a single-key tool shortcut becomes temporary: releasing it
returns to the previous tool (a quick tap still switches for good).
With the Pen chosen, holding S chooses Scale; letting go of S after
0.25 s or more, or after Scale was used while S was down, chooses the
Pen again. A press shorter than 0.25 s, with nothing done in between,
keeps Scale chosen as before. The 0.25 s threshold is VectorCraft's own
value. A Cmd/Ctrl click, which the selection tool takes, does not count
as using Scale. A drag of the Hand, Zoom or Rotate View tool chosen
this way counts as using it.

A key let go during a drag, with the pointer button held or with a
dialog open, goes back once that ends. A key let go while the tool
waits for its next click (the Dimension tool after its first point)
goes back once the tool finishes or is canceled. With S held from the
Pen and then V held, letting go of V returns to Scale, and letting go
of S then returns to the Pen. Choosing a tool on the toolbar or from a
menu while the key is down, the held tool included, ends the hold and
keeps that tool, and so does the window losing the keyboard.
While the preference is on, the key's repeats do not choose the tool
again.

The new General preference Hold a Tool Shortcut to Use the Tool
Temporarily (`holdToolKeys`, on by default) turns the behavior off.
With it off, every press and every repeat chooses the tool, as before.
Its label has a row in every catalog.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-08 13:20:11 +07:00
vangel76andClaude Opus 5.5 5f4982f66a Merge upstream/main into fix/2414-svg-classes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 07:18:04 +01:00
dotCooCoo c6cb5d9de0 Commands: an id that is not an object id is refused by commands that otherwise use the selection
Commands that read `id` and otherwise act on the selection, the highlighted
layer row or a new object read it with a lenient helper, so `{"id": "abc"}`,
`true` or `{}` acted on the selection instead. They now read it with
`object_param`, which refuses any value that is not a non-negative integer
with "`id` must be a whole number" under the command's name and changes
nothing; an omitted or null `id` keeps the default.

Commands: object.isolate, layer.locate, layer.releaseToLayers,
graphicStyle.new and redefine, charStyle and paraStyle new, apply and
redefine, image.info, links.info, links.goTo, links.unembed, shaper.release,
shaper.expand (whose errors now carry their own name),
transparency.editOpacityMask, transparency.viewOpacityMask, symbol.edit, the
object.blend.spine commands, path.curvature and the type commands that read
type ranges.
2026-11-08 13:17:30 +07:00
dotCooCoo 864e606505 Commands: an id that is not an object id is refused instead of acting on the selection
targets() read `id` as an object only when it was a number. Any other
value ("abc", "5", true, {}, [1]) fell through to the selection, so a
command such as css.export, whose `id` the shared check does not read,
acted on the selected objects instead of failing. targets() now reads
`ids` and `id` through named_ids(): a value that is not a non-negative
integer is an error, and an omitted or null `id` still means the
selection. A numeric string is refused, as it already is in `ids`.

effect.apply and appearance.addEffect accept the effect as `id` when
`effect` is left out; they now read their targets with that `id` set to
null, so the effect still applies to the selection as before.
2026-11-08 13:17:30 +07:00
dotCooCoo 31dd69ff4e Reference point: the locator is one Tab stop, and the arrow keys move the point
Each of the locator's nine squares was a Tab stop of its own, so Tab from
the field before it in the Transform panel, the Properties panel, the
Control bar and the dialogs that show it stopped up to nine times before
it reached the next field. The locator is now one Tab stop. While it has
the focus it shows an outline, and the arrow keys move the reference
point one square, stopping at the edges. A click on a square works as
before.

Screen readers get a radio group holding the nine squares as radio
buttons, with the current point as its value. The group is named after
the label next to the locator: Reference Point, or Pivot, Alignment,
Registration or Placement where the locator sets one of those.
2026-11-08 13:17:20 +07:00
dotCooCoo e592e8277b JavaScript: parser for ES5 scripts, with cargo xtask test262
New crate vectorcraft-js reads ECMAScript 5.1 source into a syntax tree
with source positions: a UTF-16 lexer (line and column tracking, automatic
semicolon insertion, the regexp and division goals), preprocessor lines
read as directive tokens, a recursive-descent parser to an arena tree, and
the ES5 early errors, strict mode included. Nesting deeper than
Options::max_depth (200 by default) gives a SyntaxError with its line and
column rather than overflowing the stack. Nothing runs scripts yet.

cargo xtask test262 downloads test262 at a pinned commit, checks its
SHA-256, unpacks it into the gitignored corpus/ folder and runs the ES5
syntax tests. A differential test in the engine compares the lexer with
scriptcheck on line counts, directives and unterminated literals.
2026-11-08 13:17:13 +07:00
dotCooCoo 67339a68c7 3D and Materials: a mixed checkbox shows a dash and keeps each effect's value
With several objects selected, Shading, Revolve's Cap, Keep visible surfaces
only when expanding and the other checkboxes read a setting the effects hold
with different values as off. A mixed Shading also turned off the Lighting
tab's controls.

- A mixed checkbox shows a dash with its reset dot filled; a click turns it on
  for every selected effect, and other changes leave it as each effect has it.
- A mixed Shading leaves the light settings available.
- A mixed menu choice shows its default; a mixed light color fills its reset dot.
- Add Light is unavailable while a setting of a single light is mixed.
2026-11-08 12:59:34 +07:00
dotCooCoo ba25415792 3D and Materials: Window › 3D and Materials edits the selection's 3D effect in place
The panel opens from Window › 3D and Materials (window.panel {panel:
"threeD"}). Its object types Plane, Extrude, Revolve and Inflate apply
Rotate, Extrude & Bevel, Revolve or Inflate to each selected object
without a 3D effect, and change the 3D effect of each other selected
object in place, all as one undo step. Below them are the effect
dialog's Object, Rotation and Lighting tabs with the same controls.
A changed setting goes to every selected 3D effect that takes it, as
one undo step; a drag previews and records one step on release, and a
change of selection during a drag commits it first, and so does closing
or hiding the panel. A setting the
selected effects hold with different values shows its default with its
reset dot filled. A light list edit reaches only the objects whose list
matches the first object's.

effect.setParams takes threeDType (plane, extrude, revolve, inflate):
the settings both effects take keep their values, rotation angles wrap
into -180..180 outside Revolve, and the rest start at the new effect's
defaults. Another value, or a non-3D effect at index, is refused; the
type an effect already has, with nothing else to change, records no
undo step.

Materials, shadows and ray-traced rendering are not part of this.

Docs: docs/three-d.md, docs/mcp.md, docs/effects-parity.md,
docs/target-app-parity.md.

Fixes #3672
2026-11-08 12:59:34 +07:00
dotCooCoo a71a6ca53d Fields: Tab moves from a field to the next past its spinner and preset chevron
A numeric field's up/down spinner and the chevron that opens its presets
were Tab stops, so Tab took three presses to get from one field to the
next in the Stroke, Character and Transform panels and two in a text
field with presets (Export for Screens' scale). The Transparency row's
opacity slider chevron was a Tab stop after the Opacity field too. All
three now sense clicks without taking the keyboard focus; clicking them
works as before, and Up/Down in the field still step it.

Without the chevron in the Tab order, Alt+Down (with no other modifier)
in a field with presets opens its preset list with the keyboard focus on
the chosen preset, or on the first one. Before, Alt+Down stepped such a
field down by one; Shift+Alt+Down still steps it by ten, and fields
without presets still step on Alt+Down. Enter on the focused preset picks
it and closes the list without also pressing a dialog's OK, as the combo
boxes do, and after a pick or Escape the field has the keyboard focus
again.
2026-11-08 12:59:34 +07:00
dotCooCoo 79dcf3b1ff .ai save: the editing copy keeps live effects
Squashed from #6150 onto main.
2026-11-08 12:59:34 +07:00
dotCooCoo 0723346135 PDF import: cropTo, password, colorMode and textAs reject a value of the wrong type
document.open, file.place and document.pdfInfo read cropTo (and its
older name crop), password, colorMode and textAs as strings and used
the default when a value was not a string, so "textAs": true or
"colorMode": 5 opened the file with the defaults. They now return an
error naming the param, and nothing opens or is placed. A wrong crop is
refused even when cropTo is given. document.pdfInfo also rejects a
thumbnailSize that is not a number, with or without a thumbnail.

Leaving a param out or giving null keeps its default, and an empty
password still means no password.
2026-11-08 12:59:34 +07:00
dotCooCoo cbe374df62 Text styles: loading imports once and reads the brush library once
charStyle.load, paraStyle.load and textStyle.loadAll imported the styles
into a copy of the document to see whether anything changed, then ran the
same import again inside the edit. The edit now takes the copy the first
import made. While a typing session is open the import still runs inside
the edit, because the edit ends the session first and that can change the
document.

Matching a loop of definitions parsed the document's brush library again
for every comparison, and placing brushes in use order parsed it again for
every brush. Each now reads it once.

The params text and docs/mcp.md now say that the symbols and brushes the
patterns use come along, that `skipped` lists names in the file, and that
charStyle.load and paraStyle.load list only the styles of their own kind.
2026-11-08 12:59:34 +07:00
dotCooCoo 8f14e2f0ec Text styles: loading skips a style whose sizes or scales are out of range
A loaded character style kept a size, scale, leading, shift or stroke width that
charStyle.new refuses. Such a style is now skipped and listed in the reply's skipped names.
2026-11-08 12:59:33 +07:00
dotCooCoo dfb507b7b7 Text styles: a loaded style's pattern brings the styles of either kind its type uses
Load Character Styles and Load Paragraph Styles left out every style of the other kind,
so a pattern that a loaded style paints with kept a reference to a paragraph or character
style the document did not get. The styles of the kind not loaded now come along when such a
pattern or symbol sets type in them, renamed like any other.

The order in which styles, symbols and patterns are placed is worked out from one graph of
their uses instead of scanning every definition left in each round.
2026-11-08 12:59:33 +07:00