Ce painter a été réalisé dans le cadre d'un projet du cours INFO0004-2 à l'Université de Liège durant l'année académique 2018-2019.

Cette documentation est partiellement adaptée des consignes du projet (écrites par C. Soldani).

Dernière mise à jour : jeudi 11 juillet 2019.


The painter is a tool for converting a textual description of geometric shapes into an image.

The textual description is contained in a .paint file and the created image is in .ppm format.

General structure of a file

A paint file is a text (ASCII) file. White-space is generally ignored, but separates tokens so that 123␣456 represents the two numbers 123 and 456, and not 123456.

A # character introduces a comment. All characters from the # to the end of the line where it appears should be ignored.

A paint file begins with a size WIDTH HEIGHT command. The file can then contain any number of shape definitions, color definitions and paint commands, which are described below.

In EBNF notation, this gives :

paint_file = size_command, {shape_def | color_def | paint_command}; size_command = "size", number, number;

The two numbers given to the size command, i.e. WIDTH and HEIGHT are the width and height of the image in pixels. Consequently, they should be positive integers (they may still be given as floating-point literals, as long as they have integer value).


Numbers in the paint language are floating-point numbers (except for the size command, as explained above). They can be given as floating-point or integer literals, or by taking the abscissa or ordinate of a point.

number = ["+" | "-"], ["."], digit, {digit} | ["+" | "-"], digit, {digit}, ".", {digit} | point, ".", "x" | "y"; digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";

All of the following are valid numbers : 123 (= 123.0), .5 (= 0.5), +42. (= 42.0), -3.14.


The paint language allows to name shapes and colors. A name begins with a letter, and can contain any number of letters, digits and underscores.

letter = ? any char c such that isalpha(c) is true ?; name = letter, {letter | digit | "_"};

All definitions and commands can only refer to names that were already defined above in the file. It is an error to use a name that is not defined, or is defined after the location where it is used.

It is also a error to try to give to a new shape the name of an existing shape, or to a color the name of an existing color. It is however fine to use the same name for a shape and a color, those lie in different namespaces.


A color is given by a triplet of numbers between braces. Each number represent a color component in the RGB color space, and should be between 0.0 and 1.0.

One can name a color with the color instruction, which takes a name and a color. A color name can be used wherever a color is expected.

color = "{" number, number, number, "}" | name; color_def = "color", name, color;


A point is defined by a pair of coordinates between braces, by referring to a point inside a shape, or by an arithmetic expression.

point = "{", number, number, "}" | named_point | point_expr; named_point = name, ".", name;

A named point is given by SHAPE_NAME.POINT_NAME where SHAPE_NAME is an existing shape name, and POINT_NAME is the name of a specific point inside that shape.

E.g. a_circle.c will return the center of the circle a_circle (assuming a_circle is a valid circle).

Valid point names depend on the shape type, and are described bellow.

A point can be subscripted with .x or .y to obtain respectively its abscissa and its ordinate.

E.g. a_circle.c.x returns the abscissa of the center of circle a_circle (if it is defined). {4 3}.y returns 3.

Point expressions allow to do arithmetic operations on points. They use a syntax reminiscent of scheme, where the operator precedes its operands, and the order of operations is explicit.

Addition and subtraction accept between one and an arbitrary number of points.

E.g. (- {12 0} {2 0} {3 1}) returns the point {7.0 -1.0} (as 12 − 2 − 3 = 7 and 0 − 0 − 1 = −1).

The multiplication and division take exactly one point and one number. They apply the operation to the coordinates of the point.

E.g. (/ {10 5} 2) returns point {5.0 2.5}.

point_expr = "(", "+" | "-", point, {point}, ")" | "(", "*" | "/", point, number, ")";


Each shape instruction is directly followed by a (new) name, which will be bound to the created shape. It is then followed by arguments which are specific to the type of shape being defined.

There are 4 primitive shapes : circles, ellipses, rectangles and triangles.

There are 4 derived shapes : shifts (translations) of a shape, rotations of a shape, unions of shapes and differences of shapes.

shape_def = "circ", name, point, number | "elli", name, point, number, number | "rect", name, point, number, number | "tri", name, point, point, point | "shift", name, point, name | "rot", name, number, point, name | "union", name, "{", name, {name}, "}" | "diff", name, name, name;

Arguments to the circ command are its center point, and its radius (which should be positive).

Among supported named points on a circle should be at least c for center, and cardinal points (n for north, nw for north-west, etc).


Arguments to the elli command are its center point {cx, cy}, its semi-major radius a, and its semi-minor radius b. Both radii should be positive.

Named points are similar to the circle ones, with the addition of f1 and f2 for the two focii of the ellipse (f1 being to the right).


Arguments to the rect command are its center point, its width and its height (both of which should be positive).


Arguments to the tri command are its three vertices (in no particular order).

Named points on a triangle are its vertices v0, v1 and v2, the middles of its sides s01, s02, s12, and its barycentre (or centroid, i.e. its center of mass) c.


Arguments to the shift command are a point giving the translation vector (tx,ty) and the name of the shape to be translated.

The resulting shape is exactly the same as the original one, but translated by (tx,ty), i.e. a point at (x, y) in the original shape will lie at (x + tx, y + ty) in the translated shape.

Named points of a shift shape are the same ones as those of the original shape, translated according to the translation vector.


Arguments to the rot command are the (trigonometric) angle of rotation in degrees, the point to rotate around, and the name of the shape to be rotated.

Named points of a rot shape are the same as the ones of the original shape, transformed according to the given rotation.


Arguments to the union command are the names of the shapes to be grouped into a new single shape. The resulting shape is the union of all given shapes, i.e. a point will be in the union if it is in at least one of the given shapes.

Named points of a union shape are those of its first shape.


Arguments to the diff command are the name of the shape to be subtracted from, and the name of the shape to be subtracted. A point is in the difference if it is in the first shape, but not in the second one.

Named points in a difference are those of the first shape (even if they are not part of the resulting shape after the subtraction).

Fill a shape

Arguments to the fill command are the name of a shape, and a color.

paint_command = "fill", name, color;

The given shape will be drawn to the image with the given color.

The origin of the image lies at (0, 0), with x direction going to the right, and y direction going to the top. A pixel is considered to have size one by one.

Finally, the image is initially black.