JagPDF
Prev Up Home Next

3.2. Graphics

Paths are used for

  • drawing lines
  • defining the shapes of filled areas
  • specifying boundaries for clipping other graphics

A path is a sequence of curved and straight line segments. Connected segments form a subpath, one or more disconnected subpaths then forms a path.

Let's show how to draw a rectangle.

canvas.rectangle(50, 400, 500, 350)
canvas.path_paint('s')

Drawing a rectangle consists of two steps: defining the rectangle and then painting it. The 's' passed to path_paint() says that we want to stroke the path. In the PDF terminology the first step is called path construction and the second is path painting.

The following example illustrates how to draw a filled shape.

canvas.move_to(50, 400)
canvas.line_to(50, 750)
canvas.line_to(550, 400)
canvas.line_to(550, 750)
canvas.path_paint('f')

Note that this time we passed 'f' to path_paint() because we want to fill the path. We could also pass 'fs' to fill and then stroke the path. Another option would be to use the path as a clipping path which is described in section Clipping Paths.

When a new page is started there is no path under construction. The path construction sequence must start with one of move_to(), rectangle(), circle() or arc(). Once a path has been painted with path_paint() it is no longer defined. A path construction sequence should not be mixed up with other operations than those in the Summary below and should be immediately followed by path_paint().

Paths Summary

Examples

pdficon_small paths

Path Construction Methods

move_to(), line_to(), circle(), arc(), arc_to(), bezier_to(), bezier_to_1st_ctrlpt(), bezier_to_2nd_ctrlpt(), path_close()

Path Painting Methods

path_paint()

PDF Reference

the PDF Reference, chapter Graphics | Path Construction and Painting

The graphics state is a set of parameters that control graphics operations. It comprises of parameters like attributes of a line used to stroke a path (Line Attributes), the current clipping path (Clipping Paths), the current transformation matrix (Coordinate Systems), the current color and the color space (Color Spaces) and various attributes related to text (Text).

At the beginning of each page, the graphics state is reset to default values. The current graphics state can be saved by state_save() and restored later by state_restore(). Calls to state_save() can be nested. For each state_save() there must be a corresponding state_restore().

Let's show an example illustrating the graphics state.

def fill_rectangle(n):
    canvas.rectangle(50, 550+n*60, 500, 55)
    canvas.path_paint('f')

fill_rectangle(1)
canvas.state_save()
canvas.color('f', 0.5)
fill_rectangle(2)
canvas.state_restore()
fill_rectangle(3)

The code draws three rectangles. The first one with a default color. Then it saves the graphics state, changes the fill color and fills the second rectangle with it. Then the state is restored and the last rectangle is filled with the same color as the first one.

Graphics State Summary

Examples

pdficon_small graphics state

Graphics State Methods

state_save(), state_restore()

PDF Reference

the PDF Reference, chapter Graphics | Graphics State

The graphics state contains a current clipping path that limits the regions of the page affected by painting operators. The closed subpaths of this path define the area that can be painted. Marks falling inside this area are applied to the page; those falling outside it are not.The initial clipping path includes the entire page.

The graphics state maintains a current clipping path. Initially it is the entire page. The following code snippet shows a rectangle used as a clipping path:

canvas.rectangle(30, 700, 190, 80)
canvas.path_paint('w')
canvas.circle(30, 700, 50)
canvas.path_paint('fs')
canvas.text(100, 698, 'clipped text')

After the rectangle is painted, the clipping path in the graphics state is set to the intersection of the current clipping path (in this case the entire page) and the rectangle. Subsequently, we painted a circle and a text string which are clipped accordingly.

Clipping path construction differs from regular path construction just in the argument 'w' passed to path_paint().

It is possible to combine 'w' with other operators. In such case the path is filled/stroked first and then used as a clipping path for the following painting operations.

canvas.rectangle(30, 700, 190, 80)
canvas.path_paint('wfs')
canvas.color('f', 0.5)
canvas.color('s', 0.8)
canvas.circle(125, 740, 60)
canvas.path_paint('fs')
Clipping Paths Summary

Examples

pdficon_small clipping paths

