Resources Application


The documentation for this page is still being written. This page will also cover:

  • Structure of resource application and model.
  • Creating new resources.

Resource Specification

Each resource requires the following:

  • Definition in YAML file.
  • Markdown file containing name/description.
  • Python module for creating the resource pages.
  • Thumbnail image.

Each of these are explained in further detail below. We recommend looking at existing resources to get a clearer picture of how new resources should look.

YAML file definition

Each resource is defined in the YAML file located at: csunplugged/resources/content/structure/resources.yaml.

Each resource requires the following:

  • <resource-key>: This is the key for the resource.
  • generator-module: Python module for generating resource, relative from csunplugged/resources/generators/ (for example: SortingNetworkResourceGenerator).
  • thumbnail-static-path: Thumbnail image for the resource, relative from csunplugged/static/ (for example: img/resources/sorting-network/thumbnail.png).
  • copies: Boolean if the resource should allow multiple copies. If each resource is exactly the same, then the value should be set to false.)

Markdown file

Every resource must have a markdown file containing the name and description of the resource. This will be parsed by Verto, and must begin with a top level heading which will be used as the resource name. This file must be named <resource-key>.md and located in csunplugged/resources/content/en.

Python module

The Python module defined in the YAML file must contain a class inheriting from resources.utils.BaseResourceGenerator. The Python class must have the same name as the module.

The generator class must contain the following method definition:


Create one copy of the resource.

Return type:A dictionary or list of dictionaries for each resource page.

Each dictionary must contain the following keys/value pairs:

  • type: Page type (either image or html) as a string.
  • data: If type is html, then a HTML string is expected. If type is image, a Pillow Image object is expected.

If a list of dictionaries is provided (when a resource has more than one page), one of the dictionaries must have the key thumbnail set to True. This is used to determine which page is used to create the resource thumbnails.

If specific user options are required for resource generation, the generator class can implement a function get_additional_options(self) which must return a dictionary mapping an option identifier to a ResourceParameter instance. For example, a resource subclass may implement the following:

