#include "Font.h"

#include "Image.h"
#include "Globals.h"
#include "Scroll.h"
#include "Text.h"
#ifdef WIN32
#    include <Windows.h>
#endif
#include "FrameworkMath.h"

using namespace Framework;

// Contents of the Character class from Font.h
// Constructor
Character::Character()
    : ReferenceCounter(),
      size(0, 0),
      alpha(0),
      fontSize(0)
{}

// Destructor
Character::~Character()
{
    if (alpha) delete[] alpha;
}

// non-constant
void Character::NewCharacter(Point& size) // Initialization
{
    this->size = size;
    if (alpha) delete[] alpha;
    alpha = new unsigned char[size.x * size.y];
    ZeroMemory(alpha, size.x * size.y);
}

void Character::setPixel(
    Point& pos, unsigned char alpha) // sets the alpha value of the pixel
{
    this->alpha[pos.x + pos.y * size.x] = alpha;
}

void Character::setPixel(int x, int y, unsigned char alpha)
{
    this->alpha[x + y * size.x] = alpha;
}

void Character::setPixel(int i, unsigned char alpha)
{
    this->alpha[i] = alpha;
}

void Character::setFontSize(int sg) // sets the font size of the character
{
    fontSize = sg;
}

int Character::getFontSize() const
{
    return fontSize;
}

// constant
const Point& Character::getSize() const // returns the character image size
{
    return size;
}

int Character::getWidth() const // character width
{
    return size.x;
}

int Character::getHeight() const // character height
{
    return size.y;
}

unsigned char* Character::getBuff() const // returns the alpha buffer
{
    return alpha;
}

// Contents of the Alphabet class from Font.h
// Constructor
Alphabet::Alphabet()
    : ReferenceCounter(),
      zeichen(new Character*[256]),
      fontSize(12)
{
    for (int i = 0; i < 256; ++i)
        zeichen[i] = 0;
}

// Destructor
Alphabet::~Alphabet()
{
    for (int i = 0; i < 256; ++i)
    {
        if (zeichen[i]) zeichen[i]->release();
    }
    delete[] zeichen;
}

// non-constant
void Alphabet::NeuAlphabet() // Initialization
{
    for (int i = 0; i < 256; ++i)
    {
        if (zeichen[i]) zeichen[i]->release();
    }
    for (int i = 0; i < 256; ++i)
        zeichen[i] = 0;
}

void Alphabet::setCharacter(
    unsigned char i, Character* buchstabe) // sets a character
{
    if (zeichen[i]) zeichen[i]->release();
    zeichen[i] = buchstabe;
    if (zeichen[i])
    {
        zeichen[i]->setFontSize(fontSize);
    }
}

void Alphabet::setFontSize(int gr) // sets the font size
{
    fontSize = gr;
    for (int i = 0; i < 256; ++i)
    {
        if (zeichen[i]) zeichen[i]->setFontSize(gr);
    }
}

// constant
Character* Alphabet::getCharacter(
    unsigned char i) const // returns a character
{
    if (zeichen[i]) return dynamic_cast<Character*>(zeichen[i]->getThis());
    return 0;
}

Character* Alphabet::zCharacter(unsigned char i) const
{
    return zeichen[i];
}

bool Alphabet::hasCharacter(unsigned char b) const
{
    return zeichen[b] != 0;
}

int Alphabet::getFontSize() const // returns the font size
{
    return fontSize;
}

// Contents of the AlphabetArray class from Font.h
// Constructor
AlphabetArray::AlphabetArray()
{
    memset(alphabets, 0, sizeof(Alphabet*) * 256);
}

// non-constant
bool AlphabetArray::addAlphabet(Alphabet* alphabet) // Adds an alphabet
{
    if (alphabets[alphabet->getFontSize()] != 0)
    {
        alphabet->release();
        return 0;
    }
    alphabets[alphabet->getFontSize()] = alphabet;
    return 1;
}

bool AlphabetArray::removeAlphabet(unsigned char sg) // removes an alphabet
{
    if (alphabets[sg])
    {
        alphabets[sg]->release();
        alphabets[sg] = 0;
        return 1;
    }
    return 0;
}

// constant
Alphabet* AlphabetArray::getAlphabet(
    unsigned char sg) const // returns getThis of an alphabet
{
    if (alphabets[sg]) return dynamic_cast<Alphabet*>(alphabets[sg]->getThis());
    return 0;
}