Clipping Path Methods

the same as in Paths

PDF Reference

the PDF Reference, chapter Graphics | Path Construction and Painting | Clipping Path Operators

Color spaces supported by JagPDF can be divided into three families.

  • Device color spaces: DeviceGray, DeviceRGB and DeviceCMYK
  • CIE-based color spaces: CalGray, CalRGB, Lab and ICC based.
  • Special color spaces: Indexed

The graphics state maintains parameters representing the current color space and the current color value - one for stroking and one for filling operations. A color value consists of one or more color components, which are usually numbers. For example, a gray level can be specified by a single number ranging from 0.0 (black) to 1.0 (white).

The default color space for both stroking and filling operations is DeviceGray. The initial color value is black.

Let's start with a simple example.

canvas.color_space('f', jagpdf.CS_DEVICE_RGB)
canvas.color_space('s', jagpdf.CS_DEVICE_CMYK)
canvas.color('f', 1.0, 0.0, 0.0)
canvas.color('s', 1.0, 0.0, 0.0, 0.0)
canvas.rectangle(50, 600, 500, 200)
canvas.path_paint('fs')

Here, DeviceRGB color space and the red color is set for stroking and DeviceCMYK and cyan for filling operations.

Device color spaces do not have any parameters so their use is quite straightforward. Other color spaces need to be specified before use. Let's show another example that defines a CalRGB color space.

calrgb = doc.color_space_load("calrgb; white=0.9505, 1.089")
canvas.color_space('f', calrgb)
canvas.color('f', 0.0, 1.0, 0.0)
canvas.rectangle(50, 600, 500, 200)
canvas.path_paint('f')

We loaded the color space by passing its specification to color_space_load(). The we set it as the current color space for filling operations. Note that the registration is performed by Document. Once a color space is loaded it can be used during the whole document lifetime.

JagPDF supports also ICC based color spaces. You can use either predefined ones or supply a file containing an ICC profile.

def paint_rectangle(n, color_space):
    cs = doc.color_space_load(color_space)
    canvas.color_space('f', cs)
    canvas.color('f', 0.6, 0.2, 0.7)
    canvas.rectangle(50, 600+n*60, 500, 55)
    canvas.path_paint('f')

paint_rectangle(1, 'srgb')
paint_rectangle(2, 'icc; components=3; profile=CIERGB.icc')

The next examples shows how to define an indexed color space. It enables using integers as indices into a palette of arbitrary colors in some other space. Color values are in the range 0 to 255 and are scaled to the range of the corresponding color component in the base color space; 0 is the minimum value and 255 is the maximum. A palette can contain up to 255 color values.

spec = 'srgb; palette=255, 0, 0,'\
                     '0, 255, 0,'\
                     '0, 0, 255'
cs = doc.color_space_load(spec)
canvas.color_space('f', cs)
for i in range(3):
    canvas.color('f', i)
    canvas.rectangle(50, 540+(i+1)*60, 500, 55)
    canvas.path_paint('f')

Here we defined an indexed color space having three color values (red, green, blue) which are interpreted in the sRGB color space. To use a color from the palette we pass its index to color().

Pattern color spaces are described in a separate section.

Color Spaces Summary

Examples

pdficon_small color spaces

Color Space Methods

color_space_load(), color_space(), color(), color(), color()

PDF Reference

the PDF Reference, chapter Graphics | Color Spaces

PDF defines several coordinate spaces. Of special importance is a device-independent coordinate system called user space. It is initialized to a default state for each page of a document (default user space):

  • The origin corresponds to the lower-left corner of a page.
  • The user space unit is 1/72 inch.

The graphics state maintains the current transformation matrix which maps positions from user space coordinates to device coordinates.

[Tip]

The information about coordinate systems and their transformations provided here is very minimalistic. The reader is encouraged to look at the PDF Reference, chapter Graphics | Coordinate Systems to understand relationships among coordinate spaces in PDF.

Common Transformation Examples

To illustrate coordinate space transformations we will define a function which paints a rectangle on the x, y coordinates. We will call it from within variously transformed user space to see the effect of the transformation.

