Skip to content

Instantly share code, notes, and snippets.

@johnsogg
Last active October 8, 2019 01:49
Show Gist options
  • Save johnsogg/2bab311ea798fd301084 to your computer and use it in GitHub Desktop.
Save johnsogg/2bab311ea798fd301084 to your computer and use it in GitHub Desktop.
Demonstration of GetPathForGlyph and CGPath in Xamarin.iOS for getting a vector representation of character glyphs. Includes quadratic and cubic Bezier functions.
using System;
using System.Collections.Generic;
using System.Drawing;
using MonoTouch.CoreGraphics;
using MonoTouch.CoreText;
namespace Mystuff
{
public class FontPathHelper
{
char m_char;
CTFont m_font;
public FontPathHelper(char c, CTFont f)
{
m_char = c;
m_font = f;
}
public CGPath GetPath()
{
ushort[] glyphs = new ushort[1];
m_font.GetGlyphsForCharacters(new char[] { m_char }, glyphs); // loads up 'glyphs'
CGPath path = m_font.GetPathForGlyph(glyphs[0]);
return path;
}
/// <summary>
/// Creates a 'flattened path' for the given character and font. This implementation is
/// just to demonstrate how to create a CGPath, how to traverse it, and how to create a
/// piecewise linear approximation.
///
/// Note that CGPath might contain several sub-paths. This is because many characters
/// have holes or non-contiguous diacriticals (e.g. the dot atop a lower-case i).
///
/// The tricky part that this does not address is how to sample the curved parts (quadratic
/// and cubic Bezier path elements). In this implementation we're just taking ten steps,
/// regardless of how big it is. In a real application (e.g. drawing to screen or generating
/// STL output for 3D printers) we'd probably use a fancier algorithm to collect fewer
/// points depending on the needed resolution. Something like this (I haven't tried it):
///
/// http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
/// Ramer-Douglas-Peucker algorithm
/// </summary>
public List<List<PointF>> FlattenedPath()
{
// Using the CGPath from GetPath(), create a piecewise linear representation of each
// sub-path. The returned list describes each subpath, which is in turn composed of
// individual points.
List<List<PointF>> ret = new List<List<PointF>>();
CGPath path = GetPath();
List<PointF> segment = null; // holds current segment
PointF currentEndpoint; // holds most recently recorded point
int numSteps = 10; // don't go overboard.
path.Apply(((CGPathElement element) =>
{
// Console.WriteLine("\t\tType: {0}", element.Type);
// Console.WriteLine ("\t\tPoint 1: {0}", element.Point1);
// Console.WriteLine ("\t\tPoint 2: {0}", element.Point2);
// Console.WriteLine ("\t\tPoint 3: {0}", element.Point3);
switch (element.Type)
{
case CGPathElementType.MoveToPoint:
segment = new List<PointF>();
ret.Add(segment);
currentEndpoint = RecordPoint(segment, element.Point1);
break;
case CGPathElementType.AddLineToPoint:
currentEndpoint = RecordPoint(segment, element.Point1);
break;
case CGPathElementType.AddQuadCurveToPoint:
currentEndpoint = RecordQuadraticPath(segment, numSteps, currentEndpoint, element.Point1, element.Point2);
break;
case CGPathElementType.AddCurveToPoint:
currentEndpoint = RecordCubicPath(segment, numSteps, currentEndpoint, element.Point1, element.Point2, element.Point3);
break;
case CGPathElementType.CloseSubpath:
segment = null; // ensures error unless next element is a MoveToPoint
break;
}
}));
return ret;
}
static PointF RecordPoint(List<PointF> segment, PointF src)
{
PointF ret = new PointF(src.X, src.Y);
segment.Add(ret);
return ret;
}
static PointF RecordQuadraticPath(List<PointF> segment, int n, PointF p0, PointF p1, PointF p2)
{
// records points using a quadratic bezier with t in (0..1], with n steps.
PointF pt;
for (int i=1; i <= n; i++) // Do not want t=0, so start at i=1.
{
float t = ((float) i) / ((float) n);
pt = BezierQuad(t, p0, p1, p2);
segment.Add(pt);
}
return pt;
}
static PointF BezierQuad(float t, PointF p0, PointF p1, PointF p2)
{
float one_minus_t = (1f - t);
float one_minus_t_sq = one_minus_t * one_minus_t;
float t_sq = t * t;
float term0 = one_minus_t_sq;
float term1 = 2f * one_minus_t * t;
float term2 = t_sq;
float x = term0 * p0.X + term1 * p1.X + term2 * p2.X;
float y = term0 * p0.Y + term1 * p1.Y + term2 * p2.Y;
// Console.WriteLine("Bezier Quad, t={0}: {1}, {2}", t.ToString("N2"), x.ToString("N2"), y.ToString("N2"));
return new PointF(x, y);
}
static PointF RecordCubicPath(List<PointF> segment, int n, PointF p0, PointF p1, PointF p2, PointF p3)
{
// records points using a cubic bezier with t in (0..1], with n steps.
PointF pt;
for (int i=1; i <= n; i++) // Do not want t=0, so start at i=1.
{
float t = ((float) i) / ((float) n);
pt = BezierCubic(t, p0, p1, p2, p3);
segment.Add(pt);
}
return pt;
}
static PointF BezierCubic(float t, PointF p0, PointF p1, PointF p2, PointF p3)
{
float one_minus_t = 1f - t;
float one_minus_t_sq = one_minus_t * one_minus_t;
float one_minus_t_cb = one_minus_t * one_minus_t * one_minus_t;
float t_sq = t * t;
float t_cb = t * t * t;
float term0 = one_minus_t_cb;
float term1 = 3f * one_minus_t_sq * t;
float term2 = 3f * one_minus_t * t_sq;
float term3 = t_cb;
float x = term0 * p0.X + term1 * p1.X + term2 * p2.X + term3 * p3.X;
float y = term0 * p0.Y + term1 * p1.Y + term2 * p2.Y + term3 * p3.Y;
// Console.WriteLine("Bezier Cubic, t={0}: {1}, {2}", t.ToString("N2"), x.ToString("N2"), y.ToString("N2"));
return new PointF(x, y);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment