~aleteoryx/muditaos

25a8f781ee7f852ea75d2c6a4fc382f62881ecdd — Lefucjusz 3 years ago 9a281df
[MOS-199] Change unknown char glyph to match with design

Fix of the issue that glyph displayed as
a substitute for unsupported character
was different than the design suggested.

Additionally minor code cleanup.
M module-gui/gui/core/DrawCommand.hpp => module-gui/gui/core/DrawCommand.hpp +1 -3
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 51,7 51,6 @@ namespace gui
        Color color{ColorFullBlack};
        uint8_t penWidth{1};

      public:
        void draw(Context *ctx) const override;
    };



@@ 82,7 81,6 @@ namespace gui
        Color fillColor{ColorFullBlack};
        Color borderColor{ColorFullBlack};

      public:
        void draw(Context *ctx) const override;
    };


M module-gui/gui/core/RawFont.cpp => module-gui/gui/core/RawFont.cpp +117 -96
@@ 1,78 1,75 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, 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 <log/log.hpp>       // for LOG_ERROR
#include "utf8/UTF8.hpp"     // for UTF8
#include <cstring>           // for memcpy
#include <utility>           // for pair
#include "Common.hpp"
#include "Context.hpp"
#include "renderers/LineRenderer.hpp"
#include "renderers/RectangleRenderer.hpp"
#include "TextConstants.hpp"
#include <log/log.hpp>
#include "utf8/UTF8.hpp"
#include <cstring>
#include <utility>

namespace gui
{

    RawFont::~RawFont()
    {
        if (fallback_font != nullptr) {
            fallback_font = nullptr;
        }
        fallback_font = nullptr;
    }