def paint_rectangle(x, y):
    canvas.rectangle(x, y, 100, 100)
    canvas.path_paint('f')

First, we will paint a rectangle in default user space.

paint_rectangle(50, 500)

Now let us translate the origin of user space.

canvas.state_save()
canvas.translate(110, 0)
paint_rectangle(50, 500)
canvas.state_restore()

We translated the origin of the user space by (110, 0). The rectangle is placed on (50, 500) in modified space, i.e. on (160, 500) in the default user space. We also restored the original graphics state so we are back in default user space. The next code fragment rotates a rectangle:

x, y = 300, 500
canvas.state_save()
canvas.translate(x+50, y+50)
canvas.rotate(math.pi/4)
canvas.translate(-(x+50), -(y+50))
paint_rectangle(x, y)
canvas.state_restore()

translate() rotates around the user space origin but we want to rotate around the rectangle center. So we translated the origin of the user space to the center of the rectangle, rotated it there and moved it back. scale() and skew() behave likewise - they scale/skew according to the user space origin.

# scale
x, y = 420, 500
canvas.state_save()
canvas.translate(x+50, y+50)
canvas.scale(0.5, 0.5)
canvas.translate(-(x+50), -(y+50))
paint_rectangle(x, y)
canvas.state_restore()
# skew
x, y = 100, 680
canvas.state_save()
canvas.translate(x+50, y+50)
canvas.skew(0.3, 0.3)
canvas.translate(-(x+50), -(y+50))
paint_rectangle(x, y)
canvas.state_restore()

As can be seen in the examples above JagPDF provides several convenience methods which performs common transformations. They internally form a matrix with appropriate values and apply it to the current transformation matrix. However, we could likewise calculate a transformation matrix ourselves and use transform().

Top-down Coordinate Space

We already mentioned that the origin of the default user space corresponds to the lower-left corner of a page. We can move it to the upper-left corner and reverse the orientation of the y axis with the doc.topdown profile option.

profile = jagpdf.create_profile()
profile.set('doc.topdown', '1')
doc = jagpdf.create_file('topdown.pdf', profile)
# ..

Coordinate Systems Summary

Examples

pdficon_small transformations

Coordinate Space Methods

transform(), translate(), rotate(), scale(), skew()

PDF Reference

the PDF Reference, chapter Graphics | Coordinate Systems

JagPDF supports PNG and JPEG standard formats as well as a native PDF image format which allows painting custom images. Before painting an image it must be loaded first. A loaded image can be painted multiple times anywhere in the document.

The following example shows how to paint an image:

img = doc.image_load_file('logo.png')
canvas.image(img, 20, 20)

Let's say we would like to tile a region of the page with our image. To do so we need to know the image dimensions. Because width() and width() return size in pixels we need to recalculate these to user space units.

img_width = img.width() / img.dpi_x() * 72
img_height = img.height() / img.dpi_y() * 72
for x in range(7):
    for y in range(15):
        canvas.image(img, 90 + x * img_width, 100 + y * img_height)

Image DPI is taken into account when the image is painted onto a canvas. An image usually specifies its DPI. If it is not so a value of images.default_dpi is used (see Profile).

Refer to the PDF Reference, chapter Graphics, Images, Sample Representation for description of how image sample data is represented.

In the following example we will show how to paint a custom image. First, we will define an 80x80 checker pattern having one bit per color.

img_data = array.array('B')
for y in range(80):
    for x in range(10):
        img_data.append((y % 8) > 3 and 0xf0 or 0x0f)

Note that we use array.array for efficient transfer of values between Python and JagPDF. We can use an ordinary Python list or a tuple as well but array.array is much faster and should be preferred. Now we can define our image:

imgdef = doc.image_definition()
imgdef.data(img_data)
imgdef.dimensions(80, 80)
imgdef.color_space(jagpdf.CS_DEVICE_GRAY)
imgdef.bits_per_component(1)
imgdef.dpi(72, 72)
imgdef.format(jagpdf.IMAGE_FORMAT_NATIVE)

Finally, we load it and paint it:

img = doc.image_load(imgdef)
canvas.image(img, 50, 450)

