Search code examples
pythonpython-3.xsvgpolygonspycairo

Filling a polygon with with multiple holes with pyCairo


I't trying to fill a polygon with pyCairo, but I want certain areas to ramin unfilled. For instance, I'd like to produce something like this:

enter image description here

The grey background is the background of the SVG viewer, so represents transparent portions of the image.

I tried with this code:

import cairo

cairo.FILL_RULE_EVEN_ODD
svg_file_pointer = open('CairoPoly.svg', 'wb')
shape_container = cairo.SVGSurface(svg_file_pointer, 500, 500)
shape_description = cairo.Context(shape_container)
shape_description.rectangle(0, 0, 500, 500)
shape_description.clip_preserve()
shape_description.stroke()

shape_description.set_line_width(1)
shape_description.set_source_rgb(20/51, 0, 0)
for r in ((100, 100, 400, 400), (200, 200, 350, 300), (150, 110, 190, 390)):
    shape_description.move_to(r[0], r[1])
    shape_description.line_to(r[0], r[3])
    shape_description.line_to(r[2], r[3])
    shape_description.line_to(r[2], r[1])
    shape_description.line_to(r[0], r[1])
shape_description.close_path()
shape_description.fill()
shape_container.finish()
svg_file_pointer.close()
del shape_container

But it doesn't work. Can this be done with pyCairo, and if yes, how?


Solution

  • I'm not familiar with pyCairo, but the basic idea of what you are doing is correct from an SVG perspective. You are close, but not quite right.

    What you want to do is create a path with sub-paths for each of your rectangles.

    <svg width="500" height="500">
        <path d="M 100,100 L 100,400 L 400,400 L 400,100 L 100,100
                 M 200,200 L 200,300 L 350,300 L 350,200 L 200,200
                 M 150,110 L 150,390 L 190,390 L 190,110 L 150,110 Z"
              fill-rule="evenodd" />
    </svg>

    It looks like this is more or less what you are doing, but you'll also need to change the fill-rule to "evenodd". The default of "nonzero" will result in your "holes" being filled in instead of being left as holes.

    set_fill_rule(cairo.FILL_RULE_EVEN_ODD)