Fixing VML text alignment issues at any angle, any font, and size

378 views
Skip to first unread message

Jean

unread,
Dec 28, 2009, 6:12:34 PM12/28/09
to Raphaël
In Raphael, up to version 1.3.0dev, text elements are not aligned the
same way in VML and SVG. The mis-alignments make it very difficult to
make cross-browser graphs (especially for labels) as soon as text is
involved. To complicate the matter, if text is rotated the mis-
alignments change and seem unpredictable (but they are not).
Furthermore for small fonts (<=8 pixels) the text gets out of its
bounding box in VML and the mis-alignments become very annoying with
surrounding lines and other graphic elements.

After trying many different things to fix these problems I finally
found a solution that works at any angle, any font and any size, at
least for the many tests I have done.

So here it is:
// let x and y be the anchor point where you would like to print your
text
// and angle the printing angle in degrees
if ( Raphael.vml ) {
// apply an offset to and y only if VML is in use
x -= 2 * Math.sin ( radians ) // be careful, use 'minus equal'
assignment here
y += 2 * Math.cos( radians )
}

// Now print, anchor, and rotate your text as usual
paper.text( x, y, 'Lorem Ipsum' ).attr({"text-anchor":anchor, "font-
size":size, "font-family":font}).rotate( x, y )

// Note: Always apply rotation after anchoring or you might have some
surprises in some browsers

To see this in action for a variety of angles, anchors, and to verify
that it works for the font of your liking and size, try this in the
Raphael Playground:

size=20 // your font size
anchor='start' // desired anchor
font='Arial'
offset = 2 // if you set this to zero, you will see the behavior
without any adjustment
// You should not need to edit parameters beyond this line
cx = 320, cy = 240, r = 60
for ( angle=0; angle < 360; angle += 30 ) {
radians = angle * Math.PI / 180
sin = Math.sin( radians ), cos = Math.cos( radians )
xo = - offset * sin
yo = + offset * cos
x = cx + r * cos, y = cy + r * sin
t = paper.text( x + xo, y + yo, "eeeeee" ).attr( {"text-
anchor":anchor, "font-size":size, "font-family":font} ).rotate(angle,
x + xo, y + yo);
paper.path(['M',x-20,y,'h',40,'M',x,y-20,'v',40].join(' ')).attr
({stroke:'red'})
}

There is still a slight difference at angles +/- 150 degrees for very
small fonts but this should be good enough most applications.

If you tune the offset above, you will see the discrepancies if offset
== 0 (no offset applied), at offset == 1 this is better but there is
still some mis-alignments, at offset == 2 this is almost perfect, and
at offset == 3 this is too much.

I am implementing this in this graphic library: http://github.com/uiteoi/ico

You can also see the result in action, on angled labels on my
development test suite:
http://ser2.dialink.com:3001/

Hope this helps.

Jean

unread,
Dec 28, 2009, 6:38:21 PM12/28/09
to Raphaël
A small variation of the playground demo above to better see the (mis-)
alignments:

size=12, anchor='start', font='Arial'
offset = 2


cx = 320, cy = 240, r = 60
for ( angle=0; angle < 360; angle += 30 ) {
radians = angle * Math.PI / 180
sin = Math.sin( radians ), cos = Math.cos( radians )

yo = + offset * cos
xo = - offset * sin
x = cx + r * cos, y = cy + r * sin
t = paper.text( x + xo, y + yo, "eeeeee" ).attr( {"text-
anchor":anchor, "font-size":size, "font-family":font} ).rotate(angle,
x + xo, y + yo);

paper.path(['M', x -20 * cos, y - 20 * sin, 'l', +40 * cos, 40 *
sin].join(' ')).attr({stroke:'red'})
// paper.path(['M', x +20 * sin, y - 20 * cos, 'l', -40 * sin, 40 *
cos].join(' ')).attr({stroke:'red'})
paper.circle(cx,cy,r).attr({stroke:'red'})
}


With this demo one can see that the optimal offset at angles +/- 150
degrees is 3. So if you use these unusual angles you could improve
further the adjustments.

Jean

unread,
Dec 29, 2009, 2:57:56 PM12/29/09
to Raphaël
After further refinements, using my text wheel in the playground, I
found that the best offset is 2.2 + font-size / 10.

The font-size offset is necessary to show the same alignment as in SVG
browsers. The text is not drawn on the base line in any browser but
slightly above the mid-line of lower case letters.

With this method it is also be possible to re-align all browsers on
the text baseline, which would correspond to the default alignment in
the SVG specification:
http://www.w3.org/TR/SVG/text.html#AlignmentBaselineProperty

To do this substract font-size / 3 to all browsers.

You can check commented source code of this recipe on github here:
http://gist.github.com/265480

I would like Dimitry's opinion on this matter.

Reply all
Reply to author
Forward
0 new messages