/*!
 * \file:   Exp1.cpp
 * \date:   2020/05/29 15:04
 * \author: wangqi
 * Contact: wangq@southzn.com
 *
 * \brief:
 *
 * TODO: long description
 *
 * \note:   实现了FT库生成字符位图,并上传到GL纹理。
            实现了字符位图缓存功能,多个字符图像保存在同一个纹理中。
            实现了简单的字体管理框架。
            实现了简单的加粗和倾斜效果。
            实现了反锯齿开关,并且兼容加粗倾斜效果。
 */

// OpenGL library
#include <gl/glut.h>

// Std misc
#include <map>
#include <vector>

// FreeType library
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_BITMAP_H
#include FT_OUTLINE_H


#ifdef CreateFont
#undef CreateFont
#endif

typedef unsigned char byte;

class CFontManager //字体管理器:管理了一个字体库
{
public:
    CFontManager();
    ~CFontManager();
    // 初始化
    bool initialize(void);
    // 释放
    void release(void);
    /*!
     * \brief:   createFont 创建字体
     *
     * \param:   filename   字体文件的路径
     * \param:   face
     * \param:   tall       高度
     * \param:   bold       加粗
     * \param:   italic     是否斜体
     * \param:   antialias  是否抗锯齿
     * \returns: int
     * \author:  wangqi
     * \date:    2020/06/02 14:16
     *
     * TODO:
     *
     */
    int createFont(const char *filename, int face, int tall, bool bold, bool italic, bool antialias);
    // 得到字符信息
    bool getCharInfo(int font_index, int code, int *wide, int *tall, int *horiBearingX, int *horiBearingY, int *horiAdvance, GLuint *texture, float coords[]);
    // 得到字体的高度
    int getFontTall(int font_index);

private:
    struct glyphMetrics //字形度量
    {
        int    width;   //宽度
        int    height;  //长度
        int    horiBearingX;
        int    horiBearingY;
        int    horiAdvance;
        //int    vertBearingX;
        //int    vertBearingY;
        //int    vertAdvance;
    };

    class CFont //字体
    {
    public:
        CFont();
        ~CFont();
        /*!
         * \brief:   create     创建字体
         *
         * \param:   library    FT库。需要提前FT_Init_FreeType
         * \param:   filename   字体文件的路径
         * \param:   face_index Face下标
         * \param:   tall       高度
         * \param:   bold       是否加粗
         * \param:   italic     是否斜体
         * \param:   antialias  是否抗锯齿
         * \returns: bool
         * \author:  wangqi
         * \date:    2020/06/02 14:13
         *
         * TODO:
         *
         */
        bool create(FT_Library library, const char *filename, FT_Long face_index,
                    int tall, bool bold, bool italic, bool antialias);
        // 释放
        void release(void);
        // 得到字符串信息
        bool getCharInfo(int code, glyphMetrics *metrics, GLuint *texture, float coords[]);
        // 得到字符高度
        int getFontTall(void);

    private:
        // 加载字符
        bool loadChar(int code, glyphMetrics *metrics);

        class CChar //渲染之后的字符
        {
        public:
            // 设置信息
            void setInfo(glyphMetrics *metrics);
            // 得到信息
            void getInfo(glyphMetrics *metrics, GLuint *texture, float coords[]);

        public:
            int m_code;             //编码
            GLuint  m_texture;      //纹理
            float   m_coords[4];    //坐标:left top right bottom

        private:
            glyphMetrics    m_metrics;
        };

        class CPage //页面:纹理=1:1。一个纹理中管理着多个字符
        {
        public:
            CPage();
            ~CPage();

            /*!
             * \brief:   append     加当前字体加入这个纹理
             *
             * \param:   wide       宽度
             * \param:   tall       高度
             * \param:   rgba       字体的内容
             * \param:   coords     字体在当前纹理中的坐标
             * \returns: bool
             * \author:  wangqi
             * \date:    2020/06/02 14:36
             *
             * TODO:
             *
             */
            bool append(int wide, int tall, byte *rgba, float coords[]);
            GLuint getTexture(void); //得到纹理

