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