A Font Tracing Engine in JavaScript

Below is a simple font-tracing engine implemented in JavaScript. To see it in action, copy it into an .html file and open it in a web browser that runs JavaScript. Or, for an online version, visit https://thiscouldbebetter.neocities.org/fonttracingengine.html.

It still needs a lot of work. It’s very very slow, and strange gaps and extraneous lines appear in the letters. Most of these problems are in the code that finds the intersections of a straight line and a Bezier curve, which needs to be debugged and optimized. There needs to be some way to “union” paths without introducing gaps. Finally, the font itself only contains uppercase letters, and frankly it would be pretty ugly even if everything else were working perfectly.

FontTracingEngine

<html>
<body>

<div id="divMain">

	<input 
		id="inputTextToDraw"
		value="THE QUICK RED FOX JUMPED OVER THE LAZY BROWN DOG."
	/>

	<button id="buttonDrawText" onclick="buttonDrawText_Click()">Draw</button>

</div>

<script type="text/javascript">

// ui events

function buttonDrawText_Click()
{
	var inputTextToDraw = document.getElementById("inputTextToDraw");

	var textToDraw = inputTextToDraw.value;

	var fontDemo = Demo.font();

	var displayHelper = new DisplayHelper
	(
		new Coords(480, 20)
	);

	displayHelper.initialize();

	displayHelper.clear();

	displayHelper.drawTextWithFontAndHeightAtPos
	(
		textToDraw,
		fontDemo,
		12, // height
		new Coords(0, 0) 
	);
}

// extensions

function ArrayExtensions()
{
	// do nothing
}
{
	Array.prototype.addLookups = function(keyName)
	{
		for (var i = 0; i < this.length; i++)
		{
			var item = this[i];
			var key = item[keyName];
			this[key] = item;
		}
	}

	Array.prototype.append = function(other)
	{
		for (var i = 0; i < other.length; i++)
		{
			var element = other[i];
			this.push(element);
		}
	}

	Array.prototype.flatten = function()
	{
		var numberOfChildrenOriginal = this.length;

		for (var i = 0; i < numberOfChildrenOriginal; i++)
		{
			var child = this[i];
			this.append(child);
		}

		for (var i = 0; i < numberOfChildrenOriginal; i++)
		{
			this.splice(i, 1);
		}

		return this;
	}

	Array.prototype.members = function(memberName)
	{
		var returnValues = [];

		for (var i = 0; i < this.length; i++)
		{
			var item = this[i];
			var member = item[memberName];
			returnValues.push(member);
		}

		return returnValues;
	}
}

// classes

