PyQt: Manipulating svg files for QGraphicsSvgItem - python

My main objective is to change stroke width of a given svg file, but I see no inbuilt method to do so.
The width's are unfortunately hardcoded in the svg file, and my initial thought was to buffer the data, and edit it in memory before changing the rendered svg file. But that is just additional processing overhead.
Next, I looked into converting the svg to a dom - tree like structure, but then I might have to save it back again before loading it in the QGraphicsSvgItem, which would be a bizarre intermediate step for changing line thickness on the fly.
Here is an example svg file,
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="3.2421772mm"
height="6.1269817mm"
viewBox="0 0 3.2421772 6.1269817"
version="1.1"
id="svg12403"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="Bag.svg">
<defs
id="defs12397" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="-229.58734"
inkscape:cy="-219.85002"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1366"
inkscape:window-height="705"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata12400">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-43.736054,-84.5377)">
<g
id="use26359"
transform="matrix(0.26458333,0,0,0.26458333,-3.9887501,73.928193)">
<desc
id="desc12960">Bag</desc>
<title
id="title12962">Bag</title>
<path
d="m 180.61365,49.4094 v 13.609494 h 11.78107 V 49.4094 C 192.27,46.875236 189.67,44.872517 186.50494,44.872517 c -3.16454,0 -5.76448,2.002719 -5.89129,4.536883 z m 2.94488,-9.07316 2.94641,4.536277 2.9449,-4.536277 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.66862381"
id="path12964"
inkscape:connector-curvature="0" />
<g
style="stroke-width:1.31504369"
id="g12968"
transform="matrix(0.507302,0,0,0.50958516,89.699529,-958.6518)">
<path
d="m 179.211,1978.199 v 26.707 h 23.223 v -26.707 c -0.246,-4.972 -5.371,-8.902 -11.61,-8.902 -6.238,0 -11.363,3.93 -11.613,8.902 z m 5.805,-17.804 5.808,8.902 5.805,-8.902 z m 5.808,0 v 8.902"
style="fill:none;stroke:#000000;stroke-width:0.93203717;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path12966"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</svg>
As you can see, there is a stroke width value in the style attribute for the path.
Is there a simple less obnoxious "hack" way of achieving this?

Related

Python SVG remove all tags with specific id attribute

I have an SVG image with Model and Group as ID attribute of tag g, like
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="1141.5200729370117" version="1.1" width="1726.9000701904297" id="temp" viewBox="0 0 1726.9000701904297 1141.5200729370117">
<defs/>
<g id="Model" class="Model A">
<g class="Group">
<g id="Group-1" class="Group A">
<g id="Line" fill="#000000" stroke="#000000" style="fill-opacity: 1; stroke-opacity: 1; stroke-width: 0.2;" class="Line External"><polygon points="118.00,21.56 1293.38,21.56 1277.38,37.56 133.97,37.56 "/> </g>
<g id="Box" fill="#FFFFFF" stroke="#FFFFFF" style="fill-opacity: 1; stroke-opacity: 1; stroke-width: 0.2;" class="Box External"><polygon points="118.00,21.56 1293.38,21.56 1277.38,37.56 133.97,37.56 "/> </g>
</g>
</g>
</g>
</svg>
I want to remove all g tags which have id other than Line. I read the SVG file using:
import xml.etree.ElementTree as ET
root = ET.parse('my file.svg')
SVG_NS = "http://www.w3.org/2000/svg"
Then I create a parent map
parent_map = {c:p for p in root.iter() for c in p}
And remove those other than Line
for node in root.findall('.//{%s}g' % SVG_NS):
name = node.get('id')
if "Line" in str(name):
print('n=', node)
else:
parent_map[node].remove(node)
How do I convert back the parent_map to an SVG and save it as a png file?

SVG file. remove element

