Commit Graph
170 Commits
Author SHA1 Message Date
Brandon Thomas 33010204cb Merge pull request #5137 from dotCooCoo/fix/vars-p16
Variables: Lock Variables' description names the refused import
2026-11-01 15:27:53 -05:00
Brandon Thomas 2aa24c1f3a Merge pull request #5136 from dotCooCoo/fix/vars-p5
Variables: a graph variable's data set applies its CSV as graph.setData does
2026-11-01 15:27:48 -05:00
Brandon Thomas 7e5eebc3ed Merge pull request #5135 from dotCooCoo/fix/vars-p15
Variables: Rename reports a missing variable or data set before a taken name
2026-11-01 15:27:45 -05:00
dotCooCoo 39e69e8446 Tests: move the lock params text test next to the lock test 2026-11-01 11:48:41 -08:00
dotCooCoo b104efc53c Graph data: a dropped empty row adds no blank series
Dropping rows with neither a label nor a value read the data padded to
the widest row, so a dropped row wider than the rest (`,,`) left every
kept row one blank cell wider. The graph then had an extra blank series,
and a right-axis assignment to that index was kept. Each kept row now
keeps its own width. This applies to graph.setData and to a graph
variable's data set.
2026-11-01 10:19:45 -08:00
dotCooCoo 637229931f Paste: Paste in Back keeps a clip group's clipping path at the bottom
With the clipping path of a clip group or a clipping layer selected, or a
member of a compound path that is the clipping path, Paste in Back put the
clipboard's objects below it. The bottom pasted object then became the
clipping path. With nothing selected in a clipping layer, Paste in Back did
the same at the bottom of the layer.

Paste in Back now puts the objects right above the clipping path, as Send to
Back keeps art above it.
2026-11-01 10:18:06 -08:00
dotCooCoo e49e102032 Variables: Lock Variables' description names the refused import
variable.lock's params text and the doc comment on Variables::locked said every
dataset command still runs while the variables are locked. dataset.import is
refused when its header would define a variable; both texts now say so, as
docs/mcp.md and dataset.import's params already do.
2026-11-01 08:43:56 -08:00
dotCooCoo 07282d95e0 Variables: a graph variable's data set applies its CSV as graph.setData does
Choosing a data set gave a graph variable's CSV to the graph without the
two steps graph.setData takes after new data: rows with neither a label
nor a value were kept and drawn as empty slots, and a right-axis series
the new data no longer had stayed assigned, so a series added by a later
row started on the right axis. The data set now goes through the same
steps.
2026-11-01 08:43:16 -08:00
dotCooCoo f98b086c5f Variables: Rename reports a missing variable or data set before a taken name
variable.rename and dataset.rename checked whether newName was taken before
checking that name exists, so renaming a missing name onto a taken one said
the new name already exists. Both now report the missing name first.
2026-11-01 08:23:40 -08:00
dotCooCoo 827e137b5c Paste: in front or back of a compound path's member, other art goes beside the compound path
With a member of a compound path selected, Paste in Front and Paste in Back
put the clipboard's objects next to that member, inside the compound path,
whatever they were. A pasted group, compound path or type object then sat
inside a compound path, which holds only paths. The model-based test
`random_command_sequences` stops on this with "compound #N has a non-path
child".

When the clipboard holds anything other than paths, the objects now go
right above or below the compound path. Paths still go next to the member,
inside the compound path.
2026-11-01 06:42:33 -08:00
dotCooCoo 38bb213917 PDF export: a Perceptual gradient that mixes spot and process inks keeps its inks
A Perceptual linear or radial gradient that mixes spot colors with each
other or with CMYK or gray stops was written as an image in process
colors. It is now written as a DeviceN shading of its inks, with the
stops on its Perceptual curve: at each one, the inks of the two stops on
either side are mixed at the weight where the two colors mixed in
display RGB have the OKLab lightness of the color on the curve.

- Gradient::ink_mixes gives the curve's stops as weights between stops,
  under the same 4,096-stop limit as ink_tints.
- Perceptual gradients that mix a spot color with RGB or Lab stops,
  dithered fills and freeform gradients stay images, and the warning
  now names those cases.
