tscircuit / tscircuit/image-utils

Separate filled SVG paths are incorrectly classified as holes

Open
#27 0 comments 0 reactions 0 assignees View on GitHub

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.