Christmas trees are a traditional symbol that date back more than four hundred years in Europe, so what could be better for an advent article than something about creating Christmas tree images.
The typical, simplified, representation of the tree is several triangles of decreasing size stacked on top of each other with a small over-lap, so are fairly easy to create with a computer program.
Here I’ll use Scalable Vector Graphics (SVG) to draw the image as, given the description above, it seems perfectly suited to the task.
A little about SVG and creating it
SVG is an XML document format that describes an image as a set of vectors between points, it has primitives for lines and shapes and provides for the styling of the objects described.
Perhaps the simplest SVG document is something like:
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="100"
height="100">
<g>
<rect x="5" y="5" width="90" height="90" stroke="black" fill="green" />
</g>
</svg>
This describes a green filled square of side 90 (the units are essentially abstract and relative to the size of the display as the scalable nature of the image means they may not equate directly to, say, pixels.)
Now we could just print out the XML with a few variable interpolations in our program, but this could quite tedious and error-prone for anything more complicated than the above example. Fortunately Perl 6 has the handy SVG module that takes care of actually creating the well-formed XML document from a data structure that describes it. So our example rectangle can be created with something like:
use SVG;
say SVG.serialize(
svg => [
width => 100, height => 100,
:g[:rect[:x<5>, :y<5>, :width<90>, :height<90>, :stroke<black>, :file<green>]],
],
);
Essentially the arguments to serialize
are a set of nested Pairs: where value is a scalar type, the key and value are used to form an XML attribute, and where the value is a List of Pairs this will create an XML element named for the key, each Pair in the list being interpreted as above, thus allowing a complex document to built up in a simple declarative manner.
So we can generate our example Christmas tree by constructing the appropriate data structure, but as we are likely to have at least four objects in our image (three triangles and a rectangle for the trunk) with their associated attributes, this may get quite unweildy and difficult to alter if we want to change something.
So…
Let’s Abstract!
In order to make our SVG generation more flexible and open up opportunities for future code reuse, it might make sense to create a set of classes that represent the SVG primitives we may want to use and abstract away the generation of the data structure that will be serialized to XML.
So let’s start with something that can generate our original rectangle example:
use SVG;
class SVG::Drawing {
role Element {
method serialize() {
...
}
}
has Element @.elements;
has Int $.width;
has Int $.height;
class Group does Element {
has Element @.elements;
method serialize( --> Pair ) {
g => @!elements.map( -> $e { $e.serialize }).list;
}
}
class Rectangle does Element {
has Int $.x;
has Int $.y;
has Int $.width;
has Int $.height;
has Str $.stroke;
has Str $.fill;
method serialize( --> Pair) {
rect => [x => $!x, y => $!y, width => $!width, height => $!height, stroke => $!stroke, fill => $!fill ];
}
}
method serialize( --> Str ) {
SVG.serialize(svg => @!elements.map(-> $e { $e.serialize }).list);
}
}
If you want to run this example you should save it as SVG/Drawing.pm
in your current directory.
This gives a class to describe our image as a whole and co-ordinate the creation of the data structure that will get serialized as our SVG document and a class each for the g
(Group) and rect
(Rectangle) primitives that we used in our original example so we can do something like:
use SVG::Drawing;
my SVG::Drawing $drawing = SVG::Drawing.new(elements => [
SVG::Drawing::Group.new(elements => [
SVG::Drawing::Rectangle.new(x => 5, y => 5, width => 100, height => 100, stroke => "black", fill => "green" )
]);
]);
say $drawing.serialize;
To generate a similar document to the first one.
You have probably noticed the role Element
with the stubbed method serialize
: this is intended to describe the interface required of the primitive classes so that the classes that collect objects of the primitive classes can depend on the serialize
when they come to serialize those collected objects when the time comes to generate the XML document. Adding this from the outset makes it easier and more reliable to add classes to describe new primitives for our drawing.
Let’s Extend!
So, unless we were interested in making a rather modernist representation of a Christmas tree made out of different sized squares stacked on top of each other, we are going to need a way of creating the triangles that we need. Fortunately SVG provides a number of ways of creating arbitrary closed shapes from a set of co-ordinate points, but we’ll use the simplest, polygon
which has a single attribute points
that is a space separated list of the comma separated co-ordinates of the vertices of the shape, where the last one joins to the first to close the shape.
We’ll a new Polygon class to describe the polygon
primitive:
use SVG;
class SVG::Drawing {
role Element {
method serialize() {
...
}
}
has Element @.elements;
has Int $.width;
has Int $.height;
class Group does Element {
has Element @.elements;
method serialize( --> Pair ) {
g => @!elements.map( -> $e { $e.serialize }).list;
}
}
class Rectangle does Element {
has Int $.x;
has Int $.y;
has Int $.width;
has Int $.height;
has Str $.stroke;
has Str $.fill;
method serialize( --> Pair) {
rect => [x => $!x, y => $!y, width => $!width, height => $!height, stroke => $!stroke, fill => $!fill ];
}
}
class Point {
has Int $.x;
has Int $.y;
method Str( --> Str ) {
($!x, $!y).join: ',';
}
}
class Polygon does Element {
has Str $.stroke;
has Str $.fill;
has Point @.points;
method serialize( --> Pair ) {
polygon => [ points => @!points.join(' '), fill => $!fill, stroke => $!stroke ];
}
}
method serialize( --> Str ) {
SVG.serialize(svg => @!elements.map(-> $e { $e.serialize }).list);
}
}
Along with our new Polygon class there is also a Point class that describes the co-ordinate of the vertices of the polygon: the Str
method is provided to simplify the implementation of the serialize
method of the Polygon class as the elements of the @.points
attribute will be stringified as they are joined in the serialize
.
So now we can generate an image of similar appearance but constructed in a different way with something like:
use SVG::Drawing;
my SVG::Drawing $drawing = SVG::Drawing.new(elements => [
SVG::Drawing::Group.new(elements => [
SVG::Drawing::Polygon.new(stroke => "black", fill => "green", points => [
SVG::Drawing::Point.new(x => 5, y => 5),
SVG::Drawing::Point.new(x => 105, y => 5),
SVG::Drawing::Point.new(x => 105, y => 105),
SVG::Drawing::Point.new(x => 5, y => 105)
])
]);
]);
say $drawing.serialize;
This will produce an XML document like:
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<polygon points="5,5 105,5 105,105 5,105" fill="green" stroke="black" />
</g>
</svg>
So now we have nearly everything we need to draw our Chritmas tree, but at this point it is worth stepping back and showing some love for our future selves (or some other person who might need to work on the code.)
A Spot of Refactoring
When we created our new Polygon class we copied the S.stroke
and $.fill
attributes and arranged for them to be serialized in a similar manner to the Rectangle class. This might make sense if we were in a hurry and these were the only places they might be used, but as we read rhe SVG documentation it becomes clear that they can be applied to many of the SVG primitives, so it would make sense to refactor this now before we add any more classes that may need them.
The most obvious way of doing this is to create a new role that contains the attributes and also provides a method that returns a list of the pairs representing the attributes in the serialization:
use SVG;
class SVG::Drawing {
role Element {
method serialize() {
...
}
}
role Styled {
has Str $.stroke;
has Str $.fill;
method styles() {
( stroke => $!stroke, fill => $!fill ).grep( { .value.defined } );
}
}
has Element @.elements;
has Int $.width;
has Int $.height;
class Group does Element {
has Element @.elements;
method serialize( --> Pair ) {
g => @!elements.map( -> $e { $e.serialize }).list;
}
}
class Rectangle does Element does Styled {
has Int $.x;
has Int $.y;
has Int $.width;
has Int $.height;
method serialize( --> Pair) {
rect => [x => $!x, y => $!y, width => $!width, height => $!height, |self.styles ];
}
}
class Point {
has Int $.x;
has Int $.y;
method Str( --> Str ) {
($!x, $!y).join: ',';
}
}
class Polygon does Element does Styled {
has Point @.points;
method serialize( --> Pair ) {
polygon => [ points => @!points.join(' '), |self.styles ];
}
}
method serialize( --> Str ) {
SVG.serialize(svg => @!elements.map(-> $e { $e.serialize }).list);
}
}
So now we have a double benefit that we can add a new styled class without having to copy the attributes, plus we can add new styling attributes that we might want without needing to change the consuming classes.
With a bit of additional work we could probably lose the need to call the method from the role in the serialize
by, say, using a trait on the attributes that will allow us to select the attributes to serialize, but I’ll live that as an exercise as Christmas is coming and we still don’t have a tree.
One further abstraction
Now we’re in a good position to create our Christmas tree, as the triangle we need is just a form of polygon with three sides after all, but we’re going to want more than one and the calculation of the vertices is going to be rather repetitive, plus, because I’ve arbitrarily chosen to use equilateral triangles for simplicity, the co-ordinates of the other two corners can be calculated from that of the apex and the length of the sides, so if we had a Triangle class it can calculate itself and we need only concern ourselves with the size and position:
use SVG;
class SVG::Drawing {
role Element {
method serialize() {
...
}
}
role Styled {
has Str $.stroke;
has Str $.fill;
method styles() {
( stroke => $!stroke, fill => $!fill ).grep( { .value.defined } );
}
}
has Element @.elements;
has Int $.width;
has Int $.height;
class Group does Element {
has Element @.elements;
method serialize( --> Pair ) {
g => @!elements.map( -> $e { $e.serialize }).list;
}
}
class Rectangle does Element does Styled {
has Int $.x;
has Int $.y;
has Int $.width;
has Int $.height;
method serialize( --> Pair) {
rect => [x => $!x, y => $!y, width => $!width, height => $!height, |self.styles ];
}
}
class Point {
has Numeric $.x;
has Numeric $.y;
method Str( --> Str ) {
($!x, $!y).join: ',';
}
}
class Polygon does Element does Styled {
has Point @.points;
method serialize( --> Pair ) {
polygon => [ points => @.points.join(' '), |self.styles ];
}
}
class Triangle is Polygon {
has Point $.apex is required;
has Int $.side is required;
method points() {
($!apex, |self.base-points);
}
method base-points() {
my $y = $!apex.y + self.get-height;
(Point.new(:$y, x => $!apex.x - ( $!side / 2 )), Point.new(:$y, x => $!apex.x + ( $!side / 2 )));
}
method get-height(--> Num ) {
sqrt($!side**2 - ($!side/2)**2)
}
}
method dimensions() {
( height => $!height, width => $!width ).grep( { .value.defined } );
}
method serialize( --> Str ) {
SVG.serialize(svg => [ |self.dimensions, |@!elements.map(-> $e { $e.serialize })]);
}
}
This required some small changes in other places. The Int
of the attributes of Point are relaxed to Numeric as the results of the calculation of the vertices of the triangle may not be integers (or we’d wind up with a wonky triangle if we roumded them,) also the serialize
method of Polygon was altered to use the accessor method for points
rather than using the attribute directly so it can be over-ridded in our Triangle class to calculate the additional points for the base line of the triangle.
The calculation itself just uses some junior geometry to determine the height of base line to the apex using Pythagoras theorem to get the y co-ordinate of the both base-line points and the x co-ordinates are half the side length either side of the apex x co-ordinate.
Also when I was testing this I noticed that I hadn’t implemented the serialization of the height and width attributes previously, we’d got away with it as the rectangles didn’t go outside the default drawing area, but the triangle did and thus wasn’t displayed.
Anyway now we can draw a triangle with a minimum of code:
use SVG::Drawing;
my SVG::Drawing $drawing = SVG::Drawing.new(
elements => [
SVG::Drawing::Group.new(elements => [
SVG::Drawing::Triangle.new(stroke => "black", fill => "green",
apex => SVG::Drawing::Point.new(x => 100, y => 50),
side => 50,
)
])
],
height => 300,
width => 200
);
say $drawing.serialize;
Which will give a nice green equilateral triangle in a large enough space to draw our tree.
And finally our tree
Now we have the means to create the component parts of our simple tree, so we can put it all together with a relatively simple script:
use SVG::Drawing;
my SVG::Drawing $drawing = SVG::Drawing.new(
elements => [
SVG::Drawing::Group.new(elements => [
SVG::Drawing::Triangle.new(stroke => "green", fill => "green",
apex => SVG::Drawing::Point.new(x => 100, y => 50),
side => 50,
),
SVG::Drawing::Triangle.new(stroke => "green", fill => "green",
apex => SVG::Drawing::Point.new(x => 100, y => 75),
side => 75,
),
SVG::Drawing::Triangle.new(stroke => "green", fill => "green",
apex => SVG::Drawing::Point.new(x => 100, y => 100),
side => 100,
),
SVG::Drawing::Rectangle.new(stroke => "brown",
fill => "brown",
x => 90,
y => 185,
width => 20,
height => 40),
])
],
height => 300,
width => 200
);
say $drawing.serialize;
I’ve selected the size and positions of the shapes by trial and error, it probably could be done more scientifically.
Anyhow this gives rise to XML like this:
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
height="300"
width="200">
<g>
<polygon points="100,50 75,93.30127018922192 125,93.30127018922192" stroke="green" fill="green" />
<polygon points="100,75 62.5,139.9519052838329 137.5,139.9519052838329" stroke="green" fill="green" />
<polygon points="100,100 50,186.60254037844385 150,186.60254037844385" stroke="green" fill="green" />
<rect x="90" y="185" width="20" height="40" stroke="brown" fill="brown" />
</g>
</svg>
Which is a reasonable stylized Christmas tree with a minimum of user code.
Because of the way that we designed the module we have put ourselves in a good place to further extend this to have, say, a Circle class that can be used to add coloured baubles to our tree easily.
SVG is a very rich specification with a large number of primitives for most drawing needs and we have only implemented the minimum we require to draw our tree, but this could be extended to enable any kind of drawing you want.
2 thoughts on “Day 18 – An SVG Christmas Tree”