6. Text

Text is next most important thing after all the fundamentals. This can be broken up into levels.

6.1. Level 1: Drawing tiles from a map

The lowest layer involves drawing a single tile from a tile map stored in memory.

6.1.1. Top Level Draw Tile

The static function draw_tile will draw the tile using a specified draw callback, to allow for regular or wraparound drawing.

reg refers to the btprnt region to draw onto.

map is a buffer that contains the tilemap as a bitmap.

xpos and ypos indicate where to draw the tile on the region. The coordinates point to the top-left corner of the tile.

mx and my are coordinates for the tile. The position (1, 2) would indicate the tile on the second row (offset 1), on the third column (offset 2).

w and h indicate the width and height dimensions of the tile (in pixels).

scale is a scaling factor. 1 is regular, 2 is 2x, 3 is 3x, etc.

color is the color of the glyph. 1 is the 'on' color (usually black), and 0 is 'off' color (usually white).


<<static_funcdefs>>=
static void draw_tile(btprnt_region *reg,
                      btprnt_buf *map,
                      int xpos, int ypos,
                      int mx, int my,
                      int w, int h,
                      int scale, int color,
                      void (*draw)(btprnt_region*,int,int,int));

The draw_tile function uses btprnt_buf_read to read bits from the tilemap. Things like stride are abstracted away there, which makes this aspect of the function a little more straight forward.

Scaling is something that is handled here, which requires an additional nested loop, and some extra variables.

<<funcs>>=
static void draw_tile(btprnt_region *reg,
                      btprnt_buf *map,
                      int xpos, int ypos,
                      int mx, int my,
                      int w, int h,
                      int scale, int color,
                      void (*draw)(btprnt_region*,int,int,int))
{
    int startx;
    int starty;
    int x;
    int y;
    int c;

    startx = mx * w;
    starty = my * h;

    for (y = 0; y < h; y++) {
        for (x = 0; x < w; x++) {
            c = btprnt_buf_read(map,
                                startx + x,
                                starty + y);
            if (c) {
                if (scale == 1) {
                    draw(reg, xpos + x, ypos + y, color);
                } else {
                    int sx, sy;
                    for (sy = 0; sy < scale; sy++) {
                        for (sx = 0; sx < scale; sx++) {
                            draw(reg,
                                 xpos + x*scale + sx,
                                 ypos + y*scale + sy,
                                 color);
                        }
                    }
                }
            }
        }
    }
}

6.1.2. Default Draw Tile

<<funcdefs>>=
void btprnt_draw_tile(btprnt_region *reg,
                      btprnt_buf *map,
                      int xpos, int ypos,
                      int mx, int my,
                      int w, int h,
                      int scale, int color);
<<funcs>>=
void btprnt_draw_tile(btprnt_region *reg,
                      btprnt_buf *map,
                      int xpos, int ypos,
                      int mx, int my,
                      int w, int h,
                      int scale, int color)
{
    draw_tile(reg, map,
              xpos, ypos,
              mx, my,
              w, h,
              scale, color,
              btprnt_region_draw);
}

6.1.3. Draw Tile with Wraparound

This draws a tile in a region, but using wraparound.

<<funcdefs>>=
void btprnt_draw_tile_wrap(btprnt_region *reg,
                           btprnt_buf *map,
                           int xpos, int ypos,
                           int mx, int my,
                           int w, int h,
                           int scale, int color);
<<funcs>>=
void btprnt_draw_tile_wrap(btprnt_region *reg,
                           btprnt_buf *map,
                           int xpos, int ypos,
                           int mx, int my,
                           int w, int h,
                           int scale, int color)
{
    draw_tile(reg, map,
              xpos, ypos,
              mx, my,
              w, h,
              scale, color,
              btprnt_region_draw_wrap);
}

6.2. Level 2: Drawing a character

To draw the right tile, we need to be be able to match an ASCII character to the position on the map, staring with the 'space' character. Maps will assume the glyphs are in ascii order, and are fixed width and height.

The number of glyph columns in the map (ncols) is obtained in order to calculate the glyph coordinates gx and gy.

<<funcdefs>>=
void btprnt_draw_char(btprnt_region *reg,
                      btprnt_buf *map,
                      int xpos, int ypos,
                      int w, int h,
                      char c, int scale, int color);
<<funcs>>=
void btprnt_draw_char(btprnt_region *reg,
                      btprnt_buf *map,
                      int xpos, int ypos,
                      int w, int h,
                      char c, int scale, int color)
{
    int gx, gy;
    char o;
    int ncols;

    o = c - ' '; /* start at 0 */

    ncols = map->w / w;

    gx = o % ncols;
    gy = o / ncols;

    btprnt_draw_tile(reg, map,
                     xpos, ypos,
                     gx, gy,
                     w, h,
                     scale, color);
}

btprnt_draw_char_wrap can do the same thing, but with wrapping.

<<funcdefs>>=
void btprnt_draw_char_wrap(btprnt_region *reg,
                           btprnt_buf *map,
                           int xpos, int ypos,
                           int w, int h,
                           char c, int scale, int color);
<<funcs>>=
void btprnt_draw_char_wrap(btprnt_region *reg,
                           btprnt_buf *map,
                           int xpos, int ypos,
                           int w, int h,
                           char c, int scale, int color)
{
    int gx, gy;
    char o;
    int ncols;

    o = c - ' '; /* start at 0 */

    ncols = map->w / w;

    gx = o % ncols;
    gy = o / ncols;

    btprnt_draw_tile_wrap(reg, map,
                          xpos, ypos,
                          gx, gy,
                          w, h,
                          scale, color);
}

6.3. Level 3: Drawing a string

From there, a string characters can be drawn onto a region.

