Raphael JS Text Along path

I am looking for an example or some confirmation on a concept. Looking to use Raphael JS on an app and want to be able to warp text similar to how graphic design applications such as Illustrator do.

example of warped Text following a path

Answers:

Answer

This isn't too difficult using path.getPointAtLength, as Kevin Nielsen suggests:

path = paper.path("M50,100c40,-50 270,50 300,0").attr("stroke", "#CCC");
message = "I want to do this in RaphaelJS";

//since not every letter is the same width, get the placement for each letter 
//along the length of the string
//however, Raphael appears to group the width of letters into intervals of 4px,
//so this won't be perfect        
for (; c < message.length; c += 1) {
        letter = paper.text(0, 0, message[c]).attr({"text-anchor" : "start"});
    letters.push(letter);
    places.push(message_length);
    //spaces get a width of 0, so set min at 4px
    message_length += Math.max(4, letter.getBBox().width);
}

ratio = path.getTotalLength() / message_length;
fontsize = 10 * ratio;

for (c = 0; c < letters.length; c += 1) {
    letters[c].attr("font-size", fontsize + "px"); 
    p = path.getPointAtLength(places[c] * ratio);
    //there does appear to be a bug in p.alpha around 180. Here's the fix
    letters[c].attr({ x: p.x, y: p.y, transform: 'r' + (p.alpha < 180 ? p.alpha + 180 : p.alpha)});
}

jsFiddle

Answer

Here's an adaptation of Chris Wilson's code, refactored as a drop-in function, with added features:

  • IE8 / VML mode support and Gecko/Firefox support (by defining the rotation origin - without this, IE8 and Firefox go nuts throwing the text all around the page)
  • A small adjustment to make text less ugly in Gecko browsers (e.g. Firefox) - without this, these increase the letter spacing arbitrarily
  • Support for manually defined font sizes and letter spacing, as well as dynamic 'fill the path' sizes and spacing
  • Support for manual kerning (fine-tuning letter spacing character by character). Text on a path often creates really ugly letter spaces; this allows you to define manual tweaks to fix these, by:
    • Numeric position in the string, or,
    • Character, or,
    • Character pairs (applying to instances of a character based on the preceding character, e.g. the below example tightens 'ae' pairs and widens 'rn' pairs)

JSBIN DEMO

function textOnPath( message, path, fontSize, letterSpacing, kerning, geckoKerning) {
    // only message and path are required, other args are optional
    // if fontSize or letterSpacing are undefined, they are calculated to fill the path
    // 10% of fontSize is usually good for manual letterspacing

    // Gecko, i.e. Firefox etc, inflates and alters the letter spacing
    var gecko = /rv:([^\)]+)\) Gecko\/\d{8}/.test(navigator.userAgent||'') ? true : false;

    var letters = [], places = [], messageLength = 0;
    for (var c=0; c < message.length; c++) {
        var letter = paper.text(0, 0, message[c]).attr({"text-anchor" : "middle"});
        letters.push(letter);

        if (kerning) {
            if(gecko && geckoKerning) {
                kerning = geckoKerning;
            }
            var character = letter.attr('text'), kern = 0;
            var predecessor = letters[c-1] ? letters[c-1].attr('text') : '';

            if (kerning[c]) {
                kern = kerning[c];
            } else if (kerning[character]) {
                if( typeof kerning[character] === 'object' ) {
                    kern = kerning[character][predecessor] || kerning[character]['default'] || 0;
                } else {
                    kern = kerning[character];
                }
            }
            if(kerning['default'] ) {
                kern = kern + (kerning['default'][predecessor] || 0);
            }            
            messageLength += kern;
        }

        places.push(messageLength);
        //spaces get a width of 0, so set min at 4px
        messageLength += Math.max(4.5, letter.getBBox().width);
    }

    if( letterSpacing ){
        if (gecko) {
            letterSpacing = letterSpacing * 0.83;
        }
    } else {
        letterSpacing = letterSpacing || path.getTotalLength() / messageLength;
    }
    fontSize = fontSize || 10 * letterSpacing;

    for (c = 0; c < letters.length; c++) {
        letters[c].attr("font-size", fontSize + "px");
        p = path.getPointAtLength(places[c] * letterSpacing);
        var rotate = 'R' + (p.alpha < 180 ? p.alpha + 180 : p.alpha > 360 ? p.alpha - 360 : p.alpha )+','+p.x+','+p.y;
    letters[c].attr({ x: p.x, y: p.y, transform: rotate });
    }
}

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.