2026-11-01 11:09:09 +07:00
dotCooCoo ff036557fb Symbols: Edit Symbol opens the symbol in its own editing mode, and every instance follows each edit
Squashed from #5064 onto main.
2026-11-01 11:09:03 +07:00
dotCooCoo 5716a996ab Type: Touch Type moves a character rotated near 90 degrees across its line
Baseline shift now moves a character in the line's frame, outside
Character Rotation, so a positive shift moves a character up from its
line at any rotation, and the character turns about its shifted pen
position. The Touch Type tool splits a drag into its part along the line
(kerning) and its part up from the line (baseline shift) at every
rotation; before, within about 6 degrees of a quarter turn it dropped
the cross-line part.

Characters that already combine a rotation with a baseline shift are
drawn in a new position. Their stored values do not change.

- text: glyph transform order, PositionedGlyph::pivot (the turning
  point), tab leaders in the same order
- tools: Touch Type splits drags against the line's up axis and measures
  rotate-handle turns about the shifted pen position
- engine: text.touchType description; dy is up from the line
- docs: mcp.md Touch Type paragraph, target-app-parity.md progress line
2026-11-01 11:08:57 +07:00
dotCooCoo 94ef9b87c2 Type: Character panel toggles show off for a mixed selection, and text.setFormat checks on/off values
The Character panel's All Caps, Underline and Strikethrough buttons, and
its menu's All Caps, Underline, Strikethrough, Proportional Metrics,
Tate-chu-yoko and No Break, read the first selected character's style.
With only some characters marked they could show on, and choosing one
turned the attribute off for every character. They now show on only
when every selected character has the attribute, and a click on a mixed
selection turns it on for all, as Small Caps and Standard Vertical Roman
Alignment do. type.underline (Cmd+Shift+U) underlines a mixed selection
the same way. The Character Alignment and Digits submenus check no item
while the selected characters differ.

text.setFormat ignored a value that was not true or false for underline,
strikethrough, allCaps, proportionalMetrics, tateChuYoko,
standardVerticalRoman, noBreak, hyphenate, hyphenateCapitalized and
romanHangingPunctuation. It now refuses it with "`<key>` must be true or
false" and changes nothing, as text.setRangeStyle does.
2026-11-01 11:08:56 +07:00
dotCooCoo d72022daf5 Grid tools: F, V, X and C change the divider skews by 10 while a grid is dragged out
Squashed from #5019 onto main.
2026-11-01 08:33:02 +07:00
dotCooCoo ac896cfe3d PDF export: a Perceptual gradient of one spot ink's tints keeps its ink
A Perceptual linear or radial gradient was always written as an image,
so a gradient of spot ink tints lost its plate, and no warning named the
spot ink.

PDF export now writes a Perceptual gradient whose stops are tints of one
spot color (and paper white) as a Separation shading in that ink. Its
tints follow the gradient's curve: at each stop the renderer places on
the curve, the tint with the OKLab lightness of the color there.

Dithered fills, freeform gradients and Perceptual gradients that mix a
spot color with other colors are still images, and the export now warns
when such a gradient has stops linked to a spot swatch that its spot
colors are written in process colors.
2026-11-01 08:32:54 +07:00
Don Omondi 961694cba6 Fix #4918: delete nested picks safely, keep picks per document
symbol.delete {names} expands every instance of the named symbols,
round by round, before it removes any definition, so a symbol and the
one placed inside it delete alike in either order. The Symbols panel's
picks belong to the document they were made in, and a single picked
symbol is deleted only while symbol.unused still lists it.
2026-11-01 07:38:01 +07:00
Don Omondi 5b639169cd Fix #4918: Symbols panel Select All Unused
symbol.unused lists the symbols nothing places: instances in the art
(opacity masks included), graph designs, pattern tiles and brush art
count, and so do instances in a used symbol's art. The Symbols panel
menu's Select All Unused shows them selected, and Delete Symbol asks
once and deletes those still unused in one undo step
(symbol.delete {names}).
2026-11-01 07:38:01 +07:00
dotCooCoo 8805fa8214 Place: Word .doc text with a broken surrogate pair is refused, and fields without a result warn
A 16-bit piece holding half of a UTF-16 surrogate pair was placed with
U+FFFD in its place; the document is now refused. A pair split between
two pieces is decoded as one character.

