Repository navigation
hyperfy on crac - #2
Merged
Merged
Conversation
<flowRoot> (SVG 1.2 flowed text, which Inkscape writes for text typed into a frame) was dropped without a word: usvg doesn't draw it. The text pass now reads it as area type: the first <rect> of its <flowRegion> is the frame (with the rect's transform), each <flowPara> a paragraph (white space collapsed), styled through the same cascade as <text>. Another kind of region leaves the text out with a warning. Fixes #1568
… to missing, Polish row count
The public Pathfinder documentation says Trim and Crop remove any strokes, and Merge removes them and merges same-coloured fills. The pieces kept every stroke, and Merge, grouping by the whole style, left same-filled objects with different strokes apart. The three now drop stroke layers before styles are compared. Fixes #1570
warp_point worked on box coordinates normalised to -1..1 on both axes: Arc's radius was in x units but its edges were offset in y units, so a box that isn't square bent into no circular arc (skewed sides from Bend 10 %), and at 100 % the radius (2/π) was smaller than the half-height, folding the inner edge over. warp_point now takes the box's aspect along the warp; Arc keeps the centre line's length on a circular arc in real proportions, with concentric edges, and an inner edge that would pass the centre stops there. Other styles are as they were; a square is bent as before. Fixes #1578
Commands whose description takes `ids` (operate on these objects; default: the selection) failed with "nothing selected" when objects were given but none was selected: execute checked their `enabled` predicate against the selection, with an exception for edit.clear only. Of the 220 commands mentioning ids, 48 were refused so; their run already used the ids. execute now evaluates `enabled` with the given ids selected and puts the selection back, so each predicate keeps its meaning (Make with Top Object still needs two objects). Fixes #1575
…document objects_at() found Select Behind targets by running hit_test_skipping() in a loop, skipping everything found so far. Each round is a full tree traversal, so a stack of n overlapping paths costs O(n^2) hit tests before the loop finds no further object. The fast path walks each editable top-level layer once. Every selectable object's subtree is visited only until its first hit, and a HashSet records found ids so subtrees already covered by an earlier hit are not searched again. Sublayers recurse the same way, so a stack of n paths costs one traversal total. The original traversal stays in charge where Select Behind semantics differ from the plain front-to-back order: isolation (scope), and Edit Contents, which exposes objects nested inside a top-level group. Non- layer roots in malformed files also fall back. objects_at_repeated() keeps the old loop for those cases. Three tests: a 256-path stack asserts front-to-back order and completeness; a mixed document (sublayer, groups, clipping group, outline and path-only options) asserts the one-pass result equals the repeated-traversal result; an envelope in Edit Contents asserts the fallback preserves the original order.
… scan Indexed export (GIF, PNG-8, BMP, TIFF) finds each pixel's nearest palette entry with a linear scan, and the k-means refinement in median cut does the same once per cell per pass. With the 256-entry palettes this code produces, that is 256 distance evaluations per query. PaletteIndex is an exact nearest-neighbour kd-tree over the palette, balanced at the widest weighted axis, with tight channel bounds in every subtree so branch-and-bound can skip whole boxes. Small palettes (12 or fewer entries) keep the scan, which is cheaper than tree traversal. Results match the linear scan exactly, including ties: an equal-distance entry with a smaller index still wins. The bound uses the same weighted squared f32 form as dist() rather than a transformed space, so rounding cannot change which entry is chosen. Three tests: randomized parity against the original scan across palette sizes spanning the cutoff, three weight vectors, duplicate colours and fractional query points; explicit tie-breaking cases; and an opt-in benchmark comparing 100k queries at both, behind -- --ignored.
With paths selected but every offset empty (an inset larger than the shape), Offset Path failed with "select paths", sending the user to look for a selection problem. It now says the offset leaves nothing of the selected paths; "select paths" stays for selections without paths. Fixes #1590
… names the key, checked over every command
… documents checked against the repeated hit test
moxcms's encode packs tagged elements back to back, so the built-in sRGB profile embedded in JPEG, TIFF and PSD exports was 634 bytes with `cprt` at offset 558, where ICC.1 §7.1.2 requires every tagged element on a 4-byte boundary (stricter readers reject the files). encode now lays the tag data out again with each element padded, updating the tag offsets, the size and the profile ID (zeroed, "not computed"); tags that share data keep sharing it. Reported to moxcms as awxkee/moxcms#198. Fixes #1584
Auto Grouping (params `autoGrouping`, a checkbox in the panel; off unless asked
for): each traced path that has others inside its outer contour comes out in a
group with them, the path first, nested as deep as the picture is. The path that
covers the whole image is nobody's group.
The grouped result paints the same picture: where paths can overlap (stacked
layers, Flat Logo, strokes) a path only joins a path below it, and only if that
moves it under nothing that shares any of its box.
The harness measures three presets with the option on ("grouped %"), painting
them in the grouped order.
Pathfinder: Trim, Merge and Crop remove strokes
SVG import: Inkscape flowed text (flowRoot) becomes area type
Warp › Arc: bend in the object's real proportions
Commands: objects given as ids stand in for the selection
Select Behind: test each object once instead of re-hitting the whole document
…-index Palette nearest-colour lookup: branch-and-bound index over the linear scan
ICC profiles written here have their tags 4-byte aligned
Offset Path: an empty result says the offset leaves nothing
Image Trace: Auto Grouping
Type on a path is written glyph by glyph, and a stroke added to it as each glyph's outline stroked right after the glyph. The path in between ended the line, so each glyph came in as its own text object. Such a stroke, in the glyph's own paint, is now held with the line and written after it, where it becomes the type's stroke as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> # Conflicts: # crates/pdf/src/tests_import_lines.rs
Straight type takes the tracking that keeps its length as the file set it; type on a path took none, so letter-spaced type on a curve came back closed up to the font's advances (an "OFFICIAL ISSUED" badge sat to one side of its band, a condensed title ran off the end of its path). Type on a path now takes the same tracking, its length measured along the path from the first glyph to the end of the last. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ct doesn't take (#1592)
PDF/.ai import: evenly tracked type on a path keeps its length
PDF/.ai import: type stroked glyph by glyph stays one object
Gradients (params `gradients`, a checkbox in the panel for Grayscale and Color; off unless asked for): an area whose colour changes evenly along one direction is traced as one path filled with a linear gradient, not as bands of flat colour. Gradients are found before the palette is made, so no colour is spent on their bands: a connected run of smoothly changing pixels is a gradient when a plane fits each colour channel with all three sloping the same way, the colours along that way are at most six stops with ramps between them, and painting them back gives the area's own pixels. Flat colour, soft edges between flat colours and shading that goes two ways are left as they were. The harness gains two linear-gradient inputs with known lines and stops, and measures whether each is found, the angle of its line and the colours of its ramp.
Effects: effect.apply and effect.setParams refuse parameters the effect doesn't take (#1592)
Image Trace: Gradients
There was a problem hiding this comment.
🟡 Changes recommended
Gradient tracing can exceed memory limits, grouping is unbounded, and effect/SVG parameter handling has correctness gaps.
4 open findings
What changed in this PR
This broad PR adds Image Trace grouping/gradients alongside assorted import, rendering, validation, performance, and documentation updates.
Changes:
- Adds gradient detection and automatic grouping to Image Trace.
- Improves SVG/PDF text import, hit testing, quantization, warping, and ICC output.
- Expands effect validation, command targeting, tests, localization, and progress documentation.
| File | Description |
|---|---|
ROADMAP.md |
Records recent project progress. |
docs/ui-parity.md |
Updates interaction parity status. |
docs/type-parity.md |
Documents Linux font discovery. |
docs/roadmap.md |
Updates milestone history. |
docs/mcp.md |
Documents effect parameter validation. |
docs/localization-parity.md |
Corrects Polish catalog counts. |
docs/gaps.md |
Updates ranked gaps and fixes. |
docs/file-format-parity.md |
Records import and native-save improvements. |
docs/effects-parity.md |
Records raster-effect export fixes. |
crates/ui-egui/src/panels/image_trace.rs |
Adds grouping and gradient controls. |
crates/ui-egui/src/menus.rs |
Uses session-aware parameter checks. |
crates/ui-egui/src/lib.rs |
Passes session to validation. |
crates/ui-egui/src/i18n/zh-hant.tsv |
Adds Traditional Chinese translation. |
crates/ui-egui/src/i18n/zh-hans.tsv |
Adds Simplified Chinese translation. |
crates/ui-egui/src/i18n/vi.tsv |
Adds Vietnamese strings. |
crates/ui-egui/src/i18n/uk.tsv |
Adds Ukrainian strings. |
crates/ui-egui/src/i18n/ru.tsv |
Adds Russian strings. |
crates/ui-egui/src/i18n/pt-br.tsv |
Adds Brazilian Portuguese error text. |
crates/ui-egui/src/i18n/pl.tsv |
Adds Polish strings. |
crates/ui-egui/src/i18n/ko.tsv |
Adds Korean strings. |
crates/ui-egui/src/i18n/ja.tsv |
Adds Japanese strings. |
crates/ui-egui/src/i18n/it.tsv |
Adds Italian strings. |
crates/ui-egui/src/i18n/hu.tsv |
Adds Hungarian strings. |
crates/ui-egui/src/i18n/fr.tsv |
Adds French strings. |
crates/ui-egui/src/i18n/es.tsv |
Adds Spanish strings. |
crates/ui-egui/src/i18n/de.tsv |
Adds German strings. |
crates/ui-egui/src/i18n/cs.tsv |
Adds Czech strings. |
crates/ui-egui/src/control.rs |
Applies session-aware validation. |
crates/trace/tests/quality/raster.rs |
Renders traced gradients in tests. |
crates/trace/tests/quality/measure.rs |
Measures grouping and gradient quality. |
crates/trace/tests/quality/main.rs |
Expands the quality report. |
crates/trace/tests/quality/inputs.rs |
Adds synthetic gradient inputs. |
crates/trace/src/logo/pipeline.rs |
Initializes gradient metadata. |
crates/trace/src/lib.rs |
Integrates grouping and gradient tracing. |
crates/trace/src/group.rs |
Implements automatic nesting. |
crates/trace/src/gradient.rs |
Implements linear-gradient detection. |
crates/svg/tests/import_text.rs |
Tests flowed-text import. |
crates/svg/src/import/text.rs |
Imports flowRoot as area text. |
crates/render/src/encode/quantize.rs |
Adds indexed palette lookup. |
crates/pdf/src/tests_realtext.rs |
Tests tracked path text. |
crates/pdf/src/tests_import_lines.rs |
Tests glyph-outline stroke handling. |
crates/pdf/src/import.rs |
Holds glyph strokes during text import. |
crates/pdf/src/import_text.rs |
Restores path-text tracking. |
crates/mcp/src/headless.rs |
Uses session-aware validation. |
crates/engine/tests/param_keys.rs |
Tests backward-compatible effect replay. |
crates/engine/src/tests_rasterfilters.rs |
Updates raster-effect parameter tests. |
crates/engine/src/tests_pathops.rs |
Tests Pathfinder and offset behavior. |
crates/engine/src/tests_params.rs |
Tests effect parameter validation. |
crates/engine/src/tests_ids_without_selection.rs |
Tests explicit command targets. |
crates/engine/src/tests_build.rs |
Tests grouping and gradients. |
crates/engine/src/lib.rs |
Enables commands using explicit IDs. |
crates/engine/src/cmd/pathops.rs |
Fixes Pathfinder strokes and offset errors. |
crates/engine/src/cmd/params.rs |
Validates effect parameters. |
crates/engine/src/cmd/effectcmd.rs |
Resolves edited effect types. |
crates/engine/src/cmd/buildcmds.rs |
Builds grouped gradient traces. |
crates/effects/src/warp.rs |
Applies aspect-aware warping. |
crates/effects/src/lib.rs |
Clarifies Free Distort parameters. |
crates/doc/src/live.rs |
Corrects Arc warp proportions. |
crates/doc/src/hit.rs |
Optimizes Select Behind hit testing. |
crates/color/src/cms/tests_icc.rs |
Tests ICC tag alignment. |
crates/color/src/cms/icc.rs |
Aligns encoded ICC data. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+116
to
+120
| let found = holders.find(|&(p, op)| { | ||
| ox.pts.first().is_some_and(|pt| inside_or_near(*pt, op)) | ||
| && (!stacked || under().iter().all(|&y| !comes_after(&parent, &depth, y, p))) | ||
| && ox.pts.iter().all(|pt| inside_or_near(*pt, op)) | ||
| }); |
| let img = smoothed.as_ref().unwrap_or(img); | ||
| let mut q = quantize(img, params); | ||
| // Gradients are found first and kept out of the palette: no colour is spent on bands of them. | ||
| let found = (params.gradients && params.mode != Mode::BlackAndWhite).then(|| gradient::detect(img)).filter(|f| !f.1.is_empty()); |
Comment on lines
+235
to
+239
| match defaults.and_then(|d| d.get(key)) { | ||
| Some(Value::Number(_)) if !v.is_number() => return Err(bad(id, format!("`{key}` must be a number"))), | ||
| Some(Value::Bool(_)) if !v.is_boolean() => return Err(bad(id, format!("`{key}` must be true or false"))), | ||
| // (A colour is also taken as `[r, g, b]` or as a colour object.) | ||
| Some(Value::String(_)) if v.is_number() || v.is_boolean() => return Err(bad(id, format!("`{key}` must be a string"))), |
Comment on lines
+583
to
+587
| let paras: Vec<XNode> = t.descendants().filter(|n| n.is_element() && n.tag_name().name() == "flowPara").collect(); | ||
| let collapse = |n: XNode| { | ||
| n.descendants().filter(XNode::is_text).filter_map(|c| c.text()).collect::<String>().split_whitespace().collect::<Vec<_>>().join(" ") | ||
| }; | ||
| let text = paras.iter().map(|p| collapse(*p)).collect::<Vec<_>>().join("\n"); |
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
How it was tested
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.