Commit Graph
14258 Commits
Author SHA1 Message Date
BFlat bb2322c568 Merge pull request #7678 from dotCooCoo/fix/optical-keeps-tsume
Type: Optical kerning keeps the space Tsume takes out
2026-11-15 08:24:19 +07:00
BFlat 905eb30384 Merge pull request #7716 from dotCooCoo/fix/svg-import-budget-shared
SVG import: bound selector matching and share the limits with linked and embedded SVGs
2026-11-15 08:24:10 +07:00
BFlat 69a9df31fe Merge pull request #7700 from dotCooCoo/fix/svg-clip-use-both-clips
SVG import: a use in a clip path clips by its own clip path and its text's
2026-11-15 08:24:03 +07:00
BFlat c6a085f20e Merge pull request #7704 from dotCooCoo/fix/perspective-repeated-ids
Perspective: a repeated id attaches, moves or transforms an object once
2026-11-15 08:23:55 +07:00
BFlat c18a83f028 Merge pull request #7698 from dotCooCoo/fix/save-checks-files-beside-read-file
Save: File > Save no longer writes over the file the document was read from through a file written beside the path
2026-11-15 08:23:45 +07:00
BFlat c605d0ded8 Merge pull request #7671 from dotCooCoo/fix/prefs-reset-row
Preferences: Reset Preferences shares the row of Export and Import
2026-11-15 08:23:38 +07:00
BFlat 17dfe41a7e Merge pull request #7672 from dotCooCoo/fix/export-selection-doc-save-panel
MCP: docs say document.exportSelection with options and no path still asks for the file
2026-11-15 08:23:31 +07:00
BFlat 40d1fc913a Merge pull request #7702 from dotCooCoo/fix/reverse-compound-stroke-records
Paths: Reverse Path Direction turns a compound path's own stroke width profile and pen records
2026-11-15 08:23:22 +07:00
BFlat dc870ecf9c Merge pull request #7686 from dotCooCoo/fix/export-places-symbols-in-masks
Export: symbol instances in opacity masks draw their art's live effects
2026-11-15 08:23:13 +07:00
BFlat 410bb908ce Merge pull request #7695 from dotCooCoo/fix/issue-7275-rename
Fix #7275: Rename artboards in Export for Screens by double-clicking their names
2026-11-15 08:23:04 +07:00
dotCooCoo 6c585de940 Eraser: Angle, Roundness and Size vary with Random, Pressure, Tilt, Bearing or Rotation
Eraser Tool Options gives Angle, Roundness and Size each a variation menu
(Fixed, Random, Pressure, Tilt, Bearing, Rotation) and a Variation field.
path.eraseRegion takes angleMode, roundnessMode and sizeMode with their
variations, sweeps a varying tip as a Calligraphic nib with what the pen
read along the drag, and refuses an unknown mode or a variation out of
range. The Eraser records the pen's pressure, tilt and rotation when a
mode follows them, and its tip outline at the pointer takes the angle,
roundness and size the pen gives while it erases.
2026-11-14 17:08:14 -08:00
dotCooCoo b599942e21 Fix #7524: the canvas's frame fits the display's largest texture side
The canvas rendered its art at the display's pixels per point whatever
the canvas's size, so a canvas more device pixels across than egui's
max_texture_side asked for a texture larger than the display supports.
The tiled panes did the same, and Pixel Preview allowed regions of up to
16,384 document pixels. In
canvas::panes::tests::tiled_documents_fit_their_panes_at_every_ui_scaling,
the pass where a new UI zoom takes effect sizes the screen from the
default 10,000-point viewport, and the test failed whenever the render
worker finished that frame before the next pass.

- The canvas and the tiled panes lower the frame's pixels per point when
  a side would pass max_texture_side, and draw the frame over the whole
  canvas at that lower resolution.
- Pans, scrolls and scroll bar drags move the view by whole pixels of
  that frame, so a pan still copies the pixels it moved.
- Pixel Preview shows the normal render when its region of document
  pixels is larger than max_texture_side.
- Tests that drew a canvas wider than egui's default limit of 2,048
  pixels now state the limit their display reports.
