#include "Converter.h"
#include "Cursor.h"
#include "DispatchResult.h"
+#include "Encoding.h"
#include "ErrorList.h"
#include "Exporter.h"
#include "Format.h"
#include "FuncRequest.h"
#include "FuncStatus.h"
#include "InsetIterator.h"
+#include "Language.h"
#include "LaTeXFeatures.h"
#include "Length.h"
#include "Lexer.h"
#include "OutputParams.h"
#include "output_xhtml.h"
#include "sgml.h"
+#include "texstream.h"
#include "TocBackend.h"
#include "frontends/alert.h"
#include "frontends/Application.h"
+#include "graphics/GraphicsCache.h"
+#include "graphics/GraphicsCacheItem.h"
+#include "graphics/GraphicsImage.h"
+
#include "support/convert.h"
#include "support/debug.h"
#include "support/docstream.h"
#include "support/lyxlib.h"
#include "support/lstrings.h"
#include "support/os.h"
+#include "support/qstring_helpers.h"
#include "support/Systemcall.h"
-#include <boost/tuple/tuple.hpp>
+#include <QProcess>
#include <algorithm>
#include <sstream>
+#include <tuple>
using namespace std;
using namespace lyx::support;
|| runparams.flavor == OutputParams::XETEX
|| runparams.flavor == OutputParams::LUATEX) {
LYXERR(Debug::GRAPHICS, "findTargetFormat: PDF mode");
- Format const * const f = formats.getFormat(format);
+ Format const * const f = theFormats().getFormat(format);
// Convert vector graphics to pdf
if (f && f->vectorFormat())
return "pdf6";
}
// for HTML, we leave the known formats and otherwise convert to png
if (runparams.flavor == OutputParams::HTML) {
- Format const * const f = formats.getFormat(format);
+ Format const * const f = theFormats().getFormat(format);
// Convert vector graphics to svg
if (f && f->vectorFormat() && theConverters().isReachable(format, "svg"))
return "svg";
}
}
-} // namespace anon
+} // namespace
InsetGraphics::InsetGraphics(Buffer * buf)
// if the inset is part of a graphics group, all the
// other members should be updated too.
if (!params_.groupId.empty())
- graphics::unifyGraphicsGroups(buffer(),
+ graphics::unifyGraphicsGroups(buffer(),
to_utf8(cmd.argument()));
break;
}
}
-string InsetGraphics::createLatexOptions() const
+void InsetGraphics::outBoundingBox(graphics::BoundingBox & bbox) const
+{
+ if (bbox.empty())
+ return;
+
+ FileName const file(params().filename.absFileName());
+
+ // No correction is necessary for a postscript image
+ bool const zipped = theFormats().isZippedFile(file);
+ FileName const unzipped_file = zipped ? unzipFile(file) : file;
+ string const format = theFormats().getFormatFromFile(unzipped_file);
+ if (zipped)
+ unzipped_file.removeFile();
+ if (Formats::isPostScriptFileFormat(format))
+ return;
+
+ // Get the actual image dimensions in pixels
+ int width = 0;
+ int height = 0;
+ graphics::Cache & gc = graphics::Cache::get();
+ if (gc.inCache(file)) {
+ graphics::Image const * image = gc.item(file)->image();
+ if (image) {
+ width = image->width();
+ height = image->height();
+ }
+ }
+ if (width == 0 || height == 0)
+ return;
+
+ // Use extractbb to find the dimensions in the typeset output
+ QProcess extractbb;
+ extractbb.start("extractbb", QStringList() << "-O" << toqstr(file.absFileName()));
+ if (!extractbb.waitForStarted() || !extractbb.waitForFinished()) {
+ LYXERR0("Cannot read output bounding box of " << file);
+ return;
+ }
+
+ string const result = extractbb.readAll().constData();
+ size_t i = result.find("%%BoundingBox:");
+ if (i == string::npos) {
+ LYXERR0("Cannot find output bounding box of " << file);
+ return;
+ }
+
+ string const bb = result.substr(i);
+ int out_width = convert<int>(token(bb, ' ', 3));
+ int out_height = convert<int>(token(bb, ' ', 4));
+
+ // Compute the scaling ratio and correct the bounding box
+ double scalex = out_width / double(width);
+ double scaley = out_height / double(height);
+
+ bbox.xl.value(scalex * bbox.xl.value());
+ bbox.xr.value(scalex * bbox.xr.value());
+ bbox.yb.value(scaley * bbox.yb.value());
+ bbox.yt.value(scaley * bbox.yt.value());
+}
+
+
+string InsetGraphics::createLatexOptions(bool const ps) 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().bbox.empty())
- options << "bb=" << params().bbox.xl.asLatexString() << ' '
- << params().bbox.yb.asLatexString() << ' '
- << params().bbox.xr.asLatexString() << ' '
- << params().bbox.yt.asLatexString() << ',';
+ if (!params().bbox.empty()) {
+ graphics::BoundingBox out_bbox = params().bbox;
+ outBoundingBox(out_bbox);
+ string const key = ps ? "bb=" : "viewport=";
+ options << key << out_bbox.xl.asLatexString() << ' '
+ << out_bbox.yb.asLatexString() << ' '
+ << out_bbox.xr.asLatexString() << ' '
+ << out_bbox.yt.asLatexString() << ',';
+ }
if (params().draft)
options << "draft,";
if (params().clip)
case Length::PLW: // Percent of LineWidth
case Length::PTH: // Percent of TextHeight
case Length::PPH: // Percent of PaperHeight
+ case Length::BLS: // Percent of BaselineSkip
// Sigh, this will go wrong.
result << len.value() << "%";
break;
// Nothing to do...
return make_pair(IDENTICAL_CONTENTS, file_out);
- Mover const & mover = getMover(formats.getFormatFromFile(file_in));
+ Mover const & mover = getMover(theFormats().getFormatFromFile(file_in));
bool const success = mover.copy(file_in, file_out);
if (!success) {
// FIXME UNICODE
return make_pair(IDENTICAL_PATHS, FileName(file_in));
string mangled = file.mangledFileName();
- if (formats.isZippedFile(file)) {
+ if (theFormats().isZippedFile(file)) {
// We need to change _eps.gz to .eps.gz. The mangled name is
// still unique because of the counter in mangledFileName().
// We can't just call mangledFileName() with the zip
// extension removed, because base.eps and base.eps.gz may
// have different content but would get the same mangled
// name in this case.
+ // Also take into account that if the name of the zipped file
+ // has no zip extension then the name of the unzipped one is
+ // prefixed by "unzipped_".
string const base = removeExtension(file.unzippedFileName());
- string::size_type const ext_len = file_in.length() - base.length();
+ string::size_type const prefix_len =
+ prefixIs(onlyFileName(base), "unzipped_") ? 9 : 0;
+ string::size_type const ext_len =
+ file_in.length() + prefix_len - base.length();
mangled[mangled.length() - ext_len] = '.';
}
FileName const file_out(makeAbsPath(mangled, dir));
{
// No conversion is needed. LaTeX can handle the graphic file as is.
// This is true even if the orig_file is compressed.
- string const to_format = formats.getFormat(to)->extension();
+ string const to_format = theFormats().getFormat(to)->extension();
string const file_format = getExtension(file);
// for latex .ps == .eps
if (to_format == file_format ||
return latex_path(file, EXCLUDE_EXTENSION);
}
-} // namespace anon
+} // namespace
string InsetGraphics::prepareFile(OutputParams const & runparams) const
// we move it to a temp dir or uncompress it.
FileName temp_file;
GraphicsCopyStatus status;
- boost::tie(status, temp_file) =
- copyToDirIfNeeded(params().filename, temp_path);
+ tie(status, temp_file) = copyToDirIfNeeded(params().filename, temp_path);
if (status == FAILURE)
return orig_file;
// determine the export format
string const tex_format = flavor2format(runparams.flavor);
- if (formats.isZippedFile(params().filename)) {
+ if (theFormats().isZippedFile(params().filename)) {
FileName const unzipped_temp_file =
FileName(unzippedFileName(temp_file.absFileName()));
output_file = unzippedFileName(output_file);
}
}
- string const from = formats.getFormatFromFile(temp_file);
+ string const from = theFormats().getFormatFromFile(temp_file);
if (from.empty())
LYXERR(Debug::GRAPHICS, "\tCould not get file format.");
string const to = findTargetFormat(from, runparams);
- string const ext = formats.extension(to);
+ string const ext = theFormats().extension(to);
LYXERR(Debug::GRAPHICS, "\t we have: from " << from << " to " << to);
// We're going to be running the exported buffer through the LaTeX
// The LaTeX compiler will not be able to determine
// the file format from the extension, so we must
// change it.
- FileName const new_file =
+ FileName const new_file =
FileName(changeExtension(temp_file.absFileName(), ext));
if (temp_file.moveTo(new_file)) {
temp_file = new_file;
output_file = changeExtension(output_file, ext);
- source_file =
+ source_file =
FileName(changeExtension(source_file.absFileName(), ext));
} else {
LYXERR(Debug::GRAPHICS, "Could not rename file `"
string before;
string after;
+ // Write the options if there are any.
+ bool const ps = runparams.flavor == OutputParams::LATEX
+ || runparams.flavor == OutputParams::DVILUATEX;
+ string const opts = createLatexOptions(ps);
+ LYXERR(Debug::GRAPHICS, "\tOpts = " << opts);
+
+ if (contains(opts, '=')) {
+ std::set<Language const *> langs = buffer().masterBuffer()->getLanguages();
+ for (auto const lang : langs) {
+ if (lang->lang() == "turkish" || lang->lang() == "latin") {
+ // Turkish and Latin activate = (#2005).
+ // Deactivate locally for keyval option parsing
+ before = "\\begingroup\\catcode`\\=12";
+ after = "\\endgroup ";
+ break;
+ }
+ }
+ }
+
if (runparams.moving_arg)
before += "\\protect";
// 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();
- LYXERR(Debug::GRAPHICS, "\tOpts = " << opts);
-
if (!opts.empty() && !message.empty())
before += ('[' + opts + ',' + message + ']');
else if (!opts.empty() || !message.empty())
// Convert the file if necessary.
// Remove the extension so LaTeX will use whatever is appropriate
// (when there are several versions in different formats)
- string file_path = prepareFile(runparams);
- latex_str += file_path;
+ docstring file_path = from_utf8(prepareFile(runparams));
+ // we can only output characters covered by the current
+ // encoding!
+ docstring uncodable;
+ docstring encodable_file_path;
+ for (size_type i = 0 ; i < file_path.size() ; ++i) {
+ char_type c = file_path[i];
+ try {
+ if (runparams.encoding->encodable(c))
+ encodable_file_path += c;
+ else if (runparams.dryrun) {
+ encodable_file_path += "<" + _("LyX Warning: ")
+ + _("uncodable character") + " '";
+ encodable_file_path += docstring(1, c);
+ encodable_file_path += "'>";
+ } else
+ uncodable += c;
+ } catch (EncodingException & /* e */) {
+ if (runparams.dryrun) {
+ encodable_file_path += "<" + _("LyX Warning: ")
+ + _("uncodable character") + " '";
+ encodable_file_path += docstring(1, c);
+ encodable_file_path += "'>";
+ } else
+ uncodable += c;
+ }
+ }
+ if (!uncodable.empty() && !runparams.silent) {
+ // issue a warning about omitted characters
+ // FIXME: should be passed to the error dialog
+ frontend::Alert::warning(_("Uncodable characters in path"),
+ bformat(_("The following characters in one of the graphic paths are\n"
+ "not representable in the current encoding and have been omitted: %1$s.\n"
+ "You need to adapt either the encoding or the path."),
+ uncodable));
+ }
+ latex_str += to_utf8(encodable_file_path);
latex_str += '}' + after;
// FIXME UNICODE
os << from_utf8(latex_str);
if (params().filename.empty())
return string();
- string const orig_file = params().filename.absFileName();
+ if (!params().filename.isReadableFile())
+ return string();
// The master buffer. This is useful when there are multiple levels
// of include files
Buffer const * masterBuffer = buffer().masterBuffer();
- if (!params().filename.isReadableFile())
- return string();
-
// We place all temporary files in the master buffer's temp dir.
// This is possible because we use mangled file names.
// FIXME We may want to put these files in some special temporary
// Copy to temporary directory.
FileName temp_file;
GraphicsCopyStatus status;
- boost::tie(status, temp_file) =
- copyToDirIfNeeded(params().filename, temp_path);
+ tie(status, temp_file) = copyToDirIfNeeded(params().filename, temp_path);
if (status == FAILURE)
return string();
- string output_file = onlyFileName(temp_file.absFileName());
-
- string const from = formats.getFormatFromFile(temp_file);
- if (from.empty())
+ string const from = theFormats().getFormatFromFile(temp_file);
+ if (from.empty()) {
LYXERR(Debug::GRAPHICS, "\tCould not get file format.");
+ return string();
+ }
string const to = findTargetFormat(from, runparams);
- string const ext = formats.extension(to);
+ string const ext = theFormats().extension(to);
+ string const orig_file = params().filename.absFileName();
+ string output_file = onlyFileName(temp_file.absFileName());
LYXERR(Debug::GRAPHICS, "\t we have: from " << from << " to " << to);
LYXERR(Debug::GRAPHICS, "\tthe orig file is: " << orig_file);
// FIXME (Abdel 12/08/06): Is there a need to show these errors?
ErrorList el;
- bool const success =
+ bool const success =
theConverters().convert(&buffer(), temp_file, to_file, params().filename,
from, to, el, Converters::try_default | Converters::try_cache);
- if (!success)
+ if (!success)
return string();
runparams.exportdata->addExternalFile("xhtml", to_file, output_to_file);
return output_to_file;
docstring InsetGraphics::xhtml(XHTMLStream & xs, OutputParams const & op) const
{
- string const output_file = prepareHTMLFile(op);
+ string const output_file = op.dryrun ? string() : prepareHTMLFile(op);
- if (output_file.empty()) {
- LYXERR0("InsetGraphics::xhtml: Unable to prepare file `"
+ if (output_file.empty() && !op.dryrun) {
+ LYXERR0("InsetGraphics::xhtml: Unable to prepare file `"
<< params().filename << "' for output. File missing?");
- string const attr = "src='" + params().filename.absFileName()
+ string const attr = "src='" + params().filename.absFileName()
+ "' alt='image: " + output_file + "'";
xs << html::CompTag("img", attr);
return docstring();
}
- // FIXME XHTML
+ // FIXME XHTML
// We aren't doing anything with the crop and rotate parameters, and it would
// really be better to do width and height conversion, rather than to output
// these parameters here.
string imgstyle;
- if (!params().width.zero())
- imgstyle += "width:" + params().width.asHTMLString() + ";";
- if (!params().height.zero())
- imgstyle += " height:" + params().height.asHTMLString() + ";";
+ bool const havewidth = !params().width.zero();
+ bool const haveheight = !params().height.zero();
+ if (havewidth || haveheight) {
+ if (havewidth)
+ imgstyle += "width:" + params().width.asHTMLString() + ";";
+ if (haveheight)
+ imgstyle += " height:" + params().height.asHTMLString() + ";";
+ } else if (params().scale != "100") {
+ // Note that this will not have the same effect as in LaTeX export:
+ // There, the image will be scaled from its original size. Here, the
+ // percentage will be interpreted by the browser, and the image will
+ // be scaled to a percentage of the window size.
+ imgstyle = "width:" + params().scale + "%;";
+ }
if (!imgstyle.empty())
imgstyle = "style='" + imgstyle + "' ";
- string const attr = imgstyle + "src='" + output_file + "' alt='image: "
+ string const attr = imgstyle + "src='" + output_file + "' alt='image: "
+ output_file + "'";
xs << html::CompTag("img", attr);
return docstring();
void InsetGraphics::editGraphics(InsetGraphicsParams const & p) const
{
- formats.edit(buffer(), p.filename,
- formats.getFormatFromFile(p.filename));
+ theFormats().edit(buffer(), p.filename,
+ theFormats().getFormatFromFile(p.filename));
}
void InsetGraphics::addToToc(DocIterator const & cpit, bool output_active,
- UpdateType) const
+ UpdateType, TocBackend & backend) const
{
//FIXME UNICODE
docstring const str = from_utf8(params_.filename.onlyFileName());
- buffer().tocBackend().toc("graphics")->push_back(TocItem(cpit, 0, str, output_active));
+ TocBuilder & b = backend.builder("graphics");
+ b.pushItem(cpit, str, output_active);
+ b.pop();
}
InsetGraphicsParams params;
InsetGraphics::string2params(argument, b, params);
- b.undo().beginUndoGroup();
+ // This handles undo groups automagically
+ UndoGroupHelper ugh(&b);
Inset & inset = b.inset();
InsetIterator it = inset_iterator_begin(inset);
InsetIterator const end = inset_iterator_end(inset);
}
}
}
- b.undo().endUndoGroup();
}