Dart DocumentationFontUtils

FontUtils library

@author zz85 / http://www.lab4games.net/zz85/blog @author alteredq / http://alteredqualia.com/

For Text operations in three.js (See TextGeometry)

It uses techniques used in:

typeface.js and canvastext For converting fonts and rendering with javascript http://typeface.neocracy.org

Triangulation ported from AS3 Simple Polygon Triangulation http://actionsnippet.com/?p=1462

A Method to triangulate shapes with holes http://www.sakri.net/blog/2009/06/12/an-approach-to-triangulating-polygons-with-holes/

Properties

var EPSILON #

This code is a quick port of code written in C++ which was submitted to flipcode.com by John W. Ratcliff // July 22, 2000 See original code and more information here: http://www.flipcode.com/archives/EfficientPolygonTriangulation.shtml

ported to actionscript by Zevan Rosser www.actionsnippet.com

ported to javascript by Joshua Koo http://www.lab4games.net/zz85/blog

var EPSILON = 0.0000000001

Functions

dynamic snip(contour, u, v, w, n, verts) #

snip( contour, u, v, w, n, verts ) {

 var p;
 var ax, ay, bx, by;
 var cx, cy, px, py;

 ax = contour[ verts[ u ] ].x;
 ay = contour[ verts[ u ] ].y;

 bx = contour[ verts[ v ] ].x;
 by = contour[ verts[ v ] ].y;

 cx = contour[ verts[ w ] ].x;
 cy = contour[ verts[ w ] ].y;

 if ( EPSILON > (((bx-ax)*(cy-ay)) - ((by-ay)*(cx-ax))) ) return false;

   for ( p = 0; p < n; p++ ) {

     if( (p == u) || (p == v) || (p == w) ) continue;

     px = contour[ verts[ p ] ].x;
     py = contour[ verts[ p ] ].y;

     if ( insideTriangle( ax, ay, bx, by, cx, cy, px, py ) ) return false;

   }

   return true;

}

dynamic insideTriangle(ax, ay, bx, by, cx, cy, px, py) #

insideTriangle( ax, ay,
                bx, by,
                cx, cy,
                px, py ) {

   var aX, aY, bX, bY;
   var cX, cY, apx, apy;
   var bpx, bpy, cpx, cpy;
   var cCROSSap, bCROSScp, aCROSSbp;

   aX = cx - bx;  aY = cy - by;
   bX = ax - cx;  bY = ay - cy;
   cX = bx - ax;  cY = by - ay;
   apx= px  -ax;  apy= py - ay;
   bpx= px - bx;  bpy= py - by;
   cpx= px - cx;  cpy= py - cy;

   aCROSSbp = aX*bpy - aY*bpx;
   cCROSSap = cX*apy - cY*apx;
   bCROSScp = bX*cpy - bY*cpx;

   return ( (aCROSSbp >= 0.0) && (bCROSScp >= 0.0) && (cCROSSap >= 0.0) );

}

dynamic area(contour) #

area( contour ) {

 var n = contour.length;
 var a = 0.0;

 for( var p = n - 1, q = 0; q < n; p = q++ ) {

   a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;

 }

 return a * 0.5;

}

List<List<Vector2>> process(List<Vector2> contour, bool indices) #

List<List<Vector2>> process( List<Vector2> contour, bool indices ) {

 var n = contour.length;

 if ( n < 3 ) return null;

 var result = [],
   verts = new List(n),
   vertIndices = [];

 /* we want a counter-clockwise polygon in verts */

 num u, v, w;

 if ( area( contour ) > 0.0 ) {

   for ( v = 0; v < n; v++ ) verts[ v ] = v;

 } else {

   for ( v = 0; v < n; v++ ) verts[ v ] = ( n - 1 ) - v;

 }

 num nv = n;

 /*  remove nv - 2 vertices, creating 1 triangle every time */

 var count = 2 * nv;   /* error detection */

 for( v = nv - 1; nv > 2; ) {

   /* if we loop, it is probably a non-simple polygon */

   if ( ( count-- ) <= 0 ) {

     //** Triangulate: ERROR - probable bad polygon!

     //throw ( "Warning, unable to triangulate polygon!" );
     //return null;
     // Sometimes warning is fine, especially polygons are triangulated in reverse.
     print( "Warning, unable to triangulate polygon!" );

     if ( indices ) return vertIndices;
     return result;

   }

   /* three consecutive vertices in current polygon, <u,v,w> */

   u = v;    if ( nv <= u ) u = 0;     /* previous */
   v = u + 1;  if ( nv <= v ) v = 0;     /* new v    */
   w = v + 1;  if ( nv <= w ) w = 0;     /* next     */

   if ( snip( contour, u, v, w, nv, verts ) ) {

     var a, b, c, s, t;

     /* true names of the vertices */

     a = verts[ u ];
     b = verts[ v ];
     c = verts[ w ];

     /* output Triangle */

     result.add( [ contour[ a ],
                   contour[ b ],
                   contour[ c ] ] );


     vertIndices.addAll( [ verts[ u ], verts[ v ], verts[ w ] ] );

     /* remove v from the remaining polygon */
     s = v;
     for( t = v + 1; t < nv; t++ ) {

       verts[ s ] = verts[ t ];
       s++;
     }

     nv--;

     /* reset error detection counter */

     count = 2 * nv;

   }

 }

 if ( indices ) return vertIndices;
 return result;

}

dynamic generateShapes(text, [int size = 100, int curveSegments = 4, String font = "helvetiker", String weight = "normal", String style = "normal"]) #

