]> git.lyx.org Git - lyx.git/blob - src/graphics/PreviewLoader.C
The func.diff patch. Functors work and some tiny cleanup.
[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().empty() ?
465                 buffer_.filePath() : buffer_.temppath();
466
467         string const filename_base(unique_filename(directory));
468
469         // Create an InProgress instance to place in the map of all
470         // such processes if it starts correctly.
471         InProgress inprogress(filename_base, pending_, pconverter_->to);
472
473         // clear pending_, so we're ready to start afresh.
474         pending_.clear();
475
476         // Output the LaTeX file.
477         string const latexfile = filename_base + ".tex";
478
479         ofstream of(latexfile.c_str());
480         of << "\\batchmode\n";
481         dumpPreamble(of);
482         of << "\n\\begin{document}\n";
483         dumpData(of, inprogress.snippets);
484         of << "\n\\end{document}\n";
485         of.close();
486
487         // The conversion command.
488         ostringstream cs;
489         cs << pconverter_->command << ' ' << latexfile << ' '
490            << int(font_scaling_factor_) << ' ' << pconverter_->to;
491
492         string const command = "sh " + support::LibScriptSearch(cs.str());
493
494         // Initiate the conversion from LaTeX to bitmap images files.
495         support::Forkedcall::SignalTypePtr
496                 convert_ptr(new support::Forkedcall::SignalType);
497         convert_ptr->connect(bind(&Impl::finishedGenerating, this, _1, _2));
498
499         support::Forkedcall call;
500         int ret = call.startscript(command, convert_ptr);
501
502         if (ret != 0) {
503                 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
504                                         << "Unable to start process \n"
505                                         << command << endl;
506                 return;
507         }
508
509         // Store the generation process in a list of all such processes
510         inprogress.pid = call.pid();
511         inprogress.command = command;
512         in_progress_[inprogress.pid] = inprogress;
513 }
514
515
516 void PreviewLoader::Impl::finishedGenerating(pid_t pid, int retval)
517 {
518         // Paranoia check!
519         InProgressProcesses::iterator git = in_progress_.find(pid);
520         if (git == in_progress_.end()) {
521                 lyxerr << "PreviewLoader::finishedGenerating(): unable to find "
522                         "data for PID " << pid << endl;
523                 return;
524         }
525
526         string const command = git->second.command;
527         string const status = retval > 0 ? "failed" : "succeeded";
528         lyxerr[Debug::GRAPHICS] << "PreviewLoader::finishedInProgress("
529                                 << retval << "): processing " << status
530                                 << " for " << command << endl;
531         if (retval > 0)
532                 return;
533
534         // Read the metrics file, if it exists
535         vector<double> ascent_fractions(git->second.snippets.size());
536         setAscentFractions(ascent_fractions, git->second.metrics_file);
537
538         // Add these newly generated bitmap files to the cache and
539         // start loading them into LyX.
540         BitmapFile::const_iterator it  = git->second.snippets.begin();
541         BitmapFile::const_iterator end = git->second.snippets.end();
542
543         std::list<PreviewImagePtr> newimages;
544
545         int metrics_counter = 0;
546         for (; it != end; ++it, ++metrics_counter) {
547                 string const & snip = it->first;
548                 string const & file = it->second;
549                 double af = ascent_fractions[metrics_counter];
550
551                 PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af));
552                 cache_[snip] = ptr;
553
554                 newimages.push_back(ptr);
555         }
556
557         // Remove the item from the list of still-executing processes.
558         in_progress_.erase(git);
559
560         // Tell the outside world
561         std::list<PreviewImagePtr>::const_reverse_iterator
562                 nit  = newimages.rbegin();
563         std::list<PreviewImagePtr>::const_reverse_iterator
564                 nend = newimages.rend();
565         for (; nit != nend; ++nit) {
566                 imageReady(*nit->get());
567         }
568 }
569
570
571 void PreviewLoader::Impl::dumpPreamble(ostream & os) const
572 {
573         // Why on earth is Buffer::makeLaTeXFile a non-const method?
574         Buffer & tmp = const_cast<Buffer &>(buffer_);
575         // Dump the preamble only.
576         OutputParams runparams;
577         runparams.flavor = OutputParams::LATEX;
578         runparams.nice = true;
579         runparams.moving_arg = true;
580         runparams.free_spacing = true;
581         tmp.makeLaTeXFile(os, buffer_.filePath(), runparams, true, false);
582
583         // FIXME! This is a HACK! The proper fix is to control the 'true'
584         // passed to WriteStream below:
585         // int InsetFormula::latex(Buffer const &, ostream & os,
586         //                         OutputParams const & runparams) const
587         // {
588         //      WriteStream wi(os, runparams.moving_arg, true);
589         //      par_->write(wi);
590         //      return wi.line();
591         // }
592         os << "\n"
593            << "\\def\\lyxlock{}\n"
594            << "\n";
595
596         // Loop over the insets in the buffer and dump all the math-macros.
597         Buffer::inset_iterator it  = buffer_.inset_const_iterator_begin();
598         Buffer::inset_iterator end = buffer_.inset_const_iterator_end();
599
600         for (; it != end; ++it)
601                 if (it->lyxCode() == InsetOld::MATHMACRO_CODE)
602                         it->latex(buffer_, os, runparams);
603
604         // All equation lables appear as "(#)" + preview.sty's rendering of
605         // the label name
606         if (lyxrc.preview_hashed_labels)
607                 os << "\\renewcommand{\\theequation}{\\#}\n";
608
609         // Use the preview style file to ensure that each snippet appears on a
610         // fresh page.
611         os << "\n"
612            << "\\usepackage[active,delayed,dvips,tightpage,showlabels,lyx]{preview}\n"
613            << "\n";
614
615         // This piece of PostScript magic ensures that the foreground and
616         // background colors are the same as the LyX screen.
617         string fg = lyx_gui::hexname(LColor::preview);
618         if (fg.empty()) fg = "000000";
619
620         string bg = lyx_gui::hexname(LColor::background);
621         if (bg.empty()) bg = "ffffff";
622
623         os << "\\AtBeginDocument{\\AtBeginDvi{%\n"
624            << "\\special{!userdict begin/bop-hook{//bop-hook exec\n"
625            << '<' << fg << bg << ">{255 div}forall setrgbcolor\n"
626            << "clippath fill setrgbcolor}bind def end}}}\n";
627 }
628
629
630 void PreviewLoader::Impl::dumpData(ostream & os,
631                                    BitmapFile const & vec) const
632 {
633         if (vec.empty())
634                 return;
635
636         BitmapFile::const_iterator it  = vec.begin();
637         BitmapFile::const_iterator end = vec.end();
638
639         for (; it != end; ++it) {
640                 os << "\\begin{preview}\n"
641                    << it->first
642                    << "\n\\end{preview}\n\n";
643         }
644 }
645
646 } // namespace graphics
647 } // namespace lyx
648
649 namespace {
650
651 string const unique_filename(string const bufferpath)
652 {
653         static int theCounter = 0;
654         string const filename = tostr(theCounter++) + "lyxpreview";
655         return support::AddName(bufferpath, filename);
656 }
657
658
659 Converter const * setConverter()
660 {
661         string const from = "lyxpreview";
662
663         Formats::FormatList::const_iterator it  = formats.begin();
664         Formats::FormatList::const_iterator end = formats.end();
665
666         for (; it != end; ++it) {
667                 string const to = it->name();
668                 if (from == to)
669                         continue;
670
671                 Converter const * ptr = converters.getConverter(from, to);
672                 if (ptr)
673                         return ptr;
674         }
675
676         static bool first = true;
677         if (first) {
678                 first = false;
679                 lyxerr << "PreviewLoader::startLoading()\n"
680                        << "No converter from \"lyxpreview\" format has been "
681                         "defined."
682                        << endl;
683         }
684
685         return 0;
686 }
687
688
689 void setAscentFractions(vector<double> & ascent_fractions,
690                         string const & metrics_file)
691 {
692         // If all else fails, then the images will have equal ascents and
693         // descents.
694         vector<double>::iterator it  = ascent_fractions.begin();
695         vector<double>::iterator end = ascent_fractions.end();
696         fill(it, end, 0.5);
697
698         ifstream in(metrics_file.c_str());
699         if (!in.good()) {
700                 lyxerr[Debug::GRAPHICS]
701                         << "setAscentFractions(" << metrics_file << ")\n"
702                         << "Unable to open file!" << endl;
703                 return;
704         }
705
706         bool error = false;
707
708         // Tightpage dimensions affect all subsequent dimensions
709         int tp_ascent;
710         int tp_descent;
711
712         int snippet_counter = 0;
713         while (!in.eof()) {
714                 // Expecting lines of the form
715                 // Preview: Tightpage tp_bl_x tp_bl_y tp_tr_x tp_tr_y
716                 // Preview: Snippet id ascent descent width
717                 string preview;
718                 string type;
719                 in >> preview >> type;
720
721                 if (!in.good())
722                         // eof after all
723                         break;
724
725                 error = preview != "Preview:"
726                         || (type != "Tightpage" && type != "Snippet");
727                 if (error)
728                         break;
729
730                 if (type == "Tightpage") {
731                         int dummy;
732                         in >> dummy >> tp_descent >> dummy >> tp_ascent;
733
734                         error = !in.good();
735                         if (error)
736                                 break;
737
738                 } else {
739                         int dummy;
740                         int snippet_id;
741                         int ascent;
742                         int descent;
743                         in >> snippet_id >> ascent >> descent >> dummy;
744
745                         error = !in.good() || ++snippet_counter != snippet_id;
746                         if (error)
747                                 break;
748
749                         double const a = ascent + tp_ascent;
750                         double const d = descent - tp_descent;
751
752                         if (!support::float_equal(a + d, 0, 0.1))
753                                 *it = a / (a + d);
754
755                         if (++it == end)
756                                 break;
757                 }
758         }
759
760         if (error) {
761                 lyxerr[Debug::GRAPHICS]
762                         << "setAscentFractions(" << metrics_file << ")\n"
763                         << "Error reading file!\n" << endl;
764         }
765 }
766
767 } // namespace anon