Skip to content

How to Make your own PDF Shape

Jorj X. McKie edited this page Oct 18, 2017 · 1 revision

How to Create your own PDF Shape with PyMuPDF

PyMuPDF now supports the creation of complex shapes using PDF operators (section pp. 985 of the manual). Many of these operators are implemented by draw*() and insertText*() methods for both, classes Page and Shape.

Here, we will explain creating the "hand" shape step by step.

If you want to just use this or another pre-defined shape, import it from the example file shapes_and_symbols.py like so:

from shapes_and_symbols import hand

Be reminded that you don't need to save the PDF if you just want the image: you can always save it as a PNG file. Perform a pix = Page.getPixmap(clip = rect.irect) (whith rect containing it) and then do a pix.writePNG(...).

1 Get / Create a Sketch of Your Shape and Define a Skeleton Rectangle

Your shape should be simple enough to be generated with a few straight lines, rectangles, ellipses, or curves. Put a rectangle around your sketch, with simple, preferrably one-digit integer dimensions. In our case, rectangle height is 3, and its width to be determined as we go.

2 Define Skeleton Points

Our "hand" shape will be drawn using the basic shapes drawLine(), drawCurve() and drawBezier(). Each of these shapes needs two or more points to get stroked.

In order to paint our "hand" shape, we need to define about 20 points. They are shown in this image as black circles with a label. For our skeleton rectangle, we have defined a tuple of point coordinates as follows:

points = ((0.0, 1.4), (1.4, 0.2), (1.4, 1.4), (2.2, 0.0), (1.4, 1.4),
          (3.4, 1.4), (3.4, 1.8), (2.8, 1.8), (2.8, 2.2), (2.6, 2.2),
          (2.6, 2.6), (2.5, 2.6), (2.5, 3.0))

Each entry in points corresponds to a labeled circle, e.g. (0.0, 1.4) is p1, with coordinates based on our rectangle height 3: it is 0 units to the right of rectangle's left border, and 1.4 units below its top. Note that p3 equals p5 (hence they overprint each other in the picture below).

More points are needed to draw the Bézier curves for the finger tips. They are prefixed with "cp". So, for stroking the first finger tip, we are using statement

drawBezier(p6, cp6, cp7, p7)

To ensure a consistent appearance across a large range of hand sizes, we also need to somehow parametrize line width by our scale factor f (look at the example script).

3 Draw the Shape

After the hand function is invoked, we first need to translate the "skeleton" coordinates in points into real coordinates for the passed-in real rectangle. This is done by statements

p1 = fitz.Point(points[0]) * f + tl

These statements turn the points items into PyMuPDF fitz.Point objects, multiplied with our scale factor f and the real rectangle's top-left point tl added.

The rest of the function is a very straightforward sequence of draw*() methods using the just defined points p* or cp* control points, respectively.

Care must be taken, that each draw*() starts with the point, that the previous draw had returned. Failing to do so, will result in creating a new PDF "subpath". The connection between the drawing primitves will be lost, and coloring and other characteristics may not be correctly applied by the finish() method.

After just 14 draw*() methods, our shape is painted and we can finish() it, applying border and fill colors along with other properties.

Special note should be taken of the morph parameter. It provides a way to display your shape in a way dramatically different from its original layout. By choosing a fitz.Point and a fitz.Matrix, you can rotate, mirror, stretch, shrink or distort it in many ways.

Caution: If the morphing parameters are unfortunately chosen, the drawing may land outside the PDF page's visible area. Best start with the enclosing rectangle's center and a rotation matrix to develop some experience.

hand image

Clone this wiki locally