Commit Graph
4482 Commits
Author SHA1 Message Date
dotCooCoo 1cea163b11 Paragraph: Insert Kashidas justifies Arabic lines with kashidas
The reference app justifies Arabic text with kashidas, set per paragraph
with the Paragraph panel's Insert Kashidas list (None, Short, Medium,
Long, Stylistic). VectorCraft had no kashidas, so a justified Arabic
line was spaced out at its word spaces like a Latin one.

The Paragraph panel, with the Indic options shown, now has Insert
Kashidas. On a justified horizontal area type line that is shorter
than its measure, each Arabic word with joined letters gets one kashida
between its last pair of them, drawn as tatweels (U+0640) in the font
of the letter after it. The kashidas share the line's room equally,
each at most a quarter of an em (Short), half an em (Medium) or an em
(Long); a Stylistic kashida is a row of the font's own tatweels, up to
an em. The word spaces take the room the kashidas leave, so the line
keeps its width, and line breaks do not change. The letter after a
kashida advances over it, so the caret before that letter, a selection
of it and a click on it take the kashida in. None, the default, leaves
lines as they were.

In horizontal type that is not on a path, the caret in a letter drawn
with several glyphs (a letter with a vowel mark) is now placed by the
whole letter, as hit testing already measured it, instead of by its
first glyph.

text.setFormat takes kashida. The setting is saved with the paragraph
only when it is not None, paragraph styles carry it, and Reset Panel
sets it back to None.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-21 16:46:22 +07:00
dotCooCoo 3677a16474 Type: the Every-line Composer ends a line after the spaces that follow it
The composer offered a break after a dash, a Thai word or a space even when
a space came next, so it could start the next line with that space. A
leading space makes the next line wider, and the composer could score that
break better. With mixed type sizes now composed in rectangular area type,
Thai text whose words come from a Thai font broke as "สวัสดีครับ" and
" ภาษาไทยง่าย". The composer now offers only the break after the spaces,
as the single-line composer ends its lines.
2026-10-21 16:46:19 +07:00
dotCooCoo 9be3ddab31 Type: the Every-line Composer composes paragraphs that mix type sizes in rectangular area type
A paragraph with one character in a different type size was broken
with the Single-line Composer, even when Every-line was chosen: the
composer gave up whenever the paragraph's glyphs did not all have the
same line metrics (ascent, descent, leading, cap height, x height and
em box top).

In rectangular area type without text wrap, rows and columns included,
the width of a line does not depend on how far down the frame it is.
There the Every-line Composer now composes a paragraph whose
characters differ in line metrics (type size, font, leading or
vertical scale, or an inline graphic taller than the text), and each
line takes its height from its own text, as with the Single-line
Composer. Area type of another shape, or with text wrap, still breaks
such a paragraph with the Single-line Composer.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-21 16:46:19 +07:00
dotCooCoo ae41ddb9a5 Undo groups: library styles and field scrubs close only the group they open
Using a library's character or paragraph style, and scrubbing a
numeric field, closed their undo step with Session::end_undo_group,
which closes the open undo group of every document. An undo group open
in another document, such as one begun with edit.undoGroup.begin for
an MCP command_batch with atomic, was closed too, and that document's
next edits became undo steps of their own. When the style failed to
apply, or Escape ended the scrub, the other document's group was
canceled as well and the edits made in it were undone. Inside an undo
group already open in the same document, a scrub closed that group,
Escape undid the edits made in it before the drag, and a style that
failed to apply left the style it had added.

Session::begin_undo_scope and end_undo_scope make the edits in between
one undo step of the active document: the scope opens an undo group
there, or joins the one already open. Ending a scope closes only the
group it opened, in its own document, even when another document
became active in the meantime, and a canceled scope undoes only its
own edits, also inside a group that was already open. library.use and
the scrub use the scope, so Escape inside an open group also takes
back a scrub of the Transform panel's Shear field. edit.undoGroup.end
still closes the open group of every document.
2026-10-21 16:46:19 +07:00
dotCooCoo 5846b00da3 Actions: a recording keeps its steps when a canceled undo group drops earlier entries
When a cancel removes journal entries numbered below a recording's
start, the entries after them are numbered down. The recording's start
and the journal's hold kept the old number, so Stop Recording left out
as many of the recorded steps as there were entries removed, and the
hold no longer covered them. `Journal::remove` now moves the hold down
with the entries, and Stop Recording reads the start from the hold.

