btel / svg_utils Goto Github PK
View Code? Open in Web Editor NEWPython tools to create and manipulate SVG files
License: MIT License
Python tools to create and manipulate SVG files
License: MIT License
Like the title says, copying e.g. a svgutils.transform.GroupElement
returns a lxml.etree._Element
, not a new GroupElement
. That means that if you try to copy an Element instead of reading it from disk again, the function does not return a copy of the Element, like the documentation says.
Honestly, I don't understand how this could ever be the desired behavior. This way, if you want to include the same SVG twice on two different spots of the figure, you need to pass the result of copy to the appropriate constructor before you can move it.
So I propose that either the documentation should reflect this, or the behaviour should be changed to be in line with what you would naturally expect.
See Stack Overflow post by user Allan for a complete description: https://stackoverflow.com/questions/45850144/is-there-a-bug-in-svgutils-compose-module-regarding-the-arrangement-of-figures/45863869#45863869
Here is a one-liner that reproduces the problem:
from svgutils import *
Figure( "21cm", "29.7cm",
Text("A"),
Text("B"),
).tile(1,2)
I propose the following fix in function svgutils.compose.Figure.tile()
:
dx = self.width.to('px').value/ncols
dy = self.height.to('px').value/nrows
template module allows for easy laying out the figure panels. It's useful, but not documented. Submit a PR that would add doc of the API and possibly a tutorial
you will need to learn a bit about sphinx: https://www.sphinx-doc.org/en/master/usage/quickstart.html and sphinx auto doc module: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html
to see the examples checkout the docs directory in svg_utils repository
Hi @btel,
do we still need issue label hacktoberfest
? I feel like it's consuming attenion without any value by now. What do you think?
Best, Sebastian
Thanks a lot for this really useful package!
Just want to report that the workaround mpl units bug, i.e. to set the size of the SVG in pixel instead of points, seems to be unnecessary. In this matplotlib commit, the units were changed on purpose.
Indeed, in the current version the resulting SVG (e.g. when opened in Inkscape) does not have the size that I specified as a figsize
in matplotlib. Undoing the workaround, e.g. using
w_svg, h_svg = svg_mpl.get_size()
svg_mpl.set_size((w_svg+'pt', h_svg+'pt'))
yields a SVG where the size agrees with the figsize
.
I assume there was a good reason to introduce the workaround so I am posting this as an issue here. Of course I would be willing to create a PR.
The following code outputs the desired plot in Jupyter Lab, but the saved graphic is blank.
sc.Figure("18cm", "9cm",
sc.Panel(
sc.SVG("fig1"),
sc.Text("A", 8, 18, size=14, font='sans')
),
sc.Panel(
sc.SVG("fig2"),
sc.Text("B", 8, 18, size=14, font='sans')
)
).save('test')
This seems to be an issue with lxml
. I couldn't report issue on their repo. The same file could be parsed by python's inbuilt xml.etree.ElementTree.parse(fpath)
without any issue. May be svg_file = etree.parse(fid, parser=etree.XMLParser())
in line 354 of transform.py could be modified to resolve this.
OS: Ubuntu 20.04 LTS
python: 3.8
I installed within a conda environment with:
sudo apt-get install libxml2-dev libxslt-dev
pip3 install svgutils --user
#!/usr/bin/env python
#coding=utf-8
from svgutils.compose import *
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(1, figsize=(4,4))
ax.plot(np.sin(np.linspace(0,2.*np.pi)), np.cos(np.linspace(0,2.*np.pi)))
fig.savefig('cover.svg')
Figure("16cm", "6.5cm",
Panel(SVG('cover.svg')),
).save("composed.svg")
cover.svg
looks correct, but composed.svg
is a blank canvas. Interestingly, it works when using svgutils.transform
Downgrading to v0.3.1
works:
pip install svgutils==0.3.1
Is there anything I am doing obviously wrong or is this a bug?
Hi!
I'm a happy user of svgutils
with my CLI tool xiangqi-setup; thank you for developing and sharing it as Open Source (or Software Libre) 🙏
I have an explicit svgutils==0.3.1
in my setup.py file (due to API incompatible changes on Git master after 0.3.1, which I accept and which are not my concern here). Today I noticed that CI started failing with 0.3.1 and it turns out that a new wheel svgutils-0.3.1-py3-none-any.whl
was upload for 0.3.1 that contains post-0.3.1 code, see https://pypi.org/project/svgutils/0.3.1/#files. I'll be working on migrating my code to 0.3.2 shortly but I can only fix it for future releases. Could you delete that wheel file please to unbreak past and current releases of existing software depending on svgutils==0.3.1
? Thanks in advance! 🙏
Best, Sebastian
Updating from 0.3.1 to 0.3.4, renamed 'scale_xy' method to 'scale', however, the documentation still lists the old method name.
Hi!
The oldest version of Python that has not reached end of life as of today is Python 3.6. The svgutils code base currently supports 2.7 and 3.5 officially and in practice. I have seen people have vastly different opinions on which versions of Python should be supported by a project in 2021, before. So I would just like to hear what the current plan or approach about end-of-life Python is, and then see how and if I can help.
A benefit of dropping support for old versions, is that we could e.g. start using f-strings and type hints in this code base if we wanted, in general.
Best, Sebastian
heres's error log:
UnicodeDecodeError Traceback (most recent call last)
<ipython-input-15-49ac8e8f02dd> in <module>
7 sc.Figure('8cm','8cm',
8 sc.Panel(sc.SVG(r'NACA0012.svg')),
----> 9 sc.Panel(sc.SVG(r'CaseVerify.svg')))
c:\python37\lib\site-packages\svgutils\compose.py in __init__(self, fname)
108 def __init__(self, fname):
109 fname = os.path.join(CONFIG['svg.file_path'], fname)
--> 110 svg = _transform.fromfile(fname)
111 self.root = svg.getroot().root
112
c:\python37\lib\site-packages\svgutils\transform.py in fromfile(fname)
308 fig = SVGFigure()
309 fid = open(fname)
--> 310 svg_file = etree.parse(fid)
311 fid.close()
312
src\lxml\etree.pyx in lxml.etree.parse()
src\lxml\parser.pxi in lxml.etree._parseDocument()
src\lxml\parser.pxi in lxml.etree._parseFilelikeDocument()
src\lxml\parser.pxi in lxml.etree._parseDocFromFilelike()
src\lxml\parser.pxi in lxml.etree._BaseParser._parseDocFromFilelike()
src\lxml\parser.pxi in lxml.etree._ParserContext._handleParseResultDoc()
src\lxml\parser.pxi in lxml.etree._handleParseResult()
src\lxml\etree.pyx in lxml.etree._ExceptionContext._raise_if_stored()
src\lxml\parser.pxi in lxml.etree._FileReaderContext.copyToBuffer()
UnicodeDecodeError: 'gbk' codec can't decode byte 0xbc in position 4126: illegal multibyte sequence
i add encoding
args to transform.py
fix the problem:
309c309
< fid = open(fname,encoding='utf-8')
---
> fid = open(fname)
The recently released 0.3.2 has resulted in the following (truncated) traceback:
File "/usr/local/miniconda/lib/python3.7/site-packages/niworkflows/viz/utils.py", line 360, in compose_view
fig = SVGFigure(width, heights[:nsvgs].sum())
File "/usr/local/miniconda/lib/python3.7/site-packages/svgutils/transform.py", line 242, in __init__
self._width = width.value
AttributeError: 'numpy.int64' object has no attribute 'value'
Looking at the docs for SVGFigure, a numeric value seems appropriate. I'm not sure what sort of object with a .value
attribute is expected to fix our invocation, so currently I'm pinning 0.3.1.
Issue introduced in #27.
We need to make sure that SVG files generated with svg_utils are compliant with the SVG standard. To do that, we could use an existing validator tool such as:
https://github.com/svenkreiss/html5validator
This should be integrated with the unit tests and continous integration (CI) system
You will need to:
note that you can (and probably should) split these tasks into a few PRs
Documentation is essential for every project. Read through the docs and find any errors, undocumented functions and maybe add more tutorials, examples
the docs are available at https://svgutils.readthedocs.io/en/latest/index.html, go through them and if you want to modify anything check click on "edit on github" in the upper right corner and edit the file directly through github interface. you don't even need to clone the repository locally, most things can be done from the browser.
if you haven't used sphinx and restructured text check out the tutorials:
https://www.sphinx-doc.org/en/master/usage/quickstart.html
https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
Width and height aren't correctly set in the XML if transform.SVGFigure
is created directly:
import svgutils
svgutils.transform.SVGFigure("10cm", "16cm").to_str()
prints
b'<?xml version=\'1.0\' encoding=\'ASCII\' standalone=\'yes\'?>\n<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"/>\n'
This code is used in the tutorial:
Therefore I expect that this is not an intended behavior.
from #13
matplotlib generates figures with those attached (relevant parts)
<?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">
<!-- Created with matplotlib (http://matplotlib.org/) -->
<svg height="144pt" version="1.1" viewBox="0 0 144 144" width="144pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
*{stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:100000;}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 144
the pt unit is unable to be parsed.
I also have to monkeypatch the Units class and fix a few other things
from svgutils.compose import Unit, SVG, Image, Figure, Text, CONFIG
# monkey patch so we can use upstream branch
Unit.per_inch = {'px': 90, 'cm': 2.54, 'mm':25.4}
and
# sometimes inkscape writes the source svg with px units, sometimes with mm, sometimes with cm, and
try:
wpx = str(Unit(tmpl.width).to('px'))
except AttributeError:
# sometimes with no units
wpx = '%dpx' % float(tmpl.width)
try: hpx = str(Unit(tmpl.height).to('px'))
except AttributeError: hpx = '%dpx' % float(tmpl.height)
# and now we know that the string is 123.456px we have to remove the px because the library regex only
# handles integers
wpx = str(int(float(wpx[:-2]))) + 'px'
hpx = str(int(float(hpx[:-2]))) + 'px'
Figure(wpx, hpx, *bits).save(args.dest)
sorry about the mess, this is pulled out of bits of code for generating figures for a paper
I reduced a file generated by Batik to the following minimal example:
<svg style="stroke:black" width="1000" height="1000" >
<line style="fill:none" x1="900" x2="100" y1="100" y2="900" />
</svg>
As interpreted by Inkscape, this contains a visible line. I tried to use this as follows:
import svgutils.compose as sc
sc.Figure( "16in", "16in", sc.SVG("input.svg") ).save("output.svg")
This produces an output.svg
with the following content:
<?xml version='1.0' encoding='ASCII' standalone='yes'?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="16.0in" viewBox="0 0 16.0in 16.0in" height="16.0in">
<g>
<g><line style="fill:none" x1="900" x2="100" y1="100" y2="900"/>
</g>
</g>
</svg>
Here, the line is not visible anymore since the stroke:black
style has gone.
I am not sure whether this is a problem with Batik not producing standard-conforming SVGs or a problem with SVG utils.
This issue seems related to it, but the issue as well as the answer are somewhat cryptic.
svgutils can be installed with pip, but also with conda. For now, the docs only cover installation with pip. Add instructions for installing with conda (from conda-forge channel)
conda install svgutils -c conda-forge
check PR #45 for further links on editting sphinx docs
Unicode characters were not parsed correctly. I could fix this by reading the file in binary within transform.fromfile()
Change open(fname)
to open(fname, 'rb')
svg_utils is useful to add annotations to existing SVG images (or even bitmaps). For now, we are supporting a few annotation primitives (like lines, texts), but it would be nice to add a few more.
for more info about SVG check out the mozilla tutorial:
Hi,
I'm trying to open a svg file as a template and position objects programmatically.
I expected a new file with the rectangle and the circle but not the triangle (that worked), where circle and rectangle are placed at the same coordinate in the top left corner (that didn't work).
instead, when looking to the resulting svg i can see that elements get the "translate()" transformation, which is move by 20 to the right and 20 to bottom.
import svgutils.transform as sg
from svgutils.compose import Unit
import sys
ovWdth = Unit('1080px')
ovHght = Unit('768px')
fig = sg.SVGFigure(ovWdth,ovHght)
#load template
tmpl = sg.fromfile('SVGTest.svg')
#get objects
boxObj = tmpl.find_id('rect846')
circObj = tmpl.find_id('path1076')
print(boxObj)
#move objects
boxObj.moveto(20,20)
circObj.moveto(20,20)
#append to figure
fig.append([boxObj, circObj])
#save figure
fig.save('SVGTest_Output.svg')
conda-forge is a repository of packages that can be installed using conda tool:
conda install my-package-name -c conda-forge
If you ever used anaconda distirbution on your computer, you are probably familiar with conda and conda-forge
For the moment, svg_utils can be only installed via pip, but not conda.
You will need to submit a PR to stage-recipes repository of conda-forge organisation:
https://github.com/conda-forge/staged-recipes
For details, how to create a package, check conda-build docs, for instructions of submitting to conda-forge, check out the README in staged-recipies repository.
E.g.
svg-utils join a.svg b.svg > c.svg
svg-utils join --horizontal a.svg b.svg > c.svg
In the height and width setters in transform.py, shouldn't viewbox
read viewBox
?
https://developer.mozilla.org/en/docs/Web/SVG/Attribute/viewBox
clearly shows the correct name to be viewBox
.
This matters particularly for old browsers which do not understand viewbox
but do understand viewBox
.
it would be nice to have more examples that can be downloaded and adapted by the user, this examples should be shown in an easy to browse gallery like the one for matplotlib:
https://matplotlib.org/gallery/index.html
Hey,
this example does not work because the following file is not there:
'../tests/circle.svg'
When exporting an svg from Adobe Illustrator, by default, "responsive" is ticked and no width or height tag is set. In that case, a vague error is thrown. I think it would really help a new user if there was a check and an appropriate message if the svg has no width or height tag.
import svgutils.compose as sc
sc.SVG(os.path.join(file_directory, 'face_border resp.svg'))
File "[main]", line 55, in
sc.SVG(os.path.join(file_directory, 'face_border resp.svg'))
File "C:\Users\keith\miniconda3\lib\site-packages\svgutils\compose.py", line 122, in init
if svg.width.endswith("%"):
AttributeError: 'NoneType' object has no attribute 'endswith'
Thanks!
It'd be great to have the option to rotate the TextElement objects using svgutils! Very helpful for Y axis labels on graphs.
Hi @btel ,
due to my rendering regression tests in xiangqi-setup I noticed that things broke when trying to upgrade to svgutils 0.3.3. What happens is that the width, height, and viewBox parameters are no longer included in the written SVG file. Here's what that looks like in Inkscape: The dotted line is where the viewbox was with svgutils 0.3.2, the solid frame is where the new defacto viewbox is:
Here's a diff of before and after, with regard to the SVG output.
--- tests/actual-board-a4_blank_2cm_margin_BEFORE.svg 2021-02-10 11:22:04.300605915 +0100
+++ tests/actual-board-a4_blank_2cm_margin_AFTER.svg 2021-02-10 11:21:49.446494230 +0100
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='ASCII' standalone='yes'?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="248.03149580999997px" viewBox="0 0 248.03149580999997 350.78740120838086" height="350.78740120838086px">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<g>
<g transform="translate(0, 0) scale(0.33333333299287476 0.33333333299287476) "><defs id="defs4"/>
<sodipodi:namedview xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.7" inkscape:cx="359.38222" inkscape:cy="512.51453" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" inkscape:window-width="1920" inkscape:window-height="1024" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1"/>
I have a feeling that #58 is causing this: .width
and .height
(with no underscores) are no longer set?
Is that a bug or a feature? How can I make svgutils 0.3.3 put width, height and viewBox into SVG output again?
Thanks and best, Sebastian
To nurish supportive community, we need a inclusive code of coduct and a way to enforce it.
I need advice or an example for the method scale_xy() from svgutils.compose.
when I try to use it like this i get the following error:
sc.Figure("12cm", "12cm",
sc.Panel(sc.SVG("./test1.svg").scale_xy(0.558, 0.559)),
).save("./ergebnis.svg")
ERRORS:
Argument 'element' has incorrect type (expected lxml.etree._Element, got NoneType)
also the fallback that should be scaling within on axis doesnt work as well, it brings up the following error:
TypeError: must be real number, not str
I used a simple .scale_xy(1) as number, not even something with a fractional number..
actually i'm done with it :/ and can not find the clue...
if someone would help out or say that i'm trying it right that would be nice.
greetings,
Philipp
The pip package for svg_utils
was last updated over a year ago, and there have been a number of helpful commits since then. Is there anything holding back another release?
But I want to be HaPpIeErRrR… With a bigger smile! (1.25x as big) kind of like this:
So I do this:
import svgutils.transform as sg
fig = sg.fromfile('smile.svg')
mouth = fig.find_id('path1069')
mouth.scale(1.25)
fig.save('smile-scale.svg')
Now that I have your attention, I actually was able to get to a solution https://stackoverflow.com/questions/76889502/ but was wondering if I was missing options from svgutils that could have helped. In particular extracting the d
attribute or calculating the bounding box of a path.
In the following code, I wanted to create an sag with a size of 1000 by 1000. Stepping through the code the values of the width and the height were not set because they generated an exception in the Width and Height Setters. This resulted in the width and the height not being set.
Please note that I tried many combinations of values for width and height.
# create an empty image
unit_fig = svgutils.transform.SVGFigure(width=1000.0, height=1000.0)
When the code reach the setter, the line self._width = value.value would fail.
def width(self, value):
**self._width = value.value**
self.root.set("width", str(value))
self.root.set("viewBox", "0 0 %s %s" % (self._width, self._height))
I replaced self._width = value. This fixed my problem.
robert
After generating the svg file using svg_utils the colors have disappeared the result file and its turned into a black & white drawing.
Not sure where else to comment, but I just wanted to say I am using the svg_compositor branch with some sucess. I have only encountered one problem - matplotlib emits svg figures with 'pt' (not px/cm) dimensions, which break the Unit()
class.
Thanks a lot for your software!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.