2026-11-14 17:07:48 -08:00
dotCooCoo a3cb4462af OpenType panel: checkboxes after Select All read every frame of the story
After Select All in a threaded story whose edited frame is empty, the
panel's commands style the text of every frame, but the checkbox rows,
Stylistic Sets and the East Asian items read only the empty frame. They
now read the runs of every frame, leaving out runs with no text while
any run has text, so a feature the story's text has shows checked and a
click turns it off.
2026-11-14 17:03:36 -08:00
dotCooCoo a3ac628576 .ai import: symbol instances in older files are read, so art outside the artboard opens
Files saved for versions 10 to 13 place each symbol instance with a
TransformMatrix. On the page it takes the symbol's art to the canvas
(y down); in a symbol's art, it takes the art to that symbol's space. The
reader only knew the RTransformMatrix of newer files, so any such
instance made the editing data unreadable, and the file opened from its
PDF part alone, which holds only the art on the artboards.

The reader now converts a TransformMatrix with the canvas point of art
space's origin that the template box gives. A file whose symbol
instances were the only part it could not read now opens from its
layers, with the art outside its artboards.

The symbols fuzz test also damages the same data in the older form.

Docs: docs/target-app-parity.md, docs/file-format-parity.md.
2026-11-14 17:03:22 -08:00
dotCooCoo 7d205368e8 PDF export: Convert to Destination converts tagged images to sRGB
With Output > Convert to Destination and an sRGB destination, a gray or
RGB image that carries an ICC profile (a Display P3 TIFF, a PNG or JPEG
with an embedded profile) is converted from that profile to sRGB with
the color settings' rendering intent and written without the profile.
Before, it kept its own numbers and profile.

- A 16-bit image that is not resampled or recompressed stays at 16 bits
  a sample from PDF 1.5 on.
- A JPEG kept as JPEG whose samples the conversion leaves unchanged (an
  sRGB profile) keeps its JPEG data; other JPEGs are encoded again.
- A black-and-white image whose converted levels are gray is compressed
  as grayscale.
- A profile the color engine can't read is kept, with a warning.
- Without conversion, and with Preserve Numbers, images keep their
  profile as before.

IccProfile::to_srgb_pixels converts a run of pixels in one pass.
Docs: docs/mcp.md, document.exportPdf params, target-app-parity.
2026-11-14 16:59:18 -08:00
dotCooCoo e6bfe40d30 Import PDF: linked .ai pages open on their trim boxes with the bleed
With linkPages and cropTo: crop, a PDF-compatible .ai opened on its crop
boxes, while opening it as art uses its trim boxes. It now uses the trim
boxes too. A page on its trim box shows its bleed box round the
artboard, and the pages' bleed (the widest on each side) becomes the
document's bleed, as the importer sets it.
2026-11-14 16:42:06 -08:00
dotCooCoo cc7d081198 Import PDF: linked pages read the file once and keep blank pages
linkPages read and checked the whole PDF again for every page it
linked. The pages are now read from the file opened once. With
cropTo: bounding, a blank page stopped the open with "has no art to
place"; it now gets its crop box, as opening the page as art does.
2026-11-14 16:42:05 -08:00
dotCooCoo c730a1c776 Import PDF: linked pages refuse a .ai saved without PDF compatibility
linkPages refused other files but let a .ai whose page is only a
placeholder through, and each such page then showed the file's editing
data. A page read from editing data is now refused with the existing
error. The file is also hashed once for all its linked pages instead
of once per page.
2026-11-14 16:42:05 -08:00
dotCooCoo 8eded96792 PSD: transform a file's clipping path in place instead of copying it
file_clipped owns the path psdread::clipping_path reads, so it moves it
to the image's place without making a second copy of every anchor.
2026-11-14 16:39:17 -08:00
dotCooCoo 0f8b20c489 Type: glyph forms and width forms follow the OpenType rules
- A form other than the default, set with text.setRangeStyle or
  text.setFormat, takes away the characters' glyph choice, which would
  otherwise keep drawing its own glyph. "default" keeps it.
- With a width form (fwid, hwid, twid, qwid), shaping leaves out palt and
  turns kern and apkn off, and SVG output writes font-kerning: none and no
  palt, as the OpenType feature list asks. proportionalMetrics and kerning
  keep their values.
- The Glyphs panel menu counts a width form as available when a face has
  the feature in GSUB or GPOS.
- At a caret, the menu's check marks and its no-op check read all the
  text of the object, which is what the command formats. Choosing the
  current form again is not skipped when a character has a glyph choice.
