Search code examples
pdf-generationvector-graphicscairorasterizingpycairo

How can I prevent cairo from rasterizing my pattern fills?


Some time around 2011, I wrote a Pycairo script to generate a PDF that included several fills of custom vector patterns. Today I re-ran it (Python 3.5.2, Pycairo 1.10.0) and was surprised to see that all these patterns were rendered as low-resolution rasterized bitmaps. I reduced my script to this minimal example:

#!/usr/bin/python3

import cairo

def main():
    surface = cairo.PDFSurface("test.pdf", 100, 100)
    ctx = cairo.Context(surface)
    pattern = make_pattern()
    ctx.rectangle(10, 10, 80, 80)
    ctx.set_source(pattern)
    ctx.fill()
    surface.finish()

def make_pattern():
    pattern_surface = cairo.PDFSurface(None, 32, 8)
    ctx = cairo.Context(pattern_surface)
    ctx.set_line_width(.5)
    ctx.set_source_rgb(0,0,0)
    ctx.move_to(5, 6)
    ctx.line_to(27, 2)
    ctx.stroke()
    pattern = cairo.SurfacePattern(pattern_surface)
    pattern.set_extend(cairo.EXTEND_REPEAT)
    return pattern

if __name__ == "__main__":
    main()

The resulting PDF, heavily zoomed, renders the pattern like this:

script output

Eyeballing the text of the PDF file confirms that this is a bitmap. Using an SVGSurface produces similar results. Is there a way to revert to the old behaviour whereby PDF pattern fills were rendered as vector fills in the final PDF rather than being rasterized like this? The only reference I've found online to the problem is this unanswered question on the cairo mailing list from January 2012.


Solution

  • I still haven't found a way to do this strictly using Pycairo, but I have found a solution using cairocffi, an improved, drop-in replacement for Pycairo. cairocffi offers the class RecordingSurface,

    a surface that records all drawing operations at the highest level of the surface backend interface, (that is, the level of paint, mask, stroke, fill, and show_text_glyphs). The recording surface can then be “replayed” against any target surface by using it as a source surface.

    I modified the script to use cairocffi and RecordingSurface:

    #!/usr/bin/python3
    
    import cairocffi as cairo
    
    def main():
        surface = cairo.PDFSurface("test.pdf", 100, 100)
        ctx = cairo.Context(surface)
        pattern = make_pattern()
        ctx.rectangle(10, 10, 80, 80)
        ctx.set_source(pattern)
        ctx.fill()
        surface.finish()
    
    def make_pattern():
        pattern_surface = \
            cairo.RecordingSurface(cairo.CONTENT_COLOR_ALPHA, (0, 0, 32, 8))
        ctx = cairo.Context(pattern_surface)
        ctx.set_line_width(.5)
        ctx.set_source_rgb(0,0,0)
        ctx.move_to(5, 6)
        ctx.line_to(27, 2)
        ctx.stroke()
        pattern = cairo.SurfacePattern(pattern_surface)
        pattern.set_extend(cairo.EXTEND_REPEAT)
        return pattern
    
    if __name__ == "__main__":
        main()
    

    This resulted in a non-rasterized pattern:

    script output