| /**************************************************************************** |
| ** |
| ** Copyright (C) 2018 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the tools applications of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "mainwindow.h" |
| #include "ui_mainwindow.h" |
| #include "distancefieldmodel.h" |
| |
| #include <QtCore/qdir.h> |
| #include <QtCore/qdatastream.h> |
| #include <QtCore/qmath.h> |
| #include <QtCore/qendian.h> |
| #include <QtCore/qbuffer.h> |
| #include <QtGui/qdesktopservices.h> |
| #include <QtGui/qrawfont.h> |
| #include <QtWidgets/qmessagebox.h> |
| #include <QtWidgets/qlabel.h> |
| #include <QtWidgets/qprogressbar.h> |
| #include <QtWidgets/qfiledialog.h> |
| #include <QtWidgets/qinputdialog.h> |
| |
| #include <QtCore/private/qunicodetables_p.h> |
| #include <QtGui/private/qdistancefield_p.h> |
| #include <QtQuick/private/qsgareaallocator_p.h> |
| #include <QtQuick/private/qsgadaptationlayer_p.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| static void openHelp() |
| { |
| QDesktopServices::openUrl(QUrl(QLatin1String("http://doc.qt.io/qt-5/qtdistancefieldgenerator-index.html"))); |
| } |
| |
| MainWindow::MainWindow(QWidget *parent) |
| : QMainWindow(parent) |
| , ui(new Ui::MainWindow) |
| , m_settings(qApp->organizationName(), qApp->applicationName()) |
| , m_model(new DistanceFieldModel(this)) |
| , m_statusBarLabel(nullptr) |
| , m_statusBarProgressBar(nullptr) |
| { |
| ui->setupUi(this); |
| ui->lvGlyphs->setModel(m_model); |
| |
| ui->actionHelp->setShortcut(QKeySequence::HelpContents); |
| |
| m_statusBarLabel = new QLabel(this); |
| m_statusBarLabel->setText(tr("Ready")); |
| ui->statusbar->addPermanentWidget(m_statusBarLabel); |
| |
| m_statusBarProgressBar = new QProgressBar(this); |
| ui->statusbar->addPermanentWidget(m_statusBarProgressBar); |
| m_statusBarProgressBar->setVisible(false); |
| |
| if (m_settings.contains(QStringLiteral("fontDirectory"))) |
| m_fontDir = m_settings.value(QStringLiteral("fontDirectory")).toString(); |
| else |
| m_fontDir = QDir::currentPath(); |
| |
| qRegisterMetaType<glyph_t>("glyph_t"); |
| qRegisterMetaType<QPainterPath>("QPainterPath"); |
| |
| restoreGeometry(m_settings.value(QStringLiteral("geometry")).toByteArray()); |
| |
| setupConnections(); |
| } |
| |
| MainWindow::~MainWindow() |
| { |
| delete ui; |
| } |
| |
| void MainWindow::open(const QString &path) |
| { |
| m_fileName.clear(); |
| m_fontFile = path; |
| m_fontDir = QFileInfo(path).absolutePath(); |
| m_settings.setValue(QStringLiteral("fontDirectory"), m_fontDir); |
| |
| ui->lwUnicodeRanges->clear(); |
| ui->lwUnicodeRanges->setDisabled(true); |
| ui->action_Save->setDisabled(true); |
| ui->action_Save_as->setDisabled(true); |
| ui->tbSave->setDisabled(true); |
| ui->action_Open->setDisabled(true); |
| m_model->setFont(path); |
| } |
| |
| void MainWindow::closeEvent(QCloseEvent * /*event*/) |
| { |
| m_settings.setValue(QStringLiteral("geometry"), saveGeometry()); |
| } |
| |
| void MainWindow::setupConnections() |
| { |
| connect(ui->action_Open, &QAction::triggered, this, &MainWindow::openFont); |
| connect(ui->actionE_xit, &QAction::triggered, qApp, &QApplication::quit); |
| connect(ui->action_Save, &QAction::triggered, this, &MainWindow::save); |
| connect(ui->action_Save_as, &QAction::triggered, this, &MainWindow::saveAs); |
| connect(ui->tbSave, &QToolButton::clicked, this, &MainWindow::save); |
| connect(ui->tbSelectAll, &QToolButton::clicked, this, &MainWindow::selectAll); |
| connect(ui->actionSelect_all, &QAction::triggered, this, &MainWindow::selectAll); |
| connect(ui->actionSelect_string, &QAction::triggered, this, &MainWindow::selectString); |
| connect(ui->actionHelp, &QAction::triggered, this, openHelp); |
| connect(ui->actionAbout_App, &QAction::triggered, this, &MainWindow::about); |
| connect(ui->actionAbout_Qt, &QAction::triggered, this, [this]() { |
| QMessageBox::aboutQt(this); |
| }); |
| connect(ui->lwUnicodeRanges, &QListWidget::itemSelectionChanged, this, &MainWindow::updateUnicodeRanges); |
| |
| connect(ui->lvGlyphs->selectionModel(), |
| &QItemSelectionModel::selectionChanged, |
| this, |
| &MainWindow::updateSelection); |
| connect(m_model, &DistanceFieldModel::startGeneration, this, &MainWindow::startProgressBar); |
| connect(m_model, &DistanceFieldModel::stopGeneration, this, &MainWindow::stopProgressBar); |
| connect(m_model, &DistanceFieldModel::distanceFieldGenerated, this, &MainWindow::updateProgressBar); |
| connect(m_model, &DistanceFieldModel::stopGeneration, this, &MainWindow::populateUnicodeRanges); |
| connect(m_model, &DistanceFieldModel::error, this, &MainWindow::displayError); |
| } |
| |
| void MainWindow::saveAs() |
| { |
| QString fileName = QFileDialog::getSaveFileName(this, |
| tr("Save distance field-enriched file"), |
| m_fontDir, |
| tr("Font files (*.ttf *.otf);;All files (*)")); |
| if (!fileName.isEmpty()) { |
| m_fileName = fileName; |
| m_fontDir = QFileInfo(m_fileName).absolutePath(); |
| m_settings.setValue(QStringLiteral("fontDirectory"), m_fontDir); |
| save(); |
| } |
| } |
| |
| |
| # pragma pack(1) |
| struct FontDirectoryHeader |
| { |
| quint32 sfntVersion; |
| quint16 numTables; |
| quint16 searchRange; |
| quint16 entrySelector; |
| quint16 rangeShift; |
| }; |
| |
| struct TableRecord |
| { |
| quint32 tag; |
| quint32 checkSum; |
| quint32 offset; |
| quint32 length; |
| }; |
| |
| struct QtdfHeader |
| { |
| quint8 majorVersion; |
| quint8 minorVersion; |
| quint16 pixelSize; |
| quint32 textureSize; |
| quint8 flags; |
| quint8 padding; |
| quint32 numGlyphs; |
| }; |
| |
| struct QtdfGlyphRecord |
| { |
| quint32 glyphIndex; |
| quint32 textureOffsetX; |
| quint32 textureOffsetY; |
| quint32 textureWidth; |
| quint32 textureHeight; |
| quint32 xMargin; |
| quint32 yMargin; |
| qint32 boundingRectX; |
| qint32 boundingRectY; |
| quint32 boundingRectWidth; |
| quint32 boundingRectHeight; |
| quint16 textureIndex; |
| }; |
| |
| struct QtdfTextureRecord |
| { |
| quint32 allocatedX; |
| quint32 allocatedY; |
| quint32 allocatedWidth; |
| quint32 allocatedHeight; |
| quint8 padding; |
| }; |
| |
| struct Head |
| { |
| quint16 majorVersion; |
| quint16 minorVersion; |
| quint32 fontRevision; |
| quint32 checkSumAdjustment; |
| }; |
| # pragma pack() |
| |
| #define PAD_BUFFER(buffer, size) \ |
| { \ |
| int paddingNeed = size % 4; \ |
| if (paddingNeed > 0) { \ |
| const char padding[3] = { 0, 0, 0 }; \ |
| buffer.write(padding, 4 - paddingNeed); \ |
| } \ |
| } |
| |
| #define ALIGN_OFFSET(offset) \ |
| { \ |
| int paddingNeed = offset % 4; \ |
| if (paddingNeed > 0) \ |
| offset += 4 - paddingNeed; \ |
| } |
| |
| #define TO_FIXED_POINT(value) \ |
| ((int)(value*qreal(65536))) |
| |
| void MainWindow::save() |
| { |
| QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes(); |
| if (list.isEmpty()) { |
| QMessageBox::warning(this, |
| tr("Nothing to save"), |
| tr("No glyphs selected for saving."), |
| QMessageBox::Ok); |
| return; |
| } |
| |
| if (m_fileName.isEmpty()) { |
| saveAs(); |
| return; |
| } |
| |
| QFile inFile(m_fontFile); |
| if (!inFile.open(QIODevice::ReadOnly)) { |
| QMessageBox::warning(this, |
| tr("Can't read original font"), |
| tr("Cannot open '%s' for reading. The original font file must remain in place until the new file has been saved.").arg(m_fontFile), |
| QMessageBox::Ok); |
| return; |
| } |
| |
| QByteArray output; |
| quint32 headOffset = 0; |
| |
| { |
| QBuffer outBuffer(&output); |
| outBuffer.open(QIODevice::WriteOnly); |
| |
| uchar *inData = inFile.map(0, inFile.size()); |
| if (inData == nullptr) { |
| QMessageBox::warning(this, |
| tr("Can't map input file"), |
| tr("Unable to memory map input file '%s'.").arg(m_fontFile)); |
| return; |
| } |
| |
| uchar *end = inData + inFile.size(); |
| if (inData + sizeof(FontDirectoryHeader) > end) { |
| QMessageBox::warning(this, |
| tr("Can't read font directory"), |
| tr("Input file seems to be invalid or corrupt."), |
| QMessageBox::Ok); |
| return; |
| } |
| |
| FontDirectoryHeader fontDirectoryHeader; |
| memcpy(&fontDirectoryHeader, inData, sizeof(FontDirectoryHeader)); |
| quint16 numTables = qFromBigEndian(fontDirectoryHeader.numTables) + 1; |
| fontDirectoryHeader.numTables = qToBigEndian(numTables); |
| { |
| quint16 searchRange = qFromBigEndian(fontDirectoryHeader.searchRange); |
| if (searchRange / 16 < numTables) { |
| quint16 pot = (searchRange / 16) * 2; |
| searchRange = pot * 16; |
| fontDirectoryHeader.searchRange = qToBigEndian(searchRange); |
| fontDirectoryHeader.rangeShift = qToBigEndian(numTables * 16 - searchRange); |
| |
| quint16 entrySelector = 0; |
| while (pot > 1) { |
| pot >>= 1; |
| entrySelector++; |
| } |
| fontDirectoryHeader.entrySelector = qToBigEndian(entrySelector); |
| } |
| } |
| |
| outBuffer.write(reinterpret_cast<char *>(&fontDirectoryHeader), |
| sizeof(FontDirectoryHeader)); |
| |
| QVarLengthArray<QPair<quint32, quint32>> offsetLengthPairs; |
| offsetLengthPairs.reserve(numTables - 1); |
| |
| // Copy the offset table, updating offsets |
| TableRecord *offsetTable = reinterpret_cast<TableRecord *>(inData + sizeof(FontDirectoryHeader)); |
| quint32 currentOffset = sizeof(FontDirectoryHeader) + sizeof(TableRecord) * numTables; |
| for (int i = 0; i < numTables - 1; ++i) { |
| ALIGN_OFFSET(currentOffset) |
| |
| quint32 originalOffset = qFromBigEndian(offsetTable->offset); |
| quint32 length = qFromBigEndian(offsetTable->length); |
| offsetLengthPairs.append(qMakePair(originalOffset, length)); |
| if (offsetTable->tag == qToBigEndian(MAKE_TAG('h', 'e', 'a', 'd'))) |
| headOffset = currentOffset; |
| |
| TableRecord newTableRecord; |
| memcpy(&newTableRecord, offsetTable, sizeof(TableRecord)); |
| newTableRecord.offset = qToBigEndian(currentOffset); |
| outBuffer.write(reinterpret_cast<char *>(&newTableRecord), sizeof(TableRecord)); |
| |
| offsetTable++; |
| currentOffset += length; |
| } |
| |
| if (headOffset == 0) { |
| QMessageBox::warning(this, |
| tr("Invalid font file"), |
| tr("Font file does not have 'head' table."), |
| QMessageBox::Ok); |
| return; |
| } |
| |
| QByteArray qtdf = createSfntTable(); |
| if (qtdf.isEmpty()) |
| return; |
| |
| { |
| ALIGN_OFFSET(currentOffset) |
| |
| TableRecord qtdfRecord; |
| qtdfRecord.offset = qToBigEndian(currentOffset); |
| qtdfRecord.length = qToBigEndian(qtdf.length()); |
| qtdfRecord.tag = qToBigEndian(MAKE_TAG('q', 't', 'd', 'f')); |
| quint32 checkSum = 0; |
| const quint32 *start = reinterpret_cast<const quint32 *>(qtdf.constData()); |
| const quint32 *end = reinterpret_cast<const quint32 *>(qtdf.constData() + qtdf.length()); |
| while (start < end) |
| checkSum += *(start++); |
| qtdfRecord.checkSum = qToBigEndian(checkSum); |
| |
| outBuffer.write(reinterpret_cast<char *>(&qtdfRecord), |
| sizeof(TableRecord)); |
| } |
| |
| // Copy all font tables |
| for (const QPair<quint32, quint32> &offsetLengthPair : offsetLengthPairs) { |
| PAD_BUFFER(outBuffer, output.size()) |
| outBuffer.write(reinterpret_cast<char *>(inData + offsetLengthPair.first), |
| offsetLengthPair.second); |
| } |
| |
| PAD_BUFFER(outBuffer, output.size()) |
| outBuffer.write(qtdf); |
| } |
| |
| // Clear 'head' checksum and calculate new check sum adjustment |
| Head *head = reinterpret_cast<Head *>(output.data() + headOffset); |
| head->checkSumAdjustment = 0; |
| |
| quint32 checkSum = 0; |
| const quint32 *start = reinterpret_cast<const quint32 *>(output.constData()); |
| const quint32 *end = reinterpret_cast<const quint32 *>(output.constData() + output.length()); |
| while (start < end) |
| checkSum += *(start++); |
| |
| head->checkSumAdjustment = qToBigEndian(0xB1B0AFBA - checkSum); |
| |
| QFile outFile(m_fileName); |
| if (!outFile.open(QIODevice::WriteOnly)) { |
| QMessageBox::warning(this, |
| tr("Can't write to file"), |
| tr("Cannot open the file '%s' for writing").arg(m_fileName), |
| QMessageBox::Ok); |
| return; |
| } |
| |
| outFile.write(output); |
| } |
| |
| QByteArray MainWindow::createSfntTable() |
| { |
| QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes(); |
| Q_ASSERT(!list.isEmpty()); |
| |
| QByteArray ret; |
| { |
| QBuffer buffer(&ret); |
| buffer.open(QIODevice::WriteOnly); |
| |
| QtdfHeader header; |
| header.majorVersion = 5; |
| header.minorVersion = 12; |
| header.pixelSize = qToBigEndian(quint16(qRound(m_model->pixelSize()))); |
| |
| const quint8 padding = 2; |
| qreal scaleFactor = qreal(1) / QT_DISTANCEFIELD_SCALE(m_model->doubleGlyphResolution()); |
| const int radius = QT_DISTANCEFIELD_RADIUS(m_model->doubleGlyphResolution()) |
| / QT_DISTANCEFIELD_SCALE(m_model->doubleGlyphResolution()); |
| |
| quint32 textureSize = ui->sbMaximumTextureSize->value(); |
| |
| // Since we are using a single area allocator that spans all textures, we need |
| // to split the textures one row before the actual maximum size, otherwise |
| // glyphs that fall on the edge between two textures will expand the texture |
| // they are assigned to, and this will end up being larger than the max. |
| textureSize -= quint32(qCeil(m_model->pixelSize() * scaleFactor) + radius * 2 + padding * 2); |
| header.textureSize = qToBigEndian(textureSize); |
| |
| header.padding = padding; |
| header.flags = m_model->doubleGlyphResolution() ? 1 : 0; |
| header.numGlyphs = qToBigEndian(quint32(list.size())); |
| buffer.write(reinterpret_cast<char *>(&header), |
| sizeof(QtdfHeader)); |
| |
| // Maximum height allocator to find optimal number of textures |
| QVector<QRect> allocatedAreaPerTexture; |
| |
| struct GlyphData { |
| QSGDistanceFieldGlyphCache::TexCoord texCoord; |
| QRectF boundingRect; |
| QSize glyphSize; |
| int textureIndex; |
| }; |
| QVector<GlyphData> glyphDatas; |
| glyphDatas.resize(m_model->rowCount()); |
| |
| int textureCount = 0; |
| |
| { |
| QTransform scaleDown; |
| scaleDown.scale(scaleFactor, scaleFactor); |
| |
| { |
| bool foundOptimalSize = false; |
| while (!foundOptimalSize) { |
| allocatedAreaPerTexture.clear(); |
| |
| QSGAreaAllocator allocator(QSize(textureSize, textureSize * (++textureCount))); |
| |
| int i; |
| for (i = 0; i < list.size(); ++i) { |
| int glyphIndex = list.at(i).row(); |
| GlyphData &glyphData = glyphDatas[glyphIndex]; |
| |
| QPainterPath path = m_model->path(glyphIndex); |
| glyphData.boundingRect = scaleDown.mapRect(path.boundingRect()); |
| int glyphWidth = qCeil(glyphData.boundingRect.width()) + radius * 2; |
| int glyphHeight = qCeil(glyphData.boundingRect.height()) + radius * 2; |
| |
| glyphData.glyphSize = QSize(glyphWidth + padding * 2, glyphHeight + padding * 2); |
| |
| if (glyphData.glyphSize.width() > qint32(textureSize) |
| || glyphData.glyphSize.height() > qint32(textureSize)) { |
| QMessageBox::warning(this, |
| tr("Glyph too large for texture"), |
| tr("Glyph %1 is too large to fit in texture of size %2.") |
| .arg(glyphIndex).arg(textureSize)); |
| return QByteArray(); |
| } |
| |
| QRect rect = allocator.allocate(glyphData.glyphSize); |
| if (rect.isNull()) |
| break; |
| |
| glyphData.textureIndex = rect.y() / textureSize; |
| while (glyphData.textureIndex >= allocatedAreaPerTexture.size()) |
| allocatedAreaPerTexture.append(QRect(0, 0, 1, 1)); |
| |
| allocatedAreaPerTexture[glyphData.textureIndex] |= QRect(rect.x(), |
| rect.y() % textureSize, |
| rect.width(), |
| rect.height()); |
| |
| glyphData.texCoord.xMargin = QT_DISTANCEFIELD_RADIUS(m_model->doubleGlyphResolution()) / qreal(QT_DISTANCEFIELD_SCALE(m_model->doubleGlyphResolution())); |
| glyphData.texCoord.yMargin = QT_DISTANCEFIELD_RADIUS(m_model->doubleGlyphResolution()) / qreal(QT_DISTANCEFIELD_SCALE(m_model->doubleGlyphResolution())); |
| glyphData.texCoord.x = rect.x() + padding; |
| glyphData.texCoord.y = rect.y() % textureSize + padding; |
| glyphData.texCoord.width = glyphData.boundingRect.width(); |
| glyphData.texCoord.height = glyphData.boundingRect.height(); |
| |
| glyphDatas.append(glyphData); |
| } |
| |
| foundOptimalSize = i == list.size(); |
| if (foundOptimalSize) |
| buffer.write(allocator.serialize()); |
| } |
| } |
| } |
| |
| QVector<QDistanceField> textures; |
| textures.resize(textureCount); |
| |
| for (int textureIndex = 0; textureIndex < textureCount; ++textureIndex) { |
| textures[textureIndex] = QDistanceField(allocatedAreaPerTexture.at(textureIndex).width(), |
| allocatedAreaPerTexture.at(textureIndex).height()); |
| |
| QRect rect = allocatedAreaPerTexture.at(textureIndex); |
| |
| QtdfTextureRecord record; |
| record.allocatedX = qToBigEndian(rect.x()); |
| record.allocatedY = qToBigEndian(rect.y()); |
| record.allocatedWidth = qToBigEndian(rect.width()); |
| record.allocatedHeight = qToBigEndian(rect.height()); |
| record.padding = padding; |
| buffer.write(reinterpret_cast<char *>(&record), |
| sizeof(QtdfTextureRecord)); |
| } |
| |
| { |
| for (int i = 0; i < list.size(); ++i) { |
| int glyphIndex = list.at(i).row(); |
| QImage image = m_model->distanceField(glyphIndex); |
| |
| const GlyphData &glyphData = glyphDatas.at(glyphIndex); |
| |
| QtdfGlyphRecord glyphRecord; |
| glyphRecord.glyphIndex = qToBigEndian(glyphIndex); |
| glyphRecord.textureOffsetX = qToBigEndian(TO_FIXED_POINT(glyphData.texCoord.x)); |
| glyphRecord.textureOffsetY = qToBigEndian(TO_FIXED_POINT(glyphData.texCoord.y)); |
| glyphRecord.textureWidth = qToBigEndian(TO_FIXED_POINT(glyphData.texCoord.width)); |
| glyphRecord.textureHeight = qToBigEndian(TO_FIXED_POINT(glyphData.texCoord.height)); |
| glyphRecord.xMargin = qToBigEndian(TO_FIXED_POINT(glyphData.texCoord.xMargin)); |
| glyphRecord.yMargin = qToBigEndian(TO_FIXED_POINT(glyphData.texCoord.yMargin)); |
| glyphRecord.boundingRectX = qToBigEndian(TO_FIXED_POINT(glyphData.boundingRect.x())); |
| glyphRecord.boundingRectY = qToBigEndian(TO_FIXED_POINT(glyphData.boundingRect.y())); |
| glyphRecord.boundingRectWidth = qToBigEndian(TO_FIXED_POINT(glyphData.boundingRect.width())); |
| glyphRecord.boundingRectHeight = qToBigEndian(TO_FIXED_POINT(glyphData.boundingRect.height())); |
| glyphRecord.textureIndex = qToBigEndian(quint16(glyphData.textureIndex)); |
| buffer.write(reinterpret_cast<char *>(&glyphRecord), sizeof(QtdfGlyphRecord)); |
| |
| int expectedWidth = qCeil(glyphData.texCoord.width + glyphData.texCoord.xMargin * 2); |
| image = image.copy(-padding, -padding, |
| expectedWidth + padding * 2, |
| image.height() + padding * 2); |
| |
| uchar *inBits = image.scanLine(0); |
| uchar *outBits = textures[glyphData.textureIndex].scanLine(int(glyphData.texCoord.y) - padding) |
| + int(glyphData.texCoord.x) - padding; |
| for (int y = 0; y < image.height(); ++y) { |
| memcpy(outBits, inBits, image.width()); |
| inBits += image.bytesPerLine(); |
| outBits += textures[glyphData.textureIndex].width(); |
| } |
| } |
| } |
| |
| for (int i = 0; i < textures.size(); ++i) { |
| const QDistanceField &texture = textures.at(i); |
| const QRect &allocatedArea = allocatedAreaPerTexture.at(i); |
| buffer.write(reinterpret_cast<const char *>(texture.constBits()), |
| allocatedArea.width() * allocatedArea.height()); |
| } |
| |
| PAD_BUFFER(buffer, ret.size()) |
| } |
| |
| return ret; |
| } |
| |
| void MainWindow::writeFile() |
| { |
| Q_ASSERT(!m_fileName.isEmpty()); |
| |
| QFile file(m_fileName); |
| if (file.open(QIODevice::WriteOnly)) { |
| |
| } else { |
| QMessageBox::warning(this, |
| tr("Can't open file for writing"), |
| tr("Unable to open file '%1' for writing").arg(m_fileName), |
| QMessageBox::Ok); |
| } |
| } |
| |
| void MainWindow::openFont() |
| { |
| QString fileName = QFileDialog::getOpenFileName(this, |
| tr("Open font file"), |
| m_fontDir, |
| tr("Fonts (*.ttf *.otf);;All files (*)")); |
| if (!fileName.isEmpty()) |
| open(fileName); |
| } |
| |
| void MainWindow::updateProgressBar() |
| { |
| m_statusBarProgressBar->setValue(m_statusBarProgressBar->value() + 1); |
| updateSelection(); |
| } |
| |
| void MainWindow::startProgressBar(quint16 glyphCount) |
| { |
| ui->action_Open->setDisabled(false); |
| m_statusBarLabel->setText(tr("Generating")); |
| m_statusBarProgressBar->setMaximum(glyphCount); |
| m_statusBarProgressBar->setMinimum(0); |
| m_statusBarProgressBar->setValue(0); |
| m_statusBarProgressBar->setVisible(true); |
| } |
| |
| void MainWindow::stopProgressBar() |
| { |
| m_statusBarLabel->setText(tr("Ready")); |
| m_statusBarProgressBar->setVisible(false); |
| } |
| |
| void MainWindow::selectAll() |
| { |
| QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes(); |
| if (list.size() == ui->lvGlyphs->model()->rowCount()) |
| ui->lvGlyphs->clearSelection(); |
| else |
| ui->lvGlyphs->selectAll(); |
| } |
| |
| void MainWindow::updateSelection() |
| { |
| QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes(); |
| QString label; |
| if (list.size() == ui->lvGlyphs->model()->rowCount()) |
| label = tr("Deselect &All"); |
| else |
| label = tr("Select &All"); |
| |
| ui->tbSelectAll->setText(label); |
| ui->actionSelect_all->setText(label); |
| |
| if (m_model != nullptr && ui->lwUnicodeRanges->count() > 0) { |
| // Ignore selection changes until we are done |
| disconnect(ui->lwUnicodeRanges, &QListWidget::itemSelectionChanged, this, &MainWindow::updateUnicodeRanges); |
| |
| QSet<int> selectedGlyphIndexes; |
| for (const QModelIndex &modelIndex : list) |
| selectedGlyphIndexes.insert(modelIndex.row()); |
| |
| QList<DistanceFieldModel::UnicodeRange> unicodeRanges = m_model->unicodeRanges(); |
| std::sort(unicodeRanges.begin(), unicodeRanges.end()); |
| |
| Q_ASSERT(ui->lwUnicodeRanges->count() == unicodeRanges.size()); |
| for (int i = 0; i < unicodeRanges.size(); ++i) { |
| DistanceFieldModel::UnicodeRange unicodeRange = unicodeRanges.at(i); |
| QListWidgetItem *item = ui->lwUnicodeRanges->item(i); |
| |
| QList<glyph_t> glyphIndexes = m_model->glyphIndexesForUnicodeRange(unicodeRange); |
| Q_ASSERT(!glyphIndexes.isEmpty()); |
| |
| item->setSelected(true); |
| for (glyph_t glyphIndex : glyphIndexes) { |
| if (!selectedGlyphIndexes.contains(glyphIndex)) { |
| item->setSelected(false); |
| break; |
| } |
| } |
| } |
| |
| connect(ui->lwUnicodeRanges, &QListWidget::itemSelectionChanged, this, &MainWindow::updateUnicodeRanges); |
| } |
| } |
| |
| void MainWindow::updateUnicodeRanges() |
| { |
| if (m_model == nullptr) |
| return; |
| |
| disconnect(ui->lvGlyphs->selectionModel(), |
| &QItemSelectionModel::selectionChanged, |
| this, |
| &MainWindow::updateSelection); |
| |
| QItemSelection selectedItems; |
| |
| for (int i = 0; i < ui->lwUnicodeRanges->count(); ++i) { |
| QListWidgetItem *item = ui->lwUnicodeRanges->item(i); |
| if (item->isSelected()) { |
| DistanceFieldModel::UnicodeRange unicodeRange = item->data(Qt::UserRole).value<DistanceFieldModel::UnicodeRange>(); |
| QList<glyph_t> glyphIndexes = m_model->glyphIndexesForUnicodeRange(unicodeRange); |
| |
| for (glyph_t glyphIndex : glyphIndexes) { |
| QModelIndex index = m_model->index(glyphIndex); |
| selectedItems.select(index, index); |
| } |
| } |
| } |
| |
| ui->lvGlyphs->selectionModel()->clearSelection(); |
| if (!selectedItems.isEmpty()) |
| ui->lvGlyphs->selectionModel()->select(selectedItems, QItemSelectionModel::Select); |
| |
| connect(ui->lvGlyphs->selectionModel(), |
| &QItemSelectionModel::selectionChanged, |
| this, |
| &MainWindow::updateSelection); |
| } |
| |
| void MainWindow::populateUnicodeRanges() |
| { |
| QList<DistanceFieldModel::UnicodeRange> unicodeRanges = m_model->unicodeRanges(); |
| std::sort(unicodeRanges.begin(), unicodeRanges.end()); |
| |
| for (DistanceFieldModel::UnicodeRange unicodeRange : unicodeRanges) { |
| QString name = m_model->nameForUnicodeRange(unicodeRange); |
| QListWidgetItem *item = new QListWidgetItem(name, ui->lwUnicodeRanges); |
| item->setData(Qt::UserRole, unicodeRange); |
| } |
| |
| ui->lwUnicodeRanges->setDisabled(false); |
| ui->action_Save->setDisabled(false); |
| ui->action_Save_as->setDisabled(false); |
| ui->tbSave->setDisabled(false); |
| } |
| |
| void MainWindow::displayError(const QString &errorString) |
| { |
| QMessageBox::warning(this, tr("Error when parsing font file"), errorString, QMessageBox::Ok); |
| } |
| |
| void MainWindow::selectString() |
| { |
| QString s = QInputDialog::getText(this, |
| tr("Select glyphs for string"), |
| tr("String to parse:")); |
| if (!s.isEmpty()) { |
| QVector<uint> ucs4String = s.toUcs4(); |
| for (uint ucs4 : ucs4String) { |
| glyph_t glyph = m_model->glyphIndexForUcs4(ucs4); |
| if (glyph != 0) { |
| ui->lvGlyphs->selectionModel()->select(m_model->index(glyph), |
| QItemSelectionModel::Select); |
| } |
| } |
| } |
| } |
| |
| void MainWindow::about() |
| { |
| QMessageBox *msgBox = new QMessageBox(this); |
| msgBox->setAttribute(Qt::WA_DeleteOnClose); |
| msgBox->setWindowTitle(tr("About Qt Distance Field Generator")); |
| msgBox->setText(tr("<h3>Qt Distance Field Generator</h3>" |
| "<p>Version %1.<br/>" |
| "The Qt Distance Field Generator tool allows " |
| "to prepare a font cache for Qt applications.</p>" |
| "<p>Copyright (C) %2 The Qt Company Ltd.</p>") |
| .arg(QLatin1String(QT_VERSION_STR)) |
| .arg(QLatin1String("2019"))); |
| msgBox->show(); |
| } |
| |
| QT_END_NAMESPACE |