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

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