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