SVG text slightly off center - svg

I have some text on a blue rectangle, centered horizontally and vertically. It is nearly perfectly in the center, however it is slightly too high. How can I fix this so that it is perfectly centered?
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg">
<g>
<rect fill="#8080ff" height="400" width="400" y="0" x="0"/>
<text font-weight="bold" stroke="black" x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" stroke-width="0" fill="#000000">Go</text>
</g>
</svg>

Changing dominant-baseline: middle to dominant-baseline: central fixed the problem. I had to look this up but if I understand it correctly, central prioritises the ideographic baseline whereas middle prioritises the alphabetic baseline. The alphabetic baseline hugs the bottom of the text, whereas the ideographic baseline rests just below the text. This extra space also sits above the text (regardless of the baseline being used), so you have to use the ideographic baseline to accomodate for this extra space, to centre it perfectly.
I think that's how it works anyway. These are the links I used to figure it out, if you want to understand it more.
https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/dominant-baseline
What is the difference between alphabetic and ideographic in Flutter's TextBaseline enum

Related

How do I create SVG with transparent text?

I'm trying to create a svg button with transparent text. It looks like there is no way to do it easily in React-native, so maybe I'll just create an svg, that will have transparent text. How do I do that?
I've tried to do it in Figma, but it looks like the only way to do it there is to put a specific image behind. Text can't be truly transparent, it can only mask a picture.
You can use an svg mask with a white rectangle and a black text. The shapes you have - like the rectangle with rounded corners in this example - will be painted only under the white parts of the mask. Since the text is black it will apear like a hole.
svg{border:solid; background:silver;}
<svg>
<mask id="m">
<rect x="10" y="10" width="280" height="130" fill="white" />
<text x="150" y="75" font-size="90" font-family="arial" dominant-baseline="middle" text-anchor="middle" fill="black">text</text>
</mask>
<rect x="10" y="10" width="280" height="130" rx="20" mask="url(#m)" />
</svg>

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: why does y="0" start outside the viewport instead of in the top edge for text?

This is the only thing preventing me from understanding how the coordination system works...
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="600" height="400" x="0" y="0">
<text x="0" y="0">
<tspan>✉</tspan>
</text>
</svg>
If the y-axis points down why does y="0" start from the outside of the viewport? Shouldn't it start right at the top edge? It blows my logic away...
The x-axis points to the right and it starts right in the left edge of the viewport, now this is logic and normal behavior.
Why does the y-axis behave like this? Or why make one start from the outside and the other not? What is the logic behind this? Unless I'm misunderstanding how it works...
It's all in the SVG specification
the initial coordinate system has the origin at the top/left with the x-axis pointing to the right and the y-axis pointing down
The origin for text is basically the bottom left corner of the glyph for left-to-right text.
For most uses of Latin text (i.e., writing-mode:lr, text-anchor:start and alignment-baseline:baseline) the alignment-point in the glyph will be the intersection of left edge of the glyph cell (or some other glyph-specific x-axis coordinate indicating a left-side origin point) with the Latin baseline of the glyph.
default baseline is in text downside, can use attr dominant-baseline change baseline, here is mdn example
<svg viewBox="0 0 200 120" xmlns="http://www.w3.org/2000/svg">
<path d="M20,20 L180,20 M20,50 L180,50 M20,80 L180,80" stroke="grey" />
<text dominant-baseline="auto" x="30" y="20">Auto</text>
<text dominant-baseline="middle" x="30" y="50">Middle</text>
<text dominant-baseline="hanging" x="30" y="80">Hanging</text>
</svg>

SVG dominant-baseline not working in Safari

I'm trying to position SVG text so that it site entirely above the y-location at which it is located. A dominant baseline of text-after-edge appears to be the appropriate setting for this.
This works just fine in Chrome, but with Safari text-after-edge renders with the text centred around the y-location.
I explored further, as seen in this codepen:
https://codepen.io/anon/pen/obrreb?editors=1010
Here is the output in Chrome:
And in Safari:
As you can see a number of the dominant baseline renderings differ.
Jakob's suggestion to use dy is the simplest and most reliable solution. I would also suggest you use values defined in em units.
1em is the height of the font glyph from the bottom of the lowest descender to the top of the highest ascender or accent.
Descenders are typically around a quarter of an em. So to raise the text above the line use dy="-0.25em". Correspondingly, to hang below the line, use dy="0.75". See the example below.
<svg width="100%" height="200">
<line y1="100" x2="100%" y2="100" stroke="grey"/>
<text x="20" y="100" font="Arial, sans-serif" font-size="40">
<tspan>Hanging</tspan>
<tspan y="100" dy="-0.25em">Hanging</tspan>
<tspan y="100" dy="0.75em">Hanging</tspan>
</text>
</svg>
The main advantage to using em units is that they are independent of the font size. So you can tweak the value to suit your font exactly, and those em values will automatically work for any font size you specify.
I ran into the same problem recently, and found a solution that worked in my case:
After trying the dominant-baseline and baseline-shift properties only to find out that both of them don't work in all browsers I'm aiming to support, a college pointed out to me that you can use the dy attribute on a <text> element to shift it after the glyphs have been positioned along a <textPath>.
Here is some pseudocode/jsx to illustrate my solution:
<g {...}>
<path
id={textPathId}
fill="none"
transform={…}
d={…}
/>
<text
textAnchor="middle"
fill={textFill}
dy={shiftText}
>
<textPath
xmlnsXlink="http://www.w3.org/1999/xlink"
xlinkHref={`#${textPathId}`}
startOffset="50%"
>
{text}
</textPath>
</text>
</g>
Notice that this depends on the knowledge of a shiftText value that has to be known or calculated independently. If this isn't given I think the only road forward is to use a combination of dominant-baseline and baseline-shift while distinguishing the browsers used.

Avoiding the aliasing / thin "bleed between touching elements

I have an svg which has multiple stroked paths, and a rectangle shape behind them that has the shape of the paths cut out of them. Effectively, the stroked paths should be "plugging the holes" in the rectangle shape.
The reason for this is that I would like to animate the paths so that they are erased, revealing what is underneath through the holes in the rectangle shape.
That's all well and good, and the animation works fine. The problem is that there is a hairline-thin space between the holes and the outside of the path strokes, so you can see what is underneath even while the paths are still there. You can see a screen capture of that here:
How do I avoid this happening? The space is not in the svg, because making it bigger on the page still has the space hairline thin:
I figure it has something to do with the aliasing, but have no idea how to combat it. I can't just apply a thicker stroke to the paths, because then the stroke starts to bleed into the other shapes, as seen here:
What else is there to do?
You could use a mask instead of a clip-path, since masks allow using the stroke to define the masked area.
<mask id="strokemask" maskContentUnits="objectBoundingBox"
x="0" y="0" width="100%" height="100%">
<circle cx="0.5" cy="0.5" r="0.1" stroke="white" fill="white"
stroke-width="0.02"/>
<circle cx="0.5" cy="0.5" r="0.15" stroke="white" fill="none"
stroke-width="0.03"/>
<circle cx="0.5" cy="0.5" r="0.22" stroke="white" fill="none"
stroke-width="0.05"/>
<circle cx="0.5" cy="0.5" r="0.3" stroke="white" fill="none"
stroke-width="0.06"/>
</mask>
Here's a live example of an animated mask that uses some stroked circles.

Resources