NaN determinants bypassed the singularity check, while determinant and translation overflow could return Some with a bogus or non-finite inverse. Reject non-finite determinants and computed inverse coefficients so callers can take their existing failure paths.
Cover all six coefficients with NaN and both infinities, determinant overflow, translation overflow with a finite determinant, and valid extreme translations.
Co-authored-by: Lucenx9 <185146821+Lucenx9@users.noreply.github.com>
Rect::tile_count counts a rect's tiles in u64; try_take_tiles uses it (no usize overflow on 32-bit). Surface::translated reuses one rebuilt tile for equal source-tile windows. auto_slices merges vertically adjacent runs through a hash index instead of searching the output.
* fix(vector): bound hostile geometry raster work
Guard geometry and tracing operations against malformed dimensions and degenerate inputs with bounded work and safe failures.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
* fix(vector): address review (knot cap on split, relative identity tol, non-vacuous test)
* revert(geom): splitting past the quilt limit is intentional (smart_map test)
* fix(geom): cap identity tolerance at 1e-6 px
---------
Co-authored-by: Codebuff <noreply@codebuff.com>
Main merged #5940, #6472, #7838 and #7922 directly. Kept from the batch:
the snapshot/History-follow identity at the history limit (#5940,
c7ef3676a; main still had the 3-tuple HistorySpot) and the strokeless
shape at a 0 px width step (#6472, 962539607; main still added the
stroke). Main already carries the other two fix-ups (34c9d0ea4, dff8bfd0e).
Overlaps with main resolved:
- #5842 vs main's #5568: one path, apply_color, tool-gated as #5842
has it, with #5568's float fill, kept fill opacity and lock skip;
shape_fill_ui's duplicate foreground_changed/gesture dropped; #5568's
tests run with a shape tool.
- #6230 vs main's #5180: one Type tool path click (path_target), no
second type.attachToPath; vertical type does not start type on a path,
as main has it; main's path_at/path_cursor and canvas arm dropped.
- #5452 moved the shape swatch to shape_paint_ui.rs: main's hex field
(#6201) ported there.
- type_extra_cmds::with_text takes main's dpi form, pub(crate) for
type_area_cmds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/file-format-parity.md and docs/gaps.md: main's status lines and rows, with #4112's entries added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Transform Warp's Cylinder (`warpCylinder`) stores seven `warpValues`
instead of a bend. WarpStyle gains Cylinder (parsed, named, not one of
the fifteen bend presets and not listed in the Warp menus yet), and
Warp::cylinder builds its mesh: four patches across (back, front left,
front right, back) and one down, in the warp's value space like a
custom mesh, so map, to_mesh and is_identity treat it as one. Warp keeps
the values for writing them back (serde: omitted when empty).
The model and its table of shape factors were measured against
Photoshop 2026's renders by the reporter of #4112 (not from Adobe's
code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EuC9R3K5QeMBrDkixa1jnt
The 15 warp presets (Edit > Transform > Warp, smart object warps, Warp
Text) were our own closed-form approximations, up to 70 px off
Photoshop's: Arc kept its centre fixed and pulled the ends in where
Photoshop keeps the inner edge's corners and raises the middle, Flag
waved the wrong way at a third of the height, Squeeze shrank instead of
pinching.
Measured on Photoshop 27.11 (scripted renders of a grid image whose
colours encode each pixel's position, 102 cases: every preset at bend
±50 and ±100, both distortions, vertical orientation, square, wide and
tall boxes), every preset is a single bicubic Bezier patch (fits within
0.5 px). Its control grid:
- Arc: each row a circular arc of b·π around one centre, the inner edge
keeping its chord; Arch, Bulge, Arc Lower/Upper and Shell built from
the same arcs. Circular in pixels on any box.
- Flag, Wave, Fish, Rise, Fisheye, Inflate, Squeeze, Twist: control
points moved linearly with the bend, in box units.
- Vertical distortion scales the top and bottom rows about their
midpoints and blends the rows between; horizontal distortion scales
each column. Vertical orientation swaps the axes and the two
distortions.
All 102 cases land within 1 px (median) of Photoshop. On the corpus's
Photoshop-made warped text, warp-arc-50 now spans 340x85 at (-3, 3)
against Photoshop's 350x88 at (-7, 3), where it was 278x74 at (28, 62).
Presets also convert to custom meshes exactly now (to_mesh splits the
patch instead of fitting). Tests that pinned the old shapes now assert
Photoshop's; presets_match_photoshop checks measured points.
Still open: Warp Text warps over our line box, while Photoshop uses the
type layer's stored `bounds`; with those the five corpus warp files land
within a few px (arc, flag, bulge with distortion, twist, vertical rise).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Image warp: grab anywhere on the grid, Ctrl-click to cut, Warp in the transform menu
Warp mode of Free Transform now works from anywhere inside the grid, not
only on its points:
- Press and drag on any part of the grid and the surface under the pointer
follows it. The surrounding anchors bend with it, fading out with
distance. The picture's border stays put unless the grab is made near
it, and then the border bows through its own handles. The four corners
never move.
- Ctrl-click (or Option-click) cuts the grid both ways at the pointer.
- Right-clicking a Free Transform box lists Warp after Perspective. The
menu marks only the box's real current mode.
- Anchor and handle drags keep every grid line smooth through its anchors.
- Grid points are quieter: anchors are small, and a handle with its arm
shows only near the pointer or while it is dragged.
- The preview draws the most displaced cells first and samples finer where
the warp pulls hard, so a strong pull no longer shows as a comb.
Refs #214
* Warp options bar: Split, Grid and Warp controls with orientation and bend always shown, Reset, and the buttons right after the fields
The Warp bar now reads Split, Grid, Warp style, orientation and Bend / H / V.
The orientation and bend fields are always there and only act on a preset
style. A new Reset button puts the grid back to the default untouched one
while staying in warp mode.
Cancel and Commit sit right after the fields instead of at the far right of
the window. A bar too narrow for both pins them to the right edge and clips
the fields, so they stay reachable. The Free Transform bar shares the same
function and gets the same placement.
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
width() and height() computed `x1 - x0` / `y1 - y0` with plain i32
subtraction. For extreme edges (e.g. an inverted i32::MAX..i32::MIN
rect, or a valid rect spanning most of the i32 range) the subtraction
overflows: debug builds panic, release builds wrap. warp.* commands
build a Rect straight from the JSON `rect` param, so an extreme value
reached this from user input.
Both now return the exact extent via `abs_diff` when the far edge is
past the near one, and 0 otherwise. The extent of any i32 pair fits in
u32, so no clamping or casting is needed. A plain `saturating_sub`
would avoid the panic but turn a wide valid rect into a width of
i32::MAX, which is wrong.
Follow-up (not in this change): warp_cmds still accepts absurd `rect`
params; it should reject them with a parameter error up front.
Co-authored-by: Luna <luna161@users.noreply.github.com>
* fix(geom): saturate Rect::translate and inflate on i32 overflow
from_xywh already uses saturating_add. translate and inflate used
plain +/- and panicked in debug (or wrapped in release) when a layer
near the i32 edges was nudged off-canvas.
* fmt: Rect::translate/inflate at the workspace width (#159)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Real crash: build_engine_data took its template from the PSD's text
engine data and did expect("EngineDict"), so a text layer whose engine
data lacked EngineDict (or wasn't a dictionary) crashed PSD export.
The template is now repaired; regression test
engine_data_template_without_engine_dict.
- color: the built-in CMYK<->sRGB transforms fall back to the naive
formulas instead of expect if they ever fail to link.
- doc: insert_above falls back to the top of the root instead of
expect("valid path"); next_layer_name searches a bounded range.
- paint: Mips::new loops with while-let instead of last().expect.
- All eight crates deny unwrap/expect/panic/unimplemented/todo/unreachable.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
CI's `cargo fmt --all -- --check` has failed on every run because the
repo had no rustfmt config and was never formatted. Use the same settings
as EffectCraft/FilmCraft (max_width 160, use_small_heuristics Max), which
also keeps the churn small. Formatting only; no code changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>