1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 1995-2002 the LyX Team.
9 * \author Herbert Voss <voss@lyx.org>
10 * ====================================================== */
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
22 Note that browseRelFile in helper_funcs.* provides a file name
23 which is relative if it is at reference path (here puffer path)
24 level or below, and an absolute path if the file name is not a
25 `natural' relative file name. In any case,
26 MakeAbsPath(filename, buf->filePath())
27 is guaranteed to provide the correct absolute path. This is what is
28 done know for include insets. Feel free to ask me -- JMarc
31 TODO Before initial production release:
33 * What advanced features the users want to do?
34 Implement them in a non latex dependent way, but a logical way.
35 LyX should translate it to latex or any other fitting format.
36 * Add a way to roll the image file into the file format.
37 * When loading, if the image is not found in the expected place, try
38 to find it in the clipart, or in the same directory with the image.
39 * Keep a tab on the image file, if it changes, update the lyx view.
40 * The image choosing dialog could show thumbnails of the image formats
41 it knows of, thus selection based on the image instead of based on
43 * Add support for the 'picins' package.
44 * Add support for the 'picinpar' package.
45 * Improve support for 'subfigure' - Allow to set the various options
51 * Current version is 1 (inset file format version), when changing it
52 * it should be changed in the Write() function when writing in one place
53 * and when reading one should change the version check and the error message.
54 * The filename is kept in the lyx file in a relative way, so as to allow
55 * moving the document file and its images with no problem.
59 * Postscript output means EPS figures.
61 * PDF output is best done with PDF figures if it's a direct conversion
62 * or PNG figures otherwise.
75 #pragma implementation
78 #include "insets/insetgraphics.h"
79 #include "insets/insetgraphicsParams.h"
81 #include "graphics/GraphicsCache.h"
82 #include "graphics/GraphicsImage.h"
87 #include "BufferView.h"
88 #include "converter.h"
91 #include "font.h" // For the lyxfont class.
94 #include "LaTeXFeatures.h"
96 #include "frontends/Dialogs.h"
97 #include "frontends/controllers/helper_funcs.h" // getVectorFromString
99 #include "support/LAssert.h"
100 #include "support/filetools.h"
101 #include "support/lyxalgo.h" // lyx::count
102 #include "support/path.h"
104 #include <algorithm> // For the std::max
106 extern string system_tempdir;
111 ///////////////////////////////////////////////////////////////////////////
112 int const VersionNumber = 1;
113 ///////////////////////////////////////////////////////////////////////////
117 // This function is a utility function
118 // ... that should be with ChangeExtension ...
120 string const RemoveExtension(string const & filename)
122 return ChangeExtension(filename, string());
130 string const unique_id()
132 static unsigned int seed = 1000;
135 ost << "graph" << ++seed;
137 // Needed if we use lyxstring.
138 return ost.str().c_str();
144 InsetGraphics::InsetGraphics()
145 : graphic_label(unique_id()),
146 cached_status_(grfx::ErrorUnknown), cache_filled_(false), old_asc(0)
151 InsetGraphics::InsetGraphics(InsetGraphics const & ig,
152 string const & filepath,
154 : Inset(ig, same_id),
156 graphic_label(unique_id()),
157 cached_status_(grfx::ErrorUnknown), cache_filled_(false), old_asc(0)
159 setParams(ig.params(), filepath);
163 InsetGraphics::~InsetGraphics()
165 cached_image_.reset(0);
166 grfx::GCache & gc = grfx::GCache::get();
169 // Emits the hide signal to the dialog connected (if any)
174 string const InsetGraphics::statusMessage() const
178 switch (cached_status_) {
179 case grfx::WaitingToLoad:
180 msg = _("Waiting for draw request to start loading...");
183 msg = _("Loading...");
185 case grfx::Converting:
186 msg = _("Converting to loadable format...");
188 case grfx::ScalingEtc:
189 msg = _("Loaded. Scaling etc...");
191 case grfx::ErrorNoFile:
192 msg = _("No file found!");
194 case grfx::ErrorLoading:
195 msg = _("Error loading file into memory");
197 case grfx::ErrorConverting:
198 msg = _("Error converting to loadable format");
200 case grfx::ErrorScalingEtc:
201 msg = _("Error scaling etc");
203 case grfx::ErrorUnknown:
207 msg = _("Loaded but not displaying");
215 void InsetGraphics::setCache() const
220 grfx::GCache & gc = grfx::GCache::get();
221 cached_status_ = gc.status(*this);
222 cached_image_ = gc.image(*this);
226 bool InsetGraphics::drawImage() const
229 Pixmap const pixmap =
230 (cached_status_ == grfx::Loaded && cached_image_.get() != 0) ?
231 cached_image_->getPixmap() : 0;
237 int InsetGraphics::ascent(BufferView *, LyXFont const &) const
241 old_asc = cached_image_->getHeight();
246 int InsetGraphics::descent(BufferView *, LyXFont const &) const
252 int InsetGraphics::width(BufferView *, LyXFont const & font) const
255 return cached_image_->getWidth();
259 LyXFont msgFont(font);
260 msgFont.setFamily(LyXFont::SANS_FAMILY);
262 string const justname = OnlyFilename (params().filename);
263 if (!justname.empty()) {
264 msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
265 font_width = lyxfont::width(justname, msgFont);
268 string const msg = statusMessage();
270 msgFont.setSize(LyXFont::SIZE_TINY);
271 int const msg_width = lyxfont::width(msg, msgFont);
272 font_width = std::max(font_width, msg_width);
275 return std::max(50, font_width + 15);
280 void InsetGraphics::draw(BufferView * bv, LyXFont const & font,
281 int baseline, float & x, bool) const
284 grfx::ImageStatus old_status_ = cached_status_;
286 int ldescent = descent(bv, font);
287 int lascent = ascent(bv, font);
288 int lwidth = width(bv, font);
290 // we may have changed while someone other was drawing us so better
291 // to not draw anything as we surely call to redraw ourself soon.
292 // This is not a nice thing to do and should be fixed properly somehow.
293 // But I still don't know the best way to go. So let's do this like this
294 // for now (Jug 20020311)
295 if (lascent != oasc) {
296 // lyxerr << "IG(" << this << "): " << x << endl;
300 // Make sure now that x is updated upon exit from this routine
304 // Initiate the loading of the graphics file
305 if (cached_status_ == grfx::WaitingToLoad) {
306 grfx::GCache & gc = grfx::GCache::get();
307 gc.startLoading(*this);
310 // This will draw the graphics. If the graphics has not been loaded yet,
311 // we draw just a rectangle.
312 Painter & paint = bv->painter();
315 // lyxerr << "IG(" << this << "): " << old_x << endl;
316 paint.image(old_x + 2, baseline - lascent,
317 lwidth - 4, lascent + ldescent,
318 *cached_image_.get());
322 paint.rectangle(old_x + 2, baseline - lascent,
326 // Print the file name.
327 LyXFont msgFont(font);
328 msgFont.setFamily(LyXFont::SANS_FAMILY);
329 string const justname = OnlyFilename (params().filename);
330 if (!justname.empty()) {
331 msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
332 paint.text(old_x + 8,
333 baseline - lyxfont::maxAscent(msgFont) - 4,
337 // Print the message.
338 string const msg = statusMessage();
340 msgFont.setSize(LyXFont::SIZE_TINY);
341 paint.text(old_x + 8, baseline - 4, msg, msgFont);
345 // the status message may mean we changed size, so indicate
346 // we need a row redraw
347 if (old_status_ != grfx::ErrorUnknown && old_status_ != cached_status_) {
348 bv->getLyXText()->status(bv, LyXText::CHANGED_IN_DRAW);
351 // Reset the cache, ready for the next draw request
352 cached_status_ = grfx::ErrorUnknown;
353 cached_image_.reset(0);
354 cache_filled_ = false;
358 // Update the inset after parameters changed (read from file or changed in
359 // dialog. The grfx::GCache makes the decisions about whether or not to draw
360 // (interogates lyxrc, ascertains whether file exists etc)
361 void InsetGraphics::updateInset(string const & filepath) const
363 grfx::GCache & gc = grfx::GCache::get();
364 gc.update(*this, filepath);
368 void InsetGraphics::edit(BufferView *bv, int, int, unsigned int)
370 bv->owner()->getDialogs()->showGraphics(this);
374 void InsetGraphics::edit(BufferView * bv, bool)
380 Inset::EDITABLE InsetGraphics::editable() const
386 void InsetGraphics::write(Buffer const *, ostream & os) const
388 os << "Graphics FormatVersion " << VersionNumber << '\n';
393 void InsetGraphics::read(Buffer const * buf, LyXLex & lex)
395 string const token = lex.getString();
397 if (token == "Graphics")
398 readInsetGraphics(lex);
399 else if (token == "Figure") // Compatibility reading of FigInset figures.
402 lyxerr[Debug::GRAPHICS] << "Not a Graphics or Figure inset!\n";
404 updateInset(buf->filePath());
408 void InsetGraphics::readInsetGraphics(LyXLex & lex)
410 bool finished = false;
412 while (lex.isOK() && !finished) {
415 string const token = lex.getString();
416 lyxerr[Debug::GRAPHICS] << "Token: '" << token << '\''
421 } else if (token == "\\end_inset") {
423 } else if (token == "FormatVersion") {
425 int version = lex.getInteger();
426 if (version > VersionNumber)
428 << "This document was created with a newer Graphics widget"
429 ", You should use a newer version of LyX to read this"
432 // TODO: Possibly open up a dialog?
435 if (! params_.Read(lex, token))
436 lyxerr << "Unknown token, " << token << ", skipping."
442 // FormatVersion < 1.0 (LyX < 1.2)
443 void InsetGraphics::readFigInset(LyXLex & lex)
445 std::vector<string> const oldUnits =
446 getVectorFromString("pt,cm,in,p%,c%");
447 bool finished = false;
448 // set the display default
449 if (lyxrc.display_graphics == "mono")
450 params_.display = InsetGraphicsParams::MONOCHROME;
451 else if (lyxrc.display_graphics == "gray")
452 params_.display = InsetGraphicsParams::GRAYSCALE;
453 else if (lyxrc.display_graphics == "color")
454 params_.display = InsetGraphicsParams::COLOR;
456 params_.display = InsetGraphicsParams::NONE;
457 while (lex.isOK() && !finished) {
460 string const token = lex.getString();
461 lyxerr[Debug::GRAPHICS] << "Token: " << token << endl;
465 else if (token == "\\end_inset") {
467 } else if (token == "file") {
469 params_.filename = lex.getString();
471 } else if (token == "extra") {
473 // kept for backwards compability. Delete in 0.13.x
474 } else if (token == "subcaption") {
476 params_.subcaptionText = lex.getString();
477 params_.subcaption = true;
478 } else if (token == "label") {
480 // kept for backwards compability. Delete in 0.13.x
481 } else if (token == "angle") {
483 params_.rotate = true;
484 params_.rotateAngle = lex.getFloat();
485 } else if (token == "size") {
487 params_.lyxwidth = LyXLength(lex.getString()+"pt");
489 params_.lyxheight = LyXLength(lex.getString()+"pt");
490 params_.lyxsize_type = InsetGraphicsParams::WH;
491 } else if (token == "flags") {
493 switch (lex.getInteger()) {
494 case 1: params_.display = InsetGraphicsParams::MONOCHROME;
496 case 2: params_.display = InsetGraphicsParams::GRAYSCALE;
498 case 3: params_.display = InsetGraphicsParams::COLOR;
501 } else if (token == "subfigure") {
502 params_.subcaption = true;
503 } else if (token == "width") {
505 int i = lex.getInteger();
508 params_.scale = lex.getInteger();
509 params_.size_type = InsetGraphicsParams::SCALE;
511 params_.width = LyXLength(lex.getString()+oldUnits[i]);
512 params_.size_type = InsetGraphicsParams::WH;
516 } else if (token == "height") {
518 int i = lex.getInteger();
520 params_.height = LyXLength(lex.getString()+oldUnits[i]);
521 params_.size_type = InsetGraphicsParams::WH;
528 string const InsetGraphics::createLatexOptions() const
530 // Calculate the options part of the command, we must do it to a string
531 // stream since we might have a trailing comma that we would like to remove
532 // before writing it to the output stream.
533 ostringstream options;
534 if (!params().bb.empty())
535 options << " bb=" << strip(params().bb) << ",\n";
537 options << " draft,\n";
539 options << " clip,\n";
540 if (params().size_type == InsetGraphicsParams::WH) {
541 if (!params().width.zero())
542 options << " width=" << params().width.asLatexString() << ",\n";
543 if (!params().height.zero())
544 options << " height=" << params().height.asLatexString() << ",\n";
545 } else if (params().size_type == InsetGraphicsParams::SCALE) {
546 if (params().scale > 0)
547 options << " scale=" << double(params().scale)/100.0 << ",\n";
549 if (params().keepAspectRatio)
550 options << " keepaspectratio,\n";
551 // Make sure it's not very close to zero, a float can be effectively
552 // zero but not exactly zero.
553 if (!lyx::float_equal(params().rotateAngle, 0, 0.001) && params().rotate) {
554 options << " angle=" << params().rotateAngle << ",\n";
555 if (!params().rotateOrigin.empty()) {
556 options << " origin=" << params().rotateOrigin[0];
557 if (contains(params().rotateOrigin,"Top"))
559 else if (contains(params().rotateOrigin,"Bottom"))
561 else if (contains(params().rotateOrigin,"Baseline"))
566 if (!params().special.empty())
567 options << params().special << ",\n";
568 string opts = options.str().c_str();
569 return opts.substr(0,opts.size()-2); // delete last ",\n"
573 string findTargetFormat(string const & suffix)
575 // lyxrc.pdf_mode means:
576 // Are we creating a PDF or a PS file?
577 // (Should actually mean, are we using latex or pdflatex).
578 lyxerr[Debug::GRAPHICS] << "decideOutput: lyxrc.pdf_mode = "
579 << lyxrc.pdf_mode << std::endl;
580 if (lyxrc.pdf_mode) {
581 if (contains(suffix,"ps") || suffix == "pdf")
583 else if (suffix == "jpg")
588 // If it's postscript, we always do eps.
589 lyxerr[Debug::GRAPHICS] << "decideOutput: we have PostScript mode\n";
599 string const InsetGraphics::prepareFile(Buffer const *buf) const
601 // do_convert = Do we need to convert the file?
602 // nice = Do we create a nice version?
603 // This is used when exporting the latex file only.
605 // return original filename
607 // convert_place = temp directory
608 // return new filename in temp directory
610 // convert_place = original file directory
611 // return original filename without the extension
613 // if it's a zipped one, than let LaTeX do the rest!!!
614 string filename_ = params().filename;
615 bool const zipped = zippedFile(filename_);
617 if ((zipped && params().noUnzip) || buf->niceFile) {
618 lyxerr[Debug::GRAPHICS] << "don't unzip file or export latex"
619 << filename_ << endl;
623 // Enable these helper functions to find the file if it is stored as
625 Path p(buf->filePath());
628 filename_ = unzipFile(filename_);
630 string const from = getExtFromContents(filename_);
631 string const to = findTargetFormat(from);
634 // No conversion needed!
638 string const temp = AddName(buf->tmppath, filename_);
639 string const outfile_base = RemoveExtension(temp);
641 lyxerr[Debug::GRAPHICS] << "tempname = " << temp << "\n";
642 lyxerr[Debug::GRAPHICS] << "buf::tmppath = " << buf->tmppath << "\n";
643 lyxerr[Debug::GRAPHICS] << "filename_ = " << filename_ << "\n";
644 lyxerr[Debug::GRAPHICS] << "outfile_base = " << outfile_base << endl;
646 converters.convert(buf, filename_, outfile_base, from, to);
651 int InsetGraphics::latex(Buffer const *buf, ostream & os,
652 bool /*fragile*/, bool/*fs*/) const
654 // If there is no file specified or not existing,
655 // just output a message about it in the latex output.
656 lyxerr[Debug::GRAPHICS] << "[latex]filename = "
657 << params().filename << endl;
658 string const message =
659 (IsFileReadable(MakeAbsPath(params().filename, buf->filePath()))
660 && !params().filename.empty()) ?
662 string("bb = 0 0 200 100, draft, type=eps]");
663 lyxerr[Debug::GRAPHICS] << "[latex]Messagestring = " << message << endl;
665 // These variables collect all the latex code that should be before and
666 // after the actual includegraphics command.
669 // Do we want subcaptions?
670 if (params().subcaption) {
671 before += "\\subfigure[" + params().subcaptionText + "]{";
674 // We never use the starred form, we use the "clip" option instead.
675 before += "\\includegraphics";
677 // Write the options if there are any.
678 string const opts = createLatexOptions();
679 lyxerr[Debug::GRAPHICS] << "[latex]opts = " << opts << endl;
680 if (!opts.empty() && !message.empty())
681 before += ("[" + opts + ',' + message);
682 else if (!message.empty())
683 before += ('[' + message);
684 else if (!opts.empty())
685 before += ("[" + opts + ']');
686 lyxerr[Debug::GRAPHICS] << "[latex]before = " << before << endl;
687 lyxerr[Debug::GRAPHICS] << "[latex]after = " << after << endl;
689 // Make the filename relative to the lyx file
690 // and remove the extension so the LaTeX will use whatever is
691 // appropriate (when there are several versions in different formats)
692 string const latex_str = message.empty() ?
693 (before + '{' + prepareFile(buf) + '}' + after) :
694 (before + '{' + params().filename + " not found!}" + after);
697 // Return how many newlines we issued.
699 int(lyx::count(latex_str.begin(), latex_str.end(),'\n') + 1);
705 int InsetGraphics::ascii(Buffer const *, ostream & os, int) const
707 // No graphics in ascii output. Possible to use gifscii to convert
708 // images to ascii approximation.
709 // 1. Convert file to ascii using gifscii
710 // 2. Read ascii output file and add it to the output stream.
711 // at least we send the filename
712 os << '<' << _("Graphic file:") << params().filename << ">\n";
717 int InsetGraphics::linuxdoc(Buffer const *, ostream &) const
719 // No graphics in LinuxDoc output. Should check how/what to add.
724 // For explanation on inserting graphics into DocBook checkout:
725 // http://linuxdoc.org/LDP/LDP-Author-Guide/inserting-pictures.html
726 // See also the docbook guide at http://www.docbook.org/
727 int InsetGraphics::docbook(Buffer const *, ostream & os) const
729 // In DocBook v5.0, the graphic tag will be eliminated from DocBook, will
730 // need to switch to MediaObject. However, for now this is sufficient and
732 os << "<graphic fileref=\"&" << graphic_label << ";\">";
737 void InsetGraphics::validate(LaTeXFeatures & features) const
739 // If we have no image, we should not require anything.
740 if (params().filename.empty())
743 features.includeFile(graphic_label, RemoveExtension(params_.filename));
745 features.require("graphicx");
747 if (params().subcaption)
748 features.require("subfigure");
752 bool InsetGraphics::setParams(InsetGraphicsParams const & p,
753 string const & filepath)
755 // If nothing is changed, just return and say so.
756 if (params() == p && !p.filename.empty()) {
760 // Copy the new parameters.
763 // Update the inset with the new parameters.
764 updateInset(filepath);
766 // We have changed data, report it.
771 InsetGraphicsParams const & InsetGraphics::params() const
777 Inset * InsetGraphics::clone(Buffer const & buffer, bool same_id) const
779 return new InsetGraphics(*this, buffer.filePath(), same_id);