1

I am trying to create some buttons with text in javascript using the Rahpael library. I would like to know the size of the styled text, before drawing to avoid that so I can create proper background (the button). Also I would like to avoid drawing the text outside of the canvas/paper (the position of the text is the position of its center).

I can use Raphaels getBBox() method, but I have to create (draw) the text in first place to do this. So I draw text to get the size to be able to draw it on the right position. This is ugly. So I am searching for some general method to estimate the metrics of styled text for given font, font-size, family ...

There is a possibility to do this using the HTML5 canvas http://www.html5canvastutorials.com/tutorials/html5-canvas-text-metrics/ but Raphael do not use canvas. Is there any possibility to get the text metrics using Raphael or plain Javascript?

Brian Webster
  • 30,033
  • 48
  • 152
  • 225
karlitos
  • 1,604
  • 3
  • 27
  • 59

2 Answers2

6

There are a couple of ways to slice up this cat. Two obvious ones come readily to mind.

Out-of-view Ruler Technique

This one's the easiest. Just create a text element outside of the canvas's viewBox, populate it with your font information and text, and measure it.

// set it up -- put the variable somewhere globally or contextually accessible, as necessary
var textRuler = paper.text( -10000, -10000, '' ).attr( { fill: 'none', stroke: 'none' } );

function getTextWidth( text, fontFamily, fontSize )
{
    textResult.attr( { text: text, 'font-family': fontFamily, 'font-size': fontSize } );
    var bbox = textResult.getBBox();
    return bbox.width;
}

It's not elegant by any stretch of the imagination. But it will do you want with relatively little overhead and no complexity.

Cufonized Font

If you were willing to consider using a cufonized font, you could calculate the size of a given text string without needing to mess with the DOM at all. In fact, this is probably approximately what the canvas's elements measureText method does behind the scenes. Given an imported font, you would simply do something like this (consider this protocode!)

//  font should be the result of a call to paper.[getFont][2] for a cufonized font
function getCufonWidth( text, font, fontSize )
{
    var textLength = text.length, cufonWidth = 0;
    for ( var i = 0; i < textLength; i++ )
    {
        var thisChar = text[i];
        if ( ! font.glyphs[thisChar] || ! font.glyphs[thisChar].w )
            continue;  //  skip missing glyphs and/or 0-width entities
        cufonWidth += font.glyphs[thisChar].w / font.face['units-per-em'] * fontSize;
    }
    return cufonWidth;
}

I really like working with cufonized fonts -- in terms of their capacity for being animated in interesting ways, they are far more useful than text. But this second approach may be more additional complexity than you need or want.

Kevin Nielsen
  • 4,413
  • 21
  • 26
5

I know it seems sloppy, but you should have no problem drawing the text, measuring it, then drawing the button and moving the label. To be safe, just draw it off the screen:

var paper = Raphael(0, 0, 500, 500);

var text = paper.text(-100, -100, "My name is Chris");

//outputs 80 12
console.log(text.getBBox().width, text.getBBox().height);

If this REALLY offends your sensibilities, however -- and I would understand! -- you can easily generate an object to remember the width off each character for a given font:

var paper = Raphael(0, 0, 500, 500),
    alphabet = "abcdefghijklmnopqrstuvwxyz";
    font = "Arial",
    charLengths = {},
    ascii_lower_bound = 32,
    ascii_upper_bound = 126;

document.getElementById("widths").style.fontFamily = font;

for (var c = ascii_lower_bound; c <= ascii_upper_bound; c += 1) {
    var letter = String.fromCharCode(c);    
    var L = paper.text(-50, -50, letter).attr("font-family", font);
    charLengths[letter] = L.getBBox().width;
}

//output

for (var key in charLengths) if (charLengths.hasOwnProperty(key)) {
    var row = document.createElement("tr");
    row.innerHTML = "<td>" + key + "</td><td>" + charLengths[key] + "</td>";    
    document.getElementById("widths").appendChild(row);    
}
Chris Wilson
  • 6,599
  • 8
  • 35
  • 71
  • Thank you very much for your answer. Cufonized fonts does not come in quetion, because I need the text to behave like a normal text, be selectable and support copying. Also the think I was not considering yet is text-wrapping. Your second suggestion with an object (map) holding the width of letters printed with specific font and font-size quite be very useful for this. There is a question if there is not another better solution for this, than using the Raphaels text-object, like combining Raphael and normal divs ... – karlitos Feb 14 '13 at 09:52
  • Yeah, I almost always use regular divs over SVG/VML text boxes, specifically for the wrapping (and the full access to CSS). Could you accept answer if it works for you? Need the affirmation of my self-worth ;) – Chris Wilson Feb 14 '13 at 15:08
  • user1991697 -- you should definitely confirm @ChrisWilson's answer as the one that worked for you. I'll throw my $0.02 in, Chris. – Kevin Nielsen Feb 15 '13 at 00:28
  • Hello, of course I confirm the answer - anyway thanks again very much. But, I am afraid, that counting the sum of widths of all characters in a string does not result in a accurate measure, - for example the letters 'ae' are often printed very close together for some 'typographic'reasons. I tried to implement the assumed solution in coffee-script for text-wrapping, and got a mixed results. But there is probably bug in my code I assume. I posted the code here if someone would like to check it on : http://pastebin.com/C3u1m36a – karlitos Feb 15 '13 at 09:41
  • Thanks, Kevin. And user\d+, you should probably go with the out-of-view solution we both suggested. – Chris Wilson Feb 15 '13 at 14:43