]> git.lyx.org Git - lyx.git/blob - src/graphics/PreviewLoader.cpp
de.po: fix doubled accelerator keys
[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/FileName.h"
36 #include "support/filetools.h"
37 #include "support/ForkedCalls.h"
38 #include "support/lstrings.h"
39
40 #include <boost/bind.hpp>
41
42 #include <sstream>
43 #include <fstream>
44 #include <iomanip>
45
46 using namespace std;
47 using namespace lyx::support;
48
49 using boost::bind;
50
51 namespace {
52
53 typedef pair<string, FileName> SnippetPair;
54
55 // A list of all snippets to be converted to previews
56 typedef list<string> PendingSnippets;
57
58 // Each item in the vector is a pair<snippet, image file name>.
59 typedef vector<SnippetPair> BitmapFile;
60
61
62 string const unique_filename(string const & bufferpath)
63 {
64         static int theCounter = 0;
65         string const filename = lyx::convert<string>(theCounter++) + "lyxpreview";
66         return addName(bufferpath, filename);
67 }
68
69
70 lyx::Converter const * setConverter()
71 {
72         string const from = "lyxpreview";
73
74         typedef vector<string> FmtList;
75         typedef lyx::graphics::Cache GCache;
76         FmtList const & loadableFormats = GCache::get().loadableFormats();
77         FmtList::const_iterator it = loadableFormats.begin();
78         FmtList::const_iterator const end = loadableFormats.end();
79
80         for (; it != end; ++it) {
81                 string const to = *it;
82                 if (from == to)
83                         continue;
84
85                 lyx::Converter const * ptr = lyx::theConverters().getConverter(from, to);
86                 if (ptr)
87                         return ptr;
88         }
89
90         static bool first = true;
91         if (first) {
92                 first = false;
93                 LYXERR0("PreviewLoader::startLoading()\n"
94                   << "No converter from \"lyxpreview\" format has been defined.");
95         }
96         return 0;
97 }
98
99
100 void setAscentFractions(vector<double> & ascent_fractions,
101                         FileName const & metrics_file)
102 {
103         // If all else fails, then the images will have equal ascents and
104         // descents.
105         vector<double>::iterator it  = ascent_fractions.begin();
106         vector<double>::iterator end = ascent_fractions.end();
107         fill(it, end, 0.5);
108
109         ifstream in(metrics_file.toFilesystemEncoding().c_str());
110         if (!in.good()) {
111                 LYXERR(lyx::Debug::GRAPHICS, "setAscentFractions(" << metrics_file << ")\n"
112                         << "Unable to open file!");
113                 return;
114         }
115
116         bool error = false;
117
118         int snippet_counter = 1;
119         while (!in.eof() && it != end) {
120                 string snippet;
121                 int id;
122                 double ascent_fraction;
123
124                 in >> snippet >> id >> ascent_fraction;
125
126                 if (!in.good())
127                         // eof after all
128                         break;
129
130                 error = snippet != "Snippet";
131                 if (error)
132                         break;
133
134                 error = id != snippet_counter;
135                 if (error)
136                         break;
137
138                 *it = ascent_fraction;
139
140                 ++snippet_counter;
141                 ++it;
142         }
143
144         if (error) {
145                 LYXERR(lyx::Debug::GRAPHICS, "setAscentFractions(" << metrics_file << ")\n"
146                         << "Error reading file!\n");
147         }
148 }
149
150
151 class FindFirst
152 {
153 public:
154         FindFirst(string const & comp) : comp_(comp) {}
155         bool operator()(SnippetPair const & sp) const { return sp.first == comp_; }
156 private:
157         string const comp_;
158 };
159
160
161 /// Store info on a currently executing, forked process.
162 class InProgress {
163 public:
164         ///
165         InProgress() : pid(0) {}
166         ///
167         InProgress(string const & filename_base,
168                    PendingSnippets const & pending,
169                    string const & to_format);
170         /// Remove any files left lying around and kill the forked process.
171         void stop() const;
172
173         ///
174         pid_t pid;
175         ///
176         string command;
177         ///
178         FileName metrics_file;
179         ///
180         BitmapFile snippets;
181 };
182
183 typedef map<pid_t, InProgress>  InProgressProcesses;
184
185 typedef InProgressProcesses::value_type InProgressProcess;
186
187 } // namespace anon
188
189
190
191 namespace lyx {
192 namespace graphics {
193
194 class PreviewLoader::Impl : public boost::signals::trackable {
195 public:
196         ///
197         Impl(PreviewLoader & p, Buffer const & b);
198         /// Stop any InProgress items still executing.
199         ~Impl();
200         ///
201         PreviewImage const * preview(string const & latex_snippet) const;
202         ///
203         PreviewLoader::Status status(string const & latex_snippet) const;
204         ///
205         void add(string const & latex_snippet);
206         ///
207         void remove(string const & latex_snippet);
208         ///
209         void startLoading();
210
211         /// Emit this signal when an image is ready for display.
212         boost::signal<void(PreviewImage const &)> imageReady;
213
214         Buffer const & buffer() const { return buffer_; }
215
216 private:
217         /// Called by the ForkedCall process that generated the bitmap files.
218         void finishedGenerating(pid_t, int);
219         ///
220         void dumpPreamble(odocstream &) const;
221         ///
222         void dumpData(odocstream &, BitmapFile const &) const;
223
224         /** cache_ allows easy retrieval of already-generated images
225          *  using the LaTeX snippet as the identifier.
226          */
227         typedef boost::shared_ptr<PreviewImage> PreviewImagePtr;
228         ///
229         typedef map<string, PreviewImagePtr> Cache;
230         ///
231         Cache cache_;
232
233         /** pending_ stores the LaTeX snippets in anticipation of them being
234          *  sent to the converter.
235          */
236         PendingSnippets pending_;
237
238         /** in_progress_ stores all forked processes so that we can proceed
239          *  thereafter.
240             The map uses the conversion commands as its identifiers.
241          */
242         InProgressProcesses in_progress_;
243
244         ///
245         PreviewLoader & parent_;
246         ///
247         Buffer const & buffer_;
248         ///
249         double font_scaling_factor_;
250
251         /// We don't own this
252         static lyx::Converter const * pconverter_;
253 };
254
255
256 lyx::Converter const * PreviewLoader::Impl::pconverter_;
257
258
259 //
260 // The public interface, defined in PreviewLoader.h
261 //
262
263 PreviewLoader::PreviewLoader(Buffer const & b)
264         : pimpl_(new Impl(*this, b))
265 {}
266
267
268 PreviewLoader::~PreviewLoader()
269 {
270         delete pimpl_;
271 }
272
273
274 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
275 {
276         return pimpl_->preview(latex_snippet);
277 }
278
279
280 PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const
281 {
282         return pimpl_->status(latex_snippet);
283 }
284
285
286 void PreviewLoader::add(string const & latex_snippet) const
287 {
288         pimpl_->add(latex_snippet);
289 }
290
291
292 void PreviewLoader::remove(string const & latex_snippet) const
293 {
294         pimpl_->remove(latex_snippet);
295 }
296
297
298 void PreviewLoader::startLoading() const
299 {
300         pimpl_->startLoading();
301 }
302
303
304 boost::signals::connection PreviewLoader::connect(slot_type const & slot) const
305 {
306         return pimpl_->imageReady.connect(slot);
307 }
308
309
310 void PreviewLoader::emitSignal(PreviewImage const & pimage) const
311 {
312         pimpl_->imageReady(pimage);
313 }
314
315
316 Buffer const & PreviewLoader::buffer() const
317 {
318         return pimpl_->buffer();
319 }
320
321 } // namespace graphics
322 } // namespace lyx
323
324
325 // The details of the Impl
326 // =======================
327
328 namespace {
329
330 class IncrementedFileName {
331 public:
332         IncrementedFileName(string const & to_format,
333                             string const & filename_base)
334                 : to_format_(to_format), base_(filename_base), counter_(1)
335         {}
336
337         SnippetPair const operator()(string const & snippet)
338         {
339                 ostringstream os;
340                 os << base_ << counter_++ << '.' << to_format_;
341                 string const file = os.str();
342
343                 return make_pair(snippet, FileName(file));
344         }
345
346 private:
347         string const & to_format_;
348         string const & base_;
349         int counter_;
350 };
351
352
353 InProgress::InProgress(string const & filename_base,
354                        PendingSnippets const & pending,
355                        string const & to_format)
356         : pid(0),
357           metrics_file(filename_base + ".metrics"),
358           snippets(pending.size())
359 {
360         PendingSnippets::const_iterator pit  = pending.begin();
361         PendingSnippets::const_iterator pend = pending.end();
362         BitmapFile::iterator sit = snippets.begin();
363
364         transform(pit, pend, sit,
365                        IncrementedFileName(to_format, filename_base));
366 }
367
368
369 void InProgress::stop() const
370 {
371         if (pid)
372                 ForkedCallsController::kill(pid, 0);
373
374         if (!metrics_file.empty())
375                 metrics_file.removeFile();
376
377         BitmapFile::const_iterator vit  = snippets.begin();
378         BitmapFile::const_iterator vend = snippets.end();
379         for (; vit != vend; ++vit) {
380                 if (!vit->second.empty())
381                         vit->second.removeFile();
382         }
383 }
384
385 } // namespace anon
386
387
388 namespace lyx {
389 namespace graphics {
390
391 PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b)
392         : parent_(p), buffer_(b), font_scaling_factor_(0.0)
393 {
394         font_scaling_factor_ = 0.01 * lyxrc.dpi * lyxrc.zoom *
395                 convert<double>(lyxrc.preview_scale_factor);
396
397         LYXERR(Debug::GRAPHICS, "The font scaling factor is "
398                                 << font_scaling_factor_);
399
400         if (!pconverter_)
401                 pconverter_ = setConverter();
402 }
403
404
405 PreviewLoader::Impl::~Impl()
406 {
407         InProgressProcesses::iterator ipit  = in_progress_.begin();
408         InProgressProcesses::iterator ipend = in_progress_.end();
409
410         for (; ipit != ipend; ++ipit)
411                 ipit->second.stop();
412 }
413
414
415 PreviewImage const *
416 PreviewLoader::Impl::preview(string const & latex_snippet) const
417 {
418         Cache::const_iterator it = cache_.find(latex_snippet);
419         return (it == cache_.end()) ? 0 : it->second.get();
420 }
421
422
423 namespace {
424
425 class FindSnippet {
426 public:
427         FindSnippet(string const & s) : snippet_(s) {}
428         bool operator()(InProgressProcess const & process) const
429         {
430                 BitmapFile const & snippets = process.second.snippets;
431                 BitmapFile::const_iterator beg  = snippets.begin();
432                 BitmapFile::const_iterator end = snippets.end();
433                 return find_if(beg, end, FindFirst(snippet_)) != end;
434         }
435
436 private:
437         string const snippet_;
438 };
439
440 } // namespace anon
441
442 PreviewLoader::Status
443 PreviewLoader::Impl::status(string const & latex_snippet) const
444 {
445         Cache::const_iterator cit = cache_.find(latex_snippet);
446         if (cit != cache_.end())
447                 return Ready;
448
449         PendingSnippets::const_iterator pit  = pending_.begin();
450         PendingSnippets::const_iterator pend = pending_.end();
451
452         pit = find(pit, pend, latex_snippet);
453         if (pit != pend)
454                 return InQueue;
455
456         InProgressProcesses::const_iterator ipit  = in_progress_.begin();
457         InProgressProcesses::const_iterator ipend = in_progress_.end();
458
459         ipit = find_if(ipit, ipend, FindSnippet(latex_snippet));
460         if (ipit != ipend)
461                 return Processing;
462
463         return NotFound;
464 }
465
466
467 void PreviewLoader::Impl::add(string const & latex_snippet)
468 {
469         if (!pconverter_ || status(latex_snippet) != NotFound)
470                 return;
471
472         string const snippet = trim(latex_snippet);
473         if (snippet.empty())
474                 return;
475
476         LYXERR(Debug::GRAPHICS, "adding snippet:\n" << snippet);
477
478         pending_.push_back(snippet);
479 }
480
481
482 namespace {
483
484 class EraseSnippet {
485 public:
486         EraseSnippet(string const & s) : snippet_(s) {}
487         void operator()(InProgressProcess & process)
488         {
489                 BitmapFile & snippets = process.second.snippets;
490                 BitmapFile::iterator it  = snippets.begin();
491                 BitmapFile::iterator end = snippets.end();
492
493                 it = find_if(it, end, FindFirst(snippet_));
494                 if (it != end)
495                         snippets.erase(it, it+1);
496         }
497
498 private:
499         string const & snippet_;
500 };
501
502 } // namespace anon
503
504
505 void PreviewLoader::Impl::remove(string const & latex_snippet)
506 {
507         Cache::iterator cit = cache_.find(latex_snippet);
508         if (cit != cache_.end())
509                 cache_.erase(cit);
510
511         PendingSnippets::iterator pit  = pending_.begin();
512         PendingSnippets::iterator pend = pending_.end();
513
514         pending_.erase(std::remove(pit, pend, latex_snippet), pend);
515
516         InProgressProcesses::iterator ipit  = in_progress_.begin();
517         InProgressProcesses::iterator ipend = in_progress_.end();
518
519         for_each(ipit, ipend, EraseSnippet(latex_snippet));
520
521         while (ipit != ipend) {
522                 InProgressProcesses::iterator curr = ipit++;
523                 if (curr->second.snippets.empty())
524                         in_progress_.erase(curr);
525         }
526 }
527
528
529 void PreviewLoader::Impl::startLoading()
530 {
531         if (pending_.empty() || !pconverter_)
532                 return;
533
534         // Only start the process off after the buffer is loaded from file.
535         if (!buffer_.isFullyLoaded())
536                 return;
537
538         LYXERR(Debug::GRAPHICS, "PreviewLoader::startLoading()");
539
540         // As used by the LaTeX file and by the resulting image files
541         string const directory = buffer_.temppath();
542
543         string const filename_base = unique_filename(directory);
544
545         // Create an InProgress instance to place in the map of all
546         // such processes if it starts correctly.
547         InProgress inprogress(filename_base, pending_, pconverter_->to);
548
549         // clear pending_, so we're ready to start afresh.
550         pending_.clear();
551
552         // Output the LaTeX file.
553         FileName const latexfile(filename_base + ".tex");
554
555         // we use the encoding of the buffer
556         Encoding const & enc = buffer_.params().encoding();
557         odocfstream of;
558         try { of.reset(enc.iconvName()); }
559         catch (iconv_codecvt_facet_exception & e) {
560                 LYXERR0("Caught iconv exception: " << e.what()
561                         << "\nUnable to create LaTeX file: " << latexfile);
562                 return;
563         }
564
565         TexRow texrow;
566         OutputParams runparams(&enc);
567         LaTeXFeatures features(buffer_, buffer_.params(), runparams);
568
569         if (!openFileWrite(of, latexfile))
570                 return;
571
572         if (!of) {
573                 LYXERR(Debug::GRAPHICS, "PreviewLoader::startLoading()\n"
574                                         << "Unable to create LaTeX file\n" << latexfile);
575                 return;
576         }
577         of << "\\batchmode\n";
578         dumpPreamble(of);
579         // handle inputenc etc.
580         buffer_.params().writeEncodingPreamble(of, features, texrow);
581         of << "\n\\begin{document}\n";
582         dumpData(of, inprogress.snippets);
583         of << "\n\\end{document}\n";
584         of.close();
585         if (of.fail()) {
586                 LYXERR(Debug::GRAPHICS, "PreviewLoader::startLoading()\n"
587                                          << "File was not closed properly.");
588                 return;
589         }
590
591         // The conversion command.
592         ostringstream cs;
593         cs << pconverter_->command << ' ' << pconverter_->to << ' '
594            << quoteName(latexfile.toFilesystemEncoding()) << ' '
595            << int(font_scaling_factor_) << ' '
596            << theApp()->hexName(Color_preview) << ' '
597            << theApp()->hexName(Color_background);
598
599         string const command = libScriptSearch(cs.str());
600
601         // Initiate the conversion from LaTeX to bitmap images files.
602         ForkedCall::SignalTypePtr
603                 convert_ptr(new ForkedCall::SignalType);
604         convert_ptr->connect(bind(&Impl::finishedGenerating, this, _1, _2));
605
606         ForkedCall call;
607         int ret = call.startScript(command, convert_ptr);
608
609         if (ret != 0) {
610                 LYXERR(Debug::GRAPHICS, "PreviewLoader::startLoading()\n"
611                                         << "Unable to start process\n" << command);
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);
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         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         list<PreviewImagePtr>::const_reverse_iterator
668                 nit  = newimages.rbegin();
669         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         Inset & 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() == MATHMACRO_CODE)
711                         it->latex(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         // Also support PDF output (automatically generated e.g. when
721         // \usepackage[pdftex]{hyperref} is used.
722         os << "\n"
723            << "\\newif\\ifpdf\n"
724            << "\\ifx\\pdfoutput\\undefined\n"
725            << "\\else\\ifx\\pdfoutput\\relax\n"
726            << "\\else\\ifnum0=\\pdfoutput\n"
727            << "\\else\\pdftrue\\fi\\fi\\fi\n"
728            << "\\ifpdf\n"
729            << "  \\usepackage[active,delayed,tightpage,showlabels,lyx,pdftex]{preview}\n"
730            << "\\else\n"
731            << "  \\usepackage[active,delayed,showlabels,lyx,dvips]{preview}\n"
732            << "\\fi\n"
733            << "\n";
734 }
735
736
737 void PreviewLoader::Impl::dumpData(odocstream & os,
738                                    BitmapFile const & vec) const
739 {
740         if (vec.empty())
741                 return;
742
743         BitmapFile::const_iterator it  = vec.begin();
744         BitmapFile::const_iterator end = vec.end();
745
746         for (; it != end; ++it) {
747                 // FIXME UNICODE
748                 os << "\\begin{preview}\n"
749                    << from_utf8(it->first)
750                    << "\n\\end{preview}\n\n";
751         }
752 }
753
754 } // namespace graphics
755 } // namespace lyx