Commit Graph
90 Commits
Author SHA1 Message Date
dotCooCoo a2c09544c8 Render: a placed document drawn exactly lends its own composite fonts
A placed document drawn without a bitmap (renders without progressive
placed art, exports) drew type in the placed document's own composite
font in the fallback font: only the outer document's composite fonts were
lent to the font database. The placed document's composite fonts are now
lent while its art is drawn.
2026-11-10 23:22:00 -08:00
dotCooCoo 5452b516f6 Render: type in a document's own composite font draws in its definition
Raster renders, ink renders, thumbnails and node renders now lend the
document's composite fonts to the font database while they draw, as the
PDF, SVG and EPS writers already do. A document that is not in front, a
document read from a file (library thumbnails, save previews) and a
placed document's art drew such type in the fallback font or in another
definition of that name.
2026-11-10 21:39:36 -08:00
dotCooCoo afac976482 Canvas: installing or removing a plug-in draws its effects again
The canvas key did not change when a plug-in was installed, replaced or
removed, so the canvas kept showing the frame drawn with the old plug-in,
and a pan by whole pixels copied that frame's pixels next to strips drawn
with the new one.

The key now carries the plug-in revision of the last install or removal
after which the document draws a plug-in effect, in its layers, in a
symbol's or pattern's art, or in a brush's art. The canvas looks through a
document once after each install or removal; a document without a plug-in
effect keeps its key.
2026-11-10 17:09:41 -08:00
Drew Dibble b242f5acf5 Fit to Artwork Bounds: a change in place is measured again; the fix's evidence names EPS export
A new test changes the document in place (Arc::make_mut, with no other
holder and no change of revision) and checks the answer is measured
again: keyed on the document's address alone, it would keep the old
answer. The kept answer's doc says what it holds after the measure,
and that the fonts set the bounds of all type. Should measuring ever
ask again while it runs, it is measured without being kept, rather
than borrowed twice.

The stack overflow fixed in d5d04f868 also hit EPS export, and the
parity rows now say so. Two test docs are corrected: the fill-only
clipping is an older bug on main, and the outline-view difference runs
both ways on a few dozen hairline pixels.
2026-11-09 16:50:36 -07:00
Drew Dibble a9a061c8a9 Merge main into type-3d-redraw
# Conflicts:
#	docs/target-app-parity.md
2026-11-09 16:36:25 -07:00
Drew Dibble 59cc303912 Canvas redraw: the stack overflow fix's evidence, and two tests' known differences
- The parity rows say what the fix covers: a 3D effect on a fill or
  stroke that builds nothing overflowed the stack with no effect of the
  object's own too, on the canvas, in Object › Rasterize and in raster
  exports.
- The builds-nothing test says the fill-only difference (a raster
  effect clipped to the path, cutting Roughen's spikes, when drawn
  without the 3D effect) is an older bug, not the design.
- The traced-shape test says why its area is bounded rather than drawn
  again: a partial render in outline view differs from the whole by one
  in alpha on 37 pixels, older than the test and unrelated to the edit.
- A doc comment rewrapped.
2026-11-09 15:13:42 -07:00
dotCooCoo 5eb6b62632 Flatten: blend modes do not read the white under the art
Flatten Transparency composited each region over white, so a blend mode
on the bare page read that white. Raster renders now draw their
background under the finished art, and the flattened color no longer
matched them: a Color Burn object on the bare page flattened to white
and rendered in its own color. The region now composites the art alone
and puts the white under it.

- Tests that relied on the white under the art now draw art under the
  style (`every_builtin_style_paints_visibly_on_a_rectangle`) or compare
  only whole-pixel pans with a fresh render
  (`a_coarser_glow_kept_whole_draws_after_a_pan_as_made_again`).
- New tests cover every blend mode on white and blue backgrounds, a
  thumbnail and the ink planes with a Screen glow, and
  `over_background` at background and pixel alpha 0, 128 and 255.
2026-11-09 14:13:08 -08:00
Drew Dibble bd9f6a04ef Canvas redraw: review follow-ups to the strengthened tests
- A 3D effect that builds nothing: af754bcb2 said the object draws exactly
  as without it under a Drop Shadow or Gaussian Blur. That holds only for
  a stroked object. With only a fill, the raster effect draws from the
  art made for the object, where without the 3D effect it is clipped to
  the path, so Roughen's spikes differ: the earlier wording was right.
  The test keeps the stroked cases exact and restores fill-only cases
  with a Drop Shadow and a Gaussian Blur, which differ in under 4% of
  the pixels and nowhere inside the path. Its reach check compares with
  the object drawn without its raster effect, since Roughen alone
  reaches past the path.
- The Vector Halftone test's bounds are a consistency check, and only
  holding no art tests the branch: its doc no longer says no solid is
  built (the free measure's halftone arm builds one). The overprint
  test's area check is labeled likewise.
- Recoloring a traced shape bounds the area the renderer that drew the
  trace object measures too, inflated by half the stroke and a pixel.
- The let-go test pins the nothing-to-draw branch: no frame, whole or
  part, began since the measure.
- The parity doc's feature row names the stack overflow fix and its
  test; a group test's doc says what its fixture holds.
2026-11-09 14:18:05 -07:00
dotCooCoo faea85f772 Fix #6125: raster exports keep a Screen glow on the bare page
A raster export filled its background into the page before the art, so
blend modes read it as art underneath: an Outer Glow in Screen mode, the
default, turned white on a white background and was missing from PNG and
JPEG files while the canvas showed it. Blending against nothing also
differed from the canvas for Difference and the other modes.

The background now goes under the finished art, in frames and in object
thumbnails. A blend mode reads only the art below it. A white object
under a Difference object still inverts.

Page Isolated Blending no longer changes a raster export on a bare page.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-09 13:02:28 -08:00
Drew Dibble af754bcb23 Canvas redraw: the renderer's measure tests check what their names say
Review follow-ups:
- The tests that a group with effects of its own is measured once a pass
  plant its kept bounds after the first artboard and read them back over
  the next two, so a measure made again fails. Its solid is built by the
  free measure, which the renderer's three_d_built doesn't count.
- The budget test checks that every object, past the budget too, is
  measured as the free measure does and draws as a new renderer draws it.
- A 3D effect that builds nothing draws the object exactly as without it,
  with a Drop Shadow or Gaussian Blur too, which reach past the path. The
  doc comment said those differ past the path; they don't.
- The brush widened in view first checks that the wider brush draws
  otherwise.
- Recoloring a traced shape changes no more than the shape, its stroke
  and a pixel's anti-aliasing.
2026-11-09 13:13:17 -07:00
Drew Dibble 049f8c3963 Canvas redraw: tests for edits measured under overprint, with Vector Halftone, and to the instance symbol editing came from
Review follow-up: these three branches of what a renderer measures had no
test. Under Overprint Preview a renderer measures an edit as changed()
does and holds none of the 3D art it made for it; a 3D effect with
Vector Halftone is measured by the free measure, with no solid built and
no art held; and on screen in symbol editing mode, an edit to the
instance it was entered from changes nothing. Each test fails with its
branch taken out.
2026-11-09 13:10:41 -07:00
Drew Dibble d8472b2acc Canvas redraw: a trace object's source in each version; review follow-ups
Review of #6290.

- An Image Trace object's hidden source image counts as shown in each
  version by that version's own first member: an edit that puts it
  first again (undoing Send to Back on a traced shape), or a new source
  image in its place, redraws where it shows. Tested with an image
  source on screen with the objects' views.
- A shape of an Image Trace object in Outline view is left out by the
  rule every object is (node_checks::left_out), so the drawing and the
  measure of an edit can't disagree.
- The renderer keeps the brush library's key from its first measure of
  3D art until the library changes; the test says so, and that a change
  lets it go.
- The test of a 3D effect that builds nothing covers it on a fill and
  on a stroke, without effects of the object's own (exactly as without
  it) and under a raster effect.

On the commit before, "Canvas: a 3D effect on a fill that builds nothing
draws without it": it needs no effect of the object's own. A 3D effect
on a fill or stroke that builds nothing overflowed the stack on its own
too. With a raster effect, the object now draws from its art, where
without the 3D effect it draws as a path, whose raster effects are
clipped to the path's own geometry, cutting off the item's Roughen
spikes: the new drawing is the one that keeps them.
2026-11-09 02:18:53 -07:00
Drew Dibble 3617c678b7 Canvas redraw: what the renderer leaves out measured as nothing, alike
Review of #6290.