        private:
            GLuint    m_texture; //纹理
            int        m_wide;  //宽度
            int        m_tall;  //高度
            int        m_posx;
            int        m_posy;
            int        m_maxCharTall;
        };

        typedef std::map<int, CChar *> TCharMap;
        //键值对:找到字符的位置
        //key: 字符
        //value: 字符的信息

        FT_Library                m_library;
        FT_Face                    m_face;
        bool                    m_antialias;
        bool                    m_bold;
        int                        m_tall;
        int                        m_rgbaSize;
        GLubyte                    *m_rgba;
        TCharMap                m_chars;
        std::vector<CPage *>    m_pages;
    };

    FT_Library                m_library;
    std::vector<CFont *>      m_fonts; //一个字体库有多个字体
};

//------------------------------------------------------------
// CFont
//------------------------------------------------------------
CFontManager::CFont::CFont()
{
    m_face = NULL;
    m_rgba = NULL;
    m_antialias = false;
    m_bold = false;
    m_tall = 0;
}

CFontManager::CFont::~CFont()
{
    release();
}

bool CFontManager::CFont::create(FT_Library library, const char *filename, FT_Long face_index,
                                 int tall, bool bold, bool italic, bool antialias)
{
    FT_Error err;

    if(tall > 256)
    {
        // Bigger than a page size
        return false;
    }

    // 得到字符信息
    if((err = FT_New_Face(library, filename, face_index, &m_face)) != FT_Err_Ok)
    {
        printf("FT_New_Face() Error %d\n", err);
        return false;
    }

    // 设置像素
    if((err = FT_Set_Pixel_Sizes(m_face, 0, tall)) != FT_Err_Ok)
    {
        printf("FT_Set_Pixel_Sizes() Error %d\n", err);
        return false;
    }

    //m_rgbaSize = (tall * 2) * tall * 4; //源代码是*2,但是觉得博主写错了
    m_rgbaSize = (tall) * tall * 4; //高*宽*4。但位图的高宽相同,故可以高*高*4;4代表RGBA四个通道

    m_rgba = new GLubyte[m_rgbaSize];

    if(m_rgba == NULL)
    {
        return false;
    }

    m_library = library;
    m_antialias = antialias;
    m_bold = bold;
    m_tall = tall;

    if(italic) //是否抗锯齿
    {
        FT_Matrix m;
        m.xx = 0x10000L;
        m.xy = 0.5f * 0x10000L;
        m.yx = 0;
        m.yy = 0x10000L;
        FT_Set_Transform(m_face, &m, NULL);
    }

    return true;
}

void CFontManager::CFont::release(void)
{
    FT_Error err;

    if(m_face)
    {
        if((err = FT_Done_Face(m_face)) != FT_Err_Ok)
        {
            printf("FT_Done_Face() Error %d\n", err);
        }

        m_face = NULL;
    }

    if(m_rgba)
    {
        delete[] m_rgba;
        m_rgba = NULL;
    }

    for(TCharMap::iterator it = m_chars.begin(); it != m_chars.end(); it++)
    {
        delete it->second;
        it->second = NULL;
    }

    m_chars.clear();

    for(size_t i = 0; i < m_pages.size(); i++)
    {
        delete m_pages[i];
        m_pages[i] = NULL;
    }

    m_pages.clear();
}

bool CFontManager::CFont::getCharInfo(int code, glyphMetrics *metrics, GLuint *texture, float coords[])
{
    // 记忆化搜索
    TCharMap::iterator it = m_chars.find(code); //根据code获取CChar

    if(it != m_chars.end()) //找到
    {
        // 得到绘制信息
        it->second->getInfo(metrics, texture, coords); //字符、纹理、 坐标
        return true;
    }

    // 没有找到
    glyphMetrics gm;

    if(loadChar(code, &gm) == false) //加载字符
    {
        return false;
    }

    CChar *ch = new CChar();

    ch->m_code = code;
    ch->setInfo(&gm);

    // 追加到一张纸中
    for(size_t i = 0; i < m_pages.size(); i++)
    {
        CPage *page = m_pages[i];

        if(page->append(gm.width, gm.height, m_rgba, ch->m_coords))
        {
            // 这张纸还放的下
            ch->m_texture = page->getTexture();
            ch->getInfo(metrics, texture, coords);
            m_chars.insert(TCharMap::value_type(code, ch));
            return true;
        }
    }

    // 之前的纸都放不下了,新建一张纸
    CPage *page = new CPage();

    if(page->append(gm.width, gm.height, m_rgba, ch->m_coords))
    {
        ch->m_texture = page->getTexture();
        ch->getInfo(metrics, texture, coords);
        m_chars.insert(TCharMap::value_type(code, ch));
        m_pages.push_back(page);
        return true;
    }

    delete ch;
    delete page;

    return false;
}

int CFontManager::CFont::getFontTall(void)
{
    return m_tall;
}

// bitmap.width  位图宽度
// bitmap.rows   位图行数(高度)
// bitmap.pitch  位图一行占用的字节数

//MONO模式每1个像素仅用1bit保存,只有黑和白。
//1个byte可以保存8个像素,1个int可以保存8*4个像素。
void ConvertMONOToRGBA(FT_Bitmap *source, GLubyte *rgba)
{
    GLubyte *s = source->buffer;
    GLubyte *t = rgba;

    for(GLuint y = source->rows; y > 0; y--)
    {
        GLubyte *ss = s;
        GLubyte *tt = t;

        for(GLuint x = source->width >> 3; x > 0; x--)
        {
            GLuint val = *ss;

            for(GLuint i = 8; i > 0; i--)
            {
                tt[0] = tt[1] = tt[2] = tt[3] = (val & (1 << (i - 1))) ? 0xFF : 0x00;
                tt += 4;
            }

            ss += 1;
        }

        GLuint rem = source->width & 7;

        if(rem > 0)
        {
            GLuint val = *ss;

            for(GLuint x = rem; x > 0; x--)
            {
                tt[0] = tt[1] = tt[2] = tt[3] = (val & 0x80) ? 0xFF : 0x00;
                tt += 4;
                val <<= 1;
            }
        }

        s += source->pitch;
        t += source->width * 4;    //pitch
    }
}

//GRAY模式1个像素用1个字节保存。
void ConvertGRAYToRGBA(FT_Bitmap *source, GLubyte *rgba)
{
    for(GLuint y = 0; y < source->rows; y++)
    {
        for(GLuint x = 0; x < source->width; x++)
        {
            GLubyte *s = &source->buffer[(y * source->pitch) + x];
            GLubyte *t = &rgba[((y * source->pitch) + x) * 4];

            t[0] = t[1] = t[2] = 0xFF;
            t[3] = *s;
        }
    }
}

bool CFontManager::CFont::loadChar(int code, glyphMetrics *metrics)
{
    FT_Error err;

    FT_UInt glyph_index = FT_Get_Char_Index(m_face, (FT_ULong)code); //得到下标

    if(glyph_index > 0)
    {
        //将一个字形图像装到字形槽中
        if((err = FT_Load_Glyph(m_face, glyph_index, FT_LOAD_DEFAULT)) == FT_Err_Ok)
        {
            FT_GlyphSlot glyph = m_face->glyph; //获得图形信息
            // 模式:抗锯齿为256级灰度;FT_RENDER_MODE_MONO黑白
            FT_Render_Mode render_mode = m_antialias ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO;

            if(m_antialias && m_bold) //加粗
            {
                if((err = FT_Outline_EmboldenXY(&glyph->outline, 60, 60)) != FT_Err_Ok)
                {
                    printf("FT_Outline_EmboldenXY() Error %d\n", err);
                }
            }

            // 得到图形信息
            if((err = FT_Render_Glyph(glyph, render_mode)) == FT_Err_Ok)
            {
                FT_Bitmap *bitmap = &glyph->bitmap; //得到位图

                switch(bitmap->pixel_mode) //像素模式
                {
                case FT_PIXEL_MODE_MONO: //黑白
                {
                    if(!m_antialias && m_bold)
                    {
                        // 加粗
                        if((err = FT_Bitmap_Embolden(m_library, bitmap, 60, 0)) != FT_Err_Ok)
                        {
                            printf("FT_Bitmap_Embolden() Error %d\n", err);
                        }
                    }

                    // 将黑白转成RGBA
                    ConvertMONOToRGBA(bitmap, m_rgba);
                    break;
                }

                case FT_PIXEL_MODE_GRAY: //灰度
                {
                    ConvertGRAYToRGBA(bitmap, m_rgba); //灰度转RGBA
                    break;
                }

                default:
                {
                    memset(m_rgba, 0xFF, m_rgbaSize);
                    break;
                }
                }

                metrics->width = bitmap->width;
                metrics->height = bitmap->rows;
                metrics->horiBearingX = glyph->bitmap_left;
                metrics->horiBearingY = glyph->bitmap_top;
                metrics->horiAdvance = glyph->advance.x >> 6;

                return true;
            }
            else
            {
                printf("FT_Render_Glyph() Error %d\n", err);
            }
        }
        else
        {
            printf("FT_Load_Glyph() Error %d\n", err);
        }
    }

    memset(metrics, 0, sizeof(glyphMetrics));

    return false;
}

//------------------------------------------------------------
// CChar
//------------------------------------------------------------
void CFontManager::CFont::CChar::setInfo(glyphMetrics *metrics)
{
    memcpy(&m_metrics, metrics, sizeof(glyphMetrics));
}

void CFontManager::CFont::CChar::getInfo(glyphMetrics *metrics, GLuint *texture, float coords[])
{
    memcpy(metrics, &m_metrics, sizeof(glyphMetrics));

    *texture = m_texture;
    memcpy(coords, m_coords, sizeof(float) * 4);
}

