PageRenderTime 168ms CodeModel.GetById 118ms RepoModel.GetById 0ms app.codeStats 1ms

/src/distancefieldgenerator/mainwindow.cpp

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