10

I want to put a rectangle around a text in SVG. The height of the text is known to me (the font-size attribute of the text element). But the width is dependent on the actual content. Using getBBox() or getComputedTextLength() should work. But this only works after rendering.

Is there a way to specify that in an other way? For example defining the x and width attributes relative to other values? I didn't find anything like that in the SVG Spec.

radlan
  • 2,393
  • 4
  • 33
  • 53
  • What's wrong with measuring it after rendering? – Robert Longson Oct 08 '13 at 13:23
  • @RobertLongson Code complexity and visual jumping. I am generating the whole document dynamically. All objects should be prepared then added to the doc and then rendered. When calculating after rendering, I would have to render unfinished objects, then change them after rendering. This would lead to visual jumping. – radlan Oct 08 '13 at 14:15
  • Add your code to the question? Are you trying to get the bbox and text length of an element not yet inserted into the document? – Erik Dahlström Oct 08 '13 at 15:17
  • Visual jumping is easily fixed. Make things visibility="hidden" and then unhide them when you've finished. – Robert Longson Oct 08 '13 at 16:14
  • @ErikDahlström I don't have any code yet. I am still searching for it. You are right, that I would like to get the bbox and/or text length of an element not yet inserted into the document. Since that isn't possible, I am searching for some workaround. Maybe there are attribute values to specify a length in relation to the length of an arbitrary other element. – radlan Oct 08 '13 at 16:41
  • @RobertLongson Yet there is the code complexity. When constructing the elements in the SVG I do this in a logical manner. I have all information on how to position an size the shapes there. Doing only half of it, then waiting for the rendering and modifying them again would bloat the code massively. – radlan Oct 08 '13 at 16:42
  • Show the code as it sounds like you are assuming restrictions that don't, in practice exist. – Robert Longson Oct 08 '13 at 17:37
  • Glad to know there's a work-around involving pre-rendering invisibly. But if you have to render to know the length, the getComputedTextLength function should be called 'getRenderedTextLength'. It is very useful to know the length before rendering, such as when deciding on word wrapping and hyphenation. Or, as in my case, I want to left-justify next to the bars in a horizontal bar graph until the text gets too close to the right-hand edge, then right-justify. – Buzzy Hopewell Nov 16 '15 at 20:16

3 Answers3

4

Figuring where text ends presumably requires roughly the same underlying code path as the rendering itself implements - going through the width of each character based on the font and style, etc... As I am not aware the SVG standards define a method for directly getting this information without doing the actual full rendering, till such methods emerge or are reported here by others, the approach should be to render invisibly before doing the actual rendering.

You can do that in a hidden layer (z-index, opacity and stuff) or outside the visible viewport, whichever works best in experimentation. You just need to get the browser do the rendering to find out, so you render invisibly for that sake, then use getComputedTextLength()

matanster
  • 15,072
  • 19
  • 88
  • 167
1

It is possible using canvas with measureText():

// Get text width before rendering
const getTextWidth = (text, font) => {
  const element = document.createElement('canvas');
  const context = element.getContext('2d');
  context.font = font;
  return context.measureText(text).width;
}

// Demo
const font = '16px serif';
const text = 'My svg text';
const textWidth = getTextWidth(text, font);

document.body.innerHTML = `
  <svg>
    <text x="0" y="20" font="${font}">${text}</text>
    <rect x="0" y="30" width="${textWidth}" height="4" fill="red" />
   </svg>
`;

Adapted from https://stackoverflow.com/a/31305410/1657101

DarthVanger
  • 1,063
  • 10
  • 10
0

I know this is old, but a few ideas:

  1. If you can choose a mono-spaced font, you know your width by a simple constant multiplication with glyph count

  2. If you are bound to proportional fonts, you can find an average glyph size, do the math as with mono-space, and leave enough padding. Alternatively you can fill the padding with text element textLength attribute. If the constant is chosen carefully, the results are not very displeasing.

EDIT: As matanster found it to be hacky

  1. Predetermine glyph widths with getComputedTextLength() and build a lookup table. Downside is that it does not account for kerning, but if your cache size is not a problem, you can append glyph-pair widths to this lookup.

Going beyond that is to find some way to do server side rendering: Is there a way to perform server side rendering of SVG graphics using React?