Serebyte


Home | Tutorials | Portfolio

Win32 Software Renderer in C: Part 11 - Drawing Multi-Line Text


Note: this tutorial series requires significant knowledge of the C programming language


In this tutorial, we will look at drawing multi-line text to the screen.




The Theory:

We will draw our multi-line text by iterating through each of the characters in the text, and keeping track of the width of the current line. When the width of the current line is exceeded, we will find the most recent space (if there is one) and we will drop down a line from that point. After deciding where the end of the line should be, we will draw said line. When the end of the final line of text is reached, we will draw this line to the screen.




Modifications to Our drawSingleLineText Function

When we come to draw multi-line text to the screen, we will want to call our drawSingleLineText function to draw each individual line to the screen. Since each individual line is not null termiated (each line is a part of a larger string - the inputted text to draw), we will need a means of specifying how many characters we want to draw. To do this, we will need to pass in a new variable, an integer called length. This will replace the local variable called "length" in the function. I will put this parameter after the charSize parameter, so that the line creating our function now looks like this: "void drawSingleLineText(int x, int y, char text[], int charSize, int length, uint8_t red, uint8_t green, uint8_t blue)".


When we want to specify a number of characters to draw, we should pass a positive integer into the length argument. When we want to draw an unknown number of characters (up to a null terminator), we should pass in 0.


Inside our function, we should replace the first line "int length = strlen(text);" with the following three lines: "if(length < 1){", "length = strlen(text);" and "};". These lines will check to see if an invalid length has been passed in. If so, we will set the length to the length of the string (until null termination). This means that we can pass 0 into the length parameter when we want to draw a single line of text of unknown length. The rest of our drawSingleLineText function should go unchanged.




The drawMultiLineText Function

Now we can create our drawMultiLineText function. This should be done in the render.c file, and the function should be declared in the main.h file. It should be a void function and take 8 parameters. The first two should be the x and y coordinates. These will mark the bottom left corner of the top line of text. I will name these "x" and "y". The next is a null terminated string. I will name this "text". Next is another integer: the character size in pixels. I will name this "charSize". After this is the maximum width of a single line of text. I will call this "maxWidth". This should also be an integer. The next three are all of the type uint8_t. They are the red, green and blue values of the colour the text will be drawn in. This function can be created with the following function: "void drawMultiLineText(int x, int y, char text[], int charSize, int maxWidth, uint8_t red, uint8_t green, uint8_t blue){".


Inside this function, we first want to check if maxWidth is smaller than charSize. If this is the case, then we will not be able to draw any text, as the line width is smaller than a single character. We should return out of the function if this is the case. We can do this with the if statement: "if(charSize > maxWidth){", with the line "return;" inside.


If maxWidth is at least the size of a single character, we can continue with our function. Next, we should calculate the length of our string. We should save it to an integer variable called length. This can be done with the line: "int length = strlen(text);". Next, we should create another integer variable called startIndex. This will always point to the first character of the current line as we iterate through all the characters. This should be initialised to 0. This can be done with the line: "int startIndex = 0;". After this, we should create another integer named "rowNumber". This will store the number of the current line/row (starting with the top line being row 0). This should also be initialised to 0. This can be done with the line: "int rowNumber = 0;". The next variable to create is an integer named "charactersPerRow". This stores the maximum number of characters in a row. This should be initialised to maxWidth / charSize. We can do this with the line: "int charactersPerRow = maxWidth / charSize;".


Next, we should iterate through each character in the row. We should first create a loop counter called "i". This should be done with the line: "int i;". We can loop through all the characters using the line: "for(i = 0; i < length; i++){".


Inside the loop we want to continuously iterate until the end of a line is reached. Then we want to check for the most recent space and drop down a line. We can check if the end of the current line has been reaced using the if statement: "if((i + 1 - startIndex) * charSize > maxWidth){". The number of the current character on the current line is equal to i - startIndex, since startIndex always points to the start of the current line. We add 1 to i - startIndex because we want to get the rightmost side of the current chraacter, and we multiply by charSize to get the width of the current line so far (including our current character). If the width of the current line will be greater than maxWidth when our character is added, we want to drop down a new line.


Inside the previously mentioned if statement, we want to write the code to drop down a new line. Before we drop down a new line, we want to find the most recent space character, so we can drop the current word down onto the next line (and we don't have words spanning multiple lines if we can help it). To do this, we need to create a variable called endIndex. It should be an integer and should be initialised to -1. The purpose of this variable is to point to the most recent space character on the current line. We can create this with the line "int endIndex = -1;". After this, we should create a loop counter called j. We will use this to check the previous characters on the current line. We should start at the current character. Therefore, we should set j to the value of i, with the line: "int j = i;".


Now we need to subtract j until a space is found at text[j], until j is smaller than 0 or until the difference between i and j is greater than the number of characters per line (this indicates that the previous line has been exceeded). If there is not a space in the current line, we just want to drop down the current character to the next line. This can be accomplished with the while loop: "while(j >= 0 && j > i - charactersPerRow){". Inside this loop, we want to check if the character text[j] is a space, which we can do with the line: "if(text[j] == ' '){". If a space has been found, we should set endIndex to j. We should then break out of the loop. This can be achieved using the lines "endIndex = j;" and "break;". After the space-checking if statement, we want to subtract 1 from j to move along to the next character. We can do this with the line: "j -= 1;".


After this while loop, we should check if endIndex is still equal to -1 using the if statement: "if(endIndex == -1){". This means that no space could be found. If this is the case, then we want our current character to be the start of the new line, and therefore the character before it (at i - 1) should be the last character on the line. Because of this, we should set endIndex to i - 1, which we can do with this line: "endIndex = i - 1;". Since we want to draw the current character on the next line, we want to subtract 1 from i so that our current character is added to the next line in the next iteration. We can do this with the line: "i -= 1;". If endIndex is not equal to -1, then it must be a valid index. Since we want to draw everything after endIndex on a separate line, we should set i to endIndex + 1, so that everything after the previous space gets added to the next line. We can do this with the line: "i = endIndex;".


After setting the values of endIndex and i, we finally want to display our line of text. This can be done with the line: "drawSingleLineText(x, y - rowNumber * (charSize * 1.25), text + startIndex, charSize, (endIndex + 1 - startIndex), red, green, blue);". We always want to start at the x coordinate passed into our drawMultiLineText function, as this is the coordinate of the left side of the text. The y will vary with each row. I want the vertical distance between the bottom corners of each row to be equal to 1.25 multiplied by the character size, so that there is always a neat gap between lines to make them easier to read. We want to subtract this value once from y for each row of characters we wish to draw, hence why the y coordinate passed in is y - rowNumber * 1.25 * charSize. You can use whatever value you would like in the place of the 1.25. We add the startIndex value to the text string so that the string always starts from the beginning of the current line when drawing. The length of the string is set to endIndex + 1 - startIndex. This is because we want to include the character at endIndex in our rendering. To better understand this, imagine there are two characters you want to read, at positions 0 and 1 in a string. The difference between the positions of these characters is equal to 1 - 0 = 1, even though there are two characters we want to read, not one. This is why we add 1 to endIndex - startIndex.


After drawing this line of text, we want to increment the row number. We can do this using the line: "rowNumber += 1;". We also want to set the start index to endIndex + 1, as this will be the first character in the new line. This can be done with the line: "startIndex = endIndex + 1;".


If the maxWidth value has not been exceeded on the current line (i.e. the "if((i + 1 - startIndex) * charSize > maxWidth){" if statement failed), we want to check if the end of the string has been reached. We can do this with the else if statement: "} else if(i == length - 1){". If this else if statement is true, then we should draw the final row of characters using the line of code: "drawSingleLineText(x, y - rowNumber * (charSize * 1.25), text + startIndex, charSize, (i + 1 - startIndex), red, green, blue);". Here, i is the final character of the text and of our line.


That completes our drawMultiLineText call. Now we simply have to run it inside our render function. I will do this with the line: "drawSingleLineText(x, y - rowNumber * (charSize * 1.25), text + startIndex, charSize, (i + 1 - startIndex), red, green, blue);".


That's all for the theory of this tutorial. As usual, the code for this tutorial is shown below.




The Code:

I have only included the code for render.c here, as the code for main.c has not changed and the only addition to main.h is the modification of the drawSingleLineText declaration, and the addition of the drawMultiLineText declaration. As usual, I will be compiling this code with the MinGW compiler, using the command "gcc main.c -lgdi32 -o app".


render.c

/*
 render.c

 This file contains all of the code used for rendering shapes, sprites, etc.

 This file is included into main.c directly, so we can still use global variables.
*/


//draw rectangle
void drawRectangle(int startX, int startY, int endX, int endY, uint8_t red, uint8_t green, uint8_t blue){
/*
  drawRectangle
  
  This function draws a rectangle to the render buffer.
  It takes seven parameters:
   -The x coordinate of the bottom left corner
   -The y coordinate of the bottom left corner
   -The x coordinate of the top right corner
   -The y coordinate of the top right corner
   -The red value of the pixel
   -The green value of the pixel
   -The blue value of the pixel
 */


//create pixel pointer
 Pixel * pixel;

//iterate from startY to endY (iterate through each row)
 int i = 0;
 int j = 0;

 for(i = startY; i <= endY; i++){
  /*
   The bottom left corner of the render buffer is (0,0).
   We need to set the pixel pointer to left side of rectangle on row i so that we
   can begin rendering the new row.
   Therefore, we want set the pixel pointer to renderBuffer.pixels + i * BUFFER_WIDTH + startX.
   renderBuffer.pixels marks the start of the buffer. We want to add a row (of width BUFFER_WIDTH) to our
   pixel pointer until we get to the row we want to draw on. Therefore, we want to move i rows
   up. We then add startX to move the pixel pointer to the left side of the rectangle on that row.
  */


  //set pixel pointer to left side of rectangle on current row
  pixel = renderBuffer.pixels + i * BUFFER_WIDTH + startX;
  
  //iterate from startX to endX and draw pixels on row
  for(j = startX; j <= endX; j++){
   //check if pixel is on screen 
   if(i >= 0 && i < BUFFER_HEIGHT && j >= 0 && j < BUFFER_WIDTH){
    //render pixel by setting red, green and blue values
    pixel->red = red;
    pixel->blue = blue;
    pixel->green = green;
   };
   
   //increment pixel
   pixel++;
  };
 };
};

//draw line
void drawLine(int startX, int startY, int endX, int endY, uint8_t red, uint8_t green, uint8_t blue){
//calculate changes in x and y
 int dx = endX - startX;
 int dy = endY - startY;

//individual steps for x and y
 float xStep;
 float yStep;
 int totalSteps = 0;

//check if dy > dx
 if(abs(dy) > abs(dx)){
  /*
  The total number of steps is equal to the total increase in y, as we are increasing y by 1 (or -1 if dy < 0) each time.
  We are increasing y by 1 (or -1 if dy < 0) because it dy is larger than dx in terms of magnitude, so we must choose to increment y
  to avoid skipping pixels. If we were to increase x by 1, we would get increases of y larger than 1 as dy/dx > 1. This would cause
  some pixels to be left out of our line.
  */

  totalSteps = abs(dy);
 } else {
  /*
  The total number of steps is equal to the total increase in x, as we are increasing x by 1 (or -1 if dx < 0) each time.
  We are increasing x by 1 (or -1 if dx < 0) because it dx is larger than dy in terms of magnitude, so we must choose to increment x
  to avoid skipping pixels. If we were to increase y by 1, we would get increases of x larger than 1 as dx/dy > 1. This would cause
  some pixels to be left out of our line.
  */

  totalSteps = abs(dx);
 };

//absolute value of gradient is steeper than 1, so increase by 1 along the y-axis each step
 yStep = (float) dy / totalSteps; //calculate the change in y per step
 xStep = (float) dx / totalSteps; //the increase in x per unit y (i.e. the amount to increase x by each step)

//create loop counter and set to 0
 int i = 0;

//create x and y values
 float x = startX;
 float y = startY;

//create pixel pointer - this will always point to the pixel to set
 Pixel * pixel;

//iterate for all steps
 for(int i = 0; i <= totalSteps; i++){
  //check that x and y are within bounds
  if(x >= 0 && x < BUFFER_WIDTH && y >= 0 && y < BUFFER_HEIGHT){
   //plot (x,y)
   //first, set pixel pointer to current pixel (equal to renderBuffer.pixels + y * BUFFER_WIDTH + x)
   //Note that we add y * BUFFER_WIDTH, as we are adding y rows of size BUFFER_WIDTH to the start of our pixel pointer to move it to the correct y-value
   pixel = renderBuffer.pixels + ((int) y) * BUFFER_WIDTH + (int) x;
   
   //set pixel colours
   pixel->red = red;
   pixel->green = green;
   pixel->blue = blue;
  };
  
  //increase x and y
  x += xStep;
  y += yStep;
 };
};

//draw row
void drawRow(int x1, int x2, int y, uint8_t red, uint8_t green, uint8_t blue){
//create pixel pointer
 Pixel * pixel;

//create min and max x coordinates
 int minX;
 int maxX;

//check x coordinates
 if(x1 < x2){
  minX = x1;
  maxX = x2;
 } else {
  minX = x2;
  maxX = x1;
 };

//set pixel to start of row
 pixel = renderBuffer.pixels + (BUFFER_WIDTH * y) + minX;

//iterate through all pixels in the row
 int i;
 for(i = minX; i <= maxX; i++){
  //check if on screen
  if(i >= 0 && i < BUFFER_WIDTH && y >= 0 && y < BUFFER_HEIGHT){
   pixel->red = red;
   pixel->green = green;
   pixel->blue = blue;
  };
  
  //increment pixel
  pixel ++;
 };
};

//render triangle
void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, uint8_t red, uint8_t green, uint8_t blue){
//create lists of points
 int pointsX[3];
 int pointsY[3];

 pointsX[0] = x1;
 pointsX[1] = x2;
 pointsX[2] = x3;

 pointsY[0] = y1;
 pointsY[1] = y2;
 pointsY[2] = y3;

 int i = 0;

 for(i = 1; i < 3; i++){
  int j = i;
  
  while(j > 0 && pointsY[j - 1] > pointsY[j]){
   //swap
   int temp = pointsY[j];
   pointsY[j] = pointsY[j - 1];
   pointsY[j - 1] = temp;
   
   temp = pointsX[j];
   pointsX[j] = pointsX[j - 1];
   pointsX[j - 1] = temp;
   
   //decrement j
   j -= 1;
  };
 };

//draw first half of triangle
//create x coordinates for lines of triangles
 float lineX1 = pointsX[0];
 float lineX2 = pointsX[0];

//calculate the change in x per unit change in y for the lines 0->1 and 0->2
 float xStep1 = (float) (pointsX[1] - pointsX[0]) / (pointsY[1] - pointsY[0]);
 float xStep2 = (float) (pointsX[2] - pointsX[0]) / (pointsY[2] - pointsY[0]);

//iterate through all rows
 for(i = pointsY[0]; i < pointsY[1]; i++){
  //draw row between x1 and y1
  drawRow((int) lineX1, (int) lineX2, i, red, green, blue);
  
  //increase line x coordinates
  lineX1 += xStep1;
  lineX2 += xStep2;
 };

//draw second half of triangle
//recalculate xStep1, so that it is now the change in x per unit y
 xStep1 = (float) (pointsX[2] - pointsX[1]) / (pointsY[2] - pointsY[1]);

//iterate through all rows
 for(i = pointsY[1]; i < pointsY[2]; i++){
  //draw row between x1 and y1
  drawRow((int) lineX1, (int) lineX2, i, red, green, blue);
  
  //increase line x coordinates
  lineX1 += xStep1;
  lineX2 += xStep2;
 };
};