- An edit is measured as the renderer draws: an object it leaves out
  (hidden, a template layer or guide the view hides, drawn hidden, the
  instance a symbol is edited from) is measured as nothing, by one
  predicate the renderer and the measure share (node_checks::left_out,
  before culling).
- An Image Trace object's members are compared one by one again, its
  hidden source image, which some views draw, counted as shown: moving
  the source redraws where it was and is, and recoloring a traced shape
  redraws only the shape.
- The renderer checks the brush library between frames only while it
  keeps bounds measured with brushed strokes in 3D art.
- Tests: the sweep of 3D art's bounds counted while the art is held,
  and Fit to Artwork Bounds' menu answer kept for a revision.
2026-11-09 00:29:06 -07:00
Drew Dibble b9dbd867f6 Canvas: a 3D effect changed on type or a group redraws where its solid was
When an edit changes an object, the canvas draws again only the area
the object painted before and after it, measured by its cull bounds.
Type and groups with Extrude & Bevel, Inflate or Rotate were measured by
their letters or members, not by the solid the effect draws, which
reaches well past them. Making the solid shallower or turning it left
pieces of the old solid on the canvas outside that area until the view
was drawn again.

An object with a 3D effect is now measured by the effect's art, as the
renderer draws it: with its geometry effects, each object in it with
the raster effects of its own fills and strokes and the art its brushed
strokes draw, grown by the reach of the object's own raster effects.
One function measures that art for every bounds the renderer and
exports take, where three copies of it disagreed.

- The renderer measures the art it draws. What it makes to measure an
  object it holds until the frame ends, with what the art read from
  outside the object (fonts, symbols, color settings), and keeps it
  with the art it draws if it draws the object while that is
  unchanged. Art measured but not drawn (culled, off the view) isn't
  kept. It holds at most as much as it keeps for the art it draws
  (64 MB); past that, an object is made again to draw it. An object
  whose 3D effect builds nothing (Revolve on a group, a solid too
  complex) is measured as it is drawn otherwise, as without a renderer.
  FrameStats::three_d_built counts the 3D art the renderer makes.
- The canvas measures what an edit changed through the renderer that
  drew the frame shown (Renderer::changed). When it draws the part on
  its own thread, it draws it with the art it made to measure it; when
  the render worker draws the frame, or nothing in view changed, that
  art is let go. Under Overprint Preview or Separations Preview, which
  draw copies of the objects, the edit is measured afresh. An object
  the frame doesn't draw (hidden, in a hidden group or layer, or
  hidden by the view) is measured as nothing, before and after, so an
  edit to it builds no solid: changed() without a renderer counts it as
  nothing too, where it measured it. The canvas measures the edit
  before it knows where the frame is drawn: when the worker draws it,
  the solids it built to measure it are built again there.
- Fit to Artwork Bounds, the Artboards panel's check for it and
  per-artboard SVG export measure every artboard through one renderer
  (Renderer::art_bounds_on, Renderer::effect_bounds), which keeps each
  object's bounds for the pass, a group holding 3D art or a hidden
  member included, so each solid is built once: 30 extruded type
  objects over 30 artboards take about 2 ms, where measuring their
  letters took under 1 ms and building each solid for each artboard
  would take 70 ms. Objects with brushed strokes, which culling leaves
  room for and exports don't, are measured on each artboard. The
  Artboard tool's canvas menu keeps whether Fit to Artwork Bounds has
  art to fit for the document's revision, where it measured it on
  every frame it was open.
- Other exports, Object › Rasterize and changed() without a renderer
  build the art on each call, as 3D paths and groups already did.
  Exports and Rasterize now take the solid of 3D type, where they
  cropped it to the letters, and the art its brushed strokes draw, not
  the room culling leaves for it. Rasterize on drawn 3D art measures it
  once while the art is kept.
- A shadow after a 3D effect, on the object or on a fill after the
  fill's own 3D effect, reaches past the solid once; it was counted
  twice.
- With Crop Marks or Vector Halftone, which draw other art, the object
  is measured by that art, as before.
