ttf-parser 0.15.2

A high-level, safe, zero-allocation TrueType font parser.
Documentation
#include <QDebug>
#include <QMouseEvent>
#include <QPainter>
#include <QScrollBar>

#include <cmath>

#include "glyphsview.h"

static const int COLUMNS_COUNT = 100;

GlyphsView::GlyphsView(QWidget *parent) : QAbstractScrollArea(parent)
{
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
}

void GlyphsView::setFontInfo(const FontInfo &fi)
{
    m_fontInfo = fi;
    m_glyphs.resize(fi.numberOfGlyphs);
#ifdef WITH_FREETYPE
    m_ftGlyphs.resize(fi.numberOfGlyphs);
#endif
#ifdef WITH_HARFBUZZ
    m_hbGlyphs.resize(fi.numberOfGlyphs);
#endif

    m_indexes.clear();
    for (int i = 0; i < fi.numberOfGlyphs; ++i) {
        QStaticText text(QString::number(i));
        text.prepare();
        m_indexes << text;
    }

    updateScrollBars();
    horizontalScrollBar()->setValue(0);
    verticalScrollBar()->setValue(0);
}

void GlyphsView::setGlyph(int idx, const Glyph &glyph)
{
    m_glyphs.replace(idx, glyph);
}

#ifdef WITH_FREETYPE
void GlyphsView::setFTGlyph(int idx, const Glyph &glyph)
{
    m_ftGlyphs.replace(idx, glyph);
}
#endif

#ifdef WITH_HARFBUZZ
void GlyphsView::setHBGlyph(int idx, const Glyph &glyph)
{
    m_hbGlyphs.replace(idx, glyph);
}
#endif

void GlyphsView::setDrawBboxes(const bool flag)
{
    m_drawBboxes = flag;
    viewport()->update();
}

void GlyphsView::setDrawGlyphs(const bool flag)
{
    m_drawGlyphs = flag;
    viewport()->update();
}

void GlyphsView::setDrawFTGlyphs(const bool flag)
{
    m_drawFTGlyphs = flag;
    viewport()->update();
}

void GlyphsView::setDrawHBGlyphs(const bool flag)
{
    m_drawHBGlyphs = flag;
    viewport()->update();
}

void GlyphsView::paintEvent(QPaintEvent *)
{
    QPainter p(viewport());
    p.translate(-horizontalScrollBar()->value(), -verticalScrollBar()->value());

    const double cellHeight = m_fontInfo.height * m_scale;
    drawGrid(p, cellHeight);

    p.setRenderHint(QPainter::Antialiasing);

    {
        auto font = p.font();
        font.setPointSize(10);
        p.setFont(font);
    }

    int x = 0;
    int y = m_fontInfo.ascender;
    int num_y = m_fontInfo.height;
    for (int i = 0; i < m_glyphs.size(); ++i) {
        // Text rendering is the slowest part, so we are using preprocessed text.
        p.setPen(palette().color(QPalette::Text));
        p.drawStaticText(
            qRound(x * m_scale + 1),
            qRound(num_y * m_scale - p.fontMetrics().ascent() - 2),
            m_indexes.at(i)
        );

        if (m_drawGlyphs) {
            p.save();

            const int dx = qRound((m_fontInfo.height - m_glyphs.at(i).bbox.width()) / 2.0)
                - m_glyphs.at(i).bbox.x();

            p.scale(m_scale, m_scale);
            p.translate(x + dx, y);

            if (m_drawBboxes) {
                p.setPen(QPen(Qt::darkGreen, 0.5 / m_scale));
                p.setBrush(Qt::NoBrush);
                p.drawRect(m_glyphs.at(i).bbox);
            }

            p.setPen(Qt::NoPen);
            p.setPen(Qt::NoPen);
            if (m_drawFTGlyphs || m_drawHBGlyphs) {
                p.setBrush(Qt::red);
            } else {
                p.setBrush(palette().color(QPalette::Text));
            }

            p.drawPath(m_glyphs.at(i).outline);

            p.restore();
        }

#ifdef WITH_HARFBUZZ
        if (m_drawHBGlyphs) {
            p.save();

            const int dx = qRound((m_fontInfo.height - m_hbGlyphs.at(i).bbox.width()) / 2.0)
                - m_hbGlyphs.at(i).bbox.x();

            p.scale(m_scale, m_scale);
            p.translate(x + dx, y);

            if (m_drawBboxes) {
                p.setPen(QPen(Qt::darkGreen, 0.5 / m_scale));
                p.setBrush(Qt::NoBrush);
                p.drawRect(m_hbGlyphs.at(i).bbox);
            }

            p.setPen(Qt::NoPen);
            if (m_drawFTGlyphs) {
                p.setBrush(Qt::blue);
            } else {
                p.setBrush(palette().color(QPalette::Text));
            }

            p.drawPath(m_hbGlyphs.at(i).outline);

            p.restore();
        }
#endif

#ifdef WITH_FREETYPE
        if (m_drawFTGlyphs) {
            p.save();

            const int dx = qRound((m_fontInfo.height - m_ftGlyphs.at(i).bbox.width()) / 2.0)
                - m_ftGlyphs.at(i).bbox.x();

            p.scale(m_scale, m_scale);
            p.translate(x + dx, y);

            if (m_drawBboxes) {
                p.setPen(QPen(Qt::darkGreen, 0.5 / m_scale));
                p.setBrush(Qt::NoBrush);
                p.drawRect(m_ftGlyphs.at(i).bbox);
            }

            p.setPen(Qt::NoPen);
            p.setBrush(palette().color(QPalette::Text));

            if (m_drawGlyphs || m_drawHBGlyphs) {
                p.setBrush(palette().color(QPalette::Base));
            }

            p.drawPath(m_ftGlyphs.at(i).outline);

            p.restore();
        }
#endif

        x += m_fontInfo.height;
        if (i > 0 && (i + 1) % COLUMNS_COUNT == 0) {
            x = 0;
            y += m_fontInfo.height;
            num_y += m_fontInfo.height;
        }
    }
}

