#include "AppHdr.h"

#ifdef USE_TILE_LOCAL
#ifdef USE_FT

#include "fontwrapper-ft.h"

#include <ft2build.h>
#include FT_FREETYPE_H

#include "defines.h"
#include "end.h"
#include "errno.h"
#include "files.h"
#include "format.h"
#include "glwrapper.h"
#include "options.h"
#include "syscalls.h"
#include "tilebuf.h"
#include "tilefont.h"
#include "tilesdl.h"
#include "unicode.h"
#include "unwind.h"

// maximum number of unique glyphs that can be rendered with this font at once; e.g. 4096, 256, 36
#define MAX_GLYPHS 256
// dimensions of glyph grid; GLYPHS_PER_ROWCOL^2 <= MAX_GLYPHS; e.g. 64, 16, 6
#define GLYPHS_PER_ROWCOL 16
// char to use if we can't find it in the font (upside-down question mark)
#define MISSING_CHAR 0xbf

#if 0
# define dprintf(...) debuglog(__VA_ARGS__)
#else
# define dprintf(...) (void)0
#endif

class FontLibrary {
public:
    static FT_Library &get() {
        static FontLibrary instance;
        return instance.library;
    }
private:
    FT_Library library;
    FontLibrary ()
    {
        if (FT_Init_FreeType(&library))
            die_noline("Failed to initialise freetype library.\n");
    };
    ~FontLibrary ()
    {
        if (FT_Done_FreeType(library))
            die_noline("Failed to unload freetype library.\n");
    };
};

FontWrapper* FontWrapper::create()
{
    return new FTFontWrapper();
}

FTFontWrapper::FTFontWrapper() :
    m_atlas(nullptr),
    m_max_advance(0, 0),
    m_min_offset(0),
    charsz(1,1),
    m_max_width(0),
    m_max_height(0),
    ttf(nullptr),
    face(nullptr),
    pixels(nullptr),
    fsize(0)
{
    m_buf = GLShapeBuffer::create(true, true);
}

FTFontWrapper::~FTFontWrapper()
{
    delete[] m_atlas;
    delete[] pixels;
    delete m_buf;
    if (face)
        FT_Done_Face(face);
    delete[] ttf;
}

/**
 * Configure the font based on metrics, and initialize caches. This may be
 * called multiple times when cached information needs to be reset, e.g. upon
 * changing DPI.
 */
bool FTFontWrapper::configure_font()
{
    FT_Error error;
    error = FT_Set_Pixel_Sizes(face,
                                display_density.logical_to_device(fsize),
                                display_density.logical_to_device(fsize));
    ASSERT(!error);

    // Get maximum advance and other global metrics
    FT_Size_Metrics metrics = face->size->metrics;
    m_max_advance   = coord_def(0,0);
    m_max_advance.x = metrics.max_advance >> 6;
    m_max_advance.y = (metrics.ascender-metrics.descender)>>6;
    m_ascender      = (metrics.ascender>>6);
    // if you're looking for realistic glyph sizes use m_max_advance
    // or char_width, these are still scaled.
    // (TODO: why would you ever use these values? m_max_advance is almost
    // certainly correct...)
    m_max_width     = (face->bbox.xMax >> 6) - (face->bbox.xMin >> 6);
    m_max_height    = (face->bbox.yMax >> 6) - (face->bbox.yMin >> 6);
    m_min_offset    = 0;

    charsz = coord_def(1,1);
    // Grow character size to power of 2
    while (charsz.x < m_max_advance.x)
        charsz.x *= 2;
    while (charsz.y < m_max_advance.y)
        charsz.y *= 2;

    // Fill out texture to be (16*charsz.x) X (16*charsz.y) X (32-bit)
    // Having to blow out 8-bit alpha values into full 32-bit textures is
    // kind of frustrating, but not all OpenGL implementations support the
    // "esoteric" ALPHA8 format and it's not like this texture is very large.

    // [frogbotherer] I think we can get memory usage lower by blowing out
    // the texture as a whole out to a power of 2, instead of each individual
    // character. Also, whilst GLES baulks at ALPHA8, there might be some
    // other compression format that we can use to get the size down a bit
    m_ft_width  = GLYPHS_PER_ROWCOL * charsz.x;
    m_ft_height = GLYPHS_PER_ROWCOL * charsz.y;

    delete[] pixels; // for repeated calls

    pixels = new unsigned char[4 * charsz.x * charsz.y];
    memset(pixels, 0, sizeof(unsigned char) * 4 * charsz.x * charsz.y);

    dprintf("new font tex %d x %d x 4 = %dpx %d bytes\n",
            m_ft_width, m_ft_height, m_ft_width * m_ft_height,
            4 * m_ft_width * m_ft_height);

    // initialise empty texture of correct size
    unwind_bool noscaling(Options.tile_filter_scaling, false);
    m_tex.load_texture(nullptr, m_ft_width, m_ft_height, MIPMAP_NONE);

    m_glyphs.clear();

    for (int i = 0; i < MAX_GLYPHS; i++)
        m_atlas[i] = FontAtlasEntry();

    // atlas[0] always contains a full-white block (never evicted)
    // this is currently used by colour_bar
    {
        for (int x = 0; x < m_max_advance.x; x++)
            for (int y = 0; y < m_max_advance.y; y++)
            {
                unsigned int idx = x + y * m_max_advance.x;
                idx *= 4;
                pixels[idx]     = 255;
                pixels[idx + 1] = 255;
                pixels[idx + 2] = 255;
                pixels[idx + 3] = 255;
            }

        bool success = m_tex.load_texture(pixels, charsz.x, charsz.y,
                                          MIPMAP_NONE, 0, 0);
        ASSERT(success);
    }

    // precache common chars
    for (int i = 0x20; i < 0x7f; i++)
        map_unicode(i);
    return true;
}