`Journal::replace` puts an entry in place of another and counts it at
its new size. A cancel uses it for the `document.activate` that takes
the place of a canceled group's entry that switched documents, so the
journal's size estimate does not keep the old entry's size.
2026-10-21 16:46:18 +07:00
dotCooCoo 1565c96123 Undo groups: a cancel keeps the journal entries made outside the canceled groups
A cancel cut the journal back to where the first canceled group began.
That also removed the commands run in other documents after it began,
which the cancel does not undo, so a replay of the journal lost them.

Each undo group now keeps the numbers of its own journal entries: the
commands that start and end in its document and the commands that edit
it. A cancel removes only those entries, and the later entries are
numbered down to close the gaps (`Journal::remove`). An entry of a
canceled group that also switched to another document, such as a batch
that draws and then activates another document, is replaced by a
`document.activate` of that document, so the commands after it replay
in the same document.
2026-10-21 16:46:18 +07:00
dotCooCoo 1613ef99f6 Undo groups: a cancel drops the edits of every canceled group from the journal
Session::end_undo_group with cancel cancels the open undo group of
every document. edit.undoGroup.end with cancel calls it, and so do a
field scrub ended with Escape and a library style that fails to apply.
The journal was then cut back to where the group of the last document
in the document list began, not to where the first of the canceled
groups began. When the group that began first belonged to an earlier
document, the journal kept the commands of that group's edits, which
the cancel had undone. After a scrub ended with Escape, which journals
no end command, replaying the journal drew those edits again.

Session::end_undo_group now cuts the journal back to the earliest start
of the groups it cancels.
2026-10-21 16:46:18 +07:00
dotCooCoo 819efd465f EPS import: a PostScript error shows what its name means in the interface language
An EPS or PostScript file that stops on a PostScript error named the
error, such as typecheck or rangecheck, with the operator and the
procedure, but did not say what the name means in any language.

The warning or error now gives the name as PostScript spells it,
followed by VectorCraft's own wording of what it means in the reader:
"a PostScript error (typecheck in `add`; a value has the wrong type for
the operator)". vectorcraft_eps::POSTSCRIPT_ERRORS lists the 14 names
the reader raises other than undefined, with their meanings, and a
test checks that each of those names has one. Each meaning and the
new message shapes have a row in every catalog. i18n::message now
translates placeholder values five levels deep instead of three, which
the status bar's line for an EPS file that can't be opened needs.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-21 16:46:17 +07:00
dotCooCoo d55a5245fb Graphs: column designs show each column's value where their type has % and two digits
The reference app's graphs help describes adding totals to a column
design: type in the design holds a percent sign followed by two digits.
VectorCraft drew that type as typed, stretched with the design.

Type in a column design that holds a % followed by two digits now shows
the value of each column or stacked segment in place of the % and the
two digits. The first digit is the least number of places before the
decimal point, with zeros added in front (0 for as many as the value
needs), and the second is the number of places after it. Values are
rounded half away from zero. The type keeps its own size and moves to
where the design's copy puts the point it is set from. A repeating
design shows it once per column, where a whole copy ending at the value
end puts it, and Chop Design does not cut it. A sliding design moves it
with the part of the design on its side of the guide. Type inside a
group that clips or has its own opacity, blend mode, appearance or
opacity mask, or inside a blend, compound shape, envelope, repeat or
intertwine, stays in the design and shows the value in each copy. Type
inside a symbol stays as typed. The legend swatch leaves the totals
out.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-21 16:46:15 +07:00
dotCooCoo 96592e9e27 Tabs: in vertical type the panel names its alignments Top-Justified and Bottom-Justified Tab and labels the position Y
The Tabs panel named its alignment buttons Left-, Center-, Right- and
Decimal-Justified Tab and labeled the position field X for every text.
The reference app's alignments for vertical text include Top-Justified
Tab and Bottom-Justified Tab, and its position field is Y for vertical
text.

In vertical type the Left-Justified button is now named Top-Justified
Tab and the Right-Justified button is named Bottom-Justified Tab, in
their tool tips and for screen readers, and the position field is
labeled Y. The reference page does not say which buttons these are;
VectorCraft's own mapping follows the line direction: a line of
vertical type runs from top to bottom, so a Left stop aligns the text
to the top and a Right stop aligns it to the bottom. Screen readers now
call every alignment button by its name; before, they read its glyph.
In horizontal type the tool tips and the X label do not change.