generateShapes( text, [ int size = 100,
                       int curveSegments = 4,
                       String font = "helvetiker",
                       String weight = "normal",
                       String style = "normal"] ) {

 var face = _faces[font][weight][style];

 if (_faces == null) {
   face = new FontFace(size: size, divisions: curveSegments);
   _faces[font][weight][style] = face;
 }
 
 _size = size;
 _divisions = curveSegments;

 _face = font;
 _weight = weight;
 _style = style;

 // Get a Font data json object

 var data = drawText( text );

 var paths = data["paths"];
 var shapes = [];
 var pl = paths.length;

 for ( var p = 0; p < pl; p ++ ) {

   shapes.addAll(paths[ p ].toShapes() );

 }

 return shapes;

}

dynamic extractGlyphPoints(String c, face, scale, offset, path) #

extractGlyphPoints ( String c, face, scale, offset, path ) {

 List<Vector2> pts = [];

 var i, i2, divisions,
   outline, action, length,
   scaleX, scaleY,
   x, y, cpx, cpy, cpx0, cpy0, cpx1, cpy1, cpx2, cpy2,
   laste;

 var glyph = face["glyphs"][ c ];
 if (glyph == null) glyph = face["glyphs"][ '?' ];

 if ( glyph == null ) return null;

 if ( glyph["o"] != null) {

   outline = glyph["_cachedOutline"];
   if (outline == null) {
     glyph["_cachedOutline"] = glyph["o"].split( ' ' );
     outline = glyph["_cachedOutline"];
   }
   length = outline.length;

   scaleX = scale;
   scaleY = scale;

   for ( i = 0; i < length; ) {

     action = outline[ i ++ ];

     //console.log( action );

     switch( action ) {

     case 'm':

       // Move To

       x = int.parse(outline[ i++ ]) * scaleX + offset;
       y = int.parse(outline[ i++ ]) * scaleY;

       path.moveTo( x, y );
       break;

     case 'l':

       // Line To

       x = int.parse(outline[ i++ ]) * scaleX + offset;
       y = int.parse(outline[ i++ ]) * scaleY;
       path.lineTo(x,y);
       break;

     case 'q':

       // QuadraticCurveTo

       cpx  = int.parse(outline[ i++ ]) * scaleX + offset;
       cpy  = int.parse(outline[ i++ ]) * scaleY;
       cpx1 = int.parse(outline[ i++ ]) * scaleX + offset;
       cpy1 = int.parse(outline[ i++ ]) * scaleY;

       path.quadraticCurveTo(cpx1, cpy1, cpx, cpy);

       if (pts.length > 0) laste = pts[ pts.length - 1 ];

       if ( laste != null ) {

         cpx0 = laste.x;
         cpy0 = laste.y;

         for ( i2 = 1; i2 <= divisions; i2 ++ ) {

           var t = i2 / divisions;
           var tx = ShapeUtils.b2( t, cpx0, cpx1, cpx );
           var ty = ShapeUtils.b2( t, cpy0, cpy1, cpy );
         }

       }

       break;

     case 'b':

       // Cubic Bezier Curve

       cpx  = int.parse(outline[ i++ ]) *  scaleX + offset;
       cpy  = int.parse(outline[ i++ ]) *  scaleY;
       cpx1 = int.parse(outline[ i++ ]) *  scaleX + offset;
       cpy1 = int.parse(outline[ i++ ]) * -scaleY;
       cpx2 = int.parse(outline[ i++ ]) *  scaleX + offset;
       cpy2 = int.parse(outline[ i++ ]) * -scaleY;

       path.bezierCurveTo( cpx, cpy, cpx1, cpy1, cpx2, cpy2 );

       if (pts.length > 0) laste = pts[ pts.length - 1 ];

       if ( laste != null ) {

         cpx0 = laste.x;
         cpy0 = laste.y;

         for ( i2 = 1; i2 <= divisions; i2 ++ ) {

           var t = i2 / divisions;
           var tx = ShapeUtils.b3( t, cpx0, cpx1, cpx2, cpx );
           var ty = ShapeUtils.b3( t, cpy0, cpy1, cpy2, cpy );

         }

       }

       break;

     }

   }
 }



 return { "offset": glyph["ha"]*scale, "path":path};
}

dynamic drawText(String text) #

drawText( String text ) {

 var characterPts = [], allPts = [];

 // RenderText

 var i, p,
   face = getFace(),
   scale = _size / face["resolution"],
   offset = 0,
   chars = text.split( '' ),
   length = chars.length;

 var fontPaths = [];

 for ( i = 0; i < length; i ++ ) {

   var path = new Path();

   var ret = extractGlyphPoints( chars[ i ], face, scale, offset, path );
   offset += ret["offset"];

   fontPaths.add( ret["path"] );

 }

 // get the width

 var width = offset / 2;
 //
 // for ( p = 0; p < allPts.length; p++ ) {
 //
 //  allPts[ p ].x -= width;
 //
 // }

 //var extract = this.extractPoints( allPts, characterPts );
 //extract.contour = allPts;

 //extract.paths = fontPaths;
 //extract.offset = width;

 return { "paths" : fontPaths, "offset" : width };

}

dynamic loadFace(data) #

loadFace( data ) {

 var family = data["familyName"].toLowerCase();

 if (_faces[ family ] == null) _faces[ family ] = {};

 if (_faces[ family ][ data["cssFontWeight"] ] == null) _faces[ family ][ data["cssFontWeight"] ] = {};
 _faces[ family ][ data["cssFontWeight"] ][ data["cssFontStyle"] ] = data;

 // TODO - Parse data
 var face = _faces[ family ][ data["cssFontWeight"] ][ data["cssFontStyle"] ] = data;

 return data;

}

Map<String, Map> getFace() #

Map<String, Map> getFace() => _faces[ _face ][ _weight ][ _style ];

Classes