Exploring the TrueType Font File Format in JavaScript

The JavaScript program below, when run, will present the user with a file upload button. If the user clicks this button and uploads a valid .ttf (TrueType font) file, the glyphs of that font will be displayed in outline.

This program displays the paths (or, as the TrueType specification calls them, “contours”) in outline, rather than as solid blocks of color, because the HTML5 canvas graphics context doesn’t really provide any easy way to fill the outline of the letter while still leaving the inner “counters” unfilled. For example, the letter “O” might render as just a giant black circle, with no empty space inside it. A simple way around this might be just to fill in the inner paths with the background color, or maybe to use fancy alpha compositing modes to “erase” the pixels inside the counters. But that raises the question of how to determine which paths are “inside” other paths. That might be possible with some sort of bounds checking, but that’s kind of a hack. The right thing to do is probably just to figure out the computational geometry, but that’s not a trivial task.

The program also doesn’t handle “composite” glyphs. Composite glyphs are composed of multiple simple glyphs. For example, if a language requires a glyph for “e” with an accent over it, this is often implemented as a composite glyph that references the simple glyphs for “e” and the accent mark.

I’d like to eventually update this code to allow the user to draw an arbitrary string in an arbitrary point size, using a TrueType font. Even though there’s certainly other JavaScript libraries out there that do it already, and for all I know it might be possible to do it with vanilla JavaScript.

Note that the image included with this post was made with an earlier version of this program, which incorrectly handled curved contours on glyphs. This has since been fixed.

TrueTypeFont

<html>
<body>

<!-- user interface -->

<input id="inputFileToLoad" type="file" onchange="inputFileToLoad_Changed();" />

<script type="text/javascript">

// events

function inputFileToLoad_Changed(event)
{
	var inputFileToLoad = document.getElementById("inputFileToLoad");
	var fileToLoad = inputFileToLoad.files[0];
	FontFile.readFromFile(fileToLoad, inputFileToLoad_Changed_FileLoaded);
}

function inputFileToLoad_Changed_FileLoaded(fontFile)
{
	fontFile.drawGlyphs
	(
		32 // canvasDimensionInPixels
	);
}

// classes

function ByteStreamBigEndian(bytes)
{
	this.bytes = bytes;  

	this.numberOfBytesTotal = this.bytes.length;
	this.byteIndexCurrent = 0;
}
{
	ByteStreamBigEndian.prototype.hasMoreBytes = function()
	{
		return (this.byteIndexCurrent < this.numberOfBytesTotal);
	}

	ByteStreamBigEndian.prototype.peekBytes = function(numberOfBytesToRead)
	{
		var returnValue = [];

		for (var b = 0; b < numberOfBytesToRead; b++)
		{
			returnValue[b] = this.bytes[this.byteIndexCurrent + b];
		}

		return returnValue;
	}

	ByteStreamBigEndian.prototype.readBytes = function(numberOfBytesToRead)
	{
		var returnValue = [];

		for (var b = 0; b < numberOfBytesToRead; b++)
		{
			returnValue[b] = this.readByte();
		}

		return returnValue;
	}

	ByteStreamBigEndian.prototype.readByte = function()
	{
		var returnValue = this.bytes.charCodeAt(this.byteIndexCurrent);

		this.byteIndexCurrent++;

		return returnValue;
	}

	ByteStreamBigEndian.prototype.readByteSigned = function()
	{
		var returnValue = this.readByte();
	
		var maxValue = 128; // hack
		if (returnValue >= maxValue)
		{
			returnValue -= maxValue + maxValue;
		}

		return returnValue;
	}

	ByteStreamBigEndian.prototype.readInt = function()
	{
		var returnValue =
		(
			(this.readByte() & 0xFF << 24)
			| ((this.readByte() & 0xFF) << 16 )
			| ((this.readByte() & 0xFF) << 8 )
			| ((this.readByte() & 0xFF) )
		);

		return returnValue;
	}

	ByteStreamBigEndian.prototype.readShort = function()
	{
		var returnValue =
		(
			((this.readByte() & 0xFF) << 8)
			| ((this.readByte() & 0xFF))
		);

		return returnValue;
	}

	ByteStreamBigEndian.prototype.readShortSigned = function()
	{
		var returnValue =
		(
			((this.readByte() & 0xFF) << 8)
			| ((this.readByte() & 0xFF))
		);

		var maxValue = Math.pow(2, 15); // hack
		if (returnValue >= maxValue)
		{
			returnValue -= maxValue + maxValue;
		}

		return returnValue;
	}

	ByteStreamBigEndian.prototype.readString = function(numberOfBytesToRead)
	{
		var returnValue = "";

		for (var b = 0; b < numberOfBytesToRead; b++)
		{
			var charAsByte = this.readByte();
			returnValue += String.fromCharCode(charAsByte);
		}

		return returnValue;
	}
}

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.divideScalar = 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;
	}
}