function BezierCurve(points)
{
	// The first point is the start,
	// the last point is the end,
	// and the middle points are "control" points.

	this.points = points;
}
{
	BezierCurve.prototype.bounds = function(distanceThreshold)
	{
		var thisAsSegment = new BezierCurveSegment(this, [0, 1]);

		var returnValue = thisAsSegment.bounds(distanceThreshold);

		return returnValue;
	}

	BezierCurve.prototype.draw = function(graphics, distanceThreshold)
	{
		var pointsOnCurve = this.pointsAtResolutionThreshold(distanceThreshold);

		graphics.beginPath();

		var point = pointsOnCurve[0];
		graphics.moveTo(point.x, point.y);

		for (var i = 1; i < pointsOnCurve.length; i++)
		{
			point = pointsOnCurve[i];
			graphics.lineTo(point.x, point.y);
		}

		graphics.stroke();

		this.bounds(distanceThreshold).draw(graphics);
	}

	BezierCurve.prototype.intersectionsWith = function(other, distanceThreshold)
	{
		var thisAsSegment = new BezierCurveSegment(this, [0, 1]);
		var otherAsSegment = new BezierCurveSegment(other, [0, 1]);

		var intersections = this.intersectionsWith_CollideSegmentsRecursively
		(
			distanceThreshold,
			thisAsSegment,
			otherAsSegment,
			[] // instersectionsSoFar
		);

		intersections = this.intersectionsWith_RemoveDuplicates
		(
			intersections, distanceThreshold
		);

		return intersections;
	}

	BezierCurve.prototype.intersectionsWith_RemoveDuplicates = function
	(
		intersections,
		distanceThreshold
	)
	{
		var displacement = new Coords();

		for (var i = 0; i < intersections.length; i++)
		{
			var intersectionThis = intersections[i];

			for (var j = i + 1; j < intersections.length; j++)
			{
				var intersectionOther = intersections[j];

				displacement.overwriteWith
				(
					intersectionOther
				).subtract
				(
					intersectionThis
				);

				var distance = displacement.magnitude();
				if (distance < distanceThreshold)
				{
					intersectionThis.add(intersectionOther).divideScalar(2);
					intersections.splice(j, 1);
					j--;
				}
			}
		}

		return intersections;
	}

	BezierCurve.prototype.intersectionsWith_CollideSegmentsRecursively = function
	(
		distanceThreshold,
		curveSegment0, 
		curveSegment1,
		intersectionsSoFar
	)
	{
		var curveSegment0Bounds = curveSegment0.bounds(distanceThreshold);
		var curveSegment1Bounds = curveSegment1.bounds(distanceThreshold);

		var doBoundsOverlap = curveSegment0Bounds.overlapWith
		(
			curveSegment1Bounds
		);

		if (doBoundsOverlap == true)
		{			
			var curveSegment0Magnitude = curveSegment0Bounds.size.magnitude();
			var curveSegment1Magnitude = curveSegment1Bounds.size.magnitude();

			if 
			(
				curveSegment0Magnitude < distanceThreshold
				|| curveSegment1Magnitude < distanceThreshold
			)
			{
				var curveSegments = [ curveSegment0, curveSegment1 ];
				var curveSegmentEdgeGroups = [];

				for (var c = 0; c < 2; c++)
				{
					var curveSegment = curveSegments[c];
					var points = curveSegment.pointsAtThresholdAddToList
					(
						distanceThreshold,
						[],
						true // includeEnd
					);

					var edges = Edge.manyFromPoints(points, false);

					curveSegmentEdgeGroups.push(edges);
				}

				CollisionHelper.intersectionsOfGroupsAddToList
				(
					curveSegmentEdgeGroups,
					intersectionsSoFar
				);
			}
			else
			{
				var curveSegment0Subdivisions = curveSegment0.subdivide();
				var curveSegment1Subdivisions = curveSegment1.subdivide();

				for (var i = 0; i < curveSegment0Subdivisions.length; i++)
				{
					var curveSegment0Subdivision = curveSegment0Subdivisions[i];

					for (var j = 0; j < curveSegment1Subdivisions.length; j++)
					{
						var curveSegment1Subdivision = curveSegment1Subdivisions[j];

						this.intersectionsWith_CollideSegmentsRecursively
						(
							distanceThreshold,
							curveSegment0Subdivision,
							curveSegment1Subdivision,
							intersectionsSoFar	
						);
					}
				}
			}
		}

		return intersectionsSoFar;
	}

	BezierCurve.prototype.intersectionsWithEdge = function(edgeOther)
	{
		var edgeOtherAsCurve = new BezierCurve(edgeOther.vertices);

		var intersections = this.intersectionsWith
		(
			edgeOtherAsCurve, 
			.1 // hack
		);

		return intersections;
	}

	BezierCurve.prototype.pointsAtResolutionThreshold = function(distanceThreshold)
	{
		var thisAsSegment = new BezierCurveSegment(this, [0, 1]);

		var pointsOnCurve = thisAsSegment.pointsAtThresholdAddToList
		(
			distanceThreshold,
			[], // pointsSoFar
			true // includeEndPoint
		);

		return pointsOnCurve;
	}
}

function BezierCurveSegment(curve, tMinAndMax)
{
	this.curve = curve;
	this.tMinAndMax = tMinAndMax;
}
{
	BezierCurveSegment.prototype.bounds = function(distanceThreshold)
	{
		var points = this.pointsAtThresholdAddToList(distanceThreshold, [], true);
		var returnValue = Bounds.fromPositionsMany(points);

		return returnValue;
	}

	BezierCurveSegment.prototype.pointsAtThresholdAddToList = function
	(
		distanceThreshold, 
		pointsSoFar,
		includeEndPoint
	)
	{
		// Based on a description of "de Casteljau's algorithm"
		// found at the URL "https://pomax.github.io/bezierinfo/".

		var displacement = new Coords();

		var pointsAtTMinAndMax = [];

		for (var tmm = 0; tmm < this.tMinAndMax.length; tmm++)
		{
			var t = this.tMinAndMax[tmm];	

			var points = this.curve.points;

			while (points.length > 1)
			{
				var pointsNext = [];

				for (var i = 0; i < points.length - 1; i++)
				{
					var point = points[i];
					var pointNext = points[i + 1];
	
					displacement.overwriteWith
					(
						pointNext
					).subtract
					(
						point
					).multiplyScalar
					(
						t
					).add
					(
						point
					);
	
					pointsNext.push
					(
						displacement.clone()
					);
				}

				points = pointsNext;

			} // end while (points.length > 1)

			var pointOnCurve = points[0];

			pointsAtTMinAndMax.push(pointOnCurve);
		} // end for t

		displacement.overwriteWith
		(
			pointsAtTMinAndMax[1]
		).subtract
		(
			pointsAtTMinAndMax[0]
		);

		// hack
		// This could fail if the start and end point
		// are closer than the threshold distance.

		var distanceBetweenPoints = displacement.magnitude();

		if (distanceBetweenPoints > distanceThreshold)
		{
			var subdivisions = this.subdivide();

			subdivisions[0].pointsAtThresholdAddToList
			( 
				distanceThreshold,
				pointsSoFar,
				false // includeEndPoint
			);
			subdivisions[1].pointsAtThresholdAddToList
			(
				distanceThreshold,
				pointsSoFar,
				false // includeEndPoint
			);
		}
		else
		{
			pointsSoFar.push(pointsAtTMinAndMax[0]);
		}

		if (includeEndPoint == true)
		{
			pointsSoFar.push(pointsAtTMinAndMax[1]);
		}

		return pointsSoFar;
	}

	BezierCurveSegment.prototype.subdivide = function()
	{
		var tMin = this.tMinAndMax[0];
		var tMax = this.tMinAndMax[1];
		var tMidpoint = (tMin + tMax) / 2;

		var returnValues = 
		[
			new BezierCurveSegment(this.curve, [tMin, tMidpoint]),
			new BezierCurveSegment(this.curve, [tMidpoint, tMax])
		];

		return returnValues;
	}
}

