]> git.lyx.org Git - lyx.git/blob - src/graphics/PreviewLoader.cpp
* docstream: factorize out some code and introduce odocfstream::reset()
[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::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 PreviewImage const *
430 PreviewLoader::Impl::preview(string const & latex_snippet) const
431 {
432         Cache::const_iterator it = cache_.find(latex_snippet);
433         return (it == cache_.end()) ? 0 : it->second.get();
434 }
435
436
437 namespace {
438
439 class FindSnippet {
440 public:
441         FindSnippet(string const & s) : snippet_(s) {}
442         bool operator()(InProgressProcess const & process) const
443         {
444                 BitmapFile const & snippets = process.second.snippets;
445                 BitmapFile::const_iterator beg  = snippets.begin();
446                 BitmapFile::const_iterator end = snippets.end();
447                 return find_if(beg, end, FindFirst(snippet_)) != end;
448         }
449
450 private:
451         string const snippet_;
452 };
453
454 } // namespace anon
455
456 PreviewLoader::Status
457 PreviewLoader::Impl::status(string const & latex_snippet) const
458 {
459         Cache::const_iterator cit = cache_.find(latex_snippet);
460         if (cit != cache_.end())
461                 return Ready;
462
463         PendingSnippets::const_iterator pit  = pending_.begin();
464         PendingSnippets::const_iterator pend = pending_.end();
465
466         pit = find(pit, pend, latex_snippet);
467         if (pit != pend)
468                 return InQueue;
469
470         InProgressProcesses::const_iterator ipit  = in_progress_.begin();
471         InProgressProcesses::const_iterator ipend = in_progress_.end();
472
473         ipit = find_if(ipit, ipend, FindSnippet(latex_snippet));
474         if (ipit != ipend)
475                 return Processing;
476
477         return NotFound;
478 }
479
480
481 void PreviewLoader::Impl::add(string const & latex_snippet)
482 {
483         if (!pconverter_ || status(latex_snippet) != NotFound)
484                 return;
485
486         string const snippet = support::trim(latex_snippet);
487         if (snippet.empty())
488                 return;
489
490         LYXERR(Debug::GRAPHICS, "adding snippet:\n" << snippet);
491
492         pending_.push_back(snippet);
493 }
494
495
496 namespace {
497
498 class EraseSnippet {
499 public:
500         EraseSnippet(string const & s) : snippet_(s) {}
501         void operator()(InProgressProcess & process)
502         {
503                 BitmapFile & snippets = process.second.snippets;
504                 BitmapFile::iterator it  = snippets.begin();
505                 BitmapFile::iterator end = snippets.end();
506
507                 it = find_if(it, end, FindFirst(snippet_));
508                 if (it != end)
509                         snippets.erase(it, it+1);
510         }
511
512 private:
513         string const & snippet_;
514 };
515
516 } // namespace anon
517
518
519 void PreviewLoader::Impl::remove(string const & latex_snippet)
520 {
521         Cache::iterator cit = cache_.find(latex_snippet);
522         if (cit != cache_.end())
523                 cache_.erase(cit);
524
525         PendingSnippets::iterator pit  = pending_.begin();
526         PendingSnippets::iterator pend = pending_.end();
527
528         pending_.erase(std::remove(pit, pend, latex_snippet), pend);
529
530         InProgressProcesses::iterator ipit  = in_progress_.begin();
531         InProgressProcesses::iterator ipend = in_progress_.end();
532
533         std::for_each(ipit, ipend, EraseSnippet(latex_snippet));
534
535         while (ipit != ipend) {
536                 InProgressProcesses::iterator curr = ipit++;
537                 if (curr->second.snippets.empty())
538                         in_progress_.erase(curr);
539         }
540 }
541
542
543 void PreviewLoader::Impl::startLoading()
544 {
545         if (pending_.empty() || !pconverter_)
546                 return;
547
548         // Only start the process off after the buffer is loaded from file.
549         if (!buffer_.isFullyLoaded())
550                 return;
551
552         LYXERR(Debug::GRAPHICS, "PreviewLoader::startLoading()");
553
554         // As used by the LaTeX file and by the resulting image files
555         string const directory = buffer_.temppath();
556
557         string const filename_base = unique_filename(directory);
558
559         // Create an InProgress instance to place in the map of all
560         // such processes if it starts correctly.
561         InProgress inprogress(filename_base, pending_, pconverter_->to);
562
563         // clear pending_, so we're ready to start afresh.
564         pending_.clear();
565
566         // Output the LaTeX file.
567         FileName const latexfile(filename_base + ".tex");
568
569         // we use the encoding of the buffer
570         Encoding const & enc = buffer_.params().encoding();
571         odocfstream of(enc.iconvName());
572         TexRow texrow;
573         OutputParams runparams(&enc);
574         LaTeXFeatures features(buffer_, buffer_.params(), runparams);
575
576         if (!openFileWrite(of, latexfile))
577                 return;
578
579         if (!of) {
580                 LYXERR(Debug::GRAPHICS, "PreviewLoader::startLoading()\n"
581                                         << "Unable to create LaTeX file\n" << latexfile);
582                 return;
583         }
584         of << "\\batchmode\n";
585         dumpPreamble(of);
586         // handle inputenc etc.
587         buffer_.params().writeEncodingPreamble(of, features, texrow);
588         of << "\n\\begin{document}\n";
589         dumpData(of, inprogress.snippets);
590         of << "\n\\end{document}\n";
591         of.close();
592         if (of.fail()) {
593                 LYXERR(Debug::GRAPHICS, "PreviewLoader::startLoading()\n"
594                                          << "File was not closed properly.");
595                 return;
596         }
597
598         // The conversion command.
599         ostringstream cs;
600         cs << pconverter_->command << ' ' << pconverter_->to << ' '
601            << support::quoteName(latexfile.toFilesystemEncoding()) << ' '
602            << int(font_scaling_factor_) << ' '
603            << theApp()->hexName(Color_preview) << ' '
604            << theApp()->hexName(Color_background);
605
606         string const command = support::libScriptSearch(cs.str());
607
608         // Initiate the conversion from LaTeX to bitmap images files.
609         support::ForkedCall::SignalTypePtr
610                 convert_ptr(new support::ForkedCall::SignalType);
611         convert_ptr->connect(bind(&Impl::finishedGenerating, this, _1, _2));
612
613         support::ForkedCall call;
614         int ret = call.startScript(command, convert_ptr);
615
616         if (ret != 0) {
617                 LYXERR(Debug::GRAPHICS, "PreviewLoader::startLoading()\n"
618                                         << "Unable to start process\n" << command);
619                 return;
620         }
621
622         // Store the generation process in a list of all such processes
623         inprogress.pid = call.pid();
624         inprogress.command = command;
625         in_progress_[inprogress.pid] = inprogress;
626 }
627
628
629 void PreviewLoader::Impl::finishedGenerating(pid_t pid, int retval)
630 {
631         // Paranoia check!
632         InProgressProcesses::iterator git = in_progress_.find(pid);
633         if (git == in_progress_.end()) {
634                 lyxerr << "PreviewLoader::finishedGenerating(): unable to find "
635                         "data for PID " << pid << endl;
636                 return;
637         }
638
639         string const command = git->second.command;
640         string const status = retval > 0 ? "failed" : "succeeded";
641         LYXERR(Debug::GRAPHICS, "PreviewLoader::finishedInProgress("
642                                 << retval << "): processing " << status
643                                 << " for " << command);
644         if (retval > 0)
645                 return;
646
647         // Read the metrics file, if it exists
648         vector<double> ascent_fractions(git->second.snippets.size());
649         setAscentFractions(ascent_fractions, git->second.metrics_file);
650
651         // Add these newly generated bitmap files to the cache and
652         // start loading them into LyX.
653         BitmapFile::const_iterator it  = git->second.snippets.begin();
654         BitmapFile::const_iterator end = git->second.snippets.end();
655
656         std::list<PreviewImagePtr> newimages;
657
658         int metrics_counter = 0;
659         for (; it != end; ++it, ++metrics_counter) {
660                 string const & snip = it->first;
661                 FileName const & file = it->second;
662                 double af = ascent_fractions[metrics_counter];
663
664                 PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af));
665                 cache_[snip] = ptr;
666
667                 newimages.push_back(ptr);
668         }
669
670         // Remove the item from the list of still-executing processes.
671         in_progress_.erase(git);
672
673         // Tell the outside world
674         std::list<PreviewImagePtr>::const_reverse_iterator
675                 nit  = newimages.rbegin();
676         std::list<PreviewImagePtr>::const_reverse_iterator
677                 nend = newimages.rend();
678         for (; nit != nend; ++nit) {
679                 imageReady(*nit->get());
680         }
681 }
682
683
684 void PreviewLoader::Impl::dumpPreamble(odocstream & os) const
685 {
686         // Why on earth is Buffer::makeLaTeXFile a non-const method?
687         Buffer & tmp = const_cast<Buffer &>(buffer_);
688         // Dump the preamble only.
689         // We don't need an encoding for runparams since it is not used by
690         // the preamble.
691         OutputParams runparams(0);
692         runparams.flavor = OutputParams::LATEX;
693         runparams.nice = true;
694         runparams.moving_arg = true;
695         runparams.free_spacing = true;
696         tmp.writeLaTeXSource(os, buffer_.filePath(), runparams, true, false);
697
698         // FIXME! This is a HACK! The proper fix is to control the 'true'
699         // passed to WriteStream below:
700         // int InsetMathNest::latex(Buffer const &, odocstream & os,
701         //                          OutputParams const & runparams) const
702         // {
703         //      WriteStream wi(os, runparams.moving_arg, true);
704         //      par_->write(wi);
705         //      return wi.line();
706         // }
707         os << "\n"
708            << "\\def\\lyxlock{}\n"
709            << "\n";
710
711         // Loop over the insets in the buffer and dump all the math-macros.
712         Inset & inset = buffer_.inset();
713         InsetIterator it = inset_iterator_begin(inset);
714         InsetIterator const end = inset_iterator_end(inset);
715
716         for (; it != end; ++it)
717                 if (it->lyxCode() == MATHMACRO_CODE)
718                         it->latex(buffer_, os, runparams);
719
720         // All equation labels appear as "(#)" + preview.sty's rendering of
721         // the label name
722         if (lyxrc.preview_hashed_labels)
723                 os << "\\renewcommand{\\theequation}{\\#}\n";
724
725         // Use the preview style file to ensure that each snippet appears on a
726         // fresh page.
727         os << "\n"
728            << "\\usepackage[active,delayed,dvips,showlabels,lyx]{preview}\n"
729            << "\n";
730 }
731
732
733 void PreviewLoader::Impl::dumpData(odocstream & os,
734                                    BitmapFile const & vec) const
735 {
736         if (vec.empty())
737                 return;
738
739         BitmapFile::const_iterator it  = vec.begin();
740         BitmapFile::const_iterator end = vec.end();
741
742         for (; it != end; ++it) {
743                 // FIXME UNICODE
744                 os << "\\begin{preview}\n"
745                    << from_utf8(it->first)
746                    << "\n\\end{preview}\n\n";
747         }
748 }
749
750 } // namespace graphics
751 } // namespace lyx