//draw circle
void drawCircle(int centreX, int centreY, int radius, uint8_t red, uint8_t green, uint8_t blue){
/*
  The circle is made up of 2 * radius rows of pixels.
  We want to iterate from the bottom-most row (centreY - radius) to
  the top-most row (centreY + radius).
 */

 int i = 0;

 for(i = centreY - radius; i <= centreY + radius; i++){
  /*
   The equation of a circle is given by the formula (x - a)^2 + (y - b)^2 = r^2,
   where (a,b) is the centre of the circle and r is the radius.
   We know that y = i, a = centreX, b = centreY and r = radius.
   This gives the equation: (x - a)^2 = k, where k is a constant
   equal to r^2 - (y - b)^2
   Therefore, x - a = + or - sqrt(k).
   Therefore the two values of x to draw a row between are:
   a + sqrt(k)
   a - sqrt(k)
  */

  
  //set k value
  double k = radius * radius - (i - centreY) * (i - centreY);
  
  //draw row from centreX - sqrt(k) to centreX + sqrt(k)
  drawRow((int) centreX - sqrt(k), (int) centreX + sqrt(k), i, red, green, blue);
 }; 
};

//load BMP file
int loadBMPFile(char filePath[], Bitmap * bitmap){
//attempt to open file
 FILE * file = fopen(filePath, "rb");

//check if file opened correctly
 if(!file){
  //return unsuccessful
  return 0;
 };

//get file size
 fseek(file, 0, SEEK_END);
 int fileSize = ftell(file);
 rewind(file);

//check if file is too small to be a bitmap file
 if(fileSize < sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)){
  fclose(file);
  
  //return unsuccessful
  return 0;
 };