2026-11-14 16:39:01 -08:00
dotCooCoo 8cba5fae0c Merge main into fix/live-paint-make-drawn-rows 2026-11-14 16:37:54 -08:00
dotCooCoo 15a6171021 Tests: texture-file tests remove their folders when they fail
The Rough Pastels and Glass texture tests make their folders with
vectorcraft_testkit::TempDir, which removes them on every exit path.
2026-11-14 16:32:18 -08:00
dotCooCoo c7aa27ec12 Transform: saved Scale and Reflect steps without a basis keep the page's axes
An action or journal entry recorded before Scale and Reflect followed the
turned bounding box has no basis value. Replayed, such a step now acts along
the page's axes, as it did when it was recorded; steps recorded now carry
basis "box" and replay along the box. The built-in Reflect Horizontal action
names basis "box", so it keeps reflecting across the box's own axis.

The symbol test that reflects a turned instance names basis "page", which
is the reflection it was written for.
2026-11-14 16:30:56 -08:00
dotCooCoo eff1500bd2 Object: a Distribute submenu runs the Align panel's distribute buttons
Object › Distribute follows Object › Align and lists Horizontal
Distribute Left, Center and Right, Vertical Distribute Top, Center and
Bottom, Horizontal Distribute Space and Vertical Distribute Space. Each
item runs the button's `align.distribute*` command, so it uses Align To,
the key object and the Distribute Spacing value, and with the Artboard
tool chosen it spaces the selected artboards.
2026-11-14 16:28:19 -08:00
dotCooCoo 8dfbe54db6 Units: a very long expression reads as nothing instead of overflowing the stack
Unit::parse took one level of recursion per + - * / sign, so a text such
as "1+1+...+1" with 200,000 terms overflowed the stack and aborted the
process. Every number field, parse_number and every command parameter read
as a length went through it.

Text with more than 128 + - * / signs now reads as nothing; text with 128
or fewer reads as before. docs/mcp.md states the limit for length strings.
The test a_very_long_expression_reads_as_nothing aborted with a stack
overflow before the change.
2026-11-14 16:26:09 -08:00
dotCooCoo e10f87818b OpenType panel: caret checkboxes read the whole text they change
At a Type tool caret the panel's commands style all of the text, but the
checkbox rows, Stylistic Sets and the H or V Style and Roman Italics
items showed only the caret's style. A click could then turn a feature
off for the whole text while most of it did not have the feature. At a
caret they now read every character of the text: a mixed row shows a
dash, a mixed item shows unchecked, and a click turns the feature on
throughout.
2026-11-14 16:24:52 -08:00
dotCooCoo 2d790aab20 Merge main into fix/graph-design-art-aligned-rows 2026-11-14 16:21:34 -08:00
dotCooCoo 67f427b2b4 Merge main into fix/ai-pen-samples-test-after-path 2026-11-14 16:21:15 -08:00
dotCooCoo d0f1346f87 Docs: record the PSD clipping path in the PSB row and the evidence list
The PSB row of docs/file-format-parity.md says the merged image comes in
clipped by the file's clipping path, as the PSD row does, and
docs/target-app-parity.md's evidence list gets a line for it.
2026-11-14 16:20:16 -08:00
BFlat 895e9493d2 Merge pull request #7693 from dotCooCoo/fix/hidden-chars-test-retry-edit
Tests: each Show Hidden Characters retry sets new text
2026-11-15 07:11:58 +07:00
dotCooCoo 89f20c45c4 SVG import: bound selector matching and share the limits with linked and embedded SVGs
The measurement that runs before the style cascade and usvg now covers
the inputs that still got past it:

- usvg matches a selector without keeping what it found, so one whose
  leftmost part matches nothing tests every set of ancestors in turn.
  The measurement counts those tests for each copy and stops at
  50,000,000; past that the SVG is refused with "the SVG's style sheets
  take too long to match". An SVG image's matching stops at what the
  SVG showing it left.
- A test counts one more for every 16 attributes a lookup scans and
  every 16 bytes of the value it compares, so many classes against a
  long class list count the work they do, in usvg's matching and in
  the import's own cascade.
- The declarations the import's own cascade keeps, once per element,
  count toward the 64 MB, including those of `:not()` rules, which usvg
  drops. Each `:not()` alternative counts as a test.