function Bounds(min, max)
{
	this.min = min;
	this.max = max;
	this.size = new Coords();
	this.recalculateDerivedValues();

}
{
	// static methods

	Bounds.fromPositionsMany = function(positions)
	{
		var positionMin = positions[0].clone();
		var positionMax = positions[0].clone();

		for (var i = 1; i < positions.length; i++)
		{
			var position = positions[i];

			if (position.x < positionMin.x)
			{
				positionMin.x = position.x;
			}
			else if (position.x > positionMax.x)
			{
				positionMax.x = position.x;
			}

			if (position.y < positionMin.y)
			{
				positionMin.y = position.y;
			}
			else if (position.y > positionMax.y)
			{
				positionMax.y = position.y;
			}
		}

		var returnValue = new Bounds(positionMin, positionMax);

		return returnValue;
	}

	Bounds.mergeMany = function(boundsGroupToMerge)
	{
		var boundsMerged = boundsGroupToMerge[0].clone();
		var boundsMergedMin = boundsMerged.min;
		var boundsMergedMax = boundsMerged.max;

		for (var i = 1; i < boundsGroupToMerge.length; i++)
		{
			var boundsToMerge = boundsGroupToMerge[i];
			var boundsToMergeMin = boundsToMerge.min;
			var boundsToMergeMax = boundsToMerge.max;
	
			if (boundsToMergeMin.x < boundsMergedMin.x)
			{
				boundsMergedMin.x = boundsToMergeMin.x;
			}
			else if (boundsToMergeMax.x > boundsMergedMax.x)
			{
				boundsMergedMax.x = boundsToMergeMax.x;
			}
	
			if (boundsToMergeMin.y < boundsMergedMin.y)
			{
				boundsMergedMin.y = boundsToMergeMin.y;
			}
			else if (boundsToMergeMax.y > boundsMergedMax.y)
			{
				boundsMergedMax.y = boundsToMergeMax.y;
			}
		}

		boundsMerged.recalculateDerivedValues();

		return boundsMerged;
	}

	// instance methods

	Bounds.prototype.add = function(offset)
	{
		this.min.add(offset);
		this.max.add(offset);
		this.recalculateDerivedValues();
		return this;
	}

	Bounds.prototype.clone = function()
	{
		return new Bounds(this.min.clone(), this.max.clone());
	}

	Bounds.prototype.overlapWith = function(other)
	{
		var returnValue = false;

		var bounds = [ this, other ];

		for (var b = 0; b < bounds.length; b++)
		{
			var boundsThis = bounds[b];
			var boundsOther = bounds[1 - b];			

			var doAllDimensionsOverlapSoFar = true;

			for (var d = 0; d < Coords.NumberOfDimensions; d++)
			{
				if 
				(
					boundsThis.max.dimension(d) < boundsOther.min.dimension(d)
					|| boundsThis.min.dimension(d) > boundsOther.max.dimension(d)
				)
				{
					doAllDimensionsOverlapSoFar = false;
					break;
				}
			}

			if (doAllDimensionsOverlapSoFar == true)
			{
				returnValue = true;
				break;
			}
		}

		return returnValue;
	}

	Bounds.prototype.overwriteWith = function(other)
	{
		this.min.overwriteWith(other.min);
		this.max.overwriteWith(other.max);
		this.recalculateDerivedValues();
		return this;
	}

	Bounds.prototype.recalculateDerivedValues = function()
	{
		this.size.overwriteWith(this.max).subtract(this.min);
	}
}

function Cloneable()
{
	// static class
}
{
	Cloneable.cloneMany = function(cloneablesToClone)
	{
		var returnValues = [];

		for (var i = 0; i < cloneablesToClone.length; i++)
		{
			var cloneableToClone = cloneablesToClone[i];
			var cloneableCloned = cloneableToClone.clone();
			returnValues.push(cloneableCloned);
		}

		return returnValues;
	}
}

function CollisionHelper()
{
	// static class
}
{
	CollisionHelper.intersectionsOfGroupsAddToList = function(groupsToIntersect, listToAddTo)
	{
		var numberOfGroups = groupsToIntersect.length;

		for (var g = 0; g < numberOfGroups; g++)
		{
			var group0 = groupsToIntersect[g];
			
			for (var h = g + 1; h < numberOfGroups; h++)
			{
				var group1 = groupsToIntersect[h];

				for (var i = 0; i < group0.length; i++)
				{
					var collider0 = group0[i];

					for (var j = 0; j < group1.length; j++)
					{
						var collider1 = group1[j];
						
						var intersection = collider0.intersectionWith
						(
							collider1
						);
	
						if (intersection != null)
						{
							listToAddTo.push(intersection);
						}
					}
				}
			}
		}

		return listToAddTo;
	}
}