//read bitmap file header
 fread(&(bitmap->fileHeader), sizeof(BITMAPFILEHEADER), 1, file);

//read bitmap info header
 fread(&(bitmap->infoHeader), sizeof(BITMAPINFOHEADER), 1, file);

//allocate memory for pixels
//the size of the bitmap can be found using bitmap->infoHeader.biWidth * bitmap->infoHeader.biHeight * sizeof(Pixel)
 bitmap->pixels = (Pixel *) malloc(bitmap->infoHeader.biWidth * bitmap->infoHeader.biHeight * sizeof(Pixel));

//copy bitmap data into pixel buffer
 fread(bitmap->pixels, sizeof(Pixel), bitmap->infoHeader.biWidth * bitmap->infoHeader.biHeight, file);

//close file
 fclose(file);

//return successful
 return 1;
};

//load all bitmaps
void loadAllBitmaps(){
//load test bitmap
 loadBMPFile("test.bmp", &bmp_test);

//load font bitmap
 loadBMPFile("font.bmp", &bmp_font);
};

//draw BMP file
void drawBitmap(int x, int y, Bitmap * bitmap){
//create pixel pointer
 Pixel * pixel;

//create loop counters
 int i = 0;
 int j = 0;

//iterate through all rows of bitmap
 for(i = 0; i < bitmap->infoHeader.biHeight; i++){
  //set pixel pointer to start of row y + i
  pixel = renderBuffer.pixels + (y + i) * BUFFER_WIDTH + x;
  
  //iterate through x coordinates
  for(j = 0; j < bitmap->infoHeader.biWidth; j++){
   //check that pixel is on screen
   if(j >= 0 && j < BUFFER_WIDTH && y + i >= 0 && y + i < BUFFER_HEIGHT){
    //get pointer to source pixel
    Pixel * srcPixel = bitmap->pixels + i * bitmap->infoHeader.biWidth + j;
   
    //check that pixel is not the colour we will be using as transparent
    if(srcPixel->red != TRANSPARENT_RED && srcPixel->green != TRANSPARENT_GREEN && srcPixel->blue != TRANSPARENT_BLUE){ 
     //pixel is not colour used to indicate transparency
     
     //set colour of pixel on renderBuffer pointed to by pixel pointer
     pixel->red = srcPixel->red;
     pixel->green = srcPixel->green;
     pixel->blue = srcPixel->blue;
    };
    
    //increment pixel pointer
    pixel++;
   };
  };
 };
};