bool FTFontWrapper::load_font(const char *font_name, unsigned int font_size)
{
    FT_Error error;
    FT_Library library = FontLibrary::get();

    fsize = font_size;

    // TODO enne - need to find a cross-platform way to also
    // attempt to locate system fonts by name...
    // 1KB: fontconfig if we are not scared of hefty libraries

    // TODO: probably don't want to end here, but try a fallback font in
    // the calling function.
    string font_path = datafile_path(font_name, false, true);
    if (font_path.c_str()[0] == 0)
        end(1, false, "Could not find font '%s'", font_name);

    // Certain versions of freetype have problems reading files on Windows,
    // do that ourselves.
    FILE *f = fopen_u(font_path.c_str(), "rb");
    if (!f)
        end(1, false, "Could not read font '%s'\n", font_name);
    unsigned long size = file_size(f);
    ttf = new FT_Byte[size];
    ASSERT(ttf);
    if (fread(ttf, 1, size, f) != size)
        end(1, false, "Could not read font '%s': %s\n", font_name, strerror(errno));
    fclose(f);

    error = FT_New_Memory_Face(library, ttf, size, 0, &face);
    if (error == FT_Err_Unknown_File_Format)
        end(1, false, "Unknown font format for file '%s'\n", font_path.c_str());
    else if (error)
    {
        end(1, false, "Invalid font from file '%s' (size %lu): 0x%0x\n",
                   font_path.c_str(), size, error);
    }

    m_atlas = new FontAtlasEntry[MAX_GLYPHS];
    m_atlas_lru.clear();
    m_atlas_lru.reserve(MAX_GLYPHS);

    return configure_font();
}

FTFontWrapper::GlyphInfo& FTFontWrapper::get_glyph_info(char32_t ch)
{
    // cache glyph info in a single large buffer by unicode codepoint
    // currently dat/ only has codepoints going up to around 65536
    if (ch >= m_glyphs.size())
    {
        auto old_sz = m_glyphs.size();
        m_glyphs.resize(ch+1);
        for (size_t i = old_sz; i < m_glyphs.size(); i++)
            m_glyphs[i].valid = false;
    }
    GlyphInfo &glyph = m_glyphs[ch];
    if (!glyph.valid)
    {
        FT_Int glyph_index = FT_Get_Char_Index(face, ch);
        if (!glyph_index)
            glyph_index = FT_Get_Char_Index(face, MISSING_CHAR);
        // need to use FT_LOAD_RENDER, otherwise glyph->bitmap isn't loaded
        FT_Error error = FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER |
                (Options.tile_font_ft_light ? FT_LOAD_TARGET_LIGHT : 0));
        ASSERT(!error);
        FT_Bitmap *bmp = &face->glyph->bitmap;
        ASSERT(bmp);

        glyph.offset = face->glyph->bitmap_left;
        glyph.advance = face->glyph->advance.x >> 6;
        glyph.ascender = face->glyph->bitmap_top;
        glyph.width = bmp->width;
        glyph.renderable = !!bmp->buffer;
        glyph.valid = true;
    }
    return glyph;
}

