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