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"
24 #include "lyxtextclasslist.h"
27 #include "insets/inset.h"
29 #include "frontends/lyx_gui.h" // hexname
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"
37 #include <boost/bind.hpp>
38 #include <boost/signals/trackable.hpp>
62 double getScalingFactor(Buffer &);
64 typedef pair<string, string> StrPair;
67 bool operator()(StrPair const & lhs, StrPair const & rhs)
69 return lhs.second < rhs.second;
74 FindFirst(string const & comp) : comp_(comp) {}
75 bool operator()(StrPair const & sp)
77 return sp.first < comp_;
84 string const unique_filename(string const bufferpath)
86 static int theCounter = 0;
87 string const filename = tostr(theCounter++) + "lyxpreview";
88 return AddName(bufferpath, filename);
92 /// Store info on a currently executing, forked process.
95 InProgress() : pid(0) {}
97 InProgress(string const & f, vector<StrPair> const & s)
98 : pid(0), metrics_file(f), snippets(s)
100 /// Remove any files left lying around and kill the forked process.
107 /// Each item in the vector is a pair<snippet, image file name>.
108 vector<StrPair> snippets;
117 struct PreviewLoader::Impl : public boost::signals::trackable {
119 Impl(PreviewLoader & p, Buffer const & b);
120 /// Stop any InProgress items still executing.
123 PreviewImage const * preview(string const & latex_snippet) const;
125 PreviewLoader::Status status(string const & latex_snippet) const;
127 void add(string const & latex_snippet);
129 void remove(string const & latex_snippet);
135 static bool haveConverter();
136 /// We don't own this
137 static Converter const * pconverter_;
139 /// Called by the Forkedcall process that generated the bitmap files.
140 void finishedGenerating(string const &, pid_t, int);
142 void dumpPreamble(ostream &) const;
144 void dumpData(ostream &, vector<StrPair> const &) const;
146 double fontScalingFactor() const;
148 /** cache_ allows easy retrieval of already-generated images
149 * using the LaTeX snippet as the identifier.
151 typedef boost::shared_ptr<PreviewImage> PreviewImagePtr;
153 typedef map<string, PreviewImagePtr> Cache;
157 /** pending_ stores the LaTeX snippets in anticipation of them being
158 * sent to the converter.
160 vector<string> pending_;
162 /** in_progress_ stores all forked processes so that we can proceed
164 The map uses the conversion commands as its identifiers.
166 typedef map<string, InProgress> InProgressMap;
168 InProgressMap in_progress_;
171 PreviewLoader & parent_;
173 Buffer const & buffer_;
175 mutable double font_scaling_factor_;
179 Converter const * PreviewLoader::Impl::pconverter_;
182 // The public interface, defined in PreviewLoader.h
183 // ================================================
184 PreviewLoader::PreviewLoader(Buffer const & b)
185 : pimpl_(new Impl(*this, b))
189 PreviewLoader::~PreviewLoader()
193 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
195 return pimpl_->preview(latex_snippet);
199 PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const
201 return pimpl_->status(latex_snippet);
205 void PreviewLoader::add(string const & latex_snippet)
207 pimpl_->add(latex_snippet);
211 void PreviewLoader::remove(string const & latex_snippet)
213 pimpl_->remove(latex_snippet);
217 void PreviewLoader::startLoading()
219 pimpl_->startLoading();
225 // The details of the Impl
226 // =======================
230 void InProgress::stop() const
233 ForkedcallsController::get().kill(pid, 0);
235 if (!metrics_file.empty())
236 lyx::unlink(metrics_file);
238 vector<StrPair>::const_iterator vit = snippets.begin();
239 vector<StrPair>::const_iterator vend = snippets.end();
240 for (; vit != vend; ++vit) {
241 if (!vit->second.empty())
242 lyx::unlink(vit->second);
251 bool PreviewLoader::Impl::haveConverter()
256 string const from = "lyxpreview";
258 Formats::FormatList::const_iterator it = formats.begin();
259 Formats::FormatList::const_iterator end = formats.end();
261 for (; it != end; ++it) {
262 string const to = it->name();
265 Converter const * ptr = converters.getConverter(from, to);
275 static bool first = true;
278 lyxerr << "PreviewLoader::startLoading()\n"
279 << "No converter from \"lyxpreview\" format has been "
288 PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b)
289 : parent_(p), buffer_(b), font_scaling_factor_(0.0)
293 PreviewLoader::Impl::~Impl()
295 InProgressMap::iterator ipit = in_progress_.begin();
296 InProgressMap::iterator ipend = in_progress_.end();
298 for (; ipit != ipend; ++ipit) {
305 PreviewLoader::Impl::preview(string const & latex_snippet) const
307 Cache::const_iterator it = cache_.find(latex_snippet);
308 return (it == cache_.end()) ? 0 : it->second.get();
312 PreviewLoader::Status
313 PreviewLoader::Impl::status(string const & latex_snippet) const
315 Cache::const_iterator cit = cache_.find(latex_snippet);
316 if (cit != cache_.end())
319 vector<string>::const_iterator vit = pending_.begin();
320 vector<string>::const_iterator vend = pending_.end();
321 vit = find(vit, vend, latex_snippet);
326 InProgressMap::const_iterator ipit = in_progress_.begin();
327 InProgressMap::const_iterator ipend = in_progress_.end();
329 for (; ipit != ipend; ++ipit) {
330 vector<StrPair> const & snippets = ipit->second.snippets;
331 vector<StrPair>::const_iterator vit = snippets.begin();
332 vector<StrPair>::const_iterator vend = snippets.end();
333 vit = find_if(vit, vend, FindFirst(latex_snippet));
343 void PreviewLoader::Impl::add(string const & latex_snippet)
345 if (!haveConverter())
348 if (status(latex_snippet) != NotFound)
351 pending_.push_back(latex_snippet);
352 sort(pending_.begin(), pending_.end());
356 void PreviewLoader::Impl::remove(string const & latex_snippet)
358 Cache::iterator cit = cache_.find(latex_snippet);
359 if (cit != cache_.end())
362 vector<string>::iterator vit = pending_.begin();
363 vector<string>::iterator vend = pending_.end();
364 vit = find(vit, vend, latex_snippet);
367 pending_.erase(vit, vit+1);
369 InProgressMap::iterator ipit = in_progress_.begin();
370 InProgressMap::iterator ipend = in_progress_.end();
372 while (ipit != ipend) {
373 InProgressMap::iterator curr = ipit;
376 vector<StrPair> & snippets = curr->second.snippets;
377 vector<StrPair>::iterator vit = snippets.begin();
378 vector<StrPair>::iterator vend = snippets.end();
379 vit = find_if(vit, vend, FindFirst(latex_snippet));
382 snippets.erase(vit, vit+1);
384 if (snippets.empty())
385 in_progress_.erase(curr);
390 void PreviewLoader::Impl::startLoading()
392 if (pending_.empty())
395 if (!haveConverter())
398 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()" << endl;
400 // As used by the LaTeX file and by the resulting image files
401 string const filename_base(unique_filename(buffer_.tmppath));
403 // Create an InProgress instance to place in the map of all
404 // such processes if it starts correctly.
405 vector<StrPair> snippets(pending_.size());
406 vector<StrPair>::iterator sit = snippets.begin();
407 vector<string>::const_iterator pit = pending_.begin();
408 vector<string>::const_iterator pend = pending_.end();
410 int counter = 1; // file numbers start at 1
411 for (; pit != pend; ++pit, ++sit, ++counter) {
414 << setfill('0') << setw(3) << counter
415 << "." << pconverter_->to;
416 string const file = os.str().c_str();
418 *sit = make_pair(*pit, file);
421 string const metrics_file = filename_base + ".metrics";
422 InProgress inprogress(metrics_file, snippets);
424 // clear pending_, so we're ready to start afresh.
427 // Output the LaTeX file.
428 string const latexfile = filename_base + ".tex";
430 ofstream of(latexfile.c_str());
432 of << "\n\\begin{document}\n";
433 dumpData(of, inprogress.snippets);
434 of << "\n\\end{document}\n";
437 // The conversion command.
438 double const scaling_factor = fontScalingFactor();
439 lyxerr[Debug::GRAPHICS] << "The font scaling factor is "
440 << scaling_factor << endl;
442 cs << pconverter_->command << " " << latexfile << " "
445 string const command = cs.str().c_str();
447 // Initiate the conversion from LaTeX to bitmap images files.
448 Forkedcall::SignalTypePtr convert_ptr;
449 convert_ptr.reset(new Forkedcall::SignalType);
451 convert_ptr->connect(
452 boost::bind(&Impl::finishedGenerating, this, _1, _2, _3));
455 int ret = call.startscript(command, convert_ptr);
458 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
459 << "Unable to start process \n"
464 // Store the generation process in a list of all such processes
465 inprogress.pid = call.pid();
466 in_progress_[command] = inprogress;
470 void PreviewLoader::Impl::finishedGenerating(string const & command,
471 pid_t /* pid */, int retval)
473 string const status = retval > 0 ? "failed" : "succeeded";
474 lyxerr[Debug::GRAPHICS] << "PreviewLoader::finishedInProgress("
475 << retval << "): processing " << status
476 << " for " << command << endl;
480 InProgressMap::iterator git = in_progress_.find(command);
481 if (git == in_progress_.end()) {
482 lyxerr << "PreviewLoader::finishedGenerating(): unable to find "
484 << command << "!" << endl;
488 // Read the metrics file, if it exists
489 PreviewMetrics metrics_file(git->second.metrics_file);
491 // Add these newly generated bitmap files to the cache and
492 // start loading them into LyX.
493 vector<StrPair>::const_iterator it = git->second.snippets.begin();
494 vector<StrPair>::const_iterator end = git->second.snippets.end();
496 int metrics_counter = 0;
497 for (; it != end; ++it) {
498 string const & snip = it->first;
501 Cache::const_iterator chk = cache_.find(snip);
502 if (chk != cache_.end())
505 // Mental note (Angus, 4 July 2002, having just found out the
507 // We /must/ first add to the cache and then start the
508 // image loading process.
509 // If not, then outside functions can be called before by the
510 // image loader before the PreviewImage/map is properly
512 // This can lead to all sorts of horribleness if such a
513 // function attempts to access the cache's internals.
514 string const & file = it->second;
515 double af = metrics_file.ascent_fraction(metrics_counter++);
516 PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af));
523 // Remove the item from the list of still-executing processes.
524 in_progress_.erase(git);
528 void PreviewLoader::Impl::dumpPreamble(ostream & os) const
530 // Why on earth is Buffer::makeLaTeXFile a non-const method?
531 Buffer & tmp = const_cast<Buffer &>(buffer_);
532 // Dump the preamble only.
533 tmp.makeLaTeXFile(os, string(), true, false, true);
535 // Loop over the insets in the buffer and dump all the math-macros.
536 Buffer::inset_iterator it = buffer_.inset_const_iterator_begin();
537 Buffer::inset_iterator end = buffer_.inset_const_iterator_end();
539 for (; it != end; ++it) {
540 if ((*it)->lyxCode() == Inset::MATHMACRO_CODE) {
541 (*it)->latex(&buffer_, os, true, true);
545 // Use the preview style file to ensure that each snippet appears on a
548 << "\\usepackage[active,delayed,dvips,tightpage,showlabels]{preview}\n"
551 // This piece of PostScript magic ensures that the foreground and
552 // background colors are the same as the LyX screen.
553 string fg = lyx_gui::hexname(LColor::preview);
554 if (fg.empty()) fg = "000000";
556 string bg = lyx_gui::hexname(LColor::background);
557 if (bg.empty()) bg = "ffffff";
559 os << "\\AtBeginDocument{\\AtBeginDvi{%\n"
560 << "\\special{!userdict begin/bop-hook{//bop-hook exec\n"
561 << "<" << fg << bg << ">{255 div}forall setrgbcolor\n"
562 << "clippath fill setrgbcolor}bind def end}}}\n";
566 void PreviewLoader::Impl::dumpData(ostream & os,
567 vector<StrPair> const & vec) const
572 vector<StrPair>::const_iterator it = vec.begin();
573 vector<StrPair>::const_iterator end = vec.end();
575 for (; it != end; ++it) {
576 os << "\\begin{preview}\n"
578 << "\n\\end{preview}\n\n";
583 double PreviewLoader::Impl::fontScalingFactor() const
585 static double const lyxrc_preview_scale_factor = 0.9;
587 if (font_scaling_factor_ > 0.01)
588 return font_scaling_factor_;
590 font_scaling_factor_ = getScalingFactor(const_cast<Buffer &>(buffer_));
591 return font_scaling_factor_;
600 double getScalingFactor(Buffer & buffer)
602 static double const lyxrc_preview_scale_factor = 0.9;
603 double scale_factor = 0.01 * lyxrc.dpi * lyxrc.zoom *
604 lyxrc_preview_scale_factor;
606 // Has the font size been set explicitly?
607 string const & fontsize = buffer.params.fontsize;
608 lyxerr[Debug::GRAPHICS] << "PreviewLoader::scaleToFitLyXView()\n"
609 << "font size is " << fontsize << endl;
611 if (isStrUnsignedInt(fontsize))
612 return 10.0 * scale_factor / strToDbl(fontsize);
614 // No. We must extract it from the LaTeX class file.
615 LyXTextClass const & tclass = textclasslist[buffer.params.textclass];
616 string const textclass(tclass.latexname() + ".cls");
617 string const classfile(findtexfile(textclass, "cls"));
619 lyxerr[Debug::GRAPHICS] << "text class is " << textclass << '\n'
620 << "class file is " << classfile << endl;
622 ifstream ifs(classfile.c_str());
624 lyxerr[Debug::GRAPHICS] << "Unable to open class file!" << endl;
629 double scaling = scale_factor;
633 // To get the default font size, look for a line like
634 // "\ExecuteOptions{letterpaper,10pt,oneside,onecolumn,final}"
635 if (!prefixIs(str, "\\ExecuteOptions"))
638 str = split(str, '{');
640 string tok = token(str, ',', count++);
641 while (!isValidLength(tok) && !tok.empty())
642 tok = token(str, ',', count++);
645 lyxerr[Debug::GRAPHICS]
646 << "Extracted default font size from "
647 "LaTeX class file successfully!" << endl;
648 LyXLength fsize(tok);
649 scaling *= 10.0 / fsize.value();