function FontFile()
{}
{
	FontFile.prototype.drawGlyphs = function(sizeInPoints)
	{
		var divGlyphs = document.createElement("div");

		document.body.appendChild(divGlyphs);

		for (var g = 0; g < this.glyphs.length; g++)
		{
			var glyph = this.glyphs[g];
			glyph.draw(divGlyphs, sizeInPoints);
		}

	}

	FontFile.readFromFile = function(fileToReadFrom, callback)
	{	
		var returnValue = new FontFile(fileToReadFrom.name, null, null);

		var fileReader = new FileReader();
		fileReader.onloadend = function(fileLoadedEvent)
		{
			if (fileLoadedEvent.target.readyState == FileReader.DONE)
			{
				var bytesFromFile = fileLoadedEvent.target.result;
				var reader = new ByteStreamBigEndian(bytesFromFile);

				returnValue.readFromFile_ReadTables(reader);
			}

			callback(returnValue);
		}

		fileReader.readAsBinaryString(fileToReadFrom);
	}

	FontFile.prototype.readFromFile_ReadTables = function(reader)
	{
		// offset table

		var sfntVersionAsBytes = reader.readInt();
		var numberOfTables = reader.readShort();
		var searchRange = reader.readShort(); // (max power of 2 <= numTables) * 16
		var entrySelector = reader.readShort(); // log2(max power of 2 <= numTables)
		var rangeShift = reader.readShort(); // numberOfTables * 16 - searchRange

		// table record entries

		var tableDefns = [];

		for (var t = 0; t < numberOfTables; t++)
		{
			var tableTypeTag = reader.readString(4);
			var checkSum = reader.readInt();
			var offset = reader.readInt();
			var length = reader.readInt();
			
			var tableDefn = new FontFileTableDefn
			(
				tableTypeTag,
				checkSum,
				offset,
				length
			);

			tableDefns.push(tableDefn);
		}

		for (var t = 0; t < numberOfTables; t++)
		{
			var tableDefn = tableDefns[t]; 
			reader.byteIndexCurrent = tableDefn.offset;

			var tableTypeTag = tableDefn.tableTypeTag;			
			if (tableTypeTag == "cmap")
			{
				this.readFromFile_ReadTables_Cmap
				(
					reader, tableDefn.length
				);
			}
			else if (tableTypeTag == "glyf")
			{
				this.readFromFile_ReadTables_Glyf
				(
					reader, tableDefn.length
				);
			}
			else if (tableTypeTag == "maxp")
			{
				this.readFromFile_ReadTables_Maxp
				(
					reader, tableDefn.length
				);
			}
		}
	}

	FontFile.prototype.readFromFile_ReadTables_Cmap = function(reader, length)
	{
		var readerByteOffsetOriginal = reader.byteIndexCurrent;

		var version = reader.readShort();

		var numberOfEncodingTables = reader.readShort();
		var encodingTables = [];

		for (var e = 0; e < numberOfEncodingTables; e++)
		{
			var platformID = reader.readShort();
			var encodingID = reader.readShort();
			var offset = reader.readShort();

			var encodingTable = new FontFileCmapEncodingTable
			(
				platformID,
				encodingID,
				offset
			);

			encodingTables.push(encodingTable);
		}
		
		for (var e = 0; e < numberOfEncodingTables; e++)
		{
			var encodingTable = encodingTables[e];

			reader.byteIndexCurrent = 
				readerByteOffsetOriginal 
				+ encodingTable.offset;

			// todo
		}

		reader.byteIndexCurrent = readerByteOffsetOriginal;
	}

	FontFile.prototype.readFromFile_ReadTables_Glyf = function(reader, length)
	{
		this.glyphs = [];

		var readerByteOffsetOriginal = reader.byteIndexCurrent;

		while (reader.byteIndexCurrent < readerByteOffsetOriginal + length)
		{
			// header

			var numberOfContours = reader.readShortSigned();
			var min = new Coords
			(
				reader.readShortSigned(),
				reader.readShortSigned()
			);
			var max = new Coords
			(
				reader.readShortSigned(),
				reader.readShortSigned()
			);
			var minAndMax = [min, max];

			if (numberOfContours >= 0)
			{
				this.readFromFile_ReadTables_Glyf_Simple
				(
					reader, numberOfContours, minAndMax
				);
			}
			else
			{
				this.readFromFile_ReadTables_Glyf_Composite(reader);
			}
		}
	}

	FontFile.prototype.readFromFile_ReadTables_Glyf_Simple = function(reader, numberOfContours, minAndMax)
	{
		var endPointsOfContours = [];
		for (var c = 0; c < numberOfContours; c++)
		{
			var endPointOfContour = reader.readShort();
			endPointsOfContours.push(endPointOfContour);
		}

		var totalLengthOfInstructionsInBytes = reader.readShort();
		var instructionsAsBytes = reader.readBytes
		(
			totalLengthOfInstructionsInBytes
		);

		var numberOfPoints = 
			endPointsOfContours[endPointsOfContours.length - 1] 
			+ 1;

		var flagSets = [];
		var numberOfPointsSoFar = 0;
		while (numberOfPointsSoFar < numberOfPoints)
		{
			var flagsAsByte = reader.readByte();

			var flags = GlyphContourFlags.fromByte(flagsAsByte);

			flags.timesToRepeat  = (flags.timesToRepeat == true ? reader.readByte() : 0);	

			numberOfPointsSoFar += (1 + flags.timesToRepeat);
					
			flagSets.push(flags);
		}

		var coordinates = [];

		var xPrev = 0;
		for (var f = 0; f < flagSets.length; f++)
		{		
			var flags = flagSets[f];
			for (var r = 0; r <= flags.timesToRepeat; r++)
			{		
				var x;
				if (flags.xShortVector == true)
				{
					x = reader.readByte();
					var sign = (flags.xIsSame ? 1 : -1);
					x *= sign;
					x += xPrev;
				}
				else
				{	
					if (flags.xIsSame == true)
					{
						x = xPrev;
					}
					else
					{
						x = reader.readShortSigned();
						x += xPrev;
					}
				}

				var coordinate = new Coords(x, 0);
				coordinates.push(coordinate);
				xPrev = x;
			}
		}

		var yPrev = 0;
		var coordinateIndex = 0;
		for (var f = 0; f < flagSets.length; f++)
		{
			var flags = flagSets[f];
			for (var r = 0; r <= flags.timesToRepeat; r++)
			{
				var coordinate = coordinates[coordinateIndex];

				var y;
				if (flags.yShortVector == true)
				{
					y = reader.readByte();
					var sign = (flags.yIsSame ? 1 : -1);
					y *= sign;
					y += yPrev;
				}
				else
				{	
					if (flags.yIsSame == true)
					{
						y = yPrev;
					}
					else
					{
						y = reader.readShortSigned();
						y += yPrev;
					}
				}

				coordinate.y = y;
				yPrev = y;

				coordinateIndex++;
			}
		}

		// hack
		// Align to 16-bit?
		while (reader.byteIndexCurrent % 2 != 0)
		{
			reader.readByte();
		}

		var glyph = new Glyph
		(
			minAndMax,
			endPointsOfContours,
			instructionsAsBytes,
			flagSets, 
			coordinates
		);

		this.glyphs.push(glyph);
	}

	FontFile.prototype.readFromFile_ReadTables_Glyf_Composite = function(reader)
	{
		// "composite" glyph

		var flagSets = [];
		var flags = null;

		while (true)
		{
			flags = GlyphCompositeFlags.fromShort(reader.readShort());
			flagSets.push(flags);

			var glyphIndex = reader.readShort();

			var argument1 = (flags.areArgs1And2Words? reader.readShort() : reader.readByte());
			var argument2 = (flags.areArgs1And2Words? reader.readShort() : reader.readByte());
	
			if (flags.isThereASimpleScale == true)
			{
				var scaleFactor = reader.readShort();
				var scale = new Coords(scaleFactor, scaleFactor);
			}
			else if (flags.isXScaleDifferentFromYScale == true)
			{
				var scale = new Coords
				(
					reader.readShort(),
					reader.readShort()
				);
			}
			else if (flags.use2By2Transform == true)
			{
				// ???
				var scaleX = reader.readShort();
				var scale01 = reader.readShort();
				var scale02 = reader.readShort();
				var scaleY = reader.readShort();
			}

			if (flags.areThereMoreComponentGlyphs == false)
			{
				break;
			}
		}

		if (flags.areThereInstructions == true)
		{
			var numberOfInstructions = reader.readShort();
			var instructions = reader.readBytes();
		}

		// todo - store these values

		var glyph = new GlyphComposite();

		this.glyphs.push(glyph);
	}

	FontFile.prototype.readFromFile_ReadTables_Maxp = function(reader, length)
	{
		// "Maximum Profile"

		var readerByteOffsetOriginal = reader.byteIndexCurrent;

		var version = reader.readInt();
		var numGlyphs = reader.readShort();
		var maxPointsPerGlyphSimple = reader.readShort();
		var maxContoursPerGlyphSimple = reader.readShort();
		var maxPointsPerGlyphComposite = reader.readShort();
		var maxContoursPerGlyphComposite = reader.readShort();

		// todo - Many more fields.

		reader.byteIndexCurrent = readerByteOffsetOriginal;
	}

}