I'm trying to remove element with id "area_3". I used something like:
for node in tree.xpath('//ellipse'):
node.getparent().remove(node)
SVG example:
<svg width="600" height="600" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
<g>
<title>Layer 1</title>
<image id="svg_1" y="0" x="0"/>
<image stroke="null" xlink:href="tehplan.jpg" id="svg_5" height="587.777769" width="585.333339" y="0.578137" x="20.083334"/>
<ellipse ry="19" rx="18" id="area_2" cy="172" cx="189" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke="#000000" fill="#ffffff"/>
<ellipse id="area_3" ry="19" rx="18" cy="161" cx="275" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke="#000000" fill="#ffffff"/>
</g>
</svg>
Try this:
from lxml import etree
tree = etree.parse(open("so.svg"))
to_remove = tree.xpath("/svg:svg/svg:g/svg:ellipse[#id=\"area_3\"]",
namespaces={"svg": "http://www.w3.org/2000/svg"})[0]
g = to_remove.getparent()
g.remove(to_remove)
with open("so.out.svg", "wb") as o:
o.write(etree.tostring(tree, pretty_print=True))
Output:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="600" height="600">
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
<g>
<title>Layer 1</title>
<image id="svg_1" y="0" x="0"/>
<image stroke="null" xlink:href="tehplan.jpg" id="svg_5" height="587.777769" width="585.333339" y="0.578137" x="20.083334"/>
<ellipse ry="19" rx="18" id="area_2" cy="172" cx="189" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke="#000000" fill="#ffffff"/>
</g>
</svg>

Select SVG paths of group by group id using lxml