def get_additional_options(cls):
    """Additional options for BinaryCardsSmallResourceGenerator."""
    return {
        "number_bits": EnumResourceParameter(
            description=_("Number of Bits"),
            values= {
                "4": _("Four (1 to 8)"),
                "8": _("Eight (1 to 128)"),
                "12": _("Twelve (1 to 2048)")
        "dot_counts": BoolResourceParameter(
            description=_("Display Dot Counts"),

The following ResourceParameter subclasses are available:

  • EnumResourceParameter - One from a set of predefined values
  • BoolResourceParameter - True/False values
  • TextResourceParameter - Freeform text value
  • IntegerResourceParameter - Integer value

Each ResourceParameter class has configurable options which are documented on in the class docstring. We recommend looking at existing resources to see how the various ResourceParameter classes can be used.

If get_additional_options is implemented, the subtitle property method should be overridden. The method should display the additional options and also call the parent’s subtitle result:


The subtitle method should be declared as a property with @property and only have one parameter self.


Return the subtitle string of the resource.

Used after the resource name in the filename, and also on the resource image.

Return type:Text for subtitle (str)

For example, a resource with options display_numbers and black_back might implement the following subtitle method

def subtitle(self):
    """Return the subtitle string of the resource.

    Used after the resource name in the filename, and
    also on the resource image.

        Text for subtitle (str).
    if self.requested_options["display_numbers"]:
        display_numbers_text = "with numbers"
        display_numbers_text = "without numbers"

    if self.requested_options["black_back"]:
        black_back_text = "with black back"
        black_back_text = "without black back"

    text = "{} - {} - {}".format(
    return text

If copies are required for the resource, COPIES = True should be added as a class constant on the subclass.

If custom thumbnails are to be displayed for each resource combination, the save_thumbnail method can be overridden.

Thumbnail image

This image should represent the resource, and be at least 350px high.

Dynamic Text Overlay

In many cases, resources comprise of a base PNG image with text dynamically overlayed from within the python view, based on a users request. Cases where this is necessary include:

  • Randomly generated numbers or data.
  • Text, which must be translated into the user’s language.

While the actual text is added dynamically, the layout/colour/font/size of that text on the resource should be determined as part of the design process. To achieve this, we have developed a pipeline to allow designers to define these text fields in Adobe Illustrator, and export them in an SVG format. This information can then be used to dynamically render the required text as closely as possible to the intended design. This process is outlined in more detail below, for both developers and designers.

For Designers

The following workflow has been designed for Adobe Illustrator. Currently, other graphics software is not supported.

Setting up the document

  1. Create a new layer called TEXT (Anything on this layer will be ignored during the export to PNG).

Creating a new dynamic text field

  1. Create a new transparent rectangular text box in the TEXT layer.
  2. Add sample text.
  3. Set font, font size and colour.
  4. Position and rotate text box as required.
  5. Expand text box to define the area in which the text will be allowed.
  6. Give the text box element a unique identifier.
    • This is achieved by expanding the TEXT layer in the layers panel, and double-clicking on the text box element.
    • The identifier must be unique across all layers.
  • Sample text must be added - do not leave the box empty.
  • The colour, font and font size information of the first character in the text will be used.
  • While we strive to match the original design as much as possible during rendering, the result is not exact. Ensure there is some padding around all sides of the text box to allow for this.
  • Text boxes in shapes other than rectangles are currently not supported.
  • During the design process, consider that some languages are written right to left.

Export procedure

Firstly, check that every element in the TEXT layer has been given a unique, lowercase identifier as outlined above.

Next, resize the artboard to fit to artwork bounds:

  1. Ensure all layers (including the TEXT layer) are visible.
  2. Click Object -> Artboards -> Fit To Artwork Bounds.

Now export the base PNG image:

  1. Ensure the TEXT layer is hidden.
  2. Export PNG, ensuring that Use Artboards is selected.

Finally, export the SVG file containing the text field information:

  1. Ensure all layers (including the TEXT layer) are visible.
  2. Click File -> Save As.
  3. Use the same file name (without extension) as was used for the PNG.
  4. Choose SVG as the format, and select Use Artboards.
  5. Click Save.
  6. In the dropdown for CSS Properties, choose Style Attributes.
  7. Click OK.

For Developers

Rendering text into defined text fields (i.e. defined in an SVG file)

To dynamically render text onto the resource, use the TextBoxDrawer class.

from utils.TextBoxDrawer import TextBoxDrawer

Load the base PNG image, set it up for editing, and then instantiate a TextBoxDrawer object.

image ="my_resource.png")
draw = ImageDraw.Draw(image)
textbox_drawer = TextBoxDrawer(image, draw, svg_path="my_resource.svg")

Dynamically populate text fields by calling the write_text_box function.

    "This is some text",
  • Justification information (horizontal and vertical) is not extracted from the original design. The default is top left, but can be overrided using the kwargs horiz_just and vert_just.
  • Colour, font, and font size information is extracted from the original design, but can also be set with kwargs here. If provided, kwargs will take precedence.
  • See the docstrings in for more detailed information on the options available.

Rendering text without defined text fields (i.e. without an SVG file)

It is also possible to use this class for dynamically rendering text without an SVG file to define the parameters of the text fields.

  1. Initialise TextBoxDrawer without an SVG path.
  2. Create an instance of the TextBox class to define the parameters of the text field (this is normally created automatically with the information extracted from the SVG).
  3. Call write_text_box with the instantiated TextBox object instead of a textbox identifier.

A simple example:

from utils.TextBoxDrawer import TextBoxDrawer, TextBox

image ="my_resource.png")
draw = ImageDraw.Draw(image)
textbox_drawer = TextBoxDrawer(image, draw)

font_path = "static/fonts/PatrickHand-Regular.ttf"
font_size = 40

x, y = 100, 200
width, height = 300, 400

# Vertices clockwise from top left
vertices = [(x, y), (x + width, y), (x + width, y + height), (x, y + height)]

box = TextBox(vertices,

  "This is some text"

Specific Resource Details

Pixel Painter

Adding new images

Each pixel grid page contains 20 rows and 15 columns of pixels. Therefore when adding new images, the width and height should be multiples of these numbers for optimal page usage (for example 40 pixels high by 60 pixels wide).

Each image is required to be available in the following variants:

  1. Black and white: The image must only contain a grayscale channel, with pixels either being white (255) or black (0).
  2. Greyscale: The image must only contain a grayscale channel, and the pixels must be one of the following values:
    • 0
    • 84
    • 168
    • 255
  3. Colour: The image must only contain a red, green, and blue channels (no alpha channel). Pixels must be one of the following RGB values:
    • 255, 255, 255 - White
    • 0, 0, 0 - Black
    • 255, 0, 0 - Red
    • 255, 143, 0 - Orange
    • 255, 243, 0 - Yellow
    • 76, 219, 5 - Green
    • 0, 162, 255 - Blue
    • 138, 0, 255 - Purple