]> git.lyx.org Git - lyx.git/blob - src/graphics/PreviewLoader.C
Further clean ups.
[lyx.git] / src / graphics / PreviewLoader.C
1 /*
2  *  \file PreviewLoader.C
3  *  Copyright 2002 the LyX Team
4  *  Read the file COPYING
5  *
6  * \author Angus Leeming <a.leeming@ic.ac.uk>
7  */
8
9 #include <config.h>
10
11 #ifdef __GNUG__
12 #pragma implementation
13 #endif
14
15 #include "PreviewLoader.h"
16 #include "PreviewImage.h"
17
18 #include "buffer.h"
19 #include "bufferparams.h"
20 #include "converter.h"
21 #include "debug.h"
22 #include "lyxrc.h"
23 #include "lyxtextclasslist.h"
24 #include "LColor.h"
25
26 #include "insets/inset.h"
27
28 #include "frontends/lyx_gui.h" // hexname
29
30 #include "support/filetools.h"
31 #include "support/forkedcall.h"
32 #include "support/forkedcontr.h"
33 #include "support/lstrings.h"
34 #include "support/lyxlib.h"
35
36 #include <boost/bind.hpp>
37 #include <boost/signals/trackable.hpp>
38
39 #include <fstream>
40 #include <iomanip>
41 #include <list>
42 #include <map>
43 #include <utility>
44 #include <vector>
45
46 using std::endl;
47 using std::find;
48 using std::fill;
49 using std::find_if;
50 using std::getline;
51 using std::make_pair;
52 using std::setfill;
53 using std::setw;
54
55 using std::list;
56 using std::map;
57 using std::ifstream;
58 using std::ofstream;
59 using std::ostream;
60 using std::pair;
61 using std::vector;
62
63 namespace {
64
65 typedef pair<string, string> StrPair;
66
67 typedef list<string> PendingStore;
68
69 typedef vector<StrPair> InProgressStore;
70
71
72 double setFontScalingFactor(Buffer &);
73
74 string const unique_filename(string const bufferpath);
75
76 Converter const * setConverter();
77
78 void setAscentFractions(vector<double> & ascent_fractions,
79                         string const & metrics_file);
80
81 struct FindFirst {
82         FindFirst(string const & comp) : comp_(comp) {}
83         bool operator()(StrPair const & sp)
84         {
85                 return sp.first < comp_;
86         }
87 private:
88         string const comp_;
89 };
90
91
92 /// Store info on a currently executing, forked process.
93 struct InProgress {
94         ///
95         InProgress() : pid(0) {}
96         ///
97         InProgress(string const & filename_base,
98                    PendingStore const & pending,
99                    string const & to_format);
100         /// Remove any files left lying around and kill the forked process. 
101         void stop() const;
102
103         ///
104         pid_t pid;
105         ///
106         string metrics_file;
107         /// Each item in the vector is a pair<snippet, image file name>.
108         InProgressStore snippets;
109 };
110
111
112 } // namespace anon
113
114
115 namespace grfx {
116
117 struct PreviewLoader::Impl : public boost::signals::trackable {
118         ///
119         Impl(PreviewLoader & p, Buffer const & b);
120         /// Stop any InProgress items still executing.
121         ~Impl();
122         ///
123         PreviewImage const * preview(string const & latex_snippet) const;
124         ///
125         PreviewLoader::Status status(string const & latex_snippet) const;
126         ///
127         void add(string const & latex_snippet);
128         ///
129         void remove(string const & latex_snippet);
130         ///
131         void startLoading();
132
133 private:
134         /// Called by the Forkedcall process that generated the bitmap files.
135         void finishedGenerating(string const &, pid_t, int);
136         ///
137         void dumpPreamble(ostream &) const;
138         ///
139         void dumpData(ostream &, InProgressStore const &) const;
140
141         /** cache_ allows easy retrieval of already-generated images
142          *  using the LaTeX snippet as the identifier.
143          */
144         typedef boost::shared_ptr<PreviewImage> PreviewImagePtr;
145         ///
146         typedef map<string, PreviewImagePtr> Cache;
147         ///
148         Cache cache_;
149
150         /** pending_ stores the LaTeX snippets in anticipation of them being
151          *  sent to the converter.
152          */
153         PendingStore pending_;
154
155         /** in_progress_ stores all forked processes so that we can proceed
156          *  thereafter.
157             The map uses the conversion commands as its identifiers.
158          */
159         typedef map<string, InProgress> InProgressMap;
160         ///
161         InProgressMap in_progress_;
162
163         ///
164         PreviewLoader & parent_;
165         ///
166         Buffer const & buffer_;
167         ///
168         double font_scaling_factor_;
169
170         /// We don't own this
171         static Converter const * pconverter_;   
172 };
173
174
175 Converter const * PreviewLoader::Impl::pconverter_;
176
177
178 // The public interface, defined in PreviewLoader.h
179 // ================================================
180 PreviewLoader::PreviewLoader(Buffer const & b)
181         : pimpl_(new Impl(*this, b))
182 {}
183
184
185 PreviewLoader::~PreviewLoader()
186 {}
187
188
189 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
190 {
191         return pimpl_->preview(latex_snippet);
192 }
193
194
195 PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const
196 {
197         return pimpl_->status(latex_snippet);
198 }
199
200
201 void PreviewLoader::add(string const & latex_snippet)
202 {
203         pimpl_->add(latex_snippet);
204 }
205
206
207 void PreviewLoader::remove(string const & latex_snippet)
208 {
209         pimpl_->remove(latex_snippet);
210 }
211
212
213 void PreviewLoader::startLoading()
214 {
215         pimpl_->startLoading();
216 }
217
218 } // namespace grfx
219
220
221 // The details of the Impl
222 // =======================
223
224 namespace {
225
226 InProgress::InProgress(string const & filename_base,
227                        PendingStore const & pending,
228                        string const & to_format)
229         : pid(0),
230           metrics_file(filename_base + ".metrics"),
231           snippets(pending.size())
232 {
233         InProgressStore::iterator sit = snippets.begin();
234         PendingStore::const_iterator pit  = pending.begin();
235         PendingStore::const_iterator pend = pending.end();
236
237         int counter = 1; // file numbers start at 1
238         for (; pit != pend; ++pit, ++sit, ++counter) {
239                 ostringstream os;
240                 os << filename_base
241                    << setfill('0') << setw(3) << counter
242                    << "." << to_format;
243                 string const file = os.str().c_str();
244
245                 *sit = make_pair(*pit, file);
246         }
247 }
248
249
250 void InProgress::stop() const
251 {
252         if (pid)
253                 ForkedcallsController::get().kill(pid, 0);
254
255         if (!metrics_file.empty())
256                 lyx::unlink(metrics_file);
257
258         InProgressStore::const_iterator vit  = snippets.begin();
259         InProgressStore::const_iterator vend = snippets.end();
260         for (; vit != vend; ++vit) {
261                 if (!vit->second.empty())
262                         lyx::unlink(vit->second);
263         }
264 }
265  
266 } // namespace anon
267
268
269 namespace grfx {
270
271 PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b)
272         : parent_(p), buffer_(b), font_scaling_factor_(0.0)
273 {
274         font_scaling_factor_ = setFontScalingFactor(const_cast<Buffer &>(b));
275
276         lyxerr[Debug::GRAPHICS] << "The font scaling factor is "
277                                 << font_scaling_factor_ << endl;
278
279         if (!pconverter_)
280                 pconverter_ = setConverter();
281 }
282
283
284 PreviewLoader::Impl::~Impl()
285 {
286         InProgressMap::iterator ipit  = in_progress_.begin();
287         InProgressMap::iterator ipend = in_progress_.end();
288
289         for (; ipit != ipend; ++ipit) {
290                 ipit->second.stop();
291         }
292 }
293
294
295 PreviewImage const *
296 PreviewLoader::Impl::preview(string const & latex_snippet) const
297 {
298         Cache::const_iterator it = cache_.find(latex_snippet);
299         return (it == cache_.end()) ? 0 : it->second.get();
300 }
301
302
303 PreviewLoader::Status
304 PreviewLoader::Impl::status(string const & latex_snippet) const
305 {
306         Cache::const_iterator cit = cache_.find(latex_snippet);
307         if (cit != cache_.end())
308                 return Ready;
309
310         PendingStore::const_iterator pit  = pending_.begin();
311         PendingStore::const_iterator pend = pending_.end();
312         pit = find(pit, pend, latex_snippet);
313
314         if (pit != pend)
315                 return InQueue;
316
317         InProgressMap::const_iterator ipit  = in_progress_.begin();
318         InProgressMap::const_iterator ipend = in_progress_.end();
319
320         for (; ipit != ipend; ++ipit) {
321                 InProgressStore const & snippets = ipit->second.snippets;
322                 InProgressStore::const_iterator sit  = snippets.begin();
323                 InProgressStore::const_iterator send = snippets.end();
324                 sit = find_if(sit, send, FindFirst(latex_snippet));
325
326                 if (sit != send)
327                         return Processing;
328         }
329
330         return NotFound;
331 }
332
333
334 void PreviewLoader::Impl::add(string const & latex_snippet)
335 {
336         if (!pconverter_ || status(latex_snippet) != NotFound)
337                 return;
338
339         pending_.push_back(latex_snippet);
340 }
341
342
343 void PreviewLoader::Impl::remove(string const & latex_snippet)
344 {
345         Cache::iterator cit = cache_.find(latex_snippet);
346         if (cit != cache_.end())
347                 cache_.erase(cit);
348
349         PendingStore::iterator pit  = pending_.begin();
350         PendingStore::iterator pend = pending_.end();
351         pit = find(pit, pend, latex_snippet);
352
353         if (pit != pend) {
354                 PendingStore::iterator first = pit++;
355                 pending_.erase(first, pit);
356         }
357
358         InProgressMap::iterator ipit  = in_progress_.begin();
359         InProgressMap::iterator ipend = in_progress_.end();
360
361         while (ipit != ipend) {
362                 InProgressMap::iterator curr = ipit;
363                 ++ipit;
364
365                 InProgressStore & snippets = curr->second.snippets;
366                 InProgressStore::iterator sit  = snippets.begin();
367                 InProgressStore::iterator send = snippets.end();
368                 sit = find_if(sit, send, FindFirst(latex_snippet));
369
370                 if (sit != send)
371                         snippets.erase(sit, sit+1);
372
373                 if (snippets.empty())
374                         in_progress_.erase(curr);
375         }
376 }
377
378
379 void PreviewLoader::Impl::startLoading()
380 {
381         if (pending_.empty() || !pconverter_)
382                 return;
383
384         lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()" << endl;
385
386         // As used by the LaTeX file and by the resulting image files
387         string const filename_base(unique_filename(buffer_.tmppath));
388
389         // Create an InProgress instance to place in the map of all
390         // such processes if it starts correctly.
391         InProgress inprogress(filename_base, pending_, pconverter_->to);
392
393         // clear pending_, so we're ready to start afresh.
394         pending_.clear();
395
396         // Output the LaTeX file.
397         string const latexfile = filename_base + ".tex";
398
399         ofstream of(latexfile.c_str());
400         dumpPreamble(of);
401         of << "\n\\begin{document}\n";
402         dumpData(of, inprogress.snippets);
403         of << "\n\\end{document}\n";
404         of.close();
405
406         // The conversion command.
407         ostringstream cs;
408         cs << pconverter_->command << " " << latexfile << " "
409            << font_scaling_factor_;
410
411         string const command = cs.str().c_str();
412
413         // Initiate the conversion from LaTeX to bitmap images files.
414         Forkedcall::SignalTypePtr convert_ptr;
415         convert_ptr.reset(new Forkedcall::SignalType);
416
417         convert_ptr->connect(
418                 boost::bind(&Impl::finishedGenerating, this, _1, _2, _3));
419
420         Forkedcall call;
421         int ret = call.startscript(command, convert_ptr);
422
423         if (ret != 0) {
424                 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
425                                         << "Unable to start process \n"
426                                         << command << endl;
427                 return;
428         }
429
430         // Store the generation process in a list of all such processes
431         inprogress.pid = call.pid();
432         in_progress_[command] = inprogress;
433 }
434
435
436 void PreviewLoader::Impl::finishedGenerating(string const & command,
437                                              pid_t /* pid */, int retval)
438 {
439         string const status = retval > 0 ? "failed" : "succeeded";
440         lyxerr[Debug::GRAPHICS] << "PreviewLoader::finishedInProgress("
441                                 << retval << "): processing " << status
442                                 << " for " << command << endl;
443         if (retval > 0)
444                 return;
445
446         InProgressMap::iterator git = in_progress_.find(command);
447         if (git == in_progress_.end()) {
448                 lyxerr << "PreviewLoader::finishedGenerating(): unable to find "
449                         "data for\n"
450                        << command << "!" << endl;
451                 return;
452         }
453
454         // Read the metrics file, if it exists
455         vector<double> ascent_fractions(git->second.snippets.size());
456         setAscentFractions(ascent_fractions, git->second.metrics_file);
457
458         // Add these newly generated bitmap files to the cache and
459         // start loading them into LyX.
460         InProgressStore::const_iterator it  = git->second.snippets.begin();
461         InProgressStore::const_iterator end = git->second.snippets.end();
462
463         int metrics_counter = 0;
464         for (; it != end; ++it, ++metrics_counter) {
465                 string const & snip = it->first;
466                 string const & file = it->second;
467                 double af = ascent_fractions[metrics_counter];
468
469                 PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af));
470                 cache_[snip] = ptr;
471
472                 ptr->startLoading();
473         }
474
475         // Remove the item from the list of still-executing processes.
476         in_progress_.erase(git);
477 }
478
479
480 void PreviewLoader::Impl::dumpPreamble(ostream & os) const
481 {
482         // Why on earth is Buffer::makeLaTeXFile a non-const method?
483         Buffer & tmp = const_cast<Buffer &>(buffer_);
484         // Dump the preamble only.
485         tmp.makeLaTeXFile(os, string(), true, false, true);
486
487         // Loop over the insets in the buffer and dump all the math-macros.
488         Buffer::inset_iterator it  = buffer_.inset_const_iterator_begin();
489         Buffer::inset_iterator end = buffer_.inset_const_iterator_end();
490
491         for (; it != end; ++it) {
492                 if ((*it)->lyxCode() == Inset::MATHMACRO_CODE) {
493                         (*it)->latex(&buffer_, os, true, true);
494                 }
495         }
496
497         // Use the preview style file to ensure that each snippet appears on a
498         // fresh page.
499         os << "\n"
500            << "\\usepackage[active,delayed,dvips,tightpage,showlabels]{preview}\n"
501            << "\n";
502
503         // This piece of PostScript magic ensures that the foreground and
504         // background colors are the same as the LyX screen.
505         string fg = lyx_gui::hexname(LColor::preview);
506         if (fg.empty()) fg = "000000";
507
508         string bg = lyx_gui::hexname(LColor::background);
509         if (bg.empty()) bg = "ffffff";
510
511         os << "\\AtBeginDocument{\\AtBeginDvi{%\n"
512            << "\\special{!userdict begin/bop-hook{//bop-hook exec\n"
513            << "<" << fg << bg << ">{255 div}forall setrgbcolor\n"
514            << "clippath fill setrgbcolor}bind def end}}}\n";
515 }
516
517
518 void PreviewLoader::Impl::dumpData(ostream & os,
519                                    InProgressStore const & vec) const
520 {
521         if (vec.empty())
522                 return;
523
524         InProgressStore::const_iterator it  = vec.begin();
525         InProgressStore::const_iterator end = vec.end();
526
527         for (; it != end; ++it) {
528                 os << "\\begin{preview}\n"
529                    << it->first
530                    << "\n\\end{preview}\n\n";
531         }
532 }
533
534 } // namespace grfx
535
536
537 namespace {
538
539 string const unique_filename(string const bufferpath)
540 {
541         static int theCounter = 0;
542         string const filename = tostr(theCounter++) + "lyxpreview";
543         return AddName(bufferpath, filename);
544 }
545
546
547 Converter const * setConverter()
548 {
549         Converter const * converter = 0;
550
551         string const from = "lyxpreview";
552
553         Formats::FormatList::const_iterator it  = formats.begin();
554         Formats::FormatList::const_iterator end = formats.end();
555
556         for (; it != end; ++it) {
557                 string const to = it->name();
558                 if (from == to)
559                         continue;
560
561                 Converter const * ptr = converters.getConverter(from, to);
562                 if (ptr)
563                         return ptr;
564         }
565
566         static bool first = true;
567         if (first) {
568                 first = false;
569                 lyxerr << "PreviewLoader::startLoading()\n"
570                        << "No converter from \"lyxpreview\" format has been "
571                         "defined."
572                        << endl;
573         }
574         
575         return 0;
576 }
577
578
579 double setFontScalingFactor(Buffer & buffer)
580 {
581         static double const lyxrc_preview_scale_factor = 0.9;
582         double scale_factor = 0.01 * lyxrc.dpi * lyxrc.zoom *
583                 lyxrc_preview_scale_factor;
584
585         // Has the font size been set explicitly?
586         string const & fontsize = buffer.params.fontsize;
587         lyxerr[Debug::GRAPHICS] << "PreviewLoader::scaleToFitLyXView()\n"
588                                 << "font size is " << fontsize << endl;
589
590         if (isStrUnsignedInt(fontsize))
591                 return 10.0 * scale_factor / strToDbl(fontsize);
592
593         // No. We must extract it from the LaTeX class file.
594         LyXTextClass const & tclass = textclasslist[buffer.params.textclass];
595         string const textclass(tclass.latexname() + ".cls");
596         string const classfile(findtexfile(textclass, "cls"));
597
598         lyxerr[Debug::GRAPHICS] << "text class is " << textclass << '\n'
599                                 << "class file is " << classfile << endl;
600         
601         ifstream ifs(classfile.c_str());
602         if (!ifs.good()) {
603                 lyxerr[Debug::GRAPHICS] << "Unable to open class file!" << endl;
604                 return scale_factor;
605         }
606
607         string str;
608         double scaling = scale_factor;
609
610         while (ifs.good()) {
611                 getline(ifs, str);
612                 // To get the default font size, look for a line like
613                 // "\ExecuteOptions{letterpaper,10pt,oneside,onecolumn,final}"
614                 if (!prefixIs(frontStrip(str), "\\ExecuteOptions"))
615                         continue;
616
617                 str = split(str, '{');
618                 int count = 0;
619                 string tok = token(str, ',', count++);
620                 while (!isValidLength(tok) && !tok.empty())
621                         tok = token(str, ',', count++);
622
623                 if (!tok.empty()) {
624                         lyxerr[Debug::GRAPHICS]
625                                 << "Extracted default font size from "
626                                 "LaTeX class file successfully!" << endl;
627                         LyXLength fsize(tok);
628                         scaling *= 10.0 / fsize.value();
629                         break;
630                 }
631         }
632
633         return scaling;
634 }
635
636
637 void setAscentFractions(vector<double> & ascent_fractions,
638                         string const & metrics_file)
639 {
640         // If all else fails, then the images will have equal ascents and
641         // descents.
642         vector<double>::iterator it  = ascent_fractions.begin();
643         vector<double>::iterator end = ascent_fractions.end();
644         fill(it, end, 0.5);
645
646         ifstream ifs(metrics_file.c_str());
647         if (!ifs.good()) {
648                 lyxerr[Debug::GRAPHICS] << "setAscentFractions("
649                                         << metrics_file << ")\n"
650                                         << "Unable to open file!"
651                                         << endl;
652                 return;
653         }
654
655         for (; it != end; ++it) {
656                 string page;
657                 string page_id;
658                 int dummy;
659                 int ascent;
660                 int descent;
661
662                 ifs >> page >> page_id >> dummy >> dummy >> dummy >> dummy
663                     >> ascent >> descent >> dummy;
664
665                 if (!ifs.good() ||
666                     page != "%%Page" ||
667                     !isStrUnsignedInt(strip(page_id, ':'))) {
668                         lyxerr[Debug::GRAPHICS] << "setAscentFractions("
669                                                 << metrics_file << ")\n"
670                                                 << "Error reading file!"
671                                                 << endl;
672                         break;
673                 }
674                 
675                 *it = ascent / (ascent + descent);
676         }
677 }
678
679 } // namespace anon