2 * \file PreviewLoader.cpp
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 "BufferParams.h"
19 #include "Converter.h"
23 #include "InsetIterator.h"
25 #include "LaTeXFeatures.h"
28 #include "OutputParams.h"
29 #include "Paragraph.h"
32 #include "frontends/Application.h" // hexName
34 #include "insets/Inset.h"
36 #include "support/filetools.h"
37 #include "support/Forkedcall.h"
38 #include "support/ForkedcallsController.h"
39 #include "support/lstrings.h"
40 #include "support/lyxlib.h"
41 #include "support/convert.h"
43 #include <boost/bind.hpp>
49 using lyx::support::FileName;
62 using std::ostringstream;
70 typedef pair<string, FileName> SnippetPair;
72 // A list of all snippets to be converted to previews
73 typedef list<string> PendingSnippets;
75 // Each item in the vector is a pair<snippet, image file name>.
76 typedef vector<SnippetPair> BitmapFile;
79 string const unique_filename(string const & bufferpath)
81 static int theCounter = 0;
82 string const filename = lyx::convert<string>(theCounter++) + "lyxpreview";
83 return lyx::support::addName(bufferpath, filename);
87 lyx::Converter const * setConverter()
89 string const from = "lyxpreview";
91 typedef vector<string> FmtList;
92 typedef lyx::graphics::Cache GCache;
93 FmtList const loadableFormats = GCache::get().loadableFormats();
94 FmtList::const_iterator it = loadableFormats.begin();
95 FmtList::const_iterator const end = loadableFormats.end();
97 for (; it != end; ++it) {
98 string const to = *it;
102 lyx::Converter const * ptr = lyx::theConverters().getConverter(from, to);
107 static bool first = true;
110 lyx::lyxerr << "PreviewLoader::startLoading()\n"
111 << "No converter from \"lyxpreview\" format has been "
119 void setAscentFractions(vector<double> & ascent_fractions,
120 FileName const & metrics_file)
122 // If all else fails, then the images will have equal ascents and
124 vector<double>::iterator it = ascent_fractions.begin();
125 vector<double>::iterator end = ascent_fractions.end();
128 ifstream in(metrics_file.toFilesystemEncoding().c_str());
130 lyx::lyxerr[lyx::Debug::GRAPHICS]
131 << "setAscentFractions(" << metrics_file << ")\n"
132 << "Unable to open file!" << endl;
138 int snippet_counter = 1;
139 while (!in.eof() && it != end) {
142 double ascent_fraction;
144 in >> snippet >> id >> ascent_fraction;
150 error = snippet != "Snippet";
154 error = id != snippet_counter;
158 *it = ascent_fraction;
165 lyx::lyxerr[lyx::Debug::GRAPHICS]
166 << "setAscentFractions(" << metrics_file << ")\n"
167 << "Error reading file!\n" << endl;
172 class FindFirst : public std::unary_function<SnippetPair, bool> {
174 FindFirst(string const & comp) : comp_(comp) {}
175 bool operator()(SnippetPair const & sp) const
177 return sp.first == comp_;
184 /// Store info on a currently executing, forked process.
188 InProgress() : pid(0) {}
190 InProgress(string const & filename_base,
191 PendingSnippets const & pending,
192 string const & to_format);
193 /// Remove any files left lying around and kill the forked process.
201 FileName metrics_file;
206 typedef map<pid_t, InProgress> InProgressProcesses;
208 typedef InProgressProcesses::value_type InProgressProcess;
218 class PreviewLoader::Impl : public boost::signals::trackable {
221 Impl(PreviewLoader & p, Buffer const & b);
222 /// Stop any InProgress items still executing.
225 PreviewImage const * preview(string const & latex_snippet) const;
227 PreviewLoader::Status status(string const & latex_snippet) const;
229 void add(string const & latex_snippet);
231 void remove(string const & latex_snippet);
235 /// Emit this signal when an image is ready for display.
236 boost::signal<void(PreviewImage const &)> imageReady;
238 Buffer const & buffer() const { return buffer_; }
241 /// Called by the Forkedcall process that generated the bitmap files.
242 void finishedGenerating(pid_t, int);
244 void dumpPreamble(odocstream &) const;
246 void dumpData(odocstream &, BitmapFile const &) const;
248 /** cache_ allows easy retrieval of already-generated images
249 * using the LaTeX snippet as the identifier.
251 typedef boost::shared_ptr<PreviewImage> PreviewImagePtr;
253 typedef map<string, PreviewImagePtr> Cache;
257 /** pending_ stores the LaTeX snippets in anticipation of them being
258 * sent to the converter.
260 PendingSnippets pending_;
262 /** in_progress_ stores all forked processes so that we can proceed
264 The map uses the conversion commands as its identifiers.
266 InProgressProcesses in_progress_;
269 PreviewLoader & parent_;
271 Buffer const & buffer_;
273 double font_scaling_factor_;
275 /// We don't own this
276 static lyx::Converter const * pconverter_;
280 lyx::Converter const * PreviewLoader::Impl::pconverter_;
284 // The public interface, defined in PreviewLoader.h
287 PreviewLoader::PreviewLoader(Buffer const & b)
288 : pimpl_(new Impl(*this, b))
292 PreviewLoader::~PreviewLoader()
296 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
298 return pimpl_->preview(latex_snippet);
302 PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const
304 return pimpl_->status(latex_snippet);
308 void PreviewLoader::add(string const & latex_snippet) const
310 pimpl_->add(latex_snippet);
314 void PreviewLoader::remove(string const & latex_snippet) const
316 pimpl_->remove(latex_snippet);
320 void PreviewLoader::startLoading() const
322 pimpl_->startLoading();
326 boost::signals::connection PreviewLoader::connect(slot_type const & slot) const
328 return pimpl_->imageReady.connect(slot);
332 void PreviewLoader::emitSignal(PreviewImage const & pimage) const
334 pimpl_->imageReady(pimage);
338 Buffer const & PreviewLoader::buffer() const
340 return pimpl_->buffer();
343 } // namespace graphics
347 // The details of the Impl
348 // =======================
352 class IncrementedFileName {
354 IncrementedFileName(string const & to_format,
355 string const & filename_base)
356 : to_format_(to_format), base_(filename_base), counter_(1)
359 SnippetPair const operator()(string const & snippet)
362 os << base_ << counter_++ << '.' << to_format_;
363 string const file = os.str();
365 return make_pair(snippet, FileName(file));
369 string const & to_format_;
370 string const & base_;
375 InProgress::InProgress(string const & filename_base,
376 PendingSnippets const & pending,
377 string const & to_format)
379 metrics_file(FileName(filename_base + ".metrics")),
380 snippets(pending.size())
382 PendingSnippets::const_iterator pit = pending.begin();
383 PendingSnippets::const_iterator pend = pending.end();
384 BitmapFile::iterator sit = snippets.begin();
386 std::transform(pit, pend, sit,
387 IncrementedFileName(to_format, filename_base));
391 void InProgress::stop() const
394 lyx::support::ForkedcallsController::get().kill(pid, 0);
396 if (!metrics_file.empty())
397 lyx::support::unlink(metrics_file);
399 BitmapFile::const_iterator vit = snippets.begin();
400 BitmapFile::const_iterator vend = snippets.end();
401 for (; vit != vend; ++vit) {
402 if (!vit->second.empty())
403 lyx::support::unlink(vit->second);
413 PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b)
414 : parent_(p), buffer_(b), font_scaling_factor_(0.0)
416 font_scaling_factor_ = 0.01 * lyxrc.dpi * lyxrc.zoom *
417 convert<double>(lyxrc.preview_scale_factor);
419 LYXERR(Debug::GRAPHICS) << "The font scaling factor is "
420 << font_scaling_factor_ << endl;
423 pconverter_ = setConverter();
427 PreviewLoader::Impl::~Impl()
429 InProgressProcesses::iterator ipit = in_progress_.begin();
430 InProgressProcesses::iterator ipend = in_progress_.end();
432 for (; ipit != ipend; ++ipit) {
439 PreviewLoader::Impl::preview(string const & latex_snippet) const
441 Cache::const_iterator it = cache_.find(latex_snippet);
442 return (it == cache_.end()) ? 0 : it->second.get();
448 class FindSnippet : public std::unary_function<InProgressProcess, bool> {
450 FindSnippet(string const & s) : snippet_(s) {}
451 bool operator()(InProgressProcess const & process) const
453 BitmapFile const & snippets = process.second.snippets;
454 BitmapFile::const_iterator beg = snippets.begin();
455 BitmapFile::const_iterator end = snippets.end();
456 return find_if(beg, end, FindFirst(snippet_)) != end;
460 string const snippet_;
465 PreviewLoader::Status
466 PreviewLoader::Impl::status(string const & latex_snippet) const
468 Cache::const_iterator cit = cache_.find(latex_snippet);
469 if (cit != cache_.end())
472 PendingSnippets::const_iterator pit = pending_.begin();
473 PendingSnippets::const_iterator pend = pending_.end();
475 pit = find(pit, pend, latex_snippet);
479 InProgressProcesses::const_iterator ipit = in_progress_.begin();
480 InProgressProcesses::const_iterator ipend = in_progress_.end();
482 ipit = find_if(ipit, ipend, FindSnippet(latex_snippet));
490 void PreviewLoader::Impl::add(string const & latex_snippet)
492 if (!pconverter_ || status(latex_snippet) != NotFound)
495 string const snippet = support::trim(latex_snippet);
499 LYXERR(Debug::GRAPHICS) << "adding snippet:\n" << snippet << endl;
501 pending_.push_back(snippet);
509 EraseSnippet(string const & s) : snippet_(s) {}
510 void operator()(InProgressProcess & process)
512 BitmapFile & snippets = process.second.snippets;
513 BitmapFile::iterator it = snippets.begin();
514 BitmapFile::iterator end = snippets.end();
516 it = find_if(it, end, FindFirst(snippet_));
518 snippets.erase(it, it+1);
522 string const & snippet_;
528 void PreviewLoader::Impl::remove(string const & latex_snippet)
530 Cache::iterator cit = cache_.find(latex_snippet);
531 if (cit != cache_.end())
534 PendingSnippets::iterator pit = pending_.begin();
535 PendingSnippets::iterator pend = pending_.end();
537 pending_.erase(std::remove(pit, pend, latex_snippet), pend);
539 InProgressProcesses::iterator ipit = in_progress_.begin();
540 InProgressProcesses::iterator ipend = in_progress_.end();
542 std::for_each(ipit, ipend, EraseSnippet(latex_snippet));
544 while (ipit != ipend) {
545 InProgressProcesses::iterator curr = ipit++;
546 if (curr->second.snippets.empty())
547 in_progress_.erase(curr);
552 void PreviewLoader::Impl::startLoading()
554 if (pending_.empty() || !pconverter_)
557 // Only start the process off after the buffer is loaded from file.
558 if (!buffer_.fully_loaded())
561 LYXERR(Debug::GRAPHICS) << "PreviewLoader::startLoading()" << endl;
563 // As used by the LaTeX file and by the resulting image files
564 string const directory = buffer_.temppath();
566 string const filename_base = unique_filename(directory);
568 // Create an InProgress instance to place in the map of all
569 // such processes if it starts correctly.
570 InProgress inprogress(filename_base, pending_, pconverter_->to);
572 // clear pending_, so we're ready to start afresh.
575 // Output the LaTeX file.
576 FileName const latexfile(filename_base + ".tex");
578 // we use the encoding of the buffer
579 Encoding const & enc = buffer_.params().encoding();
580 odocfstream of(enc.iconvName());
582 OutputParams runparams(&enc);
583 LaTeXFeatures features(buffer_, buffer_.params(), runparams);
585 if (!openFileWrite(of, latexfile))
589 LYXERR(Debug::GRAPHICS) << "PreviewLoader::startLoading()\n"
590 << "Unable to create LaTeX file\n"
591 << latexfile << endl;
594 of << "\\batchmode\n";
596 // handle inputenc etc.
597 of << buffer_.params().writeEncodingPreamble(features, texrow);
598 of << "\n\\begin{document}\n";
599 dumpData(of, inprogress.snippets);
600 of << "\n\\end{document}\n";
603 LYXERR(Debug::GRAPHICS) << "PreviewLoader::startLoading()\n"
604 << "File was not closed properly."
609 // The conversion command.
611 cs << pconverter_->command << ' ' << pconverter_->to << ' '
612 << support::quoteName(latexfile.toFilesystemEncoding()) << ' '
613 << int(font_scaling_factor_) << ' '
614 << theApp()->hexName(Color::preview) << ' '
615 << theApp()->hexName(Color::background);
617 string const command = support::libScriptSearch(cs.str());
619 // Initiate the conversion from LaTeX to bitmap images files.
620 support::Forkedcall::SignalTypePtr
621 convert_ptr(new support::Forkedcall::SignalType);
622 convert_ptr->connect(bind(&Impl::finishedGenerating, this, _1, _2));
624 support::Forkedcall call;
625 int ret = call.startscript(command, convert_ptr);
628 LYXERR(Debug::GRAPHICS) << "PreviewLoader::startLoading()\n"
629 << "Unable to start process\n"
634 // Store the generation process in a list of all such processes
635 inprogress.pid = call.pid();
636 inprogress.command = command;
637 in_progress_[inprogress.pid] = inprogress;
641 void PreviewLoader::Impl::finishedGenerating(pid_t pid, int retval)
644 InProgressProcesses::iterator git = in_progress_.find(pid);
645 if (git == in_progress_.end()) {
646 lyxerr << "PreviewLoader::finishedGenerating(): unable to find "
647 "data for PID " << pid << endl;
651 string const command = git->second.command;
652 string const status = retval > 0 ? "failed" : "succeeded";
653 LYXERR(Debug::GRAPHICS) << "PreviewLoader::finishedInProgress("
654 << retval << "): processing " << status
655 << " for " << command << endl;
659 // Read the metrics file, if it exists
660 vector<double> ascent_fractions(git->second.snippets.size());
661 setAscentFractions(ascent_fractions, git->second.metrics_file);
663 // Add these newly generated bitmap files to the cache and
664 // start loading them into LyX.
665 BitmapFile::const_iterator it = git->second.snippets.begin();
666 BitmapFile::const_iterator end = git->second.snippets.end();
668 std::list<PreviewImagePtr> newimages;
670 int metrics_counter = 0;
671 for (; it != end; ++it, ++metrics_counter) {
672 string const & snip = it->first;
673 FileName const & file = it->second;
674 double af = ascent_fractions[metrics_counter];
676 PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af));
679 newimages.push_back(ptr);
682 // Remove the item from the list of still-executing processes.
683 in_progress_.erase(git);
685 // Tell the outside world
686 std::list<PreviewImagePtr>::const_reverse_iterator
687 nit = newimages.rbegin();
688 std::list<PreviewImagePtr>::const_reverse_iterator
689 nend = newimages.rend();
690 for (; nit != nend; ++nit) {
691 imageReady(*nit->get());
696 void PreviewLoader::Impl::dumpPreamble(odocstream & os) const
698 // Why on earth is Buffer::makeLaTeXFile a non-const method?
699 Buffer & tmp = const_cast<Buffer &>(buffer_);
700 // Dump the preamble only.
701 // We don't need an encoding for runparams since it is not used by
703 OutputParams runparams(0);
704 runparams.flavor = OutputParams::LATEX;
705 runparams.nice = true;
706 runparams.moving_arg = true;
707 runparams.free_spacing = true;
708 tmp.writeLaTeXSource(os, buffer_.filePath(), runparams, true, false);
710 // FIXME! This is a HACK! The proper fix is to control the 'true'
711 // passed to WriteStream below:
712 // int InsetMathNest::latex(Buffer const &, odocstream & os,
713 // OutputParams const & runparams) const
715 // WriteStream wi(os, runparams.moving_arg, true);
720 << "\\def\\lyxlock{}\n"
723 // Loop over the insets in the buffer and dump all the math-macros.
724 Inset & inset = buffer_.inset();
725 InsetIterator it = inset_iterator_begin(inset);
726 InsetIterator const end = inset_iterator_end(inset);
728 for (; it != end; ++it)
729 if (it->lyxCode() == Inset::MATHMACRO_CODE)
730 it->latex(buffer_, os, runparams);
732 // All equation labels appear as "(#)" + preview.sty's rendering of
734 if (lyxrc.preview_hashed_labels)
735 os << "\\renewcommand{\\theequation}{\\#}\n";
737 // Use the preview style file to ensure that each snippet appears on a
740 << "\\usepackage[active,delayed,dvips,showlabels,lyx]{preview}\n"
745 void PreviewLoader::Impl::dumpData(odocstream & os,
746 BitmapFile const & vec) const
751 BitmapFile::const_iterator it = vec.begin();
752 BitmapFile::const_iterator end = vec.end();
754 for (; it != end; ++it) {
756 os << "\\begin{preview}\n"
757 << from_utf8(it->first)
758 << "\n\\end{preview}\n\n";
762 } // namespace graphics