Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C# 3.5

How to Embroid Glyph from True Type Font File

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
11 Jan 2016CPOL6 min read 31.7K   414   12   5
Using System.Windows.Media library to create embroidery design of a glyph from true type font file

Contents

Introduction

This article describes a few steps to get embroided glyph. You can choose any glyph from any true type font and embroid it. Firstly, we need to extract glyph geometry from a true type font file. The second step is to produce zigzags on the glyph lines. Produced glyph zigzags look like the following:

Image 1

After converting glyph zigzags to an embroidery format, such as DST, it could be embroided by embroidery machine on clothes.

Background

This article is based on an idea of the ability to embroid any glyph of any true type font without any commercial software.

Functions of Library to Use

First of all, the .NET library System.Windows.Media should be referenced in the project, and the following line should be added in the namespace declaration of our file:

C#
using System.Windows.Media;

True type font family file usually contains a single font family. Font family has a few typefaces.

According to MSDN, typeface represents a combination of FontFamily, FontWeight, FontStyle, and FontStretch.

Arial true type font, which is usually installed on Windows platform has nine typefaces:

Image 2

Figure 1 - True type font variation

To get font families from a font file, we use method GetFontFamilies of a Fonts class from System.Windows.Media namespace as follows:

C#
ICollection<FontFamily> families = Fonts.GetFontFamilies(fontPath);

If we are in need of iterating through typefaces of a font family, we use a GetTypefaces class of FontFamily class from the same namespace:

C#
ICollection<Typeface> typefaces = fontFamily.GetTypefaces();

Instance of Typeface class allows to create GlyphTypeface instance:

C#
GlyphTypeface glyph;
GlyphTypeface glyphTypeface = typeface.TryGetGlyphTypeface(out glyph) ? glyph : null;

When we have got an instance of GlyphTypeface class, we can get the character map of a typeface from its property CharacterToGlyphMap. This property has type of IDictionary<int, ushort> with unicode as key and glyph index as value.

To work with glyph geometry, the class GlyphTypeface is used. Glyph geometry is obtained by its index:

C#
Geometry geom = glyphTypeface.GetGlyphOutline(glyphIndex, renderingEmSize, hintingEmSize);

Although more often, we would like to get glyph by its unicode number. So we get glyph index from character map by its unicode number and then obtain glyph geometry. In the method GetGlyphOutline, three parameters are passed. The third parameter hintingEmSize is discussed in detail here.

In the method GetGlyphPoints(), an enumeration of PointCollection is built on PathGeometry instance, which is passed in glyphGeom parameter. PathGeometry instance of a glyph is obtained by the method GetOutlinedPathGeometry() of Geometry instance. Each collection of points in the enumeration represents a sequence of points for each figure in PathGeometry. Which will be zigzagified in the next step.

C#
/// <summary>
/// Iterate through <see cref="PathFigureCollection"/> 
/// to collect enumeration of <see cref="PointCollection"/>/// </summary>
/// <param name="glyphGeom"><see cref="PathGeometry"/> of a glyph</param>
/// <returns>Returns enumeration of <see cref="PointCollection"/> 
/// of flattened <see cref="PathGeometry"/></returns>
public IEnumerable<PointCollection> GetGlyphPoints(PathGeometry glyphGeom)
{
  var result = new List<PointCollection>();
  PointCollection curColl = new PointCollection();    
  foreach (PathFigure figure in glyphGeom.Figures)
  {
    PointCollection flattenedShape = PathFigureFlattening.GetPathFigureFlattenedPoints(figure);
    result.Add(flattenedShape);
  }
  return result;
}

Now we have sequences of points for specified glyph, and it can be converted to SVG picture or just JSON object. Although let's apply zigzagifying effect on this contour of the glyph. To do that, we need to split each intercept into small ones to provide zigzagifying by them.

Get Intermediate Points on Intercept

In the following code of the GetPointsOnLine() method, an axis direction is chosen by the longest axis delta of an itercept. It means, if delta X is greater than delta Y, then the pass through X axis and calculating delta Y for each step by fixed increment value of X is chosen. In case when delta Y of intercept is greater than delta X, it passes through Y axis with fixed pitch. It allows to avoid deviation. The length variable is calculated by the Pythagorean theorem formula:

