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