Docs: docs/target-app-parity.md.
2026-10-21 16:46:14 +07:00
dotCooCoo 6041bb3c62 Objects on Path: clipping paths and parts of blends, envelopes and repeats stay in place
object.objectsOnPath.attach took a path inside a compound path, blend,
envelope, repeat or Intertwine group, or a clipping path, as the path
and put it into the new Objects on Path group, so it was no longer a
part of its container. As objects, a blend's key path, a repeat's
source art, an envelope's content and a clipping path were put into
the new group the same way.
The Objects on Path tool highlighted a blend's key path or a clipping
path and attached the selection to it.

objects_on_path::held now finds the container that holds an object.
The command refuses such a path or object with a reason, and the tool
does not highlight it or attach to it. A member of a compound path
given as an object still attaches as its whole compound path. The four
new messages have rows in every complete-message catalog.

Docs: docs/mcp.md (Objects on Path).
2026-10-21 16:46:13 +07:00
dotCooCoo 54ee0b6251 Swatches: a swatch made from a color the app shows keeps that color's model in a CMYK document
Since #2664, swatch.new separates an RGB color into CMYK through the
color settings in a CMYK document unless keepModel is given. The Color
Guide, Color Themes and Recolor Artwork send keepModel when they save
colors they show, but the Color panel menu's Create New Swatch, the
Swatches panel and the Gradient panel did not. An RGB object's color
saved from them became a CMYK swatch, and a gradient's RGB stops
became CMYK stops.

These now send keepModel: Create New Swatch, an Alt-click on New
Swatch, the New Swatch dialog for a gradient, a Fill or Stroke proxy
or the Gradient panel's thumbnail dropped on the Swatches panel or
Alt-dropped on a swatch of its kind, and the Gradient panel's Add to
Swatches and Save to Swatches. A new swatch_paint_params returns
paint_params with keepModel: true added for an unlinked color or a
gradient. swatch.new without keepModel still converts RGB input.

Docs: docs/target-app-parity.md.
2026-10-21 16:46:13 +07:00
dotCooCoo 67448e9245 Live Paint: Merge keeps the group's lock, visibility, mask and graphic style link
Object › Live Paint › Merge built the merged group anew and copied
only the bottom Live Paint group's name, opacity and blend mode, so
its lock, visibility, appearance, opacity mask, graphic style link and
other properties were lost. Make with a Live Paint group selected, and
a Live Paint Bucket click on a selected path beside a selected Live
Paint group, made a new group with a new id instead, and every face
took the fill of its paths again.

Merge now starts from the bottom Live Paint group, as Gap Options
does, and replaces only its faces, edges and paths and its Gap
Options. Make, and livePaint.fill with ids when the point is outside
that group's faces, add the other objects to the bottom Live Paint
group among them as Merge does.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-21 16:46:12 +07:00
dotCooCoo 0ec23d90bc Live Paint: the Live Paint tools skip hidden edges
An edge hidden on its own (for example with Object > Hide) while its
Live Paint group stays visible could still be highlighted, selected or
stroked, and a hidden edge nearer the pointer kept a visible one from
being picked.

lp_part_at now takes the nearest visible edge (visible_edge_near), inside
and outside the faces, for the hover, the Live Paint Selection click and
the Bucket's Shift-click. The Bucket's Shift-click sends nothing where no
edge is highlighted, and livePaint.strokeEdge also skips hidden edges, so
the Bucket strokes the edge it highlights.
2026-10-21 16:46:11 +07:00
dotCooCoo ed93963d73 Live Paint: a Live Paint Selection click outside the faces selects the edge it highlights
With Select Fills off, or with Alt held, the Live Paint Selection tool
highlighted an edge near the pointer even when the pointer was outside
every face, but a click there looked only for a face under the pointer
and cleared the selection. Outside the faces, the highlight came from
the front-most Live Paint group with an edge in reach, not from the
nearest edge, and it included hidden groups. The Live Paint Bucket's
Shift highlight and Shift-click used the same search.

The hover, the Live Paint Selection click and the Bucket's Shift-click
now call one function. Outside every face it picks the edge nearest
the pointer, within 4 screen pixels, among the visible Live Paint
groups. A click selects or strokes the edge that the highlight shows.

