summaryrefslogtreecommitdiffstats
path: root/kdvi/fontpool.cpp
blob: adec497b119937841bfa889ddd45892ecb0d2e60 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
//
// fontpool.cpp
//
// (C) 2001-2004 Stefan Kebekus
// Distributed under the GPL

#include <config.h>

#include <kdebug.h>
#include <kinstance.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kprocess.h>
#include <kprocio.h>
#include <math.h>
#include <qapplication.h>
#include <qfile.h>
#include <qimage.h>
#include <qpainter.h>
#include <stdlib.h>

#include "fontpool.h"
#include "performanceMeasurement.h"
#include "prefs.h"
#include "TeXFont.h"

//#define DEBUG_FONTPOOL



// List of permissible MetaFontModes which are supported by kdvi.

//const char *MFModes[]       = { "cx", "ljfour", "lexmarks" };
//const char *MFModenames[]   = { "Canon CX", "LaserJet 4", "Lexmark S" };
//const int   MFResolutions[] = { 300, 600, 1200 };

#ifdef PERFORMANCE_MEASUREMENT
QTime fontPoolTimer;
bool fontPoolTimerFlag;
#endif

//#define DEBUG_FONTPOOL

fontPool::fontPool()
  :  progress( "fontgen",  // Chapter in the documentation for help.
	       i18n( "KDVI is currently generating bitmap fonts..." ),
	       i18n( "Aborts the font generation. Don't do this." ),
	       i18n( "KDVI is currently generating bitmap fonts which are needed to display your document. "
		     "For this, KDVI uses a number of external programs, such as MetaFont. You can find "
		     "the output of these programs later in the document info dialog." ),
	       i18n( "KDVI is generating fonts. Please wait." ),
	       0 )
{
#ifdef DEBUG_FONTPOOL
  kdDebug(4300) << "fontPool::fontPool() called" << endl;
#endif

  setName("Font Pool");

  displayResolution_in_dpi = 100.0; // A not-too-bad-default
  useFontHints             = true;
  CMperDVIunit             = 0;
  extraSearchPath          = QString::null;
  fontList.setAutoDelete(true);


#ifdef HAVE_FREETYPE
  // Initialize the Freetype Library
  if ( FT_Init_FreeType( &FreeType_library ) != 0 ) {
    kdError(4300) << "Cannot load the FreeType library. KDVI proceeds without FreeType support." << endl;
    FreeType_could_be_loaded = false;
  } else
    FreeType_could_be_loaded = true;
#endif

  // Check if the QT library supports the alpha channel of
  // pixmaps. Experiments show that --depending of the configuration
  // of QT at compile and runtime or the availability of the XFt
  // extension, alpha channels are either supported, or silently
  // converted to 1-bit masks.
  QImage start(1, 1, 32); // Generate a 1x1 image, black with alpha=0x10
  start.setAlphaBuffer(true);
  Q_UINT32 *destScanLine = (Q_UINT32 *)start.scanLine(0);
  *destScanLine = 0x80000000;
  QPixmap intermediate(start);
  QPixmap dest(1,1);
  dest.fill(Qt::white);
  QPainter paint( &dest );
  paint.drawPixmap(0, 0, intermediate);
  paint.end();
  start = dest.convertToImage().convertDepth(32);
  Q_UINT8 result = *(start.scanLine(0)) & 0xff;

  if ((result == 0xff) || (result == 0x00)) {
#ifdef DEBUG_FONTPOOL
    kdDebug(4300) << "fontPool::fontPool(): QPixmap does not support the alpha channel" << endl;
#endif
    QPixmapSupportsAlpha = false;
  } else {
#ifdef DEBUG_FONTPOOL
    kdDebug(4300) << "fontPool::fontPool(): QPixmap supports the alpha channel" << endl;
#endif
    QPixmapSupportsAlpha = true;
  }
}


fontPool::~fontPool()
{
#ifdef DEBUG_FONTPOOL
  kdDebug(4300) << "fontPool::~fontPool() called" << endl;
#endif

  // need to manually clear the fonts _before_ freetype gets unloaded
  fontList.clear();

#ifdef HAVE_FREETYPE
  if (FreeType_could_be_loaded == true)
    FT_Done_FreeType( FreeType_library );
#endif
}


