I don't have time to write a full explanation tonight (packing calls, tomorrow I'm in Montréal for Design Engaged), but I've got a quick solution which might be of use to you if you googled upon this page. Read on for more if you're interested in a quick overview.
Here's a test showing two stars, one created from the outside-in and one created from the inside-out.
[kml_flashembed movie="http://www.tom-carden.co.uk/wp-content/uploads/2008/10/pathtests.swf" height="400" width="500" bgcolor="#ffffff" /]
The source code:
package {
import com.senocular.drawing.Path;
import flash.display.Sprite;
import flash.filters.GlowFilter;
import flash.geom.Point;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
[SWF(backgroundColor='#ffffff')]
public class PathTests extends Sprite
{
[Embed(systemFont="Helvetica Neue", fontName="Helvetica", mimeType='application/x-font', unicodeRange='U+00A0,U+0020-U+007E,U+00A1-U+00BF,U+02BB-U+02BC,U+2010-U+2015,U+2018-U+201D,U+2024-U+2026')]
private static var _ignoreMe:String;
[Embed(systemFont="Helvetica Neue", fontName="Helvetica", fontWeight="bold", mimeType='application/x-font', unicodeRange='U+00A0,U+0020-U+007E,U+00A1-U+00BF,U+02BB-U+02BC,U+2010-U+2015,U+2018-U+201D,U+2024-U+2026')]
private static var _ignoreMeToo:String;
public function PathTests()
{
stage.align = 'TL';
stage.scaleMode = 'noScale';
drawStar(new Point(stage.stageWidth/4, stage.stageHeight/2), true);
drawStar(new Point(3*stage.stageWidth/4, stage.stageHeight/2), false);
}
public function drawStar(center:Point, inToOut:Boolean):void
{
var textFormat:TextFormat = new TextFormat("Helvetica", 11, 0x000000, true);
textFormat.kerning = true;
textFormat.letterSpacing = 0;
textFormat.align = TextFormatAlign.CENTER;
var innerRadius:Number = 50;
var outerRadius:Number = 125;
var midRadius:Number = (innerRadius + (outerRadius-innerRadius)/2);
var path:Path;
var paths:Array = [];
for (var a:Number = 0; a < 2*Math.PI; a+=Math.PI/18) {
path = new Path();
paths.push(path);
if (inToOut) {
path.moveTo(center.x + innerRadius * Math.cos(a),
center.y + innerRadius * Math.sin(a));
path.lineTo(center.x + midRadius * Math.cos(a - Math.PI/36),
center.y + midRadius * Math.sin(a - Math.PI/36));
path.lineTo(center.x + outerRadius * Math.cos(a),
center.y + outerRadius * Math.sin(a));
}
else {
path.moveTo(center.x + outerRadius * Math.cos(a),
center.y + outerRadius * Math.sin(a));
path.lineTo(center.x + midRadius * Math.cos(a + Math.PI/36),
center.y + midRadius * Math.sin(a + Math.PI/36));
path.lineTo(center.x + innerRadius * Math.cos(a),
center.y + innerRadius * Math.sin(a));
}
var pathField:TextPathField = new TextPathField('Sample text', path, textFormat);
pathField.filters = [ new GlowFilter(0xffffff, 1, 2, 2, 4, 1, false, false) ]
addChild(pathField);
}
graphics.lineStyle(13,0xff9900);
for each (path in paths) {
path.draw(graphics);
}
graphics.lineStyle(11,0xffff00);
for each (path in paths) {
path.draw(graphics);
}
}
}
}
And the TextPathField code that powers it:
package
{
import com.senocular.drawing.Path;
import flash.display.Sprite;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.AntiAliasType;
import flash.text.GridFitType;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
/** depends on com.senocular.drawing.Path */
public class TextPathField extends Sprite
{
public static const DEFAULT_TEXTFIELD_PARAMS:Object = {
embedFonts: true,
antiAliasType: AntiAliasType.ADVANCED,
gridFitType: GridFitType.NONE
};
// TODO: getters and setters that call redraw
public var text:String;
public var path:Path;
public var textFormat:TextFormat;
public var textFieldParams:Object;
public function TextPathField(text:String, path:Path, textFormat:TextFormat, textFieldParams:Object=null)
{
mouseEnabled = false;
mouseChildren = false;
this.text = text;
this.path = path;
this.textFormat = textFormat;
this.textFieldParams = textFieldParams || DEFAULT_TEXTFIELD_PARAMS;
redraw();
}
public function redraw():void
{
// clear everything away...
while (numChildren > 0) {
removeChildAt(0);
}
// make a dummy textfield so we can measure its width
var textField:TextField = new TextField();
applyParamsToField(textField);
textField.defaultTextFormat = textFormat;
textField.text = text;
textField.width = textField.textWidth+4;
textField.height = textField.textHeight+4;
// bail out if there's no room
if (path.length < textField.width) return;
var t1:Number;
var t2:Number;
if (textFormat.align == TextFormatAlign.LEFT || textFormat.align == null) {
t1 = 0;
t2 = textField.width / path.length;
}
else if (textFormat.align == TextFormatAlign.RIGHT) {
t1 = 1.0 - (textField.width / path.length);
t2 = 1.0;
}
else if (textFormat.align == TextFormatAlign.CENTER) {
t1 = (path.length/2 - textField.width/2) / path.length;
t2 = (path.length/2 + textField.width/2) / path.length;
}
else {
// throw error?
trace('justify alignment unsupported in TextPathField');
}
var angleOffset:Number; // so we can do a 180ú if we're running backwards
var offsetSign:Number; // -1 if we're starting at t2
var tStart:Number; // t1 or t2
// TODO: there are probably more than two cases here...?
if (path.pointAt(t1).x < path.pointAt(t2).x && path.angleAt(t1) < Math.PI/2 && path.angleAt(t1) > -Math.PI/2) {
angleOffset = 0;
offsetSign = 1;
tStart = t1;
}
else {
// this catches text that's running right to left or upside down
angleOffset = Math.PI;
offsetSign = -1;
tStart = t2;
}
// make a textfield for each char, centered on the line, using getCharBoundaries to rotate it around its center point
var chars:Array = text.split('');
for (var i:int = 0; i < chars.length; i++) {
var rect:Rectangle = textField.getCharBoundaries(i);
var yOffset:Number = textField.height/2;
if (rect) {
var t:Number = tStart + offsetSign*(rect.left+rect.width/2)/path.length;
addCharTextField(chars[i], path.pointAt(t), new Point(rect.width/2, yOffset), angleOffset+path.angleAt(t));
}
}
}
// place the given character at pt, registered using rpt, and rotated by r radians
private function addCharTextField(char:String, pt:Point, rpt:Point, r:Number):void
{
var textField:TextField = new TextField();
applyParamsToField(textField);
textField.defaultTextFormat = textFormat;
textField.text = char;
textField.width = textField.textWidth+4;
textField.height = textField.textHeight+4;
var matrix:Matrix = new Matrix();
matrix.translate(-rpt.x, -rpt.y);
matrix.rotate(r);
//matrix.translate(rpt.x, rpt.y);
matrix.translate(pt.x, pt.y);
textField.transform.matrix = matrix;
addChild(textField);
}
private function applyParamsToField(textField:TextField):void
{
for (var param:String in textFieldParams) {
if (textField[param]) {
textField[param] = textFieldParams[param];
}
}
} }
}
Hopefully I can post more about how I'm using this class soon.