0

I am trying to grab an SVG from a browsertool (pgadmin4). However, I end up with an SVG that renders incorrectly. The generated graphic uses SVG "icons" by referencing external files. While this works nicely in the browser, all the colored pixels of these embedded SVGs are rendered 100% black. (After I fixed the links to point to the right files. As can be seen the shapes are correct).

Here is the image from the browser:

What the picture should look like

And here what I get in inkscape:

Image rendered in inkscape

Questions:

  1. How can I get my extracted image to render correctly?
  2. Is there a tool to automatically embed the linked SVGs, so I have a single file to pass on? (I have inkscape installed but anything that works would help)

Minimal example

Example source of an svg:

 <svg height="200" version="1.1" width="150" xmlns="http://www.w3.org/2000/svg"><desc>Created with Snap</desc><defs></defs><g><g><image xlink:href="misc/static/explain/img/ex_scan.svg" preserveAspectRatio="none" x="50" y="50" width="50" height="50"></image><text x="80" y="120" style="font-size: 15px; text-anchor: middle;"><tspan>public.customer</tspan></text></g></g></svg>

Human readable formatting:

<svg height="200" version="1.1" width="150" xmlns="http://www.w3.org/2000/svg">
  <desc>Created with Snap</desc>
  <defs></defs>
  <g>
    <g>
      <image xlink:href="misc/static/explain/img/ex_scan.svg" preserveAspectRatio="none" x="50" y="50" width="50" height="50"></image>
      <text x="80" y="120" style="font-size: 15px; text-anchor: middle;"> 
        <tspan>public.customer</tspan>
      </text>
    </g>
  </g>
</svg>

The image linked by the example image comes from pgadmin4 and can be found here.

  • This question is strongly related to my original question on stackoverflow, however, there I try to avoid the XY problem and ask as well if there is a better way to extract SVGs out of pgadmin4. As this seems not possilbe at the moment, I figured this would be the appropriate site for svg rendering issues. – ted May 11 '18 at 17:02
  • If you are able to access the externally linked files, are you then able to download them to your machine and import them to the existing drawing, replacing the segments that are going black? – fred_dot_u May 11 '18 at 17:33
  • @fred_dot_u I can grab all the images, however I do not want to manually replace them, as that is a lot of images (20 images for a single graph I am looking at), and technically the image is there and perectly positioned..., so I would rather know the cause why my linked image is rendered black,/ how I can get ti to render correctly as that fix will be quicker, if I have to generate a lot of these images (which I do) – ted May 11 '18 at 18:06
  • 1
    I can understand how a large quantity would make an alternate method cumbersome. The size and positioning appear to be standard code, the location must be the question here. It's consistent with others' experiences with this problem. I found something possibly useful here: superuser.com/questions/299977/… but I'm not code-qualified. It appears that there is an accepted answer posted that might be of value at that location. – fred_dot_u May 11 '18 at 18:56
  • @fred_dot_u thanks for the research. i think the scritp solves another problem though: It saves images that are linked in svgs to a folder. I have the image already (there is around 30 or so of them). The problem is that they are not rendered correctly (see post) and that I would like to actually embed them. – ted May 11 '18 at 19:27
3

The reason is the styling (or the misinterpretation of it), which is applied via classes in the linked image. Those work okay in Inkscape 0.92.3 when the objects are part of the currently viewed SVG, but they do not seem to work for linked SVG files.

Inkscape's support for linked SVG files is limited, currently. They are rasterized before they are displayed, and that rasterization algorithm that is used does not seem to support CSS classes.

If you change the styling to actual fills and stroke as when created in Inkscape, it will work.

Or if you use the development version of Inkscape, where a lot of work has been invested into CSS classes and a GUI for linking SVG images, it works, too. There, the resolution for the rasterization of linked SVGs can be set, too.

All in all, I'd recommend manually importing the SVGs, in the current 0.92.3 version of Inkscape. This will give you a higher image quality (because it's not rasterized) and a stable Inkscape version. For 30 pictures, which you can select all at once, importing will be quick. Resizing to the size of the linked images can be done via copy - Paste size separately on the (separately grouped) images, aligning with their predecessors can be done quickly by exchanging positions via the Align and Distribute dialog.