- A linked SVG file is measured before its cascade runs. The linked SVG
  files share one measurement: once they pass the limits together, the
  files after that are shown without their links. The tests a file past
  the limits made still count.
- SVG images share the element limit and the selector tests with the
  SVG showing them, as they already shared the 64 MB.

The svg_filters reader measures its SVG before the cascade too.
2026-11-14 16:10:10 -08:00
dotCooCoo af010b6dec PSD: a file's clipping path clips its merged image on Open and Place
A PSD or PSB file can name one of its saved paths (image resources 2000
to 2997) as its clipping path in resource 2999. psdread::clipping_path
reads that path in the image's pixel space, under the even-odd rule the
file format specification gives, with a subpath around the whole image
first when the initial fill record says the fill starts with all
pixels.

File > Place of such a file, and File > Open of its merged image, put
the image in a clip group of that path with no paint. file.place answers
the image's id as `image`, as for a DCS file. Replacing an image alone
in a clip group with such a file keeps the image's scale and rotation.
links.update and links.relink leave the clip group as it is. Files that
name no clipping path come in as before.
2026-11-14 15:55:34 -08:00
dotCooCoo 67fd1cd127 Merge main into feat/live-paint-bucket-multi-click 2026-11-14 15:48:45 -08:00
dotCooCoo 514ee4f81e 3D: test that the check builds a filled shape about its drawn bounds
#7569 changed the Extrude & Bevel and Inflate check so that a filled
shape's solid is built once, about the bounds the effect draws it
about, and stated that a filled shape whose solid builds about those
bounds but not about its profile's own bounds now passes. No test
covered that case.

The new test builds a group of a filled shape of 900 small curves and
a line with a 2000 pt stroke. About the shapes' own bounds, the filled
shape's solid is too complex with a repeated rounded bevel; about the
bounds that take in the stroke, its curves are sampled more coarsely
and it fits. The test asserts the first result, then that the check
passes and the solid draws. It fails when the check builds every
shape's profile about the profiles' bounds again.
2026-11-14 15:47:34 -08:00
dotCooCoo 403023f834 Effects: Rough Pastels loads its texture from an image file
The Rough Pastels dialog has a Load Texture… button, as Glass and
Texturizer have. The image's gray levels become the paper surface,
stored with the effect as `textureImage` and listed as Loaded Texture
in the Texture menu; Scaling, Relief, Light Direction and Invert act on
it as on the built-in surfaces. The dialog's Texture and Light
Direction controls are now menus instead of text fields.

effect.apply and effect.setParams read `textureImage` from a file or
take the stored string and refuse anything else, and a plug-in filter
cannot have the host read a file for it.

In the Effect Gallery's Settings, a file dialog that answers later
(the macOS sheet, the Linux dialog) now gives the texture to the values
that asked: the filter being browsed, or the stack row that asked,
found by a number each row now carries. An answer for a removed row, a
filter no longer browsed or a gallery that closed is dropped.

