Skip to content

Separate filled SVG paths are incorrectly classified as holes #27

Description

@ugin-man

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 81a79bc (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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions