Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
mainwindow.cpp
Go to the documentation of this file.
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"
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
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(QUrl(url));
34}
35
36MainWindow::MainWindow(QWidget *parent)
38 , ui(new Ui::MainWindow)
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("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>("glyph_t");
63 qRegisterMetaType<QPainterPath>("QPainterPath");
64
65 restoreGeometry(m_settings.value(QStringLiteral("geometry")).toByteArray());
66
67 setupConnections();
68}
69
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"), 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"), 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(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(m_model, &DistanceFieldModel::startGeneration, this, &MainWindow::startProgressBar);
118 connect(m_model, &DistanceFieldModel::stopGeneration, this, &MainWindow::stopProgressBar);
119 connect(m_model, &DistanceFieldModel::distanceFieldGenerated, this, &MainWindow::updateProgressBar);
120 connect(m_model, &DistanceFieldModel::stopGeneration, this, &MainWindow::populateUnicodeRanges);
121 connect(m_model, &DistanceFieldModel::error, this, &MainWindow::displayError);
122}
123
124void MainWindow::saveAs()
125{
126 QString fileName = QFileDialog::getSaveFileName(this,
127 tr("Save distance field-enriched file"),
128 m_fontDir,
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);
134 save();
135 }
136}
137
138
139# pragma pack(1)
148
156
167
183
192
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(this,
226 tr("Nothing to save"),
227 tr("No glyphs selected for saving."),
228 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(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),
242 QMessageBox::Ok);
243 return;
244 }
245
246 QByteArray output;
247 quint32 headOffset = 0;
248
249 {
250 QBuffer outBuffer(&output);
251 outBuffer.open(QIODevice::WriteOnly);
252
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));
258 return;
259 }
260
261 uchar *end = inData + inFile.size();
262 if (inData + sizeof(FontDirectoryHeader) > end) {
263 QMessageBox::warning(this,
264 tr("Can't read font directory"),
265 tr("Input file seems to be invalid or corrupt."),
266 QMessageBox::Ok);
267 return;
268 }
269
270 FontDirectoryHeader fontDirectoryHeader;
271 memcpy(&fontDirectoryHeader, inData, sizeof(FontDirectoryHeader));
272 quint16 numTables = qFromBigEndian(fontDirectoryHeader.numTables) + 1;
273 fontDirectoryHeader.numTables = qToBigEndian(numTables);
274 {
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);
281
282 quint16 entrySelector = 0;
283 while (pot > 1) {
284 pot >>= 1;
285 entrySelector++;
286 }
287 fontDirectoryHeader.entrySelector = qToBigEndian(entrySelector);
288 }
289 }
290
291 outBuffer.write(reinterpret_cast<char *>(&fontDirectoryHeader),
292 sizeof(FontDirectoryHeader));
293
294 QVarLengthArray<std::pair<quint32, quint32>> offsetLengthPairs;
295 offsetLengthPairs.reserve(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(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;
308
309 TableRecord newTableRecord;
310 memcpy(&newTableRecord, offsetTable, sizeof(TableRecord));
311 newTableRecord.offset = qToBigEndian(currentOffset);
312 outBuffer.write(reinterpret_cast<char *>(&newTableRecord), sizeof(TableRecord));
313
314 offsetTable++;
315 currentOffset += length;
316 }
317
318 if (headOffset == 0) {
319 QMessageBox::warning(this,
320 tr("Invalid font file"),
321 tr("Font file does not have 'head' table."),
322 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(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());
340 while (start < end)
341 checkSum += *(start++);
342 qtdfRecord.checkSum = qToBigEndian(checkSum);
343
344 outBuffer.write(reinterpret_cast<char *>(&qtdfRecord),
345 sizeof(TableRecord));
346 }
347
348 // Copy all font tables
349 for (const std::pair<quint32, quint32> &offsetLengthPair : offsetLengthPairs) {
350 PAD_BUFFER(outBuffer, output.size())
351 outBuffer.write(reinterpret_cast<char *>(inData + offsetLengthPair.first),
352 offsetLengthPair.second);
353 }
354
355 PAD_BUFFER(outBuffer, output.size())
356 outBuffer.write(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(0xB1B0AFBA - checkSum);
370
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),
376 QMessageBox::Ok);
377 return;
378 }
379
380 outFile.write(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(QIODevice::WriteOnly);
392
393 QtdfHeader header;
394 header.majorVersion = 5;
395 header.minorVersion = 12;
396 header.pixelSize = qToBigEndian(quint16(qRound(m_model->pixelSize())));
397
398 const quint8 padding = 2;
399 qreal scaleFactor = qreal(1) / QT_DISTANCEFIELD_SCALE(m_model->doubleGlyphResolution());
400 const int radius = QT_DISTANCEFIELD_RADIUS(m_model->doubleGlyphResolution())
401 / QT_DISTANCEFIELD_SCALE(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(m_model->pixelSize() * scaleFactor) + radius * 2 + padding * 2);
410 header.textureSize = qToBigEndian(textureSize);
411
412 header.padding = padding;
413 header.flags = m_model->doubleGlyphResolution() ? 1 : 0;
414 header.numGlyphs = qToBigEndian(quint32(list.size()));
415 buffer.write(reinterpret_cast<char *>(&header),
416 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(m_model->rowCount());
429
430 int textureCount = 0;
431
432 {
433 QTransform scaleDown;
434 scaleDown.scale(scaleFactor, 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(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;
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(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));
461 return QByteArray();
462 }
463
464 QRect rect = allocator.allocate(glyphData.glyphSize);
465 if (rect.isNull())
466 break;
467
468 glyphData.textureIndex = rect.y() / textureSize;
469 while (glyphData.textureIndex >= allocatedAreaPerTexture.size())
470 allocatedAreaPerTexture.append(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(m_model->doubleGlyphResolution()) / qreal(QT_DISTANCEFIELD_SCALE(m_model->doubleGlyphResolution()));
478 glyphData.texCoord.yMargin = QT_DISTANCEFIELD_RADIUS(m_model->doubleGlyphResolution()) / qreal(QT_DISTANCEFIELD_SCALE(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(glyphData);
485 }
486
487 foundOptimalSize = i == list.size();
488 if (foundOptimalSize)
489 buffer.write(allocator.serialize());
490 }
491 }
492 }
493
494 QList<QDistanceField> textures;
495 textures.resize(textureCount);
496
497 for (int textureIndex = 0; textureIndex < textureCount; ++textureIndex) {
498 textures[textureIndex] = QDistanceField(allocatedAreaPerTexture.at(textureIndex).width(),
499 allocatedAreaPerTexture.at(textureIndex).height());
500
501 QRect rect = allocatedAreaPerTexture.at(textureIndex);
502
503 QtdfTextureRecord record;
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),
510 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(glyphIndex);
517
518 const GlyphData &glyphData = glyphDatas.at(glyphIndex);
519
520 QtdfGlyphRecord glyphRecord;
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));
534
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);
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(outBits, inBits, 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(reinterpret_cast<const char *>(texture.constBits()),
555 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(QIODevice::WriteOnly)) {
570
571 } else {
572 QMessageBox::warning(this,
573 tr("Can't open file for writing"),
574 tr("Unable to open file '%1' for writing").arg(m_fileName),
575 QMessageBox::Ok);
576 }
577}
578
579void MainWindow::openFont()
580{
581 QString fileName = QFileDialog::getOpenFileName(this,
582 tr("Open font file"),
583 m_fontDir,
584 tr("Fonts (*.ttf *.otf);;All files (*)"));
585 if (!fileName.isEmpty())
586 open(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("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("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("Deselect &All");
626 else
627 label = tr("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(unicodeRanges.begin(), 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(unicodeRange);
649 Q_ASSERT(!glyphIndexes.isEmpty());
650
651 item->setSelected(true);
652 for (glyph_t glyphIndex : glyphIndexes) {
653 if (!selectedGlyphIndexes.contains(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(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(unicodeRanges.begin(), unicodeRanges.end());
703
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);
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(this, tr("Error when parsing font file"), errorString, QMessageBox::Ok);
719}
720
721void MainWindow::selectString()
722{
723 QString s = QInputDialog::getText(this,
724 tr("Select glyphs for string"),
725 tr("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(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("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)));
749 msgBox->show();
750}
751
752QT_END_NAMESPACE
bool doubleGlyphResolution() const
void distanceFieldGenerated()
void closeEvent(QCloseEvent *event) override
[21]
~MainWindow() override
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
quint16 majorVersion
quint32 fontRevision
quint16 minorVersion
quint32 textureHeight
quint32 textureOffsetX
quint32 boundingRectWidth
quint32 boundingRectHeight
quint32 textureOffsetY
quint16 pixelSize
quint8 padding
quint8 minorVersion
quint32 textureSize
quint8 majorVersion
quint32 numGlyphs
quint32 checkSum
quint32 length
quint32 offset