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