Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a gradient fill in a PDF file using reportlab

Is it possible to create a gradient fill in a PDF using ReportLab (python)?

like image 394
Adam Tegen Avatar asked Jan 16 '09 21:01

Adam Tegen


3 Answers

ReportLab now supports PDF gradients.

A patch for gradient support was posted to the ReportLab mailing list by Peter Johnson on 6 August 2012 and was added to the source the next day. I cannot spot anything in the release notes for ReportLab 2.6 but since that was released on the 1st of October 2012 presumably it is in there. It is definitely present in 2.7.

Both linear and radial gradients with multiple stops can be specified. Searching the documentation for the term gradient doesn't turn up anything. However, the message with the first version of the patch has a couple of examples which are the basis of some tests in the ReportLab source. Based on this I've worked up a quick demo script:

from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.colors import red, yellow, green
from reportlab.lib.units import mm

c = Canvas("gradient.pdf")

# Linear gradient with the endpoints extending over the page.
c.linearGradient(105*mm, 200*mm, 180*mm, 100*mm, (red, yellow))
c.drawString(5*mm, 290*mm, "c.linearGradient(105*mm, 200*mm, 180*mm, 100*mm, (red, yellow))")
c.line(105*mm, 200*mm, 180*mm, 100*mm)
c.showPage()

# Linear gradient constrained within the endpoints.
c.linearGradient(105*mm, 200*mm, 180*mm, 100*mm, (red, yellow), extend=False)
c.drawString(5*mm, 290*mm, "c.linearGradient(105*mm, 200*mm, 180*mm, 100*mm, (red, yellow), extend=False)")
c.line(105*mm, 200*mm, 180*mm, 100*mm)
c.showPage()

# Linear gradient with multiple stops.
c.linearGradient(105*mm, 200*mm, 180*mm, 100*mm, (red, yellow, green), (0, 0.8, 1), extend=False)
c.drawString(5*mm, 290*mm, "c.linearGradient(105*mm, 200*mm, 180*mm, 100*mm, (red, yellow, green), (0, 0.8, 1), extend=False)")
c.line(105*mm, 200*mm, 180*mm, 100*mm)
c.line(141*mm, 102*mm, 189*mm, 138*mm)
c.showPage()

# Radial gradient with the endpoint extending over the page.
c.radialGradient(105*mm, 200*mm, 60*mm, (red, yellow))
c.drawString(5*mm, 290*mm, "c.radialGradient(105*mm, 200*mm, 60*mm, (red, yellow))")
c.circle(105*mm, 200*mm, 60*mm)
c.showPage()

# Radial gradient constrained within the circle.
c.radialGradient(105*mm, 200*mm, 60*mm, (red, yellow), extend=False)
c.drawString(5*mm, 290*mm, "c.radialGradient(105*mm, 200*mm, 60*mm, (red, yellow), extend=False)")
c.circle(105*mm, 200*mm, 60*mm)
c.showPage()

# Radial gradient with multiple stops.
c.radialGradient(105*mm, 200*mm, 60*mm, (red, yellow, green), (0, 0.8, 1))
c.drawString(5*mm, 290*mm, "c.radialGradient(105*mm, 200*mm, 60*mm, (red, yellow, green), (0, 0.8, 1))")
c.circle(105*mm, 200*mm, 48*mm)
c.circle(105*mm, 200*mm, 60*mm)
c.showPage()

c.save()

This outputs six pages with various gradients plus the gradient method call and lines/circles showing where the endpoints and stops are:

Basic linear gradient extending over pageLinear gradient constrained within endpointsLinear gradient with multiple stopsBasic radial gradient extending over pageRadial gradient constrained within radiusRadial gradient with multiple stops

like image 66
Blair Avatar answered Oct 13 '22 08:10

Blair


[My answer is no longer correct, gradients are now available in Reportlab, see the other answer on this page for details.]

Sorry to ressurect this question, but I stumbled across it and it hadn't been correctly answered.

The answer is no, as of today, the current version of ReportLab does not support gradients. Gradients are supported by PDF, however. If you look in ReportLab's Canvas class you'll see that many of its methods are relatively small wrappers around the underlying PDF code generation. To access gradients in RL you'd need to extend the Canvas class and add additional methods to generate the correct PDF code. This is doable, but obviously not trivial, and it means you'd have to read up on the PDF spec.

There are two alternatives. Firstly generate the gradient as a raster image and use that, secondly generate the gradient by drawing a whole series of rectangles in different colors.

start_color = (1,0,0)
end_color = (0,1,0)
for i in range(100):
    p = i * 0.01
    canvas.setFillColorRGB(*[start_color[i]*(1.0-p)+end_color[i]*p for i in range(3)])
    canvas.rect(i, 0, 2, 100)

For example. Unfortunately getting the gradient smooth takes a lot of rectangles, and this can cause the PDF to be large and render slowly. You're better off with the raster approach.

Finally, you might consider using PyCairo. This has better support for lots of graphical elements, and can render to a PDF or PNG. It lacks reportlabs higher lever constructs (such as page layout), however.

like image 35
Ian Avatar answered Oct 13 '22 09:10

Ian


You want to fill a rectangle (or other path) with a gradient instead of a solid color?

No problem. Use clipping to bound/limit the gradient to a path. Just remember to set the clip path before setting the gradient. (And wrap it inside saveState()/restoreState() to reset the clip and gradient afterwards.)

c = canvas.Canvas (filename)
#c.translate (8*cm, 8*cm)  # use this to move the rectangle
p = c.beginPath()
p.rect (0,0 , 5*cm,5*cm)
c.clipPath (p, stroke=0)
c.linearGradient (0,0 , 5*cm, 5*cm , (red, yellow))

For radial gradients it might be enough to set the extend keyword parameter to False.

like image 32
Torkel Bjørnson-Langen Avatar answered Oct 13 '22 09:10

Torkel Bjørnson-Langen