void fontPool::setParameters( bool _useFontHints )
{
  // Check if glyphs need to be cleared
  if (_useFontHints != useFontHints) {
    double displayResolution = displayResolution_in_dpi;
    TeXFontDefinition *fontp = fontList.first();
    while(fontp != 0 ) {
      fontp->setDisplayResolution(displayResolution * fontp->enlargement);
      fontp=fontList.next();
    }
  }

  useFontHints = _useFontHints;
}


TeXFontDefinition* fontPool::appendx(const QString& fontname, Q_UINT32 checksum, Q_UINT32 scale, double enlargement)
{
  // Reuse font if possible: check if a font with that name and
  // natural resolution is already in the fontpool, and use that, if
  // possible.
  TeXFontDefinition *fontp = fontList.first();
  while( fontp != 0 ) {
    if ((fontname == fontp->fontname) && ( (int)(enlargement*1000.0+0.5)) == (int)(fontp->enlargement*1000.0+0.5)) {
      // if font is already in the list
      fontp->mark_as_used();
      return fontp;
    }
    fontp=fontList.next();
  }

  // If font doesn't exist yet, we have to generate a new font.

  double displayResolution = displayResolution_in_dpi;

  fontp = new TeXFontDefinition(fontname, displayResolution*enlargement, checksum, scale, this, enlargement);
  if (fontp == 0) {
    kdError(4300) << i18n("Could not allocate memory for a font structure!") << endl;
    exit(0);
  }
  fontList.append(fontp);

#ifdef PERFORMANCE_MEASUREMENT
  fontPoolTimer.start();
  fontPoolTimerFlag = false;
#endif

  // Now start kpsewhich/MetaFont, etc. if necessary
  return fontp;
}


QString fontPool::status()
{
#ifdef DEBUG_FONTPOOL
  kdDebug(4300) << "fontPool::status() called" << endl;
#endif

  QString       text;
  QStringList   tmp;

  if (fontList.isEmpty())
    return i18n("The fontlist is currently empty.");

  text.append("<table WIDTH=\"100%\" NOSAVE >");
  text.append( QString("<tr><td><b>%1</b></td> <td><b>%2</b></td> <td><b>%3</b></td> <td><b>%4</b> <td><b>%5</b></td> <td><b>%6</b></td></tr>")
	       .arg(i18n("TeX Name"))
	       .arg(i18n("Family"))
	       .arg(i18n("Zoom"))
	       .arg(i18n("Type"))
	       .arg(i18n("Encoding"))
	       .arg(i18n("Comment")) );

 TeXFontDefinition *fontp = fontList.first();
  while ( fontp != 0 ) {
    QString errMsg, encoding;

    if (!(fontp->flags & TeXFontDefinition::FONT_VIRTUAL)) {
#ifdef HAVE_FREETYPE
      encoding = fontp->getFullEncodingName();
#endif
      if (fontp->font != 0)
	errMsg = fontp->font->errorMessage;
      else
	errMsg = i18n("Font file not found");
    }

#ifdef HAVE_FREETYPE
    tmp << QString ("<tr><td>%1</td> <td>%2</td> <td>%3%</td> <td>%4</td> <td>%5</td> <td>%6</td></tr>")
      .arg(fontp->fontname)
      .arg(fontp->getFullFontName())
      .arg((int)(fontp->enlargement*100 + 0.5))
      .arg(fontp->getFontTypeName())
      .arg(encoding)
      .arg(errMsg);
#endif

    fontp=fontList.next();
  }

  tmp.sort();
  text.append(tmp.join("\n"));
  text.append("</table>");

  return text;
}


bool fontPool::areFontsLocated()
{
#ifdef DEBUG_FONTPOOL
  kdDebug(4300) << "fontPool::areFontsLocated() called" << endl;
#endif

  // Is there a font whose name we did not try to find out yet?
  TeXFontDefinition *fontp = fontList.first();
  while( fontp != 0 ) {
    if ( !fontp->isLocated() )
      return false;
    fontp=fontList.next();
  }

#ifdef DEBUG_FONTPOOL
  kdDebug(4300) << "... yes, all fonts are located (but not necessarily loaded)." << endl;
#endif
  return true; // That says that all fonts are located.
}


