File indexing completed on 2024-05-12 16:06:37

0001 // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*-
0002 //
0003 // fontpool.cpp
0004 //
0005 // SPDX-FileCopyrightText: 2001-2005 Stefan Kebekus
0006 // SPDX-License-Identifier: GPL-2.0-or-later
0007 
0008 #include <config.h>
0009 
0010 #include "TeXFont.h"
0011 #include "debug_dvi.h"
0012 #include "fontpool.h"
0013 
0014 #include <KLocalizedString>
0015 
0016 #include <QApplication>
0017 #include <QPainter>
0018 #include <QStandardPaths>
0019 
0020 #include <cmath>
0021 #include <math.h>
0022 
0023 //#define DEBUG_FONTPOOL
0024 
0025 // List of permissible MetaFontModes which are supported by kdvi.
0026 
0027 // const char *MFModes[]       = { "cx", "ljfour", "lexmarks" };
0028 // const char *MFModenames[]   = { "Canon CX", "LaserJet 4", "Lexmark S" };
0029 // const int   MFResolutions[] = { 300, 600, 1200 };
0030 
0031 #ifdef PERFORMANCE_MEASUREMENT
0032 QTime fontPoolTimer;
0033 bool fontPoolTimerFlag;
0034 #endif
0035 
0036 fontPool::fontPool(bool useFontHinting)
0037 {
0038 #ifdef DEBUG_FONTPOOL
0039     qCDebug(OkularDviDebug) << "fontPool::fontPool() called";
0040 #endif
0041 
0042     setObjectName(QStringLiteral("Font Pool"));
0043 
0044     displayResolution_in_dpi = 100.0; // A not-too-bad-default
0045     useFontHints = useFontHinting;
0046     CMperDVIunit = 0;
0047     extraSearchPath.clear();
0048 
0049 #ifdef HAVE_FREETYPE
0050     // Initialize the Freetype Library
0051     if (FT_Init_FreeType(&FreeType_library) != 0) {
0052         qCCritical(OkularDviDebug) << "Cannot load the FreeType library. KDVI proceeds without FreeType support.";
0053         FreeType_could_be_loaded = false;
0054     } else {
0055         FreeType_could_be_loaded = true;
0056     }
0057 #endif
0058 
0059     // Check if the QT library supports the alpha channel of
0060     // QImages. Experiments show that --depending of the configuration
0061     // of QT at compile and runtime or the availability of the XFt
0062     // extension, alpha channels are either supported, or silently
0063     // ignored.
0064     QImage start(1, 1, QImage::Format_ARGB32); // Generate a 1x1 image, black with alpha=0x10
0065     quint32 *destScanLine = reinterpret_cast<quint32 *>(start.scanLine(0));
0066     *destScanLine = 0x80000000;
0067     QPixmap intermediate = QPixmap::fromImage(start);
0068     QPixmap dest(1, 1);
0069     dest.fill(Qt::white);
0070     QPainter paint(&dest);
0071     paint.drawPixmap(0, 0, intermediate);
0072     paint.end();
0073     start = dest.toImage().convertToFormat(QImage::Format_ARGB32);
0074     quint8 result = *(start.scanLine(0)) & 0xff;
0075 
0076     if ((result == 0xff) || (result == 0x00)) {
0077 #ifdef DEBUG_FONTPOOL
0078         qCDebug(OkularDviDebug) << "fontPool::fontPool(): QPixmap does not support the alpha channel";
0079 #endif
0080         QPixmapSupportsAlpha = false;
0081     } else {
0082 #ifdef DEBUG_FONTPOOL
0083         qCDebug(OkularDviDebug) << "fontPool::fontPool(): QPixmap supports the alpha channel";
0084 #endif
0085         QPixmapSupportsAlpha = true;
0086     }
0087 }
0088 
0089 fontPool::~fontPool()
0090 {
0091 #ifdef DEBUG_FONTPOOL
0092     qCDebug(OkularDviDebug) << "fontPool::~fontPool() called";
0093 #endif
0094 
0095     // need to manually clear the fonts _before_ freetype gets unloaded
0096     qDeleteAll(fontList);
0097     fontList.clear();
0098 
0099 #ifdef HAVE_FREETYPE
0100     if (FreeType_could_be_loaded == true) {
0101         FT_Done_FreeType(FreeType_library);
0102     }
0103 #endif
0104 }
0105 
0106 void fontPool::setParameters(bool _useFontHints)
0107 {
0108     // Check if glyphs need to be cleared
0109     if (_useFontHints != useFontHints) {
0110         double displayResolution = displayResolution_in_dpi;
0111         QList<TeXFontDefinition *>::iterator it_fontp = fontList.begin();
0112         for (; it_fontp != fontList.end(); ++it_fontp) {
0113             TeXFontDefinition *fontp = *it_fontp;
0114             fontp->setDisplayResolution(displayResolution * fontp->enlargement);
0115         }
0116     }
0117 
0118     useFontHints = _useFontHints;
0119 }
0120 
0121 TeXFontDefinition *fontPool::appendx(const QString &fontname, quint32 checksum, quint32 scale, double enlargement)
0122 {
0123     // Reuse font if possible: check if a font with that name and
0124     // natural resolution is already in the fontpool, and use that, if
0125     // possible.
0126     QList<TeXFontDefinition *>::iterator it_fontp = fontList.begin();
0127     for (; it_fontp != fontList.end(); ++it_fontp) {
0128         TeXFontDefinition *fontp = *it_fontp;
0129         if ((fontname == fontp->fontname) && ((int)(enlargement * 1000.0 + 0.5)) == (int)(fontp->enlargement * 1000.0 + 0.5)) {
0130             // if font is already in the list
0131             fontp->mark_as_used();
0132             return fontp;
0133         }
0134     }
0135 
0136     // If font doesn't exist yet, we have to generate a new font.
0137 
0138     double displayResolution = displayResolution_in_dpi;
0139 
0140     TeXFontDefinition *fontp = new TeXFontDefinition(fontname, displayResolution * enlargement, checksum, scale, this, enlargement);
0141     if (fontp == nullptr) {
0142         qCCritical(OkularDviDebug) << "Could not allocate memory for a font structure";
0143         exit(0);
0144     }
0145     fontList.append(fontp);
0146 
0147 #ifdef PERFORMANCE_MEASUREMENT
0148     fontPoolTimer.start();
0149     fontPoolTimerFlag = false;
0150 #endif
0151 
0152     // Now start kpsewhich/MetaFont, etc. if necessary
0153     return fontp;
0154 }
0155 
0156 bool fontPool::areFontsLocated()
0157 {
0158 #ifdef DEBUG_FONTPOOL
0159     qCDebug(OkularDviDebug) << "fontPool::areFontsLocated() called";
0160 #endif
0161 
0162     // Is there a font whose name we did not try to find out yet?
0163     QList<TeXFontDefinition *>::const_iterator cit_fontp = fontList.constBegin();
0164     for (; cit_fontp != fontList.constEnd(); ++cit_fontp) {
0165         TeXFontDefinition *fontp = *cit_fontp;
0166         if (!fontp->isLocated()) {
0167             return false;
0168         }
0169     }
0170 
0171 #ifdef DEBUG_FONTPOOL
0172     qCDebug(OkularDviDebug) << "... yes, all fonts are located (but not necessarily loaded).";
0173 #endif
0174     return true; // That says that all fonts are located.
0175 }
0176 
0177 void fontPool::locateFonts()
0178 {
0179     kpsewhichOutput.clear();
0180 
0181     // First, we try and find those fonts which exist on disk
0182     // already. If virtual fonts are found, they will add new fonts to
0183     // the list of fonts whose font files need to be located, so that we
0184     // repeat the lookup.
0185     bool vffound;
0186     do {
0187         vffound = false;
0188         locateFonts(false, false, &vffound);
0189     } while (vffound);
0190 
0191     // If still not all fonts are found, look again, this time with
0192     // on-demand generation of PK fonts enabled.
0193     if (!areFontsLocated()) {
0194         locateFonts(true, false);
0195     }
0196 
0197     // If still not all fonts are found, we look for TFM files as a last
0198     // resort, so that we can at least draw filled rectangles for
0199     // characters.
0200     if (!areFontsLocated()) {
0201         locateFonts(false, true);
0202     }
0203 
0204     // If still not all fonts are found, we give up. We mark all fonts
0205     // as 'located', so that we won't look for them any more, and
0206     // present an error message to the user.
0207     if (!areFontsLocated()) {
0208         markFontsAsLocated();
0209         Q_EMIT error(i18n("<qt><p>Okular was not able to locate all the font files "
0210                           "which are necessary to display the current DVI file. "
0211                           "Your document might be unreadable.</p>"
0212                           "<p><small><b>PATH:</b> %1</small></p>"
0213                           "<p><small>%2</small></p></qt>",
0214                           QString::fromLocal8Bit(qgetenv("PATH")),
0215                           kpsewhichOutput.replace(QLatin1String("\n"), QLatin1String("<br/>"))),
0216                      -1);
0217     }
0218 }
0219 
0220 void fontPool::locateFonts(bool makePK, bool locateTFMonly, bool *virtualFontsFound)
0221 {
0222     // Make sure kpsewhich is in PATH and not just in the CWD
0223     static const QString kpsewhichFullPath = QStandardPaths::findExecutable(QStringLiteral("kpsewhich"));
0224     if (kpsewhichFullPath.isEmpty()) {
0225         return;
0226     }
0227 
0228     // Set up the kpsewhich process. If pass == 0, look for vf-fonts and
0229     // disable automatic font generation as vf-fonts can't be
0230     // generated. If pass == 0, enable font generation, if it was
0231     // enabled globally.
0232 
0233     // Now generate the command line for the kpsewhich
0234     // program. Unfortunately, this can be rather long and involved...
0235     QStringList kpsewhich_args;
0236     kpsewhich_args << QStringLiteral("--dpi") << QStringLiteral("1200") << QStringLiteral("--mode") << QStringLiteral("lexmarks");
0237 
0238     // Disable automatic pk-font generation.
0239     kpsewhich_args << QString::fromLocal8Bit(makePK ? "--mktex" : "--no-mktex") << QStringLiteral("pk");
0240 
0241     // Names of fonts that shall be located
0242     quint16 numFontsInJob = 0;
0243     QList<TeXFontDefinition *>::const_iterator cit_fontp = fontList.constBegin();
0244     for (; cit_fontp != fontList.constEnd(); ++cit_fontp) {
0245         TeXFontDefinition *fontp = *cit_fontp;
0246         if (!fontp->isLocated()) {
0247             numFontsInJob++;
0248 
0249             if (locateTFMonly == true) {
0250                 kpsewhich_args << QStringLiteral("%1.tfm").arg(fontp->fontname);
0251             } else {
0252 #ifdef HAVE_FREETYPE
0253                 if (FreeType_could_be_loaded == true) {
0254                     const QString &filename = fontsByTeXName.findFileName(fontp->fontname);
0255                     if (!filename.isEmpty()) {
0256                         kpsewhich_args << QStringLiteral("%1").arg(filename);
0257                     }
0258                 }
0259 #endif
0260                 kpsewhich_args << QStringLiteral("%1.vf").arg(fontp->fontname) << QStringLiteral("%1.1200pk").arg(fontp->fontname);
0261             }
0262         }
0263     }
0264 
0265     if (numFontsInJob == 0) {
0266         return;
0267     }
0268 
0269     // If PK fonts are generated, the kpsewhich command will re-route
0270     // the output of MetaFont into its stderr. Here we make sure this
0271     // output is intercepted and parsed.
0272     kpsewhich_ = std::make_unique<QProcess>();
0273     connect(kpsewhich_.get(), &QProcess::readyReadStandardError, this, &fontPool::mf_output_receiver);
0274 
0275     // Now run... kpsewhich. In case of error, kick up a fuss.
0276     // This string is not going to be quoted, as it might be were it
0277     // a real command line, but who cares?
0278     const QString kpsewhich_exe = QStringLiteral("kpsewhich");
0279     kpsewhichOutput += QStringLiteral("<b>") + kpsewhich_exe + QLatin1Char(' ') + kpsewhich_args.join(QStringLiteral(" ")) + QStringLiteral("</b>");
0280 
0281     kpsewhich_->start(kpsewhichFullPath, kpsewhich_args, QIODevice::ReadOnly | QIODevice::Text);
0282     if (!kpsewhich_->waitForStarted()) {
0283         QApplication::restoreOverrideCursor();
0284         Q_EMIT error(i18n("<qt><p>There were problems running <em>kpsewhich</em>. As a result, "
0285                           "some font files could not be located, and your document might be unreadable.<br/>"
0286                           "Possible reason: the <em>kpsewhich</em> program is perhaps not installed on your system, "
0287                           "or it cannot be found in the current search path.</p>"
0288                           "<p><small><b>PATH:</b> %1</small></p>"
0289                           "<p><small>%2</small></p></qt>",
0290                           QString::fromLocal8Bit(qgetenv("PATH")),
0291                           kpsewhichOutput.replace(QLatin1String("\n"), QLatin1String("<br/>"))),
0292                      -1);
0293 
0294         // This makes sure the we don't try to run kpsewhich again
0295         markFontsAsLocated();
0296         kpsewhich_.reset();
0297         return;
0298     }
0299     // We wait here while the external program runs concurrently.
0300     kpsewhich_->waitForFinished();
0301 
0302     // Handle fatal errors.
0303     int const kpsewhich_exit_code = kpsewhich_->exitCode();
0304     if (kpsewhich_exit_code < 0) {
0305         Q_EMIT warning(i18n("<qt>The font generation by <em>kpsewhich</em> was aborted (exit code %1, error %2). As a "
0306                             "result, some font files could not be located, and your document might be unreadable.</qt>",
0307                             kpsewhich_exit_code,
0308                             kpsewhich_->errorString()),
0309                        -1);
0310 
0311         // This makes sure the we don't try to run kpsewhich again
0312         if (makePK == false) {
0313             markFontsAsLocated();
0314         }
0315     }
0316 
0317     // Create a list with all filenames found by the kpsewhich program.
0318     const QStringList fileNameList = QString::fromLocal8Bit(kpsewhich_->readAll()).split(QLatin1Char('\n'), Qt::SkipEmptyParts);
0319 
0320     // Now associate the file names found with the fonts
0321     QList<TeXFontDefinition *>::iterator it_fontp = fontList.begin();
0322     for (; it_fontp != fontList.end(); ++it_fontp) {
0323         TeXFontDefinition *fontp = *it_fontp;
0324 
0325         if (fontp->filename.isEmpty() == true) {
0326             QStringList matchingFiles;
0327 #ifdef HAVE_FREETYPE
0328             const QString &fn = fontsByTeXName.findFileName(fontp->fontname);
0329             if (!fn.isEmpty()) {
0330                 matchingFiles = fileNameList.filter(fn);
0331             }
0332 #endif
0333             if (matchingFiles.isEmpty() == true) {
0334                 matchingFiles += fileNameList.filter(QLatin1Char('/') + fontp->fontname + QLatin1Char('.'));
0335             }
0336 
0337             if (matchingFiles.isEmpty() != true) {
0338 #ifdef DEBUG_FONTPOOL
0339                 qCDebug(OkularDviDebug) << "Associated " << fontp->fontname << " to " << matchingFiles.first();
0340 #endif
0341                 QString fname = matchingFiles.first();
0342                 fontp->fontNameReceiver(fname);
0343                 fontp->flags |= TeXFontDefinition::FONT_KPSE_NAME;
0344                 if (fname.endsWith(QLatin1String(".vf"))) {
0345                     if (virtualFontsFound != nullptr) {
0346                         *virtualFontsFound = true;
0347                     }
0348                     // Constructing a virtual font will most likely insert other
0349                     // fonts into the fontList. After that, fontList.next() will
0350                     // no longer work. It is therefore safer to start over.
0351                     it_fontp = fontList.begin();
0352                     continue;
0353                 }
0354             }
0355         } // of if (fontp->filename.isEmpty() == true)
0356     }
0357     kpsewhich_.reset();
0358 }
0359 
0360 void fontPool::setCMperDVIunit(double _CMperDVI)
0361 {
0362 #ifdef DEBUG_FONTPOOL
0363     qCDebug(OkularDviDebug) << "fontPool::setCMperDVIunit( " << _CMperDVI << " )";
0364 #endif
0365 
0366     if (CMperDVIunit == _CMperDVI) {
0367         return;
0368     }
0369 
0370     CMperDVIunit = _CMperDVI;
0371 
0372     QList<TeXFontDefinition *>::iterator it_fontp = fontList.begin();
0373     for (; it_fontp != fontList.end(); ++it_fontp) {
0374         TeXFontDefinition *fontp = *it_fontp;
0375         fontp->setDisplayResolution(displayResolution_in_dpi * fontp->enlargement);
0376     }
0377 }
0378 
0379 void fontPool::setDisplayResolution(double _displayResolution_in_dpi)
0380 {
0381 #ifdef DEBUG_FONTPOOL
0382     qCDebug(OkularDviDebug) << "fontPool::setDisplayResolution( displayResolution_in_dpi=" << _displayResolution_in_dpi << " ) called";
0383 #endif
0384 
0385     // Ignore minute changes by less than 2 DPI. The difference would
0386     // hardly be visible anyway. That saves a lot of re-painting,
0387     // e.g. when the user resizes the window, and a flickery mouse
0388     // changes the window size by 1 pixel all the time.
0389     if (fabs(displayResolution_in_dpi - _displayResolution_in_dpi) <= 2.0) {
0390 #ifdef DEBUG_FONTPOOL
0391         qCDebug(OkularDviDebug) << "fontPool::setDisplayResolution(...): resolution wasn't changed. Aborting.";
0392 #endif
0393         return;
0394     }
0395 
0396     displayResolution_in_dpi = _displayResolution_in_dpi;
0397     double displayResolution = displayResolution_in_dpi;
0398 
0399     QList<TeXFontDefinition *>::iterator it_fontp = fontList.begin();
0400     for (; it_fontp != fontList.end(); ++it_fontp) {
0401         TeXFontDefinition *fontp = *it_fontp;
0402         fontp->setDisplayResolution(displayResolution * fontp->enlargement);
0403     }
0404 
0405     // Do something that causes re-rendering of the dvi-window
0406     /*@@@@
0407     Q_EMIT fonts_have_been_loaded(this);
0408     */
0409 }
0410 
0411 void fontPool::markFontsAsLocated()
0412 {
0413     QList<TeXFontDefinition *>::iterator it_fontp = fontList.begin();
0414     for (; it_fontp != fontList.end(); ++it_fontp) {
0415         TeXFontDefinition *fontp = *it_fontp;
0416         fontp->markAsLocated();
0417     }
0418 }
0419 
0420 void fontPool::mark_fonts_as_unused()
0421 {
0422 #ifdef DEBUG_FONTPOOL
0423     qCDebug(OkularDviDebug) << "fontPool::mark_fonts_as_unused() called";
0424 #endif
0425 
0426     QList<TeXFontDefinition *>::iterator it_fontp = fontList.begin();
0427     for (; it_fontp != fontList.end(); ++it_fontp) {
0428         TeXFontDefinition *fontp = *it_fontp;
0429         fontp->flags &= ~TeXFontDefinition::FONT_IN_USE;
0430     }
0431 }
0432 
0433 void fontPool::release_fonts()
0434 {
0435 #ifdef DEBUG_FONTPOOL
0436     qCDebug(OkularDviDebug) << "Release_fonts";
0437 #endif
0438 
0439     QMutableListIterator<TeXFontDefinition *> it_fontp(fontList);
0440     while (it_fontp.hasNext()) {
0441         TeXFontDefinition *fontp = it_fontp.next();
0442         if ((fontp->flags & TeXFontDefinition::FONT_IN_USE) != TeXFontDefinition::FONT_IN_USE) {
0443             delete fontp;
0444             it_fontp.remove();
0445         }
0446     }
0447 }
0448 
0449 void fontPool::mf_output_receiver()
0450 {
0451     if (kpsewhich_) {
0452         const QString output_data = QString::fromLocal8Bit(kpsewhich_->readAllStandardError());
0453         kpsewhichOutput.append(output_data);
0454         MetafontOutput.append(output_data);
0455     }
0456 
0457     // We'd like to print only full lines of text.
0458     int numleft;
0459     while ((numleft = MetafontOutput.indexOf(QLatin1Char('\n'))) != -1) {
0460         QString line = MetafontOutput.left(numleft + 1);
0461 #ifdef DEBUG_FONTPOOL
0462         qCDebug(OkularDviDebug) << "MF OUTPUT RECEIVED: " << line;
0463 #endif
0464         // If the Output of the kpsewhich program contains a line starting
0465         // with "kpathsea:", this means that a new MetaFont-run has been
0466         // started. We filter these lines out and update the display
0467         // accordingly.
0468         int startlineindex = line.indexOf(QStringLiteral("kpathsea:"));
0469         if (startlineindex != -1) {
0470             int endstartline = line.indexOf(QStringLiteral("\n"), startlineindex);
0471             QString startLine = line.mid(startlineindex, endstartline - startlineindex);
0472 
0473             // The last word in the startline is the name of the font which we
0474             // are generating. The second-to-last word is the resolution in
0475             // dots per inch. Display this info in the text label below the
0476             // progress bar.
0477             int lastblank = startLine.lastIndexOf(QLatin1Char(' '));
0478             QString fontName = startLine.mid(lastblank + 1);
0479             int secondblank = startLine.lastIndexOf(QLatin1Char(' '), lastblank - 1);
0480             QString dpi = startLine.mid(secondblank + 1, lastblank - secondblank - 1);
0481 
0482             Q_EMIT warning(i18n("Currently generating %1 at %2 dpi...", fontName, dpi), -1);
0483         }
0484         MetafontOutput = MetafontOutput.remove(0, numleft + 1);
0485     }
0486 }