How to center SVG text vertically in IE9 - svg

In order to align text vertically in SVG one has to use the dominant-baseline attribute.
This has already been discussed on SO (Aligning text in SVG) and is part of the specification.
My problem is with IE9 which apparently does not support dominant-baseline and a bunch of other things.
Do you have any ideas on how to approximate dominant-baseline: central in IE9?
Here is a sample that works in FF and Chrome. It does not work in IE9, Opera 11. Safari on Windows doesn't support central, but supports middle which is still good.
<?xml version="1.0"?>
<svg width="300" height="300" xmlns="http://www.w3.org/2000/svg">
<path d="M 10 100 h 290" stroke="blue" stroke-width=".5" />
<text x="40" y="100" font-size="16" style="dominant-baseline: auto;">
XXX dominant-baseline: auto; XXX
</text>
<path d="M 10 200 h 290" stroke="blue" stroke-width=".5" />
<text x="40" y="200" font-family="sans-serif" font-size="15" style="dominant-baseline: central;">
XXX dominant-baseline: central XXX
</text>
</svg>

One way to accomplish this in IE is to set the position related to the size of the font:
<text font-size="WHATEVER YOU WANT" text-anchor="middle" "dy"="-.4em"> M </text>
Setting the "dy" attribute will shift the text up (if value is negative) or down (if value is positive). Setting the "text-anchor" attribute centers the text on the x axis just fine in IE. Although this might hackish, but so is IE's support of SVG!

This is a giant hack, but we can approximate the vertical middle position by taking the font size into account.
The specification defines central like that:
central
This identifies a computed baseline
that is at the center of the EM box.
We can take an EM box of known font size and measure its bounding box to compute the center.
<?xml version="1.0"?>
<svg width="300" height="300" xmlns="http://www.w3.org/2000/svg">
<path d="M 10 100 h 290" stroke="blue" stroke-width=".5" />
<text id="default-text" x="20" y="100" font-size="5em">
M
</text>
<script>
window.onload = function() {
var text = document.getElementById("default-text"),
bbox = text.getBBox(),
actualHeight = (100 - bbox.y),
fontSize = parseInt(window.getComputedStyle(text)["fontSize"]),
offsetY = (actualHeight / 2) - (bbox.height - fontSize);
text.setAttribute("transform", "translate(0, " + offsetY + ")");
}
</script>
<path d="M 10 200 h 290" stroke="blue" stroke-width=".5" />
<text id="reference-text" x="20" y="200" font-size="5em"
style="dominant-baseline: central;">
M
</text>
</svg>
Obviously, the code can be much cleaner, but this is just a proof-of-concept.

You could try baseline-shift to see if that works in IE9:
<?xml version="1.0"?>
<svg width="300" height="500" xmlns="http://www.w3.org/2000/svg">
<path d="M 10 100 h 290" stroke="blue" stroke-width=".5" />
<text x="40" y="100" font-size="16" style="dominant-baseline: auto;">
XXX dominant-baseline: auto; XXX
</text>
<path d="M 10 200 h 290" stroke="blue" stroke-width=".5" />
<text x="40" y="200" font-family="sans-serif" font-size="15" style="dominant-baseline: central;">
XXX dominant-baseline: central XXX
</text>
<path d="M 10 300 h 290" stroke="blue" stroke-width=".5" />
<text x="40" y="300" font-family="sans-serif" font-size="15">
<tspan style="baseline-shift:-30%;">
XXX baseline-shift: -30% XXX
</tspan>
</text>
</svg>
Firefox doesn't seem to support baseline-shift though, but Webkit and Opera do.

Related

SVG feImage filter clips stroke - how to stop that?

