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