- Culling leaves room for a 3D object's brushed strokes as for any
  object's, and the renderer measures a 3D object again when the brush
  library changes: a brush widened past that room would leave the
  object culled.

A document's first frame builds the solid of every 3D object, those off
the view too, to know whether it reaches into the view: 200 extruded
words with 150 off the view take about 12 ms, where measuring their
letters took 6 ms.

Type whose object effects leave it as it is (Revolve builds no art from
type) is measured by its own box grown to hold its glyphs, as plain
type is; it was measured by its box alone, which needn't hold them.
2026-11-08 23:17:50 -07:00
BFlat 04aedc87e5 Merge pull request #6268 from dotCooCoo/fix/freeform-mixes-in-point-model
Gradients: freeform points that share a color model mix in that model
2026-11-09 10:08:22 +08:00
dotCooCoo ac0bb3c4ce Gradients: freeform points that share a color model mix in that model
The canvas, the gradient chips and the SVG, PDF, EPS and EMF writers mixed
freeform points in display RGB, while Object > Expand and Create Gradient
Mesh mixed them in the model the points share. Points that are all CMYK,
Gray, Lab or RGB now mix in that model in those places too. Points in
different models mix as before: in display RGB, and on ink planes as the
inks of each point.

This changes how existing art looks:

- CMYK and Lab points. With the default Generic CMYK working space and no
  ink planes, halfway between C100 and K100 the canvas drew (17, 106, 139)
  and now draws (54, 118, 143).
- RGB points in a working RGB space other than sRGB mix in that space.
- On ink planes (a CMYK document that shows transparency), RGB and Lab
  points mix first and then separate.

Gray mixes keep their values, and CMYK and Gray points on ink planes draw
as before. RGB points in an sRGB working space draw as before except on
ink planes.

- PDF and EPS write a gradient whose points are all CMYK, or all Gray in a
  CMYK file, as a CMYK image. PDF's Output settings convert it as they
  convert any CMYK image.
- Darkening applies to the points before they mix.
- With Appearance of Black set to Rich, the canvas mixes a gradient with a
  pure CMYK black point in display RGB, and the point fades from RGB black.
2026-11-08 17:18:43 -08:00
dotCooCoo e2bac253f1 Merge main into fix/fitted-gradient-turns-with-transform 2026-11-08 15:56:12 -08:00
BFlat d83bb6c0b8 Merge pull request #5058 from dotCooCoo/feat/character-anti-aliasing-method-r
Character: Anti-aliasing Method sets how a type object's edges are drawn as pixels
2026-11-09 07:47:57 +08:00
dotCooCoo e3e716c441 Tests: translucent brushed type compares each pixel with full opacity
The test for Crisp and Strong type in a scatter brush stroke at 50% opacity
now checks that every pixel is half of what the same stroke draws at full
opacity. It fails without the coverage fix for Crisp as well as for Strong.
2026-11-08 14:05:55 -08:00
dotCooCoo d8ff63f5d0 Merge main into feat/character-anti-aliasing-method-r 2026-11-08 13:24:43 -08:00
dotCooCoo 1bfd916d63 Type: Strong type in a translucent brushed path keeps the path's opacity
Crisp and Strong reshape each edge pixel by the type's coverage, drawn
in black. Type in the art of a scatter brush on an undashed path drew
that coverage with the path's opacity folded in, so Strong made fully
covered pixels opaque at 50% path opacity. The coverage is now drawn
at full opacity; the pixels keep the folded opacity.

The evidence entry for the Anti-aliasing Method notes that scores and
hours are unchanged.
2026-11-08 13:21:48 -08:00
dotCooCoo 5aec304ef1 Merge main into fix/placed-gradient-follows-warp
# Conflicts:
#	crates/effects/src/reshape.rs
2026-11-08 13:11:12 -08:00
dotCooCoo 9d0b2b0c1c Merge main into fix/fitted-gradient-turns-with-transform 2026-11-08 13:07:04 -08:00
dotCooCoo c63f634e0c Merge main into fix/placed-gradient-follows-warp 2026-11-08 12:22:54 -08:00
dotCooCoo b88f4ce311 Merge main into feat/character-anti-aliasing-method-r 2026-11-08 12:08:40 -08:00
dotCooCoo c94269ff72 Type: Composite Fonts… sets each class of character in a font of its own
Squashed from #6244 onto main.
2026-11-09 03:08:23 +07:00
dotCooCoo aee3a76e2a Merge main into fix/fitted-gradient-turns-with-transform 2026-11-08 11:16:42 -08:00
dotCooCoo 732434b4e5 Effects: placed gradients move with Transform and Free Distort effects
A gradient placed with the Gradient tool stayed where it was when the
object's Transform or Free Distort effect moved, turned or scaled the
outline, so the shape slid across its own gradient.

