SVG rectangle blurred in all browsers - svg

This SVG looks blurry in all browsers, at all zoom levels.
<svg xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="240" height="240" version="1.1">
<rect width="200" height="200" x="20" y="20"
ry="20" style="fill:#fff;stroke:#000" />
</svg>
In Chrome, Safari and Firefox it looks like this:
If you zoom in you can see that the stroke has a two pixel width, even though the default stroke width is 1px. Manually setting it to 1px does not affect the output.

This has to do with the pixel rastering. The line-width is 1px and it is centered at (20,20). It is drawn between 19.5 and 20.5 px, so that the browser has to color both pixels to "use enough ink".
Solution: Use 19.5 as coordinates to be in the pixel raster.
<svg xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="240" height="240" version="1.1">
<rect width="200" height="200" x="19.5" y="19.5"
ry="20" style="fill:#fff;stroke:#000" />
</svg>
Edit:
In the following image, the blue dot has size of 1px and is located (centered) at (1,1). All four pixels will be colored to get a pixel image that is as close as possible to the not displayable dot.

Related

Why does an svg with viewbox of different ratio to dimensions renders element in center?

I am trying to understand why in the following svg the element appears in the center
<svg xmlns="http://www.w3.org/2000/svg"
width="400" height="100"
viewBox="0 0 100 100"
style="outline: 1px solid green"
>
<rect x="0" y="0" width="75" height="100" fill="#2222FF" stroke="green"/>
</svg>
My understanding is that viewBox would take the box defined by the coordinates 0,0 - 100,100 on the svg and map it to the 400x100 image. So that should give me a top-to-bottom rectangle that reaches 3/4 of the way across. At the very least I would expect the rectangle to be all the way on the left.
I cannot for the life of me understand why the rectangle here appears in the center. What is going on?
Because the default preserveAspectRatio is xMidYMid.

Understand SVG viewport

I'm trying to understand the SVG viewport. Why are these three examples so different?
svg {
border: 2px solid red;
}
<div>
<svg>
<rect x="0" y="0" width="100" height="100" fill="blue" />
</svg>
<svg width="200" height="200">
<rect x="0" y="0" width="100" height="100" fill="blue" />
</svg>
<svg viewBox="0 0 200 200">
<rect x="0" y="0" width="100" height="100" fill="blue" />
</svg>
</div>
The first example is missing a width and height. The CSS specification says that replaced elements missing width and height values fallback to 300px x 150px so you'd see the top left 300 x 150 px of whatever you're drawing onto the canvas.
The second example has width and height so you'd see that part of the canvas.
The third example also has no width/height but oddly we're now going to use the 100% x 100% lacuna values for the width/height because we have a usable aspect ratio from the viewBox. The viewBox also scales the 200 x 200 internal co-ordinate system to fix into that canvas so everything looks bigger.

Programmatically Fix text into viewBox

I have this svg:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 ' + size + ' ' + size +'" width="'+ boxW +'" height="'+ boxH +'">
<text>Sample Text</text>
</svg>
size: A parameter that is needed to the viewBox in order to create the wrapper.
width & height: the width and height of the container of the text.
I have a function that generate this svg. The problem is that the text is not fitting into the box; the result is like this:
(Is blue due to the Chrome inspector, you can see up in the top-left corner the text being small instead of full size.
The SVG resulted is this:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 580 532">
<text x="0" y="15" style="font-family:Arial;fill:%230000ff;fill-opacity:1;font-weight:normal;font-style:normal;"
>test</text>
</svg>
The whole img is this:
<img src="data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 580 532"><text x="0" y="15" style="font-family:Arial;fill:%230000ff;fill-opacity:1;font-weight:normal;font-style:normal;">test</text></svg>" class="leaflet-marker-icon leaflet-zoom-animated leaflet-interactive" alt="" tabindex="0" style="margin-left: -298px; margin-top: -291px; width: 309px; height: 295px; transform: translate3d(683px, 317px, 0px); z-index: 317; outline: none;">
So my quesiton is: How to fit the text into the main wrapper?
You either have to:
fit the viewBox to the text, or
fit the text to the viewBox.
You are not doing either. You are not even setting a font-size.
Option 1 is not really available to you. You can measure the text if you have access to the SVG DOM, but you can't do that if you aren't in a rendering environment, like a browser.
Perhaps you could use a font loading library to get metadata about the glyphs in the font. Then calculate the size of a piece of text that way. You don't mention which language you are using to produce these SVGs, so I can't advise further on that.
So you are left with option 2. The only option that SVG has to let you fit text to a particular size, is the textLength and lengthAdjust attributes on the <text> element.
textLength
Sets a length to which you want the text to be fitted
lengthAdjust
Sets the method to be used to adjust the length. You can either stretch just the spacing between the letters, or you can stretch the letter glyphs
See the <text> section in the spec for more information
There are no options for adjusting the text height.
svg {
width: 400px;
background-color: linen;
}
<svg viewBox="0 0 200 40">
<line x1="10" y1="30" x2="190" y2="30" stroke="black" opacity="0.2"/>
<text x="10" y="30"
textLength="180"
lengthAdjust="spacing">Sample Text</text>
</svg>
<br/>
<svg viewBox="0 0 200 40">
<line x1="10" y1="30" x2="190" y2="30" stroke="black" opacity="0.2"/>
<text x="10" y="30"
textLength="180"
lengthAdjust="spacingAndGlyphs">Sample Text</text>
</svg>
If you want the font size to be a better match, then you are going to have to work out a method of calculating an approximate font size. Eg.
var numChars = text.length()
var fontSize = (desiredTextWidth / numChars) * someScalingFactor
The scaling factor will depend on your font.
This is my solution: I'm putting the text inside a <symbol>. I get the size of the text with getBBox() and use it to set the viewBox for the <symbol>. Please note that the <use> element has a width of 100%.
let bb = text.getBBox();
test.setAttributeNS(null, "viewBox", `${bb.x} ${bb.y} ${bb.width} ${bb.height}`);
*{font-size:16px;}
svg{border:1px solid;width:90vh}
body{font-family:Arial;fill:#0000ff;fill-opacity:1;font-weight:normal;font-style:normal;}
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 580 532">
<symbol id="test">
<text id="text" dominant-baseline="central" text-anchor="middle" >test</text>
</symbol>
<use id="_use" xlink:href="#test" width="100%" />
</svg>

How to reduce svg size to clipped area?

Is it possible to reduce the actual size (i.e. width and height) to the clipping? Let's see the svg below for an example:
The underlying "base" image has a size of 272x136 pixels. The clipping result has a size of 17x17 pixels. Now I would like that the resulting svg is resized to 17x17 pixels. Is that even possible?
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<clipPath id="cut-off-bottom">
<rect x="102" y="102" width="17" height="17"/>
</clipPath>
</defs>
<image xlink:href="https://openmaptiles.github.io/osm-bright-gl-style/sprite.png" clip-path="url(#cut-off-bottom)" />
</svg>
Select the area you want to see with a viewBox and then set the size of the SVG to whatever you want using the outer <svg> element's width and height
I've also added width and height attributes to the image element so it works on browsers other than Chrome/Opera.
<svg width="17px" height="17px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="102 102 17 17">
<defs>
<clipPath id="cut-off-bottom">
<rect x="102" y="102" width="17" height="17"/>
</clipPath>
</defs>
<image xlink:href="https://openmaptiles.github.io/osm-bright-gl-style/sprite.png" clip-path="url(#cut-off-bottom)" width="272px" height="136px" />
</svg>

An SVG having only definitions for another scaling SVG still needs to scale?

My SVG is width="1200" height="600" viewBox="0 0 1200 600". It uses a clipPath from defs of another SVG.
<svg class="svg-def">
<defs>
<clipPath id="clip-1"> ...
</defs>
</svg>
<svg width="1200" height="600" viewBox="0 0 1200 600">
<g clip-path="url(#clip-1)">
...
</g>
</svg>
Demo
When .svg-def does not have width="1200" height="600" viewBox="0 0 1200 600" (the first image), on window width narrower than 1200, the right side is clipped. This is not desired.
I want the second image -- the clip is just the size of the SVG. The second image is good because the <clipPath> being used is from an <svg> element with the same width="1200" height="600" viewBox="0 0 1200 600"
<svg class="svg-def">
<defs>
<clipPath id="clip-1"> ...
</defs>
</svg>
<svg class="svg-def" width="1200" height="600" viewBox="0 0 1200 600">
<defs>
<clipPath id="clip-2"> ...
</defs>
</svg>
Questions
1) In <clipPath> <rect width="100%" height="100%"/>, what is 100% relative to?
2) The first clip's display width varies with window width (when the latter is narrow than 1200px). Narrower window width = narrower display width. What is the display width relative to?
3) So if I have an SVG which has only <defs>, its <svg> tag still has to have viewbox values, so that the other SVG which uses the definitions (and which scales with window width) can have definitions in correct sizes?

Resources