Alphabet* AlphabetArray::zAlphabet(
    unsigned char sg) const // returns an alphabet
{
    return alphabets[sg];
}

// Contents of the Font class from Font.h
// Constructor
Font::Font()
    : ReferenceCounter(),
      alphabetCount(0),
      alphabet(new AlphabetArray())
{}

// Destructor
Font::~Font()
{
    delete alphabet;
}

bool Font::addAlphabet(
    Alphabet* alphabet) // Adds an alphabet to the font
{
    if (this->alphabet->addAlphabet(alphabet))
    {
        ++alphabetCount;
        return true;
    }
    return false;
}

void Font::removeAlphabet(unsigned char sg) // Removes an alphabet
{
    if (alphabet->removeAlphabet(sg)) --alphabetCount;
}

// constant
Alphabet* Font::getAlphabet(unsigned char sg) const
{
    Alphabet* drawAlphabet = alphabet->zAlphabet(sg);
    if (!drawAlphabet)
    {
        for (int i = 0; i < 256; ++i)
        {
            if (sg - i > 0)
            {
                drawAlphabet = alphabet->zAlphabet((unsigned char)(sg - i));
                if (drawAlphabet) break;
            }
            if (sg + i < 256)
            {
                drawAlphabet = alphabet->zAlphabet((unsigned char)(sg + i));
                if (drawAlphabet) break;
            }
        }
    }
    return dynamic_cast<Alphabet*>(drawAlphabet->getThis());
}

Alphabet* Font::zAlphabet(unsigned char sg) const
{
    Alphabet* drawAlphabet = alphabet->zAlphabet(sg);
    if (!drawAlphabet)
    {
        for (int i = 0; i < 256; ++i)
        {
            if (sg - i > 0)
            {
                drawAlphabet = alphabet->zAlphabet((unsigned char)(sg - i));
                if (drawAlphabet) break;
            }
            if (sg + i < 256)
            {
                drawAlphabet = alphabet->zAlphabet((unsigned char)(sg + i));
                if (drawAlphabet) break;
            }
        }
    }
    return drawAlphabet;
}

unsigned char Font::getAlphabetCount()
    const // returns the number of alphabets contained in the font
{
    return alphabetCount;
}

TextRenderer::TextRenderer()
    : TextRenderer(0)
{}

TextRenderer::TextRenderer(Font* font)
    : ReferenceCounter()
{
    s = font;
    lineSpacing = 5;
    charSpacing = 0;
    fontSize = 0;
    setFontSize(12);
}

TextRenderer::~TextRenderer()
{
    if (s) s->release();
}

void TextRenderer::setFontZ(Font* font)
{
    if (s != font)
    {
        if (s) s->release();
        s = font;
        memset(charWidths, 0, sizeof(charWidths));
        memset(charHeights, 0, sizeof(charHeights));
        if (s)
        {
            Alphabet* a = s->zAlphabet((unsigned char)fontSize);
            for (int i = 0; i < 256; i++)
            {
                Character* b = a->zCharacter((unsigned char)i);
                if (b)
                {
                    charWidths[i]
                        = (int)((b->getWidth() / (double)a->getFontSize())
                                    * fontSize
                                + 0.5);
                    charHeights[i]
                        = (int)((b->getHeight() / (double)a->getFontSize())
                                    * fontSize
                                + 0.5);
                }
                else
                {
                    charWidths[i] = 0;
                    charHeights[i] = 0;
                }
            }
        }
    }
    else
    {
        font->release();
    }
}

Font* TextRenderer::getFont()
{
    if (s) return dynamic_cast<Font*>(s->getThis());
    return 0;
}

Font* TextRenderer::zFont()
{
    return s;
}

// Sets the font size to draw with. The font automatically selects the
// appropriate alphabet for drawing
//  sg: The font size
void TextRenderer::setFontSize(int sg)
{
    if (fontSize != sg)
    {
        fontSize = sg;
        memset(charWidths, 0, sizeof(charWidths));
        memset(charHeights, 0, sizeof(charHeights));
        if (s)
        {
            Alphabet* a = s->zAlphabet((unsigned char)fontSize);
            for (int i = 0; i < 256; i++)
            {
                Character* b = a->zCharacter((unsigned char)i);
                if (b)
                {
                    charWidths[i]
                        = (int)((b->getWidth() / (double)a->getFontSize())
                                    * fontSize
                                + 0.5);
                    charHeights[i]
                        = (int)((b->getHeight() / (double)a->getFontSize())
                                    * fontSize
                                + 0.5);
                }
                else
                {
                    charWidths[i] = 0;
                    charHeights[i] = 0;
                }
            }
        }
    }
}

