Is there a way to draw a rectangle around individual letters in SVG text

Is it possible to draw rectangles behind the only letters of the SVG text path? I tried to paint an image of what I'm trying to do . SVG is part of the HTML page. There seems to be no way to get to the text path using CSS, but maybe there is a way with javascript?

This is my html / svg code:

<svg id="foo" width="100%" height="100%" viewBox="-30 -220 1000 800" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <path id="MyPath" d="M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100" /> </defs> <use xlink:href="#MyPath" fill="none" stroke="black" /> <text font-family="arial" font-size="140" > <textPath xlink:href="#MyPath" startOffset="20" dy="10"> Lorem ipsum </textPath> </text> </svg> 
0
source share
3 answers

How about this ... It uses the SVG DOM to get characters, and then draws a rectangle after the character under the mouse.

 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width='400' height='400'> <script><![CDATA[ function details(evt) { var letters='Move mouse over letters...'; var pathLetters='ABCDEFGHIJKLMNOPQRSTUVWXYZ'; var svgdoc=evt.target.ownerDocument; var xm=evt.clientX; var ym=evt.clientY; var text=svgdoc.getElementById('text'); var d=text.getStartPositionOfChar(1); dx=xm; dy=ym; var p=text.getCharNumAtPosition(d); if (p >= 0) { var f=text.getExtentOfChar(p); var node=document.createTextNode('You are on character '+letters.substring(p,p+1)); var letter=svgdoc.getElementById('letter'); letter.replaceChild(node,letter.firstChild); var outline=svgdoc.getElementById('outline'); outline.setAttribute('x',fx); outline.setAttribute('y',fy); outline.setAttribute('width',f.width); outline.setAttribute('height',f.height) } var textPath=svgdoc.getElementById('textPath'); p=textPath.getCharNumAtPosition(d); if (p >= 0) { var f=textPath.getExtentOfChar(p); var node=document.createTextNode('You are on character '+pathLetters.substring(p,p+1)); var letter=svgdoc.getElementById('letter'); letter.replaceChild(node,letter.firstChild); var outline=svgdoc.getElementById('outline'); outline.setAttribute('x',fx); outline.setAttribute('y',fy); outline.setAttribute('width',f.width); outline.setAttribute('height',f.height) } } function zero(evt) { var svgdoc=evt.target.ownerDocument; var outline=svgdoc.getElementById('outline'); outline.setAttribute('x',0); outline.setAttribute('y',0); outline.setAttribute('width',0); outline.setAttribute('height',0); var letter=svgdoc.getElementById('letter'); node=document.createTextNode('You are on character '); letter.replaceChild(node,letter.firstChild); } ]]></script> <defs> <path id="s3" d="M 10,200 Q 100,125 200,180 Q 340,260 400,140" /> </defs> <rect id='outline' x='0' y='0' width='0' height='0' style='stroke:green;fill:yellow'/> <g> <text onmousemove='details(evt)' onmouseout='zero(evt)' id='text' x='200' y='100' style='text-anchor:middle;font-size:24pt;font-family:Arial;fill:red'>Move mouse over letters...</text> <text style='font-size:20pt;font-family:Arial;fill:red'><textPath onmousemove='details(evt)' onmouseout='zero(evt)' id='textPath' xlink:href="#s3">ABCDEFGHIJKLMNOPQRSTUVWXYZ</textPath> </text> <text id='letter' x='20' y='250' style='text-anchor:start;font-size:16pt;fill:blue'>You are on character </text> </g> </svg> 
+1
source

You will need Javascript, but it is not as complicated as the previous examples.

All text elements have a set of corresponding interface functions for searching for each character:

http://www.w3.org/TR/SVG11/text.html#InterfaceSVGTextContentElement

The easiest way is to use getExtentOfChar(i) to find the rectangle rectangle for each character symbol. This is the approach used in the @Robert Longson example. Without all the extra event handling code, you can simplify it:

 var texts = document.getElementsByClassName("backgroundRect"); var svgNS ="http://www.w3.org/2000/svg"; for (var i=0, max= texts.length; i<max; i++) { var t = texts[i]; var g = document.createElementNS(svgNS, "g"); for (var j=0, nchar=t.getNumberOfChars(); j<nchar; j++) { var r = t.getExtentOfChar(j); var re = document.createElementNS(svgNS, "rect"); re.setAttribute("width", r.width); re.setAttribute("height", r.height); re.setAttribute("x", rx); re.setAttribute("y", ry); g.insertBefore(re, null); } t.parentNode.insertBefore(g, t); } 

http://fiddle.jshell.net/T5qWb/1/

The limitation is that the bounding rectangle is the narrowest rectangle that will contain the letter in the original horizontal and vertical coordinates, and not the rotating rectangle, and therefore the rectangles are larger than the letters and overlapping ones.