function FontFileCmapEncodingTable
(
	platformID,
	encodingID,
	offset
)
{
	this.platformID = platformID;
	this.encodingID = encodingID; 
	this.offset = offset;

	// Unicode BMP for Windows : PlatformID = 3, EncodingID = 1 
}

function FontFileTableDefn
(
	tableTypeTag,
	checkSum,
	offset,
	length
)
{
	this.tableTypeTag = tableTypeTag;
	this.checkSum = checkSum;
	this.offset = offset;
	this.length = length;
}

function Glyph
(
	minAndMax,
	endPointsOfContours, 
	instructionsAsBytes, 
	flagSets, 
	coordinates
)
{
	this.minAndMax = minAndMax;
	this.endPointsOfContours = endPointsOfContours;
	this.instructionsAsBytes = instructionsAsBytes;
	this.flagSets = flagSets;
	this.coordinates = coordinates;
}
{
	// constants
	
	Glyph.PointsPerInch = 72;
	Glyph.DimensionInFUnits = 2048;

	// methods

	Glyph.prototype.draw = function(parentDOMElement, sizeInPoints)
	{
		var canvasDimensionInPixels = sizeInPoints; // hack

		var canvasSizeInPixels = new Coords
		(
			canvasDimensionInPixels, canvasDimensionInPixels
		);
		var offsetForBaseLines = new Coords
		(
			canvasSizeInPixels.x * .2, canvasSizeInPixels.y * .2
		);

		var canvas = document.createElement("canvas");
		canvas.width = canvasSizeInPixels.x;
		canvas.height = canvasSizeInPixels.y;
		parentDOMElement.appendChild(canvas);
		var graphics = canvas.getContext("2d");

		this.draw_Background
		(
			graphics, canvasSizeInPixels, offsetForBaseLines
		);

		var fUnitsPerPixel = Glyph.DimensionInFUnits / canvasDimensionInPixels;

		var contourPointSets = this.draw_ContourPointSetsBuild
		(
			fUnitsPerPixel, offsetForBaseLines, canvasSizeInPixels
		);

		var contours = this.draw_ContoursBuild
		(
			contourPointSets
		);

		this.draw_ContoursDraw(contours, graphics);
	}

	Glyph.prototype.draw_Background = function
	(
		graphics, canvasSizeInPixels, offset
	)
	{
		graphics.strokeStyle = "Gray";
		graphics.fillStyle = "Gray";

		graphics.strokeRect
		(
			0, 0, 
			canvasSizeInPixels.x, canvasSizeInPixels.y
		);
		graphics.beginPath()
		graphics.moveTo(offset.x, 0);
		graphics.lineTo(offset.x, canvasSizeInPixels.y);
		graphics.stroke();

		graphics.beginPath()
		graphics.moveTo(0, canvasSizeInPixels.y - offset.y);
		graphics.lineTo(canvasSizeInPixels.x, canvasSizeInPixels.y - offset.y);
		graphics.stroke();
	}

	Glyph.prototype.draw_ContourPointSetsBuild = function
	(
		fUnitsPerPixel, offsetForBaseLines, canvasSizeInPixels
	)
	{
		// Convert the flags and coordinates 
		// into sets of points on the contours of the glyph.

		var contourPointSets = [];

		var contourIndex = 0;
		var coordinateIndex = 0;
		var endPointOfContourCurrent = this.endPointsOfContours[contourIndex];

		var coordinateInFUnits = new Coords(0, 0);
		var coordinateInPixels = new Coords(0, 0);
		var coordinateInPixelsPrev = new Coords(0, 0);

		var numberOfContours = this.endPointsOfContours.length;
		var curveControlPoints = [];

		var contourPoints = [];

		for (var f = 0; f < this.flagSets.length; f++)
		{
			var flags = this.flagSets[f];
			for (var r = 0; r <= flags.timesToRepeat; r++)
			{
				var coordinateInFUnits = this.coordinates[coordinateIndex];

				coordinateInPixelsPrev.overwriteWith(coordinateInPixels);
				coordinateInPixels.overwriteWith
				(
					coordinateInFUnits
				).divideScalar
				(
					fUnitsPerPixel
				).add
				(
					offsetForBaseLines
				);

				coordinateInPixels.y = 
					canvasSizeInPixels.y - coordinateInPixels.y;

				var contourPoint = new GlyphContourPoint
				(
					coordinateInPixels.clone(),
					flags.onCurve
				);

				contourPoints.push(contourPoint);

				if (coordinateIndex == endPointOfContourCurrent)
				{
					contourPointSets.push(contourPoints);
					contourPoints = [];
					contourIndex++;
					if (contourIndex < numberOfContours)
					{
						endPointOfContourCurrent = 
							this.endPointsOfContours[contourIndex];
					}
				}

				coordinateIndex++;
			}		
		}

		return contourPointSets;
	}

	Glyph.prototype.draw_ContoursBuild = function(contourPointSets)
	{	
		// Convert sets of points on the contours of the glyph
		// into sets of line segments and/or curves, 
		// and build contours from those sets of segments and curves.

		var contours = [];

		for (var c = 0; c < contourPointSets.length; c++)
		{
			var contourPoints = contourPointSets[c];
			var contourSegments = [];

			for (var p = 0; p < contourPoints.length; p++)
			{
				var pNext = p + 1;
				if (pNext >= contourPoints.length)
				{
					pNext = 0;
				} 

				var contourPoint = contourPoints[p];
				var contourPointNext = contourPoints[pNext];

				if (contourPoint.isOnCurve == true)
				{
					if (contourPointNext.isOnCurve == true)
					{
						var segment = new GlyphContourSegment
						(
							contourPoint.position, null
						);

						contourSegments.push(segment);
					}
					else
					{
						var segment = new GlyphContourSegment
						(
							contourPoint.position, 
							contourPointNext.position
						);

						contourSegments.push(segment);
					}					
				}
				else // if (contourPoint.isOnCurve == false)
				{
					if (contourPointNext.isOnCurve == true)
					{
						// do nothing
					}
					else
					{
						var midpointBetweenContourPointAndNext = contourPoint.position.clone().add
						(
							contourPointNext.position
						).divideScalar(2);

						var segment = new GlyphContourSegment
						(
							midpointBetweenContourPointAndNext,
							contourPointNext.position
						);

						contourSegments.push(segment);
					}
				}
			}

			var contour = new GlyphContour(contourSegments);
			contours.push(contour);	
		}

		return contours;
	}

	Glyph.prototype.draw_ContoursDraw = function(contours, graphics)
	{
		// Render the contours of the glyph.

		for (var c = 0; c < contours.length; c++)
		{
			var contour = contours[c];
			var contourSegments = contour.segments;

			graphics.beginPath();

			for (var s = 0; s < contourSegments.length; s++)
			{
				var sNext = s + 1;
				if (sNext >= contourSegments.length)
				{
					sNext = 0;
				}

				var segment = contourSegments[s];
				var segmentNext = contourSegments[sNext];

				var startPoint = segment.startPoint;
				var curveControlPoint = segment.curveControlPoint;
				var endPoint = segmentNext.startPoint;

				if (s == 0)
				{
					graphics.moveTo
					(
						startPoint.x, startPoint.y
					);
				}

				if (curveControlPoint == null)
				{
					graphics.lineTo
					(
						endPoint.x, endPoint.y
					);
				}
				else
				{
					graphics.quadraticCurveTo
					(
						curveControlPoint.x, 
						curveControlPoint.y,
						endPoint.x, 
						endPoint.y
					);
				}
			} 

			graphics.stroke();
		}

	}
}