// Sets the line spacing to use for drawing
//  za: The line spacing to the bottom of the line above in pixels
void TextRenderer::setLineSpacing(int za)
{
    lineSpacing = za;
}

// Sets the character spacing to use for drawing
//  za: The character spacing in pixels
void TextRenderer::setCharSpacing(int za)
{
    charSpacing = za;
}

// Inserts line breaks into the text so it is fully displayed at a given width
//  zText: The text to insert line breaks into
//  maxWidth: The width in pixels at which the text should be fully displayed
void TextRenderer::formatText(Text* zTxt, int maxWidth)
{
    int lastPos = -1;
    int x = 0;
    const char* txt = zTxt->getText();
    Text result = txt;
    int len = zTxt->getLength();
    for (int i = 0; i < len; ++i)
    {
        if (txt[i] == ' ')
        {
            lastPos = i;
            x += fontSize / 2 + charSpacing;
            continue;
        }
        if (txt[i] == '\t')
        {
            lastPos = i;
            x += fontSize + charSpacing;
            continue;
        }
        if (txt[i] == '\n')
        {
            x = 0;
            lastPos = -1;
            continue;
        }
        x += getCharWidth(txt[i]) + charSpacing;
        if (x > maxWidth && lastPos > -1)
        {
            result.replace(lastPos, lastPos + 1, "\n");
            x = 0;
            i = lastPos;
            lastPos = -1;
        }
    }
    zTxt->setText(result);
}

// Draws a specific text with cursor and coloring onto an image
// Use (setPosition) and (setDrawFontGroesse) to change position and size
//  x: x position of the first character
//  y: y position of the first character
//  txt: The text to draw
//  zRObj: The image to draw on
//  cpos: The position of the cursor in the text
//  cf: The color of the cursor
//  fbeg: The position of the character in the text where coloring should begin.
//  The text is colored from there to the cursor position
//  ff: The background color of the colored text
//  f: A function called for each character that returns its color
void TextRenderer::renderText(int x,
    int y,
    const char* txt,
    Image& zRObj,
    std::function<int(int, int, int)> f,
    int cpos,
    int cf,
    int fbeg,
    int ff)
{
    if (!s) return;
    if (fbeg == -1) fbeg = cpos;
    int zRObjBr = zRObj.getWidth();
    int zRObjHi = zRObj.getHeight();
    const Point& zRObjOff = zRObj.getDrawOff();
    int beginX = x;
    int zh = getRowHeight();
    if (y + (zh + lineSpacing) * Text(txt).countOf('\n') + zh + zRObjOff.y
            < 0
        || x + zRObjOff.x >= zRObjBr || y + zRObjOff.y >= zRObjHi)
        return;
    bool faerb = 0;
    int len = textLength(txt);
    for (int i = 0; i < len; ++i)
    {
        if (i == fbeg) faerb = !faerb;
        if (i == cpos)
        {
            zRObj.drawLineVAlpha(x, y, zh, cf);
            faerb = !faerb;
        }
        if (txt[i] == ' ')
        {
            if (faerb)
                zRObj.alphaRegion(
                    x, y, fontSize / 2 + charSpacing, zh, ff);
            x += fontSize / 2 + charSpacing;
            continue;
        }
        if (txt[i] == '\t')
        {
            if (faerb)
                zRObj.alphaRegion(x, y, fontSize + charSpacing, zh, ff);
            x += fontSize + charSpacing;
            continue;
        }
        if (txt[i] == '\n')
        {
            y += zh + lineSpacing;
            x = beginX;
            continue;
        }
        renderChar(x, y, txt[i], zRObj, f(x, y, i), 0, faerb, ff);
    }
    if (textLength(txt) == cpos) zRObj.drawLineVAlpha(x, y, zh, cf);
}

// Draws a specific text with cursor and coloring onto an image
// Use (setPosition) and (setDrawFontGroesse) to change position and size
//  x: x position of the first character
//  y: y position of the first character
//  txt: The text to draw
//  zRObj: The image to draw on
//  cpos: The position of the cursor in the text
//  cf: The color of the cursor
//  fbeg: The position of the character in the text where coloring should begin.
//  The text is colored from there to the cursor position
//  ff: The background color of the colored text
//  f: The color in which the text should be drawn
void TextRenderer::renderText(int x,
    int y,
    const char* txt,
    Image& zRObj,
    int f,
    int cpos,
    int cf,
    int fbeg,
    int ff)
{
    return renderText(
        x,
        y,
        txt,
        zRObj,
        [f](int a, int b, int c) { return f; },
        cpos,
        cf,
        fbeg,
        ff);
}