Examples

pdficon_small images

Images Reference

class Image, class ImageDef, image(), image_load_file(), image_definition(), image_load()

PDF Reference

the PDF Reference, chapter Graphics | Images

Pattern color space is a special type of color space which allows painting an area of the page with a pattern. A pattern can be either a repeating graphical figure or a smoothly varying gradient.

Graphical objects in a pattern are interpreted in the pattern coordinate space. Every pattern can specify its pattern matrix. A pattern color space is defined as the concatenation of the pattern matrix and the initial coordinate space of the Canvas the pattern is used in. Changes to canvas's coordinate space does not affect the pattern coordinate space.

A tiling pattern uses a Canvas to define a pattern cell. Painting an area with a tiling pattern tiles the area with the pattern cell. There are two categories of tiling patterns: colored and uncolored tiling patterns.

Colored Tiling Patterns

A colored tiling pattern is a pattern whose canvas defines color for its objects. A colored tiling pattern can contain objects painted in different colors or it can contain images.

Let's show an example of a colored tiling pattern whose pattern cell comprises of a red circle:

pcell = doc.canvas_create()
pcell.color_space('s', jagpdf.CS_DEVICE_RGB)
pcell.color('s', 1.0, 0.0, 0.0)
pcell.circle(15, 15, 15)
pcell.path_paint('s')
pattern = doc.tiling_pattern_load("step=30, 30", pcell)

In the code above we created a canvas and painted a red circle on it. Then we loaded our pattern and specified the canvas as its pattern cell. The option step specifies spacing between pattern cells in both directions. Now we can establish the pattern color space, set the pattern as the current color and paint a rectangle tiled with our pattern:

canvas.color_space_pattern('f')
canvas.pattern('f', pattern)
canvas.rectangle(75, 75, 450, 700)
canvas.path_paint('f')
Uncolored Tiling Patterns

An uncolored tiling pattern does not define any color information for its objects. An underlying color space must be specified whenever the pattern color space is established. Also, a color value in the underlying color space must be supplied to operations which set an uncolored tiling pattern as the current color.

In the following example we will create an uncolored tiling pattern. The pattern cell will comprise of a circle again but this time we will not specify any color:

pcell = doc.canvas_create()
pcell.circle(15, 15, 15)
pcell.path_paint('s')
pattern = doc.tiling_pattern_load("step=30, 30", pcell)

Let's establish pattern color space with underlying DeviceRGB color space:

canvas.color_space_pattern_uncolored('f', jagpdf.CS_DEVICE_RGB)

Now we will draw two rectangles tiled with our pattern; the pattern will be colorized with different colors, green:

canvas.pattern('f', pattern, 0.0, 1.0, 0.0)
canvas.rectangle(75, 75, 450, 350)
canvas.path_paint('f')

and blue:

canvas.pattern('f', pattern, 0.0, 0.0, 1.0)
canvas.rectangle(75, 425, 450, 350)
canvas.path_paint('f')

Painting an area with a shading pattern results into smooth transition of colors across the area. JagPDF supports axial, radial and function based shadings.

To illustrate let's show an example of an axial shading pattern. First, we will define a Type 2 function which linearly interpolates between red and blue:

red_to_blue = doc.function_2_load("domain=0, 1; c0=1, 0, 0; c1=0, 0, 1")

Now we will load our axial shading pattern. The axis will extend from (100, 100) to (500, 500):

sh = doc.shading_pattern_load("axial; coords=100, 100, 500, 500",
                              jagpdf.CS_DEVICE_RGB,
                              red_to_blue)

Finally, we can establish the pattern color space, set our shading pattern as the current color and paint a rectangle:

canvas.color_space_pattern('f')
canvas.pattern('f', sh)
canvas.rectangle(0, 0, 600, 600)
canvas.path_paint('f')
Shading Operator

PDF offers another possibility how to apply a shading pattern. It is called shading operator and its effect differs from that of establishing the pattern color space and using a pattern as the current color:

  • the pattern coordinate space is identical to the current user space; the pattern matrix is ignored
  • it paints the current clipping path
  • the current color is nor used neither altered

