Scaling when using a defined symbol - svg

I want to create a SVG graphic that loads an external SVG file, stores it as a symbol into its defs section and uses this symbol in the main section:
<svg width="1000" height="1000">
<defs>
<symbol id="sym1" width="50" height="50" viewBox="0 0 45 45">
<svg width="45" height="45">
<rect x="0" y="0" width="45" height="45" style="stroke:red;stroke-width:1;fill:none"/>
</svg>
</symbol>
</defs>
<rect x="100" y="100" width="50" height="50" style="stroke:black;stroke-width:1;fill:none"/>
<use x="100" y="100" xlink:href="#sym1"></use>
</svg>
http://jsfiddle.net/tx0rpxdf/4/
One frame condition is that I cannot change the inner SVG element, because it will be loaded from an external file.
The symbol should have a size of 50x50, so I define its width and height accordingly. I know that the inner SVG element has a size of 45x45, so I define its viewBox as "0 0 45 45".
To test the usage of the symbol, I create a black rectangle of size 50x50 and place a symbol instance at the same position. The lines should match.
But they don't match using Chrome (Chromium) on Debian Jessie. Even worse, it produces different results in Firefox and Opera.
What's the problem here and how can I produce the correct result in all browsers that support SVG?

There are a couple of issues:
a <rect> with width/height 45 and a stroke-width of 1 is not 45 user units wide, it's 46 user units wide as the stroke pokes out inside by 0.5 of a unit and outside by 0.5 of a unit on each edge. If you can't change the inner <svg> element then you won't be able to fix this issue though.
drawing two shapes one on top of another will not make one hide another as there will be antialiasing in effect. shape-rendering="optimizeSpeed" can prevent this.

Related

Inline SVG pattern repeating horizontally and scaling vertically?

I'm looking for a way to use an inline SVG pattern that scales vertically and only repeats horizontally. Does anyone know if this is possible and if so how?
I know I can makes this just using a SVG as a background-image, but I want to be able to use this SVG in a javascript/component-based workflow so inline is the best fit for that.
Here is link with my work-in-progress: https://codepen.io/devotee/pen/GRJJpKL
And some code:
<div class="divider">
<svg width="100%" height="40px">
<defs>
<pattern id="pattern" x="0" y="0" width="60" height="6" patternUnits="userSpaceOnUse">
<path fill="none" stroke="#F5A861" d="M60 5C45 5 45 1 30 1S15 5 0 5"/>
</pattern>
</defs>
<rect x="10" y="6" width="100%" height="12" fill="url(#pattern)" />
</svg>
As you can see in the link, this repeats in both directions, so setting a bigger height value does not accomplish what I want. I would like this pattern to always fill the containers height (or simply be set to a value with CSS) but repeat horizontally.
Here are some images to illustrate what I mean:
Top is wanted behaviour, bottom is unwanted behaviour:
The background scales vertically and does not repeat. It takes as much space vertically as it can (fills parent height or whatever height value it has specified)
Top is wanted behaviour, bottom is unwanted behaviour:
It does not stretch the SVG horizontally but merely repeats it.
Any ideas or input on how to achieve this?

Zero stroke-width in SVG

Having used Postscript for years, I am now learning SVG. There is a feature of PS that I have not been able to replicate so far: zero-width lines. In PS, a line with zero width is always visible: PostScript converts zero line width to the smallest printable width. On the screen, when zooming they never get any thinkness, yet are visible no matter the scale. I have used them when I wanted to render very thin lines, without worring about the final resolution I was going to use, and they turned out really useful.
However, in the official SVG docs (https://www.w3.org/TR/svg-strokes/) it says that:
A zero value causes no stroke to be painted. A negative value is invalid.
Is there a way in SVG to build zero-width lines in the sense of PostScript?
As Robert said, the nearest thing to what you want in SVG is vector-effect="non-scaling-stroke". This fixes the stroke width at 1 no matter how the SVG is scaled.
This works on Chrome and Firefox (and probably Opera - haven't checked), but AFAIK not IE/Edge.
<svg viewBox="0 0 100 100">
<rect x="10" y="10" width="80" height="80"
fill="none" stroke="black" stroke-width="1"
vector-effect="non-scaling-stroke"/>
</svg>
Note that antialiasing will come into play depending on the position of the lines. The position will be affected by the scale.
If your lines are rectilinear (horizontal or vertical), you might also want to use shape-rendering="crispEdges". This will turn off antialiasing for the shape on which it is used, resulting in sharp one-pixel lines.
<svg viewBox="0 0 100 100">
<rect x="10" y="10" width="80" height="80"
fill="none" stroke="black" stroke-width="1"
vector-effect="non-scaling-stroke" shape-rendering="crispEdges"/>
</svg>

SVG viewBox scales some elements differently than others

The client asked to scale the drawing down so I added width/height and viewBox to the SVG element. The problem is that some elements are scaled differently than others. The structure of the svg is like this:
<svg width="100%" height="100%" viewBox="0 50 700 200">
<defs>...</defs>
<g id="group-ab">
Here are the boxes (drawn with polyline) that are scaled correctly, both in their own group (group-a, group-b)
</g>
<g id="group-a-id">
<text>A</text>
<circle id="group-a-id-bg-circle"></circle>
</g>
<g id="group-B-id">
<text>B</text>
<circle id="group-b-id-bg-circle"></circle>
</g>
<path id="group-a-pattern" d="..."></path>
<path id="group-b-pattern" d="..."></path>
</svg>
The path is the pattern for the pieces. It seems that everything that isn't under the group "group-ab" isn't scaled correctly, see the image below. Normally (when not using viewBox) the pattern fills up the boxes and the A/B are centered with the boxes (same translates are done to them).
Why is this happening? One would think that it doesn't matter if elements are within groups or not if svg is scaled.

SVG in Safari: Element positioned incorrectly when translated

I am trying to make an SVG shape a dropzone, and it works perfectly in Chrome/Firefox, but when testing on Safari I have come across a strange behaviour which I am looking for a way to get round.
The problem (shown in the code below and this fiddle https://jsfiddle.net/tp5e98rs/) is that when the viewBox width/height does not match the view port width/height (the first SVG), a translated element is not positioned correctly when viewed in the inspector. If you inspect the first rectangle, the blue highlight is not directly over it, but if you inspect the second rectangle the blue highlight is, as you can see in the attached image. The difference between the two SVGs is that in one the viewbox doesn't match the view port, and in the other they do match. If you do the same in Chrome, the highlight is correct in both cases.
This is a problem for making dropzones from translated rectangles, because the dropzone ends up being where the incorrect highlight is shown.
Any ideas of a workaround to fix this problem?
<svg width="100" height="100" viewBox="0 0 200 200">
<rect transform="translate(20 20)" width="100" height="100" fill="#aaeeee"></rect>
</svg>
<svg width="100" height="100" viewBox="0 0 100 100">
<rect transform="translate(10 10)" width="50" height="50" fill="#aaeeee"></rect>
</svg>

Programmatically centering svg path

I'm working on a PHP script that generates a jpg wallpaper from an SVG-file according to the screen resolution of the visitor. The wallpaper consists of a circular gradient (rectangle) background and a path on top of it. How would you go about centering the path horizontally and vertically to the rectangle? Remember that the rectangle's size and proportions are not a constant. Should I separate the background and path to different svg files or is there an easy way to center paths? Maybe a framework?
This is easilly achieved by using nested <svg> elements and the preserveAspectRatio attribute. Put your background in the outer svg and your path in the inner one.
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%">
<rect id="background" width="100%" height="100%" fill="grey"/>
<svg preserveAspectRatio="xMidYMid meet" viewBox="0 0 30 40" width="100%" height="100%">
<g>
<circle cx="15" cy="20" r="10" fill="yellow"/>
<circle cx="12" cy="17" r="1.5" fill="black"/>
<circle cx="18" cy="17" r="1.5" fill="black"/>
<path d="M 10 23 A 8 13 0 0 0 20 23" stroke="black" stroke-width="2" fill="none"/>
</g>
</svg>
</svg>
Run this snippet and try resizing the window.
To get this to work, all you need to ensure is that the viewBox attribute on the inner <svg> element is correctly set.
If you know the coordinates of the paths, you could take the total of the x/y coordinates and divide by the number of coordinates, this will give you the average position for the coordinate set. Then, offset each coordinate by the coordinates for half the width/height of the square, plus any offset, minus the difference between the center of the coordinate set and half the width/height of the square.
This should result in your coordinates being centered within the square, I think (it is rather early here, and I've just started my first coffee, so I could be wrong). This is of course assuming you know all the variables in play (the width/height of the square, any offset applied and the coordinates of the path).

Resources