// Draws a specific character with coloring onto an image
// Use (setPosition) and (setDrawFontGroesse) to change position and size
//  x: x position of the first character
//  y: y position of the first character
//  txt: The text to draw
//  zRObj: The image to draw on
//  color: The color in which the text should be drawn
//  underlined: 1 if the text should be underlined
//  selected: 1 if the character should be colored
//  selectedBackgroundColor: The background color of the colored text
void TextRenderer::renderChar(int& x,
    int y,
    char c,
    Image& zRObj,
    int color,
    bool underlined,
    bool selected,
    int selectedBackgroundColor)
{
    if (!s) return;
    Alphabet* a = s->zAlphabet((unsigned char)fontSize);
    if (!a) return;
    Character* b = a->zCharacter(c);
    if (b)
    {
        if (x >= zRObj.getWidth()) return;
        if (zRObj.isAreaDrawable(x, y, getCharWidth(c), getCharHeight(c)))
        {
            if (selected)
            {
                int br = getCharWidth(c) + charSpacing;
                zRObj.alphaRegion(x,
                    y,
                    br,
                    getRowHeight() + lineSpacing,
                    selectedBackgroundColor);
            }
            if (b->getBuff())
            {
                const Point& zRObjGr = zRObj.getDrawGr();
                const Point& zRObjPos = zRObj.getDrawPos();
                const Point& zRObjOff = zRObj.getDrawOff();
                int xp = x + zRObjOff.x, yp = y + zRObjOff.y;
                int xs = xp < zRObjPos.x ? (zRObjPos.x - xp) : 0,
                    ys = yp < zRObjPos.y ? (zRObjPos.y - yp) : 0;
                int br = b->getWidth();
                unsigned char a2 = (unsigned char)(255 - (color >> 24));
                color &= 0x00FFFFFF;
                float xoff = (float)b->getFontSize() / (float)fontSize,
                      yoff = (float)b->getFontSize() / (float)fontSize;
                float x = (float)xs * xoff, y = (float)ys * yoff;
                int maxX = getCharWidth(c), maxY = getCharHeight(c);
                maxX = (xp + maxX) >= zRObjGr.x ? (zRObjGr.x - xp) : maxX;
                maxY = (yp + maxY) >= zRObjGr.y ? (zRObjGr.y - yp) : maxY;
                int a, dx, ygr, ygr2;
                if (zRObj.hasAlpha3D())
                {
                    for (int dy = ys; dy < maxY; ++dy)
                    {
                        ygr2 = (yp + dy) * zRObj.getWidth() + xp;
                        ygr = (int)y * br;
                        for (dx = xs; dx < maxX; ++dx)
                        {
                            a = b->getBuff()[(int)x + ygr] - a2;
                            zRObj.alphaPixel3D(dx + ygr2, color | (a << 24));
                            x += xoff;
                        }
                        x = (float)xs;
                        y += yoff;
                    }
                }
                else
                {
                    for (int dy = ys; dy < maxY; ++dy)
                    {
                        ygr2 = (yp + dy) * zRObj.getWidth() + xp;
                        ygr = (int)y * br;
                        for (dx = xs; dx < maxX; ++dx)
                        {
                            a = b->getBuff()[(int)x + ygr] - a2;
                            zRObj.alphaPixel2D(dx + ygr2, color | (a << 24));
                            x += xoff;
                        }
                        x = (float)xs;
                        y += yoff;
                    }
                }
            }
            if (underlined)
                zRObj.drawLineHAlpha(x - (int)(charSpacing / 2.0 + 0.5),
                    y + getRowHeight() + getCharSpacing() / 2,
                    getCharWidth(c) + (int)(charSpacing / 2.0 + 0.5),
                    0xFF000000 | color);
        }
        x += getCharWidth(c) + charSpacing;
    }
    else if (c == ' ')
    {
        if (selected)
            zRObj.alphaRegion(x,
                y,
                fontSize / 2 + charSpacing,
                getRowHeight() + lineSpacing,
                selectedBackgroundColor);
        if (underlined)
            zRObj.drawLineHAlpha(x - (int)(charSpacing / 2.0 + 0.5),
                y + getRowHeight() + getCharSpacing() / 2,
                fontSize / 2 + charSpacing
                    + (int)(charSpacing / 2.0 + 0.5),
                0xFF000000 | color);
        x += fontSize / 2 + charSpacing;
    }
    else if (c == '\t')
    {
        if (selected)
            zRObj.alphaRegion(x,
                y,
                fontSize + charSpacing,
                getRowHeight() + lineSpacing,
                selectedBackgroundColor);
        if (underlined)
            zRObj.drawLineHAlpha(x - (int)(charSpacing / 2.0 + 0.5),
                y + getRowHeight() + getCharSpacing() / 2,
                fontSize + charSpacing
                    + (int)(charSpacing / 2.0 + 0.5),
                0xFF000000 | color);
        x += fontSize + charSpacing;
    }
}