void FTFontWrapper::load_glyph(unsigned int c, char32_t uchar)
{
    // get on with rendering the new glyph
    FT_Error error;
    FT_Int glyph_index = FT_Get_Char_Index(face, uchar);

    if (!glyph_index)
        glyph_index = FT_Get_Char_Index(face, MISSING_CHAR);

    error = FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER |
        (Options.tile_font_ft_light ? FT_LOAD_TARGET_LIGHT : 0));
    ASSERT(!error);

    FT_Bitmap *bmp = &face->glyph->bitmap;
    ASSERT(bmp);

    // Was int prior to freetype 2.5.4, then became unsigned.
    typedef decltype(bmp->width) ftint;

    // Some glyphs (e.g. ' ') don't get a buffer.
    if (bmp->buffer)
    {
        ASSERT(bmp->pixel_mode == FT_PIXEL_MODE_GRAY);
        ASSERT(bmp->num_grays == 256);

        // Horizontal offset stored in m_atlas and handled when drawing
        const unsigned int offset_x = 0;
        const unsigned int offset_y = 0;
        memset(pixels, 0, sizeof(unsigned char) * 4 * charsz.x * charsz.y);

        // Some fonts have wrong size info
        const ftint charw = bmp->width;
        bmp->width = min(bmp->width, ftint(charsz.x));
        bmp->rows = min(bmp->rows, ftint(charsz.y));

        for (ftint x = 0; x < bmp->width; x++)
            for (ftint y = 0; y < bmp->rows; y++)
            {
                unsigned int idx = offset_x + x + (offset_y + y) * charsz.x;
                idx *= 4;
                if (x < bmp->width && y < bmp->rows)
                {
                    unsigned char alpha = bmp->buffer[x + charw * y];
                    pixels[idx] = 255;
                    pixels[idx + 1] = 255;
                    pixels[idx + 2] = 255;
                    pixels[idx + 3] = alpha;
                }
            }

        unwind_bool noscaling(Options.tile_filter_scaling, false);
        bool success = m_tex.load_texture(pixels, charsz.x, charsz.y,
                            MIPMAP_NONE,
                            (c % GLYPHS_PER_ROWCOL) * charsz.x,
                            (c / GLYPHS_PER_ROWCOL) * charsz.y);
        ASSERT(success);
    }
}

unsigned int FTFontWrapper::map_unicode(char *ch)
{
    char32_t c;
    utf8towc(&c, ch);
    return map_unicode(c);
}

unsigned int FTFontWrapper::map_unicode(char32_t uchar)
{
    unsigned int c = MAX_GLYPHS;
    for (unsigned int i = 1; i < MAX_GLYPHS; i++)
        if (m_atlas[i].uchar == uchar)
        {
            c = i;
            break;
        }

    if (c == MAX_GLYPHS) // not found: need to load into atlas
    {
        bool atlas_full = m_atlas_lru.size() == MAX_GLYPHS-1;
        c = atlas_full ? m_atlas_lru[0] : m_atlas_lru.size()+1;
        m_atlas[c].uchar = uchar;
        load_glyph(c, uchar);
        n_subst++;
    }

    auto it = find(m_atlas_lru.begin(), m_atlas_lru.end(), (uint8_t)c);
    if (it != m_atlas_lru.end())
        m_atlas_lru.erase(it);
    m_atlas_lru.push_back(c);

    return c;
}

