2 * \file insetgraphics.C
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
9 * Full author contact details are available in file CREDITS.
15 * What advanced features the users want to do?
16 Implement them in a non latex dependent way, but a logical way.
17 LyX should translate it to latex or any other fitting format.
18 * Add a way to roll the image file into the file format.
19 * When loading, if the image is not found in the expected place, try
20 to find it in the clipart, or in the same directory with the image.
21 * The image choosing dialog could show thumbnails of the image formats
22 it knows of, thus selection based on the image instead of based on
24 * Add support for the 'picins' package.
25 * Add support for the 'picinpar' package.
26 * Improve support for 'subfigure' - Allow to set the various options
32 * The filename is kept in the lyx file in a relative way, so as to allow
33 * moving the document file and its images with no problem.
37 * Postscript output means EPS figures.
39 * PDF output is best done with PDF figures if it's a direct conversion
40 * or PNG figures otherwise.
52 #include "insets/insetgraphics.h"
53 #include "insets/render_graphic.h"
56 #include "BufferView.h"
57 #include "converter.h"
60 #include "dispatchresult.h"
62 #include "funcrequest.h"
64 #include "LaTeXFeatures.h"
67 #include "metricsinfo.h"
68 #include "outputparams.h"
70 #include "frontends/Alert.h"
71 #include "frontends/LyXView.h"
73 #include "support/filetools.h"
74 #include "support/lyxalgo.h" // lyx::count
75 #include "support/lyxlib.h" // float_equal
76 #include "support/os.h"
77 #include "support/systemcall.h"
78 #include "support/tostr.h"
79 #include "support/std_sstream.h"
81 #include <boost/bind.hpp>
82 #include <boost/tuple/tuple.hpp>
84 namespace support = lyx::support;
85 using lyx::support::AbsolutePath;
86 using lyx::support::bformat;
87 using lyx::support::ChangeExtension;
88 using lyx::support::compare_timestamps;
89 using lyx::support::contains;
90 using lyx::support::FileName;
91 using lyx::support::float_equal;
92 using lyx::support::GetExtension;
93 using lyx::support::getExtFromContents;
94 using lyx::support::IsFileReadable;
95 using lyx::support::LibFileSearch;
96 using lyx::support::rtrim;
97 using lyx::support::subst;
98 using lyx::support::Systemcall;
99 using lyx::support::unzipFile;
100 using lyx::support::unzippedFileName;
102 namespace os = lyx::support::os;
107 using std::istringstream;
109 using std::ostringstream;
114 // This function is a utility function
115 // ... that should be with ChangeExtension ...
117 string const RemoveExtension(string const & filename)
119 return ChangeExtension(filename, string());
123 string const uniqueID()
125 static unsigned int seed = 1000;
126 return "graph" + tostr(++seed);
130 string findTargetFormat(string const & suffix, OutputParams const & runparams)
132 // Are we using latex or pdflatex).
133 if (runparams.flavor == OutputParams::PDFLATEX) {
134 lyxerr[Debug::GRAPHICS] << "findTargetFormat: PDF mode" << endl;
135 if (contains(suffix, "ps") || suffix == "pdf")
137 if (suffix == "jpg") // pdflatex can use jpeg
139 return "png"; // and also png
141 // If it's postscript, we always do eps.
142 lyxerr[Debug::GRAPHICS] << "findTargetFormat: PostScript mode" << endl;
143 if (suffix != "ps") // any other than ps
144 return "eps"; // is changed to eps
145 return suffix; // let ps untouched
151 InsetGraphics::InsetGraphics()
152 : graphic_label(uniqueID()),
153 graphic_(new RenderGraphic(this))
157 InsetGraphics::InsetGraphics(InsetGraphics const & ig)
159 boost::signals::trackable(),
160 graphic_label(uniqueID()),
161 graphic_(new RenderGraphic(*ig.graphic_, this))
163 setParams(ig.params());
167 auto_ptr<InsetBase> InsetGraphics::clone() const
169 return auto_ptr<InsetBase>(new InsetGraphics(*this));
173 InsetGraphics::~InsetGraphics()
175 InsetGraphicsMailer(*this).hideDialog();
179 void InsetGraphics::priv_dispatch(LCursor & cur, FuncRequest & cmd)
181 switch (cmd.action) {
182 case LFUN_GRAPHICS_EDIT: {
183 Buffer const & buffer = *cur.bv().buffer();
184 InsetGraphicsParams p;
185 InsetGraphicsMailer::string2params(cmd.argument, buffer, p);
186 editGraphics(p, buffer);
190 case LFUN_INSET_MODIFY: {
191 Buffer const & buffer = cur.buffer();
192 InsetGraphicsParams p;
193 InsetGraphicsMailer::string2params(cmd.argument, buffer, p);
194 if (!p.filename.empty()) {
201 case LFUN_INSET_DIALOG_UPDATE:
202 InsetGraphicsMailer(*this).updateDialog(&cur.bv());
205 case LFUN_MOUSE_RELEASE:
206 InsetGraphicsMailer(*this).showDialog(&cur.bv());
210 InsetOld::priv_dispatch(cur, cmd);
216 void InsetGraphics::edit(LCursor & cur, bool)
218 InsetGraphicsMailer(*this).showDialog(&cur.bv());
222 void InsetGraphics::metrics(MetricsInfo & mi, Dimension & dim) const
224 graphic_->metrics(mi, dim);
229 void InsetGraphics::draw(PainterInfo & pi, int x, int y) const
231 setPosCache(pi, x, y);
232 graphic_->draw(pi, x, y);
236 InsetOld::EDITABLE InsetGraphics::editable() const
242 void InsetGraphics::write(Buffer const & buf, ostream & os) const
245 params().Write(os, buf.filePath());
249 void InsetGraphics::read(Buffer const & buf, LyXLex & lex)
251 string const token = lex.getString();
253 if (token == "Graphics")
254 readInsetGraphics(lex, buf.filePath());
256 lyxerr[Debug::GRAPHICS] << "Not a Graphics inset!" << endl;
258 graphic_->update(params().as_grfxParams());
262 void InsetGraphics::readInsetGraphics(LyXLex & lex, string const & bufpath)
264 bool finished = false;
266 while (lex.isOK() && !finished) {
269 string const token = lex.getString();
270 lyxerr[Debug::GRAPHICS] << "Token: '" << token << '\''
275 } else if (token == "\\end_inset") {
278 if (!params_.Read(lex, token, bufpath))
279 lyxerr << "Unknown token, " << token << ", skipping."
286 string const InsetGraphics::createLatexOptions() const
288 // Calculate the options part of the command, we must do it to a string
289 // stream since we might have a trailing comma that we would like to remove
290 // before writing it to the output stream.
291 ostringstream options;
292 if (!params().bb.empty())
293 options << " bb=" << rtrim(params().bb) << ",\n";
295 options << " draft,\n";
297 options << " clip,\n";
298 if (!float_equal(params().scale, 0.0, 0.05)) {
299 if (!float_equal(params().scale, 100.0, 0.05))
300 options << " scale=" << params().scale / 100.0
303 if (!params().width.zero())
304 options << " width=" << params().width.asLatexString() << ",\n";
305 if (!params().height.zero())
306 options << " height=" << params().height.asLatexString() << ",\n";
307 if (params().keepAspectRatio)
308 options << " keepaspectratio,\n";
311 // Make sure rotation angle is not very close to zero;
312 // a float can be effectively zero but not exactly zero.
313 if (!float_equal(params().rotateAngle, 0, 0.001)) {
314 options << " angle=" << params().rotateAngle << ",\n";
315 if (!params().rotateOrigin.empty()) {
316 options << " origin=" << params().rotateOrigin[0];
317 if (contains(params().rotateOrigin,"Top"))
319 else if (contains(params().rotateOrigin,"Bottom"))
321 else if (contains(params().rotateOrigin,"Baseline"))
327 if (!params().special.empty())
328 options << params().special << ",\n";
330 string opts = options.str();
332 return opts.substr(0, opts.size() - 2);
346 std::pair<CopyStatus, string> const
347 copyFileIfNeeded(string const & file_in, string const & file_out)
349 BOOST_ASSERT(AbsolutePath(file_in));
350 BOOST_ASSERT(AbsolutePath(file_out));
352 unsigned long const checksum_in = support::sum(file_in);
353 unsigned long const checksum_out = support::sum(file_out);
355 if (checksum_in == checksum_out)
357 return std::make_pair(IDENTICAL_CONTENTS, file_out);
359 bool const success = support::copy(file_in, file_out);
361 lyxerr[Debug::GRAPHICS]
362 << support::bformat(_("Could not copy the file\n%1$s\n"
363 "into the temporary directory."),
368 CopyStatus status = success ? SUCCESS : FAILURE;
369 return std::make_pair(status, file_out);
373 std::pair<CopyStatus, string> const
374 copyToDirIfNeeded(string const & file_in, string const & dir, bool zipped)
376 using support::rtrim;
378 BOOST_ASSERT(AbsolutePath(file_in));
380 string const only_path = support::OnlyPath(file_in);
381 if (rtrim(support::OnlyPath(file_in) , "/") == rtrim(dir, "/"))
382 return std::make_pair(IDENTICAL_PATHS, file_in);
384 string mangled = FileName(file_in).mangledFilename();
386 // We need to change _eps.gz to .eps.gz. The mangled name is
387 // still unique because of the counter in mangledFilename().
388 // We can't just call mangledFilename() with the zip
389 // extension removed, because base.eps and base.eps.gz may
390 // have different content but would get the same mangled
391 // name in this case.
392 string const base = RemoveExtension(unzippedFileName(file_in));
393 string::size_type const ext_len = file_in.length() - base.length();
394 mangled[mangled.length() - ext_len] = '.';
396 string const file_out = support::MakeAbsPath(mangled, dir);
398 return copyFileIfNeeded(file_in, file_out);
402 string const stripExtensionIfPossible(string const & file, string const & to)
404 // No conversion is needed. LaTeX can handle the graphic file as is.
405 // This is true even if the orig_file is compressed.
406 if (formats.getFormat(to)->extension() == GetExtension(file))
407 return RemoveExtension(file);
414 string const InsetGraphics::prepareFile(Buffer const & buf,
415 OutputParams const & runparams) const
417 string orig_file = params().filename.absFilename();
418 string const rel_file = params().filename.relFilename(buf.filePath());
420 // LaTeX can cope if the graphics file doesn't exist, so just return the
422 if (!IsFileReadable(orig_file)) {
423 lyxerr[Debug::GRAPHICS]
424 << "InsetGraphics::prepareFile\n"
425 << "No file '" << orig_file << "' can be found!" << endl;
429 // If the file is compressed and we have specified that it
430 // should not be uncompressed, then just return its name and
431 // let LaTeX do the rest!
432 bool const zipped = params().filename.isZipped();
434 // temp_file will contain the file for LaTeX to act on if, for example,
435 // we move it to a temp dir or uncompress it.
436 string temp_file = orig_file;
438 // We place all temporary files in the master buffer's temp dir.
439 // This is possible because we use mangled file names.
440 // This is necessary for DVI export.
441 string const temp_path = buf.getMasterBuffer()->temppath();
443 bool conversion_needed = true;
446 boost::tie(status, temp_file) =
447 copyToDirIfNeeded(orig_file, temp_path, zipped);
449 if (status == FAILURE)
451 else if (status == IDENTICAL_CONTENTS)
452 conversion_needed = false;
455 if (params().noUnzip) {
456 lyxerr[Debug::GRAPHICS]
457 << "\tpass zipped file to LaTeX.\n";
458 // LaTeX needs the bounding box file in the tmp dir
460 boost::tie(status, bb_file) =
461 copyFileIfNeeded(ChangeExtension(orig_file, "bb"),
462 ChangeExtension(temp_file, "bb"));
463 if (status == FAILURE)
468 string const unzipped_temp_file = unzippedFileName(temp_file);
469 if (compare_timestamps(unzipped_temp_file, temp_file) > 0) {
470 // temp_file has been unzipped already and
471 // orig_file has not changed in the meantime.
472 temp_file = unzipped_temp_file;
473 lyxerr[Debug::GRAPHICS]
474 << "\twas already unzipped to " << temp_file
477 // unzipped_temp_file does not exist or is too old
478 temp_file = unzipFile(temp_file);
479 lyxerr[Debug::GRAPHICS]
480 << "\tunzipped to " << temp_file << endl;
484 string const from = getExtFromContents(temp_file);
485 string const to = findTargetFormat(from, runparams);
486 lyxerr[Debug::GRAPHICS]
487 << "\t we have: from " << from << " to " << to << '\n';
489 // We're going to be running the exported buffer through the LaTeX
490 // compiler, so must ensure that LaTeX can cope with the graphics
493 lyxerr[Debug::GRAPHICS]
494 << "\tthe orig file is: " << orig_file << endl;
497 return stripExtensionIfPossible(temp_file, to);
499 string const to_file_base = RemoveExtension(temp_file);
500 string const to_file = ChangeExtension(to_file_base, to);
502 // Do we need to perform the conversion?
503 // Yes if to_file does not exist or if temp_file is newer than to_file
504 if (!conversion_needed ||
505 compare_timestamps(temp_file, to_file) < 0) {
506 lyxerr[Debug::GRAPHICS]
507 << bformat(_("No conversion of %1$s is needed after all"),
513 lyxerr[Debug::GRAPHICS]
514 << "\tThe original file is " << orig_file << "\n"
515 << "\tA copy has been made and convert is to be called with:\n"
516 << "\tfile to convert = " << temp_file << '\n'
517 << "\tto_file_base = " << to_file_base << '\n'
518 << "\t from " << from << " to " << to << '\n';
520 // if no special converter defined, then we take the default one
521 // from ImageMagic: convert from:inname.from to:outname.to
522 if (!converters.convert(&buf, temp_file, to_file_base, from, to)) {
523 string const command =
524 "sh " + LibFileSearch("scripts", "convertDefault.sh") +
525 ' ' + from + ':' + temp_file + ' ' +
526 to + ':' + to_file_base + '.' + to;
527 lyxerr[Debug::GRAPHICS]
528 << "No converter defined! I use convertDefault.sh:\n\t"
531 one.startscript(Systemcall::Wait, command);
532 if (!IsFileReadable(ChangeExtension(to_file_base, to))) {
533 string str = bformat(_("No information for converting %1$s "
534 "format files to %2$s.\n"
535 "Try defining a convertor in the preferences."), from, to);
536 Alert::error(_("Could not convert image"), str);
544 int InsetGraphics::latex(Buffer const & buf, ostream & os,
545 OutputParams const & runparams) const
547 // The master buffer. This is useful when there are multiple levels
549 Buffer const * m_buffer = buf.getMasterBuffer();
551 // If there is no file specified or not existing,
552 // just output a message about it in the latex output.
553 lyxerr[Debug::GRAPHICS]
554 << "insetgraphics::latex: Filename = "
555 << params().filename.absFilename() << endl;
557 string const relative_file =
558 params().filename.relFilename(buf.filePath());
560 string const file_ = params().filename.absFilename();
561 bool const file_exists = !file_.empty() && IsFileReadable(file_);
562 string const message = file_exists ?
563 string() : string("bb = 0 0 200 100, draft, type=eps");
564 // if !message.empty() than there was no existing file
565 // "filename" found. In this case LaTeX
566 // draws only a rectangle with the above bb and the
567 // not found filename in it.
568 lyxerr[Debug::GRAPHICS]
569 << "\tMessage = \"" << message << '\"' << endl;
571 // These variables collect all the latex code that should be before and
572 // after the actual includegraphics command.
575 // Do we want subcaptions?
576 if (params().subcaption) {
577 before += "\\subfigure[" + params().subcaptionText + "]{";
580 // We never use the starred form, we use the "clip" option instead.
581 before += "\\includegraphics";
583 // Write the options if there are any.
584 string const opts = createLatexOptions();
585 lyxerr[Debug::GRAPHICS] << "\tOpts = " << opts << endl;
587 if (!opts.empty() && !message.empty())
588 before += ("[%\n" + opts + ',' + message + ']');
589 else if (!opts.empty() || !message.empty())
590 before += ("[%\n" + opts + message + ']');
592 lyxerr[Debug::GRAPHICS]
593 << "\tBefore = " << before
594 << "\n\tafter = " << after << endl;
597 string latex_str = before + '{';
598 // "nice" means that the buffer is exported to LaTeX format but not
599 // run through the LaTeX compiler.
600 if (runparams.nice) {
601 // a relative filename should be relative to the master
603 string basename = params().filename.outputFilename(m_buffer->filePath());
604 // Remove the extension so the LaTeX will use whatever
605 // is appropriate (when there are several versions in
606 // different formats)
607 basename = RemoveExtension(basename);
608 if(params().filename.isZipped())
609 basename = RemoveExtension(basename);
610 // This works only if the filename contains no dots besides
611 // the just removed one. We can fool here by replacing all
612 // dots with a macro whose definition is just a dot ;-)
613 latex_str += subst(basename, ".", "\\lyxdot ");
614 } else if (file_exists) {
615 // Make the filename relative to the lyx file
616 // and remove the extension so the LaTeX will use whatever
617 // is appropriate (when there are several versions in
618 // different formats)
619 latex_str += os::external_path(prepareFile(buf, runparams));
621 latex_str += relative_file + " not found!";
623 latex_str += '}' + after;
626 lyxerr[Debug::GRAPHICS] << "InsetGraphics::latex outputting:\n"
627 << latex_str << endl;
628 // Return how many newlines we issued.
629 return int(lyx::count(latex_str.begin(), latex_str.end(),'\n') + 1);
633 int InsetGraphics::plaintext(Buffer const &, ostream & os,
634 OutputParams const &) const
636 // No graphics in ascii output. Possible to use gifscii to convert
637 // images to ascii approximation.
638 // 1. Convert file to ascii using gifscii
639 // 2. Read ascii output file and add it to the output stream.
640 // at least we send the filename
641 os << '<' << bformat(_("Graphics file: %1$s"),
642 params().filename.absFilename()) << ">\n";
647 int InsetGraphics::linuxdoc(Buffer const & buf, ostream & os,
648 OutputParams const & runparams) const
650 string const file_name = runparams.nice ?
651 params().filename.relFilename(buf.filePath()):
652 params().filename.absFilename();
654 os << "<eps file=\"" << file_name << "\">\n";
655 os << "<img src=\"" << file_name << "\">";
660 // For explanation on inserting graphics into DocBook checkout:
661 // http://en.tldp.org/LDP/LDP-Author-Guide/inserting-pictures.html
662 // See also the docbook guide at http://www.docbook.org/
663 int InsetGraphics::docbook(Buffer const &, ostream & os,
664 OutputParams const &) const
666 // In DocBook v5.0, the graphic tag will be eliminated from DocBook, will
667 // need to switch to MediaObject. However, for now this is sufficient and
669 os << "<graphic fileref=\"&" << graphic_label << ";\">";
674 void InsetGraphics::validate(LaTeXFeatures & features) const
676 // If we have no image, we should not require anything.
677 if (params().filename.empty())
680 features.includeFile(graphic_label,
681 RemoveExtension(params().filename.absFilename()));
683 features.require("graphicx");
685 if (features.nice()) {
686 Buffer const * m_buffer = features.buffer().getMasterBuffer();
688 params().filename.outputFilename(m_buffer->filePath());
689 string const file_ = params().filename.absFilename();
690 if (!(IsFileReadable(file_ + ".eps") || IsFileReadable(file_ + ".ps")))
691 basename = RemoveExtension(basename);
692 if (contains(basename, "."))
693 features.require("lyxdot");
696 if (params().subcaption)
697 features.require("subfigure");
701 bool InsetGraphics::setParams(InsetGraphicsParams const & p)
703 // If nothing is changed, just return and say so.
704 if (params() == p && !p.filename.empty())
707 // Copy the new parameters.
710 // Update the display using the new parameters.
711 graphic_->update(params().as_grfxParams());
713 // We have changed data, report it.
718 InsetGraphicsParams const & InsetGraphics::params() const
724 void InsetGraphics::editGraphics(InsetGraphicsParams const & p, Buffer const & buffer) const
726 string const file_with_path = p.filename.absFilename();
727 formats.edit(buffer, file_with_path, getExtFromContents(file_with_path));
731 string const InsetGraphicsMailer::name_("graphics");
733 InsetGraphicsMailer::InsetGraphicsMailer(InsetGraphics & inset)
738 string const InsetGraphicsMailer::inset2string(Buffer const & buffer) const
740 return params2string(inset_.params(), buffer);
744 void InsetGraphicsMailer::string2params(string const & in,
745 Buffer const & buffer,
746 InsetGraphicsParams & params)
748 params = InsetGraphicsParams();
752 istringstream data(in);
758 if (!lex || name != name_)
759 return print_mailer_error("InsetGraphicsMailer", in, 1, name_);
762 inset.readInsetGraphics(lex, buffer.filePath());
763 params = inset.params();
768 InsetGraphicsMailer::params2string(InsetGraphicsParams const & params,
769 Buffer const & buffer)
772 data << name_ << ' ';
773 params.Write(data, buffer.filePath());
774 data << "\\end_inset\n";