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