This is going to take less time than writing a script that converts class properties to element styling, or compiling the master branch of Inkscape.

  • Thank you for your insights, I tried compiling inkscape a while ago as I am stuck on Centos for now since it is a requirement. I hacked together a crude pythonscript (see answer below), that might be very buggy as I did not read the svg spec and just picked out the bread crumbs i required. – ted May 12 '18 at 1:51
  • 1
    Wouldn't have expected that it works with symbols - but makes sense. They aren't rasterized, and they are part of the same file, so don't go through that same conversion as the linked images. Good idea! There seems to be something missing at the end of the script you posted (at least a closing parenthesis, probably some text, too). – Moini May 12 '18 at 2:03
  • 1
    Chears for the heads up, fixed that. – ted May 13 '18 at 16:58
0

I hacked together a script, not perfect but does the job:

It tries to get all svgs that are in image tags, and embeds them as symbols. This script might be quirky and is not 100% (i.e. not enough warnings for cases like file not found,etc., overwriting files without warning, so use at your own risk).

Usage: python3 svgEmbedder.py inputfile outputfile

svgInliner.py at bitbucket

from lxml import etree
import os.path
import sys

def inlineSvg(path, outputPath):
    parser = etree.XMLParser(recover=True)
    tree = etree.parse(path, parser)

    root = tree.getroot()

    #can't add a namespace
    #root.set('xmlns:xlink', 'http://www.w3.org/1999/xlink')

    nsmap = {'xlink': 'http://www.w3.org/1999/xlink'}

    etree.register_namespace('xlink','http://www.w3.org/1999/xlink')

    inlinedFiles = set()

    svgParentFolder = os.path.dirname(path)

    #for img in tree.iterfind('image', namespaces=root.nsmap):
    for img in tree.iter('{http://www.w3.org/2000/svg}image'):
        #lxml: we have to translate xlink: to {http://www.w3.org/1999/xlink} for it to work, despit the lib returning xlink: ...
        settableAttributes = dict(img.items()) #img.attribute
        settableAttributes['{http://www.w3.org/1999/xlink}href'] = settableAttributes['xlink:href']

        #treat all svg paths as relative to the current folder
        pathSvg = os.path.join(svgParentFolder, settableAttributes['xlink:href'].lstrip('\\/'))
        svgId   = os.path.splitext(os.path.basename(pathSvg))[0]

        del settableAttributes['xlink:href']

        if pathSvg.endswith('.svg'):
            if not pathSvg in inlinedFiles:
                inlineTree = etree.parse(pathSvg, parser)

                symbol = img.makeelement('symbol', id=svgId)

                symbol.extend(inlineTree.getroot())

                root.insert(0, symbol)

                inlinedFiles.add(pathSvg)

                print('Embedded '+svgId)

            settableAttributes['{http://www.w3.org/1999/xlink}href'] = '#'+svgId

        #print(etree.tostring(img.makeelement('use', attrib=img.attrib, nsmap=nsmap)))
        img.getparent().replace(img, img.makeelement('use', settableAttributes))
        #print(etree.tostring(img.makeelement('use', settableAttributes)))
        #print(etree.tostring(img.makeelement('use', attrib=img.attrib, nsmap=nsmap)))

    file=open(outputPath,'wb')
    file.write(etree.tostring(tree, pretty_print=True))
    file.close()

    return tree #.getroot()

if __name__ == "__main__":
    if len(sys.argv) == 2:
        filename, extension = os.path.splitext(os.path.basename(sys.argv[1]))
        convertedFilename = os.path.join(os.path.dirname(sys.argv[1]), filename + '-selfcontained'+extension)
        inlineSvg(sys.argv[1], convertedFilename)
    elif len(sys.argv) == 3:
        inlineSvg(sys.argv[1], sys.argv[2])
    else:
        print('Attemtps to inline svg files. Usage: python '+sys.argv[0]+' input.svg [output.svg]\n\nIf output.svg is ommitted -selfcontained will be attached to the filename. Warning:overwrites files without warning.')

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.