X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Fgraphics%2FPreviewLoader.C;h=5eb37010644edb289abdabada41b42da36cc3ee2;hb=3aa7e91a827fa15b3e0906b975c4755a2dcdb76d;hp=9519bf49fd428d99312b16870fc8dfb87900275c;hpb=9e8b8fe2d85b83ff459d00fab250f920bda057b2;p=lyx.git diff --git a/src/graphics/PreviewLoader.C b/src/graphics/PreviewLoader.C index 9519bf49fd..5eb3701064 100644 --- a/src/graphics/PreviewLoader.C +++ b/src/graphics/PreviewLoader.C @@ -1,9 +1,10 @@ -/* +/** * \file PreviewLoader.C - * Copyright 2002 the LyX Team * Read the file COPYING * - * \author Angus Leeming + * \author Angus Leeming + * + * Full author contact details available in file CREDITS */ #include @@ -14,10 +15,8 @@ #include "PreviewLoader.h" #include "PreviewImage.h" -#include "PreviewMetrics.h" #include "buffer.h" -#include "bufferparams.h" #include "converter.h" #include "debug.h" #include "lyxrc.h" @@ -29,6 +28,7 @@ #include "support/filetools.h" #include "support/forkedcall.h" +#include "support/forkedcontr.h" #include "support/lstrings.h" #include "support/lyxlib.h" @@ -37,15 +37,23 @@ #include #include +#include #include +#include +#include using std::endl; +using std::find; +using std::fill; using std::find_if; +using std::getline; +using std::make_pair; using std::setfill; using std::setw; -using std::sort; +using std::list; using std::map; +using std::ifstream; using std::ofstream; using std::ostream; using std::pair; @@ -55,12 +63,18 @@ namespace { typedef pair StrPair; -struct CompSecond { - bool operator()(StrPair const & lhs, StrPair const & rhs) - { - return lhs.second < rhs.second; - } -}; +// A list of alll snippets to be converted to previews +typedef list PendingSnippets; + +// Each item in the vector is a pair. +typedef vector BitmapFile; + +string const unique_filename(string const bufferpath); + +Converter const * setConverter(); + +void setAscentFractions(vector & ascent_fractions, + string const & metrics_file); struct FindFirst { FindFirst(string const & comp) : comp_(comp) {} @@ -73,22 +87,28 @@ private: }; -string const unique_filename() -{ - - static string dir; - if (dir.empty()) { - string const tmp = lyx::tempName(); - lyx::unlink(tmp); - dir = OnlyPath(tmp); - } +/// Store info on a currently executing, forked process. +struct InProgress { + /// + InProgress() : pid(0) {} + /// + InProgress(string const & filename_base, + PendingSnippets const & pending, + string const & to_format); + /// Remove any files left lying around and kill the forked process. + void stop() const; - static int theCounter = 0; - ostringstream os; - os << dir << theCounter++ << "lyxpreview"; + /// + pid_t pid; + /// + string metrics_file; + /// + BitmapFile snippets; +}; - return os.str().c_str(); -} +typedef map InProgressProcesses; + +typedef InProgressProcesses::value_type InProgressProcess; } // namespace anon @@ -98,6 +118,8 @@ namespace grfx { struct PreviewLoader::Impl : public boost::signals::trackable { /// Impl(PreviewLoader & p, Buffer const & b); + /// Stop any InProgress items still executing. + ~Impl(); /// PreviewImage const * preview(string const & latex_snippet) const; /// @@ -109,10 +131,10 @@ struct PreviewLoader::Impl : public boost::signals::trackable { /// void startLoading(); - /// - typedef pair StrPair; - /// - typedef map PendingMap; + /// Emit this signal when an image is ready for display. + boost::signal1 imageReady; + + Buffer const & buffer() const { return buffer_; } private: /// Called by the Forkedcall process that generated the bitmap files. @@ -120,12 +142,7 @@ private: /// void dumpPreamble(ostream &) const; /// - void dumpData(ostream &, vector const &) const; - - /// - static void setConverter(); - /// We don't own this - static Converter const * pconverter_; + void dumpData(ostream &, BitmapFile const &) const; /** cache_ allows easy retrieval of already-generated images * using the LaTeX snippet as the identifier. @@ -136,50 +153,34 @@ private: /// Cache cache_; - /** pending_ stores the LaTeX snippet and the name of the generated - * bitmap image file in anticipation of them being sent to the - * converter. + /** pending_ stores the LaTeX snippets in anticipation of them being + * sent to the converter. */ - PendingMap pending_; - - /// Store info on a currently executing, forked process. - struct InProgress { - /// - InProgress() {} - /// - InProgress(string const & f, PendingMap const & m) - : metrics_file(f), snippets(m.begin(), m.end()) - { - sort(snippets.begin(), snippets.end(), CompSecond()); - } - - /// - string metrics_file; - - /** Store the info in the PendingMap as a vector. - Ensures that the data is output in the order - file001, file002 etc, as we expect, which is /not/ what - happens when we iterate through the map. - */ - vector snippets; - }; - - /// Store all forked processes so that we can proceed thereafter. - typedef map InProgressMap; - /// - InProgressMap in_progress_; + PendingSnippets pending_; + + /** in_progress_ stores all forked processes so that we can proceed + * thereafter. + The map uses the conversion commands as its identifiers. + */ + InProgressProcesses in_progress_; - /// - string filename_base_; /// PreviewLoader & parent_; /// Buffer const & buffer_; + /// + double font_scaling_factor_; + + /// We don't own this + static Converter const * pconverter_; }; + Converter const * PreviewLoader::Impl::pconverter_; +// The public interface, defined in PreviewLoader.h +// ================================================ PreviewLoader::PreviewLoader(Buffer const & b) : pimpl_(new Impl(*this, b)) {} @@ -201,62 +202,131 @@ PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const } -void PreviewLoader::add(string const & latex_snippet) +void PreviewLoader::add(string const & latex_snippet) const { pimpl_->add(latex_snippet); } -void PreviewLoader::remove(string const & latex_snippet) +void PreviewLoader::remove(string const & latex_snippet) const { pimpl_->remove(latex_snippet); } -void PreviewLoader::startLoading() +void PreviewLoader::startLoading() const { pimpl_->startLoading(); } -void PreviewLoader::Impl::setConverter() +boost::signals::connection PreviewLoader::connect(slot_type const & slot) const { - if (pconverter_) - return; + return pimpl_->imageReady.connect(slot); +} - string const from = "lyxpreview"; - Formats::FormatList::const_iterator it = formats.begin(); - Formats::FormatList::const_iterator end = formats.end(); +void PreviewLoader::emitSignal(PreviewImage const & pimage) const +{ + pimpl_->imageReady(pimage); +} - for (; it != end; ++it) { - string const to = it->name(); - if (from == to) - continue; - Converter const * ptr = converters.getConverter(from, to); - if (ptr) { - pconverter_ = ptr; - break; - } + +Buffer const & PreviewLoader::buffer() const +{ + return pimpl_->buffer(); +} + +} // namespace grfx + + +// The details of the Impl +// ======================= + +namespace { + +struct IncrementedFileName { + IncrementedFileName(string const & to_format, + string const & filename_base) + : to_format_(to_format), base_(filename_base), counter_(1) + {} + + StrPair const operator()(string const & snippet) + { + ostringstream os; + os << base_ << counter_++ << "." << to_format_; + string const file = os.str().c_str(); + + return make_pair(snippet, file); } - if (pconverter_) - return; +private: + string const & to_format_; + string const & base_; + int counter_; +}; - static bool first = true; - if (!first) - return; - first = false; - lyxerr << "PreviewLoader::startLoading()\n" - << "No converter from \"lyxpreview\" format has been defined." - << endl; +InProgress::InProgress(string const & filename_base, + PendingSnippets const & pending, + string const & to_format) + : pid(0), + metrics_file(filename_base + ".metrics"), + snippets(pending.size()) +{ + PendingSnippets::const_iterator pit = pending.begin(); + PendingSnippets::const_iterator pend = pending.end(); + BitmapFile::iterator sit = snippets.begin(); + + std::transform(pit, pend, sit, + IncrementedFileName(to_format, filename_base)); +} + + +void InProgress::stop() const +{ + if (pid) + ForkedcallsController::get().kill(pid, 0); + + if (!metrics_file.empty()) + lyx::unlink(metrics_file); + + BitmapFile::const_iterator vit = snippets.begin(); + BitmapFile::const_iterator vend = snippets.end(); + for (; vit != vend; ++vit) { + if (!vit->second.empty()) + lyx::unlink(vit->second); + } } +} // namespace anon + + +namespace grfx { PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b) - : filename_base_(unique_filename()), parent_(p), buffer_(b) -{} + : parent_(p), buffer_(b), font_scaling_factor_(0.0) +{ + font_scaling_factor_ = 0.01 * lyxrc.dpi * lyxrc.zoom * + lyxrc.preview_scale_factor; + + lyxerr[Debug::GRAPHICS] << "The font scaling factor is " + << font_scaling_factor_ << endl; + + if (!pconverter_) + pconverter_ = setConverter(); +} + + +PreviewLoader::Impl::~Impl() +{ + InProgressProcesses::iterator ipit = in_progress_.begin(); + InProgressProcesses::iterator ipend = in_progress_.end(); + + for (; ipit != ipend; ++ipit) { + ipit->second.stop(); + } +} PreviewImage const * @@ -267,87 +337,106 @@ PreviewLoader::Impl::preview(string const & latex_snippet) const } +namespace { + +struct FindSnippet { + FindSnippet(string const & s) : snippet_(s) {} + bool operator()(InProgressProcess const & process) + { + BitmapFile const & snippets = process.second.snippets; + BitmapFile::const_iterator it = snippets.begin(); + BitmapFile::const_iterator end = snippets.end(); + it = find_if(it, end, FindFirst(snippet_)); + return it != end; + } + +private: + string const & snippet_; +}; + +} // namespace anon + PreviewLoader::Status PreviewLoader::Impl::status(string const & latex_snippet) const { Cache::const_iterator cit = cache_.find(latex_snippet); if (cit != cache_.end()) - return PreviewLoader::Ready; + return Ready; - PendingMap::const_iterator pit = pending_.find(latex_snippet); - if (pit != pending_.end()) - return PreviewLoader::InQueue; + PendingSnippets::const_iterator pit = pending_.begin(); + PendingSnippets::const_iterator pend = pending_.end(); - InProgressMap::const_iterator ipit = in_progress_.begin(); - InProgressMap::const_iterator ipend = in_progress_.end(); + pit = find(pit, pend, latex_snippet); + if (pit != pend) + return InQueue; - for (; ipit != ipend; ++ipit) { - vector const & snippets = ipit->second.snippets; - vector::const_iterator vit = snippets.begin(); - vector::const_iterator vend = snippets.end(); - vit = find_if(vit, vend, FindFirst(latex_snippet)); - - if (vit != vend) - return PreviewLoader::Processing; - } + InProgressProcesses::const_iterator ipit = in_progress_.begin(); + InProgressProcesses::const_iterator ipend = in_progress_.end(); + + ipit = find_if(ipit, ipend, FindSnippet(latex_snippet)); + if (ipit != ipend) + return Processing; - return PreviewLoader::NotFound; + return NotFound; } void PreviewLoader::Impl::add(string const & latex_snippet) { - if (!pconverter_) { - setConverter(); - if (!pconverter_) - return; - } - - Cache::const_iterator cit = cache_.find(latex_snippet); - if (cit != cache_.end()) + if (!pconverter_ || status(latex_snippet) != NotFound) return; - PendingMap::const_iterator pit = pending_.find(latex_snippet); - if (pit != pending_.end()) + string const snippet = trim(latex_snippet); + if (snippet.empty()) return; - int const snippet_counter = int(pending_.size()) + 1; - ostringstream os; - os << filename_base_ - << setfill('0') << setw(3) << snippet_counter - << "." << pconverter_->to; - string const image_filename = os.str().c_str(); + lyxerr[Debug::GRAPHICS] << "adding snippet:\n" << snippet << endl; - pending_[latex_snippet] = image_filename; + pending_.push_back(snippet); } +namespace { + +struct EraseSnippet { + EraseSnippet(string const & s) : snippet_(s) {} + void operator()(InProgressProcess & process) + { + BitmapFile & snippets = process.second.snippets; + BitmapFile::iterator it = snippets.begin(); + BitmapFile::iterator end = snippets.end(); + + it = find_if(it, end, FindFirst(snippet_)); + if (it != end) + snippets.erase(it, it+1); + } + +private: + string const & snippet_; +}; + +} // namespace anon + + void PreviewLoader::Impl::remove(string const & latex_snippet) { Cache::iterator cit = cache_.find(latex_snippet); if (cit != cache_.end()) cache_.erase(cit); - PendingMap::iterator pit = pending_.find(latex_snippet); - if (pit != pending_.end()) - pending_.erase(pit); + PendingSnippets::iterator pit = pending_.begin(); + PendingSnippets::iterator pend = pending_.end(); - InProgressMap::iterator ipit = in_progress_.begin(); - InProgressMap::iterator ipend = in_progress_.end(); + pending_.erase(std::remove(pit, pend, latex_snippet), pend); - while (ipit != ipend) { - InProgressMap::iterator curr = ipit; - ++ipit; + InProgressProcesses::iterator ipit = in_progress_.begin(); + InProgressProcesses::iterator ipend = in_progress_.end(); - vector & snippets = curr->second.snippets; - vector::iterator vit = snippets.begin(); - vector::iterator vend = snippets.end(); - vit = find_if(vit, vend, FindFirst(latex_snippet)); - - if (vit != vend) - snippets.erase(vit, vit+1); + std::for_each(ipit, ipend, EraseSnippet(latex_snippet)); - if (snippets.empty()) + for (; ipit != ipend; ++ipit) { + InProgressProcesses::iterator curr = ipit++; + if (curr->second.snippets.empty()) in_progress_.erase(curr); } } @@ -355,43 +444,38 @@ void PreviewLoader::Impl::remove(string const & latex_snippet) void PreviewLoader::Impl::startLoading() { - if (pending_.empty()) + if (pending_.empty() || !pconverter_) return; - if (!pconverter_) { - setConverter(); - if (!pconverter_) - return; - } - lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()" << endl; + // As used by the LaTeX file and by the resulting image files + string const filename_base(unique_filename(buffer_.tmppath)); + // Create an InProgress instance to place in the map of all // such processes if it starts correctly. - string const metrics_file = filename_base_ + ".metrics"; - InProgress inprogress(metrics_file, pending_); + InProgress inprogress(filename_base, pending_, pconverter_->to); + + // clear pending_, so we're ready to start afresh. + pending_.clear(); // Output the LaTeX file. - string const latexfile = filename_base_ + ".tex"; + string const latexfile = filename_base + ".tex"; ofstream of(latexfile.c_str()); + of << "\\batchmode\n"; dumpPreamble(of); of << "\n\\begin{document}\n"; dumpData(of, inprogress.snippets); of << "\n\\end{document}\n"; of.close(); - // Reset the filename and clear pending_, so we're ready to - // start afresh. - pending_.clear(); - filename_base_ = unique_filename(); - // The conversion command. ostringstream cs; cs << pconverter_->command << " " << latexfile << " " - << tostr(0.01 * lyxrc.dpi * lyxrc.zoom); + << int(font_scaling_factor_) << " " << pconverter_->to; - string const command = cs.str().c_str(); + string const command = "sh " + LibScriptSearch(cs.str().c_str()); // Initiate the conversion from LaTeX to bitmap images files. Forkedcall::SignalTypePtr convert_ptr; @@ -409,9 +493,9 @@ void PreviewLoader::Impl::startLoading() << command << endl; return; } - - // Store the generation process in a list of all generating processes - // (I anticipate that this will be small!) + + // Store the generation process in a list of all such processes + inprogress.pid = call.pid(); in_progress_[command] = inprogress; } @@ -426,7 +510,8 @@ void PreviewLoader::Impl::finishedGenerating(string const & command, if (retval > 0) return; - InProgressMap::iterator git = in_progress_.find(command); + // Paranoia check! + InProgressProcesses::iterator git = in_progress_.find(command); if (git == in_progress_.end()) { lyxerr << "PreviewLoader::finishedGenerating(): unable to find " "data for\n" @@ -435,40 +520,37 @@ void PreviewLoader::Impl::finishedGenerating(string const & command, } // Read the metrics file, if it exists - PreviewMetrics metrics_file(git->second.metrics_file); - + vector ascent_fractions(git->second.snippets.size()); + setAscentFractions(ascent_fractions, git->second.metrics_file); + // Add these newly generated bitmap files to the cache and // start loading them into LyX. - vector::const_iterator it = git->second.snippets.begin(); - vector::const_iterator end = git->second.snippets.end(); + BitmapFile::const_iterator it = git->second.snippets.begin(); + BitmapFile::const_iterator end = git->second.snippets.end(); + + std::list newimages; int metrics_counter = 0; - for (; it != end; ++it) { + for (; it != end; ++it, ++metrics_counter) { string const & snip = it->first; - - // Paranoia check - Cache::const_iterator chk = cache_.find(snip); - if (chk != cache_.end()) - continue; - - // Mental note (Angus, 4 July 2002, having just found out the - // hard way :-(). - // We /must/ first add to the cache and then start the - // image loading process. - // If not, then outside functions can be called before by the - // image loader before the PreviewImage is properly constucted. - // This can lead to all sorts of horribleness if such a - // function attempts to access its internals. string const & file = it->second; - double af = metrics_file.ascent_fraction(metrics_counter++); - PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af)); + double af = ascent_fractions[metrics_counter]; + PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af)); cache_[snip] = ptr; - ptr->startLoading(); + newimages.push_back(ptr); } + // Remove the item from the list of still-executing processes. in_progress_.erase(git); + + // Tell the outside world + std::list::const_iterator nit = newimages.begin(); + std::list::const_iterator nend = newimages.end(); + for (; nit != nend; ++nit) { + imageReady(*nit->get()); + } } @@ -477,22 +559,25 @@ void PreviewLoader::Impl::dumpPreamble(ostream & os) const // Why on earth is Buffer::makeLaTeXFile a non-const method? Buffer & tmp = const_cast(buffer_); // Dump the preamble only. - tmp.makeLaTeXFile(os, string(), true, false, true); + tmp.makeLaTeXFile(os, buffer_.filePath(), true, false, true); // Loop over the insets in the buffer and dump all the math-macros. Buffer::inset_iterator it = buffer_.inset_const_iterator_begin(); Buffer::inset_iterator end = buffer_.inset_const_iterator_end(); - for (; it != end; ++it) { - if ((*it)->lyxCode() == Inset::MATHMACRO_CODE) { - (*it)->latex(&buffer_, os, true, true); - } - } + for (; it != end; ++it) + if (it->lyxCode() == Inset::MATHMACRO_CODE) + it->latex(&buffer_, os, true, true); + + // All equation lables appear as "(#)" + preview.sty's rendering of + // the label name + if (lyxrc.preview_hashed_labels) + os << "\\renewcommand{\\theequation}{\\#}\n"; // Use the preview style file to ensure that each snippet appears on a // fresh page. os << "\n" - << "\\usepackage[active,dvips,tightpage]{preview}\n" + << "\\usepackage[active,delayed,dvips,tightpage,showlabels,lyx]{preview}\n" << "\n"; // This piece of PostScript magic ensures that the foreground and @@ -502,7 +587,7 @@ void PreviewLoader::Impl::dumpPreamble(ostream & os) const string bg = lyx_gui::hexname(LColor::background); if (bg.empty()) bg = "ffffff"; - + os << "\\AtBeginDocument{\\AtBeginDvi{%\n" << "\\special{!userdict begin/bop-hook{//bop-hook exec\n" << "<" << fg << bg << ">{255 div}forall setrgbcolor\n" @@ -510,20 +595,141 @@ void PreviewLoader::Impl::dumpPreamble(ostream & os) const } -void PreviewLoader::Impl::dumpData(ostream & os, - vector const & vec) const +void PreviewLoader::Impl::dumpData(ostream & os, + BitmapFile const & vec) const { if (vec.empty()) return; - vector::const_iterator it = vec.begin(); - vector::const_iterator end = vec.end(); + BitmapFile::const_iterator it = vec.begin(); + BitmapFile::const_iterator end = vec.end(); for (; it != end; ++it) { os << "\\begin{preview}\n" - << it->first + << it->first << "\n\\end{preview}\n\n"; } } } // namespace grfx + + +namespace { + +string const unique_filename(string const bufferpath) +{ + static int theCounter = 0; + string const filename = tostr(theCounter++) + "lyxpreview"; + return AddName(bufferpath, filename); +} + + +Converter const * setConverter() +{ + string const from = "lyxpreview"; + + Formats::FormatList::const_iterator it = formats.begin(); + Formats::FormatList::const_iterator end = formats.end(); + + for (; it != end; ++it) { + string const to = it->name(); + if (from == to) + continue; + + Converter const * ptr = converters.getConverter(from, to); + if (ptr) + return ptr; + } + + static bool first = true; + if (first) { + first = false; + lyxerr << "PreviewLoader::startLoading()\n" + << "No converter from \"lyxpreview\" format has been " + "defined." + << endl; + } + + return 0; +} + + +void setAscentFractions(vector & ascent_fractions, + string const & metrics_file) +{ + // If all else fails, then the images will have equal ascents and + // descents. + vector::iterator it = ascent_fractions.begin(); + vector::iterator end = ascent_fractions.end(); + fill(it, end, 0.5); + + ifstream in(metrics_file.c_str()); + if (!in.good()) { + lyxerr[Debug::GRAPHICS] + << "setAscentFractions(" << metrics_file << ")\n" + << "Unable to open file!" << endl; + return; + } + + bool error = false; + + // Tightpage dimensions affect all subsequent dimensions + int tp_ascent; + int tp_descent; + + int snippet_counter = 0; + while (!in.eof()) { + // Expecting lines of the form + // Preview: Tightpage tp_bl_x tp_bl_y tp_tr_x tp_tr_y + // Preview: Snippet id ascent descent width + string preview; + string type; + in >> preview >> type; + + if (!in.good()) + // eof after all + break; + + error = preview != "Preview:" + || (type != "Tightpage" && type != "Snippet"); + if (error) + break; + + if (type == "Tightpage") { + int dummy; + in >> dummy >> tp_descent >> dummy >> tp_ascent; + + error = !in.good(); + if (error) + break; + + } else { + int dummy; + int snippet_id; + int ascent; + int descent; + in >> snippet_id >> ascent >> descent >> dummy; + + error = !in.good() || ++snippet_counter != snippet_id; + if (error) + break; + + double const a = ascent + tp_ascent; + double const d = descent - tp_descent; + + if (!lyx::float_equal(a + d, 0, 0.1)) + *it = a / (a + d); + + if (++it == end) + break; + } + } + + if (error) { + lyxerr[Debug::GRAPHICS] + << "setAscentFractions(" << metrics_file << ")\n" + << "Error reading file!\n" << endl; + } +} + +} // namespace anon