//initialiseSprites
void initialiseSprites(){
//test sprite
 spr_test.bitmap = &bmp_test;
 spr_test.startX = 64;
 spr_test.endX = 128;
 spr_test.startY = 0;
 spr_test.endY = 64;
 spr_test.centreX = 32;
 spr_test.centreY = 32;
 spr_test.xScale = 1.0;
 spr_test.yScale = 1.0;
 spr_test.isPlainColour = 0;
};

//draw sprite
void drawSprite(int x, int y, Sprite * sprite){
//get width and height
 int width = abs(sprite->endX - sprite->startX);
 int height = abs(sprite->endY - sprite->startY);

//create vertex array
//4 vertices in the format {x, y}
 float vertices[4][2];

//bottom left
 vertices[0][0] = -sprite->centreX;
 vertices[0][1] = -sprite->centreY;

//top left
 vertices[1][0] = vertices[0][0];
 vertices[1][1] = vertices[0][1] + height;

//top right
 vertices[2][0] = vertices[0][0] + width;
 vertices[2][1] = vertices[1][1];

//bottom right
 vertices[3][0] = vertices[2][0];
 vertices[3][1] = vertices[0][1];

//multiply each vertex by xScale and yScale
 int i;

 for(i = 0; i < 4; i++){
  vertices[i][0] *= sprite->xScale;
  vertices[i][1] *= sprite->yScale;
 };

//create minimum and maximum coordinates
 int minX = vertices[0][0];
 int maxX = vertices[0][0];
 int minY = vertices[0][1];
 int maxY = vertices[0][1];

//calculate minimum and maximum coordinates
 for(i = 0; i < 4; i++){
  if(vertices[i][0] > maxX){
   maxX = vertices[i][0];
  };
  
  if(vertices[i][0] < minX){
   minX = vertices[i][0];
  };
  
  if(vertices[i][1] > maxY){
   maxY = vertices[i][1];
  };
  
  if(vertices[i][1] < minY){
   minY = vertices[i][1];
  };
 };

//add x and y coordinates to bounding coordiantes to translate sprite to screen
 minX += x;
 maxX += x;
 minY += y;
 maxY += y;

//create pixel pointer
 Pixel * pixel;

//create second loop counter
 int j;

//iterate from min y to max y
 for(i = minY; i < maxY; i++){
  //set pixel pointer to start of row
  pixel = renderBuffer.pixels + i * BUFFER_WIDTH + minX;
  
  //iterate through x coordiantes
  for(j = minX; j < maxX; j++){
   //check the coordinates (j, i) are on screen
   if(j >= 0 && j < BUFFER_WIDTH && i >= 0 && i < BUFFER_HEIGHT){
    //get coordinates of src pixel
    int pixelX = (int) ((j - x) / sprite->xScale) + sprite->centreX + sprite->startX;
    int pixelY = (int) ((i - y) / sprite->yScale) + sprite->centreY + sprite->startY;
    
    //check that pixelX and pixelY are within sprite bounds
    if(pixelX >= sprite->startX && pixelX < sprite->endX && pixelY >= sprite->startY && pixelY < sprite->endY){
     //retrieve source pixel
     Pixel * srcPixel = sprite->bitmap->pixels + (pixelY * sprite->bitmap->infoHeader.biWidth) + pixelX;
     
     //check that pixel is not transparent
     if(srcPixel->red != TRANSPARENT_RED && srcPixel->green != TRANSPARENT_GREEN && srcPixel->blue != TRANSPARENT_BLUE){
      //check if plain colour is enabled
      if(sprite->isPlainColour){
       //if plain colour is enable, replace all non-transparent pixels with the colour value stored in the sprite
       pixel->red = sprite->red;
       pixel->green = sprite->green;
       pixel->blue = sprite->blue;
      } else {
       //draw pixel
       pixel->red = srcPixel->red;
       pixel->green = srcPixel->green;
       pixel->blue = srcPixel->blue;
      };
     };
    };
   };
   
   //increment pixel pointer
   pixel ++;
  };
 };
};

//get coordinates from character
void getCoordinatesFromCharacter(char c, int * x, int * y){
//get the coordinates of the character

//note that the numerical value of 'a' is 97, and the numerical value of A is 65

//check if character is lower case - if so, convert to upper case (as font.bmp only supports upper case characters)
 if(c >= 'a' && c <= 'z'){
  /*
   Convert to upper case.
   We can do this by subtracting the difference between the numerical
   values of 'a' and 'A' from c.
  */

  c -= ('a' - 'A');
 };

//check if character is an upper case letter
 if(c >= 'A' && c <= 'Z'){
  /*
   Set the x coordinate of the character on the font bitmap.
   The font.bmp file is made of two rows of 26 characters.
   Each character is 8x8 px. Therefore, the starting x coordinate
   of the character is the number of the letter (starting at A = 0)
   multiplied by the character size. We can get the number of the letter by subtrating
   the value of 'A' from our character (c).
  */

  *x = (c - ('A')) * CHARACTER_SIZE; //letter number multiplied by size of one character
  
  
  /*
   Set the y coordinate of the character on the font bitmap.
   There are two rows in the font.bmp file. All of the letters
   are located on the top row. Remember that the bottom left
   corner is the point (0, 0). Therefore, we can get the y coordinate
   of the top row by getting the height of the bitmap and subtracting
   the height of one character (8 pixels).
  */

  *y = bmp_font.infoHeader.biHeight - CHARACTER_SIZE; //bitmap height subtract height of one character
 } else if(c >= '0' && c <= '9'){
  /*
   The numbers are on the second row down. They start at x coordinate 0, and
   there are ten digits that we may need to render. Therefore the x coordinate
   of each number is (c - '0') * CHARACTER_SIZE. The y coordinate of each number is the height
   of the bitmap - 2 * CHARACTER_SIZE - the y coordinate of the second row down.
  */

  
  *x = (c - '0') * CHARACTER_SIZE; //number value multiplied by width of character
  *y = bmp_font.infoHeader.biHeight - 2 * CHARACTER_SIZE; //bitmap height - 2 * height of character
 } else {
  //set default y coordinate (second row down)
  *y = bmp_font.infoHeader.biHeight - 2 * CHARACTER_SIZE;
  
  //all other characters we want to render should be handled here
  switch(c){
   case '.':{
    *x = 10 * CHARACTER_SIZE;
    break;
   };
   
   case ',':{
    *x = 11 * CHARACTER_SIZE;
    break;
   };
   
   case ':':{
    *x = 12 * CHARACTER_SIZE;
    break;
   };
   
   case '!':{
    *x = 13 * CHARACTER_SIZE;
    break;
   };
   
   case '?':{
    *x = 14 * CHARACTER_SIZE;
    break;
   };
   
   case '-':{
    *x = 15 * CHARACTER_SIZE;
    break;
   };
   
   case '+':{
    *x = 16 * CHARACTER_SIZE;
    break;
   };
   
   default:{
    //render blank space
    *x = 17 * CHARACTER_SIZE;
    break;
   };
  };
 };
};

