Alignment

Drawing text in Inferno is fairly easy once you are reminded about the way the Draw module does things.

Drawing aligned text

The code for the Limbo module (aligned.b) develops the previous version. However, it's a bit more complicated than it really needs to be. As a result, it helps to split it up a bit.

The file starts like the previous ones, with the usual includes, global variable declarations and the module interface definition.

implement Test;

include "sys.m";
include "draw.m";
Context, Display, Font, Image, Point, Rect: import draw;

draw: Draw;
display: ref Display;
font: ref Font;

Test: module
{
    init: fn(nil: ref Context, nil: list of string);
};

It seems like a good idea to create an abstract data type (ADT) to encapsulate the properties and functions of a label. This will allow the properties of each label to be defined once and stored in one place, then the label can be drawn by calling its draw function.

Label: adt
{
    left, centre, right: con iota;

    text: string;
    font: ref Font;
    alignment: int;

    draw: fn(label: self ref Label, rect: Rect);
};

It's not necessary to go into the finer points of ADTs but it's worth noting that the self keyword in the definition of the draw function allows the function to be used as a method, just like in the Python language except that self is not used to refer to the instance.

The draw method is defined using its fully-qualified name, Label.draw. The instance being acted on, label, is passed as the first parameter.

Label.draw(label: self ref Label, rect: Rect)
{
    x: int;

    case label.alignment {
    Label.left =>
        x = rect.min.x;
    Label.centre =>
        width := label.font.width(label.text);
        x = rect.min.x + (rect.dx() - width) / 2;
    Label.right =>
        width := label.font.width(label.text);
        x = rect.max.x - width;
    * =>
        x = rect.min.x;
    }

    display.image.text((x, rect.min.y), display.white, (0,0), label.font,
                       label.text);
}

The case structure ensures that the position of the text is calculated according to the label's alignment property. Drawing the text is then a simple matter of calling the text method on the display's image, exposed by its image property.

Much of the init function is the same as for previous examples. The main difference being that the text is placed in Label instances with font and alignment information. The draw method of each label is called with a Rect value to draw it within a given rectangular area on the screen.

init(context: ref Context, nil: list of string)
{
    draw = load Draw Draw->PATH;
    display = Display.allocate(nil);
    font = Font.open(display, "*default*");

    # Draw the background, filling the entire screen.
    display.image.draw(Rect(Point(0,0), Point(320,240)), display.rgb(0,0,64),
                       nil, (0,0));

    # Create labels to show the text with different alignments.
    left_label := ref Label("Left", font, Label.left);
    centre_label := ref Label("Centre", font, Label.centre);
    right_label := ref Label("Right", font, Label.right);

    # Draw the labels.
    left_label.draw(Rect((2,50), (318,90)));
    centre_label.draw(Rect((2,100), (318,140)));
    right_label.draw(Rect((2,150), (318,190)));
}

Each Rect is defined using the top-left and bottom-right points. Here, a form of shorthand is used to avoid explicitly using Point to specify these. You can pass tuples with the appropriate contents instead of an ADT as arguments and Limbo will do the right thing.

The result of running the code in a 320×240 sized window is shown below.


Different text alignments

The Label type needs to be polished a bit. That can be done as I try to build something more complicated.


David Boddie
18 December 2021