function Coords(x, y)
{
	this.x = x;
	this.y = y;
}
{
	Coords.prototype.add = function(other)
	{
		this.x += other.x;
		this.y += other.y;
		return this;
	}

	Coords.prototype.clone = function()
	{
		return new Coords(this.x, this.y);
	}

	Coords.prototype.dimensionMin = function()
	{
		return (this.x <= this.y ? this.x : this.y);
	}

	Coords.prototype.divide = function(other)
	{
		this.x /= other.x;
		this.y /= other.y;
		return this;
	}

	Coords.prototype.divideScalar = function(scalar)
	{
		this.x /= scalar;
		this.y /= scalar;
		return this;
	}

	Coords.prototype.dotProduct = function(other)
	{
		return (this.x * other.x + this.y * other.y);
	}

	Coords.prototype.floor = function()
	{
		this.x = Math.floor(this.x);
		this.y = Math.floor(this.y);
		return this;
	}

	Coords.prototype.isInRange = function(max)
	{
		var returnValue = 
		(
			this.x >= 0
			&& this.x <= max.x
			&& this.y >= 0
			&& this.y <= max.y
		);

		return returnValue;
	}

	Coords.prototype.magnitude = function()
	{
		return Math.sqrt(this.x * this.x + this.y * this.y);
	}

	Coords.prototype.multiply = function(other)
	{
		this.x *= other.x;
		this.y *= other.y;
		return this;
	}

	Coords.prototype.multiplyScalar = function(scalar)
	{
		this.x *= scalar;
		this.y *= scalar;
		return this;
	}

	Coords.prototype.overwriteWith = function(other)
	{
		this.x = other.x;
		this.y = other.y;
		return this;
	}

	Coords.prototype.overwriteWithDimensions = function(x, y)
	{
		this.x = x;
		this.y = y;
		return this;
	}

	Coords.prototype.randomize = function()
	{
		this.x = Math.random();
		this.y = Math.random();
		return this;
	}

	Coords.prototype.right = function()
	{
		var temp = this.x;
		this.x = 0 - this.y;
		this.y = temp;
		return this;
	}

	Coords.prototype.subtract = function(other)
	{
		this.x -= other.x;
		this.y -= other.y;
		return this;
	}

	Coords.prototype.toString = function()
	{
		return "(" + this.x + "," + this.y + ")";
	}
}

function DisplayHelper(viewSizeInPixels)
{
	this.viewSizeInPixels = viewSizeInPixels;
}
{
	DisplayHelper.prototype.clear = function()
	{
		this.graphics.fillStyle = "White";
		this.graphics.fillRect
		(
			0, 0,
			this.viewSizeInPixels.x,
			this.viewSizeInPixels.y
		);

		this.graphics.strokeStyle = "Gray";
		this.graphics.strokeRect
		(
			0, 0,
			this.viewSizeInPixels.x,
			this.viewSizeInPixels.y
		);
	}

	DisplayHelper.prototype.drawTextWithFontAndHeightAtPos = function
	(
		text, font, fontHeight, pos
	)
	{
		var characterPos = new Coords(0, 0);
		var characterBounds = new Bounds(new Coords(), new Coords());
		this.graphics.fillStyle = "Gray";
		var drawPos = new Coords();

		for (var c = 0; c < text.length; c++)
		{
			var character = text.charAt(c);
			var glyph = font.glyphs[character];
			if (glyph == null)
			{
				glyph = font.glyphs[" "];
			}
			var glyphBounds = glyph.bounds;
			characterBounds.overwriteWith
			(
				glyphBounds
			).add
			(
				characterPos
			);	
			var glyphSize = glyphBounds.size;			
			var glyphEdges = glyph.edges;

			var yStep = 1 / fontHeight;

			for (var y = 0; y < glyphSize.y; y += yStep)
			{
				var edgeForPixelRow = new Edge
				([
					new Coords(0, y),
					new Coords(glyphSize.x, y)
				]);

				var edgeCollisions = [];

				for (var e = 0; e < glyphEdges.length; e++)
				{
					var glyphEdge = glyphEdges[e];

					var collisions = glyphEdge.intersectionsWithEdge
					(
						edgeForPixelRow
					);

					for (var d = 0; d < collisions.length; d++)
					{
						var collision = collisions[d];

						var ec;
						for (ec = 0; ec < edgeCollisions.length; ec++)
						{
							var collisionSorted = edgeCollisions[ec];
							if (collisionSorted.x > collision.x)
							{
								break;
							}
						}

						edgeCollisions.splice
						(
							ec, 0, collision
						);
					}
				}

				var numberOfCollisions = edgeCollisions.length;

				for (var ec = 0; ec < numberOfCollisions; ec ++)
				{
					/*
					// debug
					var hueMax = 360;
					var hue = Math.floor(Math.random() * hueMax);
					this.graphics.strokeStyle = "hsl(" + hue +  ", 100%, 50%)";
					*/

					var ecNext = ec + 1;
					if (ec % 2 == 0 && ecNext < numberOfCollisions)
					{
						var collision = edgeCollisions[ec];
						var collisionNext = edgeCollisions[ecNext];

	
						this.graphics.beginPath();
						drawPos.overwriteWith
						(
							collision
						).add
						(
							characterPos
						).multiplyScalar
						(
							fontHeight
						);
						this.graphics.moveTo(drawPos.x, drawPos.y);
						drawPos.overwriteWith
						(
							collisionNext
						).add
						(
							characterPos
						).multiplyScalar
						(
							fontHeight
						);
						this.graphics.lineTo(drawPos.x, drawPos.y);
						this.graphics.stroke();							
					}
				}
			}

			characterPos.x += glyphSize.x;
		}
	}

	DisplayHelper.prototype.initialize = function()
	{
		var canvas = document.createElement("canvas");
		canvas.width = this.viewSizeInPixels.x; // + "px";
		canvas.height = this.viewSizeInPixels.y; // + "px";

		this.graphics = canvas.getContext("2d");
	
		var divMain = document.getElementById("divMain");
		divMain.appendChild(canvas);
	}
}