A field with no result (no separator, never ended, or nested past 64)
is left out with its own warning, and the warning about fields placed
as their results is given only for fields that have one.
2026-11-01 07:16:30 +07:00
dotCooCoo 5fdccdf602 Place: Word 97-2003 .doc files place as area type with their formatting
File > Place reads a Word 97-2003 document (a compound file named .doc,
or one that holds a WordDocument stream) and sets its main text as area
type, as it does for .rtf and .docx files. The text comes from the
piece table, in 16-bit Unicode or compressed 8-bit pieces. Formatting
is resolved from the style sheet's default font, the paragraph style,
the character style and the direct character and paragraph properties,
and keeps the same properties the .docx reader maps.

Table cells are separated by tabs and each row is a paragraph, and a
field places as its result. Hidden and deleted text, pictures, headers,
footers, notes, comments and text boxes are left out, and the reply's
warnings name each kind of content left out or changed once. Word 6.0
and Word 95 documents, encrypted documents and documents last saved
with fast save are refused, and so are structures that lie outside
their streams.

The compound file is read with the cfb crate (MIT), which adds one
package to Cargo.lock.
2026-11-01 07:16:29 +07:00
bflatastic 7d8f1d4e06 Fix #4978: Area Type Options › Text Flow by rows or by columns 2026-11-01 06:11:11 +07:00
bflatastic 8704c15c6d Fix #4980: Pen with Cmd/Ctrl drags the path, not hidden bounding-box handles
Cmd/Ctrl held with the Pen lends the last-used selection tool (Direct
Selection until one is chosen), which already moved anchors, straight
segments and curves of the path being drawn, one undo step each, and
handed the path back to the Pen.

With the Selection tool used last, the lent tool hit-tested the
bounding box even though the canvas hides it while anchors are selected
on their own, as the end anchor of a path being drawn is. Over the
path it showed a resize pointer, and the drag scaled the path by a
handle nobody could see: a flat path did nothing at all.

The Selection tool's handles now take the pointer only while the box is
shown (View > Show Bounding Box, no anchors selected on their own), so
the lent Selection tool moves the path being drawn.

Tests: select::tests::hidden_box_handles_take_no_press_while_anchors_are_selected,
tests::draw2::pen_cmd_drag_edits_anchors_and_segments_and_goes_on_drawing.
2026-11-01 06:11:09 +07:00
dotCooCoo f66b583b5b Tests: mixed spot gradients export as DeviceN shadings when radial or in a Lab ink
- A radial gradient from a spot swatch to a CMYK color is written as a
  DeviceN shading (ShadingType 3) of Yellow and the spot ink.
- A Lab spot color used as a flat fill and in a mixed gradient is
  written as one Separation space with the Lab alternate, which the
  DeviceN space names under /Colorants.
- docs/roadmap.md lists the gradients that still export without the
  spot ink: mixed spot/process gradients in SVG and PDF gradients with
  RGB or Lab stops. docs/target-app-parity.md also notes that a
  Perceptual gradient is written to PDF as an image.
2026-11-01 04:55:11 +07:00
dotCooCoo ba16468710 PDF export: a gradient that mixes a spot color with process colors keeps the spot ink
A linear or radial gradient whose stops mix a spot color with process
colors, or two spot colors, was written in process colors with a
warning. It is now written as a DeviceN shading of the process inks
its stops use and its spot inks, so each spot ink separates onto its
own plate. An off-center midpoint becomes a stop with the inks halfway
between its two stops. A gradient that mixes a spot color with RGB or
Lab colors is still written without the spot ink, with a warning that
says so.
2026-11-01 04:55:11 +07:00
dotCooCoo bbcb886a40 Perspective: a stored projection whose determinant overflows reads back at a w row of 1
A stored projection whose determinant is not finite is read back as the
same map divided by the largest magnitude in its w row, the scale the
overflow branch of conjugated already uses, so points that overflowed at
the stored scale draw: the identity with every entry 1e308 maps (2, 2)
to (2, 2). The overflow branch of inverse scales the map so that the
largest of the products its determinant sums is near 1, so a map with
large entries and a small w row, such as a scale by 1e305 with a move
by -3.9e307, is still inverted and read back.

The two overflow tests in persp_text.rs build their stored projections
with a w row of 1, so they still overflow after the read-back.
2026-11-01 04:55:11 +07:00
dotCooCoo 18510b77af Appearance: effect.apply reports the new effect's index in the object whose row it names
The reply's index came from the last object the effect was added to, while
its item came from the first selected object (or the first target for an
explicit item). When those objects held different numbers of effects on the
row, the reply named no effect of that object. The index is now read from
that object's effect list.
2026-11-01 04:55:10 +07:00
dotCooCoo af86b2c833 Objects on Path: double-clicking the tool opens its options
Double-clicking the Objects on Path tool opens Objects on Path Tool
Options: a nine-point Pivot grid, a Rotate field, Reset (the center and
0) and Save. Save sets the tool's options, which last across tool
switches and are saved with the preferences. The tool sends them with
each attach, and object.objectsOnPath.attach without pivot or rotate
uses them, so they apply to the objects attached next; groups attached
before keep theirs.
2026-11-01 04:53:41 +07:00
bflatastic f22caa6108 Fix #4938: Dynamic symbols keep per-instance appearance linked to the symbol 2026-11-01 04:53:38 +07:00
bflatastic 8c35bacfb3 Global Edit: the menu checks a selected group's kind only; its contents are checked on start 2026-11-01 03:36:43 +07:00
bflatastic 3146ca513b Fix #4939: Global Edit finds and edits groups like the selected one 2026-11-01 03:36:43 +07:00
dotCooCoo dfd07552c8 Cursors: Caps Lock gives the drawing tools the crosshair
While Caps Lock is on, the tools whose pointer General > Use Precise
Cursors changes show the crosshair, and the saved preference does not
change. The desktop app reads the lock state on macOS, Windows and
Linux X11 and passes each change to the new view.capsLock command. A
change to view.capsLock or usePreciseCursors through app.run refreshes
the canvas pointer. The view.preciseCursors command is labeled Use
Precise Cursors, so command search finds it by the preference's name.
2026-11-01 01:59:54 +07:00
dotCooCoo db6c5ea9df Fix #2678: Ungroup no longer multiplies the group's opacity into its members
Ungroup multiplied each member's opacity by the group's, so a member of a
50% group stayed at 50% after the group was gone, and members that
overlapped showed through each other. Each member now keeps its own
opacity, and the group's opacity is removed with the group.
2026-11-01 01:22:08 +07:00
dotCooCoo 5a5a7b9ce8 Mesh tool: a click on a mesh's edge from just outside adds the lines there
A Mesh tool click within the hit tolerance outside a gradient mesh hit the
mesh, but object.mesh.addLine looked for the point inside the outline only
and failed with "point is outside the mesh". The same click on a path's edge
made a 1x1 mesh without the line.

- GradientMesh::locate_within takes a tolerance: a point outside the mesh
  within it of the outline gives the nearest point of the edge.
- object.mesh.addLine and object.mesh.create take an optional `tolerance`
  (0 by default), and the Mesh tool sends its hit tolerance.
2026-11-01 01:22:08 +07:00
dotCooCoo 9896f633da Type: Kerning has an Optical option that spaces characters by their shapes
Squashed from #4825 onto main.
2026-11-01 01:22:04 +07:00
dotCooCoo 012af4fd49 Brushes: each Pattern Brush Options tile can be set to a pattern swatch or None
A menu under each of the five tiles in Pattern Brush Options lists None,
Original and the document's pattern swatches; the corner menus keep the
four automatic corners. A swatch choice copies the swatch's whole art and
its tile frame into the brush, so the brush keeps drawing after the swatch
is deleted, and the menu then shows that art as Original. Side cannot be
None: OK shows "You must choose a side pattern." and keeps the dialog open.

