Skip to content Skip to sidebar Skip to footer

Is There A Way To Draw A Rectangle Around Single Letters On A Svg Textpath

Is there a possiblity to draw rectangles behind single letters of a SVG textpath? I have tried to draw an image of what I am trying to do. The SVG is part of a HTML Page. There see

Solution 1:

How about this... It uses the SVG DOM to get the character boxes and then draws a rectangle behind 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);
    d.x=xm;
    d.y=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',f.x);
      outline.setAttribute('y',f.y);
      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',f.x);
      outline.setAttribute('y',f.y);
      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>

Solution 2:

You'll need Javascript, but it's not quite as complicated as the previous examples.

There are a set of relevant interface functions on all text elements to locate each character:

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

The simplest approach is to use getExtentOfChar(i) to find the bounding-box rectangle for each character glyph. This is the approach used in @Robert Longson's example. Without all the extra event-handling code, it can be simplified to:

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", r.x);
        re.setAttribute("y", r.y);
        g.insertBefore(re, null);        
    }

    t.parentNode.insertBefore(g, t);
}

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

The limitation is that the bounding box is the tightest rectangle that will contain the letter within the original horizontal and vertical coordinates, not a rotated rectangle, and so the rectangles are larger than the letters and overlapping.

To figure out a tight-bounding rectangle, you'll 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'm using a <path> instead of a <rect> this time, using relative coordinates to draw straight lines along the baseline of the character and then 1em up at 90degrees to that line. This positions each rectangle's base on your text path, but it doesn't cover the "descenders" of the letters.

To do that, I decided it would be easier to switch back to <rect> elements, and use transforms to position the rectangles. I translate the rectangle to the start point, rotate it based on .getRotationOfChar(i), and then translate it away from the baseline. The only limitation is that I had to hard-code in an estimate of what proportion of the character's height should be below the baseline, as I couldn't figure out any method to calculate that 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/

Tested and all the interface methods are implemented and working as expected in the latest Chrome & Firefox, and IE11/10/9 (via the developer's emulator).


Solution 3:

If you truly, truly want to do this;).... It can accomplished by using a 'background' textPath with unicode character #96xx series. However, to get this to align with the parent characters, you would have to develop a table that would choose the correct character and size to match the parent. This would require a bit of javascript, some patience with defining bounding boxes for the characters, and a understanding of a text alignments. And, of course, the way different browsers render it will challenge your sanity.

Hmm... I guess if you can create this, you will have something to brag about.

Below is a rough example of the concept using your textPath.

<!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>

Post a Comment for "Is There A Way To Draw A Rectangle Around Single Letters On A Svg Textpath"