function Edge(vertices)
{
	this.vertices = vertices;
	this.recalculateDerivedValues();
}
{
	// static methods

	Edge.manyFromPoints = function(points, connectPointFinalToFirst)
	{
		var edges = [];

		var numberOfPoints = points.length;
		
		for (var i = 0; i < numberOfPoints - 1; i++)
		{
			var point = points[i];
			var pointNext = points[i + 1];
	
			var edge = new Edge
			([
				point, pointNext
			]);

			edges.push(edge);
		}

		if (connectPointFinalToFirst == true)
		{
			var edge = new Edge([points[points.length - 1], points[0]]);
			edges.push(edge);
		}

		return edges;
	}

	// instance methods

	Edge.prototype.clone = function()
	{
		return new Edge(Cloneable.cloneMany(this.vertices));
	}

	Edge.prototype.intersectionWith = function(other)
	{
		var edge0 = this;
		var edge1 = other;

		var returnValue = null;

		var edge0ProjectedOntoEdge1 = edge0.clone().projectOnto
		(
			edge1
		);

		var distanceAlongEdge0To1 = 
			0 
			- edge0ProjectedOntoEdge1.vertices[0].y 
			/ edge0ProjectedOntoEdge1.direction.y;
	
		// Because the math's not perfect...
		var correctionFactor = 0.001; 

		if 
		(
			distanceAlongEdge0To1 >= 0 - correctionFactor
			&& distanceAlongEdge0To1 <= edge0.length + correctionFactor
		)
		{
			var distanceAlongEdge1To0 = 
				edge0ProjectedOntoEdge1.vertices[0].x
				+ 
				(
					edge0ProjectedOntoEdge1.direction.x
					* distanceAlongEdge0To1
				);

			if 
			(
				distanceAlongEdge1To0 >= 0 - correctionFactor
				&& distanceAlongEdge1To0 <= edge1.length + correctionFactor
			)
			{	
				returnValue = edge0.vertices[0].clone().add
				(
					edge0.direction.clone().multiplyScalar
					(
						distanceAlongEdge0To1
					)
				);
			}
		}

		return returnValue;
	}

	Edge.prototype.projectOnto = function(other)
	{
		for (var v = 0; v < this.vertices.length; v++)
		{
			var vertexToProject = this.vertices[v];

			vertexToProject.subtract
			(
				other.vertices[0]
			).overwriteWithDimensions
			(
				vertexToProject.dotProduct(other.direction),
				vertexToProject.dotProduct(other.right)
			);
		}

		this.recalculateDerivedValues();

		return this;
	}

	Edge.prototype.recalculateDerivedValues = function()
	{
		this.displacement = this.vertices[1].clone().subtract
		(
			this.vertices[0]
		);
		this.length = this.displacement.magnitude();
		this.direction = this.displacement.clone().divideScalar
		(
			this.length
		);
		this.right = this.direction.clone().right();

		this.bounds = Bounds.fromPositionsMany(this.vertices);
	}

	Edge.prototype.toString = function()
	{
		return this.vertices.toString();
	}
}

function Font(name, glyphs)
{
	this.name = name;
	this.glyphs = glyphs;

	this.glyphs.addLookups("symbol");
}
		
function Glyph(name, symbol, paths)
{
	this.name = name;
	this.symbol = symbol;
	this.paths = paths;

	this.bounds = Bounds.mergeMany(this.paths.members("bounds"));
	this.edges = [];
	for (var i = 0; i < this.paths.length; i++)
	{
		this.edges.append(this.paths[i].edges);
	}
}

function Path(pointGroupsForEdges)
{
	var edges = [];
	var boundsForEdges = [];

	var numberOfEdges = pointGroupsForEdges.length;

	for (var e = 0; e < numberOfEdges; e++)
	{
		var eNext = (e + 1) % numberOfEdges;

		var pointsForEdge = pointGroupsForEdges[e];
		var pointsForEdgeNext = pointGroupsForEdges[eNext];

		var numberOfPointsForEdge = pointsForEdge.length;

		pointsForEdge.push(pointsForEdgeNext[0]);

		var edge = new BezierCurve(pointsForEdge);
		edges.push(edge);

		var edgeBounds = Bounds.fromPositionsMany(pointsForEdge);
		boundsForEdges.push(edgeBounds);		
	}

	this.edges = edges;

	this.bounds = Bounds.mergeMany(boundsForEdges);
}

