Repository navigation
Expand: pattern fills expand into their tiles clipped by the shape - #2616
Merged
Merged
Conversation
4 tasks done
Object > Expand with Fill expanded gradient fills only. A pattern fill stayed a live pattern, and object.expand.info did not report it, so the dialog's Fill option was disabled for it. In the reference app, Expand with Fill turns a pattern fill into the pattern's tiles, and the shape becomes their clipping mask. The Fill step now turns each visible pattern fill of a path or compound path into a clip group shaped like the object, holding the tiles the pattern lays over the object's box with fresh ids. The group isolates the tiles' blending and takes the fill's opacity, blend mode and other effects. Geometry effects of the fill or the object, such as Offset Path, shape the group's outline and the box the tiles cover. A path in the tile art with a random Transform effect is baked with its own id first, and a path with a brush that varies at random (calligraphic or scatter with Random variation, or bristle) is baked where the pattern defines it first, so every tile matches the canvas. The object's other fills and strokes stay on copies of it in paint order, and Expand stays one undo step. Type with a pattern fill expands the same way once Object outlines it, and object.expand.info reports pattern fills under fill. It also reports gradient fills under a new gradient key, and the dialog enables Expand Gradient To only when the selection has one. A pattern fill stays as it is when it lays no tiles over the box, when a group or type in its art has a random Transform effect, when the pattern is scaled, rotated, reflected, stretched or sheared and its art holds a symbol instance, and when the pattern is rotated, reflected, stretched or sheared, or scaled while its art has geometry effects, and its art has raster or 3D effects, or a geometry effect on a group or type. PatternDef gains instances_in_with, which places the tiles with a given Scaling. Expand has stroke weights, effect distances and the tiles' own pattern paints scale with the pattern's transform. When the pattern is rotated, reflected, stretched or sheared, or scaled while its art has geometry effects such as Roughen, the strokes and geometry effects of the tiles' paths become filled art first (bake_appearance, and Outline Stroke's conversion for brushed strokes), and their live shapes become plain paths, in their opacity masks too. Tiles place their opacity masks with them, unlinked ones included (place_art), which also fixes unlinked masks in pattern tiles in PDF, EPS, EMF and WMF export and in Distort Pattern Fills. Faults in the shared code are fixed for every caller. assemble dropped the effects of a sole part, so Expand lost a lone gradient fill's own effects and Outline Stroke lost those of a stroke with a gradient along or across it. Outline Stroke also dropped a brushed stroke's raster effects from its brush art. Expand replaced an object whose gradient fill could not be expanded with a plain copy; it now leaves it as it was. Expand left the geometry effects of a gradient fill, and with Object off those of the object, on the expanded art, where they reshaped every strip; they now shape the clip group's outline, and the gradient fits it, as for pattern fills. bake_appearance dropped a stroke's raster effects from its filled outline and filled the outline of a stroke with a gradient along or across it with a gradient across the outline's box; it now keeps the effects and makes the gradient meshes clipped to the outline, as Outline Stroke does. Envelope Options > Distort Appearance uses it too. PatternDef::offsets_covering overflowed on a region too large to count in integers, as for a pattern scaled by 1e-20; it now gives no offsets. Docs: docs/mcp.md, docs/control-protocol.md, docs/target-app-parity.md.
bflatastic
force-pushed
the
feat/expand-pattern-fills
branch
from
October 17, 2026 08:48
1f30608 to
62fd793
Compare
Member
|
Thanks @dotCooCoo, merged. It met the Intertwine expand from #2622 in |
dotCooCoo
added a commit
to dotCooCoo/vectorcraft
that referenced
this pull request
Oct 21, 2026
outline_strokes in pathops.rs now imports reshaped from expand.rs, which has had it since storytold#2616, and the copies of reshaped and with_path that this branch added to pathops.rs are removed. The two copies were identical, so Outline Stroke and Expand behave as before.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Object › Expand with Fill expanded gradient fills only. A pattern fill stayed a live pattern, and
object.expand.infodid not report it, so the dialog's Fill option was disabled for it. In the reference app, expanding a pattern fill makes the pattern "a series of tiles that lie within that window", with the original shape as their clipping mask (community thread), and "if you use a pattern fill and you expand the fill then the pattern becomes a vector for you to edit" (community thread).object.expandturns each visible pattern fill of a path or compound path into a clip group shaped like the object, holding the tiles the pattern lays over the object's box with fresh ids. The group isolates the tiles' blending. The tiles keep the shapes the canvas draws: a scaled pattern's stroke weights and effects scale with it (PatternDef::instances_in_with), and in a rotated, reflected, stretched or sheared pattern, or a scaled one whose art has geometry effects such as Roughen, the strokes and geometry effects of the tiles' paths become filled art (bake_appearance, as Distort Appearance uses, and Outline Stroke's conversion for brushed strokes), and their live shapes become plain paths, in their opacity masks too. Opacity masks in the tile art, unlinked ones included, move with each tile. The group takes the fill's opacity, blend mode and other effects. Geometry effects of the fill or the object, such as Offset Path, shape the group's outline and the box the tiles cover. A path in the tile art with a Transform effect set to Random is baked with its own id first, and a path with a brush that varies at random (calligraphic or scatter with Random variation, or bristle) is baked where the pattern defines it first, so every tile matches the canvas. The object's other fills and strokes stay on copies of it in paint order, and Expand is still one undo step.object.expand.inforeports pattern fills underfill, and reports gradient fills under a newgradientkey as well; the dialog enables Expand Gradient To only when the selection has a gradient fill. A pattern fill stays as it is when it lays no tiles over the box, when a group or type in its art has a Transform effect set to Random, when the pattern is scaled, rotated, reflected, stretched or sheared and its art holds a symbol instance, and when the pattern is rotated, reflected, stretched or sheared, or scaled while its art has geometry effects, and its art has raster or 3D effects, or a geometry effect on a group or type.assemble); a gradient fill and, in Outline Stroke, a stroke with a gradient along or across it now keep them. Outline Stroke also gives a brushed stroke's raster effects to its brush art. A gradient fill that cannot be expanded now leaves the object as it was, instead of replacing it with a plain copy. The geometry effects of a gradient fill, and with Object off those of the object, now shape the clip group's outline, and the gradient fits it; before, they stayed on the expanded art and reshaped every strip.bake_appearancenow keeps a stroke's raster effects on its filled outline and turns a gradient along or across a stroke into gradient meshes clipped to the outline, as Outline Stroke does, so Envelope Options › Distort Appearance keeps both too.PatternDef::instances_innow moves unlinked opacity masks in the tile art with each tile, for PDF, EPS, EMF and WMF export and Distort Pattern Fills too.PatternDef::offsets_coveringoverflowed on a region too large to count in integers, as for a pattern scaled by 1e-20; it now gives no offsets.docs/mcp.mddescribes pattern fills underobject.expand,docs/control-protocol.mddescribes the dialog's__gradientfield, anddocs/target-app-parity.mdhas one line for it. The change adds no user-visible strings.How it was tested
New tests (vectorcraft-engine unless named), each failing on
main:tests_objexpand::a_pattern_fill_becomes_its_tiles_clipped_by_the_object: the clip group, its 12 tiles, fresh ids, a render with no pixel off by more than 2, the undo step,object.expand.infoandfill: false.tests_objexpand::an_expanded_pattern_fill_keeps_its_transparency_and_the_stroke_on_top: 50% Multiply on the clip group, the stroke and a fill above it in paint order, the render, and the outlined stroke with Stroke on.tests_objexpand::type_filled_with_a_pattern_expands_to_outlines_holding_the_tiles: no pattern paint is left.tests_objexpand::the_tiles_of_a_scaled_pattern_keep_the_stroke_weight_the_canvas_draws: a 1 pt stroke in a pattern at 200% expands to 2 pt.tests_objexpandtests that render before and after Expand and compare:the_tiles_of_a_stretched_pattern_draw_their_strokes_stretched(200% × 100%),the_rounded_corners_of_a_stretched_pattern_stay_stretched,a_rounded_mask_in_a_stretched_pattern_stays_stretched,a_gradient_along_a_tile_stroke_follows_the_stroke_in_a_stretched_pattern,a_brushed_tile_stroke_draws_its_brush_stretched,a_flat_brush_in_a_rotated_pattern_turns_with_it(a quarter turn),a_pattern_inside_a_stretched_pattern_moves_with_each_tile,a_random_transform_in_the_tile_art_draws_every_tile_alike(as is and turned; a group with one stays live),a_random_brush_in_the_tile_art_draws_every_tile_alike(Confetti, as is and turned),roughen_in_the_tile_art_of_a_scaled_pattern_keeps_its_detail(200%),an_unlinked_opacity_mask_in_the_tile_art_moves_with_each_tileandtiles_that_blend_blend_only_with_each_other(a Multiply tile over a blue backdrop).dialogs::expand::tests::expand_gradient_to_is_disabled_for_a_pattern_fill_alone(vectorcraft-ui-egui): Fill is enabled for a pattern fill alone and Expand Gradient To is not, until the fill is a gradient.tests_objexpand::a_fill_effect_stays_on_the_expanded_fill: a drop shadow on a lone pattern fill and on a lone gradient fill.tests_objexpand::a_gradient_that_cannot_be_expanded_leaves_the_object_as_it_was: a freeform gradient on a one-anchor path.tests_objexpand::a_geometry_effect_shapes_the_clip_of_an_expanded_pattern_and_not_its_tilesandtests_objexpand::a_geometry_effect_shapes_the_clip_of_an_expanded_gradient_and_not_its_strips: an Offset Path on the fill and on the object, with{object: false, stroke: false}; the clip is the offset outline, no offset is left on the art, and the render matches.tests_strokegradient::outline_stroke_keeps_the_effect_of_a_stroke_with_a_gradient_along_itandtests_outlinestroke::a_brushed_stroke_keeps_its_effects_on_its_brush_art.bake::tests::a_baked_stroke_keeps_its_raster_effectsandbake::tests::a_baked_stroke_with_a_gradient_along_it_becomes_meshes_clipped_to_its_outline(vectorcraft-effects), andpattern::tests::instances_carry_unlinked_masks_with_themandpattern::tests::a_region_too_large_to_count_has_no_offsets(vectorcraft-doc).tests_objexpand::a_pattern_fill_that_lays_no_tiles_stays_live,tests_objexpand::a_turned_pattern_whose_tiles_have_effects_it_cannot_bake_stays_liveandtests_objexpand::a_scaled_pattern_whose_tiles_hold_a_symbol_stays_live(which also expands the pattern when it only moves) check that those pattern fills are kept and the object is left as it was.Targeted checks on the final tree:
cargo clippy --all-targets -- -D warningsfor vectorcraft-doc, vectorcraft-effects, vectorcraft-engine and vectorcraft-ui-egui;cargo fmt --all -- --check;cargo test -p vectorcraft-engine --lib;cargo test -p vectorcraft-effects;cargo test -p vectorcraft-doc --lib;cargo test -p vectorcraft-pdf -p vectorcraft-eps -p vectorcraft-metafile;cargo test -p vectorcraft-engine --test command_sweep;cargo test -p vectorcraft-ui-egui --lib i18nanddialogs::expand;cargo xtask brands,layersandassets.On Windows 11 (x86-64) with rustc 1.99.1, on top of
mainat8bc6532:CRAFT_FONTS_DIR=<craft-fonts checkout> cargo xtask cipasses its 9 steps: 7,394 tests passed, 0 failed, 11 ignored.git merge-treereports no conflicts withmainat8bc6532. With #2326 it conflicts in theobject.expandparams text, where keeping both edits resolves it. With #1196 it conflicts in the stroke branch ofbake_appearance, which both change: #1196's block, with this change's gradient-mesh case and the stroke's effects on itsFillLayeradded forbase, resolves it. It also conflicts in the item loop ofexpand_fills, where keeping this change'sfill_shapecall and #1196'spaintscheck resolves it.Checklist
cargo xtask cipasses (fmt, clippy, tests, assets, brands, layers, wasm).unwrap(),expect(),panic!,unreachable!,todo!orunimplemented!outside tests, and no unchecked indexing on data from files, parameters ormessages. Errors are returned as
Resultand shown to the user(why and how).
ASSETS.md.