3

When linking to PNG raster graphics from Inkscape SVG documents, it seems that Inkscape always insists on writing the absolute path to the PNG graphic to the file. This has many disadvantages (which, obviously, can be mitigated by manually editing the SVG file in a text editor after every save, but that cannot be the idea behind a graphical editor such as Inkscape):

  • I cannot move the directory with my graphics without breaking the SVG.
  • I cannot store the directory with my graphics on a portable memory to work on it from various computers, as the portable device will get assigned different drive letters.
  • I cannot edit my graphics from different operating systems, as they may mount the partition that stores the SVG files and the PNG graphics on different mount points.
  • I cannot provide the directory with my graphics to colleagues on a network share from where they can copy it, because the paths will not match up.
  • The files cannot be directly committed to and shared via version control systems such as SVN or Git for the same reason. At the very least, every time someone commits the changed file, lines that contain full paths will be pointlessly registered as changes by the VCS.
  • With files frequently stored in the user's home directory, this even introduces a privacy risk by giving away the user name of the creator (in work settings, often the real name), or other information about the creator's file system.

Even now, that Inkscape seems to use relative paths and just add the full path in an extra attribute (sodipodi:absref), several of the issues above are still around (in particular, the privacy concern is unchanged, and so is the VCS issue).

Note that I do not want to embed the PNG data in the Inkscape documents for various reasons, two of which are:

  • The graphics may change later on, and it is more flexible to have the PNG image as a separate PNG file where I can edit it (and it will thus be updated automatically when opening Inkscape again), instead of embedding the PNG data in the SVG, still saving it as an extra file (so I can edit the PNG image later on) and every time I change the PNG; embedding the new version again in Inkscape, manually adapting its position and size to match that of the previous version and then removing the previous version.
  • Various of the PNG images (think logos or other corporate-identity-related graphics) are used in a multitude of SVG documents, so it would simply be a waste of space to embed one such image into every single SVG document, rather than storing it just once and referring to that original PNG graphic from each SVG document. (And of course, the effort from the previous item multiplies with every SVG document I use a given modified raster graphic in.)

As a workaround, I have thought of writing a script that has to be run over SVG files after saving, either with XSLT, or with some conventional language that can load SVGs. Possibly, this could also be set up as a pre-commit-hook for the aforementioned version control systems. However, doing so seems sufficiently tedious to me, so I only want to go to those lengths if there really is no other way.

Is there a way to force Inkscape to write only the relative paths to linked images into the SVG file? (Also for the most recently used bitmap export path, if possible.)

1

This is a great question, and I wish I had a really good solution. All I've been able to come up with is the following hackish script:

#!/usr/bin/perl

use strict;

# usage:
#   relativise_svg.pl foo.svg
# Looks for absolute links in svg file and makes them relative.
# Also checks whether files linked to exist.

use File::Spec; 
use File::Basename;
use Cwd 'abs_path';
my $svg = $ARGV[0];

-e $svg or err("file $svg doesn't exist");
-w $svg or err("file $svg not writeable");

local $/; # slurp whole file

open(F,"<$svg");
my $xml = <F>;
close F;

# Absolute links look like this:
#   xlink:href="file:///home/bcrowell/Documents/writing/books/physics/share/..."
# After we relativise them, they look like this:
#   xlink:href="foo/bar.jpg"

my $cwd = Cwd::getcwd();
my $svg_dir = File::Basename::dirname(abs_path($svg));
my $original_xml = $xml;

my @changes = ();
while ($xml=~m@(file://(/[^'"]*))@g) {
  my $whole = $1;
  my $path = $2;
  my $rel = relativise($path,$svg_dir,$cwd);
  print "changing absolute path in $svg to $rel\n";
  push @changes,[$whole,$rel];
}
foreach my $change(@changes) {
  my $from = quotemeta($change->[0]);
  my $to = $change->[1];
  $xml =~ s/$from/$to/g;
}

while ($xml=~m@xlink:href\s*=\s*"([^'"]*)@g) {
  my $rel = $1;
  if ($rel=~/\.(png|jpg)$/ && !($rel=~/\A#/ || $rel=~/\Adata:;/)) {
    my $abs = File::Spec->rel2abs($rel,$svg_dir);
    -e $abs or err("file $rel doesn't exist, resolved to absolute path $abs");
  }
}

if ($xml ne $original_xml) {
  open(F,">$svg");
  print F $xml;
  close F;
}

sub err {
  my $message = shift;
  print "relativise_svg.pl, working on $svg, ",$message,"\n";
  exit(-1);
}

sub relativise {
  my ($abs,$rel_to,$within) = @_;
  my $rel = File::Spec->abs2rel($abs,$rel_to);
  if (File::Spec->abs2rel($rel,$within)=~/\.\./) {
    err("result, $rel, would have been outside $within");
  }
  return $rel;
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.