]> git.lyx.org Git - lyx.git/blobdiff - src/insets/insetgraphics.C
2001-12-28 Lars Gullik Bj�nnes <larsbj@birdstep.com>
[lyx.git] / src / insets / insetgraphics.C
index 4f5c82d0494e230d4cab00b3c35e3e18a2d2e43d..b4f7f7cd5ea4255c4f5d571a557725c3b0ba559f 100644 (file)
@@ -8,11 +8,6 @@
  *           This file Copyright 2000 Baruch Even.
  * ====================================================== */
 
-/*
-How to use it for now:
-    * The lyxfunc 'graphics-insert' will insert this inset into the document.
-*/
-
 /*
 Major tasks:
        * Switch to convert the images in the background, this requires work on
@@ -20,7 +15,6 @@ Major tasks:
 
 Minor tasks:
     * Pop up a dialog if the widget version is higher than what we accept.
-       * Prepare code to read FigInset insets to upgrade upwards
        * Provide sed/awk/C code to downgrade from InsetGraphics to FigInset(?)
         
 */
@@ -34,10 +28,7 @@ Known BUGS:
        We should probably use what the user asks to use... but when he chooses
        by the file dialog we normally get an absolute path and this may not be 
        what the user meant.
-    * Bug in FileDlg class (src/filedlg.[hC]) when selecting a file and then
-        pressing ok, it counts as if no real selection done. Apparently
-        when choosing a file it doesn't update the select file input line.
-               
+
        * If we are trying to create a file in a read-only directory and there
                are graphics that need converting, the converting will fail because
                it is done in-place, into the same directory as the original image.
@@ -47,14 +38,22 @@ Known BUGS:
        * We do not dither or resize the image in a WYSIWYM way, we load it at
                its original size and color, resizing is done in the final output,
                but not in the LyX window.
+
+       * The scale option is only handled for the horizontal part, the vertical
+               part will not work. For now it is also shown only for horizontal
+               resizing on the form.
+
+       * EPS figures are not fully detected, they may have a lot of possible
+               suffixes so we need to read the file and detect if it's EPS or not.
+               [Implemented, need testing]
                
 TODO Before initial production release:
     * Replace insetfig everywhere
-        * Read it's file format
-        * Get created by all commands used to create figinset currently.
         * Search for comments of the form
             // INSET_GRAPHICS: remove this when InsetFig is thrown.
-          And act upon them.
+          And act upon them. Make sure not to remove InsetFig code for the 
+                 1.2.0 release, only afterwards, after deployment shows InsetGraphics
+                 to be ok.
  
 TODO Extended features:
  
@@ -76,6 +75,9 @@ TODO Extended features:
        * Add support for the 'picinpar' package.
        * Improve support for 'subfigure' - Allow to set the various options
                that are possible.
+       * Add resizing by percentage of image size (50%, 150%) - usefull for two
+               images of different size to be resized where they both should have
+               the same scale compared to each other.
  */
 
 /* NOTES:
@@ -112,9 +114,16 @@ TODO Extended features:
  * moving the document file and its images with no problem.
  *
  * Conversions:
- *  
- *  Apparently the PNG output is preferred over PDF images when doing PDF
- *  documents (i.e. prefer imagemagick eps2png over eps2pdf)
+ *   Postscript output means EPS figures.
+ *
+ *   PDF output is best done with PDF figures if it's a direct conversion
+ *   or PNG figures otherwise.
+ *     Image format
+ *     from        to
+ *     EPS         epstopdf
+ *     JPG/PNG     direct
+ *     PDF         direct
+ *     others      PNG
  */
 
 #include <config.h> 
@@ -142,6 +151,7 @@ TODO Extended features:
 #include "lyxtext.h"
 #include "lyxrc.h"
 #include "font.h" // For the lyxfont class.
+#include "fstream" // for ifstream in isEPS
 #include <algorithm> // For the std::max
 #include "support/lyxmanip.h"
 #include "debug.h"
@@ -149,9 +159,13 @@ TODO Extended features:
 
 extern string system_tempdir;
 
+using std::ifstream;
 using std::ostream;
+using std::endl;
+
 
 // This function is a utility function