Params text, docs/mcp.md, docs/control-protocol.md, docs/gaps.md and
the evidence line are updated.
2026-11-14 15:45:59 -08:00
dotCooCoo 094971e3ac Type on a path: type on an open path can start before the path
Type on an open path can now start before the path's start: its start
is stored below 0, the span runs from there, and the characters whose
middle falls before the path's start are not drawn, as SVG 2 lays out
text on a path (https://www.w3.org/TR/SVG2/text.html#TextLayoutPath).
The text keeps every character, and the start bracket stays at the
path's start. Past the path's end, text that does not fit is overflow,
as for other type on a path. Type that starts on the path draws every
glyph as before.

- SVG import keeps a <textPath> start before an open path's start
  (text-anchor end or middle, or a negative startOffset) instead of
  starting the text at the path's start, which drew every character.
- SVG export writes it as a negative startOffset.
- PDF export writes only the characters drawn on the path.
- The legacy .ai and .ai editing data writers write the type from the
  path's start with the type attributes warning.
- type.pathOptions takes a start below 0 for type on an open path; on a
  closed path it is the path's start, as before. A flip starts the type
  at its start bracket. Dragging the center bracket slides it from where
  it starts, and no further before the path.
- A click at the path's start places the caret by the first drawn
  character, and the caret in the hidden characters stands at the
  path's start. The highlight of substituted fonts and glyphs leaves
  them out.
- Release Type on a Path keeps the first drawn glyph where it was
  drawn, with the hidden characters before it.
- The Touch Type tool shows no box for a hidden character, and
  text.touchType returns an error for one. A character with a drawn
  glyph is changed by that glyph.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-14 15:44:12 -08:00
dotCooCoo 1d64e0f9ef Type: the harakat Tsume test also compares advances
With Noto Sans Arabic the fatha's ink reaches past the first letter's
left edge, so that letter's Tsume only narrows its advance and moves no
glyph. The test compared glyph positions alone and found no change.
It now compares each glyph's position and advance.
2026-11-14 15:39:28 -08:00
dotCooCoo bc2b5dbae6 .ai save: graph round trips leave out the art bounds the reader measures
Opening an .ai graph now sets placed_art from the art as read (main's d3789f2b0c), so the comparison in the graph round-trip tests leaves it out, as it already leaves out placed.
2026-11-14 15:37:51 -08:00
dotCooCoo 828817c4b2 Scripts: File › Scripts runs the text its check read, with includes from the checked folder
The menu item checked a listed script and then ran script.run by its path,
which opened the file again; a link put in place of the file or a folder on
its path in between could make it run a file the check never saw, and the
include folder was looked up from the path again as well.

script.run takes installed: true. With a path it reads the file once through
script::folder::read, runs that text, and reads #include and $.evalFile in the
Scripts folder's real path followed by the names that read found. The menu
item runs script.run {path, installed: true}.
2026-11-14 15:37:18 -08:00
dotCooCoo d91e6a8565 Merge main into feat/opentype-hv-style-roman-italics 2026-11-14 15:24:42 -08:00
dotCooCoo 09ec24ee28 Merge main into fix/optical-keeps-tsume 2026-11-14 15:22:48 -08:00
dotCooCoo 0431809643 OpenType panel: H or V Style and Roman Italics set kana and Latin forms
With Show East Asian Options on, the OpenType panel menu has H or V
Style and Roman Italics. H or V Style is the character feature tag
`hkna`: kana take the forms the font draws for horizontal type, and in
vertical type the shaper applies `vkna` in its place. Roman Italics is
`ital`, the font's italic half-width alphanumerics. Both are on or off
only (`hkna=2` is refused), set through `features` and
`featureChanges`, cleared by Reset OpenType Features, and are switches
in Character Style Options > OpenType Features.

Docs: docs/mcp.md, docs/type-parity.md, docs/target-app-parity.md.
2026-11-14 15:22:36 -08:00
dotCooCoo 2b9325b9e0 Merge main into feat/links-copy-info 2026-11-14 15:21:36 -08:00
dotCooCoo dba42af495 Merge main into feat/ai-save-graph-records 2026-11-14 15:21:09 -08:00
dotCooCoo 7ad98ce9b8 Perspective: a repeated id attaches, moves or transforms an object once
perspective.attach, perspective.release, perspective.move and
perspective.transform took their `ids` as given, so an id listed twice
was processed twice. Attach warped an object that was already projected
a second time, and a move or transform of an object not yet in
perspective attached it and then moved it twice. The Shift and Snap to
Grid reads of a move also used a document attached twice. The ids are
now taken once each, in the order first given.
2026-11-14 15:20:27 -08:00
BFlat dafd993395 Merge pull request #7658 from dotCooCoo/feat/svg-filter-defaults
Effects: Effect › SVG Filters lists default filters
2026-11-15 06:18:12 +07:00
BFlat 4cf0a4b182 Merge pull request #7474 from dotCooCoo/feat/ai-insert-aki
Type: .ai import reads Insert Aki, and .ai save writes it
2026-11-15 06:18:04 +07:00
BFlat 4e42e2bbb0 Merge pull request #7681 from dotCooCoo/feat/east-asian-glyph-forms
Type: Glyphs panel menu draws the selected characters in an East Asian glyph form
2026-11-15 06:17:55 +07:00
dotCooCoo 10645eff51 Merge main into fix/svg-export-confined-image-links
# Conflicts:
#	crates/engine/src/cmd/fileio/commands.rs
2026-11-14 15:17:48 -08:00
BFlat 9f6edb7239 Merge pull request #7682 from dotCooCoo/fix/graph-outline-stroke-keeps-plot
Graphs: Outline Stroke and a graph left with only labels keep the plot in place
2026-11-15 06:17:47 +07:00