Resources Application¶
Warning
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 fromcsunplugged/resources/generators/
(for example:SortingNetworkResourceGenerator
).
thumbnail-static-path
: Thumbnail image for the resource, relative fromcsunplugged/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 tofalse
.)
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:
- data(self)¶
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 (eitherimage
orhtml
) as a string.data
: Iftype
ishtml
, then a HTML string is expected. If type isimage
, 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 toTrue
. 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:
@classmethod
def get_additional_options(cls):
"""Additional options for BinaryCardsSmallResourceGenerator."""
return {
"number_bits": EnumResourceParameter(
name="number_bits",
description=_("Number of Bits"),
values= {
"4": _("Four (1 to 8)"),
"8": _("Eight (1 to 128)"),
"12": _("Twelve (1 to 2048)")
},
default="4"
),
"dot_counts": BoolResourceParameter(
name="dot_counts",
description=_("Display Dot Counts"),
default=True,
),
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:
Note
The subtitle
method should be declared as a property with @property
and only have one parameter self
.
- subtitle(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
@property
def subtitle(self):
"""Return the subtitle string of the resource.
Used after the resource name in the filename, and
also on the resource image.
Returns:
Text for subtitle (str).
"""
if self.requested_options["display_numbers"]:
display_numbers_text = "with numbers"
else:
display_numbers_text = "without numbers"
if self.requested_options["black_back"]:
black_back_text = "with black back"
else:
black_back_text = "without black back"
text = "{} - {} - {}".format(
display_numbers_text,
black_back_text,
super().subtitle
)
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¶
Create a new layer called
TEXT
(Anything on this layer will be ignored during the export to PNG).
Creating a new dynamic text field¶
Create a new transparent rectangular text box in the
TEXT
layer.Add sample text.
Set font, font size and colour.
Position and rotate text box as required.
Expand text box to define the area in which the text will be allowed.
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.
- Notes:
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:
Ensure all layers (including the
TEXT
layer) are visible.Click
Object -> Artboards -> Fit To Artwork Bounds
.
Now export the base PNG image:
Ensure the
TEXT
layer is hidden.Export PNG, ensuring that Use Artboards is selected.
Finally, export the SVG file containing the text field information:
Ensure all layers (including the
TEXT
layer) are visible.Click
File -> Save As
.Use the same file name (without extension) as was used for the PNG.
Choose
SVG
as the format, and selectUse Artboards
.Click
Save
.In the dropdown for
CSS Properties
, chooseStyle Attributes
.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 = Image.open("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.
textbox_drawer.write_text_box(
"title",
"This is some text",
horiz_just="center",
)
- Notes:
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
andvert_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
TextBoxDrawer.py
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.
Initialise TextBoxDrawer without an SVG path.
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).
Call
write_text_box
with the instantiated TextBox object instead of a textbox identifier.
A simple example:
from utils.TextBoxDrawer import TextBoxDrawer, TextBox
image = Image.open("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,
width,
height,
color="#ba325b",
font_path=font_path,
font_size=font_size
)
textbox_drawer.write_text_box(
box,
"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:
Black and white: The image must only contain a grayscale channel, with pixels either being white (255) or black (0).
Greyscale: The image must only contain a grayscale channel, and the pixels must be one of the following values:
0
84
168
255
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