    gui::Status RawFont::load(uint8_t *data)
    {
        uint32_t offset = 0;
        std::uint32_t offset = 0;

        if (info.load(data, offset) != gui::Status::GUI_SUCCESS)
        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);
        memcpy(&glyph_count, data + offset, sizeof(std::uint32_t));
        offset += sizeof(std::uint32_t);
        // offset to the beginning of the glyph data
        memcpy(&glyph_data_offset, data + offset, sizeof(uint32_t));
        offset += sizeof(uint32_t);
        memcpy(&glyph_data_offset, data + offset, sizeof(std::uint32_t));
        offset += sizeof(std::uint32_t);
        // number of kerning pairs
        memcpy(&kern_count, data + offset, sizeof(uint32_t));
        offset += sizeof(uint32_t);
        memcpy(&kern_count, data + offset, sizeof(std::uint32_t));
        offset += sizeof(std::uint32_t);
        // array of glyphs structures
        memcpy(&kern_data_offset, data + offset, sizeof(uint32_t));
        offset += sizeof(uint32_t);
        memcpy(&kern_data_offset, data + offset, sizeof(std::uint32_t));
        offset += sizeof(std::uint32_t);
        // offset to the beginning of the image data
        memcpy(&image_data_offset, data + offset, sizeof(uint32_t));
        offset += sizeof(uint32_t);
        memcpy(&image_data_offset, data + offset, sizeof(std::uint32_t));
        offset += sizeof(std::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++) {
        std::uint32_t glyphOffset = glyph_data_offset;
        for (std::uint32_t i = 0; i < glyph_count; i++) {
            auto glyph = std::make_unique<FontGlyph>();
            glyph->load(data, glyphOffset);
            glyph->loadImage(data, glyph->glyph_offset);
            glyphs.insert(std::pair<uint32_t, std::unique_ptr<FontGlyph>>(glyph->id, std::move(glyph)));
            glyphs.insert(std::pair<std::uint32_t, std::unique_ptr<FontGlyph>>(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
        // first and second 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++) {
        std::uint32_t kernOffset = kern_data_offset;
        for (std::uint32_t i = 0; i < kern_count; i++) {
            auto kern = std::make_unique<FontKerning>();
            kern->load(data, kernOffset);

            // find map using first element of kerning as a key
            auto it = kerning.find(kern->first);
            const auto it = kerning.find(kern->first);

            // element wasn't found
            if (it == kerning.end()) {
                std::map<uint32_t, std::unique_ptr<FontKerning>> kernMap;
                auto kern_first = kern->first;
                std::map<std::uint32_t, std::unique_ptr<FontKerning>> kernMap;
                const auto kern_first = kern->first;
                kernMap.emplace(std::make_pair(kern->second, std::move(kern)));

                // insert element to the first map


@@ 89,40 86,41 @@ namespace gui
        return gui::Status::GUI_SUCCESS;
    }

    int32_t RawFont::getKerning(uint32_t id1, uint32_t id2) const
    int32_t RawFont::getKerning(std::uint32_t id1, std::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);
        const auto it1 = kerning.find(id1);

        // if there is no map with kerning for id1 return 0;
        if (it1 == kerning.end())
        if (it1 == kerning.end()) {
            return 0;
        }

        auto &kernMap = it1->second;
        const 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);
        const auto it2 = kernMap.find(id2);
        if (it2 == kernMap.end()) {
            return 0;
        }

        const std::unique_ptr<FontKerning> &kern = it2->second;
        const auto &kern = it2->second;
        return kern->amount;
    }

    uint32_t RawFont::getCharCountInSpace(const UTF8 &str, const uint32_t space) const
    std::uint32_t RawFont::getCharCountInSpace(const UTF8 &str, const std::uint32_t space) const
    {
        uint32_t availableSpace = space;
        uint32_t count          = 0;
        uint32_t current        = 0;
        uint32_t previous       = none_char_id;
        std::uint32_t availableSpace = space;
        std::uint32_t count          = 0;
        std::uint32_t current        = 0;
        std::uint32_t previous       = none_char_id;

        for (uint32_t i = 0; i < str.length(); ++i, ++count) {
        for (std::uint32_t i = 0; i < str.length(); ++i, ++count) {
            current               = str[i];
            auto char_pixel_width = getCharPixelWidth(current, previous);
            const auto char_pixel_width = getCharPixelWidth(current, previous);
            if (availableSpace < char_pixel_width) {
                return count;
            }


@@ 132,7 130,7 @@ namespace gui
        return count;
    }

    FontGlyph *RawFont::getGlyph(uint32_t glyph_id) const
    FontGlyph *RawFont::getGlyph(std::uint32_t glyph_id) const
    {
        auto glyph = this->findGlyph(glyph_id);
        if (glyph != nullptr) {


@@ 147,16 145,16 @@ namespace gui
        return unsupported.get();
    }

    FontGlyph *RawFont::findGlyph(uint32_t glyph_id) const
    FontGlyph *RawFont::findGlyph(std::uint32_t glyph_id) const
    {
        auto glyph_found = glyphs.find(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
    FontGlyph *RawFont::findGlyphFallback(std::uint32_t glyph_id) const
    {
        if (fallback_font == nullptr) {
            return nullptr;


@@ 164,7 162,7 @@ namespace gui
        return fallback_font->findGlyph(glyph_id);
    }

    uint32_t RawFont::getPixelWidth(const UTF8 &str, const uint32_t start, const uint32_t count) const
    std::uint32_t RawFont::getPixelWidth(const UTF8 &str, const std::uint32_t start, const std::uint32_t count) const
    {
        if (count == 0) {
            return 0;


@@ 180,11 178,11 @@ namespace gui
        }

        // width of text in pixels
        uint32_t width     = 0;
        uint32_t idCurrent = 0;
        uint32_t idLast    = none_char_id;
        std::uint32_t width     = 0;
        std::uint32_t idCurrent = 0;
        std::uint32_t idLast    = none_char_id;

        for (uint32_t i = 0; i < count; ++i) {
        for (std::uint32_t i = 0; i < count; ++i) {
            idCurrent = str[start + i];
            width += getCharPixelWidth(idCurrent, idLast);
            idLast = idCurrent;


@@ 193,70 191,93 @@ namespace gui
        return width;
    }

    uint32_t RawFont::getPixelWidth(const UTF8 &str) const
    std::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
    std::uint32_t RawFont::getCharPixelWidth(std::uint32_t charCode, std::uint32_t previousChar) const
    {
        if (charCode == text::newline) { // newline doesn't have width
            return 0;
        }
        auto glyph = getGlyph(charCode);
        const auto glyph = getGlyph(charCode);
        return glyph->xadvance + getKerning(charCode, previousChar);
    }

    uint32_t RawFont::getCharPixelHeight(uint32_t charCode)
    std::uint32_t RawFont::getCharPixelHeight(std::uint32_t charCode)
    {
        FontGlyph *glyph = getGlyph(charCode);
        const auto glyph = getGlyph(charCode);
        return glyph->height;
    }

    void RawFont::createGlyphUnsupported()
    {
        constexpr auto ptToPx                              = 0.75f;
        constexpr auto diagonalSpacingToRectangleEdgeRatio = 0.6f; // To the center of the line
        constexpr auto fallbackLineWidth                   = 1;
        constexpr auto modelChar = 'h'; // Just get any char, it is needed only to obtain line width

        unsupported                                    = std::make_unique<FontGlyph>();
        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->height                            = info.size * ptToPx;
        unsupported->width                             = unsupported->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;
        unsupported->yoffset                           = unsupported->height;

        const auto modelGlyph = glyphs.find(modelChar);
        if (modelGlyph != glyphs.end()) {
            const auto modelGlyphData = modelGlyph->second.get();
            unsupported->xoffset      = (modelGlyphData->xadvance - modelGlyphData->width) / 2;
        }

        if (unsupported->xoffset == 0) {
            unsupported->xoffset = 1; // fallback margin.
            unsupported->xoffset = fallbackLineWidth;
        }
        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;

        const auto diagonalHorizontalDistance =
            static_cast<Position>((unsupported->width / 2) * diagonalSpacingToRectangleEdgeRatio);
        const auto diagonalVerticalDistance =
            static_cast<Position>((unsupported->height / 2) * diagonalSpacingToRectangleEdgeRatio);

        /* Correction required to place the diagonals in the center of the rectangle
         * regardless of line width. Drawing line with width > 1 is done
         * by drawing multiple 1px lines in certain direction, so correction has to
         * be applied to compensate that. */
        const auto horizontalOffset = static_cast<Position>(std::ceil(unsupported->xoffset / 2.0));

        const Point rectangleOrigin      = {0, 0};
        const Point firstDiagonalOrigin  = {diagonalHorizontalDistance - horizontalOffset, diagonalVerticalDistance};
        const Point secondDiagonalOrigin = {unsupported->width - diagonalHorizontalDistance - horizontalOffset,
                                            diagonalVerticalDistance};

        /* The "length" in draw45deg() is not an actual length, it's length of the projection
         * of the line on one of the axes (no matter which one, as it is 45 degrees angle), i.e.
         * real length divided by square root of 2.
         * Both diagonals have the same length, so compute only one of them, based on height.
         * Computation based on width will yield the same result - we're in square. */
        const auto diagonalLength = unsupported->height - (2 * diagonalVerticalDistance) + 1;

        const gui::renderer::RectangleRenderer::DrawableStyle rectangleStyle = {
            .borderWidth = static_cast<Length>(unsupported->xoffset)};
        const gui::renderer::LineRenderer::DrawableStyle diagonalStyle = {
            .penWidth  = static_cast<Length>(unsupported->xoffset),
            .direction = renderer::LineExpansionDirection::Right};

        /* Render items */
        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);
        gui::renderer::RectangleRenderer::drawFlat(
            &renderCtx, rectangleOrigin, unsupported->width, unsupported->height, rectangleStyle);
        gui::renderer::LineRenderer::draw45deg(
            &renderCtx, firstDiagonalOrigin, diagonalLength, diagonalStyle, true); // Draw to the right
        gui::renderer::LineRenderer::draw45deg(
            &renderCtx, secondDiagonalOrigin, diagonalLength, diagonalStyle, false); // Draw to the left

        const auto glyphDataSize = unsupported->width * unsupported->height;
        unsupported->data        = new uint8_t[glyphDataSize];
        std::memcpy(unsupported->data, renderCtx.getData(), glyphDataSize);
    }

    void RawFont::setFallbackFont(RawFont *fallback)

M module-gui/gui/core/RawFont.hpp => module-gui/gui/core/RawFont.hpp +23 -23
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 11,8 11,8 @@
#include <map>
#include <memory>
#include <string>
#include "FontGlyph.hpp"   // for FontGlyph
#include "FontKerning.hpp" // for FontKerning
#include "FontGlyph.hpp"
#include "FontKerning.hpp"

namespace gui
{


@@ 27,25 27,25 @@ namespace gui

        gui::Status load(uint8_t *data);

        static const uint32_t none_char_id = std::numeric_limits<const uint32_t>().max();
        static const std::uint32_t none_char_id = std::numeric_limits<const std::uint32_t>().max();
        // structure holding detailed information about font
        FontInfo info;
        // number of glyphs in the fontno
        uint32_t glyph_count;
        // number of glyphs in the font
        std::uint32_t glyph_count;
        // offset to the beginning of the glyph data
        uint32_t glyph_data_offset;
        std::uint32_t glyph_data_offset;
        // number of kerning pairs
        uint32_t kern_count;
        std::uint32_t kern_count;
        // array of glyphs structures
        uint32_t kern_data_offset;
        std::uint32_t kern_data_offset;
        // offset to the beginning of the image data
        uint32_t image_data_offset;
        // id of the font asigned by the font manager
        uint32_t id;
        std::uint32_t image_data_offset;
        // id of the font assigned by the font manager
        std::uint32_t id;

        /// return glyph for selected code
        /// if code is not found in font, it is searched in the fallback font, if not found - unsupportedGlyph returned
        FontGlyph *getGlyph(uint32_t id) const;
        FontGlyph *getGlyph(std::uint32_t id) const;

        /**
         * @brief Returns kerning value for pair of the two characters.


@@ 53,14 53,14 @@ namespace gui
         * @param id2 Code of the second character - if none_char_id then return 0
         * @return Value of the kerning or 0 if pair was not found.
         */
        int32_t getKerning(uint32_t id1, uint32_t id2) const;
        int32_t getKerning(std::uint32_t id1, std::uint32_t id2) const;
        /**
         * @brief Method calculates how many chars will fit specified width using current font.
         * @param str UTF8 string that will be used to calculate how many chars can fit provided space.
         * @param space Number of pixels in width availabale to calculate how many chars will fit.
         * @return number of chars that can fit provided space;
         */
        uint32_t getCharCountInSpace(const UTF8 &str, const uint32_t space) const;
        std::uint32_t getCharCountInSpace(const UTF8 &str, const std::uint32_t space) const;
        /**
         * @brief Calculates how many pixels will occupy selected part of the string.
         * @param str String used as a source of text.


@@ 68,23 68,23 @@ namespace gui
         * @param count Number of characters that should be used during calculating pixels width.
         * @return Number of pixels in width occupied by selected part of the text.
         */
        uint32_t getPixelWidth(const UTF8 &str, const uint32_t start, const uint32_t count) const;
        std::uint32_t getPixelWidth(const UTF8 &str, const std::uint32_t start, const std::uint32_t count) const;
        /**
         * @brief Calculates how many pixels will occupy string.
         * @param str String used as a source of text.
         * @return Number of pixels in width occupied by string.
         */
        uint32_t getPixelWidth(const UTF8 &str) const;
        std::uint32_t getPixelWidth(const UTF8 &str) const;
        /**
         * @brief returns number of pixels occupied by character horizontally.
         *
         * if previous char is set - then tries to append kerning
         */
        uint32_t getCharPixelWidth(uint32_t charCode, uint32_t previousChar = none_char_id) const;
        std::uint32_t getCharPixelWidth(std::uint32_t charCode, std::uint32_t previousChar = none_char_id) const;
        /**
         * @brief Returns number of pixels occupied by the character vertically.
         */
        uint32_t getCharPixelHeight(uint32_t charCode);
        std::uint32_t getCharPixelHeight(std::uint32_t charCode);
        const std::string getName()
        {
            return info.face;


@@ 92,8 92,8 @@ namespace gui
        void setFallbackFont(RawFont *font);

      private:
        std::map<uint32_t, std::unique_ptr<FontGlyph>> glyphs;
        std::map<uint32_t, std::map<uint32_t, std::unique_ptr<FontKerning>>> kerning;
        std::map<std::uint32_t, std::unique_ptr<FontGlyph>> glyphs;
        std::map<std::uint32_t, std::map<std::uint32_t, std::unique_ptr<FontKerning>>> kerning;
        /// if the fallback font is set it is used in case of a glyph being unsupported in the primary font
        RawFont *fallback_font = nullptr;
        /// the glyph used when requested glyph is unsupported in the font (and the fallback font if one is set)


@@ 103,9 103,9 @@ namespace gui

        /// return glyph for selected code
        /// if code is not found - nullptr is returned
        FontGlyph *findGlyph(uint32_t id) const;
        FontGlyph *findGlyph(std::uint32_t id) const;
        /// return glyph for selected code
        /// if code is not found - nullptr is returned
        FontGlyph *findGlyphFallback(uint32_t id) const;
        FontGlyph *findGlyphFallback(std::uint32_t id) const;
    };
} /* namespace gui */

M pure_changelog.md => pure_changelog.md +1 -0
@@ 16,6 16,7 @@
* Shortened duration of phone modes pop-up from 3s to 1s
* Changed responses in contacts and messages endpoints used to communicate with Center
* Added SMS template call rejection window for case when no templates are defined
* Changed unknown character glyph to the one matching the design

### Fixed