void fontPool::locateFonts()
{
  kpsewhichOutput = QString::null;

  // First, we try and find those fonts which exist on disk
  // already. If virtual fonts are found, they will add new fonts to
  // the list of fonts whose font files need to be located, so that we
  // repeat the lookup.
  bool vffound;
  do {
    vffound = false;
    locateFonts(false, false, &vffound);
  } while(vffound);
  
  // If still not all fonts are found, look again, this time with
  // on-demand generation of PK fonts enabled.
  if (!areFontsLocated())
    locateFonts(true, false);
  
  // If still not all fonts are found, we look for TFM files as a last
  // resort, so that we can at least draw filled rectangles for
  // characters.
  if (!areFontsLocated())
    locateFonts(false, true);
  
  // If still not all fonts are found, we give up. We mark all fonts
  // as 'located', so that wee won't look for them any more, and
  // present an error message to the user.
  if (!areFontsLocated()) {
    markFontsAsLocated();
    QString details = QString("<qt><p><b>PATH:</b> %1</p>%2</qt>").arg(getenv("PATH")).arg(kpsewhichOutput);
    KMessageBox::detailedError( 0, i18n("<qt><p>KDVI was not able to locate all the font files "
					"which are necessary to display the current DVI file. "
					"Your document might be unreadable.</p></qt>"),
				details,
				i18n("Not All Font Files Found") );
  }
}


