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>
56 typedef pair<string, string> StrPair;
59 bool operator()(StrPair const & lhs, StrPair const & rhs)
61 return lhs.second < rhs.second;
66 FindFirst(string const & comp) : comp_(comp) {}
67 bool operator()(StrPair const & sp)
69 return sp.first < comp_;
76 string const unique_filename()
81 string const tmp = lyx::tempName();
86 static int theCounter = 0;
88 os << dir << theCounter++ << "lyxpreview";
90 return os.str().c_str();
98 struct PreviewLoader::Impl : public boost::signals::trackable {
100 Impl(PreviewLoader & p, Buffer const & b);
102 PreviewImage const * preview(string const & latex_snippet) const;
104 PreviewLoader::Status status(string const & latex_snippet) const;
106 void add(string const & latex_snippet);
108 void remove(string const & latex_snippet);
113 typedef pair<string, string> StrPair;
115 typedef map<string, string> PendingMap;
118 /// Called by the Forkedcall process that generated the bitmap files.
119 void finishedGenerating(string const &, pid_t, int);
121 void dumpPreamble(ostream &) const;
123 void dumpData(ostream &, vector<StrPair> const &) const;
126 static void setConverter();
127 /// We don't own this
128 static Converter const * pconverter_;
130 /** cache_ allows easy retrieval of already-generated images
131 * using the LaTeX snippet as the identifier.
133 typedef boost::shared_ptr<PreviewImage> PreviewImagePtr;
135 typedef map<string, PreviewImagePtr> Cache;
139 /** pending_ stores the LaTeX snippet and the name of the generated
140 * bitmap image file in anticipation of them being sent to the
145 /// Store info on a currently executing, forked process.
150 InProgress(string const & f, PendingMap const & m)
151 : metrics_file(f), snippets(m.begin(), m.end())
153 sort(snippets.begin(), snippets.end(), CompSecond());
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.
164 vector<StrPair> snippets;
167 /// Store all forked processes so that we can proceed thereafter.
168 typedef map<string, InProgress> InProgressMap;
170 InProgressMap in_progress_;
173 string filename_base_;
175 PreviewLoader & parent_;
177 Buffer const & buffer_;
180 Converter const * PreviewLoader::Impl::pconverter_;
183 PreviewLoader::PreviewLoader(Buffer const & b)
184 : pimpl_(new Impl(*this, b))
188 PreviewLoader::~PreviewLoader()
192 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
194 return pimpl_->preview(latex_snippet);
198 PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const
200 return pimpl_->status(latex_snippet);
204 void PreviewLoader::add(string const & latex_snippet)
206 pimpl_->add(latex_snippet);
210 void PreviewLoader::remove(string const & latex_snippet)
212 pimpl_->remove(latex_snippet);
216 void PreviewLoader::startLoading()
218 pimpl_->startLoading();
222 void PreviewLoader::Impl::setConverter()
227 string const from = "lyxpreview";
229 Formats::FormatList::const_iterator it = formats.begin();
230 Formats::FormatList::const_iterator end = formats.end();
232 for (; it != end; ++it) {
233 string const to = it->name();
236 Converter const * ptr = converters.getConverter(from, to);
246 static bool first = true;
251 lyxerr << "PreviewLoader::startLoading()\n"
252 << "No converter from \"lyxpreview\" format has been defined."
257 PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b)
258 : filename_base_(unique_filename()), parent_(p), buffer_(b)
263 PreviewLoader::Impl::preview(string const & latex_snippet) const
265 Cache::const_iterator it = cache_.find(latex_snippet);
266 return (it == cache_.end()) ? 0 : it->second.get();
270 PreviewLoader::Status
271 PreviewLoader::Impl::status(string const & latex_snippet) const
273 Cache::const_iterator cit = cache_.find(latex_snippet);
274 if (cit != cache_.end())
275 return PreviewLoader::Ready;
277 PendingMap::const_iterator pit = pending_.find(latex_snippet);
278 if (pit != pending_.end())
279 return PreviewLoader::InQueue;
281 InProgressMap::const_iterator ipit = in_progress_.begin();
282 InProgressMap::const_iterator ipend = in_progress_.end();
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));
291 return PreviewLoader::Processing;
294 return PreviewLoader::NotFound;
298 void PreviewLoader::Impl::add(string const & latex_snippet)
306 Cache::const_iterator cit = cache_.find(latex_snippet);
307 if (cit != cache_.end())
310 PendingMap::const_iterator pit = pending_.find(latex_snippet);
311 if (pit != pending_.end())
314 int const snippet_counter = int(pending_.size()) + 1;
317 << setfill('0') << setw(3) << snippet_counter
318 << "." << pconverter_->to;
319 string const image_filename = os.str().c_str();
321 pending_[latex_snippet] = image_filename;
325 void PreviewLoader::Impl::remove(string const & latex_snippet)
327 Cache::iterator cit = cache_.find(latex_snippet);
328 if (cit != cache_.end())
331 PendingMap::iterator pit = pending_.find(latex_snippet);
332 if (pit != pending_.end())
335 InProgressMap::iterator ipit = in_progress_.begin();
336 InProgressMap::iterator ipend = in_progress_.end();
338 while (ipit != ipend) {
339 InProgressMap::iterator curr = ipit;
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));
348 snippets.erase(vit, vit+1);
350 if (snippets.empty())
351 in_progress_.erase(curr);
356 void PreviewLoader::Impl::startLoading()
358 if (pending_.empty())
367 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()" << endl;
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_);
374 // Output the LaTeX file.
375 string const latexfile = filename_base_ + ".tex";
377 ofstream of(latexfile.c_str());
379 of << "\n\\begin{document}\n";
380 dumpData(of, inprogress.snippets);
381 of << "\n\\end{document}\n";
384 // Reset the filename and clear pending_, so we're ready to
387 filename_base_ = unique_filename();
389 // The conversion command.
391 cs << pconverter_->command << " " << latexfile << " "
392 << tostr(0.01 * lyxrc.dpi * lyxrc.zoom);
394 string const command = cs.str().c_str();
396 // Initiate the conversion from LaTeX to bitmap images files.
397 Forkedcall::SignalTypePtr convert_ptr;
398 convert_ptr.reset(new Forkedcall::SignalType);
400 convert_ptr->connect(
401 boost::bind(&Impl::finishedGenerating, this, _1, _2, _3));
404 int ret = call.startscript(command, convert_ptr);
407 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
408 << "Unable to start process \n"
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;
419 void PreviewLoader::Impl::finishedGenerating(string const & command,
420 pid_t /* pid */, int retval)
422 string const status = retval > 0 ? "failed" : "succeeded";
423 lyxerr[Debug::GRAPHICS] << "PreviewLoader::finishedInProgress("
424 << retval << "): processing " << status
425 << " for " << command << endl;
429 InProgressMap::iterator git = in_progress_.find(command);
430 if (git == in_progress_.end()) {
431 lyxerr << "PreviewLoader::finishedGenerating(): unable to find "
433 << command << "!" << endl;
437 // Read the metrics file, if it exists
438 PreviewMetrics metrics_file(git->second.metrics_file);
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();
445 int metrics_counter = 0;
446 for (; it != end; ++it) {
447 string const & snip = it->first;
450 Cache::const_iterator chk = cache_.find(snip);
451 if (chk != cache_.end())
454 // Mental note (Angus, 4 July 2002, having just found out the
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));
471 in_progress_.erase(git);
475 void PreviewLoader::Impl::dumpPreamble(ostream & os) const
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);
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();
486 for (; it != end; ++it) {
487 if ((*it)->lyxCode() == Inset::MATHMACRO_CODE) {
488 (*it)->latex(&buffer_, os, true, true);
492 // Use the preview style file to ensure that each snippet appears on a
495 << "\\usepackage[active,dvips,tightpage]{preview}\n"
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";
503 string bg = lyx_gui::hexname(LColor::background);
504 if (bg.empty()) bg = "ffffff";
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";
513 void PreviewLoader::Impl::dumpData(ostream & os,
514 vector<StrPair> const & vec) const
519 vector<StrPair>::const_iterator it = vec.begin();
520 vector<StrPair>::const_iterator end = vec.end();
522 for (; it != end; ++it) {
523 os << "\\begin{preview}\n"
525 << "\n\\end{preview}\n\n";