void FTFontWrapper::render_textblock(unsigned int x_pos, unsigned int y_pos,
                                     char32_t *chars,
                                     uint8_t *colours,
                                     unsigned int width, unsigned int height,
                                     bool drop_shadow)
{
    if (!chars || !colours || !width || !height || !m_atlas)
        return;

    coord_def adv(max(-m_min_offset, 0), 0);
    unsigned int i = 0;

    ASSERT(m_buf);
    m_buf->clear();
    n_subst = 0;

    float texcoord_dy = (float)m_max_advance.y / (float)m_tex.height();
    for (unsigned int y = 0; y < height; y++)
    {
        for (unsigned int x = 0; x < width; x++)
        {
            GlyphInfo &glyph = get_glyph_info(chars[i]);
            uint8_t col_bg = colours[i] >> 4;
            uint8_t col_fg = colours[i] & 0xF;

            if (col_bg != 0)
            {
                GLWPrim rect(adv.x, adv.y,
                             adv.x + m_max_advance.x, adv.y + m_max_advance.y);
                // Leave tex coords at their default 0.0f
                VColour col(term_colours[col_bg].r,
                            term_colours[col_bg].g,
                            term_colours[col_bg].b);
                rect.set_col(col);
                m_buf->add(rect);
            }

            adv.x += glyph.offset;

            if (glyph.renderable)
            {
                unsigned int c = map_unicode(chars[i]);
                int this_width = glyph.width;

                float tex_x = (float)(c % GLYPHS_PER_ROWCOL) / (float)GLYPHS_PER_ROWCOL;
                float tex_y = (float)(c / GLYPHS_PER_ROWCOL) / (float)GLYPHS_PER_ROWCOL;
                float tex_x2 = tex_x + (float)this_width / (float)m_tex.width();
                float tex_y2 = tex_y + texcoord_dy;

                GLWPrim rect(adv.x, adv.y - glyph.ascender + m_ascender,
                             adv.x + this_width, adv.y + m_max_advance.y - glyph.ascender + m_ascender);

                VColour col(term_colours[col_fg].r,
                            term_colours[col_fg].g,
                            term_colours[col_fg].b);
                rect.set_col(col);
                rect.set_tex(tex_x, tex_y, tex_x2, tex_y2);

                m_buf->add(rect);
            }

            i++;
            adv.x += glyph.advance - glyph.offset;

            // See if we need to flush prematurely.
            if (n_subst == MAX_GLYPHS - 1)
            {
                draw_m_buf(x_pos, y_pos, drop_shadow);
                m_buf->clear();
                n_subst = 0;
            }
        }

        adv.x = 0;
        adv.y += m_max_advance.y;
    }

    draw_m_buf(x_pos, y_pos, drop_shadow);
}

void FTFontWrapper::draw_m_buf(unsigned int x_pos, unsigned int y_pos,
                               bool drop_shadow)
{
    if (!m_buf->size())
        return;

    GLState state;
    state.array_vertex = true;
    state.array_texcoord = true;
    state.array_colour = true;
    state.blend = true;
    state.texture = true;

    m_tex.bind();

    GLW_3VF trans(x_pos, y_pos, 0.0f);
    GLW_3VF scale(display_density.scale_to_logical(),
                  display_density.scale_to_logical(), 1);

    if (drop_shadow)
    {
        GLState state_shadow;
        state_shadow.array_colour = false;
        state_shadow.colour = VColour::black;

        GLW_3VF trans_shadow(trans.x + 1, trans.y + 1, 0.0f);
        glmanager->set_transform(trans_shadow, scale);

        m_buf->draw(state_shadow);
    }

    glmanager->set_transform(trans, scale);
    m_buf->draw(state);

    glmanager->reset_transform();
}

static void _draw_box(int x_pos, int y_pos, int width, int height, VColour colour)
{
    unique_ptr<GLShapeBuffer> buf(GLShapeBuffer::create(false, true));
    GLWPrim rect(x_pos, y_pos, x_pos + width, y_pos + height);

    rect.set_col(colour);

    buf->add(rect);

    // Load identity matrix
    glmanager->reset_transform();

    GLState state;
    state.array_vertex = true;
    state.array_colour = true;
    state.blend = true;
    buf->draw(state);
}

unsigned int FTFontWrapper::string_height(const formatted_string &str, bool logical) const
{
    string temp = str.tostring();
    return string_height(temp.c_str(), logical);
}

unsigned int FTFontWrapper::string_height(const char *text, bool logical) const
{
    int height = 1;
    for (char *itr = (char *)text; *itr; itr = next_glyph(itr))
        if (*itr == '\n')
            height++;

    return max_height(height, logical);
}

