// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2019 Intel Corporation

#include "precomp.hpp"
#include "ft_render.hpp"

#ifdef HAVE_FREETYPE

#include "ft_render_priv.hpp"

#include <opencv2/gapi/util/throw.hpp>
#include <opencv2/gapi/own/assert.hpp>

cv::gapi::wip::draw::FTTextRender::Priv::Priv(const std::string& path)
{
    if (FT_Init_FreeType(&m_library) != 0)
    {
        cv::util::throw_error(std::runtime_error("Failed to initialize FT"));
    }

    if (FT_New_Face(m_library, path.c_str(), 0, &m_face))
    {
        FT_Done_FreeType(m_library);
        cv::util::throw_error(std::runtime_error("Failed to create a font face"));
    }
}

cv::Size cv::gapi::wip::draw::FTTextRender::Priv::getTextSize(const std::wstring& text, int fh, int* baseline)
{
    //
    //
    //
    //   ^                                                           diff between size and advance(2)
    //   |       ______________               width        width    |<->|
    //   |      |      **      |            |<------>| <------------|--->
    //   |      |     *  *     |            |________| |____________|___|________
    //   | left |    *    *    |       left |* * * * | |   * * * * *|   |     ^   ^
    //   |<---->|   ** ** **   |     <----->|*      *| |       *    |   |  t  |   |
    //   |      |  *        *  |     |      |*      *| |       *    |   |  o  | h |
    //   |      | *          * |     |      |* * * * | |       *   (1)  |  p  | e |  baseline
    //   O------|*------------*|-----O----- |*-------|-|----O--*----O---|-----*-i-|------------>
    //   |      |______________|     |      |*       | |*   |  *    |   |     ^ g |
    //   |      |              |     |      |*       | |*   |  *    |   |  b  | h |
    //   |      |    width     |     |      |*       | |*   |  *    |   |  o  | t |
    //   |      |<------------>|     |      |*       | | * *|*      |   |  t  |   |
    //   |                           |      |________| |____|_______|___|_____|___*
    //   |         advance           |       advance   |    |advance| (advance maybe less than width)
    //   <---------------------------><----------------|----><------>
    //                                                 |left| (left maybe is negative)
    //                                                 |<-->|
    //
    //
    //   O                                       - The pen position for any time
    //
    //   left (m_face->glyph->bitmap_left)       - The horizontal distance from the current pen position to the glyph's left bbox edge.
    //
    //   advance (m_face->glyph->advance.x >> 6) - The horizontal distance to increment (for left-to-right writing)
    //                                              or decrement (for right-to-left writing) the pen position after a
    //                                              glyph has been rendered when processing text
    //
    //   width (bitmap->width)                   - The width of glyph
    //
    //
    //   Algorithm to compute size of the text bounding box:
    //
    //   1) Go through all symbols and shift pen position and save glyph parameters (left, advance, width)
    //      If left + pen position < 0 set left to 0. For example it's maybe happened
    //      if we print first letter 'J' or any other letter with negative 'left'
    //      We want to render glyph in pen position + left, so we must't allow it to be negative
    //
    //   2) If width == 0 we must to skip this symbol and don't save parameters for him.
    //      For example width == 0 for space sometimes
    //
    //   3) Also we compute max top and max bottom it's required for compute baseline
    //
    //   3) At the end we'll get the pen position for the symbol next to the last.
    //      See (1) on picture.
    //
    //   4) As we can see the last pen position is isn't horizontal size yet.
    //      We need to check if the glyph goes beyound the last position of the pen
    //      To do this we can:
    //      a) Return to the previous position -advance
    //      b) Shift on left value +left
    //      c) Shift on width of the last glyph
    //
    //      Compare result position with pen position and choose max
    //
    //      We can compute diff and check if diff > 0 pen.x += diff.
    //      See (2) on picture.
    //
    //  5) Return size. Complete!!!
    //
    // See also about freetype glyph metrics:
    // https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html

    GAPI_Assert(!FT_Set_Pixel_Sizes(m_face, fh, fh) &&
                "Failed to set pixel size");

    cv::Point pen(0, 0);

    int max_bot      = 0;
    int max_top      = 0;
    int last_advance = 0;
    int last_width   = 0;
    int last_left    = 0;

    for (const auto& wc : text)
    {
        GAPI_Assert(!FT_Load_Char(m_face, wc, FT_LOAD_RENDER) &&
                    "Failed to load char");

        FT_Bitmap *bitmap = &(m_face->glyph->bitmap);

        int left    = m_face->glyph->bitmap_left;
        int advance = (m_face->glyph->advance.x >> 6);
        int width   = bitmap->width;

        // NB: Read (1) paragraph of algorithm description
        if (pen.x + left < 0)
        {
            left = 0;
        }

        int bot = (m_face->glyph->metrics.height - m_face->glyph->metrics.horiBearingY) >> 6;
        max_bot = std::max(max_bot, bot);
        max_top = std::max(max_top, m_face->glyph->bitmap_top);

        // NB: Read (2) paragraph of algorithm description
        if (width != 0)
        {
            last_width = width;
            last_advance = advance;
            last_left    = left;
        }

        pen.x += advance;
    }

    // NB: Read (4) paragraph of algorithm description
    int diff = (last_width + last_left) - last_advance;
    pen.x += (diff > 0) ? diff : 0;

    if (baseline)
    {
        *baseline = max_bot;
    }

    return {pen.x, max_bot + max_top};
}

void cv::gapi::wip::draw::FTTextRender::Priv::putText(cv::Mat& mat,
                                                       const std::wstring& text,
                                                       const cv::Point& org,
                                                       int fh)
{
    GAPI_Assert(!FT_Set_Pixel_Sizes(m_face, fh, fh) &&
                "Failed to set pixel size");

    cv::Point pen = org;
    for (const auto& wc : text)
    {
        GAPI_Assert(!FT_Load_Char(m_face, wc, FT_LOAD_RENDER) &&
                    "Failed to load char");
        FT_Bitmap *bitmap = &(m_face->glyph->bitmap);

        // FIXME: Skip glyph, if size is 0
        if (bitmap->rows == 0 || bitmap->width == 0) {
            continue;
        }

        cv::Mat glyph(bitmap->rows, bitmap->width, CV_8UC1, bitmap->buffer, bitmap->pitch);

        int left    = m_face->glyph->bitmap_left;
        int top     = m_face->glyph->bitmap_top;
        int advance = (m_face->glyph->advance.x >> 6);

        if (pen.x + left < 0)
        {
            left = 0;
        }

        cv::Rect rect(pen.x + left, org.y - top, glyph.cols, glyph.rows);

        auto roi = mat(rect);
        roi += glyph;
        pen.x += advance;
    }
}

cv::gapi::wip::draw::FTTextRender::Priv::~Priv()
{
    FT_Done_Face(m_face);
    FT_Done_FreeType(m_library);
}

cv::gapi::wip::draw::FTTextRender::FTTextRender(const std::string& path)
    : m_priv(new Priv(path))
{
}

cv::Size cv::gapi::wip::draw::FTTextRender::getTextSize(const std::wstring& text,
                                                         int fh,
                                                         int* baseline)
{
    return m_priv->getTextSize(text, fh, baseline);
}

void cv::gapi::wip::draw::FTTextRender::putText(cv::Mat& mat,
                                                 const std::wstring& text,
                                                 const cv::Point& org,
                                                 int fh)
{
    m_priv->putText(mat, text, org, fh);
}

#else

cv::Size cv::gapi::wip::draw::FTTextRender::getTextSize(const std::wstring&, int, int*)
{
    cv::util::throw_error(std::runtime_error("Freetype not found"));
}

void cv::gapi::wip::draw::FTTextRender::putText(cv::Mat&, const std::wstring&, const cv::Point&, int)
{
    cv::util::throw_error(std::runtime_error("Freetype not found"));
}

cv::gapi::wip::draw::FTTextRender::FTTextRender(const std::string&)
{
    cv::util::throw_error(std::runtime_error("Freetype not found"));
}

#endif // HAVE_FREETYPE