To illustrate, we will load a radial shading pattern defined as two concentric circles with center in (0, 0) and radii 0 and 1

sh = doc.shading_pattern_load("radial; coords=0, 0, 0, 0, 0, 1",
                              jagpdf.CS_DEVICE_RGB, red_to_blue)

Now we will construct a circle that is going to be painted using our shading. We will set the circle as the current clipping path:

x, y, radius = 300, 300, 250
canvas.circle(x, y, radius)
canvas.path_paint('w')

Before we can finally apply our shading we need to transform the current user space to the circle origin and scale it by its radius.

canvas.transform(radius, 0, 0, radius, x, y)
canvas.shading_apply(sh)

JagPDF enables to specify a constant opacity value - alpha constant. There are two separate alpha constants parameters in the graphics state: one for stroking and one for all other painting operations.

Let's show an alpha constant example:

canvas.line_width(3)
canvas.color_space('fs', jagpdf.CS_DEVICE_RGB)
canvas.color('fs', 1.0, 0.0, 0.0)
canvas.circle(200, 600, 150)
canvas.path_paint('fs')
canvas.color('fs', 0.0, 0.0, 1.0)
canvas.alpha('f', 0.5)
canvas.circle(350, 600, 150)
canvas.path_paint('fs')
Transparency Summary

Examples

pdficon_small transparency

Images Reference

alpha()

PDF Reference

the PDF Reference, chapter Transparency | Specifying Transparency in PDF | Specifying Shape and Opacity

In this section we will show examples of using graphics state parameters that can change the appearance of a stroked path.

The following example shows dashed lines (see the PDF Reference, chapter Graphics | Graphics State | Details of Graphics State Parameters | Line Dash Pattern):

def dashed_line(n, phase, offset):
    canvas.line_dash(phase, offset)
    canvas.move_to(50, 700+n*15)
    canvas.line_to(550,700+n*15)
    canvas.path_paint('s')

canvas.line_width(8)
dashed_line(1, [], 0)
dashed_line(2, [3], 0)
dashed_line(3, [2], 1)
dashed_line(4, [2, 1], 0)
dashed_line(5, [3, 5], 6)
dashed_line(6, [2, 3], 11)

The following example shows line cap styles.

def stroke_line(n, cap):
    canvas.line_cap(cap)
    canvas.move_to(50, 700+n*30)
    canvas.line_to(550,700+n*30)
    canvas.path_paint('s')

canvas.line_width(20)
stroke_line(1, jagpdf.LINE_CAP_BUTT)
stroke_line(2, jagpdf.LINE_CAP_ROUND)
stroke_line(3, jagpdf.LINE_CAP_SQUARE)

The following example shows line join styles.

def stroke_shape(n, join):
    canvas.line_join(join)
    canvas.move_to(n*80, 600)
    canvas.line_to(n*80+25, 750)
    canvas.line_to(n*80+50, 600)
    canvas.path_paint('s')

canvas.line_width(20)
stroke_shape(1, jagpdf.LINE_JOIN_ROUND)
stroke_shape(2, jagpdf.LINE_JOIN_BEVEL)
stroke_shape(3, jagpdf.LINE_JOIN_MITER)
canvas.line_miter_limit(1.9)
stroke_shape(4, jagpdf.LINE_JOIN_MITER)

The following example shows the difference between an opened and a closed path in respect to the line join.

canvas.line_width(15)
canvas.line_join(jagpdf.LINE_JOIN_ROUND)
# opened triangle
canvas.move_to(50, 600)
canvas.line_to(100, 750)
canvas.line_to(150, 600)
canvas.line_to(50, 600)
canvas.path_paint('s')
# closed triangle
canvas.move_to(200, 600)
canvas.line_to(250, 750)
canvas.line_to(300, 600)
canvas.path_close()
canvas.path_paint('s')
Line Attributes Summary

Examples

pdficon_small line attributes

Line Attribute Methods

line_width(), line_dash(), line_cap(), line_join(), line_miter_limit()

PDF Reference

the PDF Reference, chapter Graphics | Graphics State | Details of Graphics State Parameters

Prev Up Home Next