// demo

function Demo()
{}
{
	Demo.font = function()
	{
		var returnValue = new Font
		(
			"FontDemo",
			[
				new Glyph
				(
					"A",
					"A",
					[
						new Path
						([
							[ new Coords(0, 1) ],
							[ new Coords(.25, 1) ],
							[ new Coords(.40, .75) ],
							[ new Coords(.60, .75) ],
							[ new Coords(.75, 1) ],
							[ new Coords(1, 1) ],
							[ new Coords(.5, 0) ],
						]),

						new Path
						([
							[ new Coords(.5, .5) ],
							[ new Coords(.55, .6) ],
							[ new Coords(.45, .6) ],
						]),
					]
				),

				new Glyph
				(
					"B",
					"B",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.3, 0), new Coords(.8, 0) ],
							[ new Coords(.8, .2), new Coords(.8, .5) ],
							[ new Coords(.5, .5) ],
							[ new Coords(.8, .8) ],
							[ new Coords(.3, 1) ],
							[ new Coords(0, 1) ],
						]),

						new Path
						([
							[ new Coords(.25, .2) ],
							[ new Coords(.5, .3) ],
							[ new Coords(.25, .4) ],
						]),

						new Path
						([
							[ new Coords(.25, .7) ],
							[ new Coords(.5, .8) ],
							[ new Coords(.25, .9) ],
						])
					]
				),

				new Glyph
				(
					"C",
					"C",
					[
						new Path
						([
							[ new Coords(.5, 0), new Coords(.9, .1) ],
							[ new Coords(.8, .2) ],
							[ new Coords(.6, .3) ],
							[ new Coords(.5, .2), new Coords(.4, .4) ],
							[ new Coords(.2, .5), new Coords(.4, .6) ],
							[ new Coords(.5, .8), new Coords(.6, .6) ],
							[ new Coords(.6, .6) ],
							[ new Coords(.8, .8), new Coords(.9, .9) ],
							[ new Coords(.5, 1), new Coords(.1, .9) ],
							[ new Coords(0, .5), new Coords(.1, .1) ],
						]),
					]
				),

				new Glyph
				(
					"D",
					"D",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.4, 0), new Coords(.8, .1) ],
							[ new Coords(.8, .5), new Coords(.8, .9) ],
							[ new Coords(.4, 1) ],
							[ new Coords(0, 1) ],
						]),

						new Path
						([
							[ new Coords(.2, .2) ],
							[ new Coords(.4, .2), new Coords(.6, .2) ],
							[ new Coords(.6, .5), new Coords(.6, .8) ],
							[ new Coords(.4, .8) ],
							[ new Coords(.2, .8) ],
						]),
					]
				),

				new Glyph
				(
					"E",
					"E",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.8, 0) ],
							[ new Coords(.8, .2) ],
							[ new Coords(.2, .2) ],
							[ new Coords(.2, .4) ],
							[ new Coords(.8, .4) ],
							[ new Coords(.8, .6) ],
							[ new Coords(.2, .6) ],
							[ new Coords(.2, .8) ],
							[ new Coords(.8, .8) ],
							[ new Coords(.8, 1) ],
							[ new Coords(0, 1) ],
						]),
					]
				),

				new Glyph
				(
					"F",
					"F",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.8, 0) ],
							[ new Coords(.8, .2) ],
							[ new Coords(.2, .2) ],
							[ new Coords(.2, .4) ],
							[ new Coords(.6, .4) ],
							[ new Coords(.6, .6) ],
							[ new Coords(.2, .6) ],
							[ new Coords(.2, 1) ],
							[ new Coords(0, 1) ],
						]),
					]
				),

				new Glyph
				(
					"G",
					"G",
					[
						new Path
						([
							[ new Coords(.5, 0), new Coords(.8, 0) ],
							[ new Coords(.8, .2) ],
							[ new Coords(.7, .3), new Coords(.7, .2) ],
							[ new Coords(.5, .2), new Coords(.2, .2) ],
							[ new Coords(.2, .5), new Coords(.2, .8) ],
							[ new Coords(.5, .8), new Coords(.7, .8) ],
							[ new Coords(.7, .7) ],
							[ new Coords(.5, .7) ],
							[ new Coords(.5, .5) ],
							[ new Coords(1, .5), new Coords(1, 1) ],
							[ new Coords(.5, 1), new Coords(0, 1) ],
							[ new Coords(0, .5), new Coords(0, 0) ],
						]),
					]
				),

				new Glyph
				(
					"H",
					"H",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.2, 0) ],
							[ new Coords(.2, .4) ],
							[ new Coords(.6, .4) ],
							[ new Coords(.6, 0) ],
							[ new Coords(.8, 0) ],
							[ new Coords(.8, 1) ],
							[ new Coords(.6, 1) ],
							[ new Coords(.6, .6) ],
							[ new Coords(.2, .6) ],
							[ new Coords(.2, 1) ],
							[ new Coords(0, 1) ],
						]),
					]
				),

				new Glyph
				(
					"I",
					"I",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.6, 0) ],
							[ new Coords(.6, .2) ],
							[ new Coords(.4, .2) ],
							[ new Coords(.4, .8) ],
							[ new Coords(.6, .8) ],
							[ new Coords(.6, 1) ],
							[ new Coords(0, 1) ],
							[ new Coords(0, .8) ],
							[ new Coords(.2, .8) ],
							[ new Coords(.2, .2) ],
							[ new Coords(0, .2) ],
						]),
					]
				),


				new Glyph
				(
					"J",
					"J",
					[
						new Path
						([
							[ new Coords(.6, 0) ],
							[ new Coords(.8, 0) ],
							[ new Coords(.8, .5), new Coords(.8, .8) ],
							[ new Coords(.4, 1), new Coords(0, .8) ],
							[ new Coords(0, .5) ],
							[ new Coords(.2, .5), new Coords(.2, .8) ],
							[ new Coords(.4, .8), new Coords(.6, .8) ],
							[ new Coords(.6, .5) ],
						]),
					]
				),

				new Glyph
				(
					"K",
					"K",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.2, 0) ],
							[ new Coords(.2, .4) ],
							[ new Coords(.6, 0) ],
							[ new Coords(.8, 0) ],
							[ new Coords(.4, .5) ],
							[ new Coords(.8, 1) ],
							[ new Coords(.6, 1) ],
							[ new Coords(.2, .6) ],
							[ new Coords(.2, 1) ],
							[ new Coords(0, 1) ],

						]),
					]
				),	

				new Glyph
				(
					"L",
					"L",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.2, 0) ],
							[ new Coords(.2, .8) ],
							[ new Coords(.8, .8) ],
							[ new Coords(.8, 1) ],
							[ new Coords(0, 1) ],
						]),
					]
				),

				new Glyph
				(
					"M",
					"M",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.2, 0) ],
							[ new Coords(.4, .4) ],
							[ new Coords(.6, 0) ],
							[ new Coords(.8, 0) ],
							[ new Coords(.8, 1) ],
							[ new Coords(.6, 1) ],
							[ new Coords(.6, .2) ],
							[ new Coords(.4, .6) ],
							[ new Coords(.2, .2) ],
							[ new Coords(.2, 1) ],
							[ new Coords(0, 1) ],
						]),
					]
				),

				new Glyph
				(
					"N",
					"N",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.2, 0) ],
							[ new Coords(.6, .8) ],
							[ new Coords(.6, 0) ],
							[ new Coords(.8, 0) ],
							[ new Coords(.8, 1) ],
							[ new Coords(.6, 1) ],
							[ new Coords(.2, .2) ],
							[ new Coords(.2, 1) ],
							[ new Coords(0, 1) ],
						]),
					]
				),

				new Glyph
				(
					"O",
					"O",
					[
						new Path
						([
							[ new Coords(.5, 0), new Coords(.9, .1) ],
							[ new Coords(1, .5), new Coords(.9, .9) ],
							[ new Coords(.5, 1), new Coords(.1, .9) ],
							[ new Coords(0, .5), new Coords(.1, .1) ],
						]),

						new Path
						([
							[ new Coords(.5, .2), new Coords(.7, .3) ],
							[ new Coords(.8, .5), new Coords(.7, .7) ],
							[ new Coords(.5, .8), new Coords(.3, .7) ],
							[ new Coords(.2, .5), new Coords(.3, .3) ],
						]),
					]
				),

				new Glyph
				(
					"P",
					"P",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.4, 0), new Coords(.8, .05) ],
							[ new Coords(.8, .25), new Coords(.8, .45) ],
							[ new Coords(.4, .5) ],
							[ new Coords(.2, .5) ],
							[ new Coords(.2, 1) ],
							[ new Coords(0, 1) ],
						]),

						new Path
						([
							[ new Coords(.3, .1) ],
							[ new Coords(.4, .1) ],
							[ new Coords(.5, .25) ],
							[ new Coords(.4, .4) ],
							[ new Coords(.3, .4) ],
						]),
					]
				),


				new Glyph
				(
					"Q",
					"Q",
					[
						new Path
						([
							[ new Coords(.5, 0), new Coords(.9, .1) ],
							[ new Coords(1, .5), new Coords(.9, .9) ],
							[ new Coords(.5, 1), new Coords(.1, .9) ],
							[ new Coords(0, .5), new Coords(.1, .1) ],
						]),

						new Path
						([
							[ new Coords(.5, .2), new Coords(.7, .3) ],
							[ new Coords(.8, .5), new Coords(.7, .7) ],
							[ new Coords(.5, .8), new Coords(.3, .7) ],
							[ new Coords(.2, .5), new Coords(.3, .3) ],
						]),

						new Path
						([
							[ new Coords(.45, .55) ],
							[ new Coords(.55, .45) ],
							[ new Coords(.95, .85) ],
							[ new Coords(.85, .95) ], 
						]),
					]
				),

				new Glyph
				(
					"R",
					"R",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.4, 0), new Coords(.8, .05) ],
							[ new Coords(.8, .25), new Coords(.8, .45) ],
							[ new Coords(.4, .5) ],
							[ new Coords(.8, 1) ],
							[ new Coords(.6, 1) ],
							[ new Coords(.2, .5) ],
							[ new Coords(.2, 1) ],
							[ new Coords(0, 1) ],
						]),

						new Path
						([
							[ new Coords(.3, .1) ],
							[ new Coords(.4, .1) ],
							[ new Coords(.5, .25) ],
							[ new Coords(.4, .4) ],
							[ new Coords(.3, .4) ],
						]),
					]
				),

				new Glyph
				(
					"S",
					"S",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.8, 0) ],
							[ new Coords(.8, .2) ],
							[ new Coords(.2, .2) ],
							[ new Coords(.2, .4) ],
							[ new Coords(.8, .4) ],
							[ new Coords(.8, 1) ],
							[ new Coords(0, 1) ],
							[ new Coords(0, .8) ],
							[ new Coords(.6, .8) ],
							[ new Coords(.6, .6) ],
							[ new Coords(0, .6) ],				
						])	
					]
				),

				new Glyph
				(
					"T",
					"T",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.8, 0) ],
							[ new Coords(.8, .2) ],
							[ new Coords(.5, .2) ],
							[ new Coords(.5, 1) ],
							[ new Coords(.3, 1) ],
							[ new Coords(.3, .2) ],
							[ new Coords(0, .2) ],
						])
					]
				),

				new Glyph
				(
					"U",
					"U",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.2, 0) ],
							[ new Coords(.2, .6), new Coords(.2, .8) ],
							[ new Coords(.4, .8), new Coords(.6, .8) ],
							[ new Coords(.6, .6) ],
							[ new Coords(.6, 0) ],
							[ new Coords(.8, 0) ],
							[ new Coords(.8, .8) ],
							[ new Coords(.4, 1) ], 
							[ new Coords(0, .8) ],
						])
					]
				),

				new Glyph
				(
					"V",
					"V",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.2, 0) ],
							[ new Coords(.4, 0.5) ],
							[ new Coords(.6, 0) ],
							[ new Coords(.8, 0) ],
							[ new Coords(.4, 1) ],
						]),
					]
				),

				new Glyph
				(
					"W",
					"W",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.2, 0) ],
							[ new Coords(.4, .5) ],
							[ new Coords(.6, 0) ],
							[ new Coords(.8, 0) ],
							[ new Coords(.4, 1) ],
						]),

						new Path
						([
							[ new Coords(.5, 0) ],
							[ new Coords(.7, 0) ],
							[ new Coords(.9, .5) ],
							[ new Coords(1.1, 0) ],
							[ new Coords(1.3, 0) ],
							[ new Coords(.9, 1) ],
						]),
					]
				),

				new Glyph
				(
					"X",
					"X",
					[
						new Path
						([
							[ new Coords(.75, 0) ],
							[ new Coords(1, 0) ],
							[ new Coords(.25, 1) ],
							[ new Coords(0, 1) ],
						]),

						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.25, 0) ],
							[ new Coords(1, 1) ],
							[ new Coords(.75, 1) ],
						]),
					]
				),

				new Glyph
				(
					"Y",
					"Y",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.2, 0) ],
							[ new Coords(.4, .4) ],
							[ new Coords(.6, 0) ],
							[ new Coords(.8, 0) ],
							[ new Coords(.5, .5) ],
							[ new Coords(.5, 1) ],
							[ new Coords(.3, 1) ],
							[ new Coords(.3, .5) ],
						]),
					]
				),

				new Glyph
				(
					"Z",
					"Z",
					[
						new Path
						([
							[ new Coords(0, 0) ],
							[ new Coords(.8, 0) ],
							[ new Coords(.3, .8) ],
							[ new Coords(.8, .8) ],
							[ new Coords(.8, 1) ],
							[ new Coords(0, 1) ],
							[ new Coords(.5, .2) ],
							[ new Coords(0, .2) ],

						]),
					]
				),

				new Glyph
				(
					"Space",
					" ",
					[
						new Path
						([
							[ new Coords(0, .8) ],
							[ new Coords(.5, .8) ],
							[ new Coords(.5, 1) ],
							[ new Coords(0, 1) ],

						]),
					]
				),

				new Glyph
				(
					".",
					".",
					[
						new Path
						([
							[ new Coords(0, .8) ],
							[ new Coords(.2, .8) ],
							[ new Coords(.2, 1) ],
							[ new Coords(0, 1) ],
						]),
					]
				),

				new Glyph
				(
					"/",
					"/",
					[
						new Path
						([
							[ new Coords(.5, 0) ],
							[ new Coords(.75, 0) ],
							[ new Coords(.25, 1) ],
							[ new Coords(0, 1) ],
						]),
					]
				),
			]
		);

		return returnValue;
	}
}

</script>
</body>
</html>

Advertisements
This entry was posted in Uncategorized and tagged , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s