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