brush.options takes side_tile, outer_corner_tile, inner_corner_tile,
start_tile and end_tile (a pattern name, none, original, the corner auto
ids, or {"pattern": name} for a swatch named like one of those words), and
rejects side_tile "none" and unknown names before editing.
2026-11-01 01:22:03 +07:00
dotCooCoo b1aa576764 Brushes: Delete Brush asks Yes or No for unused brushes, and Remove Strokes or Cancel for brushes in use
The question for brushes no stroke uses reads "Delete the brush selection?" with Yes and No, from the bottom button and the panel menu, and Alt-click (Option-click on macOS) on the button still deletes them without asking. When a stroke uses one of the brushes, the button, Alt-click and the menu item ask with Remove Strokes and Cancel; Remove Strokes deletes the brushes and the strokes that used them become plain strokes.

brush.unused also counts the tile that pattern editing's Cancel puts back, which brush.delete already clears, so the panel asks the in-use question for a brush only that tile uses.
2026-11-01 01:21:54 +07:00
dotCooCoo b46cdc4e4d Isolation mode: Lock, Unlock All, Hide and Show All work on the isolated art
Squashed from #4849 onto main.
2026-11-01 01:21:53 +07:00
dotCooCoo ec8c1cb8d1 Intertwine: a right-click where three or more objects overlap arranges the nearest one
Squashed from #4862 onto main.
2026-11-01 00:36:29 +07:00
dotCooCoo bb6cbfb2b4 Images: SVG and EPS export write colorized grayscale and 1-bit images in their fill color
SVG export ignored an image's colorize paint and wrote the image's own
black, white and gray pixels. EPS export flattened every colorized image
into a process-color raster, so a spot color lost its plate. Both now
write the image in the fill color, with the coverage the canvas and PDF
export use (darkness times alpha), so white and transparent pixels take
no color.

- SVG writes a coverage PNG as a mask over a rectangle in the color,
  shared between repeated images while each keeps its own color. Linked
  mode writes the coverage PNG as a file, and SVG Tiny 1.2 writes a PNG
  in the color. A colorized image whose pixels are missing, or whose
  grayscale pixels can't be decoded, is left out with a warning.
- EPS writes fully covered or uncovered pixels as an imagemask in the
  color's process or Separation ink (Registration on every plate), and a
  grayscale image as ink samples scaled by coverage. Level 3 masks its
  uncovered pixels out and Level 2 writes them white; gray midtones are
  written over white, with the existing warnings.
- EPS flattening no longer counts a solid colorize paint on a grayscale
  or 1-bit image as transparency by itself. Opacity, blending modes,
  opacity masks, effects and overlapping transparent art still flatten
  it, and so does a colorize paint the writer can't draw.

The svg crate's tests turn on resvg's raster-images feature to render
the embedded PNGs.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-01 00:35:27 +07:00
dotCooCoo 93e27cafd3 PDF/EPS export: live art expanded for pattern tile images counts against the object limit
Blends, envelopes, repeats and Intertwine groups that hold a pattern fill
with a transparent tile image were each expanded under a limit of their
own, so many of them together had no limit. Their art now counts toward
the same limit as the pattern tiles, and an object whose art would pass
it stays live.
2026-11-01 00:35:23 +07:00
dotCooCoo a32a7cbb0d PDF/EPS export: EPS export and PostScript printing expand pattern tile images once
EPS export expanded the pattern fills whose tile images need flattening and
then flattened the result with flatten_document, which expanded them again
with a new object limit. PostScript printing did the same, on the layers
shown rather than the layers that print. EPS export now flattens the
expanded document without expanding it again (flatten_expanded), and
PostScript printing leaves the expansion to the flattener, which runs on
the layers that print, and only checks for such pattern fills to warn.
2026-11-01 00:35:23 +07:00
dotCooCoo e2fddc6b12 PDF/EPS export: pattern fills whose tiles pass the object limit stay pattern fills
The expansion counted the objects a pattern fill made only after making
them, so one shape could clone up to 250,000 tile positions times the
tile art's objects past the limit. Each fill now counts its tiles before
they are made, and after outlining strokes and effects for turned or
sheared tiles, and keeps room for the copy and group of the fills after
it. A fill that does not fit stays a pattern fill.
2026-11-01 00:35:23 +07:00
dotCooCoo c09ea59731 PDF/EPS export: transparent images in patterns nested in tile art flatten
A pattern fill in a pattern's tile art that paints a see-through or
colorized image was not counted, so PDF 1.3 export kept it as a
pattern and failed on its transparency. Detection now follows pattern
fills in tile art down to the three levels the canvas draws, skipping
hidden tile art, and the tiles the expansion makes are expanded again
for them. Expansion stops once the expanded art holds MAX_GENERATED
objects.
2026-11-01 00:35:23 +07:00
dotCooCoo 6dfda4db76 PDF/EPS export: pattern tile images in live art and hidden shapes flatten as drawn
Blends, envelopes, repeats and Intertwine groups that hold a see-through
or colorized pattern tile image are expanded into the art the writers
draw for them before the tiles are expanded, so those tiles are
flattened too. Random Transform effects in that art are baked first.