//draw single line text
void drawSingleLineText(int x, int y, char text[], int charSize, int length, uint8_t red, uint8_t green, uint8_t blue){
//get string length if not set already
 if(length < 1){
  length = strlen(text);
 };

//set scale
 float scale = (float) charSize / CHARACTER_SIZE;

//set up character sprite
 Sprite character;
 character.bitmap = &bmp_font;
 character.xScale = scale;
 character.yScale = scale;
 character.centreX = 0;
 character.centreY = 0;
 character.isPlainColour = 1;
 character.red = red;
 character.green = green;
 character.blue = blue;

//iterate through each character
 int i;

 for(i = 0; i < length; i++){
  //get character coordinates
  getCoordinatesFromCharacter(text[i], &character.startX, &character.startY);
  
  //add size of character to start x and y to get end x and y
  character.endX = character.startX + CHARACTER_SIZE;
  character.endY = character.startY + CHARACTER_SIZE;
  
  //draw sprite
  drawSprite(x + i * scale * CHARACTER_SIZE, y, &character);
 };
};

//draw multiline text
void drawMultiLineText(int x, int y, char text[], int charSize, int maxWidth, uint8_t red, uint8_t green, uint8_t blue){
//check if size of character is bigger than size of line
 if(charSize > maxWidth){
  //not possible to draw text, exit function
  return;
 };

//get length of text
 int length = strlen(text);

//create start index - this always points to the start of the current line
 int startIndex = 0;

//get row number
 int rowNumber = 0;

//calculate characters per row
 int charactersPerRow = maxWidth / charSize;

//iterate through all characters
 int i;

 for(i = 0; i < length; i++){
  //check if max width has been exceded
  //we start from i + 1, as we want to start from the rightmost pixel of the current character
  if((i + 1 - startIndex) * charSize > maxWidth){
   /*
    Drop down a line before drawing the next character.
    The last space must be found so that we start the
    current word on the new line, and not just the new
    character.
   */

   
   /*
    endIndex stores the index of the previous space.
    If no space could be found on the last line, endIndex is
    set to -1.
   */

   int endIndex = -1;
   
   int j = i;
   
   //find closest space
   //check for previous space until end of previous line is reached, or start of text is reached
   while(j >= 0 && j > i - charactersPerRow){
    //check for space
    if(text[j] == ' '){
     //space found, so exit loop
     endIndex = j;
     break;
    };
    
    //check previous character    
    j -= 1;
   };
   
   //if a space could not be found within previous line, move current character onto next line
   if(endIndex == -1){
    //set end index
    endIndex = i - 1;
    
    //move i back one space, so that current character is drawn on next line
    i -= 1;
   } else {
    //set i to the end of the previous word, so that the loop continues from the end of the line
    i = endIndex;
   };
   
   //draw line from start index to end index
   drawSingleLineText(x, y - rowNumber * (charSize * 1.25), text + startIndex, charSize, (endIndex + 1 - startIndex), red, green, blue);
   
   //increment row value
   rowNumber += 1;
   
   //set start index
   startIndex = endIndex + 1;
  } else if(i == length - 1){
   //the end of the string has been reached draw the final line
   drawSingleLineText(x, y - rowNumber * (charSize * 1.25), text + startIndex, charSize, (i + 1 - startIndex), red, green, blue);
  };
 };
};