// Returns the font size used for drawing
int TextRenderer::getFontSize() const
{
    return fontSize;
}

// Returns the character spacing in pixels on the x axis
int TextRenderer::getCharSpacing() const
{
    return charSpacing;
}

// Determines how many pixels are needed to fully display a specific text
//  txt: The text whose width in pixels should be determined
int TextRenderer::getTextWidth(const char* txt) const
{
    int ret = 0;
    int tmp = 0;
    int len = textLength(txt);
    for (int i = 0; i < len; ++i)
    {
        if (txt[i] == '\n')
        {
            if (tmp > ret) ret = tmp;
            tmp = 0;
        }
        else
            tmp += getCharWidth(txt[i]) + charSpacing;
    }
    if (tmp > ret) ret = tmp;
    return ret;
}

// Determines how many pixels are needed to fully display a specific text
//  txt: The text whose height in pixels should be determined
int TextRenderer::getTextHeight(const char* txt) const
{
    int hi = getRowHeight();
    return hi + ((hi + lineSpacing) * Text(txt).countOf('\n'));
}

// Determines how many pixels are needed to fully display a specific character
//  c: The character whose width in pixels should be determined
int TextRenderer::getCharWidth(const char c) const
{
    if (c == '\t')
        return fontSize;
    else if (c == ' ')
        return fontSize / 2;
    else
        return charWidths[(unsigned char)c];
}

int Framework::TextRenderer::getMaxCharWidth() const
{
    int result = 0;
    for (int i = 0; i < 256; i++)
    {
        result = MAX(result, getCharWidth((char)i));
    }
    return result;
}

// Determines how many pixels are needed to fully display a specific character
//  c: The character whose height in pixels should be determined
int TextRenderer::getCharHeight(const char c) const
{
    return charHeights[(unsigned char)c];
}

// Returns the line spacing in pixels to the bottom of the line above
int TextRenderer::getLineSpacing() const
{
    return lineSpacing;
}

// Returns the scaled height needed by a drawn line in pixels
int TextRenderer::getRowHeight() const
{
    int zh = 0;
    for (int i = 0; i < 256; ++i)
    {
        zh = maxInt(getCharHeight((char)i), zh);
    }
    return zh;
}

// Determines the character in the text that the mouse points to
//  txt: The text the mouse points to
//  mouseX: The X position of the mouse in pixels relative to the position of
//  the first character
//  mouseY: The Y position of the mouse in pixels relative to the position of
//  the first character
int TextRenderer::textPos(const char* txt, int mouseX, int mouseY) const
{
    int tx = 0;
    int ty = 0;
    int sh = getRowHeight();
    if (mouseX < 0 || mouseY < 0) return -1;
    int len = textLength(txt);
    for (int i = 0; i < len; ++i)
    {
        if (txt[i] == '\n')
        {
            ty += sh + lineSpacing;
            tx = 0;
            if (mouseY < ty) return i;
        }
        if (txt[i] == '\t')
        {
            tx += fontSize + charSpacing;
        }
        else if (txt[i] == ' ')
        {
            tx += fontSize / 2 + charSpacing;
        }
        else
        {
            tx += getCharWidth(txt[i]) + charSpacing;
        }
        int txpl = getCharWidth(txt[i]) / 2;
        if (mouseX < tx - txpl && mouseY < ty + sh + lineSpacing) return i;
    }
    if (mouseY < ty + sh + lineSpacing) return textLength(txt);
    return -1;
}

EngravedTextRenderer::EngravedTextRenderer()
    : EngravedTextRenderer(0)
{}

EngravedTextRenderer::EngravedTextRenderer(Font* font)
    : TextRenderer(font)
{}