The Transform effect now maps placed gradients by its own transform,
and each of its copies takes them as many times as it takes the
transform. Under Free Distort, each path's placed gradients move and
scale from the path's bounds before the effect to its bounds after it.
This change does not alter gradients fitted to the object.

The canvas, Expand Appearance and export, Pathfinder and compound
shapes, groups, a fill's or stroke's own effects, and Object › Expand
of a gradient fill apply the same rule. In a group, the own fills and
strokes of the group, of the groups inside it and of a symbol's art
follow it too, and images and symbol instances that a group's
Transform effect moves take the placed gradients of their own fills
and strokes along. A group whose path keeps its shape because the
effect would pass the geometry budget keeps its own gradients in
place.

This changes how existing documents draw: a gradient placed on a path,
or on an image's or symbol instance's own fill, inside a group with a
Transform effect now moves with it. Before, only the gradients of type
moved (#2214).

How gradients follow each effect is VectorCraft's own rule. The
Distort & Transform help page describes what the effects do to the
object:
https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/distort-and-transform-effects-in-illustrator.html
2026-11-09 00:34:12 +07:00
dotCooCoo 00ac0ac5ae Tests: placed gradients under Warp on strokes, symbol art and compound shapes
Covers the other places a Warp now moves placed gradients: a group's
and a nested group's own strokes and a compound path's stroke (with the
stroke-inflated bounds), the art of a symbol instance and the area of a
compound shape with a Warp, and a stroke's own Warp on the canvas. A
new test checks that Arc's no-bend threshold for gradients matches the
one the warp uses, and the no-bend test now covers all fifteen styles.
The render test's Fisheye case, which left the square's bounds as they
were, is replaced by a vertical Bulge, and each style there is checked
to move the outline's left or right edge. The Object > Expand test
checks that it measures the ramp and not an edge of the outline.

The params text and docs/mcp.md now state which bounds each gradient
follows, and that a warp moves them with a bend or a distortion.
2026-11-08 09:29:57 -08:00
dotCooCoo 394bbb37c0 Effects: placed gradients move and scale with the Warp effects
A gradient placed with the Gradient tool stayed where it was when the
object's Warp effect changed the outline, so the warped shape no longer
matched its gradient.

Under each Warp style the placed gradients of a path now move and scale
from the path's bounds before the warp to its bounds after it, as they
do under Free Distort. This applies to an object's own Warp and to a
fill's or stroke's own (a stroke uses its stroke-inflated bounds), on
the canvas and in Object > Expand of the fill. In a group with a Warp
effect, each path's and compound path's gradient follows its own
bounds, and the own fills of the group and of the groups inside it
follow those groups' bounds. A warp with no bend and no distortion
leaves gradients exactly where they are, and gradients fitted to the
object are unchanged.

This changes how existing documents draw: a placed gradient on art with
a Warp effect now follows the warped outline instead of staying in
place.

How gradients follow the warp is VectorCraft's own rule. What the Warp
effects do to the object is described in
https://helpx.adobe.com/illustrator/using/reshaping-objects-effects.html
2026-11-08 08:43:27 -08:00
dotCooCoo a5ea32b386 Effects: fitted gradients turn with the Transform effect
A gradient fitted to the object refit to the outline after the object's
Transform effect rotated, reflected or scaled it unevenly, so the
gradient kept running left to right while the shape turned under it.
Object › Transform already turns such a gradient.

A Transform effect that a refit would not reproduce now fixes the
gradient to its fit on the geometry the effect takes, and then maps it
with that effect and the effects after it, as a placed gradient. Under
moves and even scales it draws as a refit does, and an effect that
scales the object to nothing across leaves it fitted. This applies to
an object's own effects, a fill's or stroke's own effects, and a
group's: in a group, the gradients fitted to its paths, to its own
fill, to the groups inside it and to its images' and symbol instances'
own fills each fix to the fit on their own object. Object › Expand of
the fill draws the same.

This changes how existing documents draw: art with a fitted gradient
and a Transform effect that rotates, reflects or scales it unevenly
now shows the gradient turned with the art. How gradients follow the
effect is VectorCraft's own rule.
2026-11-08 07:50:26 -08:00
dotCooCoo c94aec3fff Envelopes: type maps from its glyph outline bounds
An envelope mapped type from its laid-out box: ascent to descent for
point type, the frame for area type. The distorted glyphs then filled
less of the envelope than other art does, with room above the capitals
and below the baseline.

The envelope now maps type from the bounds of its glyph outlines,
underlines and strikethroughs when the type is on its own, in groups
and layers, or used as a clipping path. Inline graphics count by their
boxes. Rotated type and type in perspective map from their outlines as
they are drawn. Make with Mesh,
Make with Top Object and Reset with Mesh without Maintain Shape use
those bounds. Type that draws nothing keeps its laid-out box, and so
does type inside any other object, such as a blend, a repeat or a
symbol instance. Shrink Text to Fit type is measured as it is laid out
on the page.

vectorcraft-text's envelope_ink measures the type, and
register_envelope_ink registers it with the document crate. The engine
session, Renderer::new, live evaluation in vectorcraft-effects and the
.ai reader register it. The document crate keeps the measured boxes per
thread, by object and by a hash of the content, and measures again when
the type, the fonts, the hyphenation exceptions or the transform's
scale or rotation change.

This is VectorCraft's own rule. Envelopes of type made in VectorCraft
and in saved files map from the glyph outline bounds instead of the
type's laid-out box, so they redraw differently.
2026-11-08 13:20:19 +07:00
dotCooCoo 4fefdaf95a Canvas: moving anchors of a long path redraws only around them
When only anchors of a path or compound path move, the area an edit
changed is now the box of the segments that start or end at a moved
anchor (and the line that closes an open subpath's fill at a moved
end), before and after, grown by the farthest the strokes reach. The
canvas then draws that part of the frame again instead of the whole
object. Objects with a non-solid paint, dashes, a width profile,
arrowheads, a brush, effects or an opacity mask still count whole.

At 1600 x 1200, single-threaded, the frame after one anchor of a filled
closed path moves takes 16.6 million CPU cycles instead of 56.4 million
with 50,000 anchors, and 65.6 million instead of 180.0 million with
200,000 anchors.
2026-11-08 12:21:58 +07:00
dotCooCoo 142366d9ee Effects: placed gradients move with Transform and Free Distort effects
A gradient placed with the Gradient tool stayed where it was when the
object's Transform or Free Distort effect moved, turned or scaled the
outline, so the shape slid across its own gradient.

The Transform effect now maps placed gradients by its own transform,
and each of its copies takes them as many times as it takes the
transform. Under Free Distort, each path's placed gradients move and
scale from the path's bounds before the effect to its bounds after it.
This change does not alter gradients fitted to the object.

The canvas, Expand Appearance and export, Pathfinder and compound
shapes, groups, a fill's or stroke's own effects, and Object › Expand
of a gradient fill apply the same rule. In a group, the own fills and
strokes of the group, of the groups inside it and of a symbol's art
follow it too, and images and symbol instances that a group's
Transform effect moves take the placed gradients of their own fills
and strokes along. A group whose path keeps its shape because the
effect would pass the geometry budget keeps its own gradients in
place.

This changes how existing documents draw: a gradient placed on a path,
or on an image's or symbol instance's own fill, inside a group with a
Transform effect now moves with it. Before, only the gradients of type
moved (#2214).

How gradients follow each effect is VectorCraft's own rule. The
Distort & Transform help page describes what the effects do to the
object:
https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/distort-and-transform-effects-in-illustrator.html
2026-11-07 21:11:07 -08:00
dotCooCoo cda188864b Canvas: a pan by whole pixels copies the pixels that stay on screen
Squashed from #6070 onto main.
2026-11-08 12:01:47 +07:00
Don Omondi 3f7f743ddb 3D effects: Extrude & Bevel, Inflate and Rotate apply to groups
A 3D effect on a group or layer builds a solid from every path,
compound path and type in it, all turned about one shared axis, as the
public docs describe for groups. Fixes #4506.
2026-11-07 00:17:14 +07:00
dotCooCoo e6819a86f0 Rendering: open width-profile strokes outline only near the view 2026-11-06 12:42:33 +07:00
dotCooCoo b75b1548d4 Tests: a fitted text test changes its fit scale
The regression test for rebuilding fitted area type after a scale change
used text that overflowed at the minimum size both before and after the
change, so both layouts used the same fit scale and the pixel comparison
did not depend on the fit.

The fixture is now 2 pt type in a small frame, drawn 20 times zoomed in.
It fits at 0.7 of its size, and at 0.733 after a 1.5 times scale. The
test asserts that the two fit scales differ and that the words set at the
earlier fit draw different pixels, and keeps the comparison with a fresh
renderer and the check that the geometry is not reused.
2026-11-06 12:42:26 +07:00
dotCooCoo 25da816a08 Rebuild fitted text geometry after scaling 2026-11-06 09:37:22 +07:00
dotCooCoo a723f7359a Reuse text outlines across moves 2026-11-06 08:51:36 +07:00
Simon Hull c15383c510 Images: an image zoomed in on has its colours made only for the tiles in view (#4155)
From about 50% zoom on the file of #4155, the pixels drawn are the full 141-megapixel photo,
and in a CMYK document they were separated into ink planes (or colorized, or proofed) whole
for every view: 8 to 9 s of white canvas a zoom step.

When the pixels drawn are larger than a 1,024-pixel tile, their colours are made again for the
frame and part of them is out of view, only the tiles in view are made, each kept under its own
key (`ImageKey::tile`), and drawn together as one pixmap so their edges don't show. The colours
are made pixel by pixel, so the result is the same as for the whole image. CMYK images painting
their own inks are still made whole.

Zooming in to 50%, 100% and 200% on that file went from 9.0, 8.6 and 8.7 s to 0.78, 0.05 and
0.05 s (M3 Pro, one run each), with the same pixels on screen.
2026-11-06 07:23:32 +07:00
Simon Hull 8147d33f56 Images: a reduced image is shrunk before it is separated into ink planes (#4155)
With image mips on (the canvas, navigator and layer thumbnails), an image was colorized,
separated into the frame's ink plane and shown through Separations Preview's plates at its
full size, and only then shrunk to the mip drawn. The 141-megapixel image of #4155 made
1.6 GB of planes a frame, more than the image store holds, so every frame made them again.

The mip is now picked first and everything else is made from it, under the keys `kept_mip`
and `nearest_kept` already look up. A CMYK image's own inks are halved with its pixels
(`ink::key_inks`, `images::half_inks`), so it still paints them rather than inks separated
from its displayed colours. Colour bitmap glyphs follow the same order.

On that file the app's first frame went from 9.2 s to 1.6 s and the sharp frame after a pan
from 3.9 s to 8 ms (M3 Pro, one run each). Fine detail now shows its true average: 1-pixel
black and white lines show mid grey, where separating first showed them 44 levels darker.
2026-11-06 04:02:47 +07:00
dotCooCoo 84904020e8 Images: a colorized image's missing-link placeholder shows in its own colors
A linked 1-bit image mask from a .ai file keeps its fill color as a
colorize paint, so the image takes the color once its file is relinked.
Until then the image shows the missing-link placeholder, and the canvas
and PDF export colorized that placeholder too: its light tile turned
nearly transparent and its red cross took the fill color.

ImageBlob::takes_colorize is false for a preview whose pixels are not
grayscale. While it is false, the canvas draws the image and PDF export
writes it as it is, printed separations separate it as an uncolorized
image, and transparency flattening (EPS, PDF 1.3 and PDF/X-1a export,
Flatten Transparency) does not count its paint as transparency. The
paint stays on the image, and a grayscale preview is still colorized.
SVG and EPS export already write only grayscale images colorized.
2026-11-05 23:25:05 +07:00
FractalEncrypt e0ad7ccf16 Preserve raster coverage of thin curved strokes
Squashed from #3273 onto main.
2026-11-05 20:00:21 +07:00
dotCooCoo 924ec6f0d6 Tests: random edits of long paths keep what making the path again makes
Two seeded tests run 300 random edits each on a long path and a long
compound path: anchors added and deleted at the start, middle and end of a
subpath and where a run of segments ends, subpaths closed and opened, anchors
and handles moved, and in the compound path members added and removed and
subpaths added to a member. The subpaths range from 2 anchors to 3,000, open
and closed. After each edit the kept path's elements, subpaths, bounds and
miter tips equal those of a new `KeptPath`, its runs hold each subpath's
segments, and each run's box and tips equal those measured again.
2026-11-05 14:12:17 +07:00
dotCooCoo 50128e6025 Render: adding or deleting an anchor of a long path splices the kept path
When anchors of a kept path (a path or compound path of 1,024 anchors or
more) are added or deleted, or a subpath is closed or opened, the next
frame writes again only the path elements from the first anchor that
changed to the last, and the closing segment and ClosePath when an end
anchor changed or the subpath was closed or opened. The elements after
the change move with it. Before, such an edit made the whole path again
and measured it whole.

- The runs of segments now carry their own ends. The runs that held the
  segments written become one run, split into runs of 128 when it grows
  past 256, and only those runs are measured again, with the miter tips
  of the run of the segment with length before each span. When a
  subpath is closed or opened and the stroke reaches farther from one
  than from the other, all of that subpath's tips are measured again.
- On a subpath that is open before and after the edit, the handle before
  the first anchor and the one after the last are not compared, so
  Delete Anchor Point, which clears them, writes again only the segments
  around the anchor deleted.
- A subpath that has fewer than two anchors before or after the edit is
  written again whole. A compound path member that gains or loses
  subpaths is made again. When members are added or removed, those
  members and every member between the first and the last of them are
  made again, and the members before and after them stay.
- An edit that adds or deletes anchors in, closes or opens more than 16
  subpaths makes the whole path again, as before.
- An edit that leaves every anchor as it was, such as a new stroke,
  leaves the kept path as it is.

The pixels and the cull bounds are the same as with a path made again.

Docs: docs/target-app-parity.md.
2026-11-05 14:12:17 +07:00
dotCooCoo 63c48588b5 Render: a moved path reuses the slices of a placed gradient stroke
A move also moves where a stroke's gradient is placed, but the slices of a
gradient along or across the stroke take only its stops and kind. The
moved stroke is now compared with the stroke the slices were made for
with the gradient's placement left out, so a placed gradient no longer
makes its slices again after every move.
2026-11-05 13:58:16 +07:00
dotCooCoo d88f5eeed9 Render: a moved path reuses its gradient stroke slices
A path whose stroke has a gradient along or across it kept its slices under
the node's address, so every nudge made the slices again for the whole path,
although its stroke outline was already reused. The renderer now keeps the key
of a stroke's latest slices under the object's id, the stroke's place and the
tolerance level, and when the object has only moved (same stroke and gradient,
same fill rule, anchors moved by one offset) it draws those slices moved by the
offset, translating only the slices that reach the piece being clipped. Scale,
rotate, an anchor edit or a change to the stroke or its gradient makes the
slices again. The pixels are the same.
2026-11-05 13:58:16 +07:00
dotCooCoo 815671b3a2 Render: at very large coordinates an edited path is not taken for a moved one
The check that reuses a moved path's stroke outline allowed each
coordinate to differ from the moved one by 4 x f64::EPSILON times the
sum of the compared coordinates and the largest coordinate of the first
point, about 2.7 pt near 1e15, so a 0.5 pt anchor or handle edit there
drew the outline made before the edit.

offset_between and anchors_moved now measure the offset on each axis at
the pair of points with the smallest coordinates on that axis, then
check every pair against that offset within 3 units in the last place
of the pair's own coordinates. A first anchor far from the origin no
longer sets the tolerance for points near it.

Tests: 0.5 pt anchor and handle edits at 1e9 and 1e15 (and 0.0005 pt
at 1e9); an edit near the origin of a path that starts at 1e16; a path
whose coordinates shrink with each point, moved by growing amounts.
2026-11-05 11:29:39 +07:00
dotCooCoo 2bb6db5e8f Tests: movedstroke imports wrapped to the rustfmt width 2026-11-05 10:26:08 +07:00