//! FIGlet font loader (c) 2009 Gaz Davidson #include "CGUIFIGletFont.h" namespace irr { namespace gui { const c8* FIGSignatureString = "flf2a"; u32 FIGMandatoryCharCount = 102; //! Flags for layout. Currently only kerning is used enum E_FIGFONT_LAYOUT_RULE { //! Smush rule 1: Characters which are equal are joined together EFFLR_H_EQUAL_CHARACTER = 1, //! Smush rule 2: The underscore character is merged with others EFFLR_H_UNDERSCORE = 2, //! Smush rule 3: EFFLR_H_HIERARCHY = 4, //! Smush rule 4: EFFLR_H_OPPOSITE_PAIR = 8, //! Smush rule 5: EFFLR_H_BIG_X = 16, //! Smush rule 6: HardBlanks are merged together EFFLR_H_HARDBLANK = 32, EFFLR_DEFAULT_H_KERNING = 64, EFFLR_DEFAULT_H_SMUSHING = 128, EFFLR_V_EQUAL_CHARACTER = 256, EFFLR_V_UNDERSCORE = 512, EFFLR_V_HIERARCHY = 1024, EFFLR_V_HORIZONTAL_LINE = 2048, EFFLR_V_VERTICAL_LINE_SUPER = 4096 }; //! Old rule flags, in case new rule isn't present. enum E_FIGFONT_OLD_LAYOUT_RULE { EFFOLR_FULL_WIDTH = -1, EFFOLR_H_FIT = 0, EFFOLR_H_RULE_1 = 1, EFFOLR_H_RULE_2 = 2, EFFOLR_H_RULE_3 = 4, EFFOLR_H_RULE_4 = 8, EFFOLR_H_RULE_5 = 16, EFFOLR_H_RULE_6 = 32 }; //! Constructor CGUIFIGletFont::CGUIFIGletFont(IGUIFont *realFont) : RealFont(realFont), HardBlank('$'), Height(0), MissingChar(0), MaxLength(0) { if (RealFont) RealFont->grab(); } //! Destructor CGUIFIGletFont::~CGUIFIGletFont() { // drop the real font if (RealFont) RealFont->drop(); // delete the output buffer for (u32 i=0; i < OutputBuffer.size(); ++i) delete OutputBuffer[i]; } //! Loads the font from the given file bool CGUIFIGletFont::load(io::IReadFile* input) { if (!input) return false; // read entire file into a buffer core::array FileData; FileData.set_used(input->getSize() + 1); input->seek(0); input->read((void*)(FileData.pointer()), input->getSize()); FileData[input->getSize()] = 0; // compare signature if (strstr(FileData.pointer(), FIGSignatureString) != FileData.pointer()) { // todo: write reason for failure return false; } // get HardBlank character HardBlank = FileData[strlen(FIGSignatureString)]; // read other values c8* nextLine = FileData.pointer() + strlen(FIGSignatureString) + 2; c8* nextWord = nextLine; markLine(nextLine); Height = getInt(nextWord); u32 BaseLine = getInt(nextWord); MaxLength = getInt(nextWord); s32 OldLayout = getInt(nextWord); u32 CommentLines = getInt(nextWord), PrintDirection = getInt(nextWord), FullLayout = getInt(nextWord), CodeTagCount = getInt(nextWord); // skip comment lines for (u32 i=0; i < CommentLines; ++i) markLine(nextLine); // There are 102 required characters, the rest are special characters u32 CharCount = CodeTagCount + FIGMandatoryCharCount; // allocate font data FontData.set_used(CharCount * MaxLength * Height); // set it all to soft spaces memset(&FontData[0], 32, CharCount * MaxLength * Height); // create the output buffer (one line for each row) if (OutputBuffer.size() < Height) for (u32 i=OutputBuffer.size(); i < Height; ++i) OutputBuffer.push_back( new core::stringw() ); u32 startPos = 0; // now we read each character in turn... for (u32 i=0; i < CharCount; ++i) { s32 MapNo = i+32; // get character mapping if (i >= FIGMandatoryCharCount) { // read code tag from file data nextWord = nextLine; markLine(nextLine); // read hex code? if (nextWord[0] && (nextWord[1] == 'x' || nextWord[1] == 'X') && nextWord[2]) { MapNo = 0; nextWord += 2; while (*nextWord) { MapNo *= 16; if (*nextWord >= '0' && *nextWord <= '9') MapNo += *nextWord - '0'; else if (*nextWord >= 'a' && *nextWord <= 'f') MapNo += (*nextWord - 'a') + 10; else if (*nextWord >= 'B' && *nextWord <= 'F') MapNo += (*nextWord - 'A') + 10; else break; nextWord++; } } else { // just a decimal integer MapNo = getInt(nextWord); } // if number 0 then this is the special "missing" character if (MapNo == 0) MissingChar = Characters.size(); } // read the character data lines u32 length; for (u32 j=0; j < Height; ++j) { // cut the line nextWord = nextLine; markLine(nextLine); // on the first line, cut off the end character to get the length if (j==0) length = strlen(nextWord); // now copy this to the character buffer if (length) memcpy(&FontData[ startPos + length*j ], nextWord, length-1); } // save the character SFIGChar ch; ch.Start = startPos; ch.Width = length; CharMap.insert(wchar_t(MapNo), Characters.size()); Characters.push_back(ch); // increase startPos startPos += length*Height; } return true; } void CGUIFIGletFont::draw(const wchar_t* text, const core::rect& position, video::SColor color, bool hcenter, bool vcenter, const core::rect* clip) { generateText(text); core::rect pos = position; core::dimension2du dim = RealFont->getDimension(L"A"); if (vcenter) { u32 hHeight = (Height * dim.Height) / 2; s32 cy = pos.getCenter().Y; pos.UpperLeftCorner.Y = cy - hHeight; pos.LowerRightCorner.Y = cy + hHeight; } for (u32 i=0; i < Height; ++i) { RealFont->draw( OutputBuffer[i]->c_str(), pos, color, hcenter, false, clip ); pos.UpperLeftCorner.Y += dim.Height; } } core::dimension2d CGUIFIGletFont::getDimension(const wchar_t* text) const { generateText(text); core::dimension2d dim = RealFont->getDimension( OutputBuffer[0]->c_str() ); dim.Height *= Height; return dim; } s32 CGUIFIGletFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const { if (!thisLetter || !previousLetter) return 0; return RealFont->getDimension(L"A").Width * getFIGKerning(thisLetter, previousLetter); } //! Calculates the index of the character in the text which is on a specific position. s32 CGUIFIGletFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const { u32 subChrWidth = RealFont->getDimension(L"A").Width; s32 x = 0; s32 index = 0; const wchar_t *lastChar = 0; while(*text) { x += (getFIGKerning(text, lastChar) + Characters[getAreaFromCharacter(*text)].Width) * subChrWidth; if (x > pixel_x) return index; lastChar = text; ++text; ++index; } return -1; } s32 CGUIFIGletFont::getAreaFromCharacter(wchar_t c) const { core::map::Node* n = CharMap.find(c); if (n) return n->getValue(); else return MissingChar; } // Returns the offset in subcharacters from the end of char a to the start of b // This is basically kerning but for sub-characters instead of pixels s32 CGUIFIGletFont::getFIGKerning(const wchar_t *thisLetter, const wchar_t *previousLetter) const { const SFIGChar *previousChar=0, *thisChar=0; if (previousLetter) previousChar = &Characters[getAreaFromCharacter(*previousLetter)]; if (thisLetter) thisChar = &Characters[getAreaFromCharacter(*thisLetter)]; s32 maxKerning = previousChar ? previousChar->Width : 0; maxKerning = -maxKerning; // loop through each row for (u32 i=0; iStart + (i * previousChar->Width); pos = end + previousChar->Width - 1; while (pos > end && FontData[pos] == ' ') --pos; kerning = -previousChar->Width + (pos-end) + 1; } // now this char from start to end if (thisChar) { pos = thisChar->Start + (i * thisChar->Width); end = pos + thisChar->Width; while (pos < end && FontData[pos] == ' ') pos++; kerning -= pos - thisChar->Start - (i * thisChar->Width); } if (kerning > maxKerning) maxKerning = kerning; } return maxKerning; } void CGUIFIGletFont::generateText(const wchar_t* text) const { if (OutputBufferText == text) return; // the text needs to be regenerated. // this really means checking the smushing rules, but this isn't implemented yet // so we'll just use standard kerning for now OutputBufferText = text; for (u32 i=0; i < Height; ++i) *OutputBuffer[i] = L""; // resize the output buffers to a maximum length core::stringw blanks; s32 stringLength = wcslen(text) * MaxLength + 1; blanks.reserve(stringLength); for (s32 j=0; j < stringLength-1; ++j) blanks.append(L' '); for (u32 j=0; j < Height; ++j) *OutputBuffer[j] = blanks; const wchar_t *lastChar = 0; s32 currentLength = 0; while (*text) { // get kerning s32 kern = getFIGKerning(text, lastChar); // find the letter in the map s32 i = getAreaFromCharacter(*text); // add the letter u32 p = Characters[i].Start; u32 w = Characters[i].Width; u32 b = 0; s32 currentPos = (currentLength + kern < 0) ? 0 : currentLength + kern; s32 offset = (currentLength + kern < 0) ? currentLength + kern : 0; for (u32 j=0; j < Height; ++j) { u32 start = Characters[i].Start + w*j - offset; u32 p = start; u32 e = Characters[i].Start + w*j + w; while (p < e) { c8 ch = (FontData[p] == HardBlank) ? ' ' : FontData[p]; if (ch != L' ') (*OutputBuffer[j]) [currentPos + (p-start)] = ch; ++p; } } currentLength += kern + w; lastChar = text; // move to the next letter text++; } // null terminate output buffers for (u32 j=0; j < Height; ++j) { (*OutputBuffer[j])[currentLength] = '\0'; OutputBuffer[j]->validate(); } } void CGUIFIGletFont::markLine(c8* &startPos) { // find the next end of line character while (*startPos && *startPos != '\x0D' && *startPos != '\x0A') startPos++; // we have to cater for crlf line endings if ((*startPos) == '\x0D' && (*(startPos+1)) == '\x0A') { // in this case we wipe it *startPos = '\0'; startPos++; } // if we're not at the end of the file then we null terminate if (*startPos) { *startPos = '\0'; startPos++; } } s32 CGUIFIGletFont::getInt(c8* &startPos) { c8* original = startPos; // move cursor to next gap, do not pass EOL or EOF while (*startPos && (*startPos == '-' || (*startPos >= '0' && *startPos <= '9') ) ) startPos++; if (*startPos && *startPos != '\x0A' && *startPos != '\x0D') startPos++; return atoi(original); } } // namespace gui } // namespace irr