]> git.lyx.org Git - lyx.git/blob - src/insets/insetgraphics.C
Herbert's latest graphics patch.
[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 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
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         
377         while (lex.isOK() && !finished) {
378                 lex.next();
379
380                 string const token = lex.getString();
381                 lyxerr[Debug::INFO] << "Token: " << token << endl;
382                 
383                 if (token.empty())
384                         continue;
385                 else if (token == "\\end_inset") {
386                         finished = true;
387                 } else if (token == "file") {
388                         if (lex.next()) {
389                                 string const name = lex.getString();
390                                 string const path = buf->filePath();
391                                 params.filename = MakeAbsPath(name, path);
392                         }
393                 } else if (token == "extra") {
394                         if (lex.next());
395                         // kept for backwards compability. Delete in 0.13.x
396                 } else if (token == "subcaption") {
397                         if (lex.eatLine())
398                                 params.subcaptionText = lex.getString();
399                         params.subcaption = true;
400                 } else if (token == "label") {
401                         if (lex.next());
402                         // kept for backwards compability. Delete in 0.13.x
403                 } else if (token == "angle") {
404                         if (lex.next())
405                                 params.rotateAngle = lex.getFloat();
406                 } else if (token == "size") {
407                         if (lex.next())
408                                 params.lyxwidth = LyXLength(lex.getString()+"pt");
409                         if (lex.next())
410                                 params.lyxheight = LyXLength(lex.getString()+"pt");
411                 } else if (token == "flags") {
412                         InsetGraphicsParams::DisplayType tmp = InsetGraphicsParams::COLOR;
413                         if (lex.next())
414                                 switch (lex.getInteger()) {
415                                 case 1: tmp = InsetGraphicsParams::MONOCHROME; break;
416                                 case 2: tmp = InsetGraphicsParams::GRAYSCALE; break;
417                                 }
418                         params.display = tmp;
419                 } else if (token == "subfigure") {
420                         params.subcaption = true;
421                 } else if (token == "width") {
422                     if (lex.next()) {
423                         int i = lex.getInteger();
424                         if (lex.next()) {
425                             if (i == 5) {
426                                 params.scale = lex.getInteger();
427                                 params.size_type = InsetGraphicsParams::SCALE;
428                             } else {
429                                 params.width = LyXLength(lex.getString()+oldUnits[i]);
430                                 params.size_type = InsetGraphicsParams::WH;
431                             }
432                         }
433                     }
434                 } else if (token == "height") {
435                     if (lex.next()) {
436                         int i = lex.getInteger();
437                         if (lex.next()) {
438                             params.height = LyXLength(lex.getString()+oldUnits[i]);
439                             params.size_type = InsetGraphicsParams::WH;
440                         }
441                     }
442                 }
443         }
444 }
445
446 string const InsetGraphics::createLatexOptions() const
447 {
448         // Calculate the options part of the command, we must do it to a string
449         // stream since we might have a trailing comma that we would like to remove
450         // before writing it to the output stream.
451         ostringstream options;
452         if (!params.bb.empty())
453             options << "bb=" << strip(params.bb) << ',';
454         if (params.draft)
455             options << "%\n  draft,";
456         if (params.clip)
457             options << "%\n  clip,";
458         if (params.size_type == InsetGraphicsParams::WH) {
459             if (!params.width.zero())
460                 options << "%\n  width=" << params.width.asLatexString() << ',';
461             if (!params.height.zero())
462                 options << "%\n  height=" << params.height.asLatexString() << ',';
463         } else if (params.size_type == InsetGraphicsParams::SCALE) {
464             if (params.scale > 0)
465                 options << "%\n  scale=" << double(params.scale)/100.0 << ',';
466         }
467         if (params.keepAspectRatio)
468             options << "%\n  keepaspectratio,";
469         // Make sure it's not very close to zero, a float can be effectively
470         // zero but not exactly zero.
471         if (!lyx::float_equal(params.rotateAngle, 0, 0.001)) {
472             options << "%\n  angle=" << params.rotateAngle << ',';
473             if (!params.rotateOrigin.empty()) {
474                 options << "%\n  origin=";
475                 options << params.rotateOrigin[0];
476                 if (contains(params.rotateOrigin,"Top"))
477                     options << 't';
478                 else if (contains(params.rotateOrigin,"Bottom"))
479                     options << 'b';
480                 else if (contains(params.rotateOrigin,"Baseline"))
481                     options << 'B';
482                 options << ',';
483             }
484         }
485         if (!params.special.empty())
486             options << params.special << ',';
487         string opts = options.str().c_str();
488         opts = strip(opts, ',');
489         return opts;
490 }
491
492 namespace {
493 string decideOutputImageFormat(string const & suffix)
494 {
495         // lyxrc.pdf_mode means:
496         // Are we creating a PDF or a PS file?
497         // (Should actually mean, are we using latex or pdflatex).      
498         lyxerr << "decideOutput::lyxrc.pdf_mode = " << lyxrc.pdf_mode << "\n";
499         if (lyxrc.pdf_mode) {
500                 if (contains(suffix,"ps") || suffix == "pdf")
501                         return "pdf";
502                 else if (suffix == "jpg")
503                         return suffix;
504                 else
505                         return "png";
506         }
507         // If it's postscript, we always do eps.
508         lyxerr << "decideOutput: we have PostScript mode\n";
509         if (suffix != "ps")
510             return "eps";
511         else
512             return "ps";
513 }
514
515 } // Anon. namespace
516
517 string const InsetGraphics::prepareFile(Buffer const *buf) const
518 {
519         // do_convert = Do we need to convert the file?
520         // nice = Do we create a nice version?
521         //        This is used when exporting the latex file only.
522         // if (!do_convert)
523         //   return original filename
524         // if (!nice)
525         //   convert_place = temp directory
526         //   return new filename in temp directory
527         // else
528         //   convert_place = original file directory
529         //   return original filename without the extension
530         //
531         // Get the extension (format) of the original file.
532         // we handle it like a virtual one, so we can have
533         // different extensions with the same type
534         string const extension = getExtFromContents(params.filename);
535         // Are we creating a PDF or a PS file?
536         // (Should actually mean, are we usind latex or pdflatex).
537         string const image_target = decideOutputImageFormat(extension);
538         if (extension == image_target)
539                 return params.filename;
540         string outfile;
541         if (!buf->niceFile) {
542                 string const temp = AddName(buf->tmppath, params.filename);
543                 lyxerr << "temp = " << temp << "\n";
544                 outfile = RemoveExtension(temp);
545         } else {
546                 string const path = buf->filePath();
547                 string const relname = MakeRelPath(params.filename, path);
548                 outfile = RemoveExtension(relname);
549         }
550         lyxerr << "buf::tmppath = " << buf->tmppath << "\n";
551         lyxerr << "filename = " << params.filename << "\n";
552         lyxerr << "outfile = " << outfile << endl;
553         converters.convert(buf, params.filename, outfile, extension, image_target);
554         return outfile;
555 }
556
557
558 int InsetGraphics::latex(Buffer const *buf, ostream & os,
559                          bool /*fragile*/, bool/*fs*/) const
560 {
561         // If there is no file specified, just output a message about it in
562         // the latex output.
563         if (params.filename.empty()) {
564                 os  << "\\fbox{\\rule[-0.5in]{0pt}{1in}"
565                         << _("empty figure path") << "}\n";
566                 return 1; // One end of line marker added to the stream.
567         }
568         // Keep count of newlines that we issued.
569         int newlines = 0;
570         // This variables collect all the latex code that should be before and
571         // after the actual includegraphics command.
572         string before;
573         string after;
574         // Do we want subcaptions?
575         if (params.subcaption) {
576                 before += "\\subfigure[" + params.subcaptionText + "]{";
577                 after = '}' + after;
578         }
579         // We never use the starred form, we use the "clip" option instead.
580         os << before << "\\includegraphics";
581         // Write the options if there are any.
582         string const opts = createLatexOptions();
583         if (!opts.empty()) {
584                 os << "[%\n  " << opts << ']';
585         }
586         // Make the filename relative to the lyx file
587         // and remove the extension so the LaTeX will use whatever is
588         // appropriate (when there are several versions in different formats)
589         string const filename = prepareFile(buf);
590         os << '{' << filename << '}' << after;
591         // Return how many newlines we issued.
592         return newlines;
593 }
594
595
596 int InsetGraphics::ascii(Buffer const *, ostream & os, int) const
597 {
598         // No graphics in ascii output. Possible to use gifscii to convert
599         // images to ascii approximation.
600         // 1. Convert file to ascii using gifscii
601         // 2. Read ascii output file and add it to the output stream.
602         // at least we send the filename
603         os << '<' << _("Graphicfile:") << params.filename << ">\n";
604         return 0;
605 }
606
607
608 int InsetGraphics::linuxdoc(Buffer const *, ostream &) const
609 {
610         // No graphics in LinuxDoc output. Should check how/what to add.
611         return 0;
612 }
613
614
615 // For explanation on inserting graphics into DocBook checkout:
616 // http://linuxdoc.org/LDP/LDP-Author-Guide/inserting-pictures.html
617 // See also the docbook guide at http://www.docbook.org/
618 int InsetGraphics::docbook(Buffer const * buf, ostream & os) const
619 {
620         // Change the path to be relative to the main file.
621         string const buffer_dir = buf->filePath();
622         string filename = RemoveExtension(
623                 MakeRelPath(params.filename, buffer_dir));
624
625         if (suffixIs(filename, ".eps"))
626                 filename.erase(filename.length() - 4);
627
628         // In DocBook v5.0, the graphic tag will be eliminated from DocBook, will 
629         // need to switch to MediaObject. However, for now this is sufficient and 
630         // easier to use.
631         os << "<graphic fileref=\"" << filename << "\"></graphic>";
632         return 0;
633 }
634
635
636 void InsetGraphics::validate(LaTeXFeatures & features) const
637 {
638         // If we have no image, we should not require anything.
639         if (params.filename.empty())
640                 return ;
641
642         features.require("graphicx");
643
644         if (params.subcaption)
645                 features.require("subfigure");
646 }
647
648
649 // Update the inset after parameters changed (read from file or changed in
650 // dialog.
651 void InsetGraphics::updateInset() const
652 {
653         GraphicsCache & gc = GraphicsCache::getInstance();
654         boost::shared_ptr<GraphicsCacheItem> temp(0);
655
656         // We do it this way so that in the face of some error, we will still
657         // be in a valid state.
658         if (!params.filename.empty() && lyxrc.use_gui
659             && params.display != InsetGraphicsParams::NONE) {
660                 temp = gc.addFile(params.filename);
661         }
662
663         // Mark the image as unloaded so that it gets updated.
664         imageLoaded = false;
665
666         cacheHandle = temp;
667 }
668
669
670 bool InsetGraphics::setParams(InsetGraphicsParams const & p)
671 {
672         // If nothing is changed, just return and say so.
673         if (params == p)
674                 return false;
675
676         // Copy the new parameters.
677         params = p;
678
679         // Update the inset with the new parameters.
680         updateInset();
681
682         // We have changed data, report it.
683         return true;
684 }
685
686
687 InsetGraphicsParams InsetGraphics::getParams() const
688 {
689         return params;
690 }
691
692
693 Inset * InsetGraphics::clone(Buffer const &, bool same_id) const
694 {
695         return new InsetGraphics(*this, same_id);
696 }
697