+// ... that should be with ChangeExtension ...
 inline
 string const RemoveExtension(string const & filename)
 {
@@ -165,6 +179,17 @@ InsetGraphics::InsetGraphics()
 {}
 
 
+InsetGraphics::InsetGraphics(InsetGraphics const & ig, bool same_id)
+       : Inset(), SigC::Object()
+       , cacheHandle(ig.cacheHandle)
+       , imageLoaded(ig.imageLoaded)
+{
+       setParams(ig.getParams());
+       if (same_id)
+               id_ = ig.id_;
+}
+
+
 InsetGraphics::~InsetGraphics()
 {
        // Emits the hide signal to the dialog connected (if any)
@@ -229,11 +254,23 @@ int InsetGraphics::width(BufferView *, LyXFont const & font) const
        if (cacheHandle.get() && (pixmap = cacheHandle->getImage()))
                return pixmap->getWidth();
        else {
-               string const msg = statusMessage();
                int font_width = 0;
-               
-               if (!msg.empty())
-                       font_width = lyxfont::width(msg, font);
+
+               LyXFont msgFont(font);
+               msgFont.setFamily(LyXFont::SANS_FAMILY);
+
+               string const justname = OnlyFilename (params.filename);
+               if (!justname.empty()) {
+                       msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
+                       font_width = lyxfont::width(justname, msgFont);
+               }
+
+               string const msg = statusMessage();
+               if (!msg.empty()) {
+                       msgFont.setSize(LyXFont::SIZE_TINY);
+                       int const msg_width = lyxfont::width(msg, msgFont);
+                       font_width = std::max(font_width, msg_width);
+               }
                
                return std::max(50, font_width + 15);
        }
@@ -250,21 +287,23 @@ void InsetGraphics::draw(BufferView * bv, LyXFont const & font,
        int lwidth = width(bv, font);
 
        // Make sure x is updated upon exit from this routine
-       float old_x = x;
+       int old_x = int(x);
        x += lwidth;
 
        // This will draw the graphics. If the graphics has not been loaded yet,
        // we draw just a rectangle.
        if (imageLoaded) {
 
-               paint.image(int(old_x) + 2, baseline - lascent,
-                            lwidth - 4, lascent + ldescent,
-                                        cacheHandle->getImage());
+               paint.image(old_x + 2, baseline - lascent,
+                           lwidth - 4, lascent + ldescent,
+                           cacheHandle->getImage());
        } else {
                
                // Get the image status, default to unknown error.
                GraphicsCacheItem::ImageStatus status = GraphicsCacheItem::UnknownError;
-               if (cacheHandle.get())
+               if (lyxrc.display_graphics != "no" &&
+                   params.display != InsetGraphicsParams::NONE &&
+                   cacheHandle.get())
                        status = cacheHandle->getImageStatus();
                
                // Check if the image is now ready.
@@ -272,29 +311,31 @@ void InsetGraphics::draw(BufferView * bv, LyXFont const & font,
                        imageLoaded = true;
 
                        // Tell BufferView we need to be updated!
-                       bv->text->status = LyXText::CHANGED_IN_DRAW;
+                       bv->text->status(bv, LyXText::CHANGED_IN_DRAW);
                        return;
                }
 
-               string const msg = statusMessage();
-               
-               paint.rectangle(int(old_x) + 2, baseline - lascent,
+               paint.rectangle(old_x + 2, baseline - lascent,
                                lwidth - 4,
                                lascent + ldescent);
 
-               if (!msg.empty()) {
-                       // Print the message.
-                       LyXFont msgFont(font);
-                       msgFont.setFamily(LyXFont::SANS_FAMILY);
+               // Print the file name.
+               LyXFont msgFont(font);
+               msgFont.setFamily(LyXFont::SANS_FAMILY);
+
+               string const justname = OnlyFilename (params.filename);
+               if (!justname.empty()) {
                        msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
-                       string const justname = OnlyFilename (params.filename);
-                       paint.text(int(old_x) + 8, 
-                                       baseline - lyxfont::maxAscent(msgFont) - 4,
-                                   justname, msgFont);
+                       paint.text(old_x + 8, 
+                                  baseline - lyxfont::maxAscent(msgFont) - 4,
+                                  justname, msgFont);
+               }
 
+               // Print the message.
+               string const msg = statusMessage();
+               if (!msg.empty()) {
                        msgFont.setSize(LyXFont::SIZE_TINY);
-                       paint.text(int(old_x) + 8, baseline - 4, 
-                                       msg, msgFont);
+                       paint.text(old_x + 8, baseline - 4, msg, msgFont);
                }
        }
 }
@@ -306,6 +347,12 @@ void InsetGraphics::edit(BufferView *bv, int, int, unsigned int)
 }
 
 
+void InsetGraphics::edit(BufferView * bv, bool)
+{
+       edit(bv, 0, 0, 0);
+}
+
+
 Inset::EDITABLE InsetGraphics::editable() const
 {
        return IS_EDITABLE;
@@ -314,20 +361,34 @@ Inset::EDITABLE InsetGraphics::editable() const
 
 void InsetGraphics::write(Buffer const * buf, ostream & os) const
 {
-       os << "GRAPHICS FormatVersion 1\n";
+       os << "Graphics FormatVersion 1\n";
 
        params.Write(buf, os);
 }
 
 
 void InsetGraphics::read(Buffer const * buf, LyXLex & lex)
+{
+       string const token = lex.getString();
+
+       if (token == "Graphics")
+               readInsetGraphics(buf, lex);
+       else if (token == "Figure") // Compatibility reading of FigInset figures.
+               readFigInset(buf, lex);
+       else
+               lyxerr[Debug::INFO] << "Not a Graphics or Figure inset!\n";
+
+       updateInset();
+}
+
+void InsetGraphics::readInsetGraphics(Buffer const * buf, LyXLex & lex)
 {
        bool finished = false;
 
-       while (lex.IsOK() && !finished) {
+       while (lex.isOK() && !finished) {
                lex.next();
 
-               string const token = lex.GetString();
+               string const token = lex.getString();
                lyxerr[Debug::INFO] << "Token: '" << token << '\'' 
                                    << std::endl;
 
@@ -337,7 +398,7 @@ void InsetGraphics::read(Buffer const * buf, LyXLex & lex)
                        finished = true;
                } else if (token == "FormatVersion") {
                        lex.next();
-                       int version = lex.GetInteger();
+                       int version = lex.getInteger();
                        if (version > 1)
                                lyxerr
                                << "This document was created with a newer Graphics widget"
@@ -352,8 +413,76 @@ void InsetGraphics::read(Buffer const * buf, LyXLex & lex)
                                        << std::endl;
                }
        }
+}
 
-       updateInset();
+
+void InsetGraphics::readFigInset(Buffer const * buf, LyXLex & lex)
+{
+       bool finished = false;
+       
+       while (lex.isOK() && !finished) {
+               lex.next();
+
+               string const token = lex.getString();
+               lyxerr[Debug::INFO] << "Token: " << token << endl;
+               
+               if (token.empty())
+                       continue;
+               else if (token == "\\end_inset") {
+                       finished = true;
+               } else if (token == "file") {
+                       if (lex.next()) {
+                               string const name = lex.getString();
+                               string const path = OnlyPath(buf->fileName());
+                               params.filename = MakeAbsPath(name, path);
+                       }
+               } else if (token == "extra") {
+                       if (lex.next());
+                       // kept for backwards compability. Delete in 0.13.x
+               } else if (token == "subcaption") {
+                       if (lex.eatLine())
+                               params.subcaptionText = lex.getString();
+               } else if (token == "label") {
+                       if (lex.next());
+                       // kept for backwards compability. Delete in 0.13.x
+               } else if (token == "angle") {
+                       if (lex.next())
+                               params.rotateAngle = lex.getFloat();
+               } else if (token == "size") {
+                       // Size of image on screen is ignored in InsetGraphics, just eat
+                       // the input.
+                       if (lex.next()) {
+                               lex.getInteger();
+                       }
+                       if (lex.next()) {
+                               lex.getInteger();
+                       }
+               } else if (token == "flags") {
+                       InsetGraphicsParams::DisplayType tmp = InsetGraphicsParams::COLOR;
+                       if (lex.next())
+                               switch (lex.getInteger()) {
+                               case 1: tmp = InsetGraphicsParams::MONOCHROME; break;
+                               case 2: tmp = InsetGraphicsParams::GRAYSCALE; break;
+                               }
+                       params.display = tmp;
+               } else if (token == "subfigure") {
+                       params.subcaption = true;
+               } else if (token == "width") {
+                       if (lex.next()) {
+                               params.widthResize = static_cast<InsetGraphicsParams::Resize>(lex.getInteger());
+                       }
+                       if (lex.next()) {
+                               params.widthSize = lex.getFloat();
+                       }
+               } else if (token == "height") {
+                       if (lex.next()) {
+                               params.heightResize = static_cast<InsetGraphicsParams::Resize>(lex.getInteger());
+                       }
+                       if (lex.next()) {
+                               params.heightSize = lex.getFloat();
+                       }
+               }
+       }
 }
 
 
@@ -382,6 +511,8 @@ void formatResize(ostream & os, string const & key,
                os << key << '=' << size / 100 << "\\column" << key << ',';
                break;
 
+       case InsetGraphicsParams::SCALE:
+               os << "scale" << '=' << size/100 << ',';
        }
 }
 
@@ -394,12 +525,14 @@ InsetGraphics::createLatexOptions() const
        // Calculate the options part of the command, we must do it to a string
        // stream since we might have a trailing comma that we would like to remove
        // before writing it to the output stream.
-       std::ostringstream options;
+       ostringstream options;
 
        formatResize(options, "width", params.widthResize, params.widthSize);
        formatResize(options, "height", params.heightResize, params.heightSize);
 
-       if (params.rotateAngle != 0) {
+       // Make sure it's not very close to zero, a float can be effectively
+       // zero but not exactly zero.
+       if (!lyx::float_equal(params.rotateAngle, 0, 0.001)) {
                options << "angle="
                        << params.rotateAngle << ',';
        }
@@ -410,6 +543,89 @@ InsetGraphics::createLatexOptions() const
        return opts;
 }
 
+namespace {
+
+enum FileType {
+       EPS,
+       PNG,
+       JPEG,
+       GIF,
+       PDF,
+       UNKNOWN
+};
+
+bool isEPS(string const & filename)
+{
+       if (filename.empty() || !IsFileReadable(filename)) return false;
+
+       ifstream ifs(filename.c_str());
+
+       if (!ifs) return false; // Couldn't open file...
+
+       bool is_eps = false; // Have we recognized the file as EPS?
+       string to_find = "%!PS-Adobe-"; // The string we use to recognize
+       int const max_attempts = 500; // Maximum strings to read to attempt recognition
+       int count = 0; // Counter of attempts.
+       string str;
+       for (; count < max_attempts; ++count) {
+               if (ifs.eof()) {
+                       lyxerr[Debug::INFO] << "InsetGraphics (isEPS)"
+                               " End of file reached and it wasn't found to be EPS!" << endl;
+                       break;
+               }
+
+               ifs >> str;
+               if (str.find(to_find)) {
+                       is_eps = true;
+                       break;
+               }
+       }
+
+       return is_eps;
+}
+
+enum FileType classifyFileType(string const & filename, string const & suffix)
+{
+       if (suffix == "png")
+               return PNG;
+       else if (suffix == "jpg" || suffix == "jpeg")
+               return JPEG;
+       else if (suffix == "gif")
+               return GIF;
+       else if (suffix == "pdf")
+               return PDF;
+       else if (isEPS(filename))
+               return EPS;
+
+       return UNKNOWN;
+}
+
+string decideOutputImageFormat(string const & suffix, enum FileType type)
+{
+       // lyxrc.pdf_mode means:
+       // Are we creating a PDF or a PS file?
+       // (Should actually mean, are we using latex or pdflatex).
+       
+       if (lyxrc.pdf_mode) {
+               if (type == EPS || type == EPS || type == PDF)
+                       return "pdf";
+               else if (type == JPEG)
+                       return suffix;
+               else
+                       return "png";
+       }
+
+       // If it's postscript, we always do eps.
+       // There are many suffixes that are actually EPS (ask Garst for example)
+       // so we detect if it's an EPS by looking in the file, if it is, we return
+       // the same suffix of the file so it won't be converted.
+       if (type == EPS)
+               return suffix;
+       
+       return "eps";
+}
+
+} // Anon. namespace
 
 string const 
 InsetGraphics::prepareFile(Buffer const *buf) const
@@ -433,10 +649,11 @@ InsetGraphics::prepareFile(Buffer const *buf) const
        
        // Get the extension (format) of the original file.
        string const extension = GetExtension(params.filename);
+       FileType type = classifyFileType(params.filename, extension);
        
        // Are we creating a PDF or a PS file?
        // (Should actually mean, are we usind latex or pdflatex).
-       string const image_target = (lyxrc.pdf_mode ? "png" : "eps");
+       string const image_target = decideOutputImageFormat(extension, type);
 
        if (extension == image_target)
                return params.filename;
@@ -456,7 +673,7 @@ InsetGraphics::prepareFile(Buffer const *buf) const
                outfile = RemoveExtension(relname);
        }
 
-       converters.Convert(buf, params.filename, outfile, extension, image_target);
+       converters.convert(buf, params.filename, outfile, extension, image_target);
        
        return outfile;
 }
@@ -495,13 +712,6 @@ int InsetGraphics::latex(Buffer const *buf, ostream & os,
        string before;
        string after;
 
-       // If it's not an inline image, surround it with the centering paragraph.
-       if (! params.inlineFigure) {
-               before += "\n" "\\vspace{0.3cm}\n" "{\\par\\centering ";
-               after = " \\par}\n" "\\vspace{0.3cm}\n" + after;
-               newlines += 4;
-       }
-
        // Do we want subcaptions?
        if (params.subcaption) {
                before += "\\subfigure[" + params.subcaptionText + "]{";
@@ -551,11 +761,12 @@ int InsetGraphics::linuxdoc(Buffer const *, ostream &) const
 // For explanation on inserting graphics into DocBook checkout:
 // http://linuxdoc.org/LDP/LDP-Author-Guide/inserting-pictures.html
 // See also the docbook guide at http://www.docbook.org/
-int InsetGraphics::docBook(Buffer const * buf, ostream & os) const
+int InsetGraphics::docbook(Buffer const * buf, ostream & os) const
 {
        // Change the path to be relative to the main file.
        string const buffer_dir = OnlyPath(buf->fileName());
-       string const filename = RemoveExtension(MakeRelPath(params.filename, buffer_dir));
+       string const filename = RemoveExtension(
+                                  MakeRelPath(params.filename, buffer_dir));
 
        // In DocBook v5.0, the graphic tag will be eliminated from DocBook, will 
        // need to switch to MediaObject. However, for now this is sufficient and 
@@ -571,10 +782,10 @@ void InsetGraphics::validate(LaTeXFeatures & features) const
        if (params.filename.empty())
                return ;
 
-       features.graphicx = true;
+       features.require("graphicx");
 
        if (params.subcaption)
-               features.subfigure = true;
+               features.require("subfigure");
 }
 
 
@@ -587,7 +798,9 @@ void InsetGraphics::updateInset() const
 
        // We do it this way so that in the face of some error, we will still
        // be in a valid state.
-       if (!params.filename.empty()) {
+       if (!params.filename.empty() &&
+           lyxrc.display_graphics != "no" &&
+           params.display != InsetGraphicsParams::NONE) {
                temp = gc.addFile(params.filename);
        }
 
@@ -621,17 +834,7 @@ InsetGraphicsParams InsetGraphics::getParams() const
 }
 
 
-Inset * InsetGraphics::clone(Buffer const &) const
+Inset * InsetGraphics::clone(Buffer const &, bool same_id) const
 {
-#ifdef WITH_WARNINGS
-#warning use the copy constructor instead. (Lgb)
-#endif
-       InsetGraphics * newInset = new InsetGraphics;
-
-       newInset->cacheHandle = cacheHandle;
-       newInset->imageLoaded = imageLoaded;
-
-       newInset->setParams(getParams());
-
-       return newInset;
+       return new InsetGraphics(*this, same_id);
 }