Commit Graph
1041 Commits
Author SHA1 Message Date
BFlat bdfd445eef Merge pull request #7662 from ruchirguitar/feat/style-neon-glow
Graphic Styles: built-in Neon & Glow graphic style library
2026-11-15 13:49:11 +07:00
BFlat 6dd478a420 Merge pull request #7456 from dotCooCoo/feat/ai-save-envelope-case
.ai save: a top object with uneven sides stays a live envelope
2026-11-15 12:37:47 +07:00
BFlat 511f36ed4c Merge pull request #7721 from dotCooCoo/fix/glyph-forms-review
Type: glyph forms and width forms follow the OpenType rules
2026-11-15 11:37:33 +07:00
BFlat c7ed93dc31 Merge pull request #7736 from dotCooCoo/feat/pattern-move-tile-with-art
Pattern Options: Move Tile with Art moves the tile with the pattern's art
2026-11-15 11:36:50 +07:00
ruchirguitar 44acce4a27 Merge remote-tracking branch 'upstream/main' into feat/style-neon-glow
# Conflicts:
#	ROADMAP.md
#	docs/gaps.md
#	docs/target-app-parity.md
2026-11-15 09:46:14 +05:30
dotCooCoo 8767cf847f .ai save: check a split top object's sides at every point they have
The writer compared the top object's patch with the one the reader makes
of the written grid on an even 9 x 9 grid of places. A split side can
change how the reader samples a short curve that falls between those
places, so a top object could be written live although the reader would
map the contents differently there.

Coons::places gives the places in u and v where the patch's sides have
points. Each side runs straight between them, so the writer now compares
the four sides of both patches at every one of those places, within a
third of the tolerance; a point inside a patch blends its sides and
corners, so the patches then lie within the tolerance everywhere.
2026-11-14 19:55:47 -08:00
BFlat 26878d3d0e Merge pull request #7715 from dotCooCoo/fix/svg-open-path-type-before-start
Type on a path: type on an open path can start before the path
2026-11-15 10:46:42 +07:00
dotCooCoo e9cd607aae Merge main into fix/glyph-forms-review 2026-11-14 19:20:09 -08:00
ruchirguitar d84483ecdc Merge remote-tracking branch 'upstream/main' into feat/style-neon-glow
# Conflicts:
#	crates/effects/src/svg_filter/defaults.rs
2026-11-15 08:31:44 +05:30
dotCooCoo f80c97ec14 Merge main into feat/pattern-move-tile-with-art 2026-11-14 19:00:56 -08:00
BFlat 3f90b8ff4c Merge pull request #7719 from dotCooCoo/fix/unit-parse-long-expression
Units: a very long expression reads as nothing instead of overflowing the stack
2026-11-15 09:46:13 +07:00
dotCooCoo a20cf07fe3 Pattern Options: Move Tile with Art moves the tile with the pattern's art
Pattern Options gets the Move Tile with Art checkbox under Size Tile to
Art. With it on, a move of all of the tile art in pattern editing mode
moves the tile as far as the art moved, pixel-grid snapping included:
object.move, object.nudge, object.transform with a translation, and
drags with the Selection tool. A group counts as moved when each of its
members moved. A move of only
some of the art, a copy, a rotation, a scale and a move of
direct-selected anchors leave the tile where it is. The option is off
for a new pattern.

pattern.options takes moveTileWithArt and pattern.list reports it. A
pattern.options call that changes nothing no longer records an undo
step.
2026-11-14 18:19:17 -08:00
ruchirguitar 61df62bdff Merge remote-tracking branch 'upstream/main' into feat/style-neon-glow
# Conflicts:
#	docs/gaps.md
#	docs/target-app-parity.md
2026-11-15 07:28:31 +05:30
BFlat 61091515b3 Merge pull request #7624 from dotCooCoo/fix/cmyk-tiff-alpha-and-extra-sample
TIFF: alpha after another extra sample places and opens as transparency
2026-11-15 08:24:36 +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
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 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 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
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
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
BFlat b0444043bb Merge pull request #7677 from dotCooCoo/fix/tiff-jpeg-strip-budget
TIFF: JPEG strips larger than their strip and oversized strip tables are refused before decoding
2026-11-15 06:17:06 +07:00
BFlat a92993daf9 Merge pull request #7582 from dotCooCoo/fix/clip-effects-hit-test
Selection: clicks in a clip group follow the region its clipping path's effects give it
2026-11-15 06:16:57 +07:00
dotCooCoo 5fcccce7ce Paths: Reverse Path Direction turns a compound path's own stroke width profile and pen records
Reverse Path Direction reversed each member path of a compound path and
turned the members' own stroke records, but left the compound path's
strokes as they were. A compound path's strokes run along its members'
subpaths in order, so its width profile and pen pressure, tilt, bearing
and rotation then ran the old way and the stroke drew differently.

