/* This file is part of * ====================================================== * * LyX, The Document Processor * * Copyright 1995-2002 the LyX Team. * * \author Baruch Even * \author Herbert Voss * ====================================================== */ /* Known BUGS: * If the image is from the clipart, and the document is moved to another directory, the user is screwed. Need a way to handle it. This amounts to a problem of when to use relative or absolute file paths 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. [Note that browseRelFile in helper_funcs.* provides a file name which is relative if it is at reference path (here puffer path) level or below, and an absolute path if the file name is not a `natural' relative file name. In any case, MakeAbsPath(filename, buf->filePath()) is guaranteed to provide the correct absolute path. This is what is done know for include insets. Feel free to ask me -- JMarc 14/01/2002] * 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. This needs to be fixed in the src/converter.C file [ This is presumed to be fixed, needs testing.] * 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. TODO Before initial production release: * Replace insetfig everywhere * Search for comments of the form // INSET_GRAPHICS: remove this when InsetFig is thrown. 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. * What advanced features the users want to do? Implement them in a non latex dependent way, but a logical way. LyX should translate it to latex or any other fitting format. * Add a way to roll the image file into the file format. * When loading, if the image is not found in the expected place, try to find it in the clipart, or in the same directory with the image. * Keep a tab on the image file, if it changes, update the lyx view. * The image choosing dialog could show thumbnails of the image formats it knows of, thus selection based on the image instead of based on filename. * Add support for the 'picins' package. * Add support for the 'picinpar' package. * Improve support for 'subfigure' - Allow to set the various options that are possible. */ /* NOTES: * Fileformat: * Current version is 1 (inset file format version), when changing it * it should be changed in the Write() function when writing in one place * and when reading one should change the version check and the error message. * The filename is kept in the lyx file in a relative way, so as to allow * moving the document file and its images with no problem. * * * Conversions: * 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 * PS ps2pdf * JPG/PNG direct * PDF direct * others PNG */ #include #ifdef __GNUG__ #pragma implementation #endif #include "insets/insetgraphics.h" #include "insets/insetgraphicsParams.h" #include "graphics/GraphicsCache.h" #include "graphics/GraphicsCacheItem.h" #include "LyXView.h" #include "buffer.h" #include "BufferView.h" #include "converter.h" #include "Painter.h" #include "lyx_gui_misc.h" #include "lyxtext.h" #include "lyxrc.h" #include "font.h" #include "debug.h" #include "gettext.h" #include "frontends/Dialogs.h" #include "frontends/Alert.h" #include "frontends/controllers/helper_funcs.h" #include "frontends/support/LyXImage.h" #include "support/FileInfo.h" #include "support/filetools.h" #include "support/lyxlib.h" #include "support/lyxmanip.h" #include "support/lyxalgo.h" #include #include extern string system_tempdir; using std::ifstream; using std::ostream; using std::endl; using std::max; using std::vector; /////////////////////////////////////////////////////////////////////////// int const VersionNumber = 1; /////////////////////////////////////////////////////////////////////////// // This function is a utility function // ... that should be with ChangeExtension ... inline string const RemoveExtension(string const & filename) { return ChangeExtension(filename, string()); } namespace { string const unique_id() { static unsigned int seed = 1000; ostringstream ost; ost << "graph" << ++seed; // Needed if we use lyxstring. return ost.str().c_str(); } } // namespace anon // Initialize only those variables that do not have a constructor. InsetGraphics::InsetGraphics() : cacheHandle(0), imageLoaded(false), graphic_label(unique_id()) {} InsetGraphics::InsetGraphics(InsetGraphics const & ig, bool same_id) : Inset(), SigC::Object() , cacheHandle(ig.cacheHandle) , imageLoaded(ig.imageLoaded) , graphic_label(unique_id()) { setParams(ig.getParams()); if (same_id) id_ = ig.id_; } InsetGraphics::~InsetGraphics() { // Emits the hide signal to the dialog connected (if any) hideDialog(); } string const InsetGraphics::statusMessage() const { string msg; if (cacheHandle.get()) { switch (cacheHandle->getImageStatus()) { case GraphicsCacheItem::UnknownError: msg = _("Unknown Error"); break; case GraphicsCacheItem::Loading: msg = _("Loading..."); break; case GraphicsCacheItem::ErrorReading: msg = _("Error reading"); break; case GraphicsCacheItem::Converting: msg = _("Converting Image"); break; case GraphicsCacheItem::ErrorConverting: msg = _("Error converting"); break; case GraphicsCacheItem::Loaded: // No message to write. break; } } return msg; } int InsetGraphics::ascent(BufferView *, LyXFont const &) const { LyXImage * pixmap = 0; if (cacheHandle.get() && (pixmap = cacheHandle->getImage())) return pixmap->getHeight(); else return 50; } int InsetGraphics::descent(BufferView *, LyXFont const &) const { // this is not true if viewport is used and clip is not. return 0; } int InsetGraphics::width(BufferView *, LyXFont const & font) const { LyXImage * pixmap = 0; if (cacheHandle.get() && (pixmap = cacheHandle->getImage())) return pixmap->getWidth(); else { int font_width = 0; 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 = max(font_width, msg_width); } return max(50, font_width + 15); } } void InsetGraphics::draw(BufferView * bv, LyXFont const & font, int baseline, float & x, bool) const { Painter & paint = bv->painter(); int ldescent = descent(bv, font); int lascent = ascent(bv, font); int lwidth = width(bv, font); // Make sure x is updated upon exit from this routine 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(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 (lyxrc.use_gui && params.display != InsetGraphicsParams::NONE && cacheHandle.get()) status = cacheHandle->getImageStatus(); // Check if the image is now ready. if (status == GraphicsCacheItem::Loaded) { imageLoaded = true; // Tell BufferView we need to be updated! bv->text->status(bv, LyXText::CHANGED_IN_DRAW); return; } paint.rectangle(old_x + 2, baseline - lascent, lwidth - 4, lascent + ldescent); // 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); 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(old_x + 8, baseline - 4, msg, msgFont); } } } void InsetGraphics::edit(BufferView *bv, int, int, unsigned int) { bv->owner()->getDialogs()->showGraphics(this); } void InsetGraphics::edit(BufferView * bv, bool) { edit(bv, 0, 0, 0); } Inset::EDITABLE InsetGraphics::editable() const { return IS_EDITABLE; } void InsetGraphics::write(Buffer const * buf, ostream & os) const { os << "Graphics FormatVersion " << VersionNumber << '\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) { 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 == "FormatVersion") { lex.next(); int version = lex.getInteger(); if (version > VersionNumber) lyxerr << "This document was created with a newer Graphics widget" ", You should use a newer version of LyX to read this" " file." << endl; // TODO: Possibly open up a dialog? } else { if (! params.Read(buf, lex, token)) lyxerr << "Unknown token, " << token << ", skipping." << endl; } } } // FormatVersion < 1.0 (LyX < 1.2) void InsetGraphics::readFigInset(Buffer const * buf, LyXLex & lex) { vector const oldUnits = getVectorFromString("pt,cm,in,p%,c%"); bool finished = false; // set the display default if (lyxrc.display_graphics == "mono") params.display = InsetGraphicsParams::MONOCHROME; else if (lyxrc.display_graphics == "gray") params.display = InsetGraphicsParams::GRAYSCALE; else if (lyxrc.display_graphics == "color") params.display = InsetGraphicsParams::COLOR; else params.display = InsetGraphicsParams::NONE; 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 = buf->filePath(); 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(); params.subcaption = true; } else if (token == "label") { if (lex.next()); // kept for backwards compability. Delete in 0.13.x } else if (token == "angle") { if (lex.next()) params.rotate = true; params.rotateAngle = lex.getFloat(); } else if (token == "size") { if (lex.next()) params.lyxwidth = LyXLength(lex.getString()+"pt"); if (lex.next()) params.lyxheight = LyXLength(lex.getString()+"pt"); } else if (token == "flags") { if (lex.next()) switch (lex.getInteger()) { case 1: params.display = InsetGraphicsParams::MONOCHROME; break; case 2: params.display = InsetGraphicsParams::GRAYSCALE; break; case 3: params.display = InsetGraphicsParams::COLOR; break; } } else if (token == "subfigure") { params.subcaption = true; } else if (token == "width") { if (lex.next()) { int i = lex.getInteger(); if (lex.next()) { if (i == 5) { params.scale = lex.getInteger(); params.size_type = InsetGraphicsParams::SCALE; } else { params.width = LyXLength(lex.getString()+oldUnits[i]); params.size_type = InsetGraphicsParams::WH; } } } } else if (token == "height") { if (lex.next()) { int i = lex.getInteger(); if (lex.next()) { params.height = LyXLength(lex.getString()+oldUnits[i]); params.size_type = InsetGraphicsParams::WH; } } } } } string const 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. ostringstream options; if (!params.bb.empty()) options << " bb=" << strip(params.bb) << ",\n"; if (params.draft) options << " draft,\n"; if (params.clip) options << " clip,\n"; if (params.size_type == InsetGraphicsParams::WH) { if (!params.width.zero()) options << " width=" << params.width.asLatexString() << ",\n"; if (!params.height.zero()) options << " height=" << params.height.asLatexString() << ",\n"; } else if (params.size_type == InsetGraphicsParams::SCALE) { if (params.scale > 0) options << " scale=" << double(params.scale)/100.0 << ",\n"; } if (params.keepAspectRatio) options << " keepaspectratio,\n"; // 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) && params.rotate) { options << " angle=" << params.rotateAngle << ",\n"; if (!params.rotateOrigin.empty()) { options << " origin=" << params.rotateOrigin[0]; if (contains(params.rotateOrigin,"Top")) options << 't'; else if (contains(params.rotateOrigin,"Bottom")) options << 'b'; else if (contains(params.rotateOrigin,"Baseline")) options << 'B'; options << ",\n"; } } if (!params.special.empty()) options << params.special << ",\n"; string opts = options.str().c_str(); return opts.substr(0,opts.size()-2); // delete last ",\n" } namespace { string decideOutputImageFormat(string const & suffix) { // lyxrc.pdf_mode means: // Are we creating a PDF or a PS file? // (Should actually mean, are we using latex or pdflatex). lyxerr[Debug::INFO] << "decideOutput::lyxrc.pdf_mode = " << lyxrc.pdf_mode << "\n"; if (lyxrc.pdf_mode) { if (contains(suffix,"ps") || suffix == "pdf") return "pdf"; else if (suffix == "jpg") return suffix; else return "png"; } // If it's postscript, we always do eps. lyxerr[Debug::INFO] << "decideOutput: we have PostScript mode\n"; if (suffix != "ps") return "eps"; else return "ps"; } } // Anon. namespace string const InsetGraphics::prepareFile(Buffer const *buf) const { // do_convert = Do we need to convert the file? // nice = Do we create a nice version? // This is used when exporting the latex file only. // if (!do_convert) // return original filename // if (!nice) // convert_place = temp directory // return new filename in temp directory // else // convert_place = original file directory // return original filename without the extension // // if it's a zipped one, than let LaTeX do the rest!!! if ((zippedFile(params.filename) && params.noUnzip) || buf->niceFile) { lyxerr[Debug::INFO] << "don't unzip file or export latex" << params.filename << endl; return params.filename; } string filename_ = params.filename; if (zippedFile(filename_)) filename_ = unzipFile(filename_); // now we have unzipped files // Get the extension (format) of the original file. // we handle it like a virtual one, so we can have // different extensions with the same type. string const extension = getExtFromContents(filename_); // are we usind latex ((e)ps) or pdflatex (pdf,jpg,png) string const image_target = decideOutputImageFormat(extension); if (extension == image_target) // :-) return filename_; // commented out to check if the "not exist"bug is fixed. // if (!IsFileReadable(filename_)) { // :-( // Alert::alert(_("File") + params.filename, // _("isn't readable or doesn't exists!")); // return filename_; // } string outfile; string const temp = AddName(buf->tmppath, filename_); outfile = RemoveExtension(temp); lyxerr[Debug::INFO] << "tempname = " << temp << "\n"; lyxerr[Debug::INFO] << "buf::tmppath = " << buf->tmppath << "\n"; lyxerr[Debug::INFO] << "filename_ = " << filename_ << "\n"; lyxerr[Debug::INFO] << "outfile = " << outfile << endl; converters.convert(buf, filename_, outfile, extension, image_target); return outfile; } int InsetGraphics::latex(Buffer const *buf, ostream & os, bool /*fragile*/, bool/*fs*/) const { // If there is no file specified, just output a message about it in // the latex output. if (params.filename.empty()) { os << "\\fbox{\\rule[-0.5in]{0pt}{1in}" << _("empty figure path") << "}\n"; return 1; // One end of line marker added to the stream. } // These variables collect all the latex code that should be before and // after the actual includegraphics command. string before; string after; // Do we want subcaptions? if (params.subcaption) { before += "\\subfigure[" + params.subcaptionText + "]{"; after = '}'; } // We never use the starred form, we use the "clip" option instead. before += "\\includegraphics"; // Write the options if there are any. string const opts = createLatexOptions(); if (!opts.empty()) { before += ("[%\n" + opts +']'); } // Make the filename relative to the lyx file // and remove the extension so the LaTeX will use whatever is // appropriate (when there are several versions in different formats) string const latex_str = before + '{' + prepareFile(buf) + '}' + after; os << latex_str; // Return how many newlines we issued. int const newlines = int(lyx::count(latex_str.begin(), latex_str.end(),'\n') + 1); // lyxerr << "includegraphics: " << newlines << " lines of text" // << endl; return newlines; } int InsetGraphics::ascii(Buffer const *, ostream & os, int) const { // No graphics in ascii output. Possible to use gifscii to convert // images to ascii approximation. // 1. Convert file to ascii using gifscii // 2. Read ascii output file and add it to the output stream. // at least we send the filename os << '<' << _("Graphicfile:") << params.filename << ">\n"; return 0; } int InsetGraphics::linuxdoc(Buffer const *, ostream &) const { // No graphics in LinuxDoc output. Should check how/what to add. return 0; } // 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 *, ostream & os) const { // 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 // easier to use. os << ""; return 0; } void InsetGraphics::validate(LaTeXFeatures & features) const { // If we have no image, we should not require anything. if (params.filename.empty()) return ; features.includeFile(graphic_label, RemoveExtension(params.filename)); features.require("graphicx"); if (params.subcaption) features.require("subfigure"); } // Update the inset after parameters changed (read from file or changed in // dialog. void InsetGraphics::updateInset() const { GraphicsCache & gc = GraphicsCache::getInstance(); boost::shared_ptr temp(0); // We do it this way so that in the face of some error, we will still // be in a valid state. InsetGraphicsParams::DisplayType local_display = params.display; if (local_display == InsetGraphicsParams::DEFAULT) { if (lyxrc.display_graphics == "mono") local_display = InsetGraphicsParams::MONOCHROME; else if (lyxrc.display_graphics == "gray") local_display = InsetGraphicsParams::GRAYSCALE; else if (lyxrc.display_graphics == "color") local_display = InsetGraphicsParams::COLOR; else local_display = InsetGraphicsParams::NONE; } if (!params.filename.empty() && lyxrc.use_gui && local_display != InsetGraphicsParams::NONE) { temp = gc.addFile(params.filename); } // Mark the image as unloaded so that it gets updated. imageLoaded = false; cacheHandle = temp; } bool InsetGraphics::setParams(InsetGraphicsParams const & p) { // If nothing is changed, just return and say so. if (params == p) return false; // Copy the new parameters. params = p; // Update the inset with the new parameters. updateInset(); // We have changed data, report it. return true; } InsetGraphicsParams InsetGraphics::getParams() const { return params; } Inset * InsetGraphics::clone(Buffer const &, bool same_id) const { return new InsetGraphics(*this, same_id); }