Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
# font-view
A simple tool to preview all glyphs in the font using `ttf-parser`, `freetype` and `harfbuzz`.
## Build
```sh
# build ttf-parser C API first
cargo build --release --manifest-path ../../c-api/Cargo.toml
# build only with ttf-parser support
qmake
make
# or build with freetype support
qmake DEFINES+=WITH_FREETYPE
make
# or build with harfbuzz support
# note that harfbuzz should be built from sources using meson,
# because we're using an unstable API
#
# build harfbuzz first
meson builddir -Dexperimental_api=true --buildtype release
ninja -C builddir
# build font-view
qmake DEFINES+=WITH_HARFBUZZ HARFBUZZ_SRC=/path/to/harfbuzz-master/
make
# or with all
qmake DEFINES+=WITH_FREETYPE DEFINES+=WITH_HARFBUZZ HARFBUZZ_SRC=/path/to/harfbuzz-master/
make
```

View File

@@ -0,0 +1,50 @@
QT += widgets
CONFIG += c++14
CONFIG(release, debug|release): LIBS += -L$$PWD/../../c-api/target/release/ -lttfparser
else:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../c-api/target/debug/ -lttfparser
INCLUDEPATH += $$PWD/../../c-api
DEPENDPATH += $$PWD/../../c-api
SOURCES += \
glyphsview.cpp \
main.cpp \
mainwindow.cpp \
ttfparserfont.cpp
HEADERS += \
glyph.h \
glyphsview.h \
mainwindow.h \
ttfparserfont.h
FORMS += \
mainwindow.ui
macx {
QT_CONFIG -= no-pkg-config
PKG_CONFIG = /opt/homebrew/bin/pkg-config
}
# qmake DEFINES+=WITH_FREETYPE
contains(DEFINES, WITH_FREETYPE) {
SOURCES += freetypefont.cpp
HEADERS += freetypefont.h
CONFIG += link_pkgconfig
PKGCONFIG += freetype2
}
# qmake DEFINES+=WITH_HARFBUZZ HARFBUZZ_SRC=/path/to/harfbuzz-master/
contains(DEFINES, WITH_HARFBUZZ) {
DEFINES += HB_EXPERIMENTAL_API
SOURCES += harfbuzzfont.cpp
HEADERS += harfbuzzfont.h
# harfbuzz should be built with meson
LIBS += -L$$HARFBUZZ_SRC/builddir/src/ -lharfbuzz
INCLUDEPATH += $$HARFBUZZ_SRC/src
}

View File