EngravedTextRenderer::~EngravedTextRenderer() {}

// Draws a specific character with coloring onto an image
// Use (setPosition) and (setDrawFontGroesse) to change position and size
//  x: x position of the first character
//  y: y position of the first character
//  txt: The text to draw
//  zRObj: The image to draw on
//  color: The color in which the text should be drawn
//  underlined: 1 if the text should be underlined
//  selected: 1 if the character should be colored
//  selectedBackgroundColor: The background color of the colored text
void EngravedTextRenderer::renderChar(int& x,
    int y,
    char c,
    Image& zRObj,
    int color,
    bool underlined,
    bool selected,
    int selectedBackgroundColor)
{
    if (!s) return;
    Alphabet* a = s->zAlphabet((unsigned char)fontSize);
    Character* b = a->zCharacter(c);
    if (b)
    {
        if (x >= zRObj.getWidth()) return;
        if (zRObj.isAreaDrawable(x, y, getCharWidth(c), getCharHeight(c)))
        {
            if (selected)
            {
                int br = getCharWidth(c) + charSpacing;
                zRObj.alphaRegion(x,
                    y,
                    br,
                    getRowHeight() + lineSpacing,
                    selectedBackgroundColor);
            }
            if (b->getBuff())
            {
                const Point& zRObjGr = zRObj.getDrawGr();
                const Point& zRObjPos = zRObj.getDrawPos();
                const Point& zRObjOff = zRObj.getDrawOff();
                int xp = x + zRObjOff.x, yp = y + zRObjOff.y;
                int xs = xp < zRObjPos.x ? (zRObjPos.x - xp) : 0,
                    ys = yp < zRObjPos.y ? (zRObjPos.y - yp) : 0;
                int br = b->getWidth(), h = b->getHeight();
                color &= 0x00FFFFFF;
                double xoff = (double)b->getFontSize() / (fontSize * 2.0),
                       yoff = (double)b->getFontSize() / (fontSize * 2.0);
                double x = xs * xoff, y = ys * yoff;
                int maxX = getCharWidth(c), maxY = getCharHeight(c);
                maxX = (xp + maxX) >= zRObjGr.x ? (zRObjGr.x - xp) : maxX;
                maxY = (yp + maxY) >= zRObjGr.y ? (zRObjGr.y - yp) : maxY;
                int dx, ygr, ygr2;
                if (zRObj.hasAlpha3D())
                {
                    for (int dy = ys; dy < maxY; ++dy)
                    {
                        ygr2 = (yp + dy) * zRObj.getWidth();
                        ygr = (int)y * br;
                        for (dx = xs; dx < maxX; ++dx)
                        {
                            int f = 0;
                            if (b->getBuff()[(int)x + ygr])
                                f = 0x50000000;
                            else if (((int)(x + xoff) < br
                                         && b->getBuff()[(int)(x + xoff) + ygr])
                                     || ((int)(y - yoff) < h
                                         && b->getBuff()[(int)x
                                                         + (int)(y - yoff) * br]
                                                > 0xF0))
                                f = 0xA0000000;
                            else if (((int)(x - xoff) < br
                                         && b->getBuff()[(int)(x - xoff) + ygr])
                                     || ((int)(y + yoff) < h
                                         && b->getBuff()[(int)x
                                                         + (int)(y + yoff) * br]
                                                > 0xF0))
                            {
                                f = 0xA0FFFFFF;
                            }
                            zRObj.alphaPixel3D(xp + dx + ygr2, f);
                            x += xoff;
                        }
                        x = xs;
                        y += yoff;
                    }
                }
                else
                {
                    for (int dy = ys; dy < maxY; ++dy)
                    {
                        ygr2 = (yp + dy) * zRObj.getWidth();
                        ygr = (int)y * br;
                        for (dx = xs; dx < maxX; ++dx)
                        {
                            int f = 0;
                            if (b->getBuff()[(int)x + ygr])
                                f = 0x50000000;
                            else if (((int)(x + xoff) < br
                                         && b->getBuff()[(int)(x + xoff) + ygr])
                                     || ((int)(y - yoff) < h
                                         && b->getBuff()[(int)x
                                                         + (int)(y - yoff) * br]
                                                > 0xF0))
                                f = 0xA0000000;
                            else if (((int)(x - xoff) < br
                                         && b->getBuff()[(int)(x - xoff) + ygr])
                                     || ((int)(y + yoff) < h
                                         && b->getBuff()[(int)x
                                                         + (int)(y + yoff) * br]
                                                > 0xF0))
                            {
                                f = 0xA0FFFFFF;
                            }
                            zRObj.alphaPixel2D(xp + dx + ygr2, f);
                            x += xoff;
                        }
                        x = xs;
                        y += yoff;
                    }
                }
            }
            if (underlined)
                zRObj.drawLineHAlpha(x - (int)(charSpacing / 2.0 + 0.5),
                    y + getRowHeight() + getCharSpacing() / 2,
                    getCharWidth(c) + (int)(charSpacing / 2.0 + 0.5),
                    0xFF000000 | color);
        }
        x += getCharWidth(c) + charSpacing;
    }
    else if (c == ' ')
    {
        if (selected)
            zRObj.alphaRegion(x,
                y,
                fontSize / 2 + charSpacing,
                getRowHeight() + lineSpacing,
                selectedBackgroundColor);
        if (underlined)
            zRObj.drawLineHAlpha(x - (int)(charSpacing / 2.0 + 0.5),
                y + getRowHeight() + getCharSpacing() / 2,
                fontSize / 2 + charSpacing
                    + (int)(charSpacing / 2.0 + 0.5),
                0xFF000000 | color);
        x += fontSize / 2 + charSpacing;
    }
    else if (c == '\t')
    {
        if (selected)
            zRObj.alphaRegion(x,
                y,
                fontSize + charSpacing,
                getRowHeight() + lineSpacing,
                selectedBackgroundColor);
        if (underlined)
            zRObj.drawLineHAlpha(x - (int)(charSpacing / 2.0 + 0.5),
                y + getRowHeight() + getCharSpacing() / 2,
                fontSize + charSpacing
                    + (int)(charSpacing / 2.0 + 0.5),
                0xFF000000 | color);
        x += fontSize + charSpacing;
    }
}

