// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "RawFont.hpp" #include "Common.hpp" // for Status, Status::GUI_SUCCESS, Status::GU... #include "Context.hpp" // for Context #include "DrawCommand.hpp" // for DrawRectangle, DrawCommand (ptr only) #include "FontKerning.hpp" // for FontKerning #include "Renderer.hpp" // for Renderer #include "TextConstants.hpp" // for newline #include // for LOG_ERROR #include "utf8/UTF8.hpp" // for UTF8 #include // for memcpy #include // for pair namespace gui { RawFont::~RawFont() { if (fallback_font != nullptr) { fallback_font = nullptr; } } gui::Status RawFont::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 assigned by the font manager id = 1; // load glyphs uint32_t glyphOffset = glyph_data_offset; for (unsigned int i = 0; i < glyph_count; i++) { auto glyph = std::make_unique(); glyph->load(data, glyphOffset); glyph->loadImage(data, glyph->glyph_offset); glyphs.insert(std::pair>(glyph->id, std::move(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++) { auto kern = std::make_unique(); kern->load(data, kernOffset); // find map using first element of kerning as a key auto it = kerning.find(kern->first); // element wasn't found if (it == kerning.end()) { std::map> kernMap; auto kern_first = kern->first; kernMap.emplace(std::make_pair(kern->second, std::move(kern))); // insert element to the first map kerning.emplace(std::make_pair(kern_first, std::move(kernMap))); } else { auto &kernMap = it->second; kernMap.emplace(std::make_pair(kern->second, std::move(kern))); } } createGlyphUnsupported(); return gui::Status::GUI_SUCCESS; } int32_t RawFont::getKerning(uint32_t id1, uint32_t id2) const { if (id2 == none_char_id) { return 0; } // 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; auto &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; } const std::unique_ptr &kern = it2->second; return kern->amount; } uint32_t RawFont::getCharCountInSpace(const UTF8 &str, const uint32_t space) const { uint32_t availableSpace = space; uint32_t count = 0; uint32_t current = 0; uint32_t previous = none_char_id; for (uint32_t i = 0; i < str.length(); ++i, ++count) { current = str[i]; auto char_pixel_width = getCharPixelWidth(current, previous); if (availableSpace < char_pixel_width) { return count; } availableSpace -= char_pixel_width; previous = current; } return count; } FontGlyph *RawFont::getGlyph(uint32_t glyph_id) const { auto glyph = this->findGlyph(glyph_id); if (glyph != nullptr) { return glyph; } glyph = findGlyphFallback(glyph_id); if (glyph != nullptr) { return glyph; } return unsupported.get(); } FontGlyph *RawFont::findGlyph(uint32_t glyph_id) const { auto glyph_found = glyphs.find(glyph_id); if (glyph_found != glyphs.end()) { return glyph_found->second.get(); } return nullptr; } FontGlyph *RawFont::findGlyphFallback(uint32_t glyph_id) const { if (fallback_font == nullptr) { return nullptr; } return fallback_font->findGlyph(glyph_id); } uint32_t RawFont::getPixelWidth(const UTF8 &str, const uint32_t start, const uint32_t count) const { 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 width = 0; uint32_t idCurrent = 0; uint32_t idLast = none_char_id; for (uint32_t i = 0; i < count; ++i) { idCurrent = str[start + i]; width += getCharPixelWidth(idCurrent, idLast); idLast = idCurrent; } return width; } uint32_t RawFont::getPixelWidth(const UTF8 &str) const { return getPixelWidth(str, 0, str.length()); } uint32_t RawFont::getCharPixelWidth(uint32_t charCode, uint32_t previousChar) const { if (charCode == text::newline) { // newline doesn't have width return 0; } auto glyph = getGlyph(charCode); return glyph->xadvance + getKerning(charCode, previousChar); } uint32_t RawFont::getCharPixelHeight(uint32_t charCode) { FontGlyph *glyph = getGlyph(charCode); return glyph->height; } void RawFont::createGlyphUnsupported() { 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.get(); 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) DrawRectangle commandRect; commandRect.origin = {0, 0}; commandRect.width = unsupported->width; commandRect.height = unsupported->height; commandRect.areaX = 0; commandRect.areaY = 0; commandRect.areaW = unsupported->width; commandRect.areaH = unsupported->height; commandRect.penWidth = unsupported->xoffset; Context renderCtx(unsupported->width, unsupported->height); Renderer().render(renderCtx, commandRect); auto size = unsupported->width * unsupported->height; unsupported->data = new uint8_t[size]; std::memcpy(unsupported->data, renderCtx.getData(), size); } void RawFont::setFallbackFont(RawFont *fallback) { if (fallback != this) { fallback_font = fallback; } } } /* namespace gui */