//render function
void render(){
//set all pixels to 0 red, 0 blue, 0 green
 memset(renderBuffer.pixels, 0, BUFFER_WIDTH * BUFFER_HEIGHT * sizeof(Pixel));

//draw text
 drawMultiLineText(0, 400, "Hello world! This is a test of the multiline text system: abcdefghijklmnopqrstuvwxyz", 32, BUFFER_WIDTH, 0, 0, 255);

/*
  Send renderbuffer data to client area of window.
  We can do this with the StretchDIBits function.
  This takes many parameters, which are detailed below:
 */

 StretchDIBits(
  renderBuffer.deviceContextHandle, //a handle to the device context we wish to render to
  renderBuffer.windowClientWidth / 2 - (renderBuffer.scale * BUFFER_WIDTH) / 2, //the x coordinate of the top left coordinate of our buffer on the window client area
  renderBuffer.windowClientHeight / 2 - (renderBuffer.scale * BUFFER_HEIGHT) / 2, //the y coordinate of the top left coordinate of our buffer on the window client area
  BUFFER_WIDTH * renderBuffer.scale, //the width of the buffer on the window client area
  BUFFER_HEIGHT * renderBuffer.scale, //the height of the buffer on the window client area
  0, //the starting x coordinate on the source buffer to render from (we want to render all data, so this is 0)
  0, //the starting y coordinate on the source buffer to render from (we want to render all data, so this is 0)
  BUFFER_WIDTH, //the width of the source buffer
  BUFFER_HEIGHT, //the height of the source buffer
  renderBuffer.pixels, //a pointer to the actual data we want to send
  &renderBuffer.bitmapInfo, //a pointer to the bitmap info structure for our renderBuffer
  DIB_RGB_COLORS, //use RGB colours
  SRCCOPY //copy the source into the window's buffer
 );
};



Running Our Program:

Running our program should give the following output:

That's all for this tutorial. In the next tutorial, we will be looking at rotating a bitmap.