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