2 * \file PreviewLoader.C
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Angus Leeming
8 * Full author contact details are available in file CREDITS.
13 #include "PreviewLoader.h"
14 #include "PreviewImage.h"
15 #include "GraphicsCache.h"
18 #include "converter.h"
21 #include "insetiterator.h"
24 #include "outputparams.h"
25 #include "paragraph.h"
27 #include "frontends/lyx_gui.h" // hexname
29 #include "insets/inset.h"
31 #include "support/filetools.h"
32 #include "support/forkedcall.h"
33 #include "support/forkedcontr.h"
34 #include "support/lstrings.h"
35 #include "support/lyxlib.h"
36 #include "support/convert.h"
38 #include <boost/bind.hpp>
44 namespace support = lyx::support;
59 using std::ostringstream;
67 typedef pair<string, string> StrPair;
69 // A list of alll snippets to be converted to previews
70 typedef list<string> PendingSnippets;
72 // Each item in the vector is a pair<snippet, image file name>.
73 typedef vector<StrPair> BitmapFile;
75 string const unique_filename(string const bufferpath);
77 Converter const * setConverter();
79 void setAscentFractions(vector<double> & ascent_fractions,
80 string const & metrics_file);
82 class FindFirst : public std::unary_function<StrPair, bool> {
84 FindFirst(string const & comp) : comp_(comp) {}
85 bool operator()(StrPair const & sp) const
87 return sp.first == comp_;
94 /// Store info on a currently executing, forked process.
98 InProgress() : pid(0) {}
100 InProgress(string const & filename_base,
101 PendingSnippets const & pending,
102 string const & to_format);
103 /// Remove any files left lying around and kill the forked process.
116 typedef map<pid_t, InProgress> InProgressProcesses;
118 typedef InProgressProcesses::value_type InProgressProcess;
126 class PreviewLoader::Impl : public boost::signals::trackable {
129 Impl(PreviewLoader & p, Buffer const & b);
130 /// Stop any InProgress items still executing.
133 PreviewImage const * preview(string const & latex_snippet) const;
135 PreviewLoader::Status status(string const & latex_snippet) const;
137 void add(string const & latex_snippet);
139 void remove(string const & latex_snippet);
143 /// Emit this signal when an image is ready for display.
144 boost::signal<void(PreviewImage const &)> imageReady;
146 Buffer const & buffer() const { return buffer_; }
149 /// Called by the Forkedcall process that generated the bitmap files.
150 void finishedGenerating(pid_t, int);
152 void dumpPreamble(ostream &) const;
154 void dumpData(ostream &, BitmapFile const &) const;
156 /** cache_ allows easy retrieval of already-generated images
157 * using the LaTeX snippet as the identifier.
159 typedef boost::shared_ptr<PreviewImage> PreviewImagePtr;
161 typedef map<string, PreviewImagePtr> Cache;
165 /** pending_ stores the LaTeX snippets in anticipation of them being
166 * sent to the converter.
168 PendingSnippets pending_;
170 /** in_progress_ stores all forked processes so that we can proceed
172 The map uses the conversion commands as its identifiers.
174 InProgressProcesses in_progress_;
177 PreviewLoader & parent_;
179 Buffer const & buffer_;
181 double font_scaling_factor_;
183 /// We don't own this
184 static Converter const * pconverter_;
188 Converter const * PreviewLoader::Impl::pconverter_;
191 // The public interface, defined in PreviewLoader.h
192 // ================================================
193 PreviewLoader::PreviewLoader(Buffer const & b)
194 : pimpl_(new Impl(*this, b))
198 PreviewLoader::~PreviewLoader()
202 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
204 return pimpl_->preview(latex_snippet);
208 PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const
210 return pimpl_->status(latex_snippet);
214 void PreviewLoader::add(string const & latex_snippet) const
216 pimpl_->add(latex_snippet);
220 void PreviewLoader::remove(string const & latex_snippet) const
222 pimpl_->remove(latex_snippet);
226 void PreviewLoader::startLoading() const
228 pimpl_->startLoading();
232 boost::signals::connection PreviewLoader::connect(slot_type const & slot) const
234 return pimpl_->imageReady.connect(slot);
238 void PreviewLoader::emitSignal(PreviewImage const & pimage) const
240 pimpl_->imageReady(pimage);
244 Buffer const & PreviewLoader::buffer() const
246 return pimpl_->buffer();
249 } // namespace graphics
253 // The details of the Impl
254 // =======================
258 class IncrementedFileName {
260 IncrementedFileName(string const & to_format,
261 string const & filename_base)
262 : to_format_(to_format), base_(filename_base), counter_(1)
265 StrPair const operator()(string const & snippet)
268 os << base_ << counter_++ << '.' << to_format_;
269 string const file = os.str();
271 return make_pair(snippet, file);
275 string const & to_format_;
276 string const & base_;
281 InProgress::InProgress(string const & filename_base,
282 PendingSnippets const & pending,
283 string const & to_format)
285 metrics_file(filename_base + ".metrics"),
286 snippets(pending.size())
288 PendingSnippets::const_iterator pit = pending.begin();
289 PendingSnippets::const_iterator pend = pending.end();
290 BitmapFile::iterator sit = snippets.begin();
292 std::transform(pit, pend, sit,
293 IncrementedFileName(to_format, filename_base));
297 void InProgress::stop() const
300 support::ForkedcallsController::get().kill(pid, 0);
302 if (!metrics_file.empty())
303 support::unlink(metrics_file);
305 BitmapFile::const_iterator vit = snippets.begin();
306 BitmapFile::const_iterator vend = snippets.end();
307 for (; vit != vend; ++vit) {
308 if (!vit->second.empty())
309 support::unlink(vit->second);
319 PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b)
320 : parent_(p), buffer_(b), font_scaling_factor_(0.0)
322 font_scaling_factor_ = 0.01 * lyxrc.dpi * lyxrc.zoom *
323 convert<double>(lyxrc.preview_scale_factor);
325 lyxerr[Debug::GRAPHICS] << "The font scaling factor is "
326 << font_scaling_factor_ << endl;
329 pconverter_ = setConverter();
333 PreviewLoader::Impl::~Impl()
335 InProgressProcesses::iterator ipit = in_progress_.begin();
336 InProgressProcesses::iterator ipend = in_progress_.end();
338 for (; ipit != ipend; ++ipit) {
345 PreviewLoader::Impl::preview(string const & latex_snippet) const
347 Cache::const_iterator it = cache_.find(latex_snippet);
348 return (it == cache_.end()) ? 0 : it->second.get();
354 class FindSnippet : public std::unary_function<InProgressProcess, bool> {
356 FindSnippet(string const & s) : snippet_(s) {}
357 bool operator()(InProgressProcess const & process) const
359 BitmapFile const & snippets = process.second.snippets;
360 BitmapFile::const_iterator beg = snippets.begin();
361 BitmapFile::const_iterator end = snippets.end();
362 return find_if(beg, end, FindFirst(snippet_)) != end;
366 string const snippet_;
371 PreviewLoader::Status
372 PreviewLoader::Impl::status(string const & latex_snippet) const
374 Cache::const_iterator cit = cache_.find(latex_snippet);
375 if (cit != cache_.end())
378 PendingSnippets::const_iterator pit = pending_.begin();
379 PendingSnippets::const_iterator pend = pending_.end();
381 pit = find(pit, pend, latex_snippet);
385 InProgressProcesses::const_iterator ipit = in_progress_.begin();
386 InProgressProcesses::const_iterator ipend = in_progress_.end();
388 ipit = find_if(ipit, ipend, FindSnippet(latex_snippet));
396 void PreviewLoader::Impl::add(string const & latex_snippet)
398 if (!pconverter_ || status(latex_snippet) != NotFound)
401 string const snippet = support::trim(latex_snippet);
405 lyxerr[Debug::GRAPHICS] << "adding snippet:\n" << snippet << endl;
407 pending_.push_back(snippet);
415 EraseSnippet(string const & s) : snippet_(s) {}
416 void operator()(InProgressProcess & process)
418 BitmapFile & snippets = process.second.snippets;
419 BitmapFile::iterator it = snippets.begin();
420 BitmapFile::iterator end = snippets.end();
422 it = find_if(it, end, FindFirst(snippet_));
424 snippets.erase(it, it+1);
428 string const & snippet_;
434 void PreviewLoader::Impl::remove(string const & latex_snippet)
436 Cache::iterator cit = cache_.find(latex_snippet);
437 if (cit != cache_.end())
440 PendingSnippets::iterator pit = pending_.begin();
441 PendingSnippets::iterator pend = pending_.end();
443 pending_.erase(std::remove(pit, pend, latex_snippet), pend);
445 InProgressProcesses::iterator ipit = in_progress_.begin();
446 InProgressProcesses::iterator ipend = in_progress_.end();
448 std::for_each(ipit, ipend, EraseSnippet(latex_snippet));
450 while (ipit != ipend) {
451 InProgressProcesses::iterator curr = ipit++;
452 if (curr->second.snippets.empty())
453 in_progress_.erase(curr);
458 void PreviewLoader::Impl::startLoading()
460 if (pending_.empty() || !pconverter_)
463 // Only start the process off after the buffer is loaded from file.
464 if (!buffer_.fully_loaded())
467 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()" << endl;
469 // As used by the LaTeX file and by the resulting image files
470 string const directory = buffer_.temppath();
472 string const filename_base(unique_filename(directory));
474 // Create an InProgress instance to place in the map of all
475 // such processes if it starts correctly.
476 InProgress inprogress(filename_base, pending_, pconverter_->to);
478 // clear pending_, so we're ready to start afresh.
481 // Output the LaTeX file.
482 string const latexfile = filename_base + ".tex";
484 ofstream of(latexfile.c_str());
486 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
487 << "Unable to create LaTeX file\n"
488 << latexfile << endl;
491 of << "\\batchmode\n";
493 of << "\n\\begin{document}\n";
494 dumpData(of, inprogress.snippets);
495 of << "\n\\end{document}\n";
498 // The conversion command.
500 cs << pconverter_->command << ' ' << pconverter_->to << ' '
501 << support::quoteName(latexfile) << ' '
502 << int(font_scaling_factor_) << ' '
503 << lyx_gui::hexname(LColor::preview) << ' '
504 << lyx_gui::hexname(LColor::background);
506 string const command = support::libScriptSearch(cs.str());
508 // Initiate the conversion from LaTeX to bitmap images files.
509 support::Forkedcall::SignalTypePtr
510 convert_ptr(new support::Forkedcall::SignalType);
511 convert_ptr->connect(bind(&Impl::finishedGenerating, this, _1, _2));
513 support::Forkedcall call;
514 int ret = call.startscript(command, convert_ptr);
517 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
518 << "Unable to start process\n"
523 // Store the generation process in a list of all such processes
524 inprogress.pid = call.pid();
525 inprogress.command = command;
526 in_progress_[inprogress.pid] = inprogress;
530 void PreviewLoader::Impl::finishedGenerating(pid_t pid, int retval)
533 InProgressProcesses::iterator git = in_progress_.find(pid);
534 if (git == in_progress_.end()) {
535 lyxerr << "PreviewLoader::finishedGenerating(): unable to find "
536 "data for PID " << pid << endl;
540 string const command = git->second.command;
541 string const status = retval > 0 ? "failed" : "succeeded";
542 lyxerr[Debug::GRAPHICS] << "PreviewLoader::finishedInProgress("
543 << retval << "): processing " << status
544 << " for " << command << endl;
548 // Read the metrics file, if it exists
549 vector<double> ascent_fractions(git->second.snippets.size());
550 setAscentFractions(ascent_fractions, git->second.metrics_file);
552 // Add these newly generated bitmap files to the cache and
553 // start loading them into LyX.
554 BitmapFile::const_iterator it = git->second.snippets.begin();
555 BitmapFile::const_iterator end = git->second.snippets.end();
557 std::list<PreviewImagePtr> newimages;
559 int metrics_counter = 0;
560 for (; it != end; ++it, ++metrics_counter) {
561 string const & snip = it->first;
562 string const & file = it->second;
563 double af = ascent_fractions[metrics_counter];
565 PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af));
568 newimages.push_back(ptr);
571 // Remove the item from the list of still-executing processes.
572 in_progress_.erase(git);
574 // Tell the outside world
575 std::list<PreviewImagePtr>::const_reverse_iterator
576 nit = newimages.rbegin();
577 std::list<PreviewImagePtr>::const_reverse_iterator
578 nend = newimages.rend();
579 for (; nit != nend; ++nit) {
580 imageReady(*nit->get());
585 void PreviewLoader::Impl::dumpPreamble(ostream & os) const
587 // Why on earth is Buffer::makeLaTeXFile a non-const method?
588 Buffer & tmp = const_cast<Buffer &>(buffer_);
589 // Dump the preamble only.
590 OutputParams runparams;
591 runparams.flavor = OutputParams::LATEX;
592 runparams.nice = true;
593 runparams.moving_arg = true;
594 runparams.free_spacing = true;
595 tmp.makeLaTeXFile(os, buffer_.filePath(), runparams, true, false);
597 // FIXME! This is a HACK! The proper fix is to control the 'true'
598 // passed to WriteStream below:
599 // int InsetFormula::latex(Buffer const &, ostream & os,
600 // OutputParams const & runparams) const
602 // WriteStream wi(os, runparams.moving_arg, true);
607 << "\\def\\lyxlock{}\n"
610 // Loop over the insets in the buffer and dump all the math-macros.
611 InsetBase & inset = buffer_.inset();
612 InsetIterator it = inset_iterator_begin(inset);
613 InsetIterator const end = inset_iterator_end(inset);
615 for (; it != end; ++it)
616 if (it->lyxCode() == InsetBase::MATHMACRO_CODE)
617 it->latex(buffer_, os, runparams);
619 // All equation lables appear as "(#)" + preview.sty's rendering of
621 if (lyxrc.preview_hashed_labels)
622 os << "\\renewcommand{\\theequation}{\\#}\n";
624 // Use the preview style file to ensure that each snippet appears on a
627 << "\\usepackage[active,delayed,dvips,showlabels,lyx]{preview}\n"
632 void PreviewLoader::Impl::dumpData(ostream & os,
633 BitmapFile const & vec) const
638 BitmapFile::const_iterator it = vec.begin();
639 BitmapFile::const_iterator end = vec.end();
641 for (; it != end; ++it) {
642 os << "\\begin{preview}\n"
644 << "\n\\end{preview}\n\n";
648 } // namespace graphics
653 string const unique_filename(string const bufferpath)
655 static int theCounter = 0;
656 string const filename = convert<string>(theCounter++) + "lyxpreview";
657 return support::addName(bufferpath, filename);
661 Converter const * setConverter()
663 string const from = "lyxpreview";
665 typedef vector<string> FmtList;
666 typedef lyx::graphics::Cache GCache;
667 FmtList const loadableFormats = GCache::get().loadableFormats();
668 FmtList::const_iterator it = loadableFormats.begin();
669 FmtList::const_iterator const end = loadableFormats.end();
671 for (; it != end; ++it) {
672 string const to = *it;
676 Converter const * ptr = converters.getConverter(from, to);
681 static bool first = true;
684 lyxerr << "PreviewLoader::startLoading()\n"
685 << "No converter from \"lyxpreview\" format has been "
694 void setAscentFractions(vector<double> & ascent_fractions,
695 string const & metrics_file)
697 // If all else fails, then the images will have equal ascents and
699 vector<double>::iterator it = ascent_fractions.begin();
700 vector<double>::iterator end = ascent_fractions.end();
703 ifstream in(metrics_file.c_str());
705 lyxerr[Debug::GRAPHICS]
706 << "setAscentFractions(" << metrics_file << ")\n"
707 << "Unable to open file!" << endl;
713 int snippet_counter = 1;
714 while (!in.eof() && it != end) {
717 double ascent_fraction;
719 in >> snippet >> id >> ascent_fraction;
725 error = snippet != "Snippet";
729 error = id != snippet_counter;
733 *it = ascent_fraction;
740 lyxerr[Debug::GRAPHICS]
741 << "setAscentFractions(" << metrics_file << ")\n"
742 << "Error reading file!\n" << endl;