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