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