I am having trouble selecting a particular set of paths using lxml. The SVG structure looks like this
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with matplotlib (http://matplotlib.org/) -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="288pt" version="1.1" viewBox="0 0 432 288" width="432pt">
<defs>
<style type="text/css">
*{stroke-linecap:butt;stroke-linejoin:round;}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d=" M0 288 L432 288 L432 0 L0 0 z " style="fill:#ffffff;"/>
</g>
<g id="patch_2">
<path d=" M0 288 L432 288 L432 0 L0 0 z " style="fill:#ffffff;"/>
</g>
<g id="axes_1">
<g id="Poly3DCollection_1">
<path clip-path="url(#pe61355d493)" d=" M195.211 34.2225 L194.801 34.0894 L196.527 212.986 L196.909 212.999 z " style="fill:#0000ff;"/>
<path clip-path="url(#pe61355d493)" d=" M195.504 34.3231 L195.211 34.2225 L196.909 212.999 L197.184 213.022 z " style="fill:#0000ff;"/>
...
Its the paths listed at the bottom that I want to select and change their styles but I can't seem to get the syntax right and I fail to select the paths
ifilename = "myfig.svg"
with open( ifilename, 'r') as infile:
tree = etree.parse( infile )
elements = tree.findall(".//g[#id='Poly3DCollection_1'")
new_style = 'stroke-width:4px; stroke: linear-gradient(orange, darkblue)'
for child in elements:
child.attrib['style'] = new_style
mod_svg = 'myfigmod.svg'
tree.write(mod_svg)
EDIT
so this gets me the element I want in this instance but I would still like a specific way of getting this element
root = tree.getroot()
for child in root[1][2][0]:
child.attrib['style'] = new_style
There is no get_element_by_id in etree, so you have to use xpath, like you are doing to grab the element. I created your file and ran the code below and was able to change the style of the group.
element = tree.findall(".//{%s}g[#id='Poly3DCollection_1']" % SVG_NS)[0]
element.attrib["style"] = new_style

Python: specifying the namespace in an lxml.etree path

I'm trying to figure out how to access a specific element by id in an SVG file. I was using the python library of lxml to parse through the file, but it always comes up empty. Here is the python script I used to access the element:
#!/usr/bin/env python
from lxml import etree
XHTML_NAMESPACE = "http://www.w3.org/2000/svg"
XHTML = "{%s}" % XHTML_NAMESPACE
NSMAP = {None : XHTML_NAMESPACE}
root = etree.parse("temp.svg")
textid = "text1274"
path = ".//text[#id='" + textid + "']/title"
name = root.findtext(path=path, namespaces=NSMAP)
print name
The result is always an empty string ('None'), but no error. It believes it found what I was looking for, but what I wanted was the element text (which should have been "Wei, 771 - 661BCE."). Here is the incriminating SVG file:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
xml:space="preserve"
viewBox="0 0 54001 32400"
id="svg2"
inkscape:version="0.91 r"
sodipodi:docname="china700BC.svg"><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="692"
id="namedview2468"
showgrid="false"
inkscape:zoom="0.016419753"
inkscape:cx="17689.896"
inkscape:cy="17739.986"
inkscape:window-x="0"
inkscape:window-y="24"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<defs
id="defs4">
<filter
id="blur2">
<feGaussianBlur
id="feGaussianBlur7"
result="blur"
stdDeviation="2"
in="SourceGraphic" />
</filter>
<filter
id="blur4">
<feGaussianBlur
id="feGaussianBlur10"
result="blur"
stdDeviation="4"
in="SourceGraphic" />
</filter>
<filter
id="blur8">
<feGaussianBlur
id="feGaussianBlur13"
result="blur"
stdDeviation="8"
in="SourceGraphic" />
</filter>
<filter
id="blur16">
<feGaussianBlur
id="feGaussianBlur16"
result="blur"
stdDeviation="16"
in="SourceGraphic" />
</filter>
<filter
id="blur32">
<feGaussianBlur
id="feGaussianBlur19"
result="blur"
stdDeviation="32"
in="SourceGraphic" />
</filter>
<filter
id="blur64">
<feGaussianBlur
id="feGaussianBlur22"
result="blur"
stdDeviation="64"
in="SourceGraphic" />
</filter>
</defs>
>
<g
stroke-linecap="round"
stroke-linejoin="round"
stroke-miterlimit="7"
stroke-width="14"
fill="none"
filter="url(#blur2)"
id="fntr">
<ellipse
id="ellipse381"
fill="white"
stroke="white"
ry="1"
rx="1"
cy="0"
cx="0" />
<ellipse
id="ellipse383"
fill="white"
stroke="white"
ry="1"
rx="1"
cy="32400"
cx="54001" />
<ellipse
fill="#FEBADE"
ry="1"
rx="1"
cy="24759"
cx="48948"
id="295286-dummy" />
</g>
<g
text-anchor="middle"
id="regn">
</g>
<g
text-anchor="middle"
id="cultr">
</g>
<g
text-anchor="middle"
id="peopl">
</g>
<g
font-style="italic"
text-anchor="middle"
id="tribe">
</g>
<text
id="text455"
x="30542.088"
y="16248.173"
font-size="20"
style="font-weight:normal;font-size:233.01080322px;text-anchor:middle"><title
id="title457">Chen.</title>Chen</text>
<text
id="text1274"
x="28689.652"
y="12753.011"
font-size="28"
style="font-weight:normal;font-size:326.21511841px;text-anchor:middle"><title
id="title1276">Wei, 771 - 661BCE.</title>Wei</text>
<script
id="script2466">
function LoadHandler(event)
{
new Title(event.getTarget().getOwnerDocument(), 810);
}
</script>
</svg>
I discovered that I can eliminate the error by deleting the eighth line, beginning with "xmlns=..." (which is the namespace declaration). However, due to the nature of where I obtained this file I cannot permanently remove this line (and probably shouldn't). Is there some way (such as properly specifying the namespace) I can get the expected output without having to edit the XML at all?
Thanks a ton
Mapping default namespace to None prefix didn't work for me either. You can, however, map it to a normal string prefix and use that prefix in the xpath, the rest of your codes are working without any change :
from lxml import etree
XHTML_NAMESPACE = "http://www.w3.org/2000/svg"
XHTML = "{%s}" % XHTML_NAMESPACE
NSMAP = {'d' : XHTML_NAMESPACE} # map default namespace to prefix 'd:'
root = etree.parse("temp.svg")
textid = "text1274"
path = ".//d:text[#id='" + textid + "']/d:title" # use registered prefix in xpath
name = root.findtext(path=path, namespaces=NSMAP)
print name

Why is my base64-encoded png not visible inside my svg?

I want to convert png to base64 and insert the encoded string in an svg.
fIm = open('name.png', 'rb')
dataIm = fIm.read().encode("base64").replace('\n','')
baseIm += '<g id="%s"><image xlink:href="data:image/png;base64,%s" width="%s" height="%s"/></g>' % (newVal, dataIm, curX, curY)
The result image does not display.
What's the problem?
Here's the output svg file:
<?xml version='1.0' ?>
<svg viewBox='0 0 200 200' width='200' height='200' xmlns='http://www.w3.org/2000/svg'>
<defs><g id="name">
<image xlink:href="data:image/png;base64,..." width='20' height='20'/>
</g></defs>
<use xlink:href="#name" x='30' y='30' />
</svg>
solution
fIm = open('switchToMinus.png', 'rb')
dataIm = fIm.read().encode("base64").replace('\n','')
addText = '<image xlink:href="data:image/png;base64,{0}" width="20" height="20" x="40" y="40" />'.format(dataIm)
startSvg = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="240px" height="240px" viewBox="0 0 240 240">
"""
endSvg = """
</svg>
"""
if __name__ == '__main__':
f = open('image2.svg','w')
f.write( startSvg + addText + endSvg )
f.close()
print 'Okay!'

Categories