Hidden shapes, shapes in hidden groups or layers and template layers are
skipped: expanding a hidden shape made its tiles show in the file, and
art that is not written no longer makes a flattened copy of the document.
2026-11-01 00:35:23 +07:00
dotCooCoo 7b4d6282f1 Flattening: an option leaves colorized images to writers that draw them
Flattening counted every colorized image as transparency, in the art and
in pattern tiles. A new flattener option, keeps_colorize, stops counting
an image colorized with a solid paint whose pixels are grayscale or 1-bit,
for a format whose writer draws such images in their ink. Opacity,
blending modes, opacity masks and effects still flatten them, and pattern
tiles are still expanded for those. The option is off, so exports flatten
as before; the EPS writer that draws colorized images turns it on.

The EPS pattern tile tests now check the exported colors, and a tile
group with opacity for the warning.
2026-11-01 00:35:23 +07:00
dotCooCoo 33ab5adbc2 PDF/EPS export: flatten see-through and colorized pattern tile images
PDF 1.3, PDF/X-1a, PDF/X-3, EPS and PostScript printing flatten
transparency with `flatten_document`, which only looked at art in the
layers. An image with see-through pixels, a colorize paint, opacity or
a blending mode drawn only through a pattern fill stayed in the pattern
definition, so the flattener returned early and the writer drew the tile
opaque and uncolorized (PDF 1.3 export failed on the transparency left).

`flatten_document` now first expands pattern fills whose tile image
needs flattening into their tiles, as legacy .ai save does. See-through
tile pixels count only where the caller counts see-through images (PDF
1.3); EPS level 3 already masks them out. The EPS and PostScript print
transparency warnings now see those tile images too.
2026-11-01 00:35:23 +07:00
Wadah Badawi c35b51eb0f Intertwine: object.intertwine.arrange takes an id without a selection 2026-10-31 23:48:56 +07:00
Wadah Badawi 6eea3a5178 Pathfinder: Crop works on a selected group of shapes 2026-10-31 23:48:55 +07:00
dotCooCoo 77bdf478e4 Perspective: a homography product that overflows is refused instead of stored or drawn
Homography::then_after returned products whose entries could overflow to
infinity or become NaN. A move, transform or grid reproject of type or a
symbol instance whose stored projection has large finite entries stored such
a product, and the type was then drawn flat, since a projection that is not
finite reads as none.

- Homography::checked_then_after returns None when an entry of the product
  is not finite.
- project_node composes each type and symbol instance projection with it and
  returns false, leaving the object as it is, when one would not be finite;
  moves and transforms in perspective and Define Perspective Grid with
  reproject then return the horizon error and leave the document unchanged.
- transform_map, attach_homography and size_homography return None for such
  a product.
- Edit Text returns the horizon error, and does not enter isolation mode,
  when the type cannot be shown flat where it is drawn.
2026-10-31 23:29:37 +07:00
dotCooCoo f9e78eb144 Appearance: effect.apply reports the active row's object, and an empty aligned target list is an error
effect.apply now reports the item of the first selected object, whose row
the active item is. With nothing selected, an explicit aligned item returns
the command's error instead of an internal error.
2026-10-31 23:29:35 +07:00