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