Created
March 4, 2015 15:41
-
-
Save tjb0607/dad0d141ec280751feb8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
MAKE FLAGS: g++ -std=gnu++0x ./bdf-cli-render.cpp | |
Name Tyler Beatty | |
Started 2015-03-02 | |
Last modified 2015-03-04 | |
Final Project | |
bdf-cli-render.cpp | |
input: | |
command line args: ./bdf-cli-render [<font> [string to render]] | |
if neither are given, the user will be prompted to enter the font (or go with the default). | |
if just a font is given, it will be in quiet mode where stdout will only have the rendered glyphs. | |
if both a font and a string are given, it will run in quiet mode and render only the given string, ignoring stdin. | |
the program will load a font in the Glyph Bitmap Distribution Format (BDF). | |
output: | |
the given string will be rendered in the given font to the command line, such that each character contains two pixels. | |
Example output: | |
tyler@desktop ~/programming/bdf-cli-render | |
% ./bdf-cli-render | |
Enter font directory [default: fonts/Tewi.bdf]: | |
Enter string to render: Hello, world! | |
█ █ ▀█ ▀█ ▀█ █ █ | |
█▄▄▄█ ▄▀▀▀▄ █ █ ▄▀▀▀▄ █ ▄ █ ▄▀▀▀▄ █▄▀▀▄ █ ▄▀▀▀█ █ | |
█ █ █▀▀▀▀ █ █ █ █ ▄▄ █ █ █ █ █ █ █ █ █ ▀ | |
▀ ▀ ▀▀▀▀ ▀▀ ▀▀ ▀▀▀ ▀█ ▀ ▀ ▀▀▀ ▀ ▀▀ ▀▀▀▀ ▀ | |
▀ | |
Enter string to render: █ | |
*/ | |
#include <iostream> | |
#include <fstream> | |
#include <cstring> | |
#include <sstream> | |
#include <new> | |
#include <bitset> | |
using namespace std; | |
#ifdef _WIN32 | |
const char FULL_BLOCK[] = { (char)(unsigned char)219, '\0' } // double typecast to prevent g++ warnings | |
const char UPPER_HALF_BLOCK[] = { (char)(unsigned char)223, '\0' } | |
const char LOWER_HALF_BLOCK[] = { (char)(unsigned char)220, '\0' } | |
const char DEFAULT_FONT[] = "fonts\\Tewi.bdf"; | |
#else | |
const char FULL_BLOCK[] = "\u2588"; // unicode | |
const char UPPER_HALF_BLOCK[] = "\u2580"; | |
const char LOWER_HALF_BLOCK[] = "\u2584"; | |
const char DEFAULT_FONT[] = "/usr/share/fonts/misc/Tewi-normal-11.bdf"; // changed to full directory for ruukasu | |
#endif | |
const int TERMINAL_WIDTH = 80; | |
//const int CHAR_SIZE_X = 8; // I'd rather not use constants here but doing otherwise would mean major restructuring | |
//const int CHAR_SIZE_Y = 16; | |
class Character { | |
public: | |
unsigned int* bitmap; // use a short array for the bitmap because a bitset's size must be known during compile | |
short dwidth; | |
short bbxWidth; | |
short bbxHeight; | |
short bbxOffsetX; | |
short bbxOffsetY; | |
short bitsPerRow; | |
}; | |
class Font { | |
private: | |
void InitBitmaps(); | |
bool LoadCharacter(ifstream& fontFile); | |
short ReadBitmap(ifstream& fontFile, unsigned int* bitmap); | |
public: | |
void DeleteBitmaps(); | |
bool Load(ifstream &fontFile); | |
short bbxWidth; | |
short bbxHeight; | |
short bbxOffsetX; | |
short bbxOffsetY; | |
short ascent; | |
short descent; | |
short bitsPerRow; // of character's bitmap | |
Character chars[256]; | |
}; | |
// read up to '\n' or '\0', return false if ends with '\0', silently truncate to stringSize | |
bool GetLine(istream &inputStream, char outputString[], int stringSize) | |
{ | |
char currentChar; | |
int stringIndex = 0; | |
inputStream.get(currentChar); | |
while (currentChar != '\n' && currentChar != '\0' && | |
(stringIndex + 1) < stringSize) | |
{ | |
outputString[stringIndex] = currentChar; | |
inputStream.get(currentChar); | |
stringIndex++; | |
} | |
outputString[stringIndex] = '\0'; | |
return currentChar != '\0'; | |
} | |
// read up to ' ', '\n', '\0', or stringSize; return the char that terminated the read | |
char GetWord(ifstream &fontFile, char string[], int stringSize) | |
{ | |
char currentChar; | |
int stringIndex = 0; | |
fontFile.get(currentChar); | |
while (currentChar != '\n' && currentChar != ' ' && currentChar != '\0' && | |
(stringIndex + 1) < stringSize) | |
{ | |
string[stringIndex] = currentChar; | |
fontFile.get(currentChar); | |
stringIndex++; | |
} | |
string[stringIndex] = '\0'; | |
return currentChar; | |
} | |
// move to the properties of a found string, return the pointer to the string found | |
char* MoveToNext(ifstream &fontFile, const char strings[5][32], int numStrings) | |
{ | |
char currentWord[128]; | |
char currentChar = '\n'; | |
char* foundItem = nullptr; | |
do | |
{ | |
if (currentChar != '\n') | |
fontFile.ignore(128, '\n'); // ignore characters until next line | |
currentChar = GetWord(fontFile, currentWord, 128); | |
if (currentChar == '\r') | |
currentChar = GetWord(fontFile, currentWord, 128); // handle DOS-style newlines | |
for ( int i = 0; i < numStrings; i++ ) | |
{ | |
if ( strcmp(strings[i], currentWord) == 0 ) | |
{ | |
foundItem = (char *)strings[i]; // the string it found | |
} | |
} | |
} while ( foundItem == nullptr && !fontFile.eof() ); | |
return foundItem; | |
} | |
int CalcBitsPerRow(short bbxWidth) | |
{ | |
return (bbxWidth + 7) & ~7; // adds 1 bit so that it will always round up, then truncates the last 3 binary digits to make it divisible by 8 | |
} | |
void Font::InitBitmaps() | |
{ | |
for ( int i = 0; i < 256; i++ ) | |
{ | |
chars[i].bitmap = new unsigned int [bbxHeight]; | |
} | |
} | |
void Font::DeleteBitmaps() | |
{ | |
for ( int i = 0; i < 256; i++ ) | |
{ | |
delete[] chars[i].bitmap; | |
} | |
} | |
// takes a list of 2-digit hex numbers and converts it to a bool matrix, returns the bits per row | |
short Font::ReadBitmap(ifstream &fontFile, unsigned int* bitmap) | |
{ | |
char currentWord[128]; | |
int i = 0; | |
int bitsPerRow = 0; | |
GetWord(fontFile, currentWord, 128); | |
while (strcmp(currentWord, "ENDCHAR")) | |
{ | |
if (!i) | |
bitsPerRow = strlen(currentWord) * 4; | |
stringstream tmp; // temporary stringstream for converting hex to a number | |
tmp << hex << currentWord; | |
tmp >> bitmap[i]; // bitmap[i] now stores the bits of the bitmap's row as a number, so that when converted to binary, 0 is a pixel, 1 is no pixel | |
GetWord(fontFile, currentWord, 128); | |
i++; | |
} | |
return bitsPerRow; | |
} | |
bool Font::LoadCharacter(ifstream &fontFile) | |
{ | |
const char bdfCharStrings[5][32] = { | |
"ENCODING", | |
"DWIDTH", | |
"BBX", | |
"BITMAP", | |
"ENDCHAR" | |
}; | |
short charEncoding; | |
bool endOfChar = false; | |
while ( !endOfChar ) | |
{ | |
char* foundItem; | |
foundItem = MoveToNext(fontFile, bdfCharStrings, 5); | |
if ( foundItem == nullptr ) | |
{ | |
cout << "ERROR: Invalid BDF file." << endl; | |
return false; | |
} | |
// convert pointer to index of pointer in bdfMetaStrings | |
int foundItemIndex = 0; | |
while ( foundItem != bdfCharStrings[foundItemIndex] ) | |
{ | |
foundItemIndex++; | |
if ( foundItemIndex >= 5 ) | |
{ | |
cout << "Internal error: bad pointer" << endl; | |
return false; | |
} | |
} | |
switch (foundItemIndex) | |
{ | |
case 0: // ENCODING | |
fontFile >> charEncoding; | |
if ( charEncoding > 255 ) | |
return false; | |
break; | |
case 1: // DWIDTH | |
fontFile >> chars[charEncoding].dwidth; | |
break; | |
case 2: // BBX | |
fontFile >> chars[charEncoding].bbxWidth; | |
fontFile >> chars[charEncoding].bbxHeight; | |
fontFile >> chars[charEncoding].bbxOffsetX; | |
fontFile >> chars[charEncoding].bbxOffsetY; | |
break; | |
case 3: // BITMAP | |
chars[charEncoding].bitsPerRow = ReadBitmap(fontFile, chars[charEncoding].bitmap); | |
endOfChar = true; | |
break; | |
case 4: // ENDCHAR | |
cout << "gotcha" << endl; | |
endOfChar = true; | |
break; | |
} | |
} | |
return true; | |
} | |
// returns whether or not loading the font was successful | |
bool Font::Load(ifstream &fontFile) | |
{ | |
const char bdfStartString[5][32] = { | |
"STARTFONT", | |
"", "", "", "" | |
}; | |
if ( MoveToNext(fontFile, bdfStartString, 1) == nullptr ) | |
{ | |
cout << "ERROR: Not a BDF file." << endl; | |
throw; | |
} | |
const char bdfStrings[5][32] = { | |
"FONTBOUNDINGBOX", | |
"FONT_ASCENT", | |
"FONT_DESCENT", | |
"STARTCHAR", | |
"ENDFONT" | |
}; | |
bool done = false; | |
while ( !done ) | |
{ | |
char* foundItem; | |
foundItem = MoveToNext(fontFile, bdfStrings, 5); | |
if ( foundItem == nullptr ) | |
{ | |
cout << "ERROR: Invalid BDF file." << endl; | |
return false; | |
} | |
// convert pointer to index of pointer in bdfMetaStrings | |
int foundItemIndex = 0; | |
while ( foundItem != bdfStrings[foundItemIndex] ) | |
{ | |
foundItemIndex++; | |
if ( foundItemIndex >= 5) | |
{ | |
cout << "Internal error: bad pointer" << endl; | |
return false; | |
} | |
} | |
switch (foundItemIndex) | |
{ | |
case 0: // FONTBOUNDINGBOX | |
fontFile >> bbxWidth; | |
fontFile >> bbxHeight; | |
fontFile >> bbxOffsetX; | |
fontFile >> bbxOffsetY; | |
bitsPerRow = CalcBitsPerRow(bbxWidth); | |
InitBitmaps(); | |
break; | |
case 1: // FONT_ASCENT | |
fontFile >> ascent; | |
break; | |
case 2: // FONT_DESCENT | |
fontFile >> descent; | |
break; | |
case 3: // STARTCHAR | |
if ( ! LoadCharacter(fontFile) ) | |
done = true; | |
break; | |
case 4: | |
done = true; | |
break; | |
} | |
if (fontFile.eof()) | |
done = true; | |
} | |
return true; | |
} | |
// places the character on the bitmap and returns true iff there's enough room | |
bool PutChar(Font myFont, char myChar, int& posX, bitset<TERMINAL_WIDTH>* bitmap, short bmpHeight) | |
{ | |
Character myCharacter = myFont.chars[(unsigned char)myChar]; | |
if (posX + myCharacter.dwidth >= TERMINAL_WIDTH) | |
return false; | |
// coordinates of top right corner of boundary box | |
int topRightX = posX + myCharacter.bbxOffsetX + myFont.bbxOffsetX + myCharacter.bitsPerRow + 2; | |
int topRightY = myFont.bbxOffsetY - myCharacter.bbxOffsetY - myCharacter.bbxHeight + myFont.bbxHeight; | |
for (int charbmpY = 0; charbmpY < myCharacter.bbxHeight; charbmpY++) | |
{ | |
unsigned short charbmpRow = myCharacter.bitmap[charbmpY]; | |
for (int charbmpX = 0; charbmpX < myFont.bitsPerRow; charbmpX++) | |
{ | |
// cout << "charbmpX " << charbmpX << endl; | |
if ( charbmpRow % 2 ) | |
{ | |
//coordinates of the current pixel to be placed | |
int pxposX = topRightX - charbmpX; | |
int pxposY = charbmpY + topRightY; | |
if (pxposX >= 0 && pxposX < TERMINAL_WIDTH && | |
pxposY >= 0 && pxposY < bmpHeight) // assert that the pixel being written is in bounds | |
bitmap[pxposY].set(pxposX, 1); | |
} | |
charbmpRow /= 2; | |
} | |
} | |
posX += myCharacter.dwidth; | |
return true; | |
} | |
// prints out the bool matrix | |
void PrintBitmap(bitset<TERMINAL_WIDTH>* bitmap, short bmpHeight) | |
{ | |
for (int i = 0; i < bmpHeight; i += 2) | |
{ | |
for (int j = 0; j < TERMINAL_WIDTH; j++) | |
{ | |
if (bitmap[i][j] == 1) | |
{ | |
if (bitmap[i+1][j] == 1) | |
{ | |
cout << FULL_BLOCK; | |
} | |
else | |
{ | |
cout << UPPER_HALF_BLOCK; | |
} | |
} | |
else | |
{ | |
if (bitmap[i+1][j] == 1) | |
{ | |
cout << LOWER_HALF_BLOCK; | |
} | |
else | |
{ | |
cout << ' '; | |
} | |
} | |
bitmap[i].reset(j); | |
bitmap[i+1].reset(j); | |
if ( bitmap[i].none() && bitmap[i+1].none() ) | |
break; // break from loop if the rest of the characters in the current line are spaces | |
} | |
cout << endl; | |
} | |
} | |
// clear the bitmap for the next line | |
void ClearBitmap(bitset<TERMINAL_WIDTH>* bitmap, short bmpHeight) | |
{ | |
for (int i = 0; i < bmpHeight; i++) | |
{ | |
bitmap[i].reset(); | |
} | |
return; | |
} | |
// main function for printing out a string | |
void RenderString(Font myFont, bitset<TERMINAL_WIDTH>* bitmap, short bmpHeight, char const * stringToPrint, int& posX) | |
{ | |
int i = 0; | |
while (stringToPrint[i] != '\0') | |
{ | |
while ( !(PutChar(myFont, stringToPrint[i], posX, bitmap, bmpHeight)) ) // put the current character | |
{ | |
PrintBitmap(bitmap, bmpHeight); // if there wasn't enough room start a new line | |
posX = 0; | |
} | |
i++; | |
} | |
PrintBitmap(bitmap, bmpHeight); // print out the current line | |
return; | |
} | |
int main(int argc, char* argv[]) // input handling mostly | |
{ | |
ifstream fontFile; | |
// open the font | |
if (argc > 1) | |
{ | |
fontFile.open(argv[1]); | |
} | |
while ( !fontFile.is_open() ) | |
{ | |
cout << "Enter font directory [default: " << DEFAULT_FONT << "]: "; | |
char fontDir[256]; | |
cin.getline(fontDir, 256); | |
if (fontDir[0] == '\0') // default | |
fontFile.open(DEFAULT_FONT); | |
else | |
fontFile.open(fontDir); | |
} | |
Font myFont; | |
myFont.Load(fontFile); | |
fontFile.close(); | |
short bmpHeight = myFont.bbxHeight; | |
if ( bmpHeight % 2 ) | |
bmpHeight++; | |
bitset<TERMINAL_WIDTH> bitmap[bmpHeight]; // bitmap image to contain the current line of text | |
ClearBitmap(bitmap, bmpHeight); | |
int posX = 0; | |
char stringToPrint[512]; | |
if (argc > 2) | |
{ | |
strcpy(stringToPrint, argv[2]); // get string from args after the bdf file | |
for(int i = 3; i < argc; i++) | |
{ | |
strcat(stringToPrint, " "); | |
strcat(stringToPrint, argv[i]); | |
} | |
} | |
else | |
{ | |
if (argc == 1) | |
cout << "Enter string to render: "; | |
if (!GetLine(cin, stringToPrint, 512)) | |
return 0; | |
} | |
RenderString(myFont, bitmap, bmpHeight, stringToPrint, posX); | |
while (argc <= 2) // if the string wasn't declared from command line args, accept input forever | |
{ | |
if (argc == 1) | |
cout << "Enter string to render: "; | |
ClearBitmap(bitmap, bmpHeight); | |
posX = 0; | |
if (!GetLine(cin, stringToPrint, 512)) // can happen when being piped through stdin, for example "$ echo "Hello World" | ./bdf-cli-render ./Tewi.bdf" | |
return 0; | |
RenderString(myFont, bitmap, bmpHeight, stringToPrint, posX); | |
} | |
myFont.DeleteBitmaps(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment