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/tostr.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.
97 InProgress() : pid(0) {}
99 InProgress(string const & filename_base,
100 PendingSnippets const & pending,
101 string const & to_format);
102 /// Remove any files left lying around and kill the forked process.
115 typedef map<pid_t, InProgress> InProgressProcesses;
117 typedef InProgressProcesses::value_type InProgressProcess;
125 struct PreviewLoader::Impl : public boost::signals::trackable {
127 Impl(PreviewLoader & p, Buffer const & b);
128 /// Stop any InProgress items still executing.
131 PreviewImage const * preview(string const & latex_snippet) const;
133 PreviewLoader::Status status(string const & latex_snippet) const;
135 void add(string const & latex_snippet);
137 void remove(string const & latex_snippet);
141 /// Emit this signal when an image is ready for display.
142 boost::signal<void(PreviewImage const &)> imageReady;
144 Buffer const & buffer() const { return buffer_; }
147 /// Called by the Forkedcall process that generated the bitmap files.
148 void finishedGenerating(pid_t, int);
150 void dumpPreamble(ostream &) const;
152 void dumpData(ostream &, BitmapFile const &) const;
154 /** cache_ allows easy retrieval of already-generated images
155 * using the LaTeX snippet as the identifier.
157 typedef boost::shared_ptr<PreviewImage> PreviewImagePtr;
159 typedef map<string, PreviewImagePtr> Cache;
163 /** pending_ stores the LaTeX snippets in anticipation of them being
164 * sent to the converter.
166 PendingSnippets pending_;
168 /** in_progress_ stores all forked processes so that we can proceed
170 The map uses the conversion commands as its identifiers.
172 InProgressProcesses in_progress_;
175 PreviewLoader & parent_;
177 Buffer const & buffer_;
179 double font_scaling_factor_;
181 /// We don't own this
182 static Converter const * pconverter_;
186 Converter const * PreviewLoader::Impl::pconverter_;
189 // The public interface, defined in PreviewLoader.h
190 // ================================================
191 PreviewLoader::PreviewLoader(Buffer const & b)
192 : pimpl_(new Impl(*this, b))
196 PreviewLoader::~PreviewLoader()
200 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
202 return pimpl_->preview(latex_snippet);
206 PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const
208 return pimpl_->status(latex_snippet);
212 void PreviewLoader::add(string const & latex_snippet) const
214 pimpl_->add(latex_snippet);
218 void PreviewLoader::remove(string const & latex_snippet) const
220 pimpl_->remove(latex_snippet);
224 void PreviewLoader::startLoading() const
226 pimpl_->startLoading();
230 boost::signals::connection PreviewLoader::connect(slot_type const & slot) const
232 return pimpl_->imageReady.connect(slot);
236 void PreviewLoader::emitSignal(PreviewImage const & pimage) const
238 pimpl_->imageReady(pimage);
242 Buffer const & PreviewLoader::buffer() const
244 return pimpl_->buffer();
247 } // namespace graphics
251 // The details of the Impl
252 // =======================
256 struct IncrementedFileName {
257 IncrementedFileName(string const & to_format,
258 string const & filename_base)
259 : to_format_(to_format), base_(filename_base), counter_(1)
262 StrPair const operator()(string const & snippet)
265 os << base_ << counter_++ << '.' << to_format_;
266 string const file = os.str();
268 return make_pair(snippet, file);
272 string const & to_format_;
273 string const & base_;
278 InProgress::InProgress(string const & filename_base,
279 PendingSnippets const & pending,
280 string const & to_format)
282 metrics_file(filename_base + ".metrics"),
283 snippets(pending.size())
285 PendingSnippets::const_iterator pit = pending.begin();
286 PendingSnippets::const_iterator pend = pending.end();
287 BitmapFile::iterator sit = snippets.begin();
289 std::transform(pit, pend, sit,
290 IncrementedFileName(to_format, filename_base));
294 void InProgress::stop() const
297 support::ForkedcallsController::get().kill(pid, 0);
299 if (!metrics_file.empty())
300 support::unlink(metrics_file);
302 BitmapFile::const_iterator vit = snippets.begin();
303 BitmapFile::const_iterator vend = snippets.end();
304 for (; vit != vend; ++vit) {
305 if (!vit->second.empty())
306 support::unlink(vit->second);
316 PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b)
317 : parent_(p), buffer_(b), font_scaling_factor_(0.0)
319 font_scaling_factor_ = 0.01 * lyxrc.dpi * lyxrc.zoom *
320 lyxrc.preview_scale_factor;
322 lyxerr[Debug::GRAPHICS] << "The font scaling factor is "
323 << font_scaling_factor_ << endl;
326 pconverter_ = setConverter();
330 PreviewLoader::Impl::~Impl()
332 InProgressProcesses::iterator ipit = in_progress_.begin();
333 InProgressProcesses::iterator ipend = in_progress_.end();
335 for (; ipit != ipend; ++ipit) {
342 PreviewLoader::Impl::preview(string const & latex_snippet) const
344 Cache::const_iterator it = cache_.find(latex_snippet);
345 return (it == cache_.end()) ? 0 : it->second.get();
351 class FindSnippet : public std::unary_function<InProgressProcess, bool> {
353 FindSnippet(string const & s) : snippet_(s) {}
354 bool operator()(InProgressProcess const & process) const
356 BitmapFile const & snippets = process.second.snippets;
357 BitmapFile::const_iterator beg = snippets.begin();
358 BitmapFile::const_iterator end = snippets.end();
359 return find_if(beg, end, FindFirst(snippet_)) != end;
363 string const snippet_;
368 PreviewLoader::Status
369 PreviewLoader::Impl::status(string const & latex_snippet) const
371 Cache::const_iterator cit = cache_.find(latex_snippet);
372 if (cit != cache_.end())
375 PendingSnippets::const_iterator pit = pending_.begin();
376 PendingSnippets::const_iterator pend = pending_.end();
378 pit = find(pit, pend, latex_snippet);
382 InProgressProcesses::const_iterator ipit = in_progress_.begin();
383 InProgressProcesses::const_iterator ipend = in_progress_.end();
385 ipit = find_if(ipit, ipend, FindSnippet(latex_snippet));
393 void PreviewLoader::Impl::add(string const & latex_snippet)
395 if (!pconverter_ || status(latex_snippet) != NotFound)
398 string const snippet = support::trim(latex_snippet);
402 lyxerr[Debug::GRAPHICS] << "adding snippet:\n" << snippet << endl;
404 pending_.push_back(snippet);
410 struct EraseSnippet {
411 EraseSnippet(string const & s) : snippet_(s) {}
412 void operator()(InProgressProcess & process)
414 BitmapFile & snippets = process.second.snippets;
415 BitmapFile::iterator it = snippets.begin();
416 BitmapFile::iterator end = snippets.end();
418 it = find_if(it, end, FindFirst(snippet_));
420 snippets.erase(it, it+1);
424 string const & snippet_;
430 void PreviewLoader::Impl::remove(string const & latex_snippet)
432 Cache::iterator cit = cache_.find(latex_snippet);
433 if (cit != cache_.end())
436 PendingSnippets::iterator pit = pending_.begin();
437 PendingSnippets::iterator pend = pending_.end();
439 pending_.erase(std::remove(pit, pend, latex_snippet), pend);
441 InProgressProcesses::iterator ipit = in_progress_.begin();
442 InProgressProcesses::iterator ipend = in_progress_.end();
444 std::for_each(ipit, ipend, EraseSnippet(latex_snippet));
446 while (ipit != ipend) {
447 InProgressProcesses::iterator curr = ipit++;
448 if (curr->second.snippets.empty())
449 in_progress_.erase(curr);
454 void PreviewLoader::Impl::startLoading()
456 if (pending_.empty() || !pconverter_)
459 // Only start the process off after the buffer is loaded from file.
460 if (!buffer_.fully_loaded())
463 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()" << endl;
465 // As used by the LaTeX file and by the resulting image files
466 string const directory = buffer_.temppath();
468 string const filename_base(unique_filename(directory));
470 // Create an InProgress instance to place in the map of all
471 // such processes if it starts correctly.
472 InProgress inprogress(filename_base, pending_, pconverter_->to);
474 // clear pending_, so we're ready to start afresh.
477 // Output the LaTeX file.
478 string const latexfile = filename_base + ".tex";
480 ofstream of(latexfile.c_str());
482 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
483 << "Unable to create LaTeX file\n"
484 << latexfile << endl;
487 of << "\\batchmode\n";
489 of << "\n\\begin{document}\n";
490 dumpData(of, inprogress.snippets);
491 of << "\n\\end{document}\n";
494 // The conversion command.
496 cs << pconverter_->command << ' ' << pconverter_->to << ' '
497 << latexfile << ' ' << int(font_scaling_factor_) << ' '
498 << lyx_gui::hexname(LColor::preview) << ' '
499 << lyx_gui::hexname(LColor::background);
501 string const command = support::LibScriptSearch(cs.str());
503 // Initiate the conversion from LaTeX to bitmap images files.
504 support::Forkedcall::SignalTypePtr
505 convert_ptr(new support::Forkedcall::SignalType);
506 convert_ptr->connect(bind(&Impl::finishedGenerating, this, _1, _2));
508 support::Forkedcall call;
509 int ret = call.startscript(command, convert_ptr);
512 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
513 << "Unable to start process\n"
518 // Store the generation process in a list of all such processes
519 inprogress.pid = call.pid();
520 inprogress.command = command;
521 in_progress_[inprogress.pid] = inprogress;
525 void PreviewLoader::Impl::finishedGenerating(pid_t pid, int retval)
528 InProgressProcesses::iterator git = in_progress_.find(pid);
529 if (git == in_progress_.end()) {
530 lyxerr << "PreviewLoader::finishedGenerating(): unable to find "
531 "data for PID " << pid << endl;
535 string const command = git->second.command;
536 string const status = retval > 0 ? "failed" : "succeeded";
537 lyxerr[Debug::GRAPHICS] << "PreviewLoader::finishedInProgress("
538 << retval << "): processing " << status
539 << " for " << command << endl;
543 // Read the metrics file, if it exists
544 vector<double> ascent_fractions(git->second.snippets.size());
545 setAscentFractions(ascent_fractions, git->second.metrics_file);
547 // Add these newly generated bitmap files to the cache and
548 // start loading them into LyX.
549 BitmapFile::const_iterator it = git->second.snippets.begin();
550 BitmapFile::const_iterator end = git->second.snippets.end();
552 std::list<PreviewImagePtr> newimages;
554 int metrics_counter = 0;
555 for (; it != end; ++it, ++metrics_counter) {
556 string const & snip = it->first;
557 string const & file = it->second;
558 double af = ascent_fractions[metrics_counter];
560 PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af));
563 newimages.push_back(ptr);
566 // Remove the item from the list of still-executing processes.
567 in_progress_.erase(git);
569 // Tell the outside world
570 std::list<PreviewImagePtr>::const_reverse_iterator
571 nit = newimages.rbegin();
572 std::list<PreviewImagePtr>::const_reverse_iterator
573 nend = newimages.rend();
574 for (; nit != nend; ++nit) {
575 imageReady(*nit->get());
580 void PreviewLoader::Impl::dumpPreamble(ostream & os) const
582 // Why on earth is Buffer::makeLaTeXFile a non-const method?
583 Buffer & tmp = const_cast<Buffer &>(buffer_);
584 // Dump the preamble only.
585 OutputParams runparams;
586 runparams.flavor = OutputParams::LATEX;
587 runparams.nice = true;
588 runparams.moving_arg = true;
589 runparams.free_spacing = true;
590 tmp.makeLaTeXFile(os, buffer_.filePath(), runparams, true, false);
592 // FIXME! This is a HACK! The proper fix is to control the 'true'
593 // passed to WriteStream below:
594 // int InsetFormula::latex(Buffer const &, ostream & os,
595 // OutputParams const & runparams) const
597 // WriteStream wi(os, runparams.moving_arg, true);
602 << "\\def\\lyxlock{}\n"
605 // Loop over the insets in the buffer and dump all the math-macros.
606 InsetBase & inset = buffer_.inset();
607 InsetIterator it = inset_iterator_begin(inset);
608 InsetIterator const end = inset_iterator_end(inset);
610 for (; it != end; ++it)
611 if (it->lyxCode() == InsetOld::MATHMACRO_CODE)
612 it->latex(buffer_, os, runparams);
614 // All equation lables appear as "(#)" + preview.sty's rendering of
616 if (lyxrc.preview_hashed_labels)
617 os << "\\renewcommand{\\theequation}{\\#}\n";
619 // Use the preview style file to ensure that each snippet appears on a
622 << "\\usepackage[active,delayed,dvips,showlabels,lyx]{preview}\n"
627 void PreviewLoader::Impl::dumpData(ostream & os,
628 BitmapFile const & vec) const
633 BitmapFile::const_iterator it = vec.begin();
634 BitmapFile::const_iterator end = vec.end();
636 for (; it != end; ++it) {
637 os << "\\begin{preview}\n"
639 << "\n\\end{preview}\n\n";
643 } // namespace graphics
648 string const unique_filename(string const bufferpath)
650 static int theCounter = 0;
651 string const filename = tostr(theCounter++) + "lyxpreview";
652 return support::AddName(bufferpath, filename);
656 Converter const * setConverter()
658 string const from = "lyxpreview";
660 typedef vector<string> FmtList;
661 typedef lyx::graphics::Cache GCache;
662 FmtList const loadableFormats = GCache::get().loadableFormats();
663 FmtList::const_iterator it = loadableFormats.begin();
664 FmtList::const_iterator const end = loadableFormats.end();
666 for (; it != end; ++it) {
667 string const to = *it;
671 Converter const * ptr = converters.getConverter(from, to);
676 static bool first = true;
679 lyxerr << "PreviewLoader::startLoading()\n"
680 << "No converter from \"lyxpreview\" format has been "
689 void setAscentFractions(vector<double> & ascent_fractions,
690 string const & metrics_file)
692 // If all else fails, then the images will have equal ascents and
694 vector<double>::iterator it = ascent_fractions.begin();
695 vector<double>::iterator end = ascent_fractions.end();
698 ifstream in(metrics_file.c_str());
700 lyxerr[Debug::GRAPHICS]
701 << "setAscentFractions(" << metrics_file << ")\n"
702 << "Unable to open file!" << endl;
708 int snippet_counter = 1;
709 while (!in.eof() && it != end) {
712 double ascent_fraction;
714 in >> snippet >> id >> ascent_fraction;
720 error = snippet != "Snippet";
724 error = id != snippet_counter;
728 *it = ascent_fraction;
735 lyxerr[Debug::GRAPHICS]
736 << "setAscentFractions(" << metrics_file << ")\n"
737 << "Error reading file!\n" << endl;