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 }