// Determines how many pixels are needed to fully display a specific character
//  c: The character whose width in pixels should be determined
int EngravedTextRenderer::getCharWidth(const char c) const
{
    if (c == '\t')
        return fontSize;
    else if (c == ' ')
        return fontSize / 2;
    else
        return TextRenderer::getCharWidth(c) * 2;
}

// Determines how many pixels are needed to fully display a specific character
//  c: The character whose height in pixels should be determined
int EngravedTextRenderer::getCharHeight(const char c) const
{
    return TextRenderer::getCharHeight(c) * 2;
}

ItalicTextRenderer::ItalicTextRenderer()
    : ItalicTextRenderer(0)
{}

ItalicTextRenderer::ItalicTextRenderer(Font* font)
    : TextRenderer(font)
{}

ItalicTextRenderer::~ItalicTextRenderer() {}

// Draws a specific character with coloring onto an image
// Use (setPosition) and (setDrawFontGroesse) to change position and size
//  x: x position of the first character
//  y: y position of the first character
//  txt: The text to draw
//  zRObj: The image to draw on
//  color: The color in which the text should be drawn
//  underlined: 1 if the text should be underlined
//  selected: 1 if the character should be colored
//  selectedBackgroundColor: The background color of the colored text
void ItalicTextRenderer::renderChar(int& x,
    int y,
    char c,
    Image& zRObj,
    int color,
    bool underlined,
    bool selected,
    int selectedBackgroundColor)
{
    if (!s) return;
    Alphabet* a = s->zAlphabet((unsigned char)fontSize);
    if (!a) return;
    Character* b = a->zCharacter(c);
    if (b)
    {
        if (x >= zRObj.getWidth()) return;
        if (zRObj.isAreaDrawable(x, y, getCharWidth(c), getCharHeight(c)))
        {
            if (selected)
            {
                int br = getCharWidth(c) + charSpacing;
                zRObj.alphaRegion(x,
                    y,
                    br,
                    getRowHeight() + lineSpacing,
                    selectedBackgroundColor);
            }
            if (b->getBuff())
            {
                const Point& zRObjGr = zRObj.getDrawGr();
                const Point& zRObjPos = zRObj.getDrawPos();
                const Point& zRObjOff = zRObj.getDrawOff();
                int xp = x + zRObjOff.x, yp = y + zRObjOff.y;
                int xStartBuffer = xp < zRObjPos.x ? (zRObjPos.x - xp) : 0,
                    yStartBuffer = yp < zRObjPos.y ? (zRObjPos.y - yp) : 0;
                int bufferWidth = b->getWidth(),
                    bufferHeight = b->getHeight();
                unsigned char colorAlpha = (unsigned char)(255 - (color >> 24));
                color &= 0x00FFFFFF;
                double xStepBuffer
                    = (double)b->getFontSize() / (double)fontSize,
                    yStepBuffer
                    = (double)b->getFontSize() / (double)fontSize;
                double xBuffer = xStartBuffer * xStepBuffer,
                       yBuffer = yStartBuffer * yStepBuffer;
                int charHeight = getCharHeight(c);
                int maxXBuffer = getCharWidth(c), maxYBuffer = charHeight;
                maxXBuffer = (xp + maxXBuffer) >= zRObjGr.x ? (zRObjGr.x - xp)
                                                            : maxXBuffer;
                maxYBuffer = (yp + maxYBuffer) >= zRObjGr.y ? (zRObjGr.y - yp)
                                                            : maxYBuffer;
                std::function<int(int x, int y)> colorF = [charHeight,
                                                              bufferWidth,
                                                              bufferHeight,
                                                              colorAlpha,
                                                              b,
                                                              color](
                                                              int xx, int yy) {
                    xx -= (int)((float)(charHeight - yy) / 4.f + 0.5f);
                    if (xx < 0 || xx >= bufferWidth) return 0x00FFFFFF;
                    int a = b->getBuff()[yy * bufferWidth + xx] - colorAlpha;
                    return color | (a << 24);
                };
                if (zRObj.hasAlpha3D())
                {
                    for (int yS = yStartBuffer; yS < maxYBuffer; ++yS)
                    {
                        int ygr2 = (yp + yS) * zRObj.getWidth();
                        for (int xS = xStartBuffer; xS < maxXBuffer; ++xS)
                        {
                            zRObj.alphaPixel3D(
                                xp + xS + ygr2, colorF((int)xS, (int)yS));
                            xBuffer += xStepBuffer;
                        }
                        xBuffer = xStartBuffer;
                        yBuffer += yStepBuffer;
                    }
                }
                else
                {
                    for (int yS = yStartBuffer; yS < maxYBuffer; ++yS)
                    {
                        int ygr2 = (yp + yS) * zRObj.getWidth();
                        for (int xS = xStartBuffer; xS < maxXBuffer; ++xS)
                        {
                            zRObj.alphaPixel2D(
                                xp + xS + ygr2, colorF((int)xS, (int)yS));
                            xBuffer += xStepBuffer;
                        }
                        xBuffer = xStartBuffer;
                        yBuffer += yStepBuffer;
                    }
                }
            }
            if (underlined)
                zRObj.drawLineHAlpha(x - (int)(charSpacing / 2.0 + 0.5),
                    y + getRowHeight() + getCharSpacing() / 2,
                    getCharWidth(c) + (int)(charSpacing / 2.0 + 0.5),
                    0xFF000000 | color);
        }
        x += getCharWidth(c) + charSpacing;
    }
    else if (c == ' ')
    {
        if (selected)
            zRObj.alphaRegion(x,
                y,
                fontSize / 2 + charSpacing,
                getRowHeight() + lineSpacing,
                selectedBackgroundColor);
        if (underlined)
            zRObj.drawLineHAlpha(x - (int)(charSpacing / 2.0 + 0.5),
                y + getRowHeight() + getCharSpacing() / 2,
                fontSize / 2 + charSpacing
                    + (int)(charSpacing / 2.0 + 0.5),
                0xFF000000 | color);
        x += fontSize / 2 + charSpacing;
    }
    else if (c == '\t')
    {
        if (selected)
            zRObj.alphaRegion(x,
                y,
                fontSize + charSpacing,
                getRowHeight() + lineSpacing,
                selectedBackgroundColor);
        if (underlined)
            zRObj.drawLineHAlpha(x - (int)(charSpacing / 2.0 + 0.5),
                y + getRowHeight() + getCharSpacing() / 2,
                fontSize + charSpacing
                    + (int)(charSpacing / 2.0 + 0.5),
                0xFF000000 | color);
        x += fontSize + charSpacing;
    }
}

// Determines how many pixels are needed to fully display a specific character
//  c: The character whose width in pixels should be determined
int ItalicTextRenderer::getCharWidth(const char c) const
{
    if (c == '\t')
        return fontSize;
    else if (c == ' ')
        return fontSize / 2;
    else
        return (
            int)(TextRenderer::getCharWidth(c) + getCharHeight(c) / 4.0 + 0.5);
}