@@ -0,0 +1,175 @@
// Based on https://www.freetype.org/freetype2/docs/tutorial/example5.cpp
#include <QDebug>
#include "freetypefont.h"
const FT_Fixed MULTIPLIER_FT = 65536L;
const char* getErrorMessage(FT_Error err)
{
#undef __FTERRORS_H__
#define FT_ERRORDEF( e, v, s ) case e: return s;
#define FT_ERROR_START_LIST switch (err) {
#define FT_ERROR_END_LIST }
#include FT_ERRORS_H
return "(Unknown error)";
}
struct Outliner
{
static int moveToFn(const FT_Vector *to, void *user)
{
auto self = static_cast<Outliner *>(user);
self->path.moveTo(to->x, to->y);
return 0;
}
static int lineToFn(const FT_Vector *to, void *user)
{
auto self = static_cast<Outliner *>(user);
self->path.lineTo(to->x, to->y);
return 0;
}
static int quadToFn(const FT_Vector *control, const FT_Vector *to, void *user)
{
auto self = static_cast<Outliner *>(user);
self->path.quadTo(control->x, control->y, to->x, to->y);
return 0;
}
static int cubicToFn(const FT_Vector *controlOne,
const FT_Vector *controlTwo,
const FT_Vector *to,
void *user)
{
auto self = static_cast<Outliner *>(user);
self->path.cubicTo(controlOne->x, controlOne->y, controlTwo->x, controlTwo->y, to->x, to->y);
return 0;
}
QPainterPath path;
};
FreeTypeFont::FreeTypeFont()
{
const auto error = FT_Init_FreeType(&m_ftLibrary);
if (error) {
throw tr("Failed to init FreeType.\n%1").arg(getErrorMessage(error));
}
}
FreeTypeFont::~FreeTypeFont()
{
if (m_ftFace) {
FT_Done_Face(m_ftFace);
}
FT_Done_FreeType(m_ftLibrary);
}
void FreeTypeFont::open(const QString &path, const quint32 index)
{
if (isOpen()) {
FT_Done_Face(m_ftFace);
m_ftFace = nullptr;
}
const auto utf8Path = path.toUtf8();
const auto error = FT_New_Face(m_ftLibrary, utf8Path.constData(), index, &m_ftFace);
if (error) {
throw tr("Failed to open a font.\n%1").arg(getErrorMessage(error));
}
}
bool FreeTypeFont::isOpen() const
{
return m_ftFace != nullptr;
}
FontInfo FreeTypeFont::fontInfo() const
{
if (!isOpen()) {
throw tr("Font is not loaded.");
}
return FontInfo {
m_ftFace->ascender,
m_ftFace->height,
(quint16)m_ftFace->num_glyphs, // TrueType allows only u16.
};
}
Glyph FreeTypeFont::outline(const quint16 gid) const
{
if (!isOpen()) {
throw tr("Font is not loaded.");
}
auto error = FT_Load_Glyph(m_ftFace, gid, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP);
if (error) {
throw tr("Failed to load a glyph.\n%1").arg(getErrorMessage(error));
}
Outliner outliner;
FT_Outline_Funcs funcs;
funcs.move_to = outliner.moveToFn;
funcs.line_to = outliner.lineToFn;
funcs.conic_to = outliner.quadToFn;
funcs.cubic_to = outliner.cubicToFn;
funcs.shift = 0;
funcs.delta = 0;
auto slot = m_ftFace->glyph;
auto &outline = slot->outline;
// Flip outline around x-axis.
FT_Matrix matrix;
matrix.xx = 1L * MULTIPLIER_FT;
matrix.xy = 0L * MULTIPLIER_FT;
matrix.yx = 0L * MULTIPLIER_FT;
matrix.yy = -1L * MULTIPLIER_FT;
FT_Outline_Transform(&outline, &matrix);
FT_BBox bboxFt;
FT_Outline_Get_BBox(&outline, &bboxFt);
const QRect bbox(
(int)bboxFt.xMin,
(int)bboxFt.yMin,
(int)bboxFt.xMax - (int)bboxFt.xMin,
(int)bboxFt.yMax - (int)bboxFt.yMin
);
error = FT_Outline_Decompose(&outline, &funcs, &outliner);
if (error) {
throw tr("Failed to outline a glyph.\n%1").arg(getErrorMessage(error));
}
outliner.path.setFillRule(Qt::WindingFill);
return Glyph {
outliner.path,
bbox,
};
}
void FreeTypeFont::setVariations(const QVector<Variation> &variations)
{
if (!isOpen()) {
throw tr("Font is not loaded.");
}
QVector<FT_Fixed> ftCoords;
for (const auto &var : variations) {
ftCoords << var.value * MULTIPLIER_FT;
}
const auto error = FT_Set_Var_Design_Coordinates(m_ftFace, ftCoords.size(), ftCoords.data());
if (error) {
throw tr("Failed to set variation.\n%1").arg(getErrorMessage(error));
}
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_OUTLINE_H
#include FT_BBOX_H
#include FT_MULTIPLE_MASTERS_H
#include <QCoreApplication>
#include "glyph.h"
class FreeTypeFont
{
Q_DECLARE_TR_FUNCTIONS(FreeTypeFont)
public:
FreeTypeFont();
~FreeTypeFont();
void open(const QString &path, const quint32 index = 0);
bool isOpen() const;
FontInfo fontInfo() const;
Glyph outline(const quint16 gid) const;
void setVariations(const QVector<Variation> &variations);
private:
FT_Library m_ftLibrary = nullptr;
FT_Face m_ftFace = nullptr;
};

View File

@@ -0,0 +1,48 @@
#pragma once
#include <QPainterPath>
struct Tag
{
Tag(quint32 v) : value(v) {}
QString toString() const
{
QString s;
s.append(QChar(value >> 24 & 0xff));
s.append(QChar(value >> 16 & 0xff));
s.append(QChar(value >> 8 & 0xff));
s.append(QChar(value >> 0 & 0xff));
return s;
}
quint32 value;
};
struct FontInfo
{
qint16 ascender = 0;
qint16 height = 1000;
quint16 numberOfGlyphs = 0;
};
struct Glyph
{
QPainterPath outline;
QRect bbox;
};
struct VariationInfo
{
QString name;
Tag tag;
qint16 min = 0;
qint16 def = 0;
qint16 max = 0;
};
struct Variation
{
Tag tag;
int value;
};

View File

@@ -0,0 +1,286 @@
#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)));
}

