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