reverse_stroke_records now also takes a compound path, reading its
members' subpaths in order, and path.reverse turns each compound path's
strokes with the combined reversal of its members before they reverse.
As for a path, the strokes turn only when every subpath turns alike.
2026-11-14 14:52:36 -08:00
BFlat 9153900b1f Merge pull request #7322 from dotCooCoo/fix/graph-series-group-opacity
Graphs: the transparency set on a series group is kept
2026-11-15 05:30:04 +07:00
BFlat 052c1b1d77 Merge pull request #7465 from dotCooCoo/fix/blend-reversed-key-stroke-direction
Blend: stroke gradients and dashes follow a key path the blend turns
2026-11-15 05:27:01 +07:00
dotCooCoo ddb2098827 TIFF: rewrap the doc comments of with_room and extra_samples_tiff_rgba 2026-11-14 13:47:06 -08:00
dotCooCoo b12c78a731 Graphs: Outline Stroke and a graph left with only labels keep the plot in place
- Outline Stroke on a graph's strokes, including Object > Expand with
  Stroke on, adjusts the bounds the graph keeps, the same way
  Create Outlines on its labels does. Before, the stroke outlines
  changed the bounds of the art without type, and the next redraw
  stretched the plot by the width the outlines added.
- A graph that keeps `placedArt` and has no art other than type left
  is no longer measured on the bounds of all its art. A label edit on
  such a graph moved and stretched the plot at the next redraw; the
  plot now stays where it is.
- A redraw no longer measures a move or scale on art that has no width
  or no height now, such as a graph left with one axis line. Before,
  the plot collapsed to no width.
- A clipping group in a graph that holds type is measured on its clip
  when the clip does not depend on type, so moving art outside the clip
  does not change the bounds. Blends, envelopes and repeats are
  measured whole, as they are drawn.
- The doc crate's helper is named `graph_art_without_type` and sits
  with the file's other free functions.
- docs/mcp.md lists Outline Stroke and states that moving an outlined
  label, a swatch, an axis or a tick mark on its own can move or scale
  the plot, and that a graph left with only labels, or with art that
  has no width or height, does not follow a move.
- Tests: a scale of the graph made before outlining a label, a move of
  a graph opened from `.ai` after its label is restyled and moved,
  Outline Stroke after a move, a graph left with only its labels or
  with one line, and clipping groups holding type.
2026-11-14 13:45:46 -08:00
dotCooCoo f4a5d65f6b Type: Glyphs panel menu draws the selected characters in an East Asian glyph form
With Show East Asian Options on, the Glyphs panel menu lists Traditional
Forms, Expert Forms, JIS 04, JIS 90, JIS 78 and JIS 83 Forms, Monospaced
Full-Width, Half-Width, Third-Width and Quarter-Width Forms, and Revert
To Default Forms. Each form draws the selected characters with the
font's OpenType feature for it (trad, expt, jp04, jp90, jp78, jp83,
fwid, hwid, twid or qwid). A form is checked when every selected
character has it and disabled when none of the faces that draw the
selected text, fallback faces included, has the feature.

