]> git.lyx.org Git - lyx.git/blob - src/insets/insetgraphics.C
Yes, yet another patch from Herbert!
[lyx.git] / src / insets / insetgraphics.C
1 /* This file is part of
2  * ====================================================== 
3  * 
4  *           LyX, The Document Processor
5  *       
6  *           Copyright 1995-2002 the LyX Team.
7  *           
8  * \author Baruch Even
9  * \author Herbert Voss <voss@lyx.org>
10  * ====================================================== */
11
12 /*
13 Known BUGS:
14     
15     * If the image is from the clipart, and the document is moved to another
16        directory, the user is screwed. Need a way to handle it.
17        This amounts to a problem of when to use relative or absolute file paths
18        We should probably use what the user asks to use... but when he chooses
19        by the file dialog we normally get an absolute path and this may not be 
20        what the user meant.
21        [Note that browseRelFile in helper_funcs.* provides a file name
22         which is relative if it is at reference path (here puffer path)
23         level or below, and an absolute path if the file name is not a
24         `natural' relative file name. In any case,
25             MakeAbsPath(filename, buf->filePath())
26         is guaranteed to provide the correct absolute path. This is what is
27         done know for include insets. Feel free to ask me -- JMarc
28         14/01/2002]
29         
30         * If we are trying to create a file in a read-only directory and there
31                 are graphics that need converting, the converting will fail because
32                 it is done in-place, into the same directory as the original image.
33                 This needs to be fixed in the src/converter.C file
34                 [ This is presumed to be fixed, needs testing.]
35
36         * We do not dither or resize the image in a WYSIWYM way, we load it at
37                 its original size and color, resizing is done in the final output,
38                 but not in the LyX window.
39
40         * EPS figures are not fully detected, they may have a lot of possible
41                 suffixes so we need to read the file and detect if it's EPS or not.
42                 [Implemented, need testing]
43                 
44 TODO Before initial production release:
45     * Replace insetfig everywhere
46         * Search for comments of the form
47             // INSET_GRAPHICS: remove this when InsetFig is thrown.
48           And act upon them. Make sure not to remove InsetFig code for the 
49                   1.2.0 release, only afterwards, after deployment shows InsetGraphics
50                   to be ok.
51         * What advanced features the users want to do?
52             Implement them in a non latex dependent way, but a logical way.
53             LyX should translate it to latex or any other fitting format.
54     * Add a way to roll the image file into the file format.
55     * When loading, if the image is not found in the expected place, try
56        to find it in the clipart, or in the same directory with the image.
57     * Keep a tab on the image file, if it changes, update the lyx view.
58         * The image choosing dialog could show thumbnails of the image formats
59           it knows of, thus selection based on the image instead of based on
60           filename.
61         * Add support for the 'picins' package.
62         * Add support for the 'picinpar' package.
63         * Improve support for 'subfigure' - Allow to set the various options
64                 that are possible.
65  */
66
67 /* NOTES:
68  * Fileformat:
69  * Current version is 1 (inset file format version), when changing it
70  * it should be changed in the Write() function when writing in one place
71  * and when reading one should change the version check and the error message.
72  * The filename is kept in  the lyx file in a relative way, so as to allow
73  * moving the document file and its images with no problem.
74  * 
75  *
76  * Conversions:
77  *   Postscript output means EPS figures.
78  *
79  *   PDF output is best done with PDF figures if it's a direct conversion
80  *   or PNG figures otherwise.
81  *      Image format
82  *      from        to
83  *      EPS         epstopdf
84  *      PS          ps2pdf
85  *      JPG/PNG     direct
86  *      PDF         direct
87  *      others      PNG
88  */
89
90 #include <config.h> 
91
92 #ifdef __GNUG__
93 #pragma implementation
94 #endif 
95
96 #include "insets/insetgraphics.h"
97 #include "insets/insetgraphicsParams.h"
98 #include "graphics/GraphicsCache.h"
99 #include "graphics/GraphicsCacheItem.h"
100
101 #include "frontends/Dialogs.h"
102 #include "LyXView.h"
103 #include "buffer.h"
104 #include "BufferView.h"
105 #include "converter.h"
106 #include "frontends/support/LyXImage.h"
107 #include "Painter.h"
108 #include "lyx_gui_misc.h"
109 #include "support/FileInfo.h"
110 #include "support/filetools.h"
111 #include "frontends/controllers/helper_funcs.h"
112 #include "support/lyxlib.h"
113 #include "lyxtext.h"
114 #include "lyxrc.h"
115 #include "font.h" // For the lyxfont class.
116 #include "fstream" // for ifstream in isEPS
117 #include <algorithm> // For the std::max
118 #include "support/lyxmanip.h"
119 #include "debug.h"
120 #include "gettext.h"
121
122 extern string system_tempdir;
123
124 using std::ifstream;
125 using std::ostream;
126 using std::endl;
127
128 ///////////////////////////////////////////////////////////////////////////
129 int const VersionNumber = 1;
130 ///////////////////////////////////////////////////////////////////////////
131
132 // This function is a utility function
133 // ... that should be with ChangeExtension ...
134 inline
135 string const RemoveExtension(string const & filename)
136 {
137         return ChangeExtension(filename, string());
138 }
139
140
141 // Initialize only those variables that do not have a constructor.
142 InsetGraphics::InsetGraphics()
143         : cacheHandle(0), imageLoaded(false)
144 {}
145
146
147 InsetGraphics::InsetGraphics(InsetGraphics const & ig, bool same_id)
148         : Inset(), SigC::Object()
149         , cacheHandle(ig.cacheHandle)
150         , imageLoaded(ig.imageLoaded)
151 {
152         setParams(ig.getParams());
153         if (same_id)
154                 id_ = ig.id_;
155 }
156
157
158 InsetGraphics::~InsetGraphics()
159 {
160         // Emits the hide signal to the dialog connected (if any)
161         hideDialog();
162 }
163
164
165 string const InsetGraphics::statusMessage() const
166 {
167         string msg;
168         if (cacheHandle.get()) {
169                 switch (cacheHandle->getImageStatus()) {
170                 case GraphicsCacheItem::UnknownError:
171                         msg = _("Unknown Error");
172                         break;
173                 case GraphicsCacheItem::Loading:
174                         msg = _("Loading...");
175                         break;
176                 case GraphicsCacheItem::ErrorReading:
177                         msg = _("Error reading");
178                         break;
179                 case GraphicsCacheItem::Converting:
180                         msg = _("Converting Image");
181                         break;
182                 case GraphicsCacheItem::ErrorConverting:
183                         msg = _("Error converting");
184                         break;
185                 case GraphicsCacheItem::Loaded:
186                         // No message to write.
187                         break;
188                 }
189         }
190         return msg;
191 }
192
193
194 int InsetGraphics::ascent(BufferView *, LyXFont const &) const
195 {
196         LyXImage * pixmap = 0;
197         if (cacheHandle.get() && (pixmap = cacheHandle->getImage()))
198                 return pixmap->getHeight();
199         else
200                 return 50;
201 }
202
203
204 int InsetGraphics::descent(BufferView *, LyXFont const &) const
205 {
206         // this is not true if viewport is used and clip is not.
207         return 0;
208 }
209
210
211 int InsetGraphics::width(BufferView *, LyXFont const & font) const
212 {
213         LyXImage * pixmap = 0;
214         
215         if (cacheHandle.get() && (pixmap = cacheHandle->getImage()))
216                 return pixmap->getWidth();
217         else {
218                 int font_width = 0;
219
220                 LyXFont msgFont(font);
221                 msgFont.setFamily(LyXFont::SANS_FAMILY);
222
223                 string const justname = OnlyFilename (params.filename);
224                 if (!justname.empty()) {
225                         msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
226                         font_width = lyxfont::width(justname, msgFont);
227                 }
228
229                 string const msg = statusMessage();
230                 if (!msg.empty()) {
231                         msgFont.setSize(LyXFont::SIZE_TINY);
232                         int const msg_width = lyxfont::width(msg, msgFont);
233                         font_width = std::max(font_width, msg_width);
234                 }
235                 
236                 return std::max(50, font_width + 15);
237         }
238 }
239
240
241 void InsetGraphics::draw(BufferView * bv, LyXFont const & font,
242                          int baseline, float & x, bool) const
243 {
244         Painter & paint = bv->painter();
245
246         int ldescent = descent(bv, font);
247         int lascent = ascent(bv, font);
248         int lwidth = width(bv, font);
249
250         // Make sure x is updated upon exit from this routine
251         int old_x = int(x);
252         x += lwidth;
253
254         // This will draw the graphics. If the graphics has not been loaded yet,
255         // we draw just a rectangle.
256         if (imageLoaded) {
257
258                 paint.image(old_x + 2, baseline - lascent,
259                             lwidth - 4, lascent + ldescent,
260                             cacheHandle->getImage());
261         } else {        
262                 // Get the image status, default to unknown error.
263                 GraphicsCacheItem::ImageStatus status = GraphicsCacheItem::UnknownError;
264                 if (lyxrc.use_gui && params.display != InsetGraphicsParams::NONE &&
265                     cacheHandle.get())
266                         status = cacheHandle->getImageStatus();
267                 // Check if the image is now ready.
268                 if (status == GraphicsCacheItem::Loaded) {
269                         imageLoaded = true;
270                         // Tell BufferView we need to be updated!
271                         bv->text->status(bv, LyXText::CHANGED_IN_DRAW);
272                         return;
273                 }
274                 paint.rectangle(old_x + 2, baseline - lascent,
275                                 lwidth - 4,
276                                 lascent + ldescent);
277                 // Print the file name.
278                 LyXFont msgFont(font);
279                 msgFont.setFamily(LyXFont::SANS_FAMILY);
280                 string const justname = OnlyFilename (params.filename);
281                 if (!justname.empty()) {
282                         msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
283                         paint.text(old_x + 8, 
284                                    baseline - lyxfont::maxAscent(msgFont) - 4,
285                                    justname, msgFont);
286                 }
287                 // Print the message.
288                 string const msg = statusMessage();
289                 if (!msg.empty()) {
290                         msgFont.setSize(LyXFont::SIZE_TINY);
291                         paint.text(old_x + 8, baseline - 4, msg, msgFont);
292                 }
293         }
294 }
295
296
297 void InsetGraphics::edit(BufferView *bv, int, int, unsigned int)
298 {
299         bv->owner()->getDialogs()->showGraphics(this);
300 }
301
302
303 void InsetGraphics::edit(BufferView * bv, bool)
304 {
305         edit(bv, 0, 0, 0);
306 }
307
308
309 Inset::EDITABLE InsetGraphics::editable() const
310 {
311         return IS_EDITABLE;
312 }
313
314
315 void InsetGraphics::write(Buffer const * buf, ostream & os) const
316 {
317         os << "Graphics FormatVersion " << VersionNumber << '\n';
318         params.Write(buf, os);
319 }
320
321
322 void InsetGraphics::read(Buffer const * buf, LyXLex & lex)
323 {
324         string const token = lex.getString();
325
326         if (token == "Graphics")
327                 readInsetGraphics(buf, lex);
328         else if (token == "Figure") // Compatibility reading of FigInset figures.
329                 readFigInset(buf, lex);
330         else
331                 lyxerr[Debug::INFO] << "Not a Graphics or Figure inset!\n";
332
333         updateInset();
334 }
335
336 void InsetGraphics::readInsetGraphics(Buffer const * buf, LyXLex & lex)
337 {
338         bool finished = false;
339
340         while (lex.isOK() && !finished) {
341                 lex.next();
342
343                 string const token = lex.getString();
344                 lyxerr[Debug::INFO] << "Token: '" << token << '\'' 
345                                     << std::endl;
346
347                 if (token.empty()) {
348                         continue;
349                 } else if (token == "\\end_inset") {
350                         finished = true;
351                 } else if (token == "FormatVersion") {
352                         lex.next();
353                         int version = lex.getInteger();
354                         if (version > VersionNumber)
355                                 lyxerr
356                                 << "This document was created with a newer Graphics widget"
357                                 ", You should use a newer version of LyX to read this"
358                                 " file."
359                                 << std::endl;
360                         // TODO: Possibly open up a dialog?
361                 }
362                 else {
363                         if (! params.Read(buf, lex, token))
364                                 lyxerr << "Unknown token, " << token << ", skipping." 
365                                         << std::endl;
366                 }
367         }
368 }
369
370 // FormatVersion < 1.0  (LyX < 1.2)
371 void InsetGraphics::readFigInset(Buffer const * buf, LyXLex & lex)
372 {
373         std::vector<string> const oldUnits =
374                 getVectorFromString("pt,cm,in,p%,c%");
375         bool finished = false;
376         // set the display default      
377         if (lyxrc.display_graphics == "mono") 
378             params.display = InsetGraphicsParams::MONOCHROME;
379         else if (lyxrc.display_graphics == "gray") 
380             params.display = InsetGraphicsParams::GRAYSCALE;
381         else if (lyxrc.display_graphics == "color") 
382             params.display = InsetGraphicsParams::COLOR;
383         else
384             params.display = InsetGraphicsParams::NONE;
385         while (lex.isOK() && !finished) {
386                 lex.next();
387
388                 string const token = lex.getString();
389                 lyxerr[Debug::INFO] << "Token: " << token << endl;
390                 
391                 if (token.empty())
392                         continue;
393                 else if (token == "\\end_inset") {
394                         finished = true;
395                 } else if (token == "file") {
396                         if (lex.next()) {
397                                 string const name = lex.getString();
398                                 string const path = buf->filePath();
399                                 params.filename = MakeAbsPath(name, path);
400                         }
401                 } else if (token == "extra") {
402                         if (lex.next());
403                         // kept for backwards compability. Delete in 0.13.x
404                 } else if (token == "subcaption") {
405                         if (lex.eatLine())
406                                 params.subcaptionText = lex.getString();
407                         params.subcaption = true;
408                 } else if (token == "label") {
409                         if (lex.next());
410                         // kept for backwards compability. Delete in 0.13.x
411                 } else if (token == "angle") {
412                         if (lex.next())
413                                 params.rotateAngle = lex.getFloat();
414                 } else if (token == "size") {
415                         if (lex.next())
416                                 params.lyxwidth = LyXLength(lex.getString()+"pt");
417                         if (lex.next())
418                                 params.lyxheight = LyXLength(lex.getString()+"pt");
419                 } else if (token == "flags") {
420                         if (lex.next())
421                                 switch (lex.getInteger()) {
422                                 case 1: params.display = InsetGraphicsParams::MONOCHROME; 
423                                     break;
424                                 case 2: params.display = InsetGraphicsParams::GRAYSCALE; 
425                                     break;
426                                 case 3: params.display = InsetGraphicsParams::COLOR; 
427                                     break;
428                                 }
429                 } else if (token == "subfigure") {
430                         params.subcaption = true;
431                 } else if (token == "width") {
432                     if (lex.next()) {
433                         int i = lex.getInteger();
434                         if (lex.next()) {
435                             if (i == 5) {
436                                 params.scale = lex.getInteger();
437                                 params.size_type = InsetGraphicsParams::SCALE;
438                             } else {
439                                 params.width = LyXLength(lex.getString()+oldUnits[i]);
440                                 params.size_type = InsetGraphicsParams::WH;
441                             }
442                         }
443                     }
444                 } else if (token == "height") {
445                     if (lex.next()) {
446                         int i = lex.getInteger();
447                         if (lex.next()) {
448                             params.height = LyXLength(lex.getString()+oldUnits[i]);
449                             params.size_type = InsetGraphicsParams::WH;
450                         }
451                     }
452                 }
453         }
454 }
455
456 string const InsetGraphics::createLatexOptions() const
457 {
458         // Calculate the options part of the command, we must do it to a string
459         // stream since we might have a trailing comma that we would like to remove
460         // before writing it to the output stream.
461         ostringstream options;
462         if (!params.bb.empty())
463             options << "bb=" << strip(params.bb) << ',';
464         if (params.draft)
465             options << "%\n  draft,";
466         if (params.clip)
467             options << "%\n  clip,";
468         if (params.size_type == InsetGraphicsParams::WH) {
469             if (!params.width.zero())
470                 options << "%\n  width=" << params.width.asLatexString() << ',';
471             if (!params.height.zero())
472                 options << "%\n  height=" << params.height.asLatexString() << ',';
473         } else if (params.size_type == InsetGraphicsParams::SCALE) {
474             if (params.scale > 0)
475                 options << "%\n  scale=" << double(params.scale)/100.0 << ',';
476         }
477         if (params.keepAspectRatio)
478             options << "%\n  keepaspectratio,";
479         // Make sure it's not very close to zero, a float can be effectively
480         // zero but not exactly zero.
481         if (!lyx::float_equal(params.rotateAngle, 0, 0.001)) {
482             options << "%\n  angle=" << params.rotateAngle << ',';
483             if (!params.rotateOrigin.empty()) {
484                 options << "%\n  origin=";
485                 options << params.rotateOrigin[0];
486                 if (contains(params.rotateOrigin,"Top"))
487                     options << 't';
488                 else if (contains(params.rotateOrigin,"Bottom"))
489                     options << 'b';
490                 else if (contains(params.rotateOrigin,"Baseline"))
491                     options << 'B';
492                 options << ',';
493             }
494         }
495         if (!params.special.empty())
496             options << params.special << ',';
497         string opts = options.str().c_str();
498         opts = strip(opts, ',');
499         return opts;
500 }
501
502 namespace {
503 string decideOutputImageFormat(string const & suffix)
504 {
505         // lyxrc.pdf_mode means:
506         // Are we creating a PDF or a PS file?
507         // (Should actually mean, are we using latex or pdflatex).      
508         lyxerr << "decideOutput::lyxrc.pdf_mode = " << lyxrc.pdf_mode << "\n";
509         if (lyxrc.pdf_mode) {
510                 if (contains(suffix,"ps") || suffix == "pdf")
511                         return "pdf";
512                 else if (suffix == "jpg")
513                         return suffix;
514                 else
515                         return "png";
516         }
517         // If it's postscript, we always do eps.
518         lyxerr << "decideOutput: we have PostScript mode\n";
519         if (suffix != "ps")
520             return "eps";
521         else
522             return "ps";
523 }
524
525 } // Anon. namespace
526
527 string const InsetGraphics::prepareFile(Buffer const *buf) const
528 {
529         // do_convert = Do we need to convert the file?
530         // nice = Do we create a nice version?
531         //        This is used when exporting the latex file only.
532         // if (!do_convert)
533         //   return original filename
534         // if (!nice)
535         //   convert_place = temp directory
536         //   return new filename in temp directory
537         // else
538         //   convert_place = original file directory
539         //   return original filename without the extension
540         //
541         // Get the extension (format) of the original file.
542         // we handle it like a virtual one, so we can have
543         // different extensions with the same type.
544         // if it's a zipped one, than let LaTeX do the rest!!!
545         if (zippedFile(params.filename))        
546             return params.filename;
547         // now we have unzipped files
548         string const extension = getExtFromContents(params.filename);
549         // Are we creating a PDF or a PS file?
550         // (Should actually mean, are we usind latex or pdflatex).
551         string const image_target = decideOutputImageFormat(extension);
552         if (extension == image_target)
553                 return params.filename;
554         string outfile;
555         if (!buf->niceFile) {
556                 string const temp = AddName(buf->tmppath, params.filename);
557                 lyxerr << "temp = " << temp << "\n";
558                 outfile = RemoveExtension(temp);
559         } else {
560                 string const path = buf->filePath();
561                 string const relname = MakeRelPath(params.filename, path);
562                 outfile = RemoveExtension(relname);
563         }
564         lyxerr << "buf::tmppath = " << buf->tmppath << "\n";
565         lyxerr << "filename = " << params.filename << "\n";
566         lyxerr << "outfile = " << outfile << endl;
567         converters.convert(buf, params.filename, outfile, extension, image_target);
568         return outfile;
569 }
570
571
572 int InsetGraphics::latex(Buffer const *buf, ostream & os,
573                          bool /*fragile*/, bool/*fs*/) const
574 {
575         // If there is no file specified, just output a message about it in
576         // the latex output.
577         if (params.filename.empty()) {
578                 os  << "\\fbox{\\rule[-0.5in]{0pt}{1in}"
579                         << _("empty figure path") << "}\n";
580                 return 1; // One end of line marker added to the stream.
581         }
582         // Keep count of newlines that we issued.
583         int newlines = 0;
584         // This variables collect all the latex code that should be before and
585         // after the actual includegraphics command.
586         string before;
587         string after;
588         // Do we want subcaptions?
589         if (params.subcaption) {
590                 before += "\\subfigure[" + params.subcaptionText + "]{";
591                 after = '}' + after;
592         }
593         // We never use the starred form, we use the "clip" option instead.
594         os << before << "\\includegraphics";
595         // Write the options if there are any.
596         string const opts = createLatexOptions();
597         if (!opts.empty()) {
598                 os << "[%\n  " << opts << ']';
599         }
600         // Make the filename relative to the lyx file
601         // and remove the extension so the LaTeX will use whatever is
602         // appropriate (when there are several versions in different formats)
603         string const filename = prepareFile(buf);
604         os << '{' << filename << '}' << after;
605         // Return how many newlines we issued.
606         return newlines;
607 }
608
609
610 int InsetGraphics::ascii(Buffer const *, ostream & os, int) const
611 {
612         // No graphics in ascii output. Possible to use gifscii to convert
613         // images to ascii approximation.
614         // 1. Convert file to ascii using gifscii
615         // 2. Read ascii output file and add it to the output stream.
616         // at least we send the filename
617         os << '<' << _("Graphicfile:") << params.filename << ">\n";
618         return 0;
619 }
620
621
622 int InsetGraphics::linuxdoc(Buffer const *, ostream &) const
623 {
624         // No graphics in LinuxDoc output. Should check how/what to add.
625         return 0;
626 }
627
628
629 // For explanation on inserting graphics into DocBook checkout:
630 // http://linuxdoc.org/LDP/LDP-Author-Guide/inserting-pictures.html
631 // See also the docbook guide at http://www.docbook.org/
632 int InsetGraphics::docbook(Buffer const * buf, ostream & os) const
633 {
634         // Change the path to be relative to the main file.
635         string const buffer_dir = buf->filePath();
636         string filename = RemoveExtension(
637                 MakeRelPath(params.filename, buffer_dir));
638
639         if (suffixIs(filename, ".eps"))
640                 filename.erase(filename.length() - 4);
641
642         // In DocBook v5.0, the graphic tag will be eliminated from DocBook, will 
643         // need to switch to MediaObject. However, for now this is sufficient and 
644         // easier to use.
645         os << "<graphic fileref=\"" << filename << "\"></graphic>";
646         return 0;
647 }
648
649
650 void InsetGraphics::validate(LaTeXFeatures & features) const
651 {
652         // If we have no image, we should not require anything.
653         if (params.filename.empty())
654                 return ;
655
656         features.require("graphicx");
657
658         if (params.subcaption)
659                 features.require("subfigure");
660 }
661
662
663 // Update the inset after parameters changed (read from file or changed in
664 // dialog.
665 void InsetGraphics::updateInset() const
666 {
667         GraphicsCache & gc = GraphicsCache::getInstance();
668         boost::shared_ptr<GraphicsCacheItem> temp(0);
669
670         // We do it this way so that in the face of some error, we will still
671         // be in a valid state.
672         InsetGraphicsParams::DisplayType local_display = params.display;
673         if (local_display == InsetGraphicsParams::DEFAULT) {
674                 if (lyxrc.display_graphics == "mono")
675                         local_display = InsetGraphicsParams::MONOCHROME;
676                 else if (lyxrc.display_graphics == "gray")
677                         local_display = InsetGraphicsParams::GRAYSCALE;
678                 else if (lyxrc.display_graphics == "color")
679                         local_display = InsetGraphicsParams::COLOR;
680                 else
681                         local_display = InsetGraphicsParams::NONE;
682         }
683
684         if (!params.filename.empty() && lyxrc.use_gui &&
685             local_display != InsetGraphicsParams::NONE) {
686                 temp = gc.addFile(params.filename);
687         }
688
689         // Mark the image as unloaded so that it gets updated.
690         imageLoaded = false;
691
692         cacheHandle = temp;
693 }
694
695
696 bool InsetGraphics::setParams(InsetGraphicsParams const & p)
697 {
698         // If nothing is changed, just return and say so.
699         if (params == p)
700                 return false;
701
702         // Copy the new parameters.
703         params = p;
704
705         // Update the inset with the new parameters.
706         updateInset();
707
708         // We have changed data, report it.
709         return true;
710 }
711
712
713 InsetGraphicsParams InsetGraphics::getParams() const
714 {
715         return params;
716 }
717
718
719 Inset * InsetGraphics::clone(Buffer const &, bool same_id) const
720 {
721         return new InsetGraphics(*this, same_id);
722 }
723