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