function GlyphComposite()
{
	// todo
}
{
	GlyphComposite.prototype.draw = function(parentDOMElement, canvasDimensionInPixels)
	{
		// todo
		// Can't handle composites yet, so just draw a placeholder.

		var canvasSizeInPixels = new Coords
		(
			canvasDimensionInPixels, canvasDimensionInPixels
		);

		var canvas = document.createElement("canvas");
		canvas.width = canvasSizeInPixels.x;
		canvas.height = canvasSizeInPixels.y;
		var graphics = canvas.getContext("2d");

		graphics.strokeStyle = "Gray";
		graphics.fillStyle = "Gray";

		graphics.strokeRect(0, 0, canvasSizeInPixels.x, canvasSizeInPixels.y);
		graphics.beginPath();
		graphics.moveTo(0, 0);
		graphics.lineTo(canvasSizeInPixels.x, canvasSizeInPixels.y);
		graphics.stroke();	
		graphics.beginPath();
		graphics.moveTo(0, canvasSizeInPixels.y);
		graphics.lineTo(canvasSizeInPixels.x, 0);
		graphics.stroke();	

		parentDOMElement.appendChild(canvas);	
	}
}

function GlyphCompositeFlags
(
	areArgs1And2Words,
	areArgsXYValues,
	roundXYToGrid,
	isThereASimpleScale,
	reserved,
	areThereMoreComponentGlyphs,
	areXAndYScalesDifferent,
	use2By2Transform,
	areThereInstructions,
	useMyMetrics
)
{
	this.areArgs1And2Words = areArgs1And2Words;
	this.areArgsXYValues = areArgsXYValues;
	this.roundXYToGrid = roundXYToGrid;
	this.isThereASimpleScale = isThereASimpleScale;
	this.reserved = reserved;
	this.areThereMoreComponentGlyphs = areThereMoreComponentGlyphs;
	this.areXAndYScalesDifferent = areXAndYScalesDifferent;
	this.use2By2Transform = use2By2Transform;
	this.areThereInstructions = areThereInstructions,
	this.useMyMetrics = useMyMetrics;
}
{
	GlyphCompositeFlags.fromShort = function(flagsAsShort)
	{
		var returnValue = new GlyphCompositeFlags
		(
			((flagsAsShort & 1) > 0),
			((flagsAsShort >> 1 & 1) > 0),
			((flagsAsShort >> 2 & 1) > 0),
			((flagsAsShort >> 3 & 1) > 0),
			((flagsAsShort >> 4 & 1) > 0),
			((flagsAsShort >> 5 & 1) > 0),
			((flagsAsShort >> 6 & 1) > 0),		
			((flagsAsShort >> 7 & 1) > 0),
			((flagsAsShort >> 8 & 1) > 0),
			((flagsAsShort >> 9 & 1) > 0)
		);

		return returnValue;		
	}
}

