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