]> git.lyx.org Git - lyx.git/blob - src/graphics/PreviewLoader.C
Remove all generated files that still exist (perhaps because the process was
[lyx.git] / src / graphics / PreviewLoader.C
1 /*
2  *  \file PreviewLoader.C
3  *  Copyright 2002 the LyX Team
4  *  Read the file COPYING
5  *
6  * \author Angus Leeming <a.leeming@ic.ac.uk>
7  */
8
9 #include <config.h>
10
11 #ifdef __GNUG__
12 #pragma implementation
13 #endif
14
15 #include "PreviewLoader.h"
16 #include "PreviewImage.h"
17 #include "PreviewMetrics.h"
18
19 #include "buffer.h"
20 #include "bufferparams.h"
21 #include "converter.h"
22 #include "debug.h"
23 #include "lyxrc.h"
24 #include "LColor.h"
25
26 #include "insets/inset.h"
27
28 #include "frontends/lyx_gui.h" // hexname
29
30 #include "support/filetools.h"
31 #include "support/forkedcall.h"
32 #include "support/forkedcontr.h"
33 #include "support/lstrings.h"
34 #include "support/lyxlib.h"
35
36 #include <boost/bind.hpp>
37 #include <boost/signals/trackable.hpp>
38
39 #include <fstream>
40 #include <iomanip>
41 #include <map>
42
43 using std::endl;
44 using std::find_if;
45 using std::setfill;
46 using std::setw;
47 using std::sort;
48
49 using std::map;
50 using std::ofstream;
51 using std::ostream;
52 using std::pair;
53 using std::vector;
54
55 namespace {
56
57 typedef pair<string, string> StrPair;
58
59 struct CompSecond {
60         bool operator()(StrPair const & lhs, StrPair const & rhs)
61         {
62                 return lhs.second < rhs.second;
63         }
64 };
65
66 struct FindFirst {
67         FindFirst(string const & comp) : comp_(comp) {}
68         bool operator()(StrPair const & sp)
69         {
70                 return sp.first < comp_;
71         }
72 private:
73         string const comp_;
74 };
75
76
77 string const unique_filename()
78 {
79         
80         static string dir;
81         if (dir.empty()) {
82                 string const tmp = lyx::tempName();
83                 lyx::unlink(tmp);
84                 dir = OnlyPath(tmp);
85         }
86
87         static int theCounter = 0;
88         ostringstream os;
89         os << dir << theCounter++ << "lyxpreview";
90
91         return os.str().c_str();
92 }
93
94 } // namespace anon
95
96
97 namespace grfx {
98
99 struct PreviewLoader::Impl : public boost::signals::trackable {
100         ///
101         Impl(PreviewLoader & p, Buffer const & b);
102         ///
103         ~Impl();
104         ///
105         PreviewImage const * preview(string const & latex_snippet) const;
106         ///
107         PreviewLoader::Status status(string const & latex_snippet) const;
108         ///
109         void add(string const & latex_snippet);
110         ///
111         void remove(string const & latex_snippet);
112         ///
113         void startLoading();
114
115         ///
116         typedef pair<string, string> StrPair;
117         ///
118         typedef map<string, string> PendingMap;
119
120 private:
121         /// Called by the Forkedcall process that generated the bitmap files.
122         void finishedGenerating(string const &, pid_t, int);
123         ///
124         void dumpPreamble(ostream &) const;
125         ///
126         void dumpData(ostream &, vector<StrPair> const &) const;
127
128         ///
129         static void setConverter();
130         /// We don't own this
131         static Converter const * pconverter_;
132
133         /** cache_ allows easy retrieval of already-generated images
134          *  using the LaTeX snippet as the identifier.
135          */
136         typedef boost::shared_ptr<PreviewImage> PreviewImagePtr;
137         ///
138         typedef map<string, PreviewImagePtr> Cache;
139         ///
140         Cache cache_;
141
142         /** pending_ stores the LaTeX snippet and the name of the generated
143          *  bitmap image file in anticipation of them being sent to the
144          *  converter.
145          */
146         PendingMap pending_;
147
148         /// Store info on a currently executing, forked process.
149         struct InProgress {
150                 ///
151                 InProgress() {}
152                 ///
153                 InProgress(string const & f, PendingMap const & m)
154                         : pid(0), metrics_file(f), snippets(m.begin(), m.end())
155                 {
156                         sort(snippets.begin(), snippets.end(), CompSecond());
157                 }
158
159                 ///
160                 pid_t pid;
161                 ///
162                 string metrics_file;
163
164                 /** Store the info in the PendingMap as a vector.
165                     Ensures that the data is output in the order
166                     file001, file002 etc, as we expect, which is /not/ what
167                     happens when we iterate through the map.
168                  */
169                 vector<StrPair> snippets;
170         };
171         
172         /// Store all forked processes so that we can proceed thereafter.
173         typedef map<string, InProgress> InProgressMap;
174         ///
175         InProgressMap in_progress_;
176
177         ///
178         string filename_base_;
179         ///
180         PreviewLoader & parent_;
181         ///
182         Buffer const & buffer_;
183 };
184
185 Converter const * PreviewLoader::Impl::pconverter_;
186
187
188 PreviewLoader::PreviewLoader(Buffer const & b)
189         : pimpl_(new Impl(*this, b))
190 {}
191
192
193 PreviewLoader::~PreviewLoader()
194 {}
195
196
197 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
198 {
199         return pimpl_->preview(latex_snippet);
200 }
201
202
203 PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const
204 {
205         return pimpl_->status(latex_snippet);
206 }
207
208
209 void PreviewLoader::add(string const & latex_snippet)
210 {
211         pimpl_->add(latex_snippet);
212 }
213
214
215 void PreviewLoader::remove(string const & latex_snippet)
216 {
217         pimpl_->remove(latex_snippet);
218 }
219
220
221 void PreviewLoader::startLoading()
222 {
223         pimpl_->startLoading();
224 }
225
226
227 void PreviewLoader::Impl::setConverter()
228 {
229         if (pconverter_)
230                 return;
231
232         string const from = "lyxpreview";
233
234         Formats::FormatList::const_iterator it  = formats.begin();
235         Formats::FormatList::const_iterator end = formats.end();
236
237         for (; it != end; ++it) {
238                 string const to = it->name();
239                 if (from == to)
240                         continue;
241                 Converter const * ptr = converters.getConverter(from, to);
242                 if (ptr) {
243                         pconverter_ = ptr;
244                         break;
245                 }
246         }
247
248         if (pconverter_)
249                 return;
250
251         static bool first = true;
252         if (!first)
253                 return;
254
255         first = false;
256         lyxerr << "PreviewLoader::startLoading()\n"
257                << "No converter from \"lyxpreview\" format has been defined." 
258                << endl;
259 }
260
261
262 PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b)
263         : filename_base_(unique_filename()), parent_(p), buffer_(b)
264 {}
265
266
267 PreviewImage const *
268 PreviewLoader::Impl::preview(string const & latex_snippet) const
269 {
270         Cache::const_iterator it = cache_.find(latex_snippet);
271         return (it == cache_.end()) ? 0 : it->second.get();
272 }
273
274
275 PreviewLoader::Impl::~Impl()
276 {
277         InProgressMap::const_iterator ipit  = in_progress_.begin();
278         InProgressMap::const_iterator ipend = in_progress_.end();
279
280         for (; ipit != ipend; ++ipit) {
281                 pid_t pid = ipit->second.pid;
282                 if (pid)
283                         ForkedcallsController::get().kill(pid, 0);
284
285                 lyx::unlink(ipit->second.metrics_file);
286
287                 vector<StrPair> const & snippets = ipit->second.snippets;
288                 vector<StrPair>::const_iterator vit  = snippets.begin();
289                 vector<StrPair>::const_iterator vend = snippets.end();
290                 for (; vit != vend; ++vit) {
291                         lyx::unlink(vit->second);
292                 }
293         }
294 }
295
296
297 PreviewLoader::Status
298 PreviewLoader::Impl::status(string const & latex_snippet) const
299 {
300         Cache::const_iterator cit = cache_.find(latex_snippet);
301         if (cit != cache_.end())
302                 return PreviewLoader::Ready;
303
304         PendingMap::const_iterator pit = pending_.find(latex_snippet);
305         if (pit != pending_.end())
306                 return PreviewLoader::InQueue;
307
308         InProgressMap::const_iterator ipit  = in_progress_.begin();
309         InProgressMap::const_iterator ipend = in_progress_.end();
310
311         for (; ipit != ipend; ++ipit) {
312                 vector<StrPair> const & snippets = ipit->second.snippets;
313                 vector<StrPair>::const_iterator vit  = snippets.begin();
314                 vector<StrPair>::const_iterator vend = snippets.end();
315                 vit = find_if(vit, vend, FindFirst(latex_snippet));
316                 
317                 if (vit != vend)
318                         return PreviewLoader::Processing;
319         }
320
321         return PreviewLoader::NotFound;
322 }
323
324
325 void PreviewLoader::Impl::add(string const & latex_snippet)
326 {
327         if (!pconverter_) {
328                 setConverter();
329                 if (!pconverter_)
330                         return;
331         }
332
333         Cache::const_iterator cit = cache_.find(latex_snippet);
334         if (cit != cache_.end())
335                 return;
336
337         PendingMap::const_iterator pit = pending_.find(latex_snippet);
338         if (pit != pending_.end())
339                 return;
340
341         int const snippet_counter = int(pending_.size()) + 1;
342         ostringstream os;
343         os << filename_base_
344            << setfill('0') << setw(3) << snippet_counter
345            << "." << pconverter_->to;
346         string const image_filename = os.str().c_str();
347
348         pending_[latex_snippet] = image_filename;
349 }
350
351
352 void PreviewLoader::Impl::remove(string const & latex_snippet)
353 {
354         Cache::iterator cit = cache_.find(latex_snippet);
355         if (cit != cache_.end())
356                 cache_.erase(cit);
357
358         PendingMap::iterator pit = pending_.find(latex_snippet);
359         if (pit != pending_.end())
360                 pending_.erase(pit);
361
362         InProgressMap::iterator ipit  = in_progress_.begin();
363         InProgressMap::iterator ipend = in_progress_.end();
364
365         while (ipit != ipend) {
366                 InProgressMap::iterator curr = ipit;
367                 ++ipit;
368
369                 vector<StrPair> & snippets = curr->second.snippets;
370                 vector<StrPair>::iterator vit  = snippets.begin();
371                 vector<StrPair>::iterator vend = snippets.end();
372                 vit = find_if(vit, vend, FindFirst(latex_snippet));
373                 
374                 if (vit != vend)
375                         snippets.erase(vit, vit+1);
376
377                 if (snippets.empty())
378                         in_progress_.erase(curr);
379         }
380 }
381
382
383 void PreviewLoader::Impl::startLoading()
384 {
385         if (pending_.empty())
386                 return;
387
388         if (!pconverter_) {
389                 setConverter();
390                 if (!pconverter_)
391                         return;
392         }
393
394         lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()" << endl;
395
396         // Create an InProgress instance to place in the map of all
397         // such processes if it starts correctly.
398         string const metrics_file = filename_base_ + ".metrics";
399         InProgress inprogress(metrics_file, pending_);
400
401         // Output the LaTeX file.
402         string const latexfile = filename_base_ + ".tex";
403
404         ofstream of(latexfile.c_str());
405         dumpPreamble(of);
406         of << "\n\\begin{document}\n";
407         dumpData(of, inprogress.snippets);
408         of << "\n\\end{document}\n";
409         of.close();
410
411         // Reset the filename and clear pending_, so we're ready to
412         // start afresh.
413         pending_.clear();
414         filename_base_ = unique_filename();
415
416         // The conversion command.
417         ostringstream cs;
418         cs << pconverter_->command << " " << latexfile << " "
419            << tostr(0.01 * lyxrc.dpi * lyxrc.zoom);
420
421         string const command = cs.str().c_str();
422
423         // Initiate the conversion from LaTeX to bitmap images files.
424         Forkedcall::SignalTypePtr convert_ptr;
425         convert_ptr.reset(new Forkedcall::SignalType);
426
427         convert_ptr->connect(
428                 boost::bind(&Impl::finishedGenerating, this, _1, _2, _3));
429
430         Forkedcall call;
431         int ret = call.startscript(command, convert_ptr);
432
433         if (ret != 0) {
434                 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
435                                         << "Unable to start process \n"
436                                         << command << endl;
437                 return;
438         }
439         
440         // Store the generation process in a list of all generating processes
441         // (I anticipate that this will be small!)
442         inprogress.pid = call.pid();
443         in_progress_[command] = inprogress;
444 }
445
446
447 void PreviewLoader::Impl::finishedGenerating(string const & command,
448                                              pid_t /* pid */, int retval)
449 {
450         string const status = retval > 0 ? "failed" : "succeeded";
451         lyxerr[Debug::GRAPHICS] << "PreviewLoader::finishedInProgress("
452                                 << retval << "): processing " << status
453                                 << " for " << command << endl;
454         if (retval > 0)
455                 return;
456
457         InProgressMap::iterator git = in_progress_.find(command);
458         if (git == in_progress_.end()) {
459                 lyxerr << "PreviewLoader::finishedGenerating(): unable to find "
460                         "data for\n"
461                        << command << "!" << endl;
462                 return;
463         }
464
465         // Reset the pid to 0 as the process has finished.
466         git->second.pid = 0;
467
468         // Read the metrics file, if it exists
469         PreviewMetrics metrics_file(git->second.metrics_file);
470         
471         // Add these newly generated bitmap files to the cache and
472         // start loading them into LyX.
473         vector<StrPair>::const_iterator it  = git->second.snippets.begin();
474         vector<StrPair>::const_iterator end = git->second.snippets.end();
475
476         int metrics_counter = 0;
477         for (; it != end; ++it) {
478                 string const & snip = it->first;
479
480                 // Paranoia check
481                 Cache::const_iterator chk = cache_.find(snip);
482                 if (chk != cache_.end())
483                         continue;
484
485                 // Mental note (Angus, 4 July 2002, having just found out the
486                 // hard way :-().
487                 // We /must/ first add to the cache and then start the
488                 // image loading process.
489                 // If not, then outside functions can be called before by the
490                 // image loader before the PreviewImage is properly constucted.
491                 // This can lead to all sorts of horribleness if such a
492                 // function attempts to access its internals.
493                 string const & file = it->second;
494                 double af = metrics_file.ascent_fraction(metrics_counter++);
495                 PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af));
496
497                 cache_[snip] = ptr;
498
499                 ptr->startLoading();
500         }
501
502         in_progress_.erase(git);
503 }
504
505
506 void PreviewLoader::Impl::dumpPreamble(ostream & os) const
507 {
508         // Why on earth is Buffer::makeLaTeXFile a non-const method?
509         Buffer & tmp = const_cast<Buffer &>(buffer_);
510         // Dump the preamble only.
511         tmp.makeLaTeXFile(os, string(), true, false, true);
512
513         // Loop over the insets in the buffer and dump all the math-macros.
514         Buffer::inset_iterator it  = buffer_.inset_const_iterator_begin();
515         Buffer::inset_iterator end = buffer_.inset_const_iterator_end();
516
517         for (; it != end; ++it) {
518                 if ((*it)->lyxCode() == Inset::MATHMACRO_CODE) {
519                         (*it)->latex(&buffer_, os, true, true);
520                 }
521         }
522
523         // Use the preview style file to ensure that each snippet appears on a
524         // fresh page.
525         os << "\n"
526            << "\\usepackage[active,dvips,tightpage]{preview}\n"
527            << "\n";
528
529         // This piece of PostScript magic ensures that the foreground and
530         // background colors are the same as the LyX screen.
531         string fg = lyx_gui::hexname(LColor::preview);
532         if (fg.empty()) fg = "000000";
533
534         string bg = lyx_gui::hexname(LColor::background);
535         if (bg.empty()) bg = "ffffff";
536         
537         os << "\\AtBeginDocument{\\AtBeginDvi{%\n"
538            << "\\special{!userdict begin/bop-hook{//bop-hook exec\n"
539            << "<" << fg << bg << ">{255 div}forall setrgbcolor\n"
540            << "clippath fill setrgbcolor}bind def end}}}\n";
541 }
542
543
544 void PreviewLoader::Impl::dumpData(ostream & os, 
545                                    vector<StrPair> const & vec) const
546 {
547         if (vec.empty())
548                 return;
549
550         vector<StrPair>::const_iterator it  = vec.begin();
551         vector<StrPair>::const_iterator end = vec.end();
552
553         for (; it != end; ++it) {
554                 os << "\\begin{preview}\n"
555                    << it->first 
556                    << "\n\\end{preview}\n\n";
557         }
558 }
559
560 } // namespace grfx