I'm trying to create a filter for only the stroke of path in SVG, but the feImage keeps getting clipped to what I assume is that bounding box (see green rectangle below in code as bounding box). I've tried setting the filter's x/y and width/height to all sorts of positions/sizes, as seems to be the prevailing advice, but nothing works. x/y just offset the feImage and width/height of greater than 100% has no effect.
I won't know in advance if the stroke or fill is solid or something else (like linearGradient.
The below demonstrates what I'm looking to do - just get the stroke of a shape (regardless of size or fill) and apply a filter to it for all modern browsers.
Notes: FF doesn't even display the left-hand feImage. Chrome clips the left and top. Edge clips all 4 sides.
<html>
<body>
<svg width="960" height="540" >
<rect width="960" height="540" stroke="#385D8A" fill="white" stroke-width="3"/>
<svg name="BoundingBox1" class="rect" x="100" y="100" overflow="visible" fill="none" stroke="#00ff00" stroke-width="1">
<path d="M0,0L121.68,0L121.68,121.68L0,121.68Z" />
</svg>
<!-- BELOW IS MODIFIED "SPEECH" SHAPE. NEED A FILTER ON THE STROKE ONLY. -->
<svg name="Speech-strokeonly" x="100" y="100" overflow="visible" fill="blue" stroke="orange" stroke-width="12" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path d="M35.541,137.084L30.985,113.988A60.926,60.926 0 1 1 53.043,121.34Z" id="strokeOnly" fill="none"/>
<filter id="effs0sp9" color-interpolation-filters="sRGB" x="0" y="0">
<feImage xlink:href="#strokeOnly" />
</filter>
</defs>
<path d="M35.541,137.084L30.985,113.988A60.926,60.926 0 1 1 53.043,121.34Z" id="effs0sp9" filter="url(#effs0sp9)" overflow="visible" />
<text y="160" stroke="black" stroke-width="0.2">this is the one that has undesirable clipping of</text>
<text y="178" stroke="black" stroke-width="0.2">of stroke in Chrome/Edge. Doesn't appear at all in FF</text>
</svg>
<!-- BELOW IS ORIGINAL WITH BOTH FILL AND STROKE -->
<svg name="BoundingBox2" x="400" y="100" overflow="visible" fill="none" stroke="green" stroke-width="1">
<path d="M0,0L121.68,0L121.68,121.68L0,121.68Z" />
</svg>
<svg name="Speech-original" x="400" y="100" overflow="visible" fill="blue" stroke="orange" stroke-width="12">
<path d="M35.541,137.084L30.985,113.988A60.926,60.926 0 1 1 53.043,121.34Z" id="effs0sp9" />
<text y="-22" stroke="black" stroke-width="0.2">this is the original one</text>
<text y="-6" stroke="black" stroke-width="0.2">that I just want the stroke as feImage from</text>
</svg>
</svg>
</body>
</html>
Is there a way to grab the whole stroke of a shape only and use in a filter?

What is the expected image for this SVG file?

I am trying to make a plot with each data point labelled on the x-axis with a text string written vertically. It all seems to work as expected except that the labels appear to be shifted left by one unit. I thought it was a bug in my c++ generating code but finally made a test case. In the following, I wanted the red and blue lines to underline the text but they
seem to go through the text. The green lines, offset by one, appear where
I thought the others should be relative to the text. What do I need to do here? The center of rotation does appear as expected. Thanks. Right now I'm just kluging the code to fix the offset but would like to fix it lol.
<svg style="overflow: hidden; -moz-user-select: none; cursor: default; position: relative; background-color: rgb(255, 255, 255);" xmlns="http://www.w3.org/2000/svg" width="100" version="1.1" height="100"><desc> test foo</desc>
<rect fill-opacity="1" stroke-width="1" stroke-opacity="1" style="" stroke="#00808080" fill="#00808080" ry="0" rx="0" r="0" height="100" width="100" y="0" x="0"></rect> <g fill="none" stroke="red" stroke-width="1000" />
<path d="M0 0 L50 0 " stroke="black" stroke-width="1"/>
<text font-weight="normal" font-size="5" font-family="Verdana" fill="#00ffffff" y="10" x="10" style="dominant-baseline: alphabetic; text-anchor: start;">unidentified</text>
<text transform="rotate(90,10,10)" font-weight="normal" font-size="5" font-family="Verdana" fill="#00ffffff" y="10" x="10" style="dominant-baseline: alphabetic; text-anchor: start;">unidentified</text>
<text transform="rotate(45,10,10)" font-weight="normal" font-size="5" font-family="Verdana" fill="#00ffffff" y="10" x="10" style="dominant-baseline: alphabetic; text-anchor: start;">unidentified</text>
<path d="M0 10 L100 10 " stroke="red" stroke-width=".1"/>
<path d="M0 11 L100 11 " stroke="green" stroke-width=".1"/>
<path d="M10 0 L10 100 " stroke="blue" stroke-width=".1"/>
<path d="M9 0 L9 100 " stroke="green" stroke-width=".1"/>
</svg>