function GlyphContour(segments)
{
	this.segments = segments;
}

function GlyphContourFlags
(
	onCurve, 
	xShortVector, 
	yShortVector, 
	timesToRepeat,
	xIsSame,
	yIsSame
)
{
	this.onCurve = onCurve;
	this.xShortVector = xShortVector;
	this.yShortVector = yShortVector;	
	this.timesToRepeat = timesToRepeat;
	this.xIsSame = xIsSame;
	this.yIsSame = yIsSame;

	// flag bits
	// 0 - on curve
	// 1 - xShortVector - coord is 1 instead of 2 bytes
	// 2 - yShortVector - coord is 1 instead of 2 bytes
	// 3 - repeat - if set, next byte is number of times to repeat
	// 4 - if xShortVector is 1, indicates sign of value (1 = positive, 0 = negative)
	// 4 - if xShortVector is 0, and this flag is 1, x coord is same as previous
	// 4 - if xShortVector is 0, and this flag is 0, x coord is a delta vector
	// 5 - same as 4, but for y instead of x
	// 6 - reserved
	// 7 - reserved	
}
{
	GlyphContourFlags.fromByte = function(flagsAsByte)
	{
		var returnValue = new GlyphContourFlags
		(
			((flagsAsByte & 1) > 0),
			((flagsAsByte >> 1 & 1) > 0),
			((flagsAsByte >> 2 & 1) > 0),
			((flagsAsByte >> 3 & 1) > 0),
			((flagsAsByte >> 4 & 1) > 0),
			((flagsAsByte >> 5 & 1) > 0)		
		);

		return returnValue;
	}
}

function GlyphContourPoint(position, isOnCurve)
{
	this.position = position;
	this.isOnCurve = isOnCurve;
}

function GlyphContourSegment(startPoint, curveControlPoint)
{
	this.startPoint = startPoint;
	this.curveControlPoint = curveControlPoint;
}

</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