2 * \file PreviewLoader.C
3 * Copyright 2002 the LyX Team
4 * Read the file COPYING
6 * \author Angus Leeming <a.leeming@ic.ac.uk>
12 #pragma implementation
15 #include "PreviewLoader.h"
16 #include "PreviewImage.h"
17 #include "PreviewMetrics.h"
20 #include "bufferparams.h"
21 #include "converter.h"
26 #include "insets/inset.h"
28 #include "frontends/lyx_gui.h" // hexname
30 #include "support/filetools.h"
31 #include "support/forkedcall.h"
32 #include "support/lstrings.h"
33 #include "support/lyxlib.h"
35 #include <boost/bind.hpp>
36 #include <boost/signals/trackable.hpp>
51 string const unique_filename()
56 string const tmp = lyx::tempName();
61 static int theCounter = 0;
63 os << dir << theCounter++ << "lyxpreview";
65 return os.str().c_str();
73 struct PreviewLoader::Impl : public boost::signals::trackable {
75 Impl(PreviewLoader & p, Buffer const & b);
77 PreviewImage const * preview(string const & latex_snippet) const;
79 PreviewLoader::Status status(string const & latex_snippet) const;
81 void add(string const & latex_snippet);
83 void remove(string const & latex_snippet);
88 /// Called by the Forkedcall process that generated the bitmap files.
89 void finishedGenerating(string const &, pid_t, int);
91 void dumpPreamble(ostream &) const;
93 void dumpData(ostream &) const;
96 static void setConverter();
98 static Converter const * pconverter_;
100 /** The cache allows easy retrieval of already-generated images
101 * using the LaTeX snippet as the identifier.
103 typedef boost::shared_ptr<PreviewImage> PreviewImagePtr;
105 typedef std::map<string, PreviewImagePtr> Cache;
109 /** The map stores the LaTeX snippet and the name of the generated
112 typedef std::map<string, string> PendingMap;
116 /// Store info on a currently executing, forked process.
119 typedef std::map<string, string> PendingMap;
123 InProgress(string const & mf, PendingMap const & s)
124 : metrics_file(mf), snippets(s)
131 friend class InProgress;
133 /// Store all forked processes so that we can proceed thereafter.
134 typedef std::map<string, InProgress> InProgressMap;
136 InProgressMap in_progress_;
139 string filename_base_;
141 PreviewLoader & parent_;
143 Buffer const & buffer_;
147 Converter const * PreviewLoader::Impl::pconverter_;
150 PreviewLoader::PreviewLoader(Buffer const & b)
151 : pimpl_(new Impl(*this, b))
155 PreviewLoader::~PreviewLoader()
159 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
161 return pimpl_->preview(latex_snippet);
165 PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const
167 return pimpl_->status(latex_snippet);
171 void PreviewLoader::add(string const & latex_snippet)
173 pimpl_->add(latex_snippet);
177 void PreviewLoader::remove(string const & latex_snippet)
179 pimpl_->remove(latex_snippet);
183 void PreviewLoader::startLoading()
185 pimpl_->startLoading();
189 void PreviewLoader::Impl::setConverter()
194 string const from = "lyxpreview";
196 Formats::FormatList::const_iterator it = formats.begin();
197 Formats::FormatList::const_iterator end = formats.end();
199 for (; it != end; ++it) {
200 string const to = it->name();
203 Converter const * ptr = converters.getConverter(from, to);
213 static bool first = true;
218 lyxerr << "PreviewLoader::startLoading()\n"
219 << "No converter from \"lyxpreview\" format has been defined."
224 PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b)
225 : filename_base_(unique_filename()), parent_(p), buffer_(b)
230 PreviewLoader::Impl::preview(string const & latex_snippet) const
232 Cache::const_iterator it = cache_.find(latex_snippet);
233 return (it == cache_.end()) ? 0 : it->second.get();
237 PreviewLoader::Status
238 PreviewLoader::Impl::status(string const & latex_snippet) const
240 Cache::const_iterator cit = cache_.find(latex_snippet);
241 if (cit != cache_.end())
242 return PreviewLoader::Ready;
244 PendingMap::const_iterator pit = pending_.find(latex_snippet);
245 if (pit != pending_.end())
246 return PreviewLoader::InQueue;
248 InProgressMap::const_iterator git = in_progress_.begin();
249 InProgressMap::const_iterator gend = in_progress_.end();
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;
258 return PreviewLoader::NotFound;
262 void PreviewLoader::Impl::add(string const & latex_snippet)
270 Cache::const_iterator cit = cache_.find(latex_snippet);
271 if (cit != cache_.end())
274 PendingMap::const_iterator pit = pending_.find(latex_snippet);
275 if (pit != pending_.end())
278 int const snippet_counter = int(pending_.size()) + 1;
281 << setfill('0') << setw(3) << snippet_counter
282 << "." << pconverter_->to;
283 string const image_filename = os.str().c_str();
285 pending_[latex_snippet] = image_filename;
289 void PreviewLoader::Impl::remove(string const & latex_snippet)
291 Cache::iterator cit = cache_.find(latex_snippet);
292 if (cit != cache_.end())
295 PendingMap::iterator pit = pending_.find(latex_snippet);
296 if (pit != pending_.end())
299 InProgressMap::iterator git = in_progress_.begin();
300 InProgressMap::iterator gend = in_progress_.end();
302 while (git != gend) {
303 InProgressMap::iterator curr = git;
306 PendingMap::iterator pit =
307 curr->second.snippets.find(latex_snippet);
308 if (pit != curr->second.snippets.end())
309 curr->second.snippets.erase(pit);
311 if (curr->second.snippets.empty())
312 in_progress_.erase(curr);
317 void PreviewLoader::Impl::startLoading()
319 if (pending_.empty())
328 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()" << endl;
330 // Output the LaTeX file.
331 string const latexfile = filename_base_ + ".tex";
333 ofstream of(latexfile.c_str());
335 of << "\n\\begin{document}\n";
337 of << "\n\\end{document}\n";
340 // The conversion command.
342 cs << pconverter_->command << " " << latexfile << " "
343 << tostr(0.01 * lyxrc.dpi * lyxrc.zoom);
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_);
351 // Reset the filename and clear the data, so we're ready to
354 filename_base_ = unique_filename();
356 // Initiate the conversion from LaTeX to bitmap images files.
357 Forkedcall::SignalTypePtr convert_ptr;
358 convert_ptr.reset(new Forkedcall::SignalType);
360 convert_ptr->connect(
361 boost::bind(&Impl::finishedGenerating, this, _1, _2, _3));
364 int ret = call.startscript(command, convert_ptr);
367 InProgressMap::iterator it = in_progress_.find(command);
368 if (it != in_progress_.end())
369 in_progress_.erase(it);
371 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
372 << "Unable to start process \n"
378 void PreviewLoader::Impl::finishedGenerating(string const & command,
379 pid_t /* pid */, int retval)
381 string const status = retval > 0 ? "failed" : "succeeded";
382 lyxerr[Debug::GRAPHICS] << "PreviewLoader::finishedInProgress("
383 << retval << "): processing " << status
384 << " for " << command << endl;
388 InProgressMap::iterator git = in_progress_.find(command);
389 if (git == in_progress_.end()) {
390 lyxerr << "PreviewLoader::finishedGenerating(): unable to find "
392 << command << "!" << endl;
396 // Read the metrics file, if it exists
397 PreviewMetrics metrics_file(git->second.metrics_file);
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();
404 int metrics_counter = 0;
405 for (; it != end; ++it) {
406 string const & snip = it->first;
409 Cache::const_iterator chk = cache_.find(snip);
410 if (chk != cache_.end())
413 // Mental note (Angus, 4 July 2002, having just found out the
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));
430 in_progress_.erase(git);
434 void PreviewLoader::Impl::dumpPreamble(ostream & os) const
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);
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();
445 for (; it != end; ++it) {
446 if ((*it)->lyxCode() == Inset::MATHMACRO_CODE) {
447 (*it)->latex(&buffer_, os, true, true);
451 // Use the preview style file to ensure that each snippet appears on a
454 << "\\usepackage[active,dvips,tightpage]{preview}\n"
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";
462 string bg = lyx_gui::hexname(LColor::background);
463 if (bg.empty()) bg = "ffffff";
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";
474 typedef std::pair<string, string> StrPair;
477 bool operator()(StrPair const & lhs, StrPair const & rhs)
479 return lhs.second < rhs.second;
486 void PreviewLoader::Impl::dumpData(ostream & os) const
488 if (pending_.empty())
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());
496 std::vector<StrPair>::const_iterator it = vec.begin();
497 std::vector<StrPair>::const_iterator end = vec.end();
499 for (; it != end; ++it) {
500 os << "\\begin{preview}\n"
502 << "\n\\end{preview}\n\n";