The square of hypothenuse is equals to sum of cathetus squares.

By the length and the step value, the quantity of points on intercept is calculated. For short intercepts with less than six points, it sets the quantity of points to seven. It allows to build zigzag and leaves enough points to create curved zigzags with neighbor intercepts. It also checks for horizontal and vertical lines, there is a block of code to calculate point coordinates in this case. The method returns points as three list of coordinates: first two points, mid points, and last two points. Start point and end point are not included in the result. Mid points will be used for simple zigzagifying by finding perpendicular points to mid points of small interceptors. First two and last two will be used for finding points on curve to neighbor intercepts by Casteljau algorithm of finding points on curve, and then resolved sequence of points will be used to build zigzagified curve.

C#
/// <summary>
/// Returns points positioned with specified pitch between two points 
/// </summary>
/// <param name="start">Start point of intercept</param>
/// <param name="end">End point of intercept</param>
/// <param name="step"></param>
/// <returns></returns>
private List<List<DoubleVertice>> GetPointsOnLine(DoubleVertice start, DoubleVertice end, double step)
{
  var result = new List<List<DoubleVertice>>();
  var firstTwo = new List<DoubleVertice>();
  var lastTwo = new List<DoubleVertice>();
  var mediPoints = new List<DoubleVertice>();
  double cathetusX;
  double cathetusY;
  double dx;
  double dy;
  GetDxDy(start, end, out cathetusX, out cathetusY);
  double y0 = start.Y;
  double y1 = end.Y;
  double x0 = (double)start.X;
  double x1 = (double)end.X;          
  double k;
  bool shortLine = false;
  double deltaX = 0.0;
  double deltaY = 0.0;
  int length = (int)Math.Sqrt(cathetusX * cathetusX + cathetusY * cathetusY);
  int n = (int) Math.Sqrt((length*length)/(step*step));
  if (ShortLineIterceptsCount - 1 > n)
  {
    n = ShortLineIterceptsCount;
    shortLine = true;
    dx = dx/ShortLineIterceptsCount;
    dy = dy/ShortLineIterceptsCount;
  }
  else
  {
    var deltaVector = Get_qrtnDeltaVector(x0, y0, x1, y1, step);
    dx = Math.Abs(deltaVector.X);
    dy = Math.Abs(deltaVector.Y);
  }            
  if (dy == 0 && dx == 0)
  {
    result.Add(new List<DoubleVertice> {start, end});
    return result;
  }
  if (dy == 0 || dx == 0)
  {
    if (dy == 0) //  horizontal
    {
      deltaY = 0;
      if (shortLine) step = dx;
      if (x1 > x0) deltaX = step;
      else deltaX = -step;
    }
    else if (dx == 0) //vertical
    {
      deltaX = 0;
      if (shortLine) step = dy;
      if (y1 > y0) deltaY = step;
      else deltaY = -step;
    }
  }
  else
  {               
    if ((x0 < x1) && (y0 > y1))
    {
      // right up
      deltaX = dx;
      deltaY = -dy;
    }
    else if ((x0 > x1) && (y0 > y1))
    {
      // left up
      deltaX = -dx;
      deltaY = -dy;
    }
    else if ((x0 > x1) && (y0 < y1))
    {
      // left down
      deltaX = -dx;
      deltaY = dy;
    }
    else
    {
      deltaX = dx;
      deltaY = dy;
    }
  }
  double dX = (deltaX);
  double dY = (deltaY);
  deltaX = 0;
  deltaY = 0;
  double curX = x0, curY = y0;
  if ((dx >= dy) && (dx != 0)) // goes by X Axis
  {
    k = dY/dX;                
    for (var i = 0; (i < 2) && (dX != 0); i++)
    {
      deltaX += dX;
      deltaY = (deltaX*k);
      curX = x0 + deltaX;
      curY = y0 + deltaY;
      firstTwo.Add(new DoubleVertice {X = curX, Y = curY});
    }
    for (var i = 4; (i < n) && (dX != 0); i++)
    {
      curX = x0 + deltaX;
      curY = y0 + deltaY;
      mediPoints.Add(new DoubleVertice {X = curX, Y = curY});
      deltaX += dX;
      deltaY = (deltaX*k);
    }   
    for (var i = 0; (i < 2) && (dX != 0); i++)
    {
      curX = x0 + deltaX;
      curY = y0 + deltaY;
      lastTwo.Add(new DoubleVertice {X = curX, Y = curY});
      deltaX += dX;
      deltaY = (deltaX*k);
    }
  }
  else if (dy != 0)
  {
     k = dX/dY;  
     for (int i = 0; (i < 2) && (dY != 0); i++)
     {
       deltaX = (deltaY*k);
       deltaY += dY;
       curY = y0 + deltaY;
       curX = x0 + deltaX;
       firstTwo.Add(new DoubleVertice {X = curX, Y = curY});
     }
     for (int i = 4; (i < n) && (dY != 0); i++)
     {
       curY = y0 + deltaY;
       curX = x0 + deltaX;
       mediPoints.Add(new DoubleVertice {X = curX, Y = curY});
       deltaX = (deltaY*k);
       deltaY += dY;
     } 
     for (int i = 0; (i < 2) && (dY != 0); i++)
     {
        curY = y0 + deltaY;
        curX = x0 + deltaX;
        lastTwo.Add(new DoubleVertice {X = curX, Y = curY});
        deltaX = (deltaY*k);
        deltaY += dY;
     }
  }
  result.Add(firstTwo);
  result.Add(mediPoints);
  result.Add(lastTwo);
  if (mediPoints.Count == 0)
  {
    Trace.WriteLine(string.Format("{0} {1}", start, end));
  }
  return result;
}