//------------------------------------------------------------
// CPage 开辟一页的纹理,来存放多个字符
//------------------------------------------------------------
CFontManager::CFont::CPage::CPage() //一页对应一个纹理
{
    m_wide = m_tall = 256; //高
    m_posx = m_posy = 0;

    // In a line, for a max height character
    m_maxCharTall = 0;

    glGenTextures(1, &m_texture);    // Using your API here
    glBindTexture(GL_TEXTURE_2D, m_texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_wide, m_tall, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}

CFontManager::CFont::CPage::~CPage()
{
    // free the texture
}

bool CFontManager::CFont::CPage::append(int wide, int tall, byte *rgba, float coords[])
{
    if(m_posy + tall > m_tall)
    {
        // not enough line space in this page
        return false;
    }

    // If this line is full ...
    if(m_posx + wide > m_wide)
    {
        int newLineY = m_posy + m_maxCharTall;

        if(newLineY + tall > m_tall)
        {
            // No more space for new line in this page, should allocate a new one
            return false;
        }

        // Begin in new line
        m_posx = 0;
        m_posy = newLineY;
        // Reset
        m_maxCharTall = 0;
    }

    glBindTexture(GL_TEXTURE_2D, m_texture);
    glTexSubImage2D(GL_TEXTURE_2D, 0, m_posx, m_posy, wide, tall, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
    // 该字符的位置
    coords[0] = m_posx / (float)m_wide;                // left
    coords[1] = m_posy / (float)m_tall;                // top
    coords[2] = (m_posx + wide) / (float)m_wide;    // right
    coords[3] = (m_posy + tall) / (float)m_tall;    // bottom

    m_posx += wide;

    if(tall > m_maxCharTall)
    {
        m_maxCharTall = tall;
    }

    return true;
}

GLuint CFontManager::CFont::CPage::getTexture(void)
{
    return m_texture;
}

//------------------------------------------------------------
// CFontManager
//------------------------------------------------------------
CFontManager::CFontManager()
{
    m_library = NULL;
}

CFontManager::~CFontManager()
{
    release();
}

bool CFontManager::initialize(void)
{
    FT_Error err;

    if((err = FT_Init_FreeType(&m_library)) != FT_Err_Ok) //初始化一个库
    {
        printf("FT_Init_FreeType() Error %d\n", err);
        return false;
    }

    return true;
}

void CFontManager::release(void)
{
    FT_Error err;

    for(size_t i = 0; i < m_fonts.size(); i++)
    {
        delete m_fonts[i];
        m_fonts[i] = NULL;
    }

    m_fonts.clear();

    if((err = FT_Done_FreeType(m_library)) != FT_Err_Ok)
    {
        printf("FT_Done_FreeType() Error %d\n");
    }
}

int CFontManager::createFont(const char *filename, int face, int tall, bool bold,
                             bool italic, bool antialias)
{
    // 新建字体
    CFont *font = new CFont();

    if(font->create(m_library, filename, face, tall, bold, italic, antialias) != true)
    {
        delete font; //创建失败
        return 0;
    }

    // 放入字体数组,进行管理
    m_fonts.push_back(font);

    return m_fonts.size();
}

#define CONVERT_FONT_INDEX(x) (((x) < 1 || (x) > (int)m_fonts.size()) ? -1 : (x) - 1)

bool CFontManager::getCharInfo(int font_index, int code, int *wide, int *tall, int *horiBearingX, int *horiBearingY, int *horiAdvance, GLuint *texture, float coords[])
{
    int i = CONVERT_FONT_INDEX(font_index); //转换字符的下标

    if(i == -1)
    {
        return false;
    }

    CFont *font = m_fonts[i]; //获得字体

    glyphMetrics metrics; //绘制信息

    if(font->getCharInfo(code, &metrics, texture, coords) == false) //获得绘制信息
    {
        return false;
    }

    // 解析绘制信息
    *wide = metrics.width;
    *tall = metrics.height;
    *horiBearingX = metrics.horiBearingX;
    *horiBearingY = metrics.horiBearingY;
    *horiAdvance = metrics.horiAdvance;

    return true;
}

int CFontManager::getFontTall(int font_index)
{
    int i = CONVERT_FONT_INDEX(font_index);

    if(i == -1)
    {
        return false;
    }

    CFont *font = m_fonts[i];

    return font->getFontTall();
}

CFontManager g_FontManager;


int char_font;


void init(void)
{
    // 清除颜色
    glClearColor(0.0, 0.0, 0.0, 0.0);
    // 混合模式
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    // 初始化一个库
    g_FontManager.initialize();

    // 创建字体,返回字体集的个数
    char_font = g_FontManager.createFont("C:\\WINDOWS\\Fonts\\simhei.ttf",
                                         0,
                                         32, //字体高度
                                         false,
                                         false,
                                         true);

    if(char_font == 0)//创建失败
    {
        printf("createFont failed\n");
    }
}

wchar_t ciphertext[] =
{
    L"你好吗?\n"
    L"温馨提示:\n"
    L"不要忘记明天上午 7:30-12:00 在公司的体检哦~\n"
    L"体检前一天不要暴饮暴食,不要喝酒,不要进食太甜太咸的食物\n"
    L"以免影响体检结果\n"
    L"晚上10点后一般要求禁食。\n"
    L"体检当天早晨千万不要吃早餐!\n"
    L"不要吃早餐!\n"
    L"不要吃早餐!\n"
    L"绘制位置。忘记了,可费解为无法\n"
};

//#define DRAW_PAGE

/*!
 * \brief:   draw_string
 *
 * \param:   x              绘制位置
 * \param:   y
 * \param:   font           字体
 * \param:   string         字符串
 * \returns: void
 * \author:  wangqi
 * \date:    2020/06/02 14:18
 *
 * TODO:
 *
 */
void draw_string(int x, int y, int font_index, wchar_t *string)
{
    if(!font_index) //字体
    {
        return;
    }

    int tall = g_FontManager.getFontTall(font_index); //得到字符高:初始化字体时设置的

    // 绘制的初始位置
    int dx = x;
    int dy = y;

    GLuint sglt = 0; //纹理编号,第几张纸

    while(*string) // 遍历字符串
    {
        if(*string == L'\n') //回车
        {
            string++;
            dx = x;         //绘制的初始位置x
            dy += tall + 2; //行间距
            continue;
        }

        int cw, ct; //宽度、高度
        int bx, by, av;
        GLuint glt;   //该字符所在的纸张
        float crd[4]; //该字符的坐标

        //得到字符信息
        if(!g_FontManager.getCharInfo(font_index, *string, &cw, &ct, &bx, &by, &av, &glt, crd))
        {
            string++;
            continue;
        }

        //大多数情况下多个字符都在同一个纹理中,避免频繁绑定纹理,可以提高效率
        if(glt != sglt) //和原来的不一样,则绑定当前的纹理
        {
            glBindTexture(GL_TEXTURE_2D, glt); //绑定纹理
            sglt = glt;
        }

        int px = dx + bx;
        int py = dy - by;

        // 开始绘制四边形
        glBegin(GL_QUADS);
        // 第一个点
        glTexCoord2f(crd[0], crd[1]);   //指定该字符在纹理中的坐标,阈值为[0, 1]
        glVertex3f(px, py, 0.0f);       //对应画布中的位置
        // 第二个点
        glTexCoord2f(crd[2], crd[1]);
        glVertex3f(px + cw, py, 0.0f);
        // 第三个点
        glTexCoord2f(crd[2], crd[3]);
        glVertex3f(px + cw, py + ct, 0.0f);
        // 第四个点
        glTexCoord2f(crd[0], crd[3]);
        glVertex3f(px, py + ct, 0.0f);
        // 结束绘制
        glEnd();

        dx += av;
        string++;
    }
}

void draw_page_texture(int x, int y, GLuint glt)
{
    if(!glt)
    {
        glDisable(GL_TEXTURE_2D); //关闭
        glColor4f(0.2, 0.2, 0.2, 1.0);
    }
    else
    {
        glEnable(GL_TEXTURE_2D); //开启
        glBindTexture(GL_TEXTURE_2D, glt); //绑定纹理
        glColor4f(1.0, 1.0, 1.0, 1.0);
    }

    int w = 256;
    int t = 256;

    glBegin(GL_QUADS);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(x, y, 0.0f);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(x + w, y, 0.0f);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(x + w, y + t, 0.0f);
    glTexCoord2f(0.0, 1.0);
    glVertex3f(x, y + t, 0.0f);
    glEnd();
}

void display(void)
{
    // 清除缓存
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    // 开启纹理
    glEnable(GL_TEXTURE_2D);
    // 设置画笔颜色,即字体颜色
    glColor4f(0.0, 0.0, 1.0, 1.0);
    // 绘制字符串
    draw_string(10, 30, char_font, ciphertext);

    // 绘制整个纹理
    draw_page_texture(10, 350, 0);    //绘制一个背景
    draw_page_texture(10, 350, 1);    //绘制第一张纸(即第一个纹理)

    draw_page_texture(276, 350, 0);   //绘制一个背景
    draw_page_texture(276, 350, 2);   //绘制第二张纸(即第二个纹理)

    glutSwapBuffers();
    glutPostRedisplay();
}

void reshape(int width, int height)
{
    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, width, height, 0);
    glMatrixMode(GL_MODELVIEW);
}

void keyboard(unsigned char key, int x, int y)
{
}

void main(int argc, char **argv)
{
    glutInitWindowPosition(200, 200);
    glutInitWindowSize(1200, 680);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL);
    glutCreateWindow("FreeType OpenGL");

    init();
    glutDisplayFunc(display);

    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutMainLoop();
}