The form is a new character attribute, glyphForm, one form at a time
(text.setFormat, text.setRangeStyle). It is saved only when set,
character styles carry it, and runs that differ only in their form
shape as one piece of text. SVG writes it in font-feature-settings;
PDF draws the glyphs from the layout.
2026-11-14 13:29:30 -08:00
BFlat 20ae675b9b Merge pull request #7622 from dotCooCoo/fix/graph-label-restyle-keeps-plot
Graphs: restyling the labels leaves the plot where it is at the next redraw
2026-11-15 04:22:27 +07:00
BFlat 88b83a8689 Merge pull request #7546 from dotCooCoo/fix/svg-raster-keeps-resolution
SVG import: a raster stored again as PNG keeps its resolution
2026-11-15 04:22:10 +07:00
dotCooCoo 0257112158 TIFF: JPEG strips larger than their strip and oversized strip tables are refused before decoding
oversized_tiff now also refuses a TIFF whose JPEG-compressed strip or
tile (Compression 7) declares a frame wider or taller than the strip or
tile, one with more than MAX_TIFF_CHUNKS strips or tiles, and one whose
JPEG headers take more than 16,777,216 steps to read. It reads BigTIFF
directories, follows the layout and the repeated tags the decoder
reads, and refuses offsets of types it does not read.
2026-11-14 13:16:35 -08:00
dotCooCoo cf8b88b0ea Merge main into fix/clip-effects-hit-test 2026-11-14 12:59:50 -08:00
dotCooCoo c7c9e7f4db Merge main into fix/graph-series-group-opacity 2026-11-14 12:29:26 -08:00
dotCooCoo 81ec6498dc Merge main into fix/blend-reversed-key-stroke-direction 2026-11-14 12:28:54 -08:00
dotCooCoo ee0a3b7f3f TIFF: doc comments name every refusal; the planar copy test checks its room
The doc comments of read_samples, with_room and extra_samples_tiff_rgba
name the SampleFormat check, and those of tiff_source and tiff_inks name
the 64-sample limit, the SampleFormat check and the decoder row check.
read_samples gives the decoder's limit as 256 MiB.

the_planar_copy_is_allocated_at_its_length copies 33 samples and checks
that the copy has room for at least 33 and fewer than 64, the room a
copy grown as it was filled ends with, rather than an exact capacity.
2026-11-14 12:27:19 -08:00
BFlat 1a8614f00c Merge pull request #7550 from dotCooCoo/fix/graph-wedge-legend-black-kept
Graphs: a black fill set on wedge legend labels stays black on dark wedges
2026-11-15 03:22:47 +07:00
BFlat 85ce363b93 Merge pull request #7548 from dotCooCoo/fix/objects-on-path-widgets-flatten-once
Objects on Path: the widgets flatten the group's path once per lookup
2026-11-15 03:22:27 +07:00
BFlat 46a7dd24f6 Merge pull request #7646 from dotCooCoo/fix/nine-slice-symbol-lookups
Symbols: measuring and picking in 9-slice instances reuse one set of symbol boxes
2026-11-15 03:20:12 +07:00
dotCooCoo b7678494a2 Merge main into fix/clip-effects-hit-test 2026-11-14 12:18:53 -08:00
dotCooCoo df257c2d53 Merge main into fix/svg-raster-keeps-resolution 2026-11-14 12:12:06 -08:00
ruchirguitar af64492f1c Graphic Styles: built-in Neon & Glow graphic style library 2026-11-15 01:41:41 +05:30
dotCooCoo c347abf499 TIFF: CMYK files with unnamed extra samples past their ExtraSamples values open opaque
A CMYK TIFF with two or more samples past its inks and its ExtraSamples
values, or with no ExtraSamples tag, which the decoder gives no color
type, now opens opaque when none of those values names an alpha. It
failed to open before; one that names an alpha already read with it.
2026-11-14 12:05:50 -08:00
dotCooCoo 3a834f705a TIFF: refuse files of over 64 samples a pixel from their tags
The 64-sample limit is now checked against the file's SamplesPerPixel
before anything is decoded. It was checked against the samples the
decoder gives, which is five for any CMYK file whose first ExtraSamples
value is alpha, so a small file with 65,535 samples a pixel was read and
the decoder took a row of every sample (1 GiB for a 16-bit file 8,192
pixels wide) before reading its data.

- The row the decoder reads each strip or tile into, when the file has
  more samples a pixel than it gives, counts in the allowance.
- Files whose samples aren't unsigned integers are refused from their
  tags; no reader takes them.
- The same checks run before CMYK inks and plates are decoded.
- The copy that puts a planar file's samples pixel by pixel is allocated
  once, at its length, so the reader holds no more than it counts.
