// Copyright © 2016 Canonical Ltd.
// Author: Loïc Molinari <loic.molinari@canonical.com>
//
// This file is part of Lomiri UI Toolkit.
//
// Lomiri UI Toolkit is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; version 3.
//
// Lomiri UI Toolkit is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Lomiri UI Toolkit. If not, see <http://www.gnu.org/licenses/>.

// Generates the texture atlas containing printing ASCII codes of a monospaced
// font in different sizes. The output is a header file containing a structure
// to be accessed by the bitmap text implementation.

#include <QtCore/QFile>
#include <QtCore/QTextStream>
#include <QtGui/QGuiApplication>
#include <QtGui/QPainter>
#include <QtGui/QImage>

// Input data.
// FIXME(loicm) Make that command line arguments?
const char* fileName = "bitmaptextfont_p.h";
const char* fontFamily = "Ubuntu Mono";  // Must be monospace.
const int fontPixelSizeMin = 12;  // Must be an even number.
const int fontPixelSizeMax = 20;  // Must be an even number, higher than fontPixelSizeMin.
const bool outline = false;
const bool strongOutline = false;
const bool saveImage = false;
const quint32 backgroundColor = 0xcc000000;  // AABBGGRR (premultiplied).
const quint32 fontColor = 0xffffffff;  // AARRGGBB.
const quint32 outlineColor = 0xff000000;  // AARRGGBB.

// Several drivers prefer power-of-two or (at least) multiples of 32 for
// performance reasons.
// FIXME(loicm) Compute texture size programmatically (based on selected font).
const int textureWidth = 736;
const int textureHeight = 224;
const int textureSize = textureWidth * textureHeight;

// From 32 to 126 (without the non-printing control characters).
const QString asciiCodes[2] = {
    " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO",
    "PQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
};