SVG text shifted on Edge and IE

I have isolated a case where SVG text is shifted only on Microsoft browsers (EDGE & IE)
<svg width="360" height="186" viewBox="0 0 120 64" preserveAspectRatio="none">
<text font-size="72" font-family="Arial" fill="#ff2204" x="0" y="62" text-anchor="middle">
<tspan x="50%" dy="0">test</tspan>
</text>
</svg>
Is there a way to make the text centered as in other browsers?
Here's the sample code in jsfiddle
Removing the whitespace between </tspan> and </text> resolves the issue
<svg width="360" height="186" viewBox="0 0 120 64" preserveAspectRatio="none">
<text font-size="72" font-family="Arial" fill="#ff2204" x="0" y="62" text-anchor="middle">
<tspan x="50%" dy="0">test</tspan></text>
</svg>
jsfiddle

SVG: draw text with solid background color [duplicate]

I want to color the background of svg text similar to background-color in css
I was only able to find documentation on fill, which colors the text itself
Is it even possible?
You could use a filter to generate the background.
<svg width="100%" height="100%">
<defs>
<filter x="0" y="0" width="1" height="1" id="solid">
<feFlood flood-color="yellow" result="bg" />
<feMerge>
<feMergeNode in="bg"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<text filter="url(#solid)" x="20" y="50" font-size="50">solid background</text>
</svg>
No this is not possible, SVG elements do not have background-... presentation attributes.
To simulate this effect you could draw a rectangle behind the text attribute with fill="green" or something similar (filters). Using JavaScript you could do the following:
var ctx = document.getElementById("the-svg"),
textElm = ctx.getElementById("the-text"),
SVGRect = textElm.getBBox();
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("x", SVGRect.x);
rect.setAttribute("y", SVGRect.y);
rect.setAttribute("width", SVGRect.width);
rect.setAttribute("height", SVGRect.height);
rect.setAttribute("fill", "yellow");
ctx.insertBefore(rect, textElm);
The solution I have used is:
<svg>
<line x1="100" y1="100" x2="500" y2="100" style="stroke:black; stroke-width: 2"/>
<text x="150" y="105" style="stroke:white; stroke-width:0.6em">Hello World!</text>
<text x="150" y="105" style="fill:black">Hello World!</text>
</svg>
A duplicate text item is being placed, with stroke and stroke-width attributes. The stroke should match the background colour, and the stroke-width should be just big enough to create a "splodge" on which to write the actual text.
A bit of a hack and there are potential issues, but works for me!
Instead of using a <text> tag, the <foreignObject> tag can be used, which allows for XHTML content with CSS.
No, you can not add background color to SVG elements. You can do it programmatically with d3.
var text = d3.select("text");
var bbox = text.node().getBBox();
var padding = 2;
var rect = self.svg.insert("rect", "text")
.attr("x", bbox.x - padding)
.attr("y", bbox.y - padding)
.attr("width", bbox.width + (padding*2))
.attr("height", bbox.height + (padding*2))
.style("fill", "red");
Answer by Robert Longson (#RobertLongson) with modifications:
<svg width="100%" height="100%">
<defs>
<filter x="0" y="0" width="1" height="1" id="solid">
<feFlood flood-color="yellow"/>
<feComposite in="SourceGraphic" operator="xor"/>
</filter>
</defs>
<text filter="url(#solid)" x="20" y="50" font-size="50"> solid background </text>
<text x="20" y="50" font-size="50">solid background</text>
</svg>
and we have no bluring and no heavy "getBBox" :)
Padding is provided by white spaces in text-element with filter.
It's worked for me
Going further with #dbarton_uk answer, to avoid duplicating text you can use paint-order=stroke style:
<svg>
<line x1="100" y1="100" x2="350" y2="100" style="stroke:grey; stroke-width: 100"/>
<text x="150" y="105" style="stroke:white; stroke-width:0.5em; fill:black; paint-order:stroke; stroke-linejoin:round">Hello World!</text>
</svg>
Note the stroke-linejoin:round which is needed to avoid seeing spikes for the W sharp angle.
You can combine filter with the text.
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>SVG colored patterns via mask</title>
</head>
<body>
<svg viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter x="0" y="0" width="1" height="1" id="bg-text">
<feFlood flood-color="white"/>
<feComposite in="SourceGraphic" operator="xor" />
</filter>
</defs>
<!-- something has already existed -->
<rect fill="red" x="150" y="20" width="100" height="50" />
<circle cx="50" cy="50" r="50" fill="blue"/>
<!-- Text render here -->
<text filter="url(#bg-text)" fill="black" x="20" y="50" font-size="30">text with color</text>
<text fill="black" x="20" y="50" font-size="30">text with color</text>
</svg>
</body>
</html>
this is my favorite hack (not sure it should work). It refer an element that is not yet displayed, and it works pretty well
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 620 40" preserveAspectRatio="xMidYMid meet">
<defs>
<filter x="-0.02" y="0" width="1.04" height="1.1" id="removebackground">
<feFlood flood-color="#00ffff"/>
</filter>
</defs>
<!--Draw the text-->
<use xlink:href="#mygroup" filter="url(#removebackground)" />
<g id="mygroup">
<text id="text1" x="9" y="20" style="text-anchor:start;font-size:14px;">custom text with background</text>
<line x1="200" y1="18" x2="200" y2="36" stroke="#000" stroke-width="5"/>
<line x1="120" y1="27" x2="203" y2="27" stroke="#000" stroke-width="5"/>
</g>
</svg>
For those wondering how to apply padding to a text element when it has a background like in the Robert's answer, do the following:
<svg>
<defs>
<filter x="-0.1" y="-0.1" width="1.2" height="1.2" id="solid">
<feFlood flood-color="#171717"/>
<feComposite in="SourceGraphic" operator="xor" />
</filter>
</defs>
<text filter="url(#solid)" x="20" y="50" font-size="50">Hello</text>
</svg>
In the example above, filter's x and y positions can be used as transform: translate(-10%, -10%) would, and width and height values can be read as 120% and 120%. So we made background 20% bigger, and offsetted it -10%, so background is now 10% bigger on each side of the text.
The previous answers relied on doubling up text and lacked sufficient whitespace.
By using atop and I was able to get the results I wanted.
This example also includes arrows, a common use case for SVG text labels:
<svg viewBox="-105 -40 210 234">
<title>Size Guide</title>
<defs>
<filter x="0" y="0" width="1" height="1" id="solid">
<feFlood flood-color="white"></feFlood>
<feComposite in="SourceGraphic" operator="atop"></feComposite>
</filter>
<marker id="arrow" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z"></path>
</marker>
</defs>
<g id="garment">
<path id="right-body" fill="none" stroke="black" stroke-width="1" stroke-linejoin="round" d="M0 0 l30 0 l0 154 l-30 0"></path>
<path id="right-sleeve" d="M30 0 l35 0 l0 120 l-35 0" fill="none" stroke-linejoin="round" stroke="black" stroke-width="1"></path>
<use id="left-body" href="#right-body" transform="scale(-1,1)"></use>
<use id="left-sleeve" href="#right-sleeve" transform="scale(-1,1)"></use>
<path id="collar-right-top" fill="none" stroke="black" stroke-width="1" stroke-linejoin="round" d="M0 -6.5 l11.75 0 l6.5 6.5"></path>
<use id="collar-left-top" href="#collar-right-top" transform="scale(-1,1)"></use>
<path id="collar-left" fill="white" stroke="black" stroke-width="1" stroke-linejoin="round" d="M-11.75 -6.5 l-6.5 6.5 l30 77 l6.5 -6.5 Z"></path>
<path id="front-right" fill="white" stroke="black" stroke-width="1" d="M18.25 0 L30 0 l0 154 l-41.75 0 l0 -77 Z"></path>
<line x1="0" y1="0" x2="0" y2="154" stroke="black" stroke-width="1" stroke-dasharray="1 3"></line>
<use id="collar-right" href="#collar-left" transform="scale(-1,1)"></use>
</g>
<g id="dimension-labels">
<g id="dimension-sleeve-length">
<line marker-start="url(#arrow)" marker-end="url(#arrow)" x1="85" y1="0" x2="85" y2="120" stroke="black" stroke-width="1"></line>
<text font-size="10" filter="url(#solid)" fill="black" x="85" y="60" class="dimension" text-anchor="middle" dominant-baseline="middle"> 120 cm</text>
</g>
<g id="dimension-length">
<line marker-start="url(#arrow)" marker-end="url(#arrow)" x1="-85" y1="0" x2="-85" y2="154" stroke="black" stroke-width="1"></line>
<text font-size="10" filter="url(#solid)" fill="black" x="-85" y="77" text-anchor="middle" dominant-baseline="middle" class="dimension"> 154 cm</text>
</g>
<g id="dimension-sleeve-to-sleeve">
<line marker-start="url(#arrow)" marker-end="url(#arrow)" x1="-65" y1="-20" x2="65" y2="-20" stroke="black" stroke-width="1"></line>
<text font-size="10" filter="url(#solid)" fill="black" x="0" y="-20" text-anchor="middle" dominant-baseline="middle" class="dimension"> 130 cm </text>
</g>
<g title="Back Width" id="dimension-back-width">
<line marker-start="url(#arrow)" marker-end="url(#arrow)" x1="-30" y1="174" x2="30" y2="174" stroke="black" stroke-width="1"></line>
<text font-size="10" filter="url(#solid)" fill="black" x="0" y="174" text-anchor="middle" dominant-baseline="middle" class="dimension"> 60 cm </text>
</g>
</g>
</svg>
An obvious workaround to the problem of the blur produced by the filter effect is to render the <text> two times: once for the background (with transparent characters) and once for the characters (without a background filter).
For me, this was the only way to make the text readable in Safari.
<svg width="100%" height="100%">
<filter x="0" y="0" width="1" height="1" id="solid">
<feFlood flood-color="yellow" />
</filter>
<g transform="translate(20, 50)" font-size="50">
<text aria-hidden="true" fill="none" filter="url(#solid)">solid background</text>
<text fill="blue">solid background</text>
</g>
</svg>
The aria-hidden="true" attribute is there to prevent screen readers from speaking the text twice, if the user uses a screen reader.
You can add style to your text:
style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
text-shadow: rgb(255, 255, 255) -2px -2px 0px, rgb(255, 255, 255) -2px 2px 0px,
rgb(255, 255, 255) 2px -2px 0px, rgb(255, 255, 255) 2px 2px 0px;"
White, in this example.
Does not work in IE :)

