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/Application.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>
55 using std::ostringstream;
63 typedef pair<string, string> StrPair;
65 // A list of alll snippets to be converted to previews
66 typedef list<string> PendingSnippets;
68 // Each item in the vector is a pair<snippet, image file name>.
69 typedef vector<StrPair> BitmapFile;
72 string const unique_filename(string const & bufferpath)
74 static int theCounter = 0;
75 string const filename = lyx::convert<string>(theCounter++) + "lyxpreview";
76 return lyx::support::addName(bufferpath, filename);
80 lyx::Converter const * setConverter()
82 string const from = "lyxpreview";
84 typedef vector<string> FmtList;
85 typedef lyx::graphics::Cache GCache;
86 FmtList const loadableFormats = GCache::get().loadableFormats();
87 FmtList::const_iterator it = loadableFormats.begin();
88 FmtList::const_iterator const end = loadableFormats.end();
90 for (; it != end; ++it) {
91 string const to = *it;
95 lyx::Converter const * ptr = lyx::converters.getConverter(from, to);
100 static bool first = true;
103 lyx::lyxerr << "PreviewLoader::startLoading()\n"
104 << "No converter from \"lyxpreview\" format has been "
112 void setAscentFractions(vector<double> & ascent_fractions,
113 string const & metrics_file)
115 // If all else fails, then the images will have equal ascents and
117 vector<double>::iterator it = ascent_fractions.begin();
118 vector<double>::iterator end = ascent_fractions.end();
121 ifstream in(metrics_file.c_str());
123 lyx::lyxerr[lyx::Debug::GRAPHICS]
124 << "setAscentFractions(" << metrics_file << ")\n"
125 << "Unable to open file!" << endl;
131 int snippet_counter = 1;
132 while (!in.eof() && it != end) {
135 double ascent_fraction;
137 in >> snippet >> id >> ascent_fraction;
143 error = snippet != "Snippet";
147 error = id != snippet_counter;
151 *it = ascent_fraction;
158 lyx::lyxerr[lyx::Debug::GRAPHICS]
159 << "setAscentFractions(" << metrics_file << ")\n"
160 << "Error reading file!\n" << endl;
165 class FindFirst : public std::unary_function<StrPair, bool> {
167 FindFirst(string const & comp) : comp_(comp) {}
168 bool operator()(StrPair const & sp) const
170 return sp.first == comp_;
177 /// Store info on a currently executing, forked process.
181 InProgress() : pid(0) {}
183 InProgress(string const & filename_base,
184 PendingSnippets const & pending,
185 string const & to_format);
186 /// Remove any files left lying around and kill the forked process.
199 typedef map<pid_t, InProgress> InProgressProcesses;
201 typedef InProgressProcesses::value_type InProgressProcess;
211 class PreviewLoader::Impl : public boost::signals::trackable {
214 Impl(PreviewLoader & p, Buffer const & b);
215 /// Stop any InProgress items still executing.
218 PreviewImage const * preview(string const & latex_snippet) const;
220 PreviewLoader::Status status(string const & latex_snippet) const;
222 void add(string const & latex_snippet);
224 void remove(string const & latex_snippet);
228 /// Emit this signal when an image is ready for display.
229 boost::signal<void(PreviewImage const &)> imageReady;
231 Buffer const & buffer() const { return buffer_; }
234 /// Called by the Forkedcall process that generated the bitmap files.
235 void finishedGenerating(pid_t, int);
237 void dumpPreamble(odocstream &) const;
239 void dumpData(odocstream &, BitmapFile const &) const;
241 /** cache_ allows easy retrieval of already-generated images
242 * using the LaTeX snippet as the identifier.
244 typedef boost::shared_ptr<PreviewImage> PreviewImagePtr;
246 typedef map<string, PreviewImagePtr> Cache;
250 /** pending_ stores the LaTeX snippets in anticipation of them being
251 * sent to the converter.
253 PendingSnippets pending_;
255 /** in_progress_ stores all forked processes so that we can proceed
257 The map uses the conversion commands as its identifiers.
259 InProgressProcesses in_progress_;
262 PreviewLoader & parent_;
264 Buffer const & buffer_;
266 double font_scaling_factor_;
268 /// We don't own this
269 static Converter const * pconverter_;
273 Converter const * PreviewLoader::Impl::pconverter_;
277 // The public interface, defined in PreviewLoader.h
280 PreviewLoader::PreviewLoader(Buffer const & b)
281 : pimpl_(new Impl(*this, b))
285 PreviewLoader::~PreviewLoader()
289 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
291 return pimpl_->preview(latex_snippet);
295 PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const
297 return pimpl_->status(latex_snippet);
301 void PreviewLoader::add(string const & latex_snippet) const
303 pimpl_->add(latex_snippet);
307 void PreviewLoader::remove(string const & latex_snippet) const
309 pimpl_->remove(latex_snippet);
313 void PreviewLoader::startLoading() const
315 pimpl_->startLoading();
319 boost::signals::connection PreviewLoader::connect(slot_type const & slot) const
321 return pimpl_->imageReady.connect(slot);
325 void PreviewLoader::emitSignal(PreviewImage const & pimage) const
327 pimpl_->imageReady(pimage);
331 Buffer const & PreviewLoader::buffer() const
333 return pimpl_->buffer();
336 } // namespace graphics
340 // The details of the Impl
341 // =======================
345 class IncrementedFileName {
347 IncrementedFileName(string const & to_format,
348 string const & filename_base)
349 : to_format_(to_format), base_(filename_base), counter_(1)
352 StrPair const operator()(string const & snippet)
355 os << base_ << counter_++ << '.' << to_format_;
356 string const file = os.str();
358 return make_pair(snippet, file);
362 string const & to_format_;
363 string const & base_;
368 InProgress::InProgress(string const & filename_base,
369 PendingSnippets const & pending,
370 string const & to_format)
372 metrics_file(filename_base + ".metrics"),
373 snippets(pending.size())
375 PendingSnippets::const_iterator pit = pending.begin();
376 PendingSnippets::const_iterator pend = pending.end();
377 BitmapFile::iterator sit = snippets.begin();
379 std::transform(pit, pend, sit,
380 IncrementedFileName(to_format, filename_base));
384 void InProgress::stop() const
387 lyx::support::ForkedcallsController::get().kill(pid, 0);
389 if (!metrics_file.empty())
390 lyx::support::unlink(metrics_file);
392 BitmapFile::const_iterator vit = snippets.begin();
393 BitmapFile::const_iterator vend = snippets.end();
394 for (; vit != vend; ++vit) {
395 if (!vit->second.empty())
396 lyx::support::unlink(vit->second);
406 PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b)
407 : parent_(p), buffer_(b), font_scaling_factor_(0.0)
409 font_scaling_factor_ = 0.01 * lyxrc.dpi * lyxrc.zoom *
410 convert<double>(lyxrc.preview_scale_factor);
412 lyxerr[Debug::GRAPHICS] << "The font scaling factor is "
413 << font_scaling_factor_ << endl;
416 pconverter_ = setConverter();
420 PreviewLoader::Impl::~Impl()
422 InProgressProcesses::iterator ipit = in_progress_.begin();
423 InProgressProcesses::iterator ipend = in_progress_.end();
425 for (; ipit != ipend; ++ipit) {
432 PreviewLoader::Impl::preview(string const & latex_snippet) const
434 Cache::const_iterator it = cache_.find(latex_snippet);
435 return (it == cache_.end()) ? 0 : it->second.get();
441 class FindSnippet : public std::unary_function<InProgressProcess, bool> {
443 FindSnippet(string const & s) : snippet_(s) {}
444 bool operator()(InProgressProcess const & process) const
446 BitmapFile const & snippets = process.second.snippets;
447 BitmapFile::const_iterator beg = snippets.begin();
448 BitmapFile::const_iterator end = snippets.end();
449 return find_if(beg, end, FindFirst(snippet_)) != end;
453 string const snippet_;
458 PreviewLoader::Status
459 PreviewLoader::Impl::status(string const & latex_snippet) const
461 Cache::const_iterator cit = cache_.find(latex_snippet);
462 if (cit != cache_.end())
465 PendingSnippets::const_iterator pit = pending_.begin();
466 PendingSnippets::const_iterator pend = pending_.end();
468 pit = find(pit, pend, latex_snippet);
472 InProgressProcesses::const_iterator ipit = in_progress_.begin();
473 InProgressProcesses::const_iterator ipend = in_progress_.end();
475 ipit = find_if(ipit, ipend, FindSnippet(latex_snippet));
483 void PreviewLoader::Impl::add(string const & latex_snippet)
485 if (!pconverter_ || status(latex_snippet) != NotFound)
488 string const snippet = support::trim(latex_snippet);
492 lyxerr[Debug::GRAPHICS] << "adding snippet:\n" << snippet << endl;
494 pending_.push_back(snippet);
502 EraseSnippet(string const & s) : snippet_(s) {}
503 void operator()(InProgressProcess & process)
505 BitmapFile & snippets = process.second.snippets;
506 BitmapFile::iterator it = snippets.begin();
507 BitmapFile::iterator end = snippets.end();
509 it = find_if(it, end, FindFirst(snippet_));
511 snippets.erase(it, it+1);
515 string const & snippet_;
521 void PreviewLoader::Impl::remove(string const & latex_snippet)
523 Cache::iterator cit = cache_.find(latex_snippet);
524 if (cit != cache_.end())
527 PendingSnippets::iterator pit = pending_.begin();
528 PendingSnippets::iterator pend = pending_.end();
530 pending_.erase(std::remove(pit, pend, latex_snippet), pend);
532 InProgressProcesses::iterator ipit = in_progress_.begin();
533 InProgressProcesses::iterator ipend = in_progress_.end();
535 std::for_each(ipit, ipend, EraseSnippet(latex_snippet));
537 while (ipit != ipend) {
538 InProgressProcesses::iterator curr = ipit++;
539 if (curr->second.snippets.empty())
540 in_progress_.erase(curr);
545 void PreviewLoader::Impl::startLoading()
547 if (pending_.empty() || !pconverter_)
550 // Only start the process off after the buffer is loaded from file.
551 if (!buffer_.fully_loaded())
554 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()" << endl;
556 // As used by the LaTeX file and by the resulting image files
557 string const directory = buffer_.temppath();
559 string const filename_base = unique_filename(directory);
561 // Create an InProgress instance to place in the map of all
562 // such processes if it starts correctly.
563 InProgress inprogress(filename_base, pending_, pconverter_->to);
565 // clear pending_, so we're ready to start afresh.
568 // Output the LaTeX file.
569 string const latexfile = filename_base + ".tex";
572 // This creates an utf8 encoded file, but the proper inputenc
573 // command is missing.
574 odocfstream of(latexfile.c_str());
576 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
577 << "Unable to create LaTeX file\n"
578 << latexfile << endl;
581 of << "\\batchmode\n";
583 of << "\n\\begin{document}\n";
584 dumpData(of, inprogress.snippets);
585 of << "\n\\end{document}\n";
588 // The conversion command.
590 cs << pconverter_->command << ' ' << pconverter_->to << ' '
591 << support::quoteName(latexfile) << ' '
592 << int(font_scaling_factor_) << ' '
593 << theApp->hexName(LColor::preview) << ' '
594 << theApp->hexName(LColor::background);
596 string const command = support::libScriptSearch(cs.str());
598 // Initiate the conversion from LaTeX to bitmap images files.
599 support::Forkedcall::SignalTypePtr
600 convert_ptr(new support::Forkedcall::SignalType);
601 convert_ptr->connect(bind(&Impl::finishedGenerating, this, _1, _2));
603 support::Forkedcall call;
604 int ret = call.startscript(command, convert_ptr);
607 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
608 << "Unable to start process\n"
613 // Store the generation process in a list of all such processes
614 inprogress.pid = call.pid();
615 inprogress.command = command;
616 in_progress_[inprogress.pid] = inprogress;
620 void PreviewLoader::Impl::finishedGenerating(pid_t pid, int retval)
623 InProgressProcesses::iterator git = in_progress_.find(pid);
624 if (git == in_progress_.end()) {
625 lyxerr << "PreviewLoader::finishedGenerating(): unable to find "
626 "data for PID " << pid << endl;
630 string const command = git->second.command;
631 string const status = retval > 0 ? "failed" : "succeeded";
632 lyxerr[Debug::GRAPHICS] << "PreviewLoader::finishedInProgress("
633 << retval << "): processing " << status
634 << " for " << command << endl;
638 // Read the metrics file, if it exists
639 vector<double> ascent_fractions(git->second.snippets.size());
640 setAscentFractions(ascent_fractions, git->second.metrics_file);
642 // Add these newly generated bitmap files to the cache and
643 // start loading them into LyX.
644 BitmapFile::const_iterator it = git->second.snippets.begin();
645 BitmapFile::const_iterator end = git->second.snippets.end();
647 std::list<PreviewImagePtr> newimages;
649 int metrics_counter = 0;
650 for (; it != end; ++it, ++metrics_counter) {
651 string const & snip = it->first;
652 string const & file = it->second;
653 double af = ascent_fractions[metrics_counter];
655 PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af));
658 newimages.push_back(ptr);
661 // Remove the item from the list of still-executing processes.
662 in_progress_.erase(git);
664 // Tell the outside world
665 std::list<PreviewImagePtr>::const_reverse_iterator
666 nit = newimages.rbegin();
667 std::list<PreviewImagePtr>::const_reverse_iterator
668 nend = newimages.rend();
669 for (; nit != nend; ++nit) {
670 imageReady(*nit->get());
675 void PreviewLoader::Impl::dumpPreamble(odocstream & os) const
677 // Why on earth is Buffer::makeLaTeXFile a non-const method?
678 Buffer & tmp = const_cast<Buffer &>(buffer_);
679 // Dump the preamble only.
680 OutputParams runparams;
681 runparams.flavor = OutputParams::LATEX;
682 runparams.nice = true;
683 runparams.moving_arg = true;
684 runparams.free_spacing = true;
685 tmp.writeLaTeXSource(os, buffer_.filePath(), runparams, true, false);
687 // FIXME! This is a HACK! The proper fix is to control the 'true'
688 // passed to WriteStream below:
689 // int InsetFormula::latex(Buffer const &, odocstream & os,
690 // OutputParams const & runparams) const
692 // WriteStream wi(os, runparams.moving_arg, true);
697 << "\\def\\lyxlock{}\n"
700 // Loop over the insets in the buffer and dump all the math-macros.
701 InsetBase & inset = buffer_.inset();
702 InsetIterator it = inset_iterator_begin(inset);
703 InsetIterator const end = inset_iterator_end(inset);
705 for (; it != end; ++it)
706 if (it->lyxCode() == InsetBase::MATHMACRO_CODE)
707 it->latex(buffer_, os, runparams);
709 // All equation lables appear as "(#)" + preview.sty's rendering of
711 if (lyxrc.preview_hashed_labels)
712 os << "\\renewcommand{\\theequation}{\\#}\n";
714 // Use the preview style file to ensure that each snippet appears on a
717 << "\\usepackage[active,delayed,dvips,showlabels,lyx]{preview}\n"
722 void PreviewLoader::Impl::dumpData(odocstream & os,
723 BitmapFile const & vec) const
728 BitmapFile::const_iterator it = vec.begin();
729 BitmapFile::const_iterator end = vec.end();
731 for (; it != end; ++it) {
733 os << "\\begin{preview}\n"
734 << from_utf8(it->first)
735 << "\n\\end{preview}\n\n";
739 } // namespace graphics