Calculate Perpendicular Points Coordinates to Get Zigzags

In the following picture, we have:

  • neighbour step points of intercept A B
  • mid point of intercept M
  • points of zigzag to be found C D

To find points of zigzags, the answer from stackoverflow was used. The solution is based on formula of equilaterial triangle height calculation. So coordinates of points C and D at figure 2 are calculated in the method GetMediPerpendicularPoint(). Green dash line is our zigzag we are going to draw. Let's assume C is left point of zigzag. For point C, method is called with point A and M for point0 and point1 respectively. Parameter opposite in this case is false. So point D is right point of zigzag and opposite is true. We pass points M and D as point0 and point1 for second zigzag point (D). To get all zigzags for whole intercept, we go through all mid points we get by GetPointsOnLine() method.

Image 3

Figure 2 - Perpendicular points detection

The normal of an equilateral triangle is the square root of three-quarters, so logic in the following method is based on this statement.

C#
DoubleVertice GetMediPerpendicularPoint
(DoubleVertice point0, DoubleVertice point1, double distance, bool opposite)
{
   double x, y, lx, ly, mx, my;
   x = point1.X;
   y = point1.Y;
   lx = point0.X;
   ly = point0.Y;
   LineDirection direction;
   double xPerpOffset = 0;
   double yPerpOffset = 0;
   var dx = x - lx;
   var dy = y - ly;
   mx = (lx + x) / 2;
   my = (ly + y) / 2;
   if (dx != 0.0 && dy != 0.0)
   {
       var scale = Math.Sqrt(0.75);
       var dX = scale * (y - ly);
       var dY = -scale * (x - lx);
       var length = Math.Sqrt(dX * dX + dY * dY);
       var xnorm = Math.Abs(dX / length);
       var ynorm = Math.Abs(dY / length);
       xPerpOffset = Math.Abs(xnorm * distance);
       yPerpOffset = Math.Abs(ynorm * distance);
       if ((x > lx) && (y > ly)) direction = LineDirection.UpRight;
       else if ((x < lx) && (y < ly)) direction = LineDirection.DownLeft;
       else if ((x > lx) && (y < ly)) direction = LineDirection.DownRight;
       else direction = LineDirection.UpLeft;
       switch (direction)
       {
           case LineDirection.UpRight:
               if (opposite) yPerpOffset *= -1;
               else xPerpOffset *= -1;
               break;
           case LineDirection.DownRight:
               if (opposite)
               {
                   yPerpOffset *= -1;
                   xPerpOffset *= -1;
               }
               break;
           case LineDirection.DownLeft:
               if (opposite) xPerpOffset *= -1;
               else yPerpOffset *= -1;
               break;
           case LineDirection.UpLeft:
               if (!opposite)
               {
                   yPerpOffset *= -1;
                   xPerpOffset *= -1;
               }
               break;
       }
   }
   else if (dx == 0.0)
   {
       yPerpOffset = 0.0;
       if (ly < y)
       {
           xPerpOffset = distance * (opposite ? -1 : 1);
       }
       else
       {
           xPerpOffset = distance * (opposite ? 1 : -1);
       }
   }
   else if (dy == 0.0)
   {
       xPerpOffset = 0.0;
       if (lx < x)
       {
           yPerpOffset = distance * (opposite ? 1 : -1);
       }
       else
       {
           yPerpOffset = distance * (opposite ? -1 : 1);
       }
   }
   return new DoubleVertice { X = mx + xPerpOffset, Y = my + yPerpOffset };
}

