1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 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 
31 #include <QFontComboBox>
32 #include <QFontDatabase>
33 #include <QFileDialog>
34 #include <QMessageBox>
35 #include <QListWidget>
36 #include <QDebug>
37 #include <QShortcut>
38 #include <QCompleter>
39 #include <QDirModel>
40 #include <QRegularExpression>
41 #include <QTextCodec>
42 
43 QT_BEGIN_NAMESPACE
44 
45 MainWindow::MainWindow(const QString &customFont)
46 {
47     setupUi(this);
48     pixelSize->setValue(QFontInfo(QFont()).pixelSize());
49     populateCharacterRanges();
50 
51     {
52         weightCombo->addItem(QLatin1String("Light"), QVariant(int(QFont::Light)));
53         const int normalIdx = weightCombo->count();
54         weightCombo->addItem(QLatin1String("Normal"), QVariant(int(QFont::Normal)));
55         weightCombo->addItem(QLatin1String("DemiBold"), QVariant(int(QFont::DemiBold)));
56         weightCombo->addItem(QLatin1String("Bold"), QVariant(int(QFont::Bold)));
57         weightCombo->addItem(QLatin1String("Black"), QVariant(int(QFont::Black)));
58 
59         weightCombo->setCurrentIndex(normalIdx);
60     }
61 
62     QShortcut *sc = new QShortcut(Qt::ControlModifier + Qt::Key_A, this);
63     connect(sc, SIGNAL(activated()),
64             this, SLOT(on_selectAll_clicked()));
65     sc = new QShortcut(Qt::ControlModifier + Qt::Key_D, this);
66     connect(sc, SIGNAL(activated()),
67             this, SLOT(on_deselectAll_clicked()));
68     sc = new QShortcut(Qt::ControlModifier + Qt::Key_I, this);
69     connect(sc, SIGNAL(activated()),
70             this, SLOT(on_invertSelection_clicked()));
71 
72     QCompleter *completer = new QCompleter(this);
73     completer->setModel(new QDirModel(this));
74     path->setCompleter(completer);
75     path->setText(QDir::currentPath());
76 
77     completer = new QCompleter(this);
78     completer->setModel(new QDirModel(this));
79     sampleFile->setCompleter(completer);
80     charCount->setText(QString());
81 
82     if (!customFont.isEmpty())
83         addCustomFont(customFont);
84 
85     fontChanged();
86 
87     connect(fontComboBox, SIGNAL(currentFontChanged(QFont)),
88             this, SLOT(fontChanged()));
89     connect(pixelSize, SIGNAL(valueChanged(int)),
90             this, SLOT(fontChanged()));
91     connect(italic, SIGNAL(stateChanged(int)),
92             this, SLOT(fontChanged()));
93     connect(weightCombo, SIGNAL(currentIndexChanged(int)),
94             this, SLOT(fontChanged()));
95 }
96 
97 void MainWindow::on_actionAdd_Custom_Font_triggered()
98 {
99     QString fontFile = QFileDialog::getOpenFileName(this, tr("Add Custom Font"));
100     if (fontFile.isEmpty())
101         return;
102     addCustomFont(fontFile);
103 }
104 
105 void MainWindow::on_selectAll_clicked()
106 {
107     for (int i = 0; i < characterRangeView->count(); ++i)
108         characterRangeView->item(i)->setCheckState(Qt::Checked);
109 }
110 
111 void MainWindow::on_deselectAll_clicked()
112 {
113     for (int i = 0; i < characterRangeView->count(); ++i)
114         characterRangeView->item(i)->setCheckState(Qt::Unchecked);
115 }
116 
117 void MainWindow::on_invertSelection_clicked()
118 {
119     for (int i = 0; i < characterRangeView->count(); ++i) {
120         QListWidgetItem *item = characterRangeView->item(i);
121         if (item->checkState() == Qt::Checked)
122             item->setCheckState(Qt::Unchecked);
123         else
124             item->setCheckState(Qt::Checked);
125     }
126 }
127 
128 void MainWindow::fontChanged()
129 {
130     QFont f = preview->font();
131     f.setStyleStrategy(QFont::NoFontMerging);
132     f.setPixelSize(pixelSize->value());
133     f.setFamily(fontComboBox->currentFont().family());
134     f.setItalic(italic->isChecked());
135     f.setWeight(weightCombo->itemData(weightCombo->currentIndex()).toInt());
136 
137     if (!preview->isModified()) {
138         QFontDatabase db;
139         QFontDatabase::WritingSystem ws = db.writingSystems(f.family()).value(0, QFontDatabase::Any);
140         QString sample = db.writingSystemSample(ws);
141         preview->setText(sample);
142         preview->setModified(false);
143     }
144 
145     fileName->setText(QPF::fileNameForFont(f));
146 
147     preview->setFont(f);
148 }
149 
150 void MainWindow::on_browsePath_clicked()
151 {
152     QString dir = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
153     if (!dir.isEmpty())
154         path->setText(dir);
155 }
156 
157 void MainWindow::on_browseSampleFile_clicked()
158 {
159     QString dir = QFileDialog::getOpenFileName(this, tr("Select Sample File"));
160     if (!dir.isEmpty()) {
161         sampleFile->setText(dir);
162         on_sampleFile_editingFinished();
163     }
164 }
165 
166 void MainWindow::on_generate_clicked()
167 {
168     QFile f(path->text() + QDir::separator() + fileName->text());
169     if (f.exists()) {
170         if (QMessageBox::warning(this, QString(),
171                                  tr("%1 already exists.\nDo you want to replace it?").arg(f.fileName()),
172                                  QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
173             != QMessageBox::Yes) {
174             statusBar()->showMessage(tr("Pre-rendering aborted..."));
175             return;
176         }
177     }
178 
179     QList<QPF::CharacterRange> ranges;
180 
181     if (chooseFromSampleFile->isChecked()) {
182         ranges = sampleFileRanges;
183     } else if (chooseFromCodePoints->isChecked()) {
184         ranges.clear();
185         for (int i = 0; i < characterRangeView->count(); ++i) {
186             QListWidgetItem *item = characterRangeView->item(i);
187             if (item->checkState() != Qt::Checked)
188                 continue;
189 
190             QPF::CharacterRange range = qvariant_cast<QPF::CharacterRange>(item->data(Qt::UserRole));
191             ranges.append(range);
192         }
193     }
194 
195     const int generationOptions = QPF::IncludeCMap | QPF::RenderGlyphs;
196     QByteArray qpf = QPF::generate(preview->font(), generationOptions, ranges);
197     f.open(QIODevice::WriteOnly | QIODevice::Truncate);
198     f.write(qpf);
199     f.close();
200 
201     statusBar()->showMessage(tr("Font successfully pre-rendered to %1").arg(fileName->text()));
202 }
203 
204 void MainWindow::on_sampleFile_editingFinished()
205 {
206     sampleFileRanges.clear();
207     QFile f(sampleFile->text());
208     if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
209         sampleFileRanges.append(QPF::CharacterRange()); // default = all
210         return;
211     }
212     QTextStream stream(&f);
213     stream.setCodec(QTextCodec::codecForName("utf-8"));
214     stream.setAutoDetectUnicode(true);
215     QString text = stream.readAll();
216 
217     QSet<QChar> coverage;
218     for (int i = 0; i < text.length(); ++i)
219         coverage.insert(text.at(i));
220 
221     QList<QChar> sortedCoverage = QList<QChar>::fromSet(coverage);
222     std::sort(sortedCoverage.begin(), sortedCoverage.end());
223     // play simple :)
224     for (QChar ch : qAsConst(sortedCoverage)) {
225         QPF::CharacterRange r;
226         r.start = ch.unicode();
227         r.end = r.start + 1;
228         sampleFileRanges.append(r);
229     }
230 
231     charCount->setText(tr("(%1 unique characters found)").arg(sortedCoverage.count()));
232 }
233 
234 void MainWindow::populateCharacterRanges()
235 {
236     QFile f(":/Blocks.txt");
237     if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
238         return;
239 
240     QRegularExpression rangeExpr(
241          QRegularExpression::anchoredPattern("([0-9a-f]+)\\.\\.([0-9a-f]+); (.+)"),
242          QRegularExpression::CaseInsensitiveOption
243     );
244 
245     QString ellipsis(QChar(0x2026));
246     if (!characterRangeView->fontMetrics().inFont(ellipsis.at(0)))
247         ellipsis = QLatin1String("...");
248 
249     while (!f.atEnd()) {
250         QString line = QString::fromLatin1(f.readLine());
251 
252         if (line.endsWith(QLatin1Char('\n')))
253             line.chop(1);
254         if (line.endsWith(QLatin1Char('\r')))
255             line.chop(1);
256 
257         line = line.trimmed();
258 
259         if (line.isEmpty() || line.startsWith(QLatin1Char('#')))
260             continue;
261 
262         QRegularExpressionMatch match = rangeExpr.match(line);
263         if (!match.hasMatch())
264             continue;
265 
266         QPF::CharacterRange range;
267 
268         bool ok = false;
269         range.start = match.captured(1).toUInt(&ok, /*base*/16);
270         if (!ok)
271             continue;
272         range.end = match.captured(2).toUInt(&ok, /*base*/16);
273         if (!ok)
274             continue;
275 
276         if (range.start >= 0xffff || range.end >= 0xffff)
277             continue;
278 
279         QString description = match.captured(3);
280 
281         QListWidgetItem *item = new QListWidgetItem(characterRangeView);
282         QString text = description;
283         text.append(QLatin1String(" ("));
284         text.append(match.captured(1));
285         text.append(ellipsis);
286         text.append(match.captured(2));
287         text.append(QLatin1String(")"));
288         item->setText(text);
289         item->setCheckState(Qt::Checked);
290 
291         item->setData(Qt::UserRole, QVariant::fromValue(range));
292     }
293 }
294 
295 void MainWindow::addCustomFont(const QString &fontFile)
296 {
297     int id = QFontDatabase::addApplicationFont(fontFile);
298     if (id < 0) {
299         QMessageBox::warning(this, tr("Error Adding Custom Font"),
300                              tr("The custom font %s could not be loaded.").arg(fontFile));
301         return;
302     }
303     const QStringList families = QFontDatabase::applicationFontFamilies(id);
304     if (families.isEmpty()) {
305         QMessageBox::warning(this, tr("Error Adding Custom Font"),
306                              tr("The custom font %s provides no font families.").arg(fontFile));
307         return;
308     }
309     QFont f(families.first());
310     fontComboBox->setCurrentFont(f);
311 }
312 
313 QT_END_NAMESPACE
314