Main drawing function, which generates a cover of the given dimension and
    renders title, author, and graphics.

def draw(title, subtitle, author, cover_width=400, cover_height=600):
    """
    Main drawing function, which generates a cover of the given dimension and
    renders title, author, and graphics.
    """

    # Based on some initial constants and the title+author strings, generate a base
    # background color and a shape color to draw onto the background. Try to keep
    # these two colors somewhat compatible with each other by varying only their hue.
    def processColors():
        base_saturation = 100
        base_brightness = 90
        color_distance = 100
        invert = True

        counts = len(title) + len(author)
        color_seed = int(_map(_clip(counts, 2, 80), 2, 80, 10, 360))
        shape_color = Image.colorHSB(color_seed, base_saturation, base_brightness-(counts % 20))
        base_color = Image.colorHSB((
            color_seed + color_distance) % 360,
            base_saturation,
            base_brightness
        )
        if invert:
            shape_color, base_color = base_color, shape_color
        if (counts % 10) == 0:
            shape_color, base_color = base_color, shape_color
        return shape_color, base_color


    # Fill the background of the image with white.
    def drawBackground():
        fill = Image.colorRGB(255, 255, 255)
        cover_image.rect(0, 0, cover_width, cover_height, fill)


    # Draw the actual artwork for the cover. Given the length of the title string,
    # generate an appropriate sized grid and draw C64 PETSCII into each of the cells.
    # https://www.c64-wiki.com/index.php/PETSCII
    # https://en.wikipedia.org/wiki/PETSCII#/media/File:PET_Keyboard.svg
    def drawArtwork():
        artwork_start_x = 0
        artwork_start_y = cover_height - cover_width

        grid_count, grid_total, grid_size = breakGrid()
        cover_image.rect(0, 0, cover_width, cover_height * cover_margin / 100, base_color)
        cover_image.rect(0, 0 + artwork_start_y, cover_width, cover_width, base_color)
        c64_title = c64Convert()
        for c, i in zip(itertools.cycle(c64_title), range(0, grid_total)):
            grid_x = int(i % grid_count)
            grid_y = int(i / grid_count)
            x = grid_x * grid_size + artwork_start_x
            y = grid_y * grid_size + artwork_start_y
            drawShape(c, x, y, grid_size)


    # Compute the graphics grid size based on the length of the book title.
    def breakGrid():
        min_title = 2
        max_title = 60
        length = _clip(len(title), min_title, max_title)

        grid_count = int(_map(length, min_title, max_title, 2, 11))
        grid_total = grid_count * grid_count
        grid_size = cover_width / grid_count
        return grid_count, grid_total, grid_size


    # Given the title of the book, filter through its characters and ensure
    # that only a certain range is used for the title; characters outside of
    # that range are replaced with a somewhat random character.
    def c64Convert():
        c64_letters = " qQwWeErRtTyYuUiIoOpPaAsSdDfFgGhHjJkKlL:zZxXcCvVbBnNmM,;?<>@[]1234567890.=-+*/"
        c64_title = ""
        for c in title:
            if c in c64_letters:
                c64_title += c
            else:
                c64_title += c64_letters[ord(c) % len(c64_letters)]
        return c64_title


    # Given an alphabetic character from the book's title string and the x, y
    # coordinates and size of the cell within the cover grid, draw a PETSCII
    # shape into that cell.
    def drawShape(c, x, y, s):
        shape_thickness = 10
        thick = int(s * shape_thickness / 100)
        if c in "qQ":
            cover_image.ellipse(x, y, s, s, shape_color)
        elif c in "wW":
            cover_image.ellipse(x, y, s, s, shape_color)
            cover_image.ellipse(x+thick, y+thick, s-(thick*2), s-(thick*2), base_color)
        elif c in "eE":
            cover_image.rect(x, y+thick, s, thick, shape_color)
        elif c in "rR":
            cover_image.rect(x, y+s-(thick*2), s, thick, shape_color)
        elif c in "tT":
            cover_image.rect(x+thick, y, thick, s, shape_color)
        elif c in "yY":
            cover_image.rect(x+s-(thick*2), y, thick, s, shape_color)
        elif c in "uU":
            cover_image.arc(x, y, 2*s, 2*s, 180, 270, shape_color, thick)
        elif c in "iI":
            cover_image.arc(x-s, y, 2*s, 2*s, 270, 360, shape_color, thick)
        elif c in "oO":
            cover_image.rect(x, y, s, thick, shape_color)
            cover_image.rect(x, y, thick, s, shape_color)
        elif c in "pP":
            cover_image.rect(x, y, s, thick, shape_color)
            cover_image.rect(x+s-thick, y, thick, s, shape_color)
        elif c in "aA":
            cover_image.triangle(x, y+s, x+(s/2), y, x+s, y+s, shape_color)
        elif c in "sS":
            cover_image.triangle(x, y, x+(s/2), y+s, x+s, y, shape_color)
        elif c in "dD":
            cover_image.rect(x, y+(thick*2), s, thick, shape_color)
        elif c in "fF":
            cover_image.rect(x, y+s-(thick*3), s, thick, shape_color)
        elif c in "gG":
            cover_image.rect(x+(thick*2), y, thick, s, shape_color)
        elif c in "hH":
            cover_image.rect(x+s-(thick*3), y, thick, s, shape_color)
        elif c in "jJ":
            cover_image.arc(x, y-s, 2*s, 2*s, 90, 180, shape_color, thick)
        elif c in "kK":
            cover_image.arc(x-s, y-s, 2*s, 2*s, 0, 90, shape_color, thick)
        elif c in "lL":
            cover_image.rect(x, y, thick, s, shape_color)
            cover_image.rect(x, y+s-thick, s, thick, shape_color)
        elif c == ":":
            cover_image.rect(x+s-thick, y, thick, s, shape_color)
            cover_image.rect(x, y+s-thick, s, thick, shape_color)
        elif c in "zZ":
            cover_image.triangle(x, y+(s/2), x+(s/2), y, x+s, y+(s/2), shape_color)
            cover_image.triangle(x, y+(s/2), x+(s/2), y+s, x+s, y+(s/2), shape_color)
        elif c in "xX":
            cover_image.ellipse(x+(s/2), y+(s/3), thick*2, thick*2, shape_color)
            cover_image.ellipse(x+(s/3), y+s-(s/3), thick*2, thick*2, shape_color)
            cover_image.ellipse(x+s-(s/3), y+s-(s/3), thick*2, thick*2, shape_color)
        elif c in "cC":
            cover_image.rect(x, y+(thick*3), s, thick, shape_color)
        elif c in "vV":
            cover_image.rect(x, y, s, s, shape_color)
            cover_image.triangle(x+thick, y, x+(s/2), y+(s/2)-thick, x+s-thick, y, base_color)
            cover_image.triangle(x, y+thick, x+(s/2)-thick, y+(s/2), x, y+s-thick, base_color)
            cover_image.triangle(x+thick, y+s, x+(s/2), y+(s/2)+thick, x+s-thick, y+s, base_color)
            cover_image.triangle(x+s, y+thick, x+s, y+s-thick, x+(s/2)+thick, y+(s/2), base_color)
        elif c in "bB":
            cover_image.rect(x+(thick*3), y, thick, s, shape_color)
        elif c in "nN":
            cover_image.rect(x, y, s, s, shape_color)
            cover_image.triangle(x, y, x+s-thick, y, x, y+s-thick, base_color)
            cover_image.triangle(x+thick, y+s, x+s, y+s, x+s, y+thick, base_color)
        elif c in "mM":
            cover_image.rect(x, y, s, s, shape_color)
            cover_image.triangle(x+thick, y, x+s, y, x+s, y+s-thick, base_color)
            cover_image.triangle(x, y+thick, x, y+s, x+s-thick, y + s, base_color)
        elif c == ",":
            cover_image.rect(x+(s/2), y+(s/2), s/2, s/2, shape_color)
        elif c == ";":
            cover_image.rect(x, y+(s/2), s/2, s/2, shape_color)
        elif c == "?":
            cover_image.rect(x, y, s/2, s/2, shape_color)
            cover_image.rect(x+(s/2), y+(s/2), s/2, s/2, shape_color)
        elif c == "<":
            cover_image.rect(x+(s/2), y, s/2, s/2, shape_color)
        elif c == ">":
            cover_image.rect(x, y, s/2, s/2, shape_color)
        elif c == "@":
            cover_image.rect(x, y+(s/2)-(thick/2), s, thick, shape_color)
        elif c == "[":
            cover_image.rect(x+(s/2)-(thick/2), y, thick, s, shape_color)
        elif c == "]":
            cover_image.rect(x, y+(s/2)-(thick/2), s, thick, shape_color)
            cover_image.rect(x+(s/2)-(thick/2), y, thick, s, shape_color)
        elif c == "0":
            cover_image.rect(x+(s/2)-(thick/2), y+(s/2)-(thick/2), thick, s/2+thick/2, shape_color)
            cover_image.rect(x+(s/2)-(thick/2), y+(s/2)-(thick/2), s/2+thick/2, thick, shape_color)
        elif c == "1":
            cover_image.rect(x, y+(s/2)-(thick/2), s, thick, shape_color)
            cover_image.rect(x+(s/2)-(thick/2), y, thick, s/2+thick/2, shape_color)
        elif c == "2":
            cover_image.rect(x, y+(s/2)-(thick/2), s, thick, shape_color)
            cover_image.rect(x+(s/2)-(thick/2), y+(s/2)-(thick/2), thick, s/2+thick/2, shape_color)
        elif c == "3":
            cover_image.rect(x, y+(s/2)-(thick/2), s/2+thick/2, thick, shape_color)
            cover_image.rect(x+(s/2)-(thick/2), y, thick, s, shape_color)
        elif c == "4":
            cover_image.rect(x, y, thick*2, s, shape_color)
        elif c == "5":
            cover_image.rect(x, y, thick*3, s, shape_color)
        elif c == "6":
            cover_image.rect(x+s-(thick*3), y, thick*3, s, shape_color)
        elif c == "7":
            cover_image.rect(x, y, s, thick*2, shape_color)
        elif c == "8":
            cover_image.rect(x, y, s, thick*3, shape_color)
        elif c == "9":
            cover_image.rect(x, y+s-(thick*3), s, thick*3, shape_color)
        elif c == ".":
            cover_image.rect(x+(s/2)-(thick/2), y+(s/2)-(thick/2), thick, s/2+thick/2, shape_color)
            cover_image.rect(x, y+(s/2)-(thick/2), s/2+thick/2, thick, shape_color)
        elif c == "=":
            cover_image.rect(x+(s/2)-(thick/2), y, thick, s/2+thick/2, shape_color)
            cover_image.rect(x, y+(s/2)-(thick/2), s/2, thick, shape_color)
        elif c == "-":
            cover_image.rect(x+(s/2)-(thick/2), y, thick, s/2+thick/2, shape_color)
            cover_image.rect(x+(s/2)-(thick/2), y+(s/2)-(thick/2), s/2+thick/2, thick, shape_color)
        elif c == "+":
            cover_image.rect(x+(s/2)-(thick/2), y+(s/2)-(thick/2), s/2+thick/2, thick, shape_color)
            cover_image.rect(x+(s/2)-(thick/2), y, thick, s, shape_color)
        elif c == "*":
            cover_image.rect(x+s-(thick*2), y, thick*2, s, shape_color)
        elif c == "/":
            cover_image.rect(x, y+s-(thick*2), s, thick*2, shape_color)
        elif c == " ":
            cover_image.rect(x, y, s, s, base_color)
        else:
            assert not "Implement."

    # If the text is long, use a smaller font size.
    def scale_font(text, font_name, font_properties):
        (font_size, font_slant, font_weight) = font_properties
        w = len(text) * font_size
        if w > cover_width * 3:   #This is an empirical, unintelligent, heuristic.
            return  (font_size * 0.8, font_slant, font_weight)
        elif w < cover_width :
            return  (font_size * 1.2, font_slant, font_weight)
        else:
            return font_properties

    # return a font appropriate for the text. Uses Noto CJK if text contains CJK, otherwise
    # Noto Sans.
    
    def select_font(text):
        for char in text:
            if ord(char) >= 0x4E00:
                return 'Noto Sans CJK SC'
        return 'Noto Sans'

    # Allocate fonts for the title and the author, and draw the text.
    
    def drawText():
        fill = Image.colorRGB(50, 50, 50)

        title_font_size = cover_width * 0.08
        subtitle_font_size = cover_width * 0.05
        title_font_properties = (title_font_size, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
        subtitle_font_properties = (subtitle_font_size, cairo.FONT_SLANT_NORMAL,
                                     cairo.FONT_WEIGHT_NORMAL)
        title_font_family = select_font(title)
        subtitle_font_family = select_font(subtitle)
        title_font_properties = scale_font(title, title_font_family, title_font_properties)
        subtitle_font_properties = scale_font(
            subtitle,
            subtitle_font_family,
            subtitle_font_properties
        )
        title_font = cover_image.font(title_font_family, title_font_properties)
        subtitle_font = cover_image.font(subtitle_font_family, subtitle_font_properties)
        title_height = (cover_height - cover_width - (cover_height * cover_margin / 100)) * 0.75

        x = cover_height * cover_margin / 100
        y = cover_height * cover_margin / 100 * 2
        width = cover_width - (2 * cover_height * cover_margin / 100)
        height = title_height
        title_lines, font_height = cover_image.text(title, x, y, width, height, fill, title_font)
        if subtitle:
            y = min(
                y + font_height * title_lines * cover_height,
                title_height - subtitle_font_properties[0]
            )

            cover_image.text(subtitle, x, y, width, height, fill, subtitle_font)

        author_font_size = cover_width * 0.07
        author_font_properties = (
            author_font_size,
            cairo.FONT_SLANT_NORMAL,
            cairo.FONT_WEIGHT_NORMAL
        )
        author_font = cover_image.font(select_font(author), author_font_properties)
        author_height = (cover_height - cover_width - (cover_height * cover_margin / 100)) * 0.25
        x = cover_height * cover_margin / 100
        y = title_height
        width = cover_width - (2 * cover_height * cover_margin / 100)
        height = author_height
        cover_image.text(author, x, y, width, height, fill, author_font)

    # Create the new cover image.
    cover_margin = 2
    cover_image = Image(cover_width, cover_height)

    # Draw the book cover.
    shape_color, base_color = processColors()
    drawBackground()
    drawArtwork()
    drawText()

    # Return the cover Image instance.
    return cover_image