Create Zigzagified Curved Turn Between Intercepts

To get well rounded turns between intercepts, we add more mid points between second to last points of intercepts. Points are added by Casteljau algorithm. This is not the best way for this case, there are a few other ways, although for simple shapes, it works well. Interpretation of Casteljau algorithm adopted from a few CodeProject articles:

C#
  #region Casteljau DRAW METHOD

private List<DoubleVertice> casteljauPoints;
private List<DoubleVertice> drawCasteljau(List<DoubleVertice> list, double step)
{
    casteljauPoints = list;
    var result = new List<DoubleVertice>();
    for (double t = 0; t <= 1; t += step)
    {
        DoubleVertice tmp = getCasteljauDoubleVertice(list.Count - 1, 0, t);
        result.Add(tmp);
    }
    casteljauPoints.Clear();
    return result;
}

private DoubleVertice getCasteljauDoubleVertice(int r, int i, double t)
{
    if (r == 0) return casteljauPoints[i];
    DoubleVertice p1 = getCasteljauDoubleVertice(r - 1, i, t);
    DoubleVertice p2 = getCasteljauDoubleVertice(r - 1, i + 1, t);
    return new DoubleVertice { X = ((1 - t) * p1.X + t * p2.X), Y = ((1 - t) * p1.Y + t * p2.Y) };
}

private IEnumerable<DoubleVertice> GetZigzagifiedTurn
(IEnumerable<DoubleVertice> vertices, double zigzagWidth, double step = 0.2)
{
    var list = drawCasteljau(vertices.ToList(), step);
    int pointCount = list.Count;
    var result = new List<DoubleVertice>();
    DoubleVertice prevVertex = list[0];
    for (int i = 1; i < pointCount; i++)
    {
        var curVertex = list[i];
        result.AddRange(GetZigzaggedBetweenTwoPoint(prevVertex, curVertex, zigzagWidth));
        prevVertex = curVertex;
    }
    return result;
}
#endregion

Convert to Embroidery DST Format

How to convert sequence of points to Tajima DST format covered in an article.

Zigzagified glyph can be also converted to SVG with console application.

Conclusion

Now we have methods to get a sequence of points of any glyph from true type font file and can create zigzags on its lines. As was said, this way is not the best one. For more accurate drawing, there are another algorithms, which I did not find.

In the following figure is pictured zigzagified glyph in SVG format:

Image 4

Figure 3 - One of webdings true type font glyphs.

Live example of glyph zigzagifying is here

Source Code

You can download source code from here.

References

Points of Interest

I would like slow drawing.

History

  • 12th January, 2016: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
SuggestionNice work; what you really need Pin
sprezzatura21-Nov-17 3:24
sprezzatura21-Nov-17 3:24 
BugGreat! Pin
Jim Meadors7-Feb-16 19:18
Jim Meadors7-Feb-16 19:18 
Praiseinsanely awesome! Pin
John Torjo12-Jan-16 19:26
professionalJohn Torjo12-Jan-16 19:26 
GeneralRe: insanely awesome! Pin
Den36713-Jan-16 22:42
Den36713-Jan-16 22:42 
GeneralRe: insanely awesome! Pin
John Torjo14-Jan-16 0:10
professionalJohn Torjo14-Jan-16 0:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.