View File

@@ -0,0 +1,61 @@
#pragma once
#include <QAbstractScrollArea>
#include <QStaticText>
#include "glyph.h"
class GlyphsView : public QAbstractScrollArea
{
Q_OBJECT
public:
explicit GlyphsView(QWidget *parent = nullptr);
void setFontInfo(const FontInfo &fi);
void setGlyph(int idx, const Glyph &glyph);
#ifdef WITH_FREETYPE
void setFTGlyph(int idx, const Glyph &glyph);
#endif
#ifdef WITH_HARFBUZZ
void setHBGlyph(int idx, const Glyph &glyph);
#endif
void setDrawBboxes(const bool flag);
void setDrawGlyphs(const bool flag);
void setDrawFTGlyphs(const bool flag);
void setDrawHBGlyphs(const bool flag);
private:
void paintEvent(QPaintEvent *);
void drawGrid(QPainter &p, const double cellHeight);
void mousePressEvent(QMouseEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
void wheelEvent(QWheelEvent *e);
void resizeEvent(QResizeEvent *);
void updateScrollBars();
private:
QPoint m_mousePressPos;
QPoint m_origOffset;
double m_scale = 0.05;
bool m_drawBboxes = true;
bool m_drawGlyphs = true;
bool m_drawFTGlyphs = false;
bool m_drawHBGlyphs = false;
FontInfo m_fontInfo;
QVector<Glyph> m_glyphs;
#ifdef WITH_FREETYPE
QVector<Glyph> m_ftGlyphs;
#endif
#ifdef WITH_HARFBUZZ
QVector<Glyph> m_hbGlyphs;
#endif
QVector<QStaticText> m_indexes;
};

View File

@@ -0,0 +1,160 @@
#include <QTransform>
#include <QDebug>
#include <hb.h>
#include "harfbuzzfont.h"
struct Outliner
{
static void moveToFn(hb_position_t to_x, hb_position_t to_y, Outliner &outliner)
{
outliner.path.moveTo(to_x, to_y);
}
static void lineToFn(hb_position_t to_x, hb_position_t to_y, Outliner &outliner)
{
outliner.path.lineTo(to_x, to_y);
}
static void quadToFn(hb_position_t control_x, hb_position_t control_y,
hb_position_t to_x, hb_position_t to_y,
Outliner &outliner)
{
outliner.path.quadTo(control_x, control_y, to_x, to_y);
}
static void cubicToFn(hb_position_t control1_x, hb_position_t control1_y,
hb_position_t control2_x, hb_position_t control2_y,
hb_position_t to_x, hb_position_t to_y,
Outliner &outliner)
{
outliner.path.cubicTo(control1_x, control1_y, control2_x, control2_y, to_x, to_y);
}
static void closePathFn(Outliner &outliner)
{
outliner.path.closeSubpath();
}
QPainterPath path;
};
HarfBuzzFont::HarfBuzzFont()
{
}
HarfBuzzFont::~HarfBuzzFont()
{
reset();
}
void HarfBuzzFont::open(const QString &path, const quint32 index)
{
if (isOpen()) {
reset();
}
const auto utf8Path = path.toUtf8();
hb_blob_t *blob = hb_blob_create_from_file(utf8Path.constData());
if (!blob) {
throw tr("Failed to open a font.");
}
hb_face_t *face = hb_face_create(blob, index);
if (!face) {
throw tr("Failed to open a font.");
}
hb_font_t *font = hb_font_create(face);
if (!font) {
throw tr("Failed to open a font.");
}
m_blob = blob;
m_face = face;
m_font = font;
}
bool HarfBuzzFont::isOpen() const
{
return m_font != nullptr;
}
Glyph HarfBuzzFont::outline(const quint16 gid) const
{
if (!isOpen()) {
throw tr("Font is not loaded.");
}
Outliner outliner;
hb_draw_funcs_t *funcs = hb_draw_funcs_create();
hb_draw_funcs_set_move_to_func(funcs, (hb_draw_move_to_func_t)outliner.moveToFn);
hb_draw_funcs_set_line_to_func(funcs, (hb_draw_line_to_func_t)outliner.lineToFn);
hb_draw_funcs_set_quadratic_to_func(funcs, (hb_draw_quadratic_to_func_t)outliner.quadToFn);
hb_draw_funcs_set_cubic_to_func(funcs, (hb_draw_cubic_to_func_t)outliner.cubicToFn);
hb_draw_funcs_set_close_path_func(funcs, (hb_draw_close_path_func_t)outliner.closePathFn);
if (!hb_font_draw_glyph(m_font, gid, funcs, &outliner)) {
throw tr("Failed to outline a glyph %1.").arg(gid);
}
hb_draw_funcs_destroy(funcs);
hb_glyph_extents_t extents = {0, 0, 0, 0};
if (!hb_font_get_glyph_extents(m_font, gid, &extents)) {
throw tr("Failed to query glyph extents.");
}
const QRect bbox(
extents.x_bearing,
-extents.y_bearing,
extents.width,
-extents.height
);
// Flip outline around x-axis.
QTransform ts(1, 0, 0, -1, 0, 0);
outliner.path = ts.map(outliner.path);
outliner.path.setFillRule(Qt::WindingFill);
return Glyph {
outliner.path,
bbox,
};
}
void HarfBuzzFont::setVariations(const QVector<Variation> &variations)
{
if (!isOpen()) {
throw tr("Font is not loaded.");
}
QVector<hb_variation_t> hbVariations;
for (const auto &var : variations) {
hbVariations.append({ var.tag.value, (float)var.value });
}
hb_font_set_variations(m_font, hbVariations.constData(), hbVariations.size());
}
void HarfBuzzFont::reset()
{
if (m_blob) {
hb_blob_destroy(m_blob);
m_blob = nullptr;
}
if (m_font) {
hb_font_destroy(m_font);
m_font = nullptr;
}
if (m_face) {
hb_face_destroy(m_face);
m_face = nullptr;
}
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <QCoreApplication>
#include "glyph.h"
struct hb_blob_t;
struct hb_face_t;
struct hb_font_t;
struct hb_draw_funcs_t;
class HarfBuzzFont
{
Q_DECLARE_TR_FUNCTIONS(HarfBuzzFont)
public:
HarfBuzzFont();
~HarfBuzzFont();
void open(const QString &path, const quint32 index = 0);
bool isOpen() const;
Glyph outline(const quint16 gid) const;
void setVariations(const QVector<Variation> &variations);
private:
void reset();
private:
hb_blob_t *m_blob = nullptr;
hb_face_t *m_face = nullptr;
hb_font_t *m_font = nullptr;
};

View File

@@ -0,0 +1,13 @@
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

View File

@@ -0,0 +1,162 @@
#include <QElapsedTimer>
#include <QSlider>
#include <QTimer>
#include <QMessageBox>
#include <QDebug>
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
#ifndef WITH_FREETYPE
ui->chBoxDrawFreeType->hide();
#endif
#ifndef WITH_HARFBUZZ
ui->chBoxDrawHarfBuzz->hide();
#endif
if (qApp->arguments().size() == 2) {
QTimer::singleShot(1, this, [this](){
loadFont(qApp->arguments().at(1));
});
}
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::loadFont(const QString &path)
{
try {
m_ttfpFont.open(path);
const auto variations = m_ttfpFont.loadVariations();
if (!variations.isEmpty()) {
ui->widgetVariations->show();
// Clear layout.
while (ui->layVariations->count()) {
delete ui->layVariations->takeAt(0);
}
m_variationSliders.clear();
QVector<Variation> newVariations;
for (const auto &var : variations) {
auto hlay = new QHBoxLayout();
hlay->setContentsMargins(0, 0, 0, 0);
hlay->addWidget(new QLabel(var.name));
auto slider = new QSlider(Qt::Horizontal);
slider->setMinimum(var.min);
slider->setMaximum(var.max);
slider->setValue(var.def);
hlay->addWidget(slider);
ui->layVariations->addLayout(hlay);
m_variationSliders.append({ slider, var.tag });
connect(slider, &QSlider::valueChanged, this, &MainWindow::onVariationChanged);
newVariations.append({ var.tag, var.def });
}
m_ttfpFont.setVariations(newVariations);
} else {
ui->widgetVariations->hide();
}
#ifdef WITH_FREETYPE
m_ftFont.open(path);
#endif
#ifdef WITH_HARFBUZZ
m_hbFont.open(path);
#endif
ui->glyphsView->setFontInfo(m_ttfpFont.fontInfo());
reloadGlyphs();
} catch (const QString &err) {
QMessageBox::warning(this, tr("Error"), err);
}
}
void MainWindow::reloadGlyphs()
{
const auto fi = m_ttfpFont.fontInfo();
for (quint16 i = 0; i < fi.numberOfGlyphs; ++i) {
try {
ui->glyphsView->setGlyph(i, m_ttfpFont.outline(i));
} catch (...) {
}
#ifdef WITH_FREETYPE
try {
ui->glyphsView->setFTGlyph(i, m_ftFont.outline(i));
} catch (...) {
}
#endif
#ifdef WITH_HARFBUZZ
try {
ui->glyphsView->setHBGlyph(i, m_hbFont.outline(i));
} catch (...) {
}
#endif
}
ui->glyphsView->viewport()->update();
}
void MainWindow::onVariationChanged()
{
try {
QVector<Variation> variations;
for (auto var : m_variationSliders) {
variations.append({ var.tag, var.slider->value() });
}
#ifdef WITH_FREETYPE
m_ftFont.setVariations(variations);
#endif
#ifdef WITH_HARFBUZZ
m_hbFont.setVariations(variations);
#endif
m_ttfpFont.setVariations(variations);
reloadGlyphs();
} catch (const QString &err) {
QMessageBox::warning(this, tr("Error"), err);
}
}
void MainWindow::on_chBoxDrawBboxes_stateChanged(int flag)
{
ui->glyphsView->setDrawBboxes(flag);
}
void MainWindow::on_chBoxDrawTtfParser_stateChanged(int flag)
{
ui->glyphsView->setDrawGlyphs(flag);
}
void MainWindow::on_chBoxDrawFreeType_stateChanged(int flag)
{
ui->glyphsView->setDrawFTGlyphs(flag);
}
void MainWindow::on_chBoxDrawHarfBuzz_stateChanged(int flag)
{
ui->glyphsView->setDrawHBGlyphs(flag);
}

View File

@@ -0,0 +1,54 @@
#pragma once
#include <QMainWindow>
#ifdef WITH_FREETYPE
#include "freetypefont.h"
#endif
#ifdef WITH_FREETYPE
#include "harfbuzzfont.h"
#endif
#include "ttfparserfont.h"
namespace Ui { class MainWindow; }
class QSlider;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
void loadFont(const QString &path);
void reloadGlyphs();
void onVariationChanged();
private slots:
void on_chBoxDrawBboxes_stateChanged(int flag);
void on_chBoxDrawTtfParser_stateChanged(int flag);
void on_chBoxDrawFreeType_stateChanged(int flag);
void on_chBoxDrawHarfBuzz_stateChanged(int flag);
private:
struct VariationSlider
{
QSlider *slider;
Tag tag;
};
Ui::MainWindow * const ui;
QVector<VariationSlider> m_variationSliders;
TtfParserFont m_ttfpFont;
#ifdef WITH_FREETYPE
FreeTypeFont m_ftFont;
#endif
#ifdef WITH_HARFBUZZ
HarfBuzzFont m_hbFont;
#endif
};

View File

@@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>FontView</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="GlyphsView" name="glyphsView" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="sidebar" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="chBoxDrawTtfParser">
<property name="text">
<string>Draw ttf-parser</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="chBoxDrawFreeType">
<property name="text">
<string>Draw FreeType</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="chBoxDrawHarfBuzz">
<property name="text">
<string>Draw HarfBuzz</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="chBoxDrawBboxes">
<property name="text">
<string>Draw bboxes</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widgetVariations" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Variations:</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="layVariations"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>32</height>
</rect>
</property>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>GlyphsView</class>
<extends>QWidget</extends>
<header>glyphsview.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,165 @@
#include <QTransform>
#include <QFile>
#include <QDebug>
#include "ttfparserfont.h"
struct Outliner
{
static void moveToFn(float x, float y, void *user)
{
auto self = static_cast<Outliner *>(user);
self->path.moveTo(double(x), double(y));
}
static void lineToFn(float x, float y, void *user)
{
auto self = static_cast<Outliner *>(user);
self->path.lineTo(double(x), double(y));
}
static void quadToFn(float x1, float y1, float x, float y, void *user)
{
auto self = static_cast<Outliner *>(user);
self->path.quadTo(double(x1), double(y1), double(x), double(y));
}
static void curveToFn(float x1, float y1, float x2, float y2, float x, float y, void *user)
{
auto self = static_cast<Outliner *>(user);
self->path.cubicTo(double(x1), double(y1), double(x2), double(y2), double(x), double(y));
}
static void closePathFn(void *user)
{
auto self = static_cast<Outliner *>(user);
self->path.closeSubpath();
}
QPainterPath path;
};
TtfParserFont::TtfParserFont()
{
}
void TtfParserFont::open(const QString &path, const quint32 index)
{
if (isOpen()) {
m_face.reset();
}
QFile file(path);
file.open(QFile::ReadOnly);
m_fontData = file.readAll();
m_face.reset((ttfp_face*)malloc(ttfp_face_size_of()));
const auto res = ttfp_face_init(m_fontData.constData(), m_fontData.size(), index, m_face.get());
if (!res) {
throw tr("Failed to open a font.");
}
}
bool TtfParserFont::isOpen() const
{
return m_face != nullptr;
}
FontInfo TtfParserFont::fontInfo() const
{
if (!isOpen()) {
throw tr("Font is not loaded.");
}
return FontInfo {
ttfp_get_ascender(m_face.get()),
ttfp_get_height(m_face.get()),
ttfp_get_number_of_glyphs(m_face.get()),
};
}
Glyph TtfParserFont::outline(const quint16 gid) const
{
if (!isOpen()) {
throw tr("Font is not loaded.");
}
Outliner outliner;
ttfp_outline_builder builder;
builder.move_to = outliner.moveToFn;
builder.line_to = outliner.lineToFn;
builder.quad_to = outliner.quadToFn;
builder.curve_to = outliner.curveToFn;
builder.close_path = outliner.closePathFn;
ttfp_rect rawBbox;
const bool ok = ttfp_outline_glyph(
m_face.get(),
builder,
&outliner,
gid,
&rawBbox
);
if (!ok) {
return Glyph {
QPainterPath(),
QRect(),
};
}
const QRect bbox(
rawBbox.x_min,
-rawBbox.y_max,
rawBbox.x_max - rawBbox.x_min,
rawBbox.y_max - rawBbox.y_min
);
// Flip outline around x-axis.
QTransform ts(1, 0, 0, -1, 0, 0);
outliner.path = ts.map(outliner.path);
outliner.path.setFillRule(Qt::WindingFill);
return Glyph {
outliner.path,
bbox,
};
}
QVector<VariationInfo> TtfParserFont::loadVariations()
{
if (!isOpen()) {
throw tr("Font is not loaded.");
}
QVector<VariationInfo> variations;
for (uint16_t i = 0; i < ttfp_get_variation_axes_count(m_face.get()); ++i) {
ttfp_variation_axis axis;
ttfp_get_variation_axis(m_face.get(), i, &axis);
variations.append(VariationInfo {
Tag(axis.tag).toString(),
{ static_cast<quint32>(axis.tag) },
static_cast<qint16>(axis.min_value),
static_cast<qint16>(axis.def_value),
static_cast<qint16>(axis.max_value),
});
}
return variations;
}
void TtfParserFont::setVariations(const QVector<Variation> &variations)
{
if (!isOpen()) {
throw tr("Font is not loaded.");
}
for (const auto &variation : variations) {
ttfp_set_variation(m_face.get(), variation.tag.value, variation.value);
}
}

View File

@@ -0,0 +1,35 @@
#pragma once
#include <QCoreApplication>
#include <QPainterPath>
#include <memory>
#define TTFP_VARIABLE_FONTS
#include <ttfparser.h>
#include "glyph.h"
class TtfParserFont
{
Q_DECLARE_TR_FUNCTIONS(TtfParserFont)
public:
TtfParserFont();
void open(const QString &path, const quint32 index = 0);
bool isOpen() const;
FontInfo fontInfo() const;
Glyph outline(const quint16 gid) const;
QVector<VariationInfo> loadVariations();
void setVariations(const QVector<Variation> &variations);
private:
struct FreeCPtr
{ void operator()(void* x) { free(x); } };
QByteArray m_fontData;
std::unique_ptr<ttfp_face, FreeCPtr> m_face;
};