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