]> git.lyx.org Git - lyx.git/blob - src/graphics/PreviewLoader.cpp
Rationalise includes
[lyx.git] / src / graphics / PreviewLoader.cpp
1 /**
2  * \file PreviewLoader.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "PreviewLoader.h"
14 #include "PreviewImage.h"
15 #include "GraphicsCache.h"
16
17 #include "Buffer.h"
18 #include "BufferParams.h"
19 #include "Converter.h"
20 #include "Encoding.h"
21 #include "Format.h"
22 #include "InsetIterator.h"
23 #include "LaTeXFeatures.h"
24 #include "LyXRC.h"
25 #include "output.h"
26 #include "OutputParams.h"
27 #include "TexRow.h"
28 #include "texstream.h"
29
30 #include "frontends/Application.h" // hexName
31
32 #include "insets/Inset.h"
33
34 #include "support/convert.h"
35 #include "support/debug.h"
36 #include "support/FileName.h"
37 #include "support/filetools.h"
38 #include "support/ForkedCalls.h"
39 #include "support/lstrings.h"
40
41 #include "support/bind.h"
42 #include "support/TempFile.h"
43
44 #include <fstream>
45 #include <iomanip>
46 #include <memory>
47 #include <sstream>
48
49 #include <QTimer>
50
51 using namespace std;
52 using namespace lyx::support;
53
54
55
56 namespace {
57
58 typedef pair<string, FileName> SnippetPair;
59
60 // A list of all snippets to be converted to previews
61 typedef list<string> PendingSnippets;
62
63 // Each item in the vector is a pair<snippet, image file name>.
64 typedef vector<SnippetPair> BitmapFile;
65
66
67 FileName const unique_tex_filename(FileName const & bufferpath)
68 {
69         TempFile tempfile(bufferpath, "lyxpreviewXXXXXX.tex");
70         tempfile.setAutoRemove(false);
71         return tempfile.name();
72 }
73
74
75 lyx::Converter const * setConverter(string const & from)
76 {
77         typedef vector<string> FmtList;
78         typedef lyx::graphics::Cache GCache;
79         FmtList const & loadableFormats = GCache::get().loadableFormats();
80         FmtList::const_iterator it = loadableFormats.begin();
81         FmtList::const_iterator const end = loadableFormats.end();
82
83         for (; it != end; ++it) {
84                 string const to = *it;
85                 if (from == to)
86                         continue;
87
88                 lyx::Converter const * ptr = lyx::theConverters().getConverter(from, to);
89                 if (ptr)
90                         return ptr;
91         }
92
93         // FIXME THREAD
94         static bool first = true;
95         if (first) {
96                 first = false;
97                 LYXERR0("PreviewLoader::startLoading()\n"
98                         << "No converter from \"" << from << "\" format has been defined.");
99         }
100         return 0;
101 }
102
103
104 void setAscentFractions(vector<double> & ascent_fractions,
105                         FileName const & metrics_file)
106 {
107         // If all else fails, then the images will have equal ascents and
108         // descents.
109         vector<double>::iterator it  = ascent_fractions.begin();
110         vector<double>::iterator end = ascent_fractions.end();
111         fill(it, end, 0.5);
112
113         ifstream in(metrics_file.toFilesystemEncoding().c_str());
114         if (!in.good()) {
115                 LYXERR(lyx::Debug::GRAPHICS, "setAscentFractions(" << metrics_file << ")\n"
116                         << "Unable to open file!");
117                 return;
118         }
119
120         bool error = false;
121
122         int snippet_counter = 1;
123         while (!in.eof() && it != end) {
124                 string snippet;
125                 int id;
126                 double ascent_fraction;
127
128                 in >> snippet >> id >> ascent_fraction;
129
130                 if (!in.good())
131                         // eof after all
132                         break;
133
134                 error = snippet != "Snippet";
135                 if (error)
136                         break;
137
138                 error = id != snippet_counter;
139                 if (error)
140                         break;
141
142                 *it = ascent_fraction;
143
144                 ++snippet_counter;
145                 ++it;
146         }
147
148         if (error) {
149                 LYXERR(lyx::Debug::GRAPHICS, "setAscentFractions(" << metrics_file << ")\n"
150                         << "Error reading file!\n");
151         }
152 }
153
154
155 class FindFirst
156 {
157 public:
158         FindFirst(string const & comp) : comp_(comp) {}
159         bool operator()(SnippetPair const & sp) const { return sp.first == comp_; }
160 private:
161         string const comp_;
162 };
163
164
165 /// Store info on a currently executing, forked process.
166 class InProgress {
167 public:
168         ///
169         InProgress() : pid(0) {}
170         ///
171         InProgress(string const & filename_base,
172                    PendingSnippets const & pending,
173                    string const & to_format);
174         /// Remove any files left lying around and kill the forked process.
175         void stop() const;
176
177         ///
178         pid_t pid;
179         ///
180         string command;
181         ///
182         FileName metrics_file;
183         ///
184         BitmapFile snippets;
185 };
186
187 typedef map<pid_t, InProgress>  InProgressProcesses;
188
189 typedef InProgressProcesses::value_type InProgressProcess;
190
191 } // namespace anon
192
193
194
195 namespace lyx {
196 namespace graphics {
197
198 class PreviewLoader::Impl : public boost::signals2::trackable {
199 public:
200         ///
201         Impl(PreviewLoader & p, Buffer const & b);
202         /// Stop any InProgress items still executing.
203         ~Impl();
204         ///
205         PreviewImage const * preview(string const & latex_snippet) const;
206         ///
207         PreviewLoader::Status status(string const & latex_snippet) const;
208         ///
209         void add(string const & latex_snippet);
210         ///
211         void remove(string const & latex_snippet);
212         /// \p wait whether to wait for the process to complete or, instead,
213         /// to do it in the background.
214         void startLoading(bool wait = false);
215         ///
216         void refreshPreviews();
217
218         /// Emit this signal when an image is ready for display.
219         boost::signals2::signal<void(PreviewImage const &)> imageReady;
220
221         Buffer const & buffer() const { return buffer_; }
222
223 private:
224         /// Called by the ForkedCall process that generated the bitmap files.
225         void finishedGenerating(pid_t, int);
226         ///
227         void dumpPreamble(otexstream &, OutputParams::FLAVOR) const;
228         ///
229         void dumpData(odocstream &, BitmapFile const &) const;
230
231         /** cache_ allows easy retrieval of already-generated images
232          *  using the LaTeX snippet as the identifier.
233          */
234         typedef std::shared_ptr<PreviewImage> PreviewImagePtr;
235         ///
236         typedef map<string, PreviewImagePtr> Cache;
237         ///
238         Cache cache_;
239
240         /** pending_ stores the LaTeX snippets in anticipation of them being
241          *  sent to the converter.
242          */
243         PendingSnippets pending_;
244
245         /** in_progress_ stores all forked processes so that we can proceed
246          *  thereafter.
247             The map uses the conversion commands as its identifiers.
248          */
249         InProgressProcesses in_progress_;
250
251         ///
252         PreviewLoader & parent_;
253         ///
254         Buffer const & buffer_;
255         ///
256         mutable int font_scaling_factor_;
257         ///
258         mutable int fg_color_;
259         ///
260         mutable int bg_color_;
261         ///
262         QTimer * delay_refresh_;
263         ///
264         bool finished_generating_;
265
266         /// We don't own this
267         static lyx::Converter const * pconverter_;
268 };
269
270
271 lyx::Converter const * PreviewLoader::Impl::pconverter_;
272
273
274 //
275 // The public interface, defined in PreviewLoader.h
276 //
277
278 PreviewLoader::PreviewLoader(Buffer const & b)
279         : pimpl_(new Impl(*this, b))
280 {}
281
282
283 PreviewLoader::~PreviewLoader()
284 {
285         delete pimpl_;
286 }
287
288
289 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
290 {
291         return pimpl_->preview(latex_snippet);
292 }
293
294
295 PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const
296 {
297         return pimpl_->status(latex_snippet);
298 }
299
300
301 void PreviewLoader::add(string const & latex_snippet) const
302 {
303         pimpl_->add(latex_snippet);
304 }
305
306
307 void PreviewLoader::remove(string const & latex_snippet) const
308 {
309         pimpl_->remove(latex_snippet);
310 }
311
312
313 void PreviewLoader::startLoading(bool wait) const
314 {
315         pimpl_->startLoading(wait);
316 }
317
318
319 void PreviewLoader::refreshPreviews()
320 {
321         pimpl_->refreshPreviews();
322 }
323
324
325 boost::signals2::connection PreviewLoader::connect(slot_type const & slot) const
326 {
327         return pimpl_->imageReady.connect(slot);
328 }
329
330
331 void PreviewLoader::emitSignal(PreviewImage const & pimage) const
332 {
333         pimpl_->imageReady(pimage);
334 }
335
336
337 Buffer const & PreviewLoader::buffer() const
338 {
339         return pimpl_->buffer();
340 }
341
342 } // namespace graphics
343 } // namespace lyx
344
345
346 // The details of the Impl
347 // =======================
348
349 namespace {
350
351 class IncrementedFileName {
352 public:
353         IncrementedFileName(string const & to_format,
354                             string const & filename_base)
355                 : to_format_(to_format), base_(filename_base), counter_(1)
356         {}
357
358         SnippetPair const operator()(string const & snippet)
359         {
360                 ostringstream os;
361                 os << base_ << counter_++ << '.' << to_format_;
362                 string const file = os.str();
363
364                 return make_pair(snippet, FileName(file));
365         }
366
367 private:
368         string const & to_format_;
369         string const & base_;
370         int counter_;
371 };
372
373
374 InProgress::InProgress(string const & filename_base,
375                        PendingSnippets const & pending,
376                        string const & to_format)
377         : pid(0),
378           metrics_file(filename_base + ".metrics"),
379           snippets(pending.size())
380 {
381         PendingSnippets::const_iterator pit  = pending.begin();
382         PendingSnippets::const_iterator pend = pending.end();
383         BitmapFile::iterator sit = snippets.begin();
384
385         transform(pit, pend, sit,
386                        IncrementedFileName(to_format, filename_base));
387 }
388
389
390 void InProgress::stop() const
391 {
392         if (pid)
393                 ForkedCallsController::kill(pid, 0);
394
395         if (!metrics_file.empty())
396                 metrics_file.removeFile();
397
398         BitmapFile::const_iterator vit  = snippets.begin();
399         BitmapFile::const_iterator vend = snippets.end();
400         for (; vit != vend; ++vit) {
401                 if (!vit->second.empty())
402                         vit->second.removeFile();
403         }
404 }
405
406 } // namespace anon
407
408
409 namespace lyx {
410 namespace graphics {
411
412 PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b)
413         : parent_(p), buffer_(b), finished_generating_(true)
414 {
415         font_scaling_factor_ = int(buffer_.fontScalingFactor());
416         if (theApp()) {
417                 fg_color_ = strtol(theApp()->hexName(foregroundColor()).c_str(), 0, 16);
418                 bg_color_ = strtol(theApp()->hexName(backgroundColor()).c_str(), 0, 16);
419         } else {
420                 fg_color_ = 0x0;
421                 bg_color_ = 0xffffff;
422         }
423         if (!pconverter_)
424                 pconverter_ = setConverter("lyxpreview");
425
426         delay_refresh_ = new QTimer(&parent_);
427         delay_refresh_->setSingleShot(true);
428         QObject::connect(delay_refresh_, SIGNAL(timeout()),
429                          &parent_, SLOT(refreshPreviews()));
430 }
431
432
433 PreviewLoader::Impl::~Impl()
434 {
435         delete delay_refresh_;
436
437         InProgressProcesses::iterator ipit  = in_progress_.begin();
438         InProgressProcesses::iterator ipend = in_progress_.end();
439
440         for (; ipit != ipend; ++ipit)
441                 ipit->second.stop();
442 }
443
444
445 PreviewImage const *
446 PreviewLoader::Impl::preview(string const & latex_snippet) const
447 {
448         int fs = int(buffer_.fontScalingFactor());
449         int fg = 0x0;
450         int bg = 0xffffff;
451         if (theApp()) {
452                 fg = strtol(theApp()->hexName(foregroundColor()).c_str(), 0, 16);
453                 bg = strtol(theApp()->hexName(backgroundColor()).c_str(), 0, 16);
454         }
455         if (font_scaling_factor_ != fs || fg_color_ != fg || bg_color_ != bg) {
456                 // Schedule refresh of all previews on zoom or color changes.
457                 // The previews are regenerated only after the zoom factor
458                 // has not been changed for about 1 second.
459                 fg_color_ = fg;
460                 bg_color_ = bg;
461                 delay_refresh_->start(1000);
462         }
463         // Don't try to access the cache until we are done.
464         if (delay_refresh_->isActive() || !finished_generating_)
465                 return 0;
466         Cache::const_iterator it = cache_.find(latex_snippet);
467         return (it == cache_.end()) ? 0 : it->second.get();
468 }
469
470
471 void PreviewLoader::Impl::refreshPreviews()
472 {
473         font_scaling_factor_ = int(buffer_.fontScalingFactor());
474         // Reschedule refresh until the previous process completed.
475         if (!finished_generating_) {
476                 delay_refresh_->start(1000);
477                 return;
478         }
479         Cache::const_iterator cit = cache_.begin();
480         Cache::const_iterator cend = cache_.end();
481         while (cit != cend)
482                 parent_.remove((cit++)->first);
483         finished_generating_ = false;
484         buffer_.updatePreviews();
485 }
486
487
488 namespace {
489
490 class FindSnippet {
491 public:
492         FindSnippet(string const & s) : snippet_(s) {}
493         bool operator()(InProgressProcess const & process) const
494         {
495                 BitmapFile const & snippets = process.second.snippets;
496                 BitmapFile::const_iterator beg  = snippets.begin();
497                 BitmapFile::const_iterator end = snippets.end();
498                 return find_if(beg, end, FindFirst(snippet_)) != end;
499         }
500
501 private:
502         string const snippet_;
503 };
504
505 } // namespace anon
506
507 PreviewLoader::Status
508 PreviewLoader::Impl::status(string const & latex_snippet) const
509 {
510         Cache::const_iterator cit = cache_.find(latex_snippet);
511         if (cit != cache_.end())
512                 return Ready;
513
514         PendingSnippets::const_iterator pit  = pending_.begin();
515         PendingSnippets::const_iterator pend = pending_.end();
516
517         pit = find(pit, pend, latex_snippet);
518         if (pit != pend)
519                 return InQueue;
520
521         InProgressProcesses::const_iterator ipit  = in_progress_.begin();
522         InProgressProcesses::const_iterator ipend = in_progress_.end();
523
524         ipit = find_if(ipit, ipend, FindSnippet(latex_snippet));
525         if (ipit != ipend)
526                 return Processing;
527
528         return NotFound;
529 }
530
531
532 void PreviewLoader::Impl::add(string const & latex_snippet)
533 {
534         if (!pconverter_ || status(latex_snippet) != NotFound)
535                 return;
536
537         string const snippet = trim(latex_snippet);
538         if (snippet.empty())
539                 return;
540
541         LYXERR(Debug::GRAPHICS, "adding snippet:\n" << snippet);
542
543         pending_.push_back(snippet);
544 }
545
546
547 namespace {
548
549 class EraseSnippet {
550 public:
551         EraseSnippet(string const & s) : snippet_(s) {}
552         void operator()(InProgressProcess & process)
553         {
554                 BitmapFile & snippets = process.second.snippets;
555                 BitmapFile::iterator it  = snippets.begin();
556                 BitmapFile::iterator end = snippets.end();
557
558                 it = find_if(it, end, FindFirst(snippet_));
559                 if (it != end)
560                         snippets.erase(it, it+1);
561         }
562
563 private:
564         string const & snippet_;
565 };
566
567 } // namespace anon
568
569
570 void PreviewLoader::Impl::remove(string const & latex_snippet)
571 {
572         Cache::iterator cit = cache_.find(latex_snippet);
573         if (cit != cache_.end())
574                 cache_.erase(cit);
575
576         PendingSnippets::iterator pit  = pending_.begin();
577         PendingSnippets::iterator pend = pending_.end();
578
579         pending_.erase(std::remove(pit, pend, latex_snippet), pend);
580
581         InProgressProcesses::iterator ipit  = in_progress_.begin();
582         InProgressProcesses::iterator ipend = in_progress_.end();
583
584         for_each(ipit, ipend, EraseSnippet(latex_snippet));
585
586         while (ipit != ipend) {
587                 InProgressProcesses::iterator curr = ipit++;
588                 if (curr->second.snippets.empty())
589                         in_progress_.erase(curr);
590         }
591 }
592
593
594 void PreviewLoader::Impl::startLoading(bool wait)
595 {
596         if (pending_.empty() || !pconverter_)
597                 return;
598
599         // Only start the process off after the buffer is loaded from file.
600         if (!buffer_.isFullyLoaded())
601                 return;
602
603         LYXERR(Debug::GRAPHICS, "PreviewLoader::startLoading()");
604
605         // As used by the LaTeX file and by the resulting image files
606         FileName const directory(buffer_.temppath());
607
608         FileName const latexfile = unique_tex_filename(directory);
609         string const filename_base = removeExtension(latexfile.absFileName());
610
611         // Create an InProgress instance to place in the map of all
612         // such processes if it starts correctly.
613         InProgress inprogress(filename_base, pending_, pconverter_->to());
614
615         // clear pending_, so we're ready to start afresh.
616         pending_.clear();
617
618         // Output the LaTeX file.
619         // we use the encoding of the buffer
620         Encoding const & enc = buffer_.params().encoding();
621         ofdocstream of;
622         try { of.reset(enc.iconvName()); }
623         catch (iconv_codecvt_facet_exception const & e) {
624                 LYXERR0("Caught iconv exception: " << e.what()
625                         << "\nUnable to create LaTeX file: " << latexfile);
626                 return;
627         }
628
629         otexstream os(of);
630         OutputParams runparams(&enc);
631         LaTeXFeatures features(buffer_, buffer_.params(), runparams);
632
633         if (!openFileWrite(of, latexfile))
634                 return;
635
636         if (!of) {
637                 LYXERR(Debug::GRAPHICS, "PreviewLoader::startLoading()\n"
638                                         << "Unable to create LaTeX file\n" << latexfile);
639                 return;
640         }
641         of << "\\batchmode\n";
642
643         // Set \jobname of previews to the document name (see bug 9627)
644         of << "\\def\\jobname{"
645            << from_utf8(changeExtension(buffer_.latexName(true), ""))
646            << "}\n";
647
648         LYXERR(Debug::LATEX, "Format = " << buffer_.params().getDefaultOutputFormat());
649         string latexparam = "";
650         bool docformat = !buffer_.params().default_output_format.empty()
651                         && buffer_.params().default_output_format != "default";
652         // Use LATEX flavor if the document does not specify a specific
653         // output format (see bug 9371).
654         OutputParams::FLAVOR flavor = docformat
655                                         ? buffer_.params().getOutputFlavor()
656                                         : OutputParams::LATEX;
657         if (buffer_.params().encoding().package() == Encoding::japanese) {
658                 latexparam = " --latex=platex";
659                 flavor = OutputParams::LATEX;
660         }
661         else if (buffer_.params().useNonTeXFonts) {
662                 if (flavor == OutputParams::LUATEX)
663                         latexparam = " --latex=lualatex";
664                 else {
665                         flavor = OutputParams::XETEX;
666                         latexparam = " --latex=xelatex";
667                 }
668         }
669         else {
670                 switch (flavor) {
671                         case OutputParams::PDFLATEX:
672                                 latexparam = " --latex=pdflatex";
673                                 break;
674                         case OutputParams::XETEX:
675                                 latexparam = " --latex=xelatex";
676                                 break;
677                         case OutputParams::LUATEX:
678                                 latexparam = " --latex=lualatex";
679                                 break;
680                         case OutputParams::DVILUATEX:
681                                 latexparam = " --latex=dvilualatex";
682                                 break;
683                         default:
684                                 flavor = OutputParams::LATEX;
685                 }
686         }
687         dumpPreamble(os, flavor);
688         // handle inputenc etc.
689         // I think, this is already hadled by dumpPreamble(): Kornel
690         // buffer_.params().writeEncodingPreamble(os, features);
691         of << "\n\\begin{document}\n";
692         dumpData(of, inprogress.snippets);
693         of << "\n\\end{document}\n";
694         of.close();
695         if (of.fail()) {
696                 LYXERR(Debug::GRAPHICS, "PreviewLoader::startLoading()\n"
697                                          << "File was not closed properly.");
698                 return;
699         }
700
701         // The conversion command.
702         ostringstream cs;
703         cs << pconverter_->command()
704            << " " << quoteName(latexfile.toFilesystemEncoding())
705            << " --dpi " << font_scaling_factor_;
706
707         // FIXME XHTML 
708         // The colors should be customizable.
709         if (!buffer_.isExporting()) {
710                 ColorCode const fg = PreviewLoader::foregroundColor();
711                 ColorCode const bg = PreviewLoader::backgroundColor();
712                 cs << " --fg " << theApp()->hexName(fg) 
713                    << " --bg " << theApp()->hexName(bg);
714         }
715
716         cs << latexparam;
717         if (buffer_.params().bibtex_command != "default")
718                 cs << " --bibtex=" << quoteName(buffer_.params().bibtex_command);
719         else if (buffer_.params().encoding().package() == Encoding::japanese)
720                 cs << " --bibtex=" << quoteName(lyxrc.jbibtex_command);
721         else
722                 cs << " --bibtex=" << quoteName(lyxrc.bibtex_command);
723         if (buffer_.params().bufferFormat() == "lilypond-book")
724                 cs << " --lilypond";
725
726         string const command = cs.str();
727
728         if (wait) {
729                 ForkedCall call(buffer_.filePath(), buffer_.layoutPos());
730                 int ret = call.startScript(ForkedProcess::Wait, command);
731                 // FIXME THREAD
732                 static int fake = (2^20) + 1;
733                 int pid = fake++;
734                 inprogress.pid = pid;
735                 inprogress.command = command;
736                 in_progress_[pid] = inprogress;
737                 finishedGenerating(pid, ret);
738                 return;
739         }
740
741         // Initiate the conversion from LaTeX to bitmap images files.
742         ForkedCall::SignalTypePtr
743                 convert_ptr(new ForkedCall::SignalType);
744         convert_ptr->connect(bind(&Impl::finishedGenerating, this, _1, _2));
745
746         ForkedCall call(buffer_.filePath());
747         int ret = call.startScript(command, convert_ptr);
748
749         if (ret != 0) {
750                 LYXERR(Debug::GRAPHICS, "PreviewLoader::startLoading()\n"
751                                         << "Unable to start process\n" << command);
752                 return;
753         }
754
755         // Store the generation process in a list of all such processes
756         inprogress.pid = call.pid();
757         inprogress.command = command;
758         in_progress_[inprogress.pid] = inprogress;
759 }
760
761
762 double PreviewLoader::displayPixelRatio() const
763 {
764         return buffer().params().display_pixel_ratio;
765 }
766
767 void PreviewLoader::Impl::finishedGenerating(pid_t pid, int retval)
768 {
769         // Paranoia check!
770         InProgressProcesses::iterator git = in_progress_.find(pid);
771         if (git == in_progress_.end()) {
772                 lyxerr << "PreviewLoader::finishedGenerating(): unable to find "
773                         "data for PID " << pid << endl;
774                 finished_generating_ = true;
775                 return;
776         }
777
778         string const command = git->second.command;
779         string const status = retval > 0 ? "failed" : "succeeded";
780         LYXERR(Debug::GRAPHICS, "PreviewLoader::finishedInProgress("
781                                 << retval << "): processing " << status
782                                 << " for " << command);
783         if (retval > 0) {
784                 in_progress_.erase(git);
785                 finished_generating_ = true;
786                 return;
787         }
788
789         // Read the metrics file, if it exists
790         vector<double> ascent_fractions(git->second.snippets.size());
791         setAscentFractions(ascent_fractions, git->second.metrics_file);
792
793         // Add these newly generated bitmap files to the cache and
794         // start loading them into LyX.
795         BitmapFile::const_iterator it  = git->second.snippets.begin();
796         BitmapFile::const_iterator end = git->second.snippets.end();
797
798         list<PreviewImagePtr> newimages;
799
800         int metrics_counter = 0;
801         for (; it != end; ++it, ++metrics_counter) {
802                 string const & snip = it->first;
803                 FileName const & file = it->second;
804                 double af = ascent_fractions[metrics_counter];
805
806                 // Add the image to the cache only if it's actually present
807                 // and not empty (an empty image is signaled by af < 0)
808                 if (af >= 0 && file.isReadableFile()) {
809                         PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af));
810                         cache_[snip] = ptr;
811
812                         newimages.push_back(ptr);
813                 }
814
815         }
816
817         // Remove the item from the list of still-executing processes.
818         in_progress_.erase(git);
819
820         // Tell the outside world
821         list<PreviewImagePtr>::const_reverse_iterator
822                 nit  = newimages.rbegin();
823         list<PreviewImagePtr>::const_reverse_iterator
824                 nend = newimages.rend();
825         for (; nit != nend; ++nit) {
826                 imageReady(*nit->get());
827         }
828         finished_generating_ = true;
829 }
830
831
832 void PreviewLoader::Impl::dumpPreamble(otexstream & os, OutputParams::FLAVOR flavor) const
833 {
834         // Dump the preamble only.
835         LYXERR(Debug::LATEX, "dumpPreamble, flavor == " << flavor);
836         OutputParams runparams(&buffer_.params().encoding());
837         runparams.flavor = flavor;
838         runparams.nice = true;
839         runparams.moving_arg = true;
840         runparams.free_spacing = true;
841         runparams.is_child = buffer_.parent();
842         buffer_.writeLaTeXSource(os, buffer_.filePath(), runparams, Buffer::OnlyPreamble);
843
844         // FIXME! This is a HACK! The proper fix is to control the 'true'
845         // passed to WriteStream below:
846         // int InsetMathNest::latex(Buffer const &, odocstream & os,
847         //                          OutputParams const & runparams) const
848         // {
849         //      WriteStream wi(os, runparams.moving_arg, true);
850         //      par_->write(wi);
851         //      return wi.line();
852         // }
853         os << "\n"
854            << "\\def\\lyxlock{}\n"
855            << "\n";
856
857         // All equation labels appear as "(#)" + preview.sty's rendering of
858         // the label name
859         if (lyxrc.preview_hashed_labels)
860                 os << "\\renewcommand{\\theequation}{\\#}\n";
861
862         // Use the preview style file to ensure that each snippet appears on a
863         // fresh page.
864         // Also support PDF output (automatically generated e.g. when
865         // \usepackage[pdftex]{hyperref} is used and XeTeX.
866         os << "\n"
867            << "\\usepackage[active,delayed,showlabels,lyx]{preview}\n"
868            << "\n";
869 }
870
871
872 void PreviewLoader::Impl::dumpData(odocstream & os,
873                                    BitmapFile const & vec) const
874 {
875         if (vec.empty())
876                 return;
877
878         BitmapFile::const_iterator it  = vec.begin();
879         BitmapFile::const_iterator end = vec.end();
880
881         for (; it != end; ++it) {
882                 // FIXME UNICODE
883                 os << "\\begin{preview}\n"
884                    << from_utf8(it->first)
885                    << "\n\\end{preview}\n\n";
886         }
887 }
888
889 } // namespace graphics
890 } // namespace lyx
891
892 #include "moc_PreviewLoader.cpp"