2026-11-14 11:44:57 -08:00
dotCooCoo 6a078a0030 Blend: dashes on a closed key reversed from its own start read back from its end
A closed key that winds the other way is reversed keeping its start anchor.
Its dash offset was computed as if the reversed path ran back from position
0 instead of from the end of the subpath, so the dashes shifted by the
subpath length modulo the pattern period. The offset now uses the full
length in that case, and the dash test covers a closed key 40 long with a
7.5 period.
2026-11-14 11:41:09 -08:00
dotCooCoo a512bf5ce6 Image resolution: a WebP chunk length past the end of the file reads no resolution
The WebP chunk walk added the chunk length to the offset without a
checked add, so a length near u32::MAX overflowed on 32-bit targets
(wasm32) with overflow checks on. It now reads the chunk through the
same checked slice helper as the PNG, JPEG and TIFF readers.
2026-11-14 11:37:16 -08:00
dotCooCoo 87b861c480 TIFF: extra-sample reads count the planar copy and RGBA pixels in the allowance
A CMYK or grayscale TIFF with extra samples is now read only when its
decoded samples, the copy that puts a planar file's samples pixel by
pixel and the RGBA pixels together fit the decoding allowance; the
check counted the decoded samples alone, so a small compressed file
could make the reader hold about twice the allowance plus the RGBA
image.

- The decoder may decode no more than the counted samples, and reads no
  strip or tile of more compressed bytes than the file holds.
- A pixel of more than 64 samples is not read (VectorCraft's own limit).
2026-11-14 10:50:41 -08:00
dotCooCoo 3534dd7343 Symbols: measuring and picking in 9-slice instances reuse one set of symbol boxes
Measuring an instance of a symbol with 9-slice guides looked its map up
through Document::symbol_slice_map, which scanned the symbol list and
measured the art box again in new quick boxes, and NineSliceMap::place
and most_placed made new quick boxes of their own. Each of those boxes
built the symbol name index over the whole list on its first lookup, so
every 9-slice instance measured read every symbol once or more. Picking
an object inside a 9-slice instance's art with Direct Selection did the
same for each object of the art that holds a symbol instance.

- SymbolBoxes cuts the guides through the art box it has measured
  (SymbolBoxes::slice_map), and places and counts the art with itself as
  the boxes, in the mode quick boxes use. Art with no 9-slice map is
  moved by the instance's transform without looking the map up again.
- Document::symbol_slice_map, place_symbol_art,
  place_symbol_art_unclipped and edited_nine_slice use one set of quick
  boxes for each call.
- symbol_part_at and symbol_part_outline keep one set of quick boxes
  for the instance's art.

The new tests count the symbols read. Measuring 200 instances of a
symbol with guides and 200 of a symbol with guides holding it, among
2,002 symbols, read 1,205,204 symbols before; now each set of boxes
reads the list once. Picking in a 9-slice instance whose art holds 20
instances read 84,042 symbols among 2,001; now it reads the list at
most twice.
2026-11-14 10:32:20 -08:00
dotCooCoo 3be00f4bd3 Blend: a gradient steps where the other does only at a step of its own
When two gradients with different stop counts are blended, the second stop at the other
gradient's step took the last stop within the step tolerance, so two close but distinct stops
became a hard step. It now takes the last stop of a step of its own there, and otherwise paints
what the gradient paints at that place.
2026-11-14 10:14:38 -08:00
dotCooCoo 82d675e0b2 TIFF: the decoder's default buffer limit is 256 MiB
The doc comments of with_room and of the large-file test, and the
progress-doc line, gave the tiff decoder's default decoding buffer
(268,435,456 bytes) as 256 MB.
2026-11-14 10:10:54 -08:00
dotCooCoo 22ec066bb7 Blend: a turned hue gradient that reflects keeps its swatch links
A hue gradient that reflects unrolls through its curve, whose stops have no swatch links, so a
gradient along a closed key started elsewhere lost its links. The links now come from the
gradient's own stops unrolled as a Classic gradient's, in order, so stops at one place and the
stops where two runs meet keep their own links.
2026-11-14 10:03:08 -08:00