/* * Font.cpp * * Created on: 25 kwi 2019 * Author: robert */ #include #include #include #include "Font.hpp" #include "utf8/UTF8.hpp" #include "log/log.hpp" // for loading files #include "Context.hpp" #include "DrawCommand.hpp" #include "Renderer.hpp" #include "vfs.hpp" #include namespace gui { FontGlyph::~FontGlyph() { if (data != nullptr) delete[] data; } gui::Status FontGlyph::load(uint8_t *data, uint32_t &offset) { // character id memcpy(&id, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // offset in glyph data field memcpy(&glyph_offset, data + offset, sizeof(uint32_t)); offset += sizeof(uint32_t); // width of the character image in the texture memcpy(&width, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // height of the character image in the texture memcpy(&height, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // how much the current position should be offset when copying the image from the texture to the screen memcpy(&xoffset, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // how much the current position should be offset when copying the image from the texture to the screen memcpy(&yoffset, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // how much the current position should be advanced after drawing the character memcpy(&xadvance, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); return gui::Status::GUI_SUCCESS; } gui::Status FontGlyph::loadImage(uint8_t *data, uint32_t offset) { // image data of the glyph this->data = new uint8_t[width * height]; memcpy(this->data, data + offset, width * height); // TODO convert image to the vector of vectors where every last vector contains pairs of // TODO offset - from column 0, length - number of pixels that should be drawn. // TODO First vector contains row and pointer to the vector with pairs return gui::Status::GUI_SUCCESS; } gui::Status FontKerning::load(uint8_t *data, uint32_t &offset) { // utf16 id of the first character memcpy(&first, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // utf16 id of the following character memcpy(&second, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // distance in pixels between beginning of first character and beginning of second character memcpy(&amount, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); return gui::Status::GUI_SUCCESS; } gui::Status FontInfo::load(uint8_t *data, uint32_t &offset) { // read up to 63 chars of the fonts name char name[64] = {0}; memcpy(name, data + offset, 63); offset += 64; face = std::string(name); // size of the true type font memcpy(&size, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // flag that informs if font is bold memcpy(&bold, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // flag that informs if font is italic memcpy(&italic, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // flag that informs if smoothing was turned on. 1 - smoothing was turned on. memcpy(&smooth, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // TODO additional space between characters???? memcpy(&char_spacing, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // TODO additional space between lines memcpy(&line_spacing, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // distance in pixels between each line of text memcpy(&line_height, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // number of pixels from the absolute top of the line to the base of the characters memcpy(&base, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // width of the texture, normally used to scale the x pos of the character image memcpy(&scale_w, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // height of the texture, normally used to scale the y pos of the character image memcpy(&scale_h, data + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); return gui::Status::GUI_SUCCESS; } Font::~Font() { for (std::map::iterator it = glyphs.begin(); it != glyphs.end(); ++it) delete it->second; glyphs.clear(); for (std::map *>::iterator fit = kerning.begin(); fit != kerning.end(); ++fit) { for (std::map::iterator sit = fit->second->begin(); sit != fit->second->end(); ++sit) { delete sit->second; } delete fit->second; } kerning.clear(); } gui::Status Font::load(uint8_t *data) { uint32_t offset = 0; if (info.load(data, offset) != gui::Status::GUI_SUCCESS) return gui::Status::GUI_FAILURE; // number of glyphs in the font memcpy(&glyph_count, data + offset, sizeof(uint32_t)); offset += sizeof(uint32_t); // offset to the beginning of the glyph data memcpy(&glyph_data_offset, data + offset, sizeof(uint32_t)); offset += sizeof(uint32_t); // number of kerning pairs memcpy(&kern_count, data + offset, sizeof(uint32_t)); offset += sizeof(uint32_t); // array of glyphs structures memcpy(&kern_data_offset, data + offset, sizeof(uint32_t)); offset += sizeof(uint32_t); // offset to the beginning of the image data memcpy(&image_data_offset, data + offset, sizeof(uint32_t)); offset += sizeof(uint32_t); // id of the font asigned by the font manager id = 1; // LOG_INFO("font: %s glyphs: %d, kern: %d", info.face.c_str(), glyph_count, kern_count ); // LOG_INFO("glyph offset: %d", glyph_data_offset ); // LOG_INFO("kerning offset: %d", kern_data_offset ); // LOG_INFO("image offset: %d", image_data_offset ); // load glyphs uint32_t glyphOffset = glyph_data_offset; for (unsigned int i = 0; i < glyph_count; i++) { FontGlyph *glyph = new FontGlyph(); glyph->load(data, glyphOffset); glyph->loadImage(data, glyph->glyph_offset); glyphs.insert(std::pair(glyph->id, glyph)); } // load kerning // first map contains index of the character and the map that holds values for kerning between // first and second character character. In second map key is the value of the second character // and value is the kerning between first and second element. uint32_t kernOffset = kern_data_offset; for (unsigned int i = 0; i < kern_count; i++) { FontKerning *kern = new FontKerning(); kern->load(data, kernOffset); // find map using first element of kerning as a key auto it = kerning.find(kern->first); // element wasn't found std::map *kernMap; if (it == kerning.end()) { kernMap = new std::map(); // insert element to the first map kerning.insert(std::pair *>(kern->first, kernMap)); } else { kernMap = it->second; } kernMap->insert(std::pair(kern->second, kern)); } return gui::Status::GUI_SUCCESS; } int32_t Font::getKerning(uint32_t id1, uint32_t id2) { // search for a map with kerning for given character (id1) auto it1 = kerning.find(id1); // if there is no map with kerning for id1 return 0; if (it1 == kerning.end()) return 0; std::map *kernMap = it1->second; // otherwise search for id2 in kernMap and return value or 0 if it's not found auto it2 = kernMap->find(id2); if (it2 == kernMap->end()) return 0; FontKerning *kern = it2->second; return kern->amount; } uint32_t Font::getCharCountInSpace(const UTF8 &str, const uint32_t space, uint32_t &spaceConsumed, const bool fromBeginning, const uint32_t &delimiter) { spaceConsumed = 0; // width of text in pixels int32_t availableSpace = space; uint32_t stringChars = 0; uint16_t idLast = 0, idCurrent = 0; for (uint32_t i = 0; i < str.length(); ++i) { // go in reverse rather than UTF8::reverse. be careful, no NULL at the end. uint32_t iAnyDir = fromBeginning ? i : (str.length() - 1 - i); // i compensated for direction idCurrent = str[iAnyDir]; auto glyph_found = glyphs.find(idCurrent); std::unique_ptr unique_glyph; FontGlyph *glyph = nullptr; if (glyph_found != glyphs.end()) { glyph = glyph_found->second; } else { unique_glyph = getGlyphUnsupported(); glyph = unique_glyph.get(); } int32_t kern_value = 0; if (i > 0) { kern_value = getKerning(idLast, idCurrent); } if (availableSpace - (glyph->xadvance + kern_value) < 0) { spaceConsumed = space - availableSpace; return stringChars; } availableSpace -= (glyph->xadvance + kern_value); idLast = idCurrent; ++stringChars; // check for delimiter if (idCurrent == delimiter) { break; } } spaceConsumed = space - availableSpace; return stringChars; } uint32_t Font::getPixelWidth(const UTF8 &str, const uint32_t start, const uint32_t count) { if (count == 0) return 0; if (str.length() == 0) return 0; if ((start >= str.length()) || (start + count - 1 >= str.length())) { LOG_ERROR("incorrect string index provided"); return 0; } // width of text in pixels uint32_t stringPixelWidth = 0; uint16_t idLast = 0, idCurrent = 0; for (uint32_t i = 0; i < count; ++i) { idCurrent = str[start + i]; auto glyph_found = glyphs.find(idCurrent); if (glyph_found != glyphs.end()) { stringPixelWidth += getCharPixelWidth(idCurrent) + (i == 0 ? 0 : getKerning(idLast, idCurrent)); } else { stringPixelWidth += getGlyphUnsupported()->xadvance; } idLast = idCurrent; } return stringPixelWidth; } uint32_t Font::getPixelWidth(const UTF8 &str) { return getPixelWidth(str, 0, str.length()); } uint32_t Font::getCharPixelWidth(uint32_t charCode) { FontGlyph *glyph = glyphs.find(charCode)->second; if (glyph != NULL) return glyph->xadvance; return 0; } uint32_t Font::getCharPixelHeight(uint32_t charCode) { FontGlyph *glyph = glyphs.find(charCode)->second; if (glyph != NULL) return glyph->height; return 0; } std::unique_ptr Font::getGlyphUnsupported() const { std::unique_ptr unsupported = std::make_unique(); const float pt_to_px = 0.75; unsupported->height = this->info.size * pt_to_px; const float typical_width_as_percent_of_height = 0.62; unsupported->width = unsupported->height * typical_width_as_percent_of_height; unsupported->xoffset = 0; unsupported->yoffset = 0; // generate a rectangle based on an existing letter. otherwise use some magic numbers ↑ to approximate the size // for the rectangle char baseChar = 'h'; // arbitrary choice. h as a representative character to get an idea of glyph size. if not // found, then use magic numbers above auto baseCharFound = this->glyphs.find(baseChar); if (baseCharFound != this->glyphs.end()) { FontGlyph *baseGlyph = baseCharFound->second; unsupported->width = baseGlyph->width; unsupported->height = baseGlyph->height; unsupported->xoffset = (baseGlyph->xadvance - baseGlyph->width) / 2; } if (unsupported->xoffset == 0) { unsupported->xoffset = 1; // fallback margin. } unsupported->yoffset += unsupported->height; unsupported->xadvance = unsupported->width + (2 * unsupported->xoffset); // use xoffset as margins on the left/right of the glyph // populate with a bitmap (glyph) CommandRectangle *commandRect = new CommandRectangle(); commandRect->x = 0; commandRect->y = 0; commandRect->w = unsupported->width; commandRect->h = unsupported->height; commandRect->areaX = 0; commandRect->areaY = 0; commandRect->areaW = unsupported->width; commandRect->areaH = unsupported->height; commandRect->penWidth = unsupported->xoffset; Context *renderCtx = new Context(unsupported->width, unsupported->height); std::vector commands = {commandRect}; Renderer().render(renderCtx, commands); unsupported->data = renderCtx->getData(); return unsupported; } FontManager::FontManager() {} FontManager::~FontManager() { clear(); } void FontManager::clear() { for (Font *font : fonts) { LOG_INFO("deleting font: %s", font->getName().c_str()); delete font; } fonts.clear(); } void FontManager::loadFonts(std::string baseDirectory) { fontFolder = baseDirectory + "/fonts"; std::vector fontFiles = getFontsList(); for (std::string fontName : fontFiles) { LOG_INFO("Loading font: %s", fontName.c_str()); loadFont(fontName); } } Font *FontManager::loadFont(std::string filename) { auto file = vfs.fopen(filename.c_str(), "rb"); auto fileSize = vfs.filelength(file); char *fontData = new char[fileSize]; if (fontData == nullptr) { vfs.fclose(file); LOG_ERROR(" Failed to allocate temporary font buffer"); return nullptr; } // read data to buffer auto bytesRead = vfs.fread(fontData, 1, fileSize, file); // close file vfs.fclose(file); if (bytesRead != fileSize) { LOG_ERROR("Failed to read all file"); delete[] fontData; return nullptr; } // allocate memory for new font Font *font = new Font(); if (font->load(reinterpret_cast(fontData)) != gui::Status::GUI_SUCCESS) { delete font; delete[] fontData; return nullptr; } else { // set id and push it to vector font->id = fonts.size(); fonts.push_back(font); } delete[] fontData; return font; } bool hasEnding(std::string const &fullString, std::string const &ending) { if (fullString.length() >= ending.length()) { return (0 == fullString.compare(fullString.length() - ending.length(), ending.length(), ending)); } else { return false; } } std::vector FontManager::getFontsList() { std::vector fontFiles; LOG_INFO("Scanning fonts folder: %s", fontFolder.c_str()); auto dirList = vfs.listdir(fontFolder.c_str()); for (vfs::DirectoryEntry ent : dirList) { if ((ent.attributes != vfs::FileAttributes::Directory) && hasEnding(ent.fileName, ".mpf")) { fontFiles.push_back(fontFolder + "/" + ent.fileName); // TODO remove commented code LOG_INFO("font: %s", (fontFolder + "/" + ent.fileName).c_str()); } } LOG_INFO("Total number of images: %d", fontFiles.size()); return fontFiles; } bool FontManager::init(std::string baseDirectory) { // load fonts from specified folder loadFonts(baseDirectory); return true; } FontManager &FontManager::getInstance() { static FontManager instance; return instance; } Font *FontManager::getFont(uint32_t id) { if (id >= fonts.size()) { LOG_ERROR("Font not found! id: %d", id); return nullptr; } return fonts[id]; } uint32_t FontManager::getFontID(const std::string &name) { bool found = false; uint32_t i; for (i = 0; i < fonts.size(); i++) { if (name.compare(fonts[i]->info.face) == 0) { found = true; break; } } if (!found) { LOG_ERROR("=> font not found: %s", name.c_str()); LOG_ERROR("Using default!"); return 0; } return i; } } /* namespace gui */