tscircuit / tscircuit/image-utils
Separate filled SVG paths are incorrectly classified as holes
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 10
- PR merge metrics
- No merged PRs in 30d
Description
getSvgBRepShapes combines rings from separate SVG path elements before classifying containment. A black square with another separately filled black square inside it becomes a square with a hole, even though the center is painted by both source elements.
Reproduced on 81a79bc3665a4fedab02d3a8e273b9ac5d1af906 (0.0.10). The test checks whether the center belongs to any output outer ring without being excluded by its inner rings:
import { expect, test } from "bun:test"
import { Polygon, point } from "@flatten-js/core"
import { identity } from "transformation-matrix"
import { getSvgBRepShapes } from "../lib/svg-to-brep-shapes"
test("a separately filled inner path does not cut a hole in an outer path", () => {
const svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><path fill="black" d="M0 0H10V10H0Z"/><path fill="black" d="M3 3H7V7H3Z"/></svg>'
const shapes = getSvgBRepShapes({ svg, width: 10, height: 10, transform: identity() })
const containsCenter = (vertices: {x:number,y:number}[]) => {
const polygon = new Polygon()
polygon.addFace(vertices.map(p => point(p.x, p.y)))
return polygon.contains(point(0, 0))
}
const centerFilled = shapes.some(shape => containsCenter(shape.outer_ring.vertices) && !shape.inner_rings.some(ring => containsCenter(ring.vertices)))
console.log(JSON.stringify(shapes))
expect(centerFilled).toBe(true)
})
Run bun test tests/separate-filled-paths.test.ts. Expected centerFilled=true; actual=false. The output assigns the inner square to inner_rings of the outer square.
SVG fill operates on each graphical element; a separate path should not toggle another path's interior. See SVG 2 fill properties. Both elements here explicitly use the same opaque black fill, so this reproduction does not depend on color compositing or differing fill rules.
The path boundary is lost in getTransformedSvgPathRoutes through flatMap, then getSvgBRepShapes runs containment parity globally. Preserve per-element grouping when classifying holes; combine the resulting filled geometry without subtracting independent filled paths.
Related #24 improves whole-ring containment, but does not preserve element grouping; this reproduction uses strictly nested convex squares, so centroid/partial-overlap ambiguity is absent. This is a geometry-output reproduction, not a browser screenshot comparison.
Investigated with Codex (Astra) assistance.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with tests/separate-filled-paths.test.ts and getSvgBRepShapes in lib/svg-to-brep-shapes. Trace how getTransformedSvgPathRoutes and its flatMap feed containment classification. Run bun test tests/separate-filled-paths.test.ts; done means the separate inner filled path leaves centerFilled true rather than becoming an inner ring of the outer path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bun, typescript
- Domain
- computer-graphics
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100