To define a bounded rectangle, you need to use .getStartPositionOfChar(i) , .getEndPositionOfChar(i) and some geometry:

 var texts = document.getElementsByClassName("backgroundRect"); var svgNS ="http://www.w3.org/2000/svg"; for (var i=0, max= texts.length; i<max; i++) { var t = texts[i]; var g = document.createElementNS(svgNS, "g"); g.setAttribute("class", "textBackground"); for (var j=0, nchar=t.getNumberOfChars(); j<nchar; j++) { var p = document.createElementNS(svgNS, "path"); var start = t.getStartPositionOfChar(j), end = t.getEndPositionOfChar(j), height = parseFloat(getComputedStyle(t)["fontSize"]), vector = [(end.x - start.x), (end.y - start.y)], aspect = height / Math.sqrt(vector[0]*vector[0] + vector[1]*vector[1]), normal = [vector[1]*aspect, -vector[0]*aspect]; var d = ["M", [start.x, start.y], "l", normal, vector, [-normal[0], -normal[1]], "z" ].join(" "); p.setAttribute("d", d); g.insertBefore(p, null); } t.parentNode.insertBefore(g, t); } 

http://fiddle.jshell.net/T5qWb/2/

I use <path> instead of <rect> this time, using relative coordinates to draw straight lines along the baseline of the character, and then 1em up 90 degrees along that line. This positions each base of the rectangle in your text path, but does not cover the "descenders" of the letters.

To do this, I decided that it would be easier to go back to the <rect> elements and use transforms to place the rectangles. I translated the rectangle to the starting point, rotated it based on .getRotationOfChar(i) , and then translated it from the baseline. The only limitation is that I had to hard code in evaluating how much of the height of the character should be below the base, because I could not calculate any method to calculate this for a given font.

 var texts = document.getElementsByClassName("backgroundRect"); var svgNS ="http://www.w3.org/2000/svg"; for (var i=0, max= texts.length; i<max; i++) { var t = texts[i]; var g = document.createElementNS(svgNS, "g"); g.setAttribute("class", "textBackground"); for (var j=0, nchar=t.getNumberOfChars(); j<nchar; j++) { var re = document.createElementNS(svgNS, "rect"); var start = t.getStartPositionOfChar(j), end = t.getEndPositionOfChar(j), angle = t.getRotationOfChar(j), height = parseFloat(getComputedStyle(t)["fontSize"]), vector = [(end.x - start.x), (end.y - start.y)], width = Math.sqrt(vector[0]*vector[0] + vector[1]*vector[1]), aspect = height / width, normal = [vector[1]*aspect, -vector[0]*aspect], baseline = 0.2; re.setAttribute("height", height); re.setAttribute("width", width); re.setAttribute("transform", ["translate(", [start.x, start.y], ")", "rotate(", angle, ")", "translate(0", -height*(1-baseline), ")" ].join(" ") ); g.insertBefore(re, null); } t.parentNode.insertBefore(g, t); } 

http://fiddle.jshell.net/T5qWb/3/

It is tested and all interface methods are implemented and work as expected in the latest browsers Chrome and Firefox and IE11 / 10/9 (through the developer emulator).

+1
source

If you really, really want to do this;) .... This can be done using the "background" textPath with the unicode symbol # 96xx. However, in order to achieve consistency with the parent characters, you will need to develop a table that will select the correct character and size to match the parent. This will require a bit of javascript, some patience with defining bounding boxes for characters, and an understanding of text alignment. And, of course, the way different browsers may look will challenge your sanity.

Hmm ... I think if you can create this, you will boast.

The following is an example of using your text panel.

 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Unicode textPath character background</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> </head> <body style='padding:10px;font-family:arial'> <center> <h4>Unicode characters as textPath character background</h4> #96xx series <div style='color:blue;width:90%;background-color:gainsboro;text-align:justify;padding:10px;border-radius:6px;'> &#9605; &#9605; &#9605; &#9605; &#9605; &#9605; &nbsp; &nbsp; &nbsp; &nbsp; &#9606; &#9606; &#9606; &#9606; &#9606; &#9606; &nbsp; &nbsp; &nbsp; &nbsp; &#9607; &#9607; &#9607; &#9607; &#9607; &#9607; &nbsp; &nbsp; &nbsp; &nbsp; &#9608; &#9608; &#9608; &#9608; &#9608; &#9608; &nbsp; &nbsp; &nbsp; &nbsp; &#9609; &#9609; &#9609; &#9609; &#9609; &#9609; &nbsp; &nbsp; &nbsp; &nbsp; &#9610; &#9610; &#9610; &#9610; &#9610; &nbsp; &nbsp; &nbsp; &nbsp; &#9611; &#9611; &#9611; &#9611; &#9611; &nbsp; &nbsp; &nbsp; &nbsp; &#9612; &#9612; &#9612; &#9612; &#9612; </div> <div id="svgDiv" style='background-color:lightgreen;width:400px;height:400px;'> <svg id="foo" width="100%" height="100%" viewBox="-30 -220 1000 800" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <path id="MyPath" d="M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100" /> </defs> <use xlink:href="#MyPath" fill="none" stroke="black" /> <text fill="blue" font-size="140" > <textPath xlink:href="#MyPath" startOffset="20" dy="10"> &#9607;&#9607;&#9607;&#9607;&#9607; &#9607;&#9607;&#9607;&#9607;&#9607; </textPath> </text> <text font-family="arial" font-size="140" > <textPath xlink:href="#MyPath" startOffset="20" dy="10"> Lorem ipsum </textPath> </text> </svg> </div> </center> </body> </html> 
0
source

Source: https://habr.com/ru/post/989863/


All Articles