Code for cover artwork

Author

Clayton Cafiero

Published

2025-06-18

Here is the code used to generate the tessellated pattern used on the cover of this book. It’s written in Python, and makes use of the DrawSVG package. It’s included here because it’s a concise example of a complete Python program which includes docstrings (module and function), imported Python modules, an imported third-party module, constants, functions and functional decomposition, and driver code. This is all written in conformance to PEP 8 (or as close as I could manage with the constraint of formatting for book page).

"""
Cover background for ITPACS.
Clayton Cafiero <cbcafier@uvm.edu>

Requires DrawSVG package.
To install DrawSVG: `$ pip install "drawsvg[raster]~=2.2"`
See: https://pypi.org/project/drawsvg/

DrawSVG requires Cairo on host system.
Ubuntu: `$ sudo apt install libcairo2`
macOS: `$ brew install cairo`
Anaconda: `$ conda install -c anaconda cairo`
See: https://www.cairographics.org/

For info on SVG 2: https://svgwg.org/svg2-draft/
"""
import math  # 'cause we need a little trig
import drawsvg as draw

# A lovely selection of named SVG colors.
COLORS = ['tomato', 'salmon', 'lightsalmon', 'coral', 
          'orangered', 'sandybrown', 'orange']

TRAPEZOID_LEG = 40  # Everything is derived from this...
TRAPEZOID_HEIGHT = math.sin(math.radians(60)) * TRAPEZOID_LEG
TRAPEZOID_BASE_SEGMENT = TRAPEZOID_LEG / 2
TRAPEZOID_BASE = TRAPEZOID_LEG + 2 * TRAPEZOID_BASE_SEGMENT
TILE_HEIGHT = TRAPEZOID_LEG * math.sqrt(3)
# Taking overlap into consideration we tile @ 120 x 105
COLS = math.ceil(1950 / 120) + 2
ROWS = math.ceil(2850 / 105) + 2


def draw_tile(rotate, fill):
    """If we choose the right starting point for drawing the tile,
    and rotate about that point, we don't have to worry about x, y
    translation. """
    transform = f'rotate({rotate}, 0, 0)'  # angle, center_x, center_y
    return draw.Lines(0, 0,
                      TRAPEZOID_BASE, 0,
                      TRAPEZOID_BASE_SEGMENT + TRAPEZOID_LEG, 
                      TRAPEZOID_HEIGHT,
                      TRAPEZOID_BASE_SEGMENT, TRAPEZOID_HEIGHT,
                      0, TILE_HEIGHT,
                      -TRAPEZOID_LEG, TILE_HEIGHT,
                      close=True, stroke_width=0.5,
                      stroke='gray', fill=fill,
                      fill_opacity=1.0, transform=transform)


def draw_cube(trans_x, trans_y, colors_):
    """Assemble a cube from three rotated tiles. """
    transform = f'translate({trans_x}, {trans_y})'
    c = draw.Group(fill='none', stroke='black', 
                   transform=transform)
    c.append(draw_tile(0, colors_[0]))
    c.append(draw_tile(120, colors_[1]))
    c.append(draw_tile(240, colors_[2]))
    return c


if __name__ == '__main__':

    d = draw.Drawing(1950, 2850, origin=(0, 0))
    translate_y = 0.0

    for i in range(ROWS):
        if i % 2:
            translate_x = 0.0
        else:
            translate_x = TRAPEZOID_BASE_SEGMENT + TRAPEZOID_LEG
        for j in range(COLS):
            # rotate colors
            colors = [COLORS[(i + j) % len(COLORS)],
                      COLORS[(i + j + 4) % len(COLORS)],
                      COLORS[(i + j + 8) % len(COLORS)]]
            d.append(draw_cube(translate_x, translate_y, colors))
            translate_x += TRAPEZOID_BASE + TRAPEZOID_LEG
        translate_y += TILE_HEIGHT + TRAPEZOID_HEIGHT

    d.set_pixel_scale(1.0)  # Set number of pixels per geometry unit
    d.save_svg('cover.svg')
    d.save_png('cover.png')