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