unsigned int FTFontWrapper::string_width(const formatted_string &str, bool logical)
{
    string temp = str.tostring();
    return string_width(temp.c_str(), logical);
}

unsigned int FTFontWrapper::string_width(const char *text, bool logical)
{
    unsigned int base_width = max(-m_min_offset, 0);
    unsigned int max_str_width = 0;

    unsigned int width = base_width;
    unsigned int adjust = 0;
    for (char *itr = (char *)text; *itr; itr = next_glyph(itr))
    {
        if (*itr == '\n')
        {
            max_str_width = max(width + adjust, max_str_width);
            width = base_width;
            adjust = 0;
        }
        else
        {
            char32_t ch;
            utf8towc(&ch, itr);
            GlyphInfo &glyph = get_glyph_info(ch);
            width += glyph.advance;
            adjust = max(0, glyph.width - glyph.advance);
        }
    }

    max_str_width = max(width + adjust, max_str_width);
    return logical ? display_density.device_to_logical(max_str_width)
                   : max_str_width;
}

int FTFontWrapper::find_index_before_width(const char *text, int max_str_width)
{
    int width = max(-m_min_offset, 0);

    max_str_width *= display_density.scale_to_device();

    for (char *itr = (char *)text; *itr; itr = next_glyph(itr))
    {
        if (*itr == '\n')
        {
            width = 0;
            continue;
        }
        char32_t ch;
        utf8towc(&ch, itr);
        GlyphInfo &glyph = get_glyph_info(ch);
        width += glyph.advance;
        int adjust = max(0, glyph.width - glyph.advance);
        if (width + adjust > max_str_width)
            return itr-text;
    }

    return INT_MAX;
}

static int _find_newline(const char *s)
{
    const char *nl = strchr(s, '\n');
    return nl ? nl-s : INT_MAX;
}

formatted_string FTFontWrapper::split(const formatted_string &str,
                                      unsigned int max_str_width,
                                      unsigned int max_str_height)
{
    int max_lines = display_density.logical_to_device(max_str_height)
                                                        / char_height(false);

    if (max_lines < 1)
        return formatted_string();

    formatted_string ret;
    ret += str;

    string base = str.tostring();
    int num_lines = 0;

    char *line = &base[0];
    while (true)
    {
        int nl = _find_newline(line);
        int line_end = find_index_before_width(line, max_str_width);
        if (line_end == INT_MAX && nl == INT_MAX)
            break;

        int space_idx = 0;
        if (nl < line_end)
            space_idx = nl;
        else
        {
            space_idx = -1;
            for (char *search = &line[line_end];
                 search > line;
                 search = prev_glyph(search, line))
            {
                if (*search == ' ')
                {
                    space_idx = search - line;
                    break;
                }
            }
        }

        if (++num_lines >= max_lines || space_idx == -1)
        {
            line_end = min(line_end, nl);
            int ellipses;
            if (space_idx != -1 && space_idx - line_end > 2)
                ellipses = space_idx;
            else
            {
                ellipses = line_end;
                for (unsigned i = 0; i < strlen(".."); i++)
                {
                    char *prev = prev_glyph(&line[ellipses], line);
                    ellipses = (prev ? prev : line) - line;
                }
            }

            ret = ret.chop_bytes(&line[ellipses] - &base[0]);
            ret += "..";
            return ret;
        }
        else
        {
            line[space_idx] = '\n';
            ret[&line[space_idx] - &base[0]] = '\n';
        }

        line = &line[space_idx+1];
    }

    return ret;
}

/**
 * Render a tooltip around the given position.
 *
 * @param px the x coordinate
 * @param py the y coordinate
 * @param text the string to render
 * @param min_pos the top-left boundary of the screen
 * @param max_pos the bottom-right boundary of the screen
 */