void fontPool::locateFonts(bool makePK, bool locateTFMonly, bool *virtualFontsFound)
{
  // Set up the kpsewhich process. If pass == 0, look for vf-fonts and
  // disable automatic font generation as vf-fonts can't be
  // generated. If pass == 0, ennable font generation, if it was
  // enabled globally.
  emit setStatusBarText(i18n("Locating fonts..."));

  QStringList shellProcessCmdLine;

  KProcIO kpsewhichIO;
  // If PK fonts are generated, the kpsewhich command will re-route
  // the output of MetaFont into its stderr. Here we make sure this
  // output is intercepted and parsed.
  qApp->connect(&kpsewhichIO, SIGNAL(receivedStderr(KProcess *, char *, int)),
		this, SLOT(mf_output_receiver(KProcess *, char *, int)));
  
  
  kpsewhichIO.setUseShell(true);
  
  // Now generate the command line for the kpsewhich
  // program. Unfortunately, this can be rather long and involved...
  shellProcessCmdLine += "kpsewhich";
  shellProcessCmdLine += QString("--dpi 1200");
  shellProcessCmdLine += QString("--mode lexmarks");
  
  // Disable automatic pk-font generation.
  if (makePK == true)
    shellProcessCmdLine += "--mktex pk";
  else
    shellProcessCmdLine += "--no-mktex pk";
  
  // Names of fonts that shall be located
  Q_UINT16 numFontsInJob = 0;
  TeXFontDefinition *fontp = fontList.first();
  while ( fontp != 0 ) {
    if (!fontp->isLocated()) {
      numFontsInJob++;
      
      if (locateTFMonly == true)
	shellProcessCmdLine += KShellProcess::quote(QString("%1.tfm").arg(fontp->fontname));
      else {
#ifdef HAVE_FREETYPE
	if (FreeType_could_be_loaded == true) {
	  const QString &filename = fontsByTeXName.findFileName(fontp->fontname);
	  if (!filename.isEmpty())
	    shellProcessCmdLine += KShellProcess::quote(QString("%1").arg(filename));
	}
#endif
	shellProcessCmdLine += KShellProcess::quote(QString("%1.vf").arg(fontp->fontname));
	shellProcessCmdLine += KShellProcess::quote(QString("%1.1200pk").arg(fontp->fontname));
      }
    }
    fontp=fontList.next();
  }

  if (numFontsInJob == 0)
    return;

  progress.setTotalSteps(numFontsInJob, &kpsewhichIO);

  // Now run... kpsewhich. In case of error, kick up a fuss.
  MetafontOutput = QString::null;
  kpsewhichOutput += "<p><b>"+shellProcessCmdLine.join(" ")+"</b></p>";
  kpsewhichIO << shellProcessCmdLine;
  QString importanceOfKPSEWHICH = i18n("<p>KDVI relies on the <b>kpsewhich</b> program to locate font files "
				       "on your hard disc and to generate PK fonts, if necessary.</p>");
  if (kpsewhichIO.start(KProcess::NotifyOnExit, false) == false) {
    QString msg = i18n(	"<p>The shell process for the kpsewhich program could not "
			"be started. Consequently, some font files could not be found, "
			"and your document might by unreadable. If this error is reproducable "
			"please report the issue to the KDVI developers using the 'Help' menu.<p>" );
    QApplication::restoreOverrideCursor();
    KMessageBox::error( 0, QString("<qt>%1%2</qt>").arg(importanceOfKPSEWHICH).arg(msg),
			i18n("Problem locating fonts - KDVI") );
    markFontsAsLocated();
    return;
  }
  
  // We wait here while the kpsewhich program is concurrently
  // running. Every second we call processEvents() to keep the GUI
  // updated. This is important, e.g. for the progress dialog that is
  // shown when PK fonts are generated by MetaFont.
  while(kpsewhichIO.wait(1) == false)
    qApp->processEvents();
  progress.hide();
  
  // Handle fatal errors.
  if (!kpsewhichIO.normalExit()) {
    KMessageBox::sorry( 0, "<qt><p>The font generation was aborted. As a result, "
			"some font files could not be located, and your document might be unreadable.</p></qt>",
			i18n("Font generation aborted - KDVI") );
    
    // This makes sure the we don't try to run kpsewhich again
    if (makePK == false)
      markFontsAsLocated();
  } else
    if (kpsewhichIO.exitStatus() == 127) {
      // An exit status of 127 means that the kpsewhich executable
      // could not be found. We give extra explanation then.
      QApplication::restoreOverrideCursor();
      QString msg = i18n( "<p>There were problems running kpsewhich. As a result, "
			  "some font files could not be located, and your document might be unreadable.</p>"
			  "<p><b>Possible reason:</b> The kpsewhich program is perhaps not installed on your system, or it "
			  "cannot be found in the current search path.</p>"
			  "<p><b>What you can do:</b> The kpsewhich program is normally contained in distributions of the TeX "
			  "typesetting system. If TeX is not installed on your system, you could install the TeTeX distribution (www.tetex.org). "
			  "If you are sure that TeX is installed, please try to use the kpsewhich program from the command line to check if it "
			  "really works.</p>");
      QString details = QString("<qt><p><b>PATH:</b> %1</p>%2</qt>").arg(getenv("PATH")).arg(kpsewhichOutput);
      
      KMessageBox::detailedError( 0, QString("<qt>%1%2</qt>").arg(importanceOfKPSEWHICH).arg(msg), details,
				  i18n("Problem locating fonts - KDVI") );
      // This makes sure the we don't try to run kpsewhich again
      markFontsAsLocated();
      return;
    }
  
  // Create a list with all filenames found by the kpsewhich program.
  QStringList fileNameList;
  QString line;
  while(kpsewhichIO.readln(line) >= 0)
    fileNameList += line;
  
  // Now associate the file names found with the fonts
  fontp=fontList.first();
  while ( fontp != 0 ) {
    if (fontp->filename.isEmpty() == true) {
      QStringList matchingFiles;
#ifdef HAVE_FREETYPE
      const QString &fn = fontsByTeXName.findFileName(fontp->fontname);
      if (!fn.isEmpty())
	matchingFiles = fileNameList.grep(fn);
#endif
      if (matchingFiles.isEmpty() == true)
	matchingFiles += fileNameList.grep(fontp->fontname+".");
      
      if (matchingFiles.isEmpty() != true) {
#ifdef DEBUG_FONTPOOL
	kdDebug(4300) << "Associated " << fontp->fontname << " to " << matchingFiles.first() << endl;
#endif
	QString fname = matchingFiles.first();
	fontp->fontNameReceiver(fname);
	fontp->flags |= TeXFontDefinition::FONT_KPSE_NAME;
	if (fname.endsWith(".vf")) {
	  if (virtualFontsFound != 0)
	    *virtualFontsFound = true;
	  // Constructing a virtual font will most likely insert other
	  // fonts into the fontList. After that, fontList.next() will
	  // no longer work. It is therefore safer to start over.
	  fontp=fontList.first();
	  continue;
	}
      }
    } // of if (fontp->filename.isEmpty() == true)
    fontp = fontList.next();
  }
}


void fontPool::setCMperDVIunit( double _CMperDVI )
{
#ifdef DEBUG_FONTPOOL
  kdDebug(4300) << "fontPool::setCMperDVIunit( " << _CMperDVI << " )" << endl;
#endif

  if (CMperDVIunit == _CMperDVI)
    return;

  CMperDVIunit = _CMperDVI;

  TeXFontDefinition *fontp = fontList.first();
  while(fontp != 0 ) {
    fontp->setDisplayResolution(displayResolution_in_dpi * fontp->enlargement);
    fontp=fontList.next();
  }
}


