5#include "ui_mainwindow.h"
8#include <QtCore/qdir.h>
9#include <QtCore/qdatastream.h>
10#include <QtCore/qmath.h>
11#include <QtCore/qendian.h>
12#include <QtCore/qbuffer.h>
13#include <QtGui/qdesktopservices.h>
14#include <QtGui/qrawfont.h>
15#include <QtWidgets/qmessagebox.h>
16#include <QtWidgets/qlabel.h>
17#include <QtWidgets/qprogressbar.h>
18#include <QtWidgets/qfiledialog.h>
19#include <QtWidgets/qinputdialog.h>
21#include <QtCore/private/qunicodetables_p.h>
22#include <QtGui/private/qdistancefield_p.h>
23#include <QtQuick/private/qsgareaallocator_p.h>
24#include <QtQuick/private/qsgadaptationlayer_p.h>
30 const int qtVersion = QT_VERSION;
32 QTextStream(&url) <<
"https://doc.qt.io/qt-" << (qtVersion >> 16) <<
"/qtdistancefieldgenerator-index.html";
33 QDesktopServices::openUrl(QUrl(url));
41 , m_statusBarLabel(
nullptr)
42 , m_statusBarProgressBar(
nullptr)
45 ui->lvGlyphs->setModel(m_model);
47 ui->actionHelp->setShortcut(QKeySequence::HelpContents);
49 m_statusBarLabel =
new QLabel(
this);
50 m_statusBarLabel->setText(tr(
"Ready"));
51 ui->statusbar->addPermanentWidget(m_statusBarLabel);
53 m_statusBarProgressBar =
new QProgressBar(
this);
54 ui->statusbar->addPermanentWidget(m_statusBarProgressBar);
55 m_statusBarProgressBar->setVisible(
false);
57 if (m_settings.contains(QStringLiteral(
"fontDirectory")))
58 m_fontDir = m_settings.value(QStringLiteral(
"fontDirectory")).toString();
60 m_fontDir = QDir::currentPath();
62 qRegisterMetaType<glyph_t>(
"glyph_t");
63 qRegisterMetaType<QPainterPath>(
"QPainterPath");
65 restoreGeometry(m_settings.value(QStringLiteral(
"geometry")).toByteArray());
79 m_fontDir = QFileInfo(path).absolutePath();
80 m_settings.setValue(QStringLiteral(
"fontDirectory"), m_fontDir);
82 ui->lwUnicodeRanges->clear();
83 ui->lwUnicodeRanges->setDisabled(
true);
84 ui->action_Save->setDisabled(
true);
85 ui->action_Save_as->setDisabled(
true);
86 ui->tbSave->setDisabled(
true);
87 ui->action_Open->setDisabled(
true);
88 m_model->setFont(path);
93 m_settings.setValue(QStringLiteral(
"geometry"), saveGeometry());
98 connect(ui->action_Open, &QAction::triggered,
this, &MainWindow::openFont);
99 connect(ui->actionE_xit, &QAction::triggered, qApp, &QApplication::quit);
100 connect(ui->action_Save, &QAction::triggered,
this, &MainWindow::save);
101 connect(ui->action_Save_as, &QAction::triggered,
this, &MainWindow::saveAs);
102 connect(ui->tbSave, &QToolButton::clicked,
this, &MainWindow::save);
103 connect(ui->tbSelectAll, &QToolButton::clicked,
this, &MainWindow::selectAll);
104 connect(ui->actionSelect_all, &QAction::triggered,
this, &MainWindow::selectAll);
105 connect(ui->actionSelect_string, &QAction::triggered,
this, &MainWindow::selectString);
106 connect(ui->actionHelp, &QAction::triggered,
this, openHelp);
107 connect(ui->actionAbout_App, &QAction::triggered,
this, &MainWindow::about);
108 connect(ui->actionAbout_Qt, &QAction::triggered,
this, [
this]() {
109 QMessageBox::aboutQt(
this);
111 connect(ui->lwUnicodeRanges, &QListWidget::itemSelectionChanged,
this, &MainWindow::updateUnicodeRanges);
113 connect(ui->lvGlyphs->selectionModel(),
114 &QItemSelectionModel::selectionChanged,
116 &MainWindow::updateSelection);
117 connect(m_model, &DistanceFieldModel::startGeneration,
this, &MainWindow::startProgressBar);
121 connect(m_model, &DistanceFieldModel::error,
this, &MainWindow::displayError);
126 QString fileName = QFileDialog::getSaveFileName(
this,
127 tr(
"Save distance field-enriched file"),
129 tr(
"Font files (*.ttf *.otf);;All files (*)"));
130 if (!fileName.isEmpty()) {
131 m_fileName = fileName;
132 m_fontDir = QFileInfo(m_fileName).absolutePath();
133 m_settings.setValue(QStringLiteral(
"fontDirectory"), m_fontDir);
202#define PAD_BUFFER(buffer, size)
204 int paddingNeed = size % 4
;
205 if (paddingNeed > 0
) {
206 const char padding[3
] = { 0
, 0
, 0
};
207 buffer.write(padding, 4
- paddingNeed);
211#define ALIGN_OFFSET(offset)
213 int paddingNeed = offset % 4
;
215 offset += 4
- paddingNeed;
218#define TO_FIXED_POINT(value)
219 ((int)(value*qreal(65536
)))
223 QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes();
224 if (list.isEmpty()) {
225 QMessageBox::warning(
this,
226 tr(
"Nothing to save"),
227 tr(
"No glyphs selected for saving."),
232 if (m_fileName.isEmpty()) {
237 QFile inFile(m_fontFile);
238 if (!inFile.open(QIODevice::ReadOnly)) {
239 QMessageBox::warning(
this,
240 tr(
"Can't read original font"),
241 tr(
"Cannot open '%s' for reading. The original font file must remain in place until the new file has been saved.").arg(m_fontFile),
247 quint32 headOffset = 0;
250 QBuffer outBuffer(&output);
251 outBuffer.open(QIODevice::WriteOnly);
253 uchar *inData = inFile.map(0, inFile.size());
254 if (inData ==
nullptr) {
255 QMessageBox::warning(
this,
256 tr(
"Can't map input file"),
257 tr(
"Unable to memory map input file '%s'.").arg(m_fontFile));
261 uchar *end = inData + inFile.size();
263 QMessageBox::warning(
this,
264 tr(
"Can't read font directory"),
265 tr(
"Input file seems to be invalid or corrupt."),
272 quint16 numTables = qFromBigEndian(fontDirectoryHeader.numTables) + 1;
273 fontDirectoryHeader.numTables = qToBigEndian(numTables);
275 quint16 searchRange = qFromBigEndian(fontDirectoryHeader.searchRange);
276 if (searchRange / 16 < numTables) {
277 quint16 pot = (searchRange / 16) * 2;
278 searchRange = pot * 16;
279 fontDirectoryHeader.searchRange = qToBigEndian(searchRange);
280 fontDirectoryHeader.rangeShift = qToBigEndian(numTables * 16 - searchRange);
282 quint16 entrySelector = 0;
287 fontDirectoryHeader.entrySelector = qToBigEndian(entrySelector);
291 outBuffer.write(
reinterpret_cast<
char *>(&fontDirectoryHeader),
294 QVarLengthArray<std::pair<quint32, quint32>> offsetLengthPairs;
295 offsetLengthPairs.reserve(numTables - 1);
299 quint32 currentOffset =
sizeof(FontDirectoryHeader) +
sizeof(TableRecord) * numTables;
300 for (
int i = 0; i < numTables - 1; ++i) {
303 quint32 originalOffset = qFromBigEndian(offsetTable->offset);
304 quint32 length = qFromBigEndian(offsetTable->length);
305 offsetLengthPairs.append({originalOffset, length});
306 if (offsetTable->tag == qFromBigEndian(QFont::Tag(
"head").value()))
307 headOffset = currentOffset;
310 memcpy(&newTableRecord, offsetTable,
sizeof(
TableRecord));
311 newTableRecord.offset = qToBigEndian(currentOffset);
312 outBuffer.write(
reinterpret_cast<
char *>(&newTableRecord),
sizeof(
TableRecord));
315 currentOffset += length;
318 if (headOffset == 0) {
319 QMessageBox::warning(
this,
320 tr(
"Invalid font file"),
321 tr(
"Font file does not have 'head' table."),
326 QByteArray qtdf = createSfntTable();
334 qtdfRecord.offset = qToBigEndian(currentOffset);
335 qtdfRecord.length = qToBigEndian(qtdf.size());
336 qtdfRecord.tag = qFromBigEndian(QFont::Tag(
"qtdf").value());
337 quint32 checkSum = 0;
338 const quint32 *start =
reinterpret_cast<
const quint32 *>(qtdf.constData());
339 const quint32 *end =
reinterpret_cast<
const quint32 *>(qtdf.constData() + qtdf.size());
341 checkSum += *(start++);
342 qtdfRecord.checkSum = qToBigEndian(checkSum);
344 outBuffer.write(
reinterpret_cast<
char *>(&qtdfRecord),
349 for (
const std::pair<quint32, quint32> &offsetLengthPair : offsetLengthPairs) {
351 outBuffer.write(
reinterpret_cast<
char *>(inData + offsetLengthPair.first),
352 offsetLengthPair.second);
356 outBuffer.write(qtdf);
360 Head *head =
reinterpret_cast<
Head *>(output.data() + headOffset);
361 head->checkSumAdjustment = 0;
363 quint32 checkSum = 0;
364 const quint32 *start =
reinterpret_cast<
const quint32 *>(output.constData());
365 const quint32 *end =
reinterpret_cast<
const quint32 *>(output.constData() + output.size());
367 checkSum += *(start++);
369 head->checkSumAdjustment = qToBigEndian(0xB1B0AFBA - checkSum);
371 QFile outFile(m_fileName);
372 if (!outFile.open(QIODevice::WriteOnly)) {
373 QMessageBox::warning(
this,
374 tr(
"Can't write to file"),
375 tr(
"Cannot open the file '%s' for writing").arg(m_fileName),
380 outFile.write(output);
385 QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes();
386 Q_ASSERT(!list.isEmpty());
390 QBuffer buffer(&ret);
391 buffer.open(QIODevice::WriteOnly);
394 header.majorVersion = 5;
395 header.minorVersion = 12;
396 header.pixelSize = qToBigEndian(quint16(qRound(m_model->pixelSize())));
398 const quint8 padding = 2;
399 qreal scaleFactor = qreal(1) / QT_DISTANCEFIELD_SCALE(m_model->doubleGlyphResolution());
403 quint32 textureSize = ui->sbMaximumTextureSize->value();
409 textureSize -= quint32(qCeil(m_model->pixelSize() * scaleFactor) + radius * 2 + padding * 2);
410 header.textureSize = qToBigEndian(textureSize);
412 header.padding = padding;
414 header.numGlyphs = qToBigEndian(quint32(list.size()));
415 buffer.write(
reinterpret_cast<
char *>(&header),
419 QList<QRect> allocatedAreaPerTexture;
422 QSGDistanceFieldGlyphCache::TexCoord texCoord;
427 QList<GlyphData> glyphDatas;
428 glyphDatas.resize(m_model->rowCount());
430 int textureCount = 0;
433 QTransform scaleDown;
434 scaleDown.scale(scaleFactor, scaleFactor);
437 bool foundOptimalSize =
false;
438 while (!foundOptimalSize) {
439 allocatedAreaPerTexture.clear();
441 QSGAreaAllocator allocator(QSize(textureSize, textureSize * (++textureCount)));
444 for (i = 0; i < list.size(); ++i) {
445 int glyphIndex = list.at(i).row();
446 GlyphData &glyphData = glyphDatas[glyphIndex];
448 QPainterPath path = m_model->path(glyphIndex);
449 glyphData.boundingRect = scaleDown.mapRect(path.boundingRect());
450 int glyphWidth = qCeil(glyphData.boundingRect.width()) + radius * 2;
451 int glyphHeight = qCeil(glyphData.boundingRect.height()) + radius * 2;
453 glyphData.glyphSize = QSize(glyphWidth + padding * 2, glyphHeight + padding * 2);
455 if (glyphData.glyphSize.width() > qint32(textureSize)
456 || glyphData.glyphSize.height() > qint32(textureSize)) {
457 QMessageBox::warning(
this,
458 tr(
"Glyph too large for texture"),
459 tr(
"Glyph %1 is too large to fit in texture of size %2.")
460 .arg(glyphIndex).arg(textureSize));
464 QRect rect = allocator.allocate(glyphData.glyphSize);
468 glyphData.textureIndex = rect.y() / textureSize;
469 while (glyphData.textureIndex >= allocatedAreaPerTexture.size())
470 allocatedAreaPerTexture.append(QRect(0, 0, 1, 1));
472 allocatedAreaPerTexture[glyphData.textureIndex] |= QRect(rect.x(),
473 rect.y() % textureSize,
479 glyphData.texCoord.x = rect.x() + padding;
480 glyphData.texCoord.y = rect.y() % textureSize + padding;
481 glyphData.texCoord.width = glyphData.boundingRect.width();
482 glyphData.texCoord.height = glyphData.boundingRect.height();
484 glyphDatas.append(glyphData);
487 foundOptimalSize = i == list.size();
488 if (foundOptimalSize)
489 buffer.write(allocator.serialize());
494 QList<QDistanceField> textures;
495 textures.resize(textureCount);
497 for (
int textureIndex = 0; textureIndex < textureCount; ++textureIndex) {
498 textures[textureIndex] = QDistanceField(allocatedAreaPerTexture.at(textureIndex).width(),
499 allocatedAreaPerTexture.at(textureIndex).height());
501 QRect rect = allocatedAreaPerTexture.at(textureIndex);
504 record.allocatedX = qToBigEndian(rect.x());
505 record.allocatedY = qToBigEndian(rect.y());
506 record.allocatedWidth = qToBigEndian(rect.width());
507 record.allocatedHeight = qToBigEndian(rect.height());
508 record.padding = padding;
509 buffer.write(
reinterpret_cast<
char *>(&record),
514 for (
int i = 0; i < list.size(); ++i) {
515 int glyphIndex = list.at(i).row();
516 QImage image = m_model->distanceField(glyphIndex);
518 const GlyphData &glyphData = glyphDatas.at(glyphIndex);
521 glyphRecord.glyphIndex = qToBigEndian(glyphIndex);
522 glyphRecord.textureOffsetX = qToBigEndian(
TO_FIXED_POINT(glyphData.texCoord.x));
523 glyphRecord.textureOffsetY = qToBigEndian(
TO_FIXED_POINT(glyphData.texCoord.y));
524 glyphRecord.textureWidth = qToBigEndian(
TO_FIXED_POINT(glyphData.texCoord.width));
525 glyphRecord.textureHeight = qToBigEndian(
TO_FIXED_POINT(glyphData.texCoord.height));
526 glyphRecord.xMargin = qToBigEndian(
TO_FIXED_POINT(glyphData.texCoord.xMargin));
527 glyphRecord.yMargin = qToBigEndian(
TO_FIXED_POINT(glyphData.texCoord.yMargin));
528 glyphRecord.boundingRectX = qToBigEndian(
TO_FIXED_POINT(glyphData.boundingRect.x()));
529 glyphRecord.boundingRectY = qToBigEndian(
TO_FIXED_POINT(glyphData.boundingRect.y()));
530 glyphRecord.boundingRectWidth = qToBigEndian(
TO_FIXED_POINT(glyphData.boundingRect.width()));
531 glyphRecord.boundingRectHeight = qToBigEndian(
TO_FIXED_POINT(glyphData.boundingRect.height()));
532 glyphRecord.textureIndex = qToBigEndian(quint16(glyphData.textureIndex));
533 buffer.write(
reinterpret_cast<
char *>(&glyphRecord),
sizeof(
QtdfGlyphRecord));
535 int expectedWidth = qCeil(glyphData.texCoord.width + glyphData.texCoord.xMargin * 2);
536 image = image.copy(-padding, -padding,
537 expectedWidth + padding * 2,
538 image.height() + padding * 2);
540 uchar *inBits = image.scanLine(0);
541 uchar *outBits = textures[glyphData.textureIndex].scanLine(
int(glyphData.texCoord.y) - padding)
542 +
int(glyphData.texCoord.x) - padding;
543 for (
int y = 0; y < image.height(); ++y) {
544 memcpy(outBits, inBits, image.width());
545 inBits += image.bytesPerLine();
546 outBits += textures[glyphData.textureIndex].width();
551 for (
int i = 0; i < textures.size(); ++i) {
552 const QDistanceField &texture = textures.at(i);
553 const QRect &allocatedArea = allocatedAreaPerTexture.at(i);
554 buffer.write(
reinterpret_cast<
const char *>(texture.constBits()),
555 allocatedArea.width() * allocatedArea.height());
566 Q_ASSERT(!m_fileName.isEmpty());
568 QFile file(m_fileName);
569 if (file.open(QIODevice::WriteOnly)) {
572 QMessageBox::warning(
this,
573 tr(
"Can't open file for writing"),
574 tr(
"Unable to open file '%1' for writing").arg(m_fileName),
581 QString fileName = QFileDialog::getOpenFileName(
this,
582 tr(
"Open font file"),
584 tr(
"Fonts (*.ttf *.otf);;All files (*)"));
585 if (!fileName.isEmpty())
591 m_statusBarProgressBar->setValue(m_statusBarProgressBar->value() + 1);
595void MainWindow::startProgressBar(quint16 glyphCount)
597 ui->action_Open->setDisabled(
false);
598 m_statusBarLabel->setText(tr(
"Generating"));
599 m_statusBarProgressBar->setMaximum(glyphCount);
600 m_statusBarProgressBar->setMinimum(0);
601 m_statusBarProgressBar->setValue(0);
602 m_statusBarProgressBar->setVisible(
true);
607 m_statusBarLabel->setText(tr(
"Ready"));
608 m_statusBarProgressBar->setVisible(
false);
613 QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes();
614 if (list.size() == ui->lvGlyphs->model()->rowCount())
615 ui->lvGlyphs->clearSelection();
617 ui->lvGlyphs->selectAll();
622 QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes();
624 if (list.size() == ui->lvGlyphs->model()->rowCount())
625 label = tr(
"Deselect &All");
627 label = tr(
"Select &All");
629 ui->tbSelectAll->setText(label);
630 ui->actionSelect_all->setText(label);
632 if (m_model !=
nullptr && ui->lwUnicodeRanges->count() > 0) {
634 disconnect(ui->lwUnicodeRanges, &QListWidget::itemSelectionChanged,
this, &MainWindow::updateUnicodeRanges);
636 QSet<
int> selectedGlyphIndexes;
637 for (
const QModelIndex &modelIndex : list)
638 selectedGlyphIndexes.insert(modelIndex.row());
640 QList<DistanceFieldModel::UnicodeRange> unicodeRanges = m_model->unicodeRanges();
641 std::sort(unicodeRanges.begin(), unicodeRanges.end());
643 Q_ASSERT(ui->lwUnicodeRanges->count() == unicodeRanges.size());
644 for (
int i = 0; i < unicodeRanges.size(); ++i) {
645 DistanceFieldModel::UnicodeRange unicodeRange = unicodeRanges.at(i);
646 QListWidgetItem *item = ui->lwUnicodeRanges->item(i);
648 QList<glyph_t> glyphIndexes = m_model->glyphIndexesForUnicodeRange(unicodeRange);
649 Q_ASSERT(!glyphIndexes.isEmpty());
651 item->setSelected(
true);
652 for (glyph_t glyphIndex : glyphIndexes) {
653 if (!selectedGlyphIndexes.contains(glyphIndex)) {
654 item->setSelected(
false);
660 connect(ui->lwUnicodeRanges, &QListWidget::itemSelectionChanged,
this, &MainWindow::updateUnicodeRanges);
666 if (m_model ==
nullptr)
669 disconnect(ui->lvGlyphs->selectionModel(),
670 &QItemSelectionModel::selectionChanged,
672 &MainWindow::updateSelection);
674 QItemSelection selectedItems;
676 for (
int i = 0; i < ui->lwUnicodeRanges->count(); ++i) {
677 QListWidgetItem *item = ui->lwUnicodeRanges->item(i);
678 if (item->isSelected()) {
679 DistanceFieldModel::UnicodeRange unicodeRange = item->data(Qt::UserRole).value<DistanceFieldModel::UnicodeRange>();
680 QList<glyph_t> glyphIndexes = m_model->glyphIndexesForUnicodeRange(unicodeRange);
682 for (glyph_t glyphIndex : glyphIndexes) {
683 QModelIndex index = m_model->index(glyphIndex);
684 selectedItems.select(index, index);
689 ui->lvGlyphs->selectionModel()->clearSelection();
690 if (!selectedItems.isEmpty())
691 ui->lvGlyphs->selectionModel()->select(selectedItems, QItemSelectionModel::Select);
693 connect(ui->lvGlyphs->selectionModel(),
694 &QItemSelectionModel::selectionChanged,
696 &MainWindow::updateSelection);
701 QList<DistanceFieldModel::UnicodeRange> unicodeRanges = m_model->unicodeRanges();
702 std::sort(unicodeRanges.begin(), unicodeRanges.end());
704 for (DistanceFieldModel::UnicodeRange unicodeRange : unicodeRanges) {
705 QString name = m_model->nameForUnicodeRange(unicodeRange);
706 QListWidgetItem *item =
new QListWidgetItem(name, ui->lwUnicodeRanges);
707 item->setData(Qt::UserRole, unicodeRange);
710 ui->lwUnicodeRanges->setDisabled(
false);
711 ui->action_Save->setDisabled(
false);
712 ui->action_Save_as->setDisabled(
false);
713 ui->tbSave->setDisabled(
false);
716void MainWindow::displayError(
const QString &errorString)
718 QMessageBox::warning(
this, tr(
"Error when parsing font file"), errorString, QMessageBox::Ok);
723 QString s = QInputDialog::getText(
this,
724 tr(
"Select glyphs for string"),
725 tr(
"String to parse:"));
727 QList<uint> ucs4String = s.toUcs4();
728 for (uint ucs4 : ucs4String) {
729 glyph_t glyph = m_model->glyphIndexForUcs4(ucs4);
731 ui->lvGlyphs->selectionModel()->select(m_model->index(glyph),
732 QItemSelectionModel::Select);
740 QMessageBox *msgBox =
new QMessageBox(
this);
741 msgBox->setAttribute(Qt::WA_DeleteOnClose);
742 msgBox->setWindowTitle(tr(
"About Qt Distance Field Generator"));
743 msgBox->setText(tr(
"<h3>Qt Distance Field Generator</h3>"
744 "<p>Version %1.<br/>"
745 "The Qt Distance Field Generator tool allows "
746 "to prepare a font cache for Qt applications.</p>"
747 "<p>Copyright (C) The Qt Company Ltd. and other contributors.</p>")
748 .arg(QLatin1String(QT_VERSION_STR)));
bool doubleGlyphResolution() const
void distanceFieldGenerated()
void closeEvent(QCloseEvent *event) override
[21]
MainWindow(QWidget *parent=nullptr)
void open(const QString &path)
Combined button and popup list for selecting options.
#define ALIGN_OFFSET(offset)
static QT_BEGIN_NAMESPACE void openHelp()
#define PAD_BUFFER(buffer, size)
#define TO_FIXED_POINT(value)
quint32 checkSumAdjustment
quint32 boundingRectWidth
quint32 boundingRectHeight