Adding new nodes to an SVG <use> tag

Just playing the defs and use for the first time. What I would like to do is use defs to build a base template and then when I use it customise it by adding some more inner tags.
Is this possible, as when I try to do it in Firefox it doesn't render any tags which I place as children to the use tag. e.g:
<?xml version="1.0" standalone="no"?>
<svg width="10000px" height="5500px" version="1.1"
baseProfile="full"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<g id="storyCard">
<rect width="800px" height="500px" fill="#ffff00" />
<path d="M 20 120 L 780 120 M 640 20 L 640 120" stroke-width="6px" stroke="black" fill="none" />
</g>
</defs>
<!-- White Board -->
<path d="M 0 0 L 10000 0 L 10000 550 L 0 5500 z M 2000 0 L 2000 5500" stroke-width="20px" stroke="black" fill="none" />
<use xlink:href="#storyCard" transform="translate(100,100)" />
<use xlink:href="#storyCard" transform="translate(1000,200)" >
<text x="20" y="80" font-size="45" font-weight="bold" font-family="Comic Sans MS, cursive">
My Dummy Story
</text>
</use>
</svg>
It isn't directly possible with use. The use element can contain description and animation elements, but it doesn't behave like an XBL container.
You could use XBL, if you're targeting firefox.
To stay within SVG, make a group and use the background:
<g transform="translate(1000,200)" >
<use xlink:href="#storyCard"/>
<text x="20" y="80" font-size="45" font-weight="bold" font-family="Comic Sans MS, cursive">
My Dummy Story
</text>
</g>

Resources