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