Hi Angus, Hi José!
-- I guess this is for you! :-)
I implemented a basic version for generating bitmap files for equations in DocBook output. You can now export to Docbook or Docbook-xml and dozens of "eqn-XYZ.png" files will appear.
Here is what I changed:
* refactored PreviewLoader to use a new class SnippetConversion
* added functions generateBitmaps() and bitmaps() in sgml.[hC]
* changed Buffer::makeDocbookFile() to call createBitmaps()
* changed math_hullsinset::docbook() to look up a bitmap file for the equation
and create a <graphics> element to an external file, which is also added to
runparams.exportdata
* changed lyxpreview2bitmap.py and legacy_lyxpreview2ppm to happily create
PNG files even without dvipng
So it works, but there are some details about filehandling, architecture and the
"Right Way of Docbooking" where I would appreciate some feedback (see below)
Patch attached. Ciao /Andreas
TODO:
- find out about sporadic "child timeout" errors
- test other bitmap formats (eps)
- improve naming of bitmap files
- create bitmaps in extra directory, not at the same level as the master file
- some DocBook bells and whistles
- move the SnippetConversion* from sgml.C to exportdata
- Ask for overwrite of bitmap files (appears that eporter doesn't do that yet)
snippetconversion.diff
Description: Binary data/** * \file PreviewLoader.C * This file is part of LyX, the document processor. * Licence details can be found in the file COPYING. * * \author Angus Leeming * * Full author contact details are available in file CREDITS. */ #include <config.h> #include "GraphicsCache.h" #include "SnippetConversion.h" #include "PreviewLoader.h" #include "PreviewImage.h" #include "buffer.h" #include "converter.h" #include "debug.h" #include "format.h" #include "LColor.h" #include "lyxrc.h" #include "outputparams.h" #include "frontends/lyx_gui.h" // hexname #include "support/filetools.h" #include "support/lstrings.h" #include "support/lyxlib.h" #include "support/convert.h" #include <boost/bind.hpp> #include <sstream> #include <fstream> #include <iomanip> namespace support = lyx::support; using std::endl; using std::find; using std::fill; using std::find_if; using std::make_pair; using boost::bind; using std::ifstream; using std::list; using std::map; using std::ofstream; using std::ostream; using std::ostringstream; using std::pair; using std::vector; using std::string; namespace { typedef lyx::graphics::SnippetConversion::SnippetList SnippetList; string const unique_filename(string const bufferpath); Converter const * setConverter(); typedef map<pid_t, lyx::graphics::SnippetConversion> InProgressProcesses; typedef InProgressProcesses::value_type InProgressProcess; } // namespace anon namespace lyx { namespace graphics { class PreviewLoader::Impl : public boost::signals::trackable { public: /// Impl(PreviewLoader & p, Buffer const & b); /// Stop any SnippetConversion items still executing. ~Impl(); /// PreviewImage const * preview(string const & latex_snippet) const; /// PreviewLoader::Status status(string const & latex_snippet) const; /// void add(string const & latex_snippet); /// void remove(string const & latex_snippet); /// void startLoading(); /// Emit this signal when an image is ready for display. boost::signal<void(PreviewImage const &)> imageReady; Buffer const & buffer() const { return buffer_; } private: /// Called by the Forkedcall process that generated the bitmap files. void finishedGenerating(pid_t, int); /** cache_ allows easy retrieval of already-generated images * using the LaTeX snippet as the identifier. */ typedef boost::shared_ptr<PreviewImage> PreviewImagePtr; /// typedef map<string, PreviewImagePtr> Cache; /// Cache cache_; /** pending_ stores the LaTeX snippets in anticipation of them being * sent to the converter. */ SnippetList 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_; /// 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)) {} PreviewLoader::~PreviewLoader() {} PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const { return pimpl_->preview(latex_snippet); } PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const { return pimpl_->status(latex_snippet); } void PreviewLoader::add(string const & latex_snippet) const { pimpl_->add(latex_snippet); } void PreviewLoader::remove(string const & latex_snippet) const { pimpl_->remove(latex_snippet); } void PreviewLoader::startLoading() const { pimpl_->startLoading(); } boost::signals::connection PreviewLoader::connect(slot_type const & slot) const { return pimpl_->imageReady.connect(slot); } void PreviewLoader::emitSignal(PreviewImage const & pimage) const { pimpl_->imageReady(pimage); } Buffer const & PreviewLoader::buffer() const { return pimpl_->buffer(); } } // namespace graphics } // namespace lyx // The details of the Impl // ======================= namespace lyx { namespace graphics { PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b) : parent_(p), buffer_(b), font_scaling_factor_(0.0) { font_scaling_factor_ = 0.01 * lyxrc.dpi * lyxrc.zoom * support::strToDbl(lyxrc.preview_scale_factor); lyxerr[Debug::GRAPHICS] << "The font scaling factor is " << font_scaling_factor_ << endl; if (!pconverter_) pconverter_ = defaultConverter(); } PreviewLoader::Impl::~Impl() { InProgressProcesses::iterator ipit = in_progress_.begin(); InProgressProcesses::iterator ipend = in_progress_.end(); for (; ipit != ipend; ++ipit) { ipit->second.stop(); } } PreviewImage const * PreviewLoader::Impl::preview(string const & latex_snippet) const { Cache::const_iterator it = cache_.find(latex_snippet); return (it == cache_.end()) ? 0 : it->second.get(); } namespace { typedef SnippetConversion::SnippetList SnippetList; class FindSnippet : public std::unary_function<InProgressProcess, bool> { public: FindSnippet(string const & s) : snippet_(s) {} bool operator()(InProgressProcess const & process) const { return ! (process.second.findSnippet(snippet_).empty()); } 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 Ready; SnippetList::const_iterator pit = pending_.begin(); SnippetList::const_iterator pend = pending_.end(); pit = find(pit, pend, latex_snippet); if (pit != pend) return InQueue; 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 NotFound; } void PreviewLoader::Impl::add(string const & latex_snippet) { if (!pconverter_ || status(latex_snippet) != NotFound) return; string const snippet = support::trim(latex_snippet); if (snippet.empty()) return; lyxerr[Debug::GRAPHICS] << "adding snippet:\n" << snippet << endl; pending_.push_back(snippet); } namespace { class EraseSnippet { public: EraseSnippet(string const & s) : snippet_(s) {} void operator()(InProgressProcess & process) { process.second.eraseSnippet(snippet_); } 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); SnippetList::iterator pit = pending_.begin(); SnippetList::iterator pend = pending_.end(); pending_.erase(std::remove(pit, pend, latex_snippet), pend); InProgressProcesses::iterator ipit = in_progress_.begin(); InProgressProcesses::iterator ipend = in_progress_.end(); std::for_each(ipit, ipend, EraseSnippet(latex_snippet)); while (ipit != ipend) { InProgressProcesses::iterator curr = ipit++; if (curr->second.empty()) in_progress_.erase(curr); } } void PreviewLoader::Impl::startLoading() { if (pending_.empty() || !pconverter_) return; // Only start the process off after the buffer is loaded from file. if (!buffer_.fully_loaded()) return; lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()" << endl; // As used by the LaTeX file and by the resulting image files string const directory = buffer_.temppath(); string const filename_base(unique_filename(directory)); // Create an SnippetConversion instance to place in the map of all // such processes if it starts correctly. SnippetConversion inprogress(filename_base, pconverter_, pending_); // clear pending_, so we're ready to start afresh. pending_.clear(); // Initiate the conversion from LaTeX to bitmap images files. support::Forkedcall::SignalTypePtr convert_ptr(new support::Forkedcall::SignalType); convert_ptr->connect(bind(&Impl::finishedGenerating, this, _1, _2)); // Store the generation process in a list of all such processes inprogress.start(buffer_, lyx_gui::hexname(LColor::preview), lyx_gui::hexname(LColor::background), font_scaling_factor_, convert_ptr); in_progress_[inprogress.pid()] = inprogress; } void PreviewLoader::Impl::finishedGenerating(pid_t pid, int retval) { // Paranoia check! InProgressProcesses::iterator git = in_progress_.find(pid); if (git == in_progress_.end()) { lyxerr << "PreviewLoader::finishedGenerating(): unable to find " "data for PID " << pid << endl; return; } string const command = git->second.command(); string const status = retval > 0 ? "failed" : "succeeded"; lyxerr[Debug::GRAPHICS] << "PreviewLoader::finishedInProgress(" << retval << "): processing " << status << " for " << command << endl; if (retval > 0) return; // Read the metrics file, if it exists git->second.setAscentFractions(); std::list<PreviewImagePtr> newimages; // Add these newly generated bitmap files to the cache and // start loading them into LyX. vector<SnippetConversion::Entry>::const_iterator it = git->second.begin(); vector<SnippetConversion::Entry>::const_iterator end = git->second.end(); for (; it != end; ++it) { string const & snip = it->latex; string const & file = it->filename; double af = it->ascentfraction; PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af)); cache_[snip] = ptr; newimages.push_back(ptr); } // Remove the item from the list of still-executing processes. in_progress_.erase(git); // Tell the outside world std::list<PreviewImagePtr>::const_reverse_iterator nit = newimages.rbegin(); std::list<PreviewImagePtr>::const_reverse_iterator nend = newimages.rend(); for (; nit != nend; ++nit) { imageReady(*nit->get()); } } Converter const * defaultConverter() { string const from = "lyxpreview"; typedef vector<string> FmtList; typedef lyx::graphics::Cache GCache; FmtList const loadableFormats = GCache::get().loadableFormats(); FmtList::const_iterator it = loadableFormats.begin(); FmtList::const_iterator const end = loadableFormats.end(); for (; it != end; ++it) { string const to = *it; 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; } } // namespace graphics } // namespace lyx namespace { string const unique_filename(string const bufferpath) { static int theCounter = 0; string const filename = convert<string>(theCounter++) + "lyxpreview"; return support::AddName(bufferpath, filename); } } // namespace anon// -*- C++ -*- /** * \file SnippetConversion.h * This file is part of LyX, the document processor. * Licence details can be found in the file COPYING. * * \author Andreas Vox * * Full author contact details are available in file CREDITS. * * lyx::graphics::SnippetConversion converts latex snippets to bitmap * files. * snippets are collected one by one. Then, on start converting, these * are dumped to file and processed, converting each snippet to a * separate bitmap image file. */ #ifndef SNIPPETCONVERSION_H #define SNIPPETCONVERSION_H #include "converter.h" #include "support/forkedcall.h" #include <boost/utility.hpp> #include <boost/scoped_ptr.hpp> #include <boost/signal.hpp> #include <boost/bind.hpp> #include <sstream> #include <fstream> namespace lyx { namespace graphics { /// Converts a set of LaTeX snippets to graphic files using yxpreview2bitmap class SnippetConversion { public: /// Holds one snippet and its associated graphics file struct Entry { std::string latex; std::string filename; double ascentfraction; }; // A list of all snippets to be converted to previews typedef std::list<std::string> SnippetList; /// SnippetConversion() : pid_(0) {} /// SnippetConversion(std::string const & filename_base, Converter const * converter, SnippetList const & pending); /// Dynamically forget about a snippet void eraseSnippet(std::string const & snippet_); /// return start of entry list std::vector<Entry>::const_iterator begin() const; /// return end of entry list std::vector<Entry>::const_iterator end() const; /// is the entry list empty? bool empty() const; /// how many entries are in the list? int size() const; /// Find a snippet in the entry list. Return its associated graphics filename std::string const findSnippet(std::string const & snippet_) const; /// Start the conversion with a forked call void start(Buffer const & buffer, std::string const & hex_foreground, std::string const & hex_background, double font_scaling_factor_, lyx::support::Forkedcall::SignalTypePtr convert_ptr); /// Start the conversion with a execvp call int startAndWait(Buffer const & buffer, std::string const & hex_foreground, std::string const & hex_background, double font_scaling_factor_); /// Remove any files left lying around and kill the forked process. void stop(); /// Read the metrics file and set ascentfractions in entries void setAscentFractions(); /// The external command which gets forked std::string const & command() const { return command_; } /// The PID of the forked process pid_t pid() const { return pid_; } private: /// void dumpPreamble(std::ostream &, Buffer const & buffer) const; /// void dumpData(std::ostream &, std::vector<Entry> const &) const; /// std::string prepareCommand(Buffer const & buffer, std::string const & hex_foreground, std::string const & hex_background, double font_scaling_factor_); /// std::string command_; /// pid_t pid_; /// std::string filename_base; /// std::string metrics_file; /// std::vector<Entry> snippets; Converter const * converter_; }; } // namespace graphics } // namespace lyx #endif // SNIPPETCONVERSION_H// -*- C++ -*- /** * \file SnippetConversion.C * This file is part of LyX, the document processor. * Licence details can be found in the file COPYING. * * \author Andreas Vox * * Full author contact details are available in file CREDITS. * * lyx::graphics::SnippetConversion converts latex snippets to bitmap * files. * snippets are collected one by one. Then, on start converting, these * are dumped to file and processed, converting each snippet to a * separate bitmap image file. */ #include "SnippetConversion.h" #include "buffer.h" #include "debug.h" #include "insetiterator.h" #include "lyxrc.h" #include "paragraph.h" #include "insets/inset.h" #include "support/filetools.h" #include "support/forkedcontr.h" #include "support/lyxlib.h" #include <boost/utility.hpp> #include <boost/scoped_ptr.hpp> #include <boost/signal.hpp> namespace support = lyx::support; using std::vector; using std::string; using std::ostream; using std::ostringstream; using std::ofstream; using std::ifstream; using std::endl; // some helpers: namespace { typedef vector<lyx::graphics::SnippetConversion::Entry> BitmapFileList; typedef lyx::graphics::SnippetConversion::SnippetList SnippetList; typedef lyx::graphics::SnippetConversion::Entry Entry; class IncrementedFileName { public: IncrementedFileName(string const & to_format, string const & filename_base) : to_format_(to_format), base_(filename_base), counter_(1) {} Entry const operator()(string const & snippet) { ostringstream os; os << base_ << counter_++ << '.' << to_format_; Entry result; result.filename = os.str(); result.latex = snippet; return result; } private: string const & to_format_; string const & base_; int counter_; }; class FindFirst : public std::unary_function<Entry, bool> { public: FindFirst(string const & comp) : comp_(comp) {} bool operator()(Entry const & sp) const { return sp.latex == comp_; } private: string const comp_; }; } // namespace anon namespace lyx { namespace graphics { SnippetConversion::SnippetConversion(string const & filename_base, Converter const * converter, SnippetList const & pending) : pid_(0), filename_base(filename_base), metrics_file(filename_base + ".metrics"), snippets(pending.size()), converter_(converter) { SnippetList::const_iterator pit = pending.begin(); SnippetList::const_iterator pend = pending.end(); BitmapFileList::iterator sit = snippets.begin(); std::transform(pit, pend, sit, IncrementedFileName(converter->to, filename_base)); } bool SnippetConversion::empty() const { return snippets.empty(); } int SnippetConversion::size() const { return snippets.size(); } BitmapFileList::const_iterator SnippetConversion::begin() const { return snippets.begin(); } BitmapFileList::const_iterator SnippetConversion::end() const { return snippets.end(); } string const SnippetConversion::findSnippet(string const & snippet_) const { BitmapFileList::const_iterator beg = snippets.begin(); BitmapFileList::const_iterator end = snippets.end(); BitmapFileList::const_iterator res = find_if(beg, end, FindFirst(snippet_)); if (res != end) { return res-> filename; } else { return ""; } } void SnippetConversion::eraseSnippet(string const & snippet_) { BitmapFileList::iterator it = snippets.begin(); BitmapFileList::iterator end = snippets.end(); it = find_if(it, end, FindFirst(snippet_)); if (it != end) snippets.erase(it, it+1); } string SnippetConversion::prepareCommand(Buffer const & buffer, string const & hex_foreground, string const & hex_background, double font_scaling_factor_) { // Output the LaTeX file. string const latexfile = filename_base + ".tex"; lyxerr << "preview snippets in " << latexfile << endl; ofstream of(latexfile.c_str()); if (!of) { lyxerr[Debug::GRAPHICS] << "SnippetConversion::start()\n" << "Unable to create LaTeX file\n" << latexfile << endl; return ""; } lyxerr << "writing preview snippets now" << endl; of << "\\batchmode\n"; dumpPreamble(of, buffer); of << "\n\\begin{document}\n"; dumpData(of, snippets); of << "\n\\end{document}\n"; of.close(); lyxerr << "done writing preview snippets" << endl; // The conversion command. ostringstream cs; cs << converter_->command << ' ' << converter_->to << ' ' << latexfile << ' ' << int(font_scaling_factor_) << ' ' << hex_foreground << ' ' << hex_background; lyxerr << "preview command is " << cs.str() << endl; return support::LibScriptSearch(cs.str()); } void SnippetConversion::start(Buffer const & buffer, string const & hex_foreground, string const & hex_background, double font_scaling_factor, support::Forkedcall::SignalTypePtr convert_ptr) { command_ = prepareCommand(buffer, hex_foreground, hex_background, font_scaling_factor); support::Forkedcall call; int ret = call.startscript(command_, convert_ptr); if (ret != 0) { lyxerr[Debug::GRAPHICS] << "SnippetConversion::start()\n" << "Unable to start process\n" << command_ << endl; return; } pid_ = call.pid(); } int SnippetConversion::startAndWait(Buffer const & buffer, string const & hex_foreground, string const & hex_background, double font_scaling_factor) { command_ = prepareCommand(buffer, hex_foreground, hex_background, font_scaling_factor); support::Forkedcall call; int ret = call.startscript(support::ForkedProcess::Wait, command_); if (ret != 0) { lyxerr[Debug::GRAPHICS] << "SnippetConversion::startAndWait()\n" << "Unable to start process\n" << command_ << endl; } else { pid_ = call.pid(); } return ret; } void SnippetConversion::stop() { if (!metrics_file.empty()) support::unlink(metrics_file); BitmapFileList::const_iterator vit = snippets.begin(); BitmapFileList::const_iterator vend = snippets.end(); for (; vit != vend; ++vit) { if (!vit->filename.empty()) support::unlink(vit->filename); } if (pid_) support::ForkedcallsController::get().kill(pid_, 0); } void SnippetConversion::dumpPreamble(ostream & os, Buffer const & buffer) const { // Why on earth is Buffer::makeLaTeXFile a non-const method? Buffer & tmp = const_cast<Buffer &>(buffer); // Dump the preamble only. OutputParams runparams; runparams.flavor = OutputParams::LATEX; runparams.nice = true; runparams.moving_arg = true; runparams.free_spacing = true; tmp.makeLaTeXFile(os, buffer.filePath(), runparams, true, false); // FIXME! This is a HACK! The proper fix is to control the 'true' // passed to WriteStream below: // int InsetFormula::latex(Buffer const &, ostream & os, // OutputParams const & runparams) const // { // WriteStream wi(os, runparams.moving_arg, true); // par_->write(wi); // return wi.line(); // } os << "\n" << "\\def\\lyxlock{}\n" << "\n"; // Loop over the insets in the buffer and dump all the math-macros. InsetBase & inset = buffer.inset(); InsetIterator it = inset_iterator_begin(inset); InsetIterator const end = inset_iterator_end(inset); for (; it != end; ++it) if (it->lyxCode() == InsetBase::MATHMACRO_CODE) it->latex(buffer, os, runparams); // 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,delayed,dvips,showlabels,lyx]{preview}\n" << "\n"; } void SnippetConversion::dumpData(ostream & os, BitmapFileList const & vec) const { if (vec.empty()) return; BitmapFileList::const_iterator it = vec.begin(); BitmapFileList::const_iterator end = vec.end(); for (; it != end; ++it) { os << "\\begin{preview}\n" << it->latex << "\n\\end{preview}\n\n"; } } void SnippetConversion::setAscentFractions() { BitmapFileList::iterator it = snippets.begin(); BitmapFileList::iterator end = snippets.end(); bool error = false; ifstream in(metrics_file.c_str()); if (!in.good()) { lyxerr[Debug::GRAPHICS] << "setAscentFractions(" << metrics_file << ")\n" << "Unable to open file!" << endl; } else { int snippet_counter = 1; while (!in.eof() && it != end) { string snippet; int id; double ascent_fraction; in >> snippet >> id >> ascent_fraction; if (!in.good()) // eof after all break; error = snippet != "Snippet"; if (error) break; error = id != snippet_counter; if (error) break; it->ascentfraction = ascent_fraction; ++snippet_counter; ++it; } } // If all else fails, then the images will have equal ascents and // descents. while (it != end) { it->ascentfraction = 0.5; ++it; } if (error) { lyxerr[Debug::GRAPHICS] << "setAscentFractions(" << metrics_file << ")\n" << "Error reading file!\n" << endl; } } } // namespace } // lyx::graphics