void GlyphsView::drawGrid(QPainter &p, const double cellHeight)
{
    p.setRenderHint(QPainter::Antialiasing, false);
    p.setPen(QPen(palette().color(QPalette::Text), 0.25));
    p.setBrush(Qt::NoBrush);

    const int rows = qRound(floor(m_glyphs.size() / COLUMNS_COUNT)) + 1;
    const auto maxH = qMin(rows * cellHeight, (double)horizontalScrollBar()->maximum());

    double x = cellHeight;
    for (int c = 1; c < COLUMNS_COUNT; ++c) {
        p.drawLine(QLineF(x, 0, x, maxH));
        x += cellHeight;
    }

    double y = cellHeight;
    for (int r = 1; r <= rows; ++r) {
        p.drawLine(QLineF(0, y, horizontalScrollBar()->maximum() + viewport()->width(), y));
        y += cellHeight;
    }
}

void GlyphsView::mousePressEvent(QMouseEvent *e)
{
    if (e->button() & Qt::LeftButton) {
        m_mousePressPos = e->pos();
        m_origOffset = QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value());
    }
}

void GlyphsView::mouseMoveEvent(QMouseEvent *e)
{
    if (m_mousePressPos.isNull()) {
        return;
    }

    const auto diff = m_mousePressPos - e->pos();
    horizontalScrollBar()->setValue(m_origOffset.x() + diff.x());
    verticalScrollBar()->setValue(m_origOffset.y() + diff.y());
}

void GlyphsView::mouseReleaseEvent(QMouseEvent *)
{
    m_mousePressPos = QPoint();
    m_origOffset = QPoint();
}

void GlyphsView::wheelEvent(QWheelEvent *e)
{
    e->accept();

    if (e->angleDelta().y() > 0) {
        m_scale += 0.01;
    } else {
        m_scale -= 0.01;
    }

    m_scale = qBound(0.03, m_scale, 1.0);

    updateScrollBars();
    viewport()->update();
}

void GlyphsView::resizeEvent(QResizeEvent *e)
{
    QAbstractScrollArea::resizeEvent(e);
    updateScrollBars();
}

void GlyphsView::updateScrollBars()
{
    const double cellHeight = m_fontInfo.height * m_scale;
    const int rows = qRound(floor(m_glyphs.size() / COLUMNS_COUNT)) + 1;
    const auto w = COLUMNS_COUNT * cellHeight - viewport()->width();
    const auto h = rows * cellHeight - viewport()->height();
    horizontalScrollBar()->setMinimum(0);
    verticalScrollBar()->setMinimum(0);
    horizontalScrollBar()->setMaximum(qMax(0, qRound(w)));
    verticalScrollBar()->setMaximum(qMax(0, qRound(h)));
}