1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "mainwindow.h"
5#include "ui_mainwindow.h"
6#include "distancefieldmodel.h"
7
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>
20
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>
25
26QT_BEGIN_NAMESPACE
27
28static void openHelp()
29{
30 const int qtVersion = QT_VERSION;
31 QString url;
32 QTextStream(&url) << "https://doc.qt.io/qt-" << (qtVersion >> 16) << "/qtdistancefieldgenerator-index.html";
33 QDesktopServices::openUrl(url: QUrl(url));
34}
35
36MainWindow::MainWindow(QWidget *parent)
37 : QMainWindow(parent)
38 , ui(new Ui::MainWindow)
39 , m_settings(qApp->organizationName(), qApp->applicationName())
40 , m_model(new DistanceFieldModel(this))
41 , m_statusBarLabel(nullptr)
42 , m_statusBarProgressBar(nullptr)
43{
44 ui->setupUi(this);
45 ui->lvGlyphs->setModel(m_model);
46
47 ui->actionHelp->setShortcut(QKeySequence::HelpContents);
48
49 m_statusBarLabel = new QLabel(this);
50 m_statusBarLabel->setText(tr(s: "Ready"));
51 ui->statusbar->addPermanentWidget(m_statusBarLabel);
52
53 m_statusBarProgressBar = new QProgressBar(this);
54 ui->statusbar->addPermanentWidget(m_statusBarProgressBar);
55 m_statusBarProgressBar->setVisible(false);
56
57 if (m_settings.contains(QStringLiteral("fontDirectory")))
58 m_fontDir = m_settings.value(QStringLiteral("fontDirectory")).toString();
59 else
60 m_fontDir = QDir::currentPath();
61
62 qRegisterMetaType<glyph_t>(typeName: "glyph_t");
63 qRegisterMetaType<QPainterPath>(typeName: "QPainterPath");
64
65 restoreGeometry(geometry: m_settings.value(QStringLiteral("geometry")).toByteArray());
66
67 setupConnections();
68}
69
70MainWindow::~MainWindow()
71{
72 delete ui;
73}
74
75void MainWindow::open(const QString &path)
76{
77 m_fileName.clear();
78 m_fontFile = path;
79 m_fontDir = QFileInfo(path).absolutePath();
80 m_settings.setValue(QStringLiteral("fontDirectory"), value: m_fontDir);
81
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);
89}
90
91void MainWindow::closeEvent(QCloseEvent * /*event*/)
92{
93 m_settings.setValue(QStringLiteral("geometry"), value: saveGeometry());
94}
95
96void MainWindow::setupConnections()
97{
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(parent: this);
110 });
111 connect(ui->lwUnicodeRanges, &QListWidget::itemSelectionChanged, this, &MainWindow::updateUnicodeRanges);
112
113 connect(ui->lvGlyphs->selectionModel(),
114 &QItemSelectionModel::selectionChanged,
115 this,
116 &MainWindow::updateSelection);
117 connect(sender: m_model, signal: &DistanceFieldModel::startGeneration, context: this, slot: &MainWindow::startProgressBar);
118 connect(sender: m_model, signal: &DistanceFieldModel::stopGeneration, context: this, slot: &MainWindow::stopProgressBar);
119 connect(sender: m_model, signal: &DistanceFieldModel::distanceFieldGenerated, context: this, slot: &MainWindow::updateProgressBar);
120 connect(sender: m_model, signal: &DistanceFieldModel::stopGeneration, context: this, slot: &MainWindow::populateUnicodeRanges);
121 connect(sender: m_model, signal: &DistanceFieldModel::error, context: this, slot: &MainWindow::displayError);
122}
123
124void MainWindow::saveAs()
125{
126 QString fileName = QFileDialog::getSaveFileName(parent: this,
127 caption: tr(s: "Save distance field-enriched file"),
128 dir: m_fontDir,
129 filter: tr(s: "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"), value: m_fontDir);
134 save();
135 }
136}
137
138
139# pragma pack(1)
140struct FontDirectoryHeader
141{
142 quint32 sfntVersion;
143 quint16 numTables;
144 quint16 searchRange;
145 quint16 entrySelector;
146 quint16 rangeShift;
147};
148
149struct TableRecord
150{
151 quint32 tag;
152 quint32 checkSum;
153 quint32 offset;
154 quint32 length;
155};
156
157struct QtdfHeader
158{
159 quint8 majorVersion;
160 quint8 minorVersion;
161 quint16 pixelSize;
162 quint32 textureSize;
163 quint8 flags;
164 quint8 padding;
165 quint32 numGlyphs;
166};
167
168struct QtdfGlyphRecord
169{
170 quint32 glyphIndex;
171 quint32 textureOffsetX;
172 quint32 textureOffsetY;
173 quint32 textureWidth;
174 quint32 textureHeight;
175 quint32 xMargin;
176 quint32 yMargin;
177 qint32 boundingRectX;
178 qint32 boundingRectY;
179 quint32 boundingRectWidth;
180 quint32 boundingRectHeight;
181 quint16 textureIndex;
182};
183
184struct QtdfTextureRecord
185{
186 quint32 allocatedX;
187 quint32 allocatedY;
188 quint32 allocatedWidth;
189 quint32 allocatedHeight;
190 quint8 padding;
191};
192
193struct Head
194{
195 quint16 majorVersion;
196 quint16 minorVersion;
197 quint32 fontRevision;
198 quint32 checkSumAdjustment;
199};
200# pragma pack()
201
202#define PAD_BUFFER(buffer, size) \
203 { \
204 int paddingNeed = size % 4; \
205 if (paddingNeed > 0) { \
206 const char padding[3] = { 0, 0, 0 }; \
207 buffer.write(padding, 4 - paddingNeed); \
208 } \
209 }
210
211#define ALIGN_OFFSET(offset) \
212 { \
213 int paddingNeed = offset % 4; \
214 if (paddingNeed > 0) \
215 offset += 4 - paddingNeed; \
216 }
217
218#define TO_FIXED_POINT(value) \
219 ((int)(value*qreal(65536)))
220
221void MainWindow::save()
222{
223 QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes();
224 if (list.isEmpty()) {
225 QMessageBox::warning(parent: this,
226 title: tr(s: "Nothing to save"),
227 text: tr(s: "No glyphs selected for saving."),
228 buttons: QMessageBox::Ok);
229 return;
230 }
231
232 if (m_fileName.isEmpty()) {
233 saveAs();
234 return;
235 }
236
237 QFile inFile(m_fontFile);
238 if (!inFile.open(flags: QIODevice::ReadOnly)) {
239 QMessageBox::warning(parent: this,
240 title: tr(s: "Can't read original font"),
241 text: tr(s: "Cannot open '%s' for reading. The original font file must remain in place until the new file has been saved.").arg(a: m_fontFile),
242 buttons: QMessageBox::Ok);
243 return;
244 }
245
246 QByteArray output;
247 quint32 headOffset = 0;
248
249 {
250 QBuffer outBuffer(&output);
251 outBuffer.open(openMode: QIODevice::WriteOnly);
252
253 uchar *inData = inFile.map(offset: 0, size: inFile.size());
254 if (inData == nullptr) {
255 QMessageBox::warning(parent: this,
256 title: tr(s: "Can't map input file"),
257 text: tr(s: "Unable to memory map input file '%s'.").arg(a: m_fontFile));
258 return;
259 }
260
261 uchar *end = inData + inFile.size();
262 if (inData + sizeof(FontDirectoryHeader) > end) {
263 QMessageBox::warning(parent: this,
264 title: tr(s: "Can't read font directory"),
265 text: tr(s: "Input file seems to be invalid or corrupt."),
266 buttons: QMessageBox::Ok);
267 return;
268 }
269
270 FontDirectoryHeader fontDirectoryHeader;
271 memcpy(dest: &fontDirectoryHeader, src: inData, n: sizeof(FontDirectoryHeader));
272 quint16 numTables = qFromBigEndian(source: fontDirectoryHeader.numTables) + 1;
273 fontDirectoryHeader.numTables = qToBigEndian(source: numTables);
274 {
275 quint16 searchRange = qFromBigEndian(source: fontDirectoryHeader.searchRange);
276 if (searchRange / 16 < numTables) {
277 quint16 pot = (searchRange / 16) * 2;
278 searchRange = pot * 16;
279 fontDirectoryHeader.searchRange = qToBigEndian(source: searchRange);
280 fontDirectoryHeader.rangeShift = qToBigEndian(source: numTables * 16 - searchRange);
281
282 quint16 entrySelector = 0;
283 while (pot > 1) {
284 pot >>= 1;
285 entrySelector++;
286 }
287 fontDirectoryHeader.entrySelector = qToBigEndian(source: entrySelector);
288 }
289 }
290
291 outBuffer.write(data: reinterpret_cast<char *>(&fontDirectoryHeader),
292 len: sizeof(FontDirectoryHeader));
293
294 QVarLengthArray<QPair<quint32, quint32>> offsetLengthPairs;
295 offsetLengthPairs.reserve(sz: numTables - 1);
296
297 // Copy the offset table, updating offsets
298 TableRecord *offsetTable = reinterpret_cast<TableRecord *>(inData + sizeof(FontDirectoryHeader));
299 quint32 currentOffset = sizeof(FontDirectoryHeader) + sizeof(TableRecord) * numTables;
300 for (int i = 0; i < numTables - 1; ++i) {
301 ALIGN_OFFSET(currentOffset)
302
303 quint32 originalOffset = qFromBigEndian(source: offsetTable->offset);
304 quint32 length = qFromBigEndian(source: offsetTable->length);
305 offsetLengthPairs.append(t: qMakePair(value1&: originalOffset, value2&: length));
306 if (offsetTable->tag == qToBigEndian(MAKE_TAG('h', 'e', 'a', 'd')))
307 headOffset = currentOffset;
308
309 TableRecord newTableRecord;
310 memcpy(dest: &newTableRecord, src: offsetTable, n: sizeof(TableRecord));
311 newTableRecord.offset = qToBigEndian(source: currentOffset);
312 outBuffer.write(data: reinterpret_cast<char *>(&newTableRecord), len: sizeof(TableRecord));
313
314 offsetTable++;
315 currentOffset += length;
316 }
317
318 if (headOffset == 0) {
319 QMessageBox::warning(parent: this,
320 title: tr(s: "Invalid font file"),
321 text: tr(s: "Font file does not have 'head' table."),
322 buttons: QMessageBox::Ok);
323 return;
324 }
325
326 QByteArray qtdf = createSfntTable();
327 if (qtdf.isEmpty())
328 return;
329
330 {
331 ALIGN_OFFSET(currentOffset)
332
333 TableRecord qtdfRecord;
334 qtdfRecord.offset = qToBigEndian(source: currentOffset);
335 qtdfRecord.length = qToBigEndian(source: qtdf.size());
336 qtdfRecord.tag = qToBigEndian(MAKE_TAG('q', 't', 'd', 'f'));
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());
340 while (start < end)
341 checkSum += *(start++);
342 qtdfRecord.checkSum = qToBigEndian(source: checkSum);
343
344 outBuffer.write(data: reinterpret_cast<char *>(&qtdfRecord),
345 len: sizeof(TableRecord));
346 }
347
348 // Copy all font tables
349 for (const QPair<quint32, quint32> &offsetLengthPair : offsetLengthPairs) {
350 PAD_BUFFER(outBuffer, output.size())
351 outBuffer.write(data: reinterpret_cast<char *>(inData + offsetLengthPair.first),
352 len: offsetLengthPair.second);
353 }
354
355 PAD_BUFFER(outBuffer, output.size())
356 outBuffer.write(data: qtdf);
357 }
358
359 // Clear 'head' checksum and calculate new check sum adjustment
360 Head *head = reinterpret_cast<Head *>(output.data() + headOffset);
361 head->checkSumAdjustment = 0;
362
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());
366 while (start < end)
367 checkSum += *(start++);
368
369 head->checkSumAdjustment = qToBigEndian(source: 0xB1B0AFBA - checkSum);
370
371 QFile outFile(m_fileName);
372 if (!outFile.open(flags: QIODevice::WriteOnly)) {
373 QMessageBox::warning(parent: this,
374 title: tr(s: "Can't write to file"),
375 text: tr(s: "Cannot open the file '%s' for writing").arg(a: m_fileName),
376 buttons: QMessageBox::Ok);
377 return;
378 }
379
380 outFile.write(data: output);
381}
382
383QByteArray MainWindow::createSfntTable()
384{
385 QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes();
386 Q_ASSERT(!list.isEmpty());
387
388 QByteArray ret;
389 {
390 QBuffer buffer(&ret);
391 buffer.open(openMode: QIODevice::WriteOnly);
392
393 QtdfHeader header;
394 header.majorVersion = 5;
395 header.minorVersion = 12;
396 header.pixelSize = qToBigEndian(source: quint16(qRound(d: m_model->pixelSize())));
397
398 const quint8 padding = 2;
399 qreal scaleFactor = qreal(1) / QT_DISTANCEFIELD_SCALE(narrowOutlineFont: m_model->doubleGlyphResolution());
400 const int radius = QT_DISTANCEFIELD_RADIUS(narrowOutlineFont: m_model->doubleGlyphResolution())
401 / QT_DISTANCEFIELD_SCALE(narrowOutlineFont: m_model->doubleGlyphResolution());
402
403 quint32 textureSize = ui->sbMaximumTextureSize->value();
404
405 // Since we are using a single area allocator that spans all textures, we need
406 // to split the textures one row before the actual maximum size, otherwise
407 // glyphs that fall on the edge between two textures will expand the texture
408 // they are assigned to, and this will end up being larger than the max.
409 textureSize -= quint32(qCeil(v: m_model->pixelSize() * scaleFactor) + radius * 2 + padding * 2);
410 header.textureSize = qToBigEndian(source: textureSize);
411
412 header.padding = padding;
413 header.flags = m_model->doubleGlyphResolution() ? 1 : 0;
414 header.numGlyphs = qToBigEndian(source: quint32(list.size()));
415 buffer.write(data: reinterpret_cast<char *>(&header),
416 len: sizeof(QtdfHeader));
417
418 // Maximum height allocator to find optimal number of textures
419 QList<QRect> allocatedAreaPerTexture;
420
421 struct GlyphData {
422 QSGDistanceFieldGlyphCache::TexCoord texCoord;
423 QRectF boundingRect;
424 QSize glyphSize;
425 int textureIndex;
426 };
427 QList<GlyphData> glyphDatas;
428 glyphDatas.resize(size: m_model->rowCount());
429
430 int textureCount = 0;
431
432 {
433 QTransform scaleDown;
434 scaleDown.scale(sx: scaleFactor, sy: scaleFactor);
435
436 {
437 bool foundOptimalSize = false;
438 while (!foundOptimalSize) {
439 allocatedAreaPerTexture.clear();
440
441 QSGAreaAllocator allocator(QSize(textureSize, textureSize * (++textureCount)));
442
443 int i;
444 for (i = 0; i < list.size(); ++i) {
445 int glyphIndex = list.at(i).row();
446 GlyphData &glyphData = glyphDatas[glyphIndex];
447
448 QPainterPath path = m_model->path(row: glyphIndex);
449 glyphData.boundingRect = scaleDown.mapRect(path.boundingRect());
450 int glyphWidth = qCeil(v: glyphData.boundingRect.width()) + radius * 2;
451 int glyphHeight = qCeil(v: glyphData.boundingRect.height()) + radius * 2;
452
453 glyphData.glyphSize = QSize(glyphWidth + padding * 2, glyphHeight + padding * 2);
454
455 if (glyphData.glyphSize.width() > qint32(textureSize)
456 || glyphData.glyphSize.height() > qint32(textureSize)) {
457 QMessageBox::warning(parent: this,
458 title: tr(s: "Glyph too large for texture"),
459 text: tr(s: "Glyph %1 is too large to fit in texture of size %2.")
460 .arg(a: glyphIndex).arg(a: textureSize));
461 return QByteArray();
462 }
463
464 QRect rect = allocator.allocate(size: glyphData.glyphSize);
465 if (rect.isNull())
466 break;
467
468 glyphData.textureIndex = rect.y() / textureSize;
469 while (glyphData.textureIndex >= allocatedAreaPerTexture.size())
470 allocatedAreaPerTexture.append(t: QRect(0, 0, 1, 1));
471
472 allocatedAreaPerTexture[glyphData.textureIndex] |= QRect(rect.x(),
473 rect.y() % textureSize,
474 rect.width(),
475 rect.height());
476
477 glyphData.texCoord.xMargin = QT_DISTANCEFIELD_RADIUS(narrowOutlineFont: m_model->doubleGlyphResolution()) / qreal(QT_DISTANCEFIELD_SCALE(narrowOutlineFont: m_model->doubleGlyphResolution()));
478 glyphData.texCoord.yMargin = QT_DISTANCEFIELD_RADIUS(narrowOutlineFont: m_model->doubleGlyphResolution()) / qreal(QT_DISTANCEFIELD_SCALE(narrowOutlineFont: m_model->doubleGlyphResolution()));
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();
483
484 glyphDatas.append(t: glyphData);
485 }
486
487 foundOptimalSize = i == list.size();
488 if (foundOptimalSize)
489 buffer.write(data: allocator.serialize());
490 }
491 }
492 }
493
494 QList<QDistanceField> textures;
495 textures.resize(size: textureCount);
496
497 for (int textureIndex = 0; textureIndex < textureCount; ++textureIndex) {
498 textures[textureIndex] = QDistanceField(allocatedAreaPerTexture.at(i: textureIndex).width(),
499 allocatedAreaPerTexture.at(i: textureIndex).height());
500
501 QRect rect = allocatedAreaPerTexture.at(i: textureIndex);
502
503 QtdfTextureRecord record;
504 record.allocatedX = qToBigEndian(source: rect.x());
505 record.allocatedY = qToBigEndian(source: rect.y());
506 record.allocatedWidth = qToBigEndian(source: rect.width());
507 record.allocatedHeight = qToBigEndian(source: rect.height());
508 record.padding = padding;
509 buffer.write(data: reinterpret_cast<char *>(&record),
510 len: sizeof(QtdfTextureRecord));
511 }
512
513 {
514 for (int i = 0; i < list.size(); ++i) {
515 int glyphIndex = list.at(i).row();
516 QImage image = m_model->distanceField(row: glyphIndex);
517
518 const GlyphData &glyphData = glyphDatas.at(i: glyphIndex);
519
520 QtdfGlyphRecord glyphRecord;
521 glyphRecord.glyphIndex = qToBigEndian(source: 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(source: quint16(glyphData.textureIndex));
533 buffer.write(data: reinterpret_cast<char *>(&glyphRecord), len: sizeof(QtdfGlyphRecord));
534
535 int expectedWidth = qCeil(v: glyphData.texCoord.width + glyphData.texCoord.xMargin * 2);
536 image = image.copy(x: -padding, y: -padding,
537 w: expectedWidth + padding * 2,
538 h: image.height() + padding * 2);
539
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(dest: outBits, src: inBits, n: image.width());
545 inBits += image.bytesPerLine();
546 outBits += textures[glyphData.textureIndex].width();
547 }
548 }
549 }
550
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(data: reinterpret_cast<const char *>(texture.constBits()),
555 len: allocatedArea.width() * allocatedArea.height());
556 }
557
558 PAD_BUFFER(buffer, ret.size())
559 }
560
561 return ret;
562}
563
564void MainWindow::writeFile()
565{
566 Q_ASSERT(!m_fileName.isEmpty());
567
568 QFile file(m_fileName);
569 if (file.open(flags: QIODevice::WriteOnly)) {
570
571 } else {
572 QMessageBox::warning(parent: this,
573 title: tr(s: "Can't open file for writing"),
574 text: tr(s: "Unable to open file '%1' for writing").arg(a: m_fileName),
575 buttons: QMessageBox::Ok);
576 }
577}
578
579void MainWindow::openFont()
580{
581 QString fileName = QFileDialog::getOpenFileName(parent: this,
582 caption: tr(s: "Open font file"),
583 dir: m_fontDir,
584 filter: tr(s: "Fonts (*.ttf *.otf);;All files (*)"));
585 if (!fileName.isEmpty())
586 open(path: fileName);
587}
588
589void MainWindow::updateProgressBar()
590{
591 m_statusBarProgressBar->setValue(m_statusBarProgressBar->value() + 1);
592 updateSelection();
593}
594
595void MainWindow::startProgressBar(quint16 glyphCount)
596{
597 ui->action_Open->setDisabled(false);
598 m_statusBarLabel->setText(tr(s: "Generating"));
599 m_statusBarProgressBar->setMaximum(glyphCount);
600 m_statusBarProgressBar->setMinimum(0);
601 m_statusBarProgressBar->setValue(0);
602 m_statusBarProgressBar->setVisible(true);
603}
604
605void MainWindow::stopProgressBar()
606{
607 m_statusBarLabel->setText(tr(s: "Ready"));
608 m_statusBarProgressBar->setVisible(false);
609}
610
611void MainWindow::selectAll()
612{
613 QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes();
614 if (list.size() == ui->lvGlyphs->model()->rowCount())
615 ui->lvGlyphs->clearSelection();
616 else
617 ui->lvGlyphs->selectAll();
618}
619
620void MainWindow::updateSelection()
621{
622 QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes();
623 QString label;
624 if (list.size() == ui->lvGlyphs->model()->rowCount())
625 label = tr(s: "Deselect &All");
626 else
627 label = tr(s: "Select &All");
628
629 ui->tbSelectAll->setText(label);
630 ui->actionSelect_all->setText(label);
631
632 if (m_model != nullptr && ui->lwUnicodeRanges->count() > 0) {
633 // Ignore selection changes until we are done
634 disconnect(ui->lwUnicodeRanges, &QListWidget::itemSelectionChanged, this, &MainWindow::updateUnicodeRanges);
635
636 QSet<int> selectedGlyphIndexes;
637 for (const QModelIndex &modelIndex : list)
638 selectedGlyphIndexes.insert(modelIndex.row());
639
640 QList<DistanceFieldModel::UnicodeRange> unicodeRanges = m_model->unicodeRanges();
641 std::sort(first: unicodeRanges.begin(), last: unicodeRanges.end());
642
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);
647
648 QList<glyph_t> glyphIndexes = m_model->glyphIndexesForUnicodeRange(range: unicodeRange);
649 Q_ASSERT(!glyphIndexes.isEmpty());
650
651 item->setSelected(true);
652 for (glyph_t glyphIndex : glyphIndexes) {
653 if (!selectedGlyphIndexes.contains(value: glyphIndex)) {
654 item->setSelected(false);
655 break;
656 }
657 }
658 }
659
660 connect(ui->lwUnicodeRanges, &QListWidget::itemSelectionChanged, this, &MainWindow::updateUnicodeRanges);
661 }
662}
663
664void MainWindow::updateUnicodeRanges()
665{
666 if (m_model == nullptr)
667 return;
668
669 disconnect(ui->lvGlyphs->selectionModel(),
670 &QItemSelectionModel::selectionChanged,
671 this,
672 &MainWindow::updateSelection);
673
674 QItemSelection selectedItems;
675
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(range: unicodeRange);
681
682 for (glyph_t glyphIndex : glyphIndexes) {
683 QModelIndex index = m_model->index(glyphIndex);
684 selectedItems.select(index, index);
685 }
686 }
687 }
688
689 ui->lvGlyphs->selectionModel()->clearSelection();
690 if (!selectedItems.isEmpty())
691 ui->lvGlyphs->selectionModel()->select(selectedItems, QItemSelectionModel::Select);
692
693 connect(ui->lvGlyphs->selectionModel(),
694 &QItemSelectionModel::selectionChanged,
695 this,
696 &MainWindow::updateSelection);
697}
698
699void MainWindow::populateUnicodeRanges()
700{
701 QList<DistanceFieldModel::UnicodeRange> unicodeRanges = m_model->unicodeRanges();
702 std::sort(first: unicodeRanges.begin(), last: unicodeRanges.end());
703
704 for (DistanceFieldModel::UnicodeRange unicodeRange : unicodeRanges) {
705 QString name = m_model->nameForUnicodeRange(range: unicodeRange);
706 QListWidgetItem *item = new QListWidgetItem(name, ui->lwUnicodeRanges);
707 item->setData(Qt::UserRole, unicodeRange);
708 }
709
710 ui->lwUnicodeRanges->setDisabled(false);
711 ui->action_Save->setDisabled(false);
712 ui->action_Save_as->setDisabled(false);
713 ui->tbSave->setDisabled(false);
714}
715
716void MainWindow::displayError(const QString &errorString)
717{
718 QMessageBox::warning(parent: this, title: tr(s: "Error when parsing font file"), text: errorString, buttons: QMessageBox::Ok);
719}
720
721void MainWindow::selectString()
722{
723 QString s = QInputDialog::getText(parent: this,
724 title: tr(s: "Select glyphs for string"),
725 label: tr(s: "String to parse:"));
726 if (!s.isEmpty()) {
727 QList<uint> ucs4String = s.toUcs4();
728 for (uint ucs4 : ucs4String) {
729 glyph_t glyph = m_model->glyphIndexForUcs4(ucs4);
730 if (glyph != 0) {
731 ui->lvGlyphs->selectionModel()->select(m_model->index(row: glyph),
732 QItemSelectionModel::Select);
733 }
734 }
735 }
736}
737
738void MainWindow::about()
739{
740 QMessageBox *msgBox = new QMessageBox(this);
741 msgBox->setAttribute(Qt::WA_DeleteOnClose);
742 msgBox->setWindowTitle(tr(s: "About Qt Distance Field Generator"));
743 msgBox->setText(tr(s: "<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) %2 The Qt Company Ltd.</p>")
748 .arg(a: QLatin1String(QT_VERSION_STR))
749 .arg(a: QLatin1String("2019")));
750 msgBox->show();
751}
752
753QT_END_NAMESPACE
754

source code of qttools/src/distancefieldgenerator/mainwindow.cpp