<<funcdefs>>=
void btprnt_draw_text(btprnt_region *reg,
                      btprnt_buf *map,
                      int xpos, int ypos,
                      int w, int h,
                      const char *str);
<<funcs>>=
void btprnt_draw_text(btprnt_region *reg,
                      btprnt_buf *map,
                      int xpos, int ypos,
                      int w, int h,
                      const char *str)
{
    int len;
    int n;
    len = strlen(str);

    for (n = 0; n < len; n++) {
        btprnt_draw_char(reg, map,
                         xpos + w*n, ypos,
                         w, h,
                         str[n], 1, 1);
    }
}

btprnt_draw_scrolling_text will draw text with bounds wrapping.

<<funcdefs>>=
void btprnt_draw_scrolling_text(btprnt_region *reg,
                                btprnt_buf *map,
                                int xpos, int ypos,
                                int w, int h,
                                const char *str);
<<funcs>>=
void btprnt_draw_scrolling_text(btprnt_region *reg,
                                btprnt_buf *map,
                                int xpos, int ypos,
                                int w, int h,
                                const char *str)
{
    int len;
    int n;
    len = strlen(str);

    for (n = 0; n < len; n++) {
        btprnt_draw_char_wrap(reg, map,
                              xpos + w*n, ypos,
                              w, h,
                              str[n], 1, 1);
    }
}

6.4. Level 4: Text wrapping

Since the dimensions of the textbox are known, some basic text wrapping can be implemented.

<<funcdefs>>=
void btprnt_draw_wraptext(btprnt_region *reg,
                          btprnt_buf *map,
                          int xpos, int ypos,
                          int w, int h,
                          const char *str);
<<funcs>>=
void btprnt_draw_wraptext(btprnt_region *reg,
                          btprnt_buf *map,
                          int xpos, int ypos,
                          int w, int h,
                          const char *str)
{
    int len;
    int n;
    int curpos;
    int line;
    int c;
    len = strlen(str);
    line = 0;
    curpos = 0;
    c = 0;

    for (n = 0; n < len; n++) {
        curpos = xpos + w*c;

        if (curpos >= reg->w) {
            curpos = xpos;
            line++;
            c = 0;
        }

        btprnt_draw_char(reg, map,
                         curpos, ypos + line*h,
                         w, h,
                         str[n], 1, 1);
        c++;
    }
}

6.5. Level 5: Word Wrapping

With a bit more sophistication, some basic word wrapping can done by writing the text chunks between spaces. If a word is larger than what it left, it will know to go to the next line. If the word is larger than how many characters there are on a line, it will do the best it can to break to wrap the text up.

<<funcdefs>>=
void btprnt_draw_textbox(btprnt_region *reg,
                         btprnt_buf *map,
                         int xpos, int ypos,
                         int w, int h,
                         const char *str,
                         int scale,
                         int color);

This function works by counting characters until it reaches a space. Once it finds that space, it will write that chunk of letters up to (and including) that space. Some arithmetic will done. If it happens that the number of characters exceeds the bounds of the current line position, it will start a new line. (Space needs to be included with this count so there aren't any trailing spaces at the end of a line.) If it happens that the number of characters is greater than the length of the line, it won't matter if a newline happens, and the word will be split up as best as it can.

To be clear: a newline shouldn't happen if the number of characters in a word is longer than the width. A weird edge case I ran into involved having the first word in the textbox be long. The original code added a empty line on the first line, which looked weird.

When the text has reached the end, it has to print out the last word, if there is any. This process is pretty much indentical to what happens in the for loop, except that some of the variables updated don't matter.

For now, I literally copy-pasted this twice because I'm tired and lazy. I may come back at some point and do something more elegant when I have the time.

<<funcs>>=
void btprnt_draw_textbox(btprnt_region *reg,
                         btprnt_buf *map,
                         int xpos, int ypos,
                         int w, int h,
                         const char *str,
                         int scale,
                         int color)
{
    int len;
    int n;
    int start;
    int nchars;
    int c;
    int line;
    len = strlen(str);

    start = 0;
    nchars = 0;
    c = 0;
    line = 0;
    for (n = 0; n < len; n++) {
        nchars++;
        if (str[n] == ' ' || str[n] == '\n') {
            int wordlen;
            int off;
            int i;
            int curpos;
            char x;

            wordlen = nchars*w*scale;
            off = xpos + c*w*scale;

            if ((off + wordlen) > reg->w) {
                /* nested if is a clumsy, but it works */
                if (wordlen < reg->w) {
                    line++;
                    c = 0;
                }
            }

            for (i = 0; i < nchars; i++) {
                curpos = xpos + c*w*scale;

                x = str[start + i];

                if ((curpos + w*scale) > reg->w || x == '\n') {
                    curpos = xpos;
                    line++;
                    c = 0;
                }


                if (x != '\n') {
                    btprnt_draw_char(reg, map,
                                     curpos,
                                     ypos + line*h*scale,
                                     w, h, x, scale, color);
                    c++;
                }
            }

            start = n + 1;
            nchars = 0;
        }
    }

    if (nchars > 0) {
        /* duplicate code alert ring ring ring */
        int wordlen;
        int off;
        int i;
        int curpos;

        wordlen = nchars * w * scale;
        off = xpos + c*w*scale;

        if ((off + wordlen) > reg->w) {
            line++;
            c = 0;
        }

        for (i = 0; i < nchars; i++) {
            curpos = xpos + c*w*scale;

            if ((curpos + w*scale) > reg->w) {
                curpos = xpos;
                line++;
                c = 0;
            }

            btprnt_draw_char(reg, map,
                             curpos, ypos + line*h*scale,
                             w, h,
                             str[start + i], scale, color);
            c++;
        }

        start = n + 1;
        nchars = 0;
    }
}



prev | home | next