This change follows up #3052.
2026-10-21 16:46:11 +07:00
Jürg Lehni 3f729b6481 FilePick filters own their text, so a dialog's file types can be made at run time: FileFilter is (String, Vec<String>), FilePick::filter makes one from literals and FilePick::filters converts the engine's lists; the macOS save panel, the Linux portal and the rfd dialogs take the owned lists. No behaviour changes. 2026-10-21 16:46:10 +07:00
BFlat 5b77e422ab Merge pull request #3181 from danngreen/feat/distribute-with-guides
Distribute: selected ruler guides are spaced with the objects
2026-10-21 16:19:52 +07:00
BFlat bc441d102e Merge pull request #3246 from addibble/fix/clip-path-outline-hit
G1: selection tools take a clipping path on its outline
2026-10-21 16:19:37 +07:00
BFlat 6ee04f3805 Merge pull request #3215 from ashsajal1/main
G1: a move over a panel waits and resumes on the canvas (#2826)
2026-10-21 16:19:19 +07:00
BFlat a2abc1316b Merge pull request #3265 from dotCooCoo/fix/tabs-selection-follows-sorted-stop
Tabs: a stop moved past another stays selected
2026-10-21 16:19:01 +07:00
BFlat 61f278f894 Merge pull request #3299 from dotCooCoo/fix/data-set-status-clears-after-applying
Variables: the nothing-applied message clears when a later data set applies
2026-10-21 16:18:46 +07:00
BFlat a09828188a Merge pull request #3335 from dotCooCoo/feat/pattern-scale-slider
Brush Options: Pattern Scale and Art Width have sliders
2026-10-21 16:18:29 +07:00
BFlat 906da779ff Merge pull request #3340 from dotCooCoo/fix/edit-within-zero
Pencil: Edit selected paths within 0 turns editing off
2026-10-21 16:18:13 +07:00
BFlat f0667469e5 Merge pull request #3189 from AJMakesStuff/3179---Envelope-Distort
(3179) Envelope Distort
2026-10-21 16:17:56 +07:00
BFlat 69e73564d2 Merge pull request #3257 from FractalEncrypt/fix/polygon-star-counts
Persist Polygon and Star counts after exact-size placement
2026-10-21 16:17:38 +07:00
BFlat e2547c933d Merge pull request #3271 from FractalEncrypt/fix/live-trace-bounds
Exclude retained trace bitmaps from result bounds and highlights
2026-10-21 16:17:22 +07:00
BFlat d47346b237 Merge pull request #3316 from odysseexp/fix_svg_gradients
SVG import: support feBlend filter primitive and transparent backdrops
2026-10-21 16:17:05 +07:00
BFlat 94e3cb7bf7 Merge pull request #3333 from dotCooCoo/fix/brush-hueshift-default-key
Brushes: Hue Shift without a key uses the most-used art color
2026-10-21 16:16:53 +07:00
BFlat b29f1394c2 Merge pull request #3330 from dotCooCoo/feat/docinfo-brushes
Document Info: a Brushes category lists the document's brushes
2026-10-21 16:16:41 +07:00
BFlat c2313099d4 Merge pull request #3313 from dotCooCoo/feat/view-show-live-paint-gaps
View: Show Live Paint Gaps highlights the gaps of the selected Live Paint groups
2026-10-21 16:16:29 +07:00
BFlat f8dbbc25b2 Merge pull request #3334 from dotCooCoo/feat/brush-select-unused
Brushes: Select All Unused selects the brushes the document does not use
2026-10-21 16:16:11 +07:00
BFlat 7a1e7c1955 Merge pull request #3337 from dotCooCoo/fix/select-bristle-by-kind
Select: Bristle Brush Strokes selects objects by their brush's type
2026-10-21 16:15:54 +07:00
BFlat 6f3df95ad4 Merge pull request #3339 from dotCooCoo/fix/artboards-panel-convert-to-artboards
Artboards: the panel menu's Convert to Artboards converts the selected objects
2026-10-21 16:15:42 +07:00
BFlat 1b5d8abdcf Merge pull request #3324 from dotCooCoo/feat/new-document-without-dialog-key
File: Cmd+Alt+N makes a new document without the New Document dialog
2026-10-21 16:15:31 +07:00
BFlat 8d1bbbf35e Merge pull request #3341 from dotCooCoo/feat/appearance-new-fill-and-stroke-keys
Appearance: Cmd+/ adds a new fill and Cmd+Alt+/ adds a new stroke
2026-10-21 16:15:14 +07:00
BFlat 96fe96de77 Merge pull request #3205 from nrq6993-a11y/stream/vc-feat1-g1-anchor-point-move-measure
G1: Anchor Point tool shows move distance in measurement labels while Shift-dragging
2026-10-21 16:14:58 +07:00
BFlat 3e93c71772 Merge pull request #3217 from nrq6993-a11y/stream/vc-feat1-g1-reshape-offset-readout
G1: Reshape tool shows Smart Guide offset readouts while reshaping paths
2026-10-21 16:14:42 +07:00
BFlat 52e4fa52c5 Merge pull request #3218 from nrq6993-a11y/stream/vc-feat1-g1-place-file-marquee-size
G1: File Place cursor shows placed size in measurement labels while dragging
2026-10-21 16:14:26 +07:00
BFlat 410fa6b84f Merge pull request #3182 from nrq6993-a11y/stream/vc-feat1-g1-type-area-measure
G1: Area type drag shows width and height in measurement labels
2026-10-21 16:14:08 +07:00
BFlat d65094ac5d Merge pull request #3208 from Taha-Mahmoodi/fix/2820-image-cache-thrash
Fix #2820: images drawn small redraw from their kept mips while being moved
2026-10-21 16:13:49 +07:00
BFlat 47c0374858 Merge pull request #3270 from Silvertree2010/fix/3241-text-stroke
Keep stroke weight in document space on transformed type
2026-10-21 16:13:32 +07:00
BFlat 5fdea8749c Merge pull request #3252 from MarekSpodBiedry/issue-3234
Keep the swatch link when blending two tints of one swatch (#3234)
2026-10-21 16:13:14 +07:00
BFlat 649d3c83db Merge pull request #3200 from Taha-Mahmoodi/fix/3198-fps-readout
Fix #3198: the status bar's fps is the rate the window draws at, not an average of idle bursts
2026-10-21 16:12:55 +07:00
BFlat c2f2a49a32 Merge pull request #3206 from Taha-Mahmoodi/fix/3188-clip-arrange
Fix #3188: arranging art inside a clipping mask keeps the clipping path at the bottom
2026-10-21 16:12:43 +07:00
BFlat 5a12b47e4d Merge pull request #3269 from MiQ71/fix/issue-3219-show-hide-guides
Fix Show and Hide Guides (#3219)
2026-10-21 16:12:27 +07:00
BFlat 9674310a58 Merge pull request #3307 from jordanlemcc-ship-it/fix/hook-ime-backspace-reinject-order
Fix lost diacritics with keyboard-hook IMEs (UniKey/OpenKey)
2026-10-21 16:12:09 +07:00
BFlat b9a37cbd05 Merge pull request #3321 from dotCooCoo/fix/links-on-another-mapped-share-are-not-followed
Links: a link on a drive mapped to another share is not followed
2026-10-21 16:11:54 +07:00
BFlat 69890c37ff Merge pull request #3344 from dotCooCoo/fix/save-for-web-private-preview-folder
Save for Web: browser previews go only to a folder the current user owns
2026-10-21 16:11:38 +07:00
Dan Green e8bd0ac410 Distribute: selected ruler guides are spaced with the objects (#1597 in part)
Distribute Objects and Distribute Spacing space the selected ruler
guides across the axis (vertical ones for the horizontal options,
horizontal ones for the vertical options) together with the selected
objects, as items with no size. Guides along the axis stay where they
are. Two or more guides selected without objects are enough, and the
Align panel's Distribute buttons are enabled for them. With Align to
Artboard, the outermost items go to the artboard's edges.

A key guide (select.key {guide}) is one of those items. With a spacing,
it stays put and the other items are spaced from it, as from a key
object with no size; a key guide along the axis leaves everything where
it is. The Align panel sends the Distribute Spacing value with both
rows of Distribute buttons while a guide is the key, and enables the
value for it.
2026-10-21 15:44:27 +07:00
Drew DibbleandClaude Opus 5.5 5fd553cc8b G1: loaded text, double-clicks and guide release take what is drawn
The selection tools' clipping-path outline hit was also used where the
Selection and Direct Selection tools act on what is drawn rather than
select. Near a layer's clipping mask edge, with art drawn over it:
loaded text threaded into the unpainted mask instead of the art, a
double-click on type entered isolation on the mask instead of editing
the type (Selection) or did nothing (Direct Selection), and Cmd/Ctrl+
Shift double-click could not release a guide path there. These now
take the ordinary hit (ToolContext::hit_options); a press or hover
that selects keeps the outline hit.

Tests (each fails with the selection hit options):
select::tests::loaded_text_threads_into_the_art_on_a_layer_masks_edge,
a_double_click_on_type_at_a_layer_masks_edge_edits_it (Selection and
Direct Selection) and a_guide_on_a_layer_masks_edge_is_released, over
the new testutil helpers add_masked_layer and
doc_with_type_on_a_layer_masks_edge. Recorded in
docs/target-app-parity.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 15:44:27 +07:00