int main(int argc, char* argv[])
{
    // Prevents slow texture layout, as well as making sure the code writing
    // texture data in the header stores everything correctly (loop stores
    // pixels 4 by 4).
    Q_STATIC_ASSERT(((textureSize) % 4) == 0);
    // Prevents incorrect font sizes.
    Q_STATIC_ASSERT((fontPixelSizeMin & 1) != 1);
    Q_STATIC_ASSERT((fontPixelSizeMax & 1) != 1);
    Q_STATIC_ASSERT(fontPixelSizeMax > fontPixelSizeMin);

    QGuiApplication application(argc, argv);

    // Create and start filling header file.
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        qWarning("Can't create file \'%s\'", fileName);
        return 1;
    }
    QTextStream fileOut(&file);
    fileOut << "// Copyright 2016 Canonical Ltd.\n"
            << "// This file has been automatically generated by bitmaptextbuilder."
            << "\n\n"
            << "static const struct {\n"
            << "    int fontCount;            // Number of fonts in the texture.\n"
            << "    struct {\n"
            << "        short int size;       // Size.\n"
            << "        short int y;          // Position y of the first font line.\n"
            << "        short int width;      // Width of each character.\n"
            << "        short int height;     // Height of each character.\n"
            << "    } font[" << (fontPixelSizeMax - fontPixelSizeMin + 2) / 2 << "];\n"
            << "    short int textureWidth;   // Width of the texture.\n"
            << "    short int textureHeight;  // Height of the texture.\n"
            << "    const unsigned char textureData["
            << textureSize * 4  + 1 << "];"  // Don't forget string terminator.
            << "  // Data (premultiplied 32-bit RGBA).\n"
            << "} g_bitmapTextFont = {\n"
            << "    " << (fontPixelSizeMax - fontPixelSizeMin + 2) / 2 << ",\n"
            << "    {\n";

    // Create texture data.
    quint32* data = new quint32 [textureSize];
    for (int i = 0; i < textureSize; ++i) {
        data[i] = backgroundColor;
    }

    // Setup painter to render the fonts in the texture data.
    QPainter painter;
    QImage image(reinterpret_cast<uchar*>(data), textureWidth, textureHeight,
                 QImage::Format_RGBA8888_Premultiplied);
    painter.begin(&image);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    QFont font(fontFamily);
    font.setHintingPreference(QFont::PreferVerticalHinting);
    font.setStyleStrategy(QFont::ForceIntegerMetrics);
    font.setBold(true);
    if (outline) {
        font.setLetterSpacing(QFont::AbsoluteSpacing, 2);
    }

    for (int i = fontPixelSizeMin, y = 0; i <= fontPixelSizeMax; i += 2) {
        font.setPixelSize(i);
        painter.setFont(font);
        const QFontMetrics metrics(font);
        const int initialY = y;
        for (int j = 0; j < 2; j++) {
            if (outline) {
                // Not using QPainterPath for quality reasons.
                y += metrics.ascent() + 2;
                painter.setPen(QColor(outlineColor));
                painter.drawText(1, y - 2, asciiCodes[j]);
                painter.drawText(0, y - 1, asciiCodes[j]);
                painter.drawText(2, y - 1, asciiCodes[j]);
                painter.drawText(1, y, asciiCodes[j]);
                if (strongOutline) {
                    painter.drawText(0, y - 2, asciiCodes[j]);
                    painter.drawText(2, y - 2, asciiCodes[j]);
                    painter.drawText(0, y, asciiCodes[j]);
                    painter.drawText(2, y, asciiCodes[j]);
                }
                painter.setPen(QColor(fontColor));
                painter.drawText(1, y - 1, asciiCodes[j]);
            } else {
                y += metrics.ascent();
                painter.setPen(QColor(fontColor));
                painter.drawText(0, y, asciiCodes[j]);
            }
            y += metrics.descent() + 1;  // Add 1 for the base line (see QFontMetrics docs).
        }

        // Write font info.
        const int w = metrics.maxWidth() + (outline ? 2 : 0);
        const int h = metrics.ascent() + metrics.descent() + (outline ? 3 : 1);
        fileOut << "        { " << i << ", " << initialY << ", " << w << ", " << h
                << ((i != fontPixelSizeMax) ? " },\n" : " }\n");
    }
    painter.end();

    // Write texture data.
    fileOut << "    },\n"
            << "    " << textureWidth << ", " << textureHeight << ",\n";
    fileOut.setIntegerBase(16);
    fileOut.setFieldWidth(2);
    fileOut.setPadChar('0');
    for (int i = 0; i < textureSize; i += 4) {
        const quint32 pixel[4] = { data[i], data[i+1], data[i+2], data[i+3] };
        fileOut << "    \""
                << "\\x" << (pixel[0] & 0xff)
                << "\\x" << ((pixel[0] >> 8) & 0xff)
                << "\\x" << ((pixel[0] >> 16) & 0xff)
                << "\\x" << ((pixel[0] >> 24) & 0xff)
                << "\\x" << (pixel[1] & 0xff)
                << "\\x" << ((pixel[1] >> 8) & 0xff)
                << "\\x" << ((pixel[1] >> 16) & 0xff)
                << "\\x" << ((pixel[1] >> 24) & 0xff)
                << "\\x" << (pixel[2] & 0xff)
                << "\\x" << ((pixel[2] >> 8) & 0xff)
                << "\\x" << ((pixel[2] >> 16) & 0xff)
                << "\\x" << ((pixel[2] >> 24) & 0xff)
                << "\\x" << (pixel[3] & 0xff)
                << "\\x" << ((pixel[3] >> 8) & 0xff)
                << "\\x" << ((pixel[3] >> 16) & 0xff)
                << "\\x" << ((pixel[3] >> 24) & 0xff);
        fileOut.setFieldWidth(1);
        fileOut << "\"\n";
        fileOut.setFieldWidth(2);
    }
    fileOut << "};\n";

    if (saveImage) {
        image.save("bitmaptextfont.png");
    }

    delete [] data;
    return 0;
}