void FTFontWrapper::render_tooltip(unsigned int px, unsigned int py,
                                  const formatted_string &text,
                                  const coord_def &min_pos,
                                  const coord_def &max_pos)
{
    int outline = 7;
    const int wx = string_width(text);
    const int wy = string_height(text);

    // text starting location
    int tx = px - 15, ty = py + 20;

    // box position, before shifting
    const int sx = tx - outline;
    const int sy = ty - outline;
    const int ex = tx + wx + outline;
    const int ey = ty + wy + outline;

    if (ex > max_pos.x)
        tx += max_pos.x - ex;
    else if (sx < min_pos.x)
        tx -= sx - min_pos.x;

    if (ey > max_pos.y)
        ty += max_pos.y - ey;
    else if (sy < min_pos.y)
        ty -= sy - min_pos.y;

    const VColour border_colour(125, 98, 60);
    const VColour bg_colour(4, 2, 4);
    _draw_box(tx-outline, ty-outline, wx+2*outline, wy+2*outline, border_colour);
    outline -= 2;
    _draw_box(tx-outline, ty-outline, wx+2*outline, wy+2*outline, bg_colour);

    render_string(tx, ty, text);
}

/**
 * Render a string at the given position.
 *
 * @param px the x coordinate
 * @param py the y coordinate
 * @param text the string to render
 * @param font_colour the text colour to use
 */
void FTFontWrapper::render_string(unsigned int px, unsigned int py,
                                  const formatted_string &text)
{
    glmanager->reset_transform();
    FontBuffer m_font_buf(this);
    m_font_buf.add(text, px, py);
    m_font_buf.draw();
}

/**
 * Render a string hovering above the given position, centred horizontally.
 *
 * @param px the x coordinate
 * @param py the y coordinate
 * @param text the string to render
 * @param font_colour the text colour to use
 */
void FTFontWrapper::render_hover_string(unsigned int px, unsigned int py,
                                  const formatted_string &text)
{
    const int wx = string_width(text);
    const int wy = string_height(text);
    const int ty = py - wy;
    const int tx = px - wx / 2;

    render_string(tx, ty, text);
}

/**
 * Store a string in a FontBuffer.
 *
 * @param buf the FontBuffer to store the glyph in.
 * @param x the x coordinate
 * @param y the y coordinate
 * @param str the string to store
 * @param col a foreground color
 */
void FTFontWrapper::store(FontBuffer &buf, float &x, float &y,
                          const string &str, const VColour &col)
{
    store(buf, x, y, str, col, x);
}

/**
 * Store a string in a FontBuffer.
 *
 * @param buf the FontBuffer to store the glyph in.
 * @param x the x coordinate
 * @param y the y coordinate
 * @param str the string to store
 * @param col a foreground color
 * @param orig_x an x offset to use as an origin
 */
void FTFontWrapper::store(FontBuffer &buf, float &x, float &y,
                          const string &str, const VColour &col, float orig_x)
{
    const char *sp = str.c_str();
    char32_t c;
    while (int s = utf8towc(&c, sp))
    {
        sp += s;
        if (c == '\n')
        {
            x = orig_x;
            y += m_max_advance.y * display_density.scale_to_logical();
        }
        else
            store(buf, x, y, c, col);
    }
}

/**
 * Store a formatted_string in a FontBuffer.
 *
 * @param buf the FontBuffer to store the glyph in.
 * @param x the x coordinate
 * @param y the y coordinate
 * @param fs the formatted string to store
 */
void FTFontWrapper::store(FontBuffer &buf, float &x, float &y,
                          const formatted_string &fs)
{
    store(buf, x, y, fs, x);
}

/**
 * Store a formatted_string in a FontBuffer.
 *
 * @param buf the FontBuffer to store the glyph in.
 * @param x the x coordinate
 * @param y the y coordinate
 * @param fs the formatted string to store
 * @param orig_x an x offset to use as an origin
 */
void FTFontWrapper::store(FontBuffer &buf, float &x, float &y,
                          const formatted_string &fs, float orig_x)
{
    int colour = LIGHTGREY;
    for (const formatted_string::fs_op &op : fs.ops)
    {
        switch (op.type)
        {
            case FSOP_COLOUR:
                // Only foreground colors for now...
                colour = op.colour & 0xF;
                break;
            case FSOP_TEXT:
                store(buf, x, y, op.text, term_colours[colour], orig_x);
                break;
            default:
                break;
        }
    }
}

/**
 * Store a single glyph in a FontBuffer.
 *
 * @param buf the FontBuffer to store the glyph in.
 * @param x the x coordinate
 * @param y the y coordinate
 * @param ch a (unicode) character
 * @param fg_col the foreground color to print
 */
void FTFontWrapper::store(FontBuffer &buf, float &x, float &y,
                          char32_t ch, const VColour &col)
{
    GlyphInfo &glyph = get_glyph_info(ch);
    float density_mult = display_density.scale_to_logical();

    if (!glyph.renderable)
    {
        x += glyph.advance * density_mult;
        return;
    }

    unsigned int c = map_unicode(ch);
    int this_width = glyph.width;

    float pos_sx = x + glyph.offset * density_mult;
    float pos_sy = y - (glyph.ascender - m_ascender) * density_mult;
    float pos_ex = pos_sx + this_width * density_mult;
    float pos_ey = y + (m_max_advance.y - glyph.ascender + m_ascender)
                   * density_mult;

    float tex_sx = (float)(c % GLYPHS_PER_ROWCOL) / (float)GLYPHS_PER_ROWCOL;
    float tex_sy = (float)(c / GLYPHS_PER_ROWCOL) / (float)GLYPHS_PER_ROWCOL;
    float tex_ex = tex_sx + (float)this_width / (float)(GLYPHS_PER_ROWCOL*charsz.x);
    float tex_ey = tex_sy + (float)m_max_advance.y / (float)(GLYPHS_PER_ROWCOL*charsz.y);

    GLWPrim rect(pos_sx, pos_sy, pos_ex, pos_ey);
    rect.set_tex(tex_sx, tex_sy, tex_ex, tex_ey);
    rect.set_col(col);
    buf.add_primitive(rect);


    x += glyph.advance * density_mult;
}

/**
 * Store a single glyph, with both a background and a foreground color.
 *
 * @param buf the FontBuffer to store the glyph in.
 * @param x the x coordinate
 * @param y the y coordinate
 * @param ch a (unicode) character
 * @param fg_col the foreground color to print
 * @param bg_col the background color to print
 */
void FTFontWrapper::store(FontBuffer &buf, float &x, float &y,
                          char32_t ch, const VColour &fg_col, const VColour &bg_col)
{
    GlyphInfo &glyph = get_glyph_info(ch);
    const float density_mult = display_density.scale_to_logical();

    // if the advance is 0, use the max width
    const int this_width = glyph.advance ? glyph.advance : char_width(false);
    const float bg_width = this_width * density_mult;
    const float bg_height = char_height(false) * density_mult;
    const float pos_sx = x + glyph.offset * density_mult;

    GLWPrim bg_rect(pos_sx, y, pos_sx + bg_width, y + bg_height);
    bg_rect.set_col(bg_col);
    buf.add_primitive(bg_rect);

    store(buf, x, y, ch, fg_col);
}

/**
 * Find the (max) width of a character, in device or logical pixels.
 *
 * This will round up if a font uses logically fractional advances! It is
 * better to use max_width or string_width if you need multiple characters.
 */
unsigned int FTFontWrapper::char_width(bool logical) const
{
    return max_width(1, logical);
}

/**
 * Find the (max) height of a character, in device or logical pixels.
 *
 * This will round up if a font uses logically fractional advances! It is
 * better to use max_height or string_height if you need multiple characters.
 */
unsigned int FTFontWrapper::char_height(bool logical) const
{
    return max_height(1, logical);
}

/**
 * Find the (max) width of `length` characters, in device or logical pixels.
 *
 * This will take into account sub-logical-pixel advances. For non-fixed-width
 * fonts use string_width.
 */
unsigned int FTFontWrapper::max_width(int length, bool logical) const
{
    const int device_length = m_max_advance.x * length;

    return logical ? display_density.device_to_logical(device_length)
                   : device_length;
}

/**
 * Find the (max) height of `length` lines, in device or logical pixels.
 *
 * This will take into account sub-logical-pixel advances. For non-fixed-width
 * fonts use string_height.
 */
unsigned int FTFontWrapper::max_height(int length, bool logical) const
{
    const int device_height = m_max_advance.y * length;

    return logical ? display_density.device_to_logical(device_height)
                   : device_height;
}


const GenericTexture *FTFontWrapper::font_tex() const
{
    return &m_tex;
}

#endif // USE_FT
#endif // USE_TILE_LOCAL