void fontPool::setDisplayResolution( double _displayResolution_in_dpi )
{
#ifdef DEBUG_FONTPOOL
  kdDebug(4300) << "fontPool::setDisplayResolution( displayResolution_in_dpi=" << _displayResolution_in_dpi << " ) called" << endl;
#endif
  
  // Ignore minute changes by less than 2 DPI. The difference would
  // hardly be visible anyway. That saves a lot of re-painting,
  // e.g. when the user resizes the window, and a flickery mouse
  // changes the window size by 1 pixel all the time.
  if ( fabs(displayResolution_in_dpi - _displayResolution_in_dpi) <= 2.0 ) {
#ifdef DEBUG_FONTPOOL
    kdDebug(4300) << "fontPool::setDisplayResolution(...): resolution wasn't changed. Aborting." << endl;
#endif
    return;
  }
  
  displayResolution_in_dpi = _displayResolution_in_dpi;
  double displayResolution = displayResolution_in_dpi;
  
  TeXFontDefinition *fontp = fontList.first();
  while(fontp != 0 ) {
    fontp->setDisplayResolution(displayResolution * fontp->enlargement);
    fontp=fontList.next();
  }
  
  // Do something that causes re-rendering of the dvi-window
  /*@@@@
  emit fonts_have_been_loaded(this);
  */
}


void fontPool::markFontsAsLocated()
{
  TeXFontDefinition *fontp=fontList.first();
  while ( fontp != 0 ) {
    fontp->markAsLocated();
    fontp = fontList.next();
  }
}



void fontPool::mark_fonts_as_unused()
{
#ifdef DEBUG_FONTPOOL
  kdDebug(4300) << "fontPool::mark_fonts_as_unused() called" << endl;
#endif
  
  TeXFontDefinition  *fontp = fontList.first();
  while ( fontp != 0 ) {
    fontp->flags &= ~TeXFontDefinition::FONT_IN_USE;
    fontp=fontList.next();
  }
}


void fontPool::release_fonts()
{
#ifdef DEBUG_FONTPOOL
  kdDebug(4300) << "Release_fonts" << endl;
#endif

  TeXFontDefinition  *fontp = fontList.first();
  while(fontp != 0) {
    if ((fontp->flags & TeXFontDefinition::FONT_IN_USE) != TeXFontDefinition::FONT_IN_USE) {
      fontList.removeRef(fontp);
      fontp = fontList.first();
    } else
      fontp = fontList.next();
  }
}


void fontPool::mf_output_receiver(KProcess *, char *buffer, int buflen)
{
  // Paranoia.
  if (buflen < 0)
    return;
  
  QString op = QString::fromLocal8Bit(buffer, buflen);

  kpsewhichOutput.append(op);
  MetafontOutput.append(op);
  
  // We'd like to print only full lines of text.
  int numleft;
  bool show_prog = false;
  while( (numleft = MetafontOutput.find('\n')) != -1) {
    QString line = MetafontOutput.left(numleft+1);
#ifdef DEBUG_FONTPOOL
    kdDebug(4300) << "MF OUTPUT RECEIVED: " << line;
#endif
    // Search for a line which marks the beginning of a MetaFont run
    // and show the progress dialog at the end of this method.
    if (line.find("kpathsea:") == 0)
      show_prog = true;

    // If the Output of the kpsewhich program contains a line starting
    // with "kpathsea:", this means that a new MetaFont-run has been
    // started. We filter these lines out and update the display
    // accordingly.
    int startlineindex = line.find("kpathsea:");
    if (startlineindex != -1) {
      int endstartline  = line.find("\n",startlineindex);
      QString startLine = line.mid(startlineindex,endstartline-startlineindex);

      // The last word in the startline is the name of the font which we
      // are generating. The second-to-last word is the resolution in
      // dots per inch. Display this info in the text label below the
      // progress bar.
      int lastblank     = startLine.findRev(' ');
      QString fontName  = startLine.mid(lastblank+1);
      int secondblank   = startLine.findRev(' ',lastblank-1);
      QString dpi       = startLine.mid(secondblank+1,lastblank-secondblank-1);

      progress.show();
      progress.increaseNumSteps( i18n("Currently generating %1 at %2 dpi").arg(fontName).arg(dpi) );
    }
    MetafontOutput = MetafontOutput.remove(0,numleft+1);
  }
}


#include "fontpool.moc"