]> git.lyx.org Git - lyx.git/blob - src/graphics/PreviewLoader.C
8dbc512ce32f4c1a51d4ad977445b7f9a10d6108
[lyx.git] / src / graphics / PreviewLoader.C
1 /**
2  * \file PreviewLoader.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "PreviewLoader.h"
14 #include "PreviewImage.h"
15 #include "GraphicsCache.h"
16
17 #include "buffer.h"
18 #include "converter.h"
19 #include "debug.h"
20 #include "format.h"
21 #include "insetiterator.h"
22 #include "LColor.h"
23 #include "lyxrc.h"
24 #include "outputparams.h"
25 #include "paragraph.h"
26
27 #include "frontends/lyx_gui.h" // hexname
28
29 #include "insets/inset.h"
30
31 #include "support/filetools.h"
32 #include "support/forkedcall.h"
33 #include "support/forkedcontr.h"
34 #include "support/lstrings.h"
35 #include "support/lyxlib.h"
36 #include "support/convert.h"
37
38 #include <boost/bind.hpp>
39
40 #include <sstream>
41 #include <fstream>
42 #include <iomanip>
43
44 namespace support = lyx::support;
45
46 using std::endl;
47 using std::find;
48 using std::fill;
49 using std::find_if;
50 using std::make_pair;
51
52 using boost::bind;
53
54 using std::ifstream;
55 using std::list;
56 using std::map;
57 using std::ofstream;
58 using std::ostream;
59 using std::ostringstream;
60 using std::pair;
61 using std::vector;
62 using std::string;
63
64
65 namespace {
66
67 typedef pair<string, string> StrPair;
68
69 // A list of alll snippets to be converted to previews
70 typedef list<string> PendingSnippets;
71
72 // Each item in the vector is a pair<snippet, image file name>.
73 typedef vector<StrPair> BitmapFile;
74
75 string const unique_filename(string const bufferpath);
76
77 Converter const * setConverter();
78
79 void setAscentFractions(vector<double> & ascent_fractions,
80                         string const & metrics_file);
81
82 class FindFirst : public std::unary_function<StrPair, bool> {
83 public:
84         FindFirst(string const & comp) : comp_(comp) {}
85         bool operator()(StrPair const & sp) const
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 class InProgress {
96 public:
97         ///
98         InProgress() : pid(0) {}
99         ///
100         InProgress(string const & filename_base,
101                    PendingSnippets const & pending,
102                    string const & to_format);
103         /// Remove any files left lying around and kill the forked process.
104         void stop() const;
105
106         ///
107         pid_t pid;
108         ///
109         string command;
110         ///
111         string metrics_file;
112         ///
113         BitmapFile snippets;
114 };
115
116 typedef map<pid_t, InProgress>  InProgressProcesses;
117
118 typedef InProgressProcesses::value_type InProgressProcess;
119
120 } // namespace anon
121
122
123 namespace lyx {
124 namespace graphics {
125
126 class PreviewLoader::Impl : public boost::signals::trackable {
127 public:
128         ///
129         Impl(PreviewLoader & p, Buffer const & b);
130         /// Stop any InProgress items still executing.
131         ~Impl();
132         ///
133         PreviewImage const * preview(string const & latex_snippet) const;
134         ///
135         PreviewLoader::Status status(string const & latex_snippet) const;
136         ///
137         void add(string const & latex_snippet);
138         ///
139         void remove(string const & latex_snippet);
140         ///
141         void startLoading();
142
143         /// Emit this signal when an image is ready for display.
144         boost::signal<void(PreviewImage const &)> imageReady;
145
146         Buffer const & buffer() const { return buffer_; }
147
148 private:
149         /// Called by the Forkedcall process that generated the bitmap files.
150         void finishedGenerating(pid_t, int);
151         ///
152         void dumpPreamble(ostream &) const;
153         ///
154         void dumpData(ostream &, BitmapFile const &) const;
155
156         /** cache_ allows easy retrieval of already-generated images
157          *  using the LaTeX snippet as the identifier.
158          */
159         typedef boost::shared_ptr<PreviewImage> PreviewImagePtr;
160         ///
161         typedef map<string, PreviewImagePtr> Cache;
162         ///
163         Cache cache_;
164
165         /** pending_ stores the LaTeX snippets in anticipation of them being
166          *  sent to the converter.
167          */
168         PendingSnippets pending_;
169
170         /** in_progress_ stores all forked processes so that we can proceed
171          *  thereafter.
172             The map uses the conversion commands as its identifiers.
173          */
174         InProgressProcesses in_progress_;
175
176         ///
177         PreviewLoader & parent_;
178         ///
179         Buffer const & buffer_;
180         ///
181         double font_scaling_factor_;
182
183         /// We don't own this
184         static Converter const * pconverter_;
185 };
186
187
188 Converter const * PreviewLoader::Impl::pconverter_;
189
190
191 // The public interface, defined in PreviewLoader.h
192 // ================================================
193 PreviewLoader::PreviewLoader(Buffer const & b)
194         : pimpl_(new Impl(*this, b))
195 {}
196
197
198 PreviewLoader::~PreviewLoader()
199 {}
200
201
202 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
203 {
204         return pimpl_->preview(latex_snippet);
205 }
206
207
208 PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const
209 {
210         return pimpl_->status(latex_snippet);
211 }
212
213
214 void PreviewLoader::add(string const & latex_snippet) const
215 {
216         pimpl_->add(latex_snippet);
217 }
218
219
220 void PreviewLoader::remove(string const & latex_snippet) const
221 {
222         pimpl_->remove(latex_snippet);
223 }
224
225
226 void PreviewLoader::startLoading() const
227 {
228         pimpl_->startLoading();
229 }
230
231
232 boost::signals::connection PreviewLoader::connect(slot_type const & slot) const
233 {
234         return pimpl_->imageReady.connect(slot);
235 }
236
237
238 void PreviewLoader::emitSignal(PreviewImage const & pimage) const
239 {
240         pimpl_->imageReady(pimage);
241 }
242
243
244 Buffer const & PreviewLoader::buffer() const
245 {
246         return pimpl_->buffer();
247 }
248
249 } // namespace graphics
250 } // namespace lyx
251
252
253 // The details of the Impl
254 // =======================
255
256 namespace {
257
258 class IncrementedFileName {
259 public:
260         IncrementedFileName(string const & to_format,
261                             string const & filename_base)
262                 : to_format_(to_format), base_(filename_base), counter_(1)
263         {}
264
265         StrPair const operator()(string const & snippet)
266         {
267                 ostringstream os;
268                 os << base_ << counter_++ << '.' << to_format_;
269                 string const file = os.str();
270
271                 return make_pair(snippet, file);
272         }
273
274 private:
275         string const & to_format_;
276         string const & base_;
277         int counter_;
278 };
279
280
281 InProgress::InProgress(string const & filename_base,
282                        PendingSnippets const & pending,
283                        string const & to_format)
284         : pid(0),
285           metrics_file(filename_base + ".metrics"),
286           snippets(pending.size())
287 {
288         PendingSnippets::const_iterator pit  = pending.begin();
289         PendingSnippets::const_iterator pend = pending.end();
290         BitmapFile::iterator sit = snippets.begin();
291
292         std::transform(pit, pend, sit,
293                        IncrementedFileName(to_format, filename_base));
294 }
295
296
297 void InProgress::stop() const
298 {
299         if (pid)
300                 support::ForkedcallsController::get().kill(pid, 0);
301
302         if (!metrics_file.empty())
303                 support::unlink(metrics_file);
304
305         BitmapFile::const_iterator vit  = snippets.begin();
306         BitmapFile::const_iterator vend = snippets.end();
307         for (; vit != vend; ++vit) {
308                 if (!vit->second.empty())
309                         support::unlink(vit->second);
310         }
311 }
312
313 } // namespace anon
314
315
316 namespace lyx {
317 namespace graphics {
318
319 PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b)
320         : parent_(p), buffer_(b), font_scaling_factor_(0.0)
321 {
322         font_scaling_factor_ = 0.01 * lyxrc.dpi * lyxrc.zoom *
323                 support::strToDbl(lyxrc.preview_scale_factor);
324
325         lyxerr[Debug::GRAPHICS] << "The font scaling factor is "
326                                 << font_scaling_factor_ << endl;
327
328         if (!pconverter_)
329                 pconverter_ = setConverter();
330 }
331
332
333 PreviewLoader::Impl::~Impl()
334 {
335         InProgressProcesses::iterator ipit  = in_progress_.begin();
336         InProgressProcesses::iterator ipend = in_progress_.end();
337
338         for (; ipit != ipend; ++ipit) {
339                 ipit->second.stop();
340         }
341 }
342
343
344 PreviewImage const *
345 PreviewLoader::Impl::preview(string const & latex_snippet) const
346 {
347         Cache::const_iterator it = cache_.find(latex_snippet);
348         return (it == cache_.end()) ? 0 : it->second.get();
349 }
350
351
352 namespace {
353
354 class FindSnippet : public std::unary_function<InProgressProcess, bool> {
355 public:
356         FindSnippet(string const & s) : snippet_(s) {}
357         bool operator()(InProgressProcess const & process) const
358         {
359                 BitmapFile const & snippets = process.second.snippets;
360                 BitmapFile::const_iterator beg  = snippets.begin();
361                 BitmapFile::const_iterator end = snippets.end();
362                 return find_if(beg, end, FindFirst(snippet_)) != end;
363         }
364
365 private:
366         string const snippet_;
367 };
368
369 } // namespace anon
370
371 PreviewLoader::Status
372 PreviewLoader::Impl::status(string const & latex_snippet) const
373 {
374         Cache::const_iterator cit = cache_.find(latex_snippet);
375         if (cit != cache_.end())
376                 return Ready;
377
378         PendingSnippets::const_iterator pit  = pending_.begin();
379         PendingSnippets::const_iterator pend = pending_.end();
380
381         pit = find(pit, pend, latex_snippet);
382         if (pit != pend)
383                 return InQueue;
384
385         InProgressProcesses::const_iterator ipit  = in_progress_.begin();
386         InProgressProcesses::const_iterator ipend = in_progress_.end();
387
388         ipit = find_if(ipit, ipend, FindSnippet(latex_snippet));
389         if (ipit != ipend)
390                 return Processing;
391
392         return NotFound;
393 }
394
395
396 void PreviewLoader::Impl::add(string const & latex_snippet)
397 {
398         if (!pconverter_ || status(latex_snippet) != NotFound)
399                 return;
400
401         string const snippet = support::trim(latex_snippet);
402         if (snippet.empty())
403                 return;
404
405         lyxerr[Debug::GRAPHICS] << "adding snippet:\n" << snippet << endl;
406
407         pending_.push_back(snippet);
408 }
409
410
411 namespace {
412
413 class EraseSnippet {
414 public:
415         EraseSnippet(string const & s) : snippet_(s) {}
416         void operator()(InProgressProcess & process)
417         {
418                 BitmapFile & snippets = process.second.snippets;
419                 BitmapFile::iterator it  = snippets.begin();
420                 BitmapFile::iterator end = snippets.end();
421
422                 it = find_if(it, end, FindFirst(snippet_));
423                 if (it != end)
424                         snippets.erase(it, it+1);
425         }
426
427 private:
428         string const & snippet_;
429 };
430
431 } // namespace anon
432
433
434 void PreviewLoader::Impl::remove(string const & latex_snippet)
435 {
436         Cache::iterator cit = cache_.find(latex_snippet);
437         if (cit != cache_.end())
438                 cache_.erase(cit);
439
440         PendingSnippets::iterator pit  = pending_.begin();
441         PendingSnippets::iterator pend = pending_.end();
442
443         pending_.erase(std::remove(pit, pend, latex_snippet), pend);
444
445         InProgressProcesses::iterator ipit  = in_progress_.begin();
446         InProgressProcesses::iterator ipend = in_progress_.end();
447
448         std::for_each(ipit, ipend, EraseSnippet(latex_snippet));
449
450         while (ipit != ipend) {
451                 InProgressProcesses::iterator curr = ipit++;
452                 if (curr->second.snippets.empty())
453                         in_progress_.erase(curr);
454         }
455 }
456
457
458 void PreviewLoader::Impl::startLoading()
459 {
460         if (pending_.empty() || !pconverter_)
461                 return;
462
463         // Only start the process off after the buffer is loaded from file.
464         if (!buffer_.fully_loaded())
465                 return;
466
467         lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()" << endl;
468
469         // As used by the LaTeX file and by the resulting image files
470         string const directory = buffer_.temppath();
471
472         string const filename_base(unique_filename(directory));
473
474         // Create an InProgress instance to place in the map of all
475         // such processes if it starts correctly.
476         InProgress inprogress(filename_base, pending_, pconverter_->to);
477
478         // clear pending_, so we're ready to start afresh.
479         pending_.clear();
480
481         // Output the LaTeX file.
482         string const latexfile = filename_base + ".tex";
483
484         ofstream of(latexfile.c_str());
485         if (!of) {
486                 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
487                                         << "Unable to create LaTeX file\n"
488                                         << latexfile << endl;
489                 return;
490         }
491         of << "\\batchmode\n";
492         dumpPreamble(of);
493         of << "\n\\begin{document}\n";
494         dumpData(of, inprogress.snippets);
495         of << "\n\\end{document}\n";
496         of.close();
497
498         // The conversion command.
499         ostringstream cs;
500         cs << pconverter_->command << ' ' << pconverter_->to << ' '
501            << latexfile << ' ' << int(font_scaling_factor_) << ' '
502            << lyx_gui::hexname(LColor::preview) << ' '
503            << lyx_gui::hexname(LColor::background);
504
505         string const command = support::LibScriptSearch(cs.str());
506
507         // Initiate the conversion from LaTeX to bitmap images files.
508         support::Forkedcall::SignalTypePtr
509                 convert_ptr(new support::Forkedcall::SignalType);
510         convert_ptr->connect(bind(&Impl::finishedGenerating, this, _1, _2));
511
512         support::Forkedcall call;
513         int ret = call.startscript(command, convert_ptr);
514
515         if (ret != 0) {
516                 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
517                                         << "Unable to start process\n"
518                                         << command << endl;
519                 return;
520         }
521
522         // Store the generation process in a list of all such processes
523         inprogress.pid = call.pid();
524         inprogress.command = command;
525         in_progress_[inprogress.pid] = inprogress;
526 }
527
528
529 void PreviewLoader::Impl::finishedGenerating(pid_t pid, int retval)
530 {
531         // Paranoia check!
532         InProgressProcesses::iterator git = in_progress_.find(pid);
533         if (git == in_progress_.end()) {
534                 lyxerr << "PreviewLoader::finishedGenerating(): unable to find "
535                         "data for PID " << pid << endl;
536                 return;
537         }
538
539         string const command = git->second.command;
540         string const status = retval > 0 ? "failed" : "succeeded";
541         lyxerr[Debug::GRAPHICS] << "PreviewLoader::finishedInProgress("
542                                 << retval << "): processing " << status
543                                 << " for " << command << endl;
544         if (retval > 0)
545                 return;
546
547         // Read the metrics file, if it exists
548         vector<double> ascent_fractions(git->second.snippets.size());
549         setAscentFractions(ascent_fractions, git->second.metrics_file);
550
551         // Add these newly generated bitmap files to the cache and
552         // start loading them into LyX.
553         BitmapFile::const_iterator it  = git->second.snippets.begin();
554         BitmapFile::const_iterator end = git->second.snippets.end();
555
556         std::list<PreviewImagePtr> newimages;
557
558         int metrics_counter = 0;
559         for (; it != end; ++it, ++metrics_counter) {
560                 string const & snip = it->first;
561                 string const & file = it->second;
562                 double af = ascent_fractions[metrics_counter];
563
564                 PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af));
565                 cache_[snip] = ptr;
566
567                 newimages.push_back(ptr);
568         }
569
570         // Remove the item from the list of still-executing processes.
571         in_progress_.erase(git);
572
573         // Tell the outside world
574         std::list<PreviewImagePtr>::const_reverse_iterator
575                 nit  = newimages.rbegin();
576         std::list<PreviewImagePtr>::const_reverse_iterator
577                 nend = newimages.rend();
578         for (; nit != nend; ++nit) {
579                 imageReady(*nit->get());
580         }
581 }
582
583
584 void PreviewLoader::Impl::dumpPreamble(ostream & os) const
585 {
586         // Why on earth is Buffer::makeLaTeXFile a non-const method?
587         Buffer & tmp = const_cast<Buffer &>(buffer_);
588         // Dump the preamble only.
589         OutputParams runparams;
590         runparams.flavor = OutputParams::LATEX;
591         runparams.nice = true;
592         runparams.moving_arg = true;
593         runparams.free_spacing = true;
594         tmp.makeLaTeXFile(os, buffer_.filePath(), runparams, true, false);
595
596         // FIXME! This is a HACK! The proper fix is to control the 'true'
597         // passed to WriteStream below:
598         // int InsetFormula::latex(Buffer const &, ostream & os,
599         //                         OutputParams const & runparams) const
600         // {
601         //      WriteStream wi(os, runparams.moving_arg, true);
602         //      par_->write(wi);
603         //      return wi.line();
604         // }
605         os << "\n"
606            << "\\def\\lyxlock{}\n"
607            << "\n";
608
609         // Loop over the insets in the buffer and dump all the math-macros.
610         InsetBase & inset = buffer_.inset();
611         InsetIterator it = inset_iterator_begin(inset);
612         InsetIterator const end = inset_iterator_end(inset);
613
614         for (; it != end; ++it)
615                 if (it->lyxCode() == InsetBase::MATHMACRO_CODE)
616                         it->latex(buffer_, os, runparams);
617
618         // All equation lables appear as "(#)" + preview.sty's rendering of
619         // the label name
620         if (lyxrc.preview_hashed_labels)
621                 os << "\\renewcommand{\\theequation}{\\#}\n";
622
623         // Use the preview style file to ensure that each snippet appears on a
624         // fresh page.
625         os << "\n"
626            << "\\usepackage[active,delayed,dvips,showlabels,lyx]{preview}\n"
627            << "\n";
628 }
629
630
631 void PreviewLoader::Impl::dumpData(ostream & os,
632                                    BitmapFile const & vec) const
633 {
634         if (vec.empty())
635                 return;
636
637         BitmapFile::const_iterator it  = vec.begin();
638         BitmapFile::const_iterator end = vec.end();
639
640         for (; it != end; ++it) {
641                 os << "\\begin{preview}\n"
642                    << it->first
643                    << "\n\\end{preview}\n\n";
644         }
645 }
646
647 } // namespace graphics
648 } // namespace lyx
649
650 namespace {
651
652 string const unique_filename(string const bufferpath)
653 {
654         static int theCounter = 0;
655         string const filename = convert<string>(theCounter++) + "lyxpreview";
656         return support::AddName(bufferpath, filename);
657 }
658
659
660 Converter const * setConverter()
661 {
662         string const from = "lyxpreview";
663
664         typedef vector<string> FmtList;
665         typedef lyx::graphics::Cache GCache;
666         FmtList const loadableFormats = GCache::get().loadableFormats();
667         FmtList::const_iterator it  = loadableFormats.begin();
668         FmtList::const_iterator const end = loadableFormats.end();
669
670         for (; it != end; ++it) {
671                 string const to = *it;
672                 if (from == to)
673                         continue;
674
675                 Converter const * ptr = converters.getConverter(from, to);
676                 if (ptr)
677                         return ptr;
678         }
679
680         static bool first = true;
681         if (first) {
682                 first = false;
683                 lyxerr << "PreviewLoader::startLoading()\n"
684                        << "No converter from \"lyxpreview\" format has been "
685                         "defined."
686                        << endl;
687         }
688
689         return 0;
690 }
691
692
693 void setAscentFractions(vector<double> & ascent_fractions,
694                         string const & metrics_file)
695 {
696         // If all else fails, then the images will have equal ascents and
697         // descents.
698         vector<double>::iterator it  = ascent_fractions.begin();
699         vector<double>::iterator end = ascent_fractions.end();
700         fill(it, end, 0.5);
701
702         ifstream in(metrics_file.c_str());
703         if (!in.good()) {
704                 lyxerr[Debug::GRAPHICS]
705                         << "setAscentFractions(" << metrics_file << ")\n"
706                         << "Unable to open file!" << endl;
707                 return;
708         }
709
710         bool error = false;
711
712         int snippet_counter = 1;
713         while (!in.eof() && it != end) {
714                 string snippet;
715                 int id;
716                 double ascent_fraction;
717
718                 in >> snippet >> id >> ascent_fraction;
719
720                 if (!in.good())
721                         // eof after all
722                         break;
723
724                 error = snippet != "Snippet";
725                 if (error)
726                         break;
727
728                 error = id != snippet_counter;
729                 if (error)
730                         break;
731
732                 *it = ascent_fraction;
733
734                 ++snippet_counter;
735                 ++it;
736         }
737
738         if (error) {
739                 lyxerr[Debug::GRAPHICS]
740                         << "setAscentFractions(" << metrics_file << ")\n"
741                         << "Error reading file!\n" << endl;
742         }
743 }
744
745 } // namespace anon