]> git.lyx.org Git - lyx.git/blob - src/graphics/PreviewLoader.C
Herbert's patch to extract font size info from the LaTeX class file.
[lyx.git] / src / graphics / PreviewLoader.C
1 /*
2  *  \file PreviewLoader.C
3  *  Copyright 2002 the LyX Team
4  *  Read the file COPYING
5  *
6  * \author Angus Leeming <a.leeming@ic.ac.uk>
7  */
8
9 #include <config.h>
10
11 #ifdef __GNUG__
12 #pragma implementation
13 #endif
14
15 #include "PreviewLoader.h"
16 #include "PreviewImage.h"
17 #include "PreviewMetrics.h"
18
19 #include "buffer.h"
20 #include "bufferparams.h"
21 #include "converter.h"
22 #include "debug.h"
23 #include "lyxrc.h"
24 #include "lyxtextclasslist.h"
25 #include "LColor.h"
26
27 #include "insets/inset.h"
28
29 #include "frontends/lyx_gui.h" // hexname
30
31 #include "support/filetools.h"
32 #include "support/forkedcall.h"
33 #include "support/forkedcontr.h"
34 #include "support/lstrings.h"
35 #include "support/lyxlib.h"
36
37 #include <boost/bind.hpp>
38 #include <boost/signals/trackable.hpp>
39
40 #include <fstream>
41 #include <iomanip>
42 #include <map>
43
44 using std::endl;
45 using std::find;
46 using std::find_if;
47 using std::getline;
48 using std::make_pair;
49 using std::setfill;
50 using std::setw;
51 using std::sort;
52
53 using std::map;
54 using std::ifstream;
55 using std::ofstream;
56 using std::ostream;
57 using std::pair;
58 using std::vector;
59
60 namespace {
61
62 double getScalingFactor(Buffer &);
63
64 typedef pair<string, string> StrPair;
65
66 struct CompSecond {
67         bool operator()(StrPair const & lhs, StrPair const & rhs)
68         {
69                 return lhs.second < rhs.second;
70         }
71 };
72
73 struct FindFirst {
74         FindFirst(string const & comp) : comp_(comp) {}
75         bool operator()(StrPair const & sp)
76         {
77                 return sp.first < comp_;
78         }
79 private:
80         string const comp_;
81 };
82
83
84 string const unique_filename(string const bufferpath)
85 {
86         static int theCounter = 0;
87         string const filename = tostr(theCounter++) + "lyxpreview";
88         return AddName(bufferpath, filename);
89 }
90
91
92 /// Store info on a currently executing, forked process.
93 struct InProgress {
94         ///
95         InProgress() : pid(0) {}
96         ///
97         InProgress(string const & f, vector<StrPair> const & s)
98                 : pid(0), metrics_file(f), snippets(s)
99         {}
100         /// Remove any files left lying around and kill the forked process. 
101         void stop() const;
102
103         ///
104         pid_t pid;
105         ///
106         string metrics_file;
107         /// Each item in the vector is a pair<snippet, image file name>.
108         vector<StrPair> snippets;
109 };
110
111
112 } // namespace anon
113
114
115 namespace grfx {
116
117 struct PreviewLoader::Impl : public boost::signals::trackable {
118         ///
119         Impl(PreviewLoader & p, Buffer const & b);
120         /// Stop any InProgress items still executing.
121         ~Impl();
122         ///
123         PreviewImage const * preview(string const & latex_snippet) const;
124         ///
125         PreviewLoader::Status status(string const & latex_snippet) const;
126         ///
127         void add(string const & latex_snippet);
128         ///
129         void remove(string const & latex_snippet);
130         ///
131         void startLoading();
132
133 private:
134         ///
135         static bool haveConverter();
136         /// We don't own this
137         static Converter const * pconverter_;
138
139         /// Called by the Forkedcall process that generated the bitmap files.
140         void finishedGenerating(string const &, pid_t, int);
141         ///
142         void dumpPreamble(ostream &) const;
143         ///
144         void dumpData(ostream &, vector<StrPair> const &) const;
145         ///
146         double fontScalingFactor() const;
147
148         /** cache_ allows easy retrieval of already-generated images
149          *  using the LaTeX snippet as the identifier.
150          */
151         typedef boost::shared_ptr<PreviewImage> PreviewImagePtr;
152         ///
153         typedef map<string, PreviewImagePtr> Cache;
154         ///
155         Cache cache_;
156
157         /** pending_ stores the LaTeX snippets in anticipation of them being
158          *  sent to the converter.
159          */
160         vector<string> pending_;
161
162         /** in_progress_ stores all forked processes so that we can proceed
163          *  thereafter.
164             The map uses the conversion commands as its identifiers.
165          */
166         typedef map<string, InProgress> InProgressMap;
167         ///
168         InProgressMap in_progress_;
169
170         ///
171         PreviewLoader & parent_;
172         ///
173         Buffer const & buffer_;
174         ///
175         mutable double font_scaling_factor_;
176 };
177
178
179 Converter const * PreviewLoader::Impl::pconverter_;
180
181
182 // The public interface, defined in PreviewLoader.h
183 // ================================================
184 PreviewLoader::PreviewLoader(Buffer const & b)
185         : pimpl_(new Impl(*this, b))
186 {}
187
188
189 PreviewLoader::~PreviewLoader()
190 {}
191
192
193 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
194 {
195         return pimpl_->preview(latex_snippet);
196 }
197
198
199 PreviewLoader::Status PreviewLoader::status(string const & latex_snippet) const
200 {
201         return pimpl_->status(latex_snippet);
202 }
203
204
205 void PreviewLoader::add(string const & latex_snippet)
206 {
207         pimpl_->add(latex_snippet);
208 }
209
210
211 void PreviewLoader::remove(string const & latex_snippet)
212 {
213         pimpl_->remove(latex_snippet);
214 }
215
216
217 void PreviewLoader::startLoading()
218 {
219         pimpl_->startLoading();
220 }
221
222 } // namespace grfx
223
224
225 // The details of the Impl
226 // =======================
227
228 namespace {
229
230 void InProgress::stop() const
231 {
232         if (pid)
233                 ForkedcallsController::get().kill(pid, 0);
234
235         if (!metrics_file.empty())
236                 lyx::unlink(metrics_file);
237
238         vector<StrPair>::const_iterator vit  = snippets.begin();
239         vector<StrPair>::const_iterator vend = snippets.end();
240         for (; vit != vend; ++vit) {
241                 if (!vit->second.empty())
242                         lyx::unlink(vit->second);
243         }
244 }
245  
246 } // namespace anon
247
248
249 namespace grfx {
250
251 bool PreviewLoader::Impl::haveConverter()
252 {
253         if (pconverter_)
254                 return true;
255
256         string const from = "lyxpreview";
257
258         Formats::FormatList::const_iterator it  = formats.begin();
259         Formats::FormatList::const_iterator end = formats.end();
260
261         for (; it != end; ++it) {
262                 string const to = it->name();
263                 if (from == to)
264                         continue;
265                 Converter const * ptr = converters.getConverter(from, to);
266                 if (ptr) {
267                         pconverter_ = ptr;
268                         break;
269                 }
270         }
271
272         if (pconverter_)
273                 return true;
274
275         static bool first = true;
276         if (first) {
277                 first = false;
278                 lyxerr << "PreviewLoader::startLoading()\n"
279                        << "No converter from \"lyxpreview\" format has been "
280                         "defined."
281                        << endl;
282         }
283         
284         return false;
285 }
286
287
288 PreviewLoader::Impl::Impl(PreviewLoader & p, Buffer const & b)
289         : parent_(p), buffer_(b), font_scaling_factor_(0.0)
290 {}
291
292
293 PreviewLoader::Impl::~Impl()
294 {
295         InProgressMap::iterator ipit  = in_progress_.begin();
296         InProgressMap::iterator ipend = in_progress_.end();
297
298         for (; ipit != ipend; ++ipit) {
299                 ipit->second.stop();
300         }
301 }
302
303
304 PreviewImage const *
305 PreviewLoader::Impl::preview(string const & latex_snippet) const
306 {
307         Cache::const_iterator it = cache_.find(latex_snippet);
308         return (it == cache_.end()) ? 0 : it->second.get();
309 }
310
311
312 PreviewLoader::Status
313 PreviewLoader::Impl::status(string const & latex_snippet) const
314 {
315         Cache::const_iterator cit = cache_.find(latex_snippet);
316         if (cit != cache_.end())
317                 return Ready;
318
319         vector<string>::const_iterator vit  = pending_.begin();
320         vector<string>::const_iterator vend = pending_.end();
321         vit = find(vit, vend, latex_snippet);
322
323         if (vit != vend)
324                 return InQueue;
325
326         InProgressMap::const_iterator ipit  = in_progress_.begin();
327         InProgressMap::const_iterator ipend = in_progress_.end();
328
329         for (; ipit != ipend; ++ipit) {
330                 vector<StrPair> const & snippets = ipit->second.snippets;
331                 vector<StrPair>::const_iterator vit  = snippets.begin();
332                 vector<StrPair>::const_iterator vend = snippets.end();
333                 vit = find_if(vit, vend, FindFirst(latex_snippet));
334
335                 if (vit != vend)
336                         return Processing;
337         }
338
339         return NotFound;
340 }
341
342
343 void PreviewLoader::Impl::add(string const & latex_snippet)
344 {
345         if (!haveConverter())
346                 return;
347
348         if (status(latex_snippet) != NotFound)
349                 return;
350
351         pending_.push_back(latex_snippet);
352         sort(pending_.begin(), pending_.end());
353 }
354
355
356 void PreviewLoader::Impl::remove(string const & latex_snippet)
357 {
358         Cache::iterator cit = cache_.find(latex_snippet);
359         if (cit != cache_.end())
360                 cache_.erase(cit);
361
362         vector<string>::iterator vit  = pending_.begin();
363         vector<string>::iterator vend = pending_.end();
364         vit = find(vit, vend, latex_snippet);
365
366         if (vit != vend)
367                 pending_.erase(vit, vit+1);
368
369         InProgressMap::iterator ipit  = in_progress_.begin();
370         InProgressMap::iterator ipend = in_progress_.end();
371
372         while (ipit != ipend) {
373                 InProgressMap::iterator curr = ipit;
374                 ++ipit;
375
376                 vector<StrPair> & snippets = curr->second.snippets;
377                 vector<StrPair>::iterator vit  = snippets.begin();
378                 vector<StrPair>::iterator vend = snippets.end();
379                 vit = find_if(vit, vend, FindFirst(latex_snippet));
380
381                 if (vit != vend)
382                         snippets.erase(vit, vit+1);
383
384                 if (snippets.empty())
385                         in_progress_.erase(curr);
386         }
387 }
388
389
390 void PreviewLoader::Impl::startLoading()
391 {
392         if (pending_.empty())
393                 return;
394
395         if (!haveConverter())
396                 return;
397
398         lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()" << endl;
399
400         // As used by the LaTeX file and by the resulting image files
401         string const filename_base(unique_filename(buffer_.tmppath));
402
403         // Create an InProgress instance to place in the map of all
404         // such processes if it starts correctly.
405         vector<StrPair> snippets(pending_.size());
406         vector<StrPair>::iterator sit = snippets.begin();
407         vector<string>::const_iterator pit  = pending_.begin();
408         vector<string>::const_iterator pend = pending_.end();
409
410         int counter = 1; // file numbers start at 1
411         for (; pit != pend; ++pit, ++sit, ++counter) {
412                 ostringstream os;
413                 os << filename_base
414                    << setfill('0') << setw(3) << counter
415                    << "." << pconverter_->to;
416                 string const file = os.str().c_str();
417
418                 *sit = make_pair(*pit, file);
419         }
420
421         string const metrics_file = filename_base + ".metrics";
422         InProgress inprogress(metrics_file, snippets);
423
424         // clear pending_, so we're ready to start afresh.
425         pending_.clear();
426
427         // Output the LaTeX file.
428         string const latexfile = filename_base + ".tex";
429
430         ofstream of(latexfile.c_str());
431         dumpPreamble(of);
432         of << "\n\\begin{document}\n";
433         dumpData(of, inprogress.snippets);
434         of << "\n\\end{document}\n";
435         of.close();
436
437         // The conversion command.
438         double const scaling_factor = fontScalingFactor();
439         lyxerr[Debug::GRAPHICS] << "The font scaling factor is "
440                                 << scaling_factor << endl;
441         ostringstream cs;
442         cs << pconverter_->command << " " << latexfile << " "
443            << scaling_factor;
444
445         string const command = cs.str().c_str();
446
447         // Initiate the conversion from LaTeX to bitmap images files.
448         Forkedcall::SignalTypePtr convert_ptr;
449         convert_ptr.reset(new Forkedcall::SignalType);
450
451         convert_ptr->connect(
452                 boost::bind(&Impl::finishedGenerating, this, _1, _2, _3));
453
454         Forkedcall call;
455         int ret = call.startscript(command, convert_ptr);
456
457         if (ret != 0) {
458                 lyxerr[Debug::GRAPHICS] << "PreviewLoader::startLoading()\n"
459                                         << "Unable to start process \n"
460                                         << command << endl;
461                 return;
462         }
463
464         // Store the generation process in a list of all such processes
465         inprogress.pid = call.pid();
466         in_progress_[command] = inprogress;
467 }
468
469
470 void PreviewLoader::Impl::finishedGenerating(string const & command,
471                                              pid_t /* pid */, int retval)
472 {
473         string const status = retval > 0 ? "failed" : "succeeded";
474         lyxerr[Debug::GRAPHICS] << "PreviewLoader::finishedInProgress("
475                                 << retval << "): processing " << status
476                                 << " for " << command << endl;
477         if (retval > 0)
478                 return;
479
480         InProgressMap::iterator git = in_progress_.find(command);
481         if (git == in_progress_.end()) {
482                 lyxerr << "PreviewLoader::finishedGenerating(): unable to find "
483                         "data for\n"
484                        << command << "!" << endl;
485                 return;
486         }
487
488         // Read the metrics file, if it exists
489         PreviewMetrics metrics_file(git->second.metrics_file);
490
491         // Add these newly generated bitmap files to the cache and
492         // start loading them into LyX.
493         vector<StrPair>::const_iterator it  = git->second.snippets.begin();
494         vector<StrPair>::const_iterator end = git->second.snippets.end();
495
496         int metrics_counter = 0;
497         for (; it != end; ++it) {
498                 string const & snip = it->first;
499
500                 // Paranoia check
501                 Cache::const_iterator chk = cache_.find(snip);
502                 if (chk != cache_.end())
503                         continue;
504
505                 // Mental note (Angus, 4 July 2002, having just found out the
506                 // hard way :-().
507                 // We /must/ first add to the cache and then start the
508                 // image loading process.
509                 // If not, then outside functions can be called before by the
510                 // image loader before the PreviewImage/map is properly
511                 // constucted.
512                 // This can lead to all sorts of horribleness if such a
513                 // function attempts to access the cache's internals.
514                 string const & file = it->second;
515                 double af = metrics_file.ascent_fraction(metrics_counter++);
516                 PreviewImagePtr ptr(new PreviewImage(parent_, snip, file, af));
517
518                 cache_[snip] = ptr;
519
520                 ptr->startLoading();
521         }
522
523         // Remove the item from the list of still-executing processes.
524         in_progress_.erase(git);
525 }
526
527
528 void PreviewLoader::Impl::dumpPreamble(ostream & os) const
529 {
530         // Why on earth is Buffer::makeLaTeXFile a non-const method?
531         Buffer & tmp = const_cast<Buffer &>(buffer_);
532         // Dump the preamble only.
533         tmp.makeLaTeXFile(os, string(), true, false, true);
534
535         // Loop over the insets in the buffer and dump all the math-macros.
536         Buffer::inset_iterator it  = buffer_.inset_const_iterator_begin();
537         Buffer::inset_iterator end = buffer_.inset_const_iterator_end();
538
539         for (; it != end; ++it) {
540                 if ((*it)->lyxCode() == Inset::MATHMACRO_CODE) {
541                         (*it)->latex(&buffer_, os, true, true);
542                 }
543         }
544
545         // Use the preview style file to ensure that each snippet appears on a
546         // fresh page.
547         os << "\n"
548            << "\\usepackage[active,delayed,dvips,tightpage,showlabels]{preview}\n"
549            << "\n";
550
551         // This piece of PostScript magic ensures that the foreground and
552         // background colors are the same as the LyX screen.
553         string fg = lyx_gui::hexname(LColor::preview);
554         if (fg.empty()) fg = "000000";
555
556         string bg = lyx_gui::hexname(LColor::background);
557         if (bg.empty()) bg = "ffffff";
558
559         os << "\\AtBeginDocument{\\AtBeginDvi{%\n"
560            << "\\special{!userdict begin/bop-hook{//bop-hook exec\n"
561            << "<" << fg << bg << ">{255 div}forall setrgbcolor\n"
562            << "clippath fill setrgbcolor}bind def end}}}\n";
563 }
564
565
566 void PreviewLoader::Impl::dumpData(ostream & os,
567                                    vector<StrPair> const & vec) const
568 {
569         if (vec.empty())
570                 return;
571
572         vector<StrPair>::const_iterator it  = vec.begin();
573         vector<StrPair>::const_iterator end = vec.end();
574
575         for (; it != end; ++it) {
576                 os << "\\begin{preview}\n"
577                    << it->first
578                    << "\n\\end{preview}\n\n";
579         }
580 }
581
582
583 double PreviewLoader::Impl::fontScalingFactor() const
584 {
585         static double const lyxrc_preview_scale_factor = 0.9;
586
587         if (font_scaling_factor_ > 0.01)
588                 return font_scaling_factor_;
589
590         font_scaling_factor_ =  getScalingFactor(const_cast<Buffer &>(buffer_));
591         return font_scaling_factor_;
592 }
593
594
595 } // namespace grfx
596
597
598 namespace {
599
600 double getScalingFactor(Buffer & buffer)
601 {
602         static double const lyxrc_preview_scale_factor = 0.9;
603         double scale_factor = 0.01 * lyxrc.dpi * lyxrc.zoom *
604                 lyxrc_preview_scale_factor;
605
606         // Has the font size been set explicitly?
607         string const & fontsize = buffer.params.fontsize;
608         lyxerr[Debug::GRAPHICS] << "PreviewLoader::scaleToFitLyXView()\n"
609                                 << "font size is " << fontsize << endl;
610
611         if (isStrUnsignedInt(fontsize))
612                 return 10.0 * scale_factor / strToDbl(fontsize);
613
614         // No. We must extract it from the LaTeX class file.
615         LyXTextClass const & tclass = textclasslist[buffer.params.textclass];
616         string const textclass(tclass.latexname() + ".cls");
617         string const classfile(findtexfile(textclass, "cls"));
618
619         lyxerr[Debug::GRAPHICS] << "text class is " << textclass << '\n'
620                                 << "class file is " << classfile << endl;
621         
622         ifstream ifs(classfile.c_str());
623         if (!ifs.good()) {
624                 lyxerr[Debug::GRAPHICS] << "Unable to open class file!" << endl;
625                 return scale_factor;
626         }
627
628         string str;
629         double scaling = scale_factor;
630
631         while (ifs.good()) {
632                 getline(ifs, str);
633                 // To get the default font size, look for a line like
634                 // "\ExecuteOptions{letterpaper,10pt,oneside,onecolumn,final}"
635                 if (!prefixIs(str, "\\ExecuteOptions"))
636                         continue;
637
638                 str = split(str, '{');
639                 int count = 0;
640                 string tok = token(str, ',', count++);
641                 while (!isValidLength(tok) && !tok.empty())
642                         tok = token(str, ',', count++);
643
644                 if (!tok.empty()) {
645                         lyxerr[Debug::GRAPHICS]
646                                 << "Extracted default font size from "
647                                 "LaTeX class file successfully!" << endl;
648                         LyXLength fsize(tok);
649                         scaling *= 10.0 / fsize.value();
650                         break;
651                 }
652         }
653
654         return scaling;
655 }
656
657 } // namespace anon