sunLoadingImage
whowedImag
decoration left 1
decoration left 2
transhome
transprojects
transgallery
transarticles
decoration rigth
English
Українська
Show/Hide search bar
black cat logo variable logo
[23 Jan 2013]

Contours of symbol from True Type font

This tutorial shows how to load True Type font with help of WinAPI. Then is shows how to get and parse information about contours of the symbol (symbol outlines), and how to transform contours to finite number of vertices. You can use vertices to create lines (segments for rendering of symbol's outlines) or for triangulation (for rendering of solid text). Triangulation of contours is shown in next tutorial. You can also check tutorial about how toassemble text string from symbols.
Here is the example of what we will get in the end of the tutorial:
To load font you have to call function initializeFont(). After that you will have valid pointer to created font.
HFONT fontPtr;
const int sizeOfFont = 30;

// load font by name and parameters, initializeFont("Arial", false, false);
void initializeFont(const std::string fontName, bool bold, bool italic)
{
   bool weight = FW_REGULAR;
   if(bold)
   {
      weight = FW_BOLD;
   }

   // create WinAPI font
   fontPtr = CreateFont(
         sizeOfFont, 0, 0, 0,
         weight, italic, 0, 0, DEFAULT_CHARSET,
         OUT_DEVICE_PRECIS, CLIP_DEFAULT_PRECIS, NONANTIALIASED_QUALITY,
         FF_DONTCARE, fontName.c_str()
      );

   if(fontPtr == nullptr)
   {
      // impossible to create font
   }
}
GetSymbolOutlines() function returns contours for specified symbol. First of all function defines storage for contours. Each contour is represented with array of vertices. If you connect all adjacent vertices with line segments then you will get closed contour. Symbol can contain multiple contours. For many symbols there is only outer contour, like for symbol T. For symbols like O there are two contours: outer and inner. For symbols built from many parts, like symbol i, there are contours for each part of the symbol (two outer symbols for symbol i).
Then getSymbolOutlines() determines how much memory is required to store information for contours for specified symbol, and allocates such amount of memory. After that function queries outlines of the symbol from WinAPI. Contours are returned in quite inconvenient format, so you have to parse that data.
// get contours for symbol, getSymbolOutlines('a');
std::vector<std::vector<glm::vec2>> getSymbolOutlines(char symbol)
{
   std::vector<std::vector<glm::vec2>> out;

   HDC hdc = CreateCompatibleDC(NULL);
   if (hdc != NULL)
   {
      SelectObject(hdc, fontPtr);

      // get amount of memory required to store contours
      std::unique_ptr<GLYPHMETRICS> glyphMetrics(new GLYPHMETRICS);
      static const MAT2 rotation = { 0, 1, 0, 0, 0, 0, 0, 1 };
      DWORD bufferSize = GetGlyphOutline(hdc, symbol, GGO_NATIVE,
                        glyphMetrics.get(), 0, 0, &rotation);

      // перевірити чи розмір даних не завеликий з контурами не завеликий
      if(bufferSize > 200000)
      {
         // щось не так
      }

      // дістати дані, які містіть інформацію про контур символа
      std::vector<char> outBuffer(bufferSize);
      DWORD error = GetGlyphOutline(hdc, symbol, splineBuildType,
                     glyphMetrics.get(), bufferSize, outBuffer.data(), &identity);
      TTPOLYGONHEADER * outlinesData = reinterpret_cast(outBuffer.data());

      // дані про контури символа містяться в структурі TTPOLYGONHEADER
      out = parseSymbolOutlinesData(hdc, outlinesData, bufferSize);

      DeleteDC(hdc);
   }

   return out;
}
Function parseSymbolOutlinesData() parses buffer with information about contours, that has been provided by WinAPI. Buffer contains polygons. Each polygon has its type. Possible types are line segment and bezier spline. Among supported bezier splines are quadratic bezier curve and cubic bezier curve. If you called GetGlyphOutline() with GGO_NATIVE flag, then buffer won't contain cubic bezier curves (as in our example). And if you call GetGlyphOutline() with GGO_BEZIER flag, then buffer wan't contain quadratic bezier curves. Our example supports only quadratic bezier splines and simple line segemnts.
Lines are added to contour as two vertices (start and end vertices), and splines are tesselated to finite number of points, and then added to contour.
Coordinates of vertices in the buffer are in fixed point format. Such numbers can be transformed to floating point format with fromFixed() function.
// parse contours data
std::vector<std::vector<glm::vec2>> parseSymbolOutlinesData(
   HDC hDC,
   LPTTPOLYGONHEADER outlinesData,
   DWORD size
)
{
   // pointer to the start of the buffer
   LPTTPOLYGONHEADER dataStart = outlinesData;
   // current line segment / curve of contour
   LPTTPOLYCURVE curCuve;
   // error if true
   bool error = false;

   // parse all curve that are in contour
   std::vector<std::vector<glm::vec2>> contours;
   while ((DWORD)outlinesData < (DWORD)(((LPSTR)dataStart) + size))
   {
      std::vector<glm::vec2> contour;

      if (outlinesData->dwType == TT_POLYGON_TYPE)
      {
         // add first point to contour
         contour.push_back(glm::vec2(fromFixed(outlinesData->pfxStart.x),
                  fromFixed(outlinesData->pfxStart.y)));

         // next control point
         curCuve = (LPTTPOLYCURVE) (outlinesData + 1);

         // collect all control points for curve
         while ((DWORD)curCuve < (DWORD)(((LPSTR)outlinesData) + outlinesData->cb))
         {
            POINTFX ptStart = *(LPPOINTFX)((LPSTR)curCuve - sizeof(POINTFX));
            WORD offset = 0;
            if (curCuve->wType == TT_PRIM_LINE)
            {
               appendLine(ptStart, curCuve, contour);
            }
            else if(curCuve->wType == TT_PRIM_QSPLINE)
            {
            // quadratic bezier curve (with GGO_NATIVE flag for getGlyphOutline)
               appendQuadraticBezier(ptStart, curCuve, contour,
                     angleEpsilon, distanceEpsilon);
            }
            else if(curCuve->wType == TT_PRIM_QSPLINE)
            {
               // cubic bezier curve (with GGO_BEZIER flag for getGlyphOutline)
               // if you want to use cubic beziers then add function
               // similar to appendQuadraticBezier() here.
               // Just get one more additional point

               error = true;
               break;
            }
            else
            {
               // unknown type of primitive
               error = true;
               break;
            }

            // to the next curve in the contour
            curCuve = (LPTTPOLYCURVE)&(curCuve->apfx[curCuve->cpfx]);
         }

         if(error)
         {
            break;
         }

         // end of contour
      }
      else
      {
         // unknown structure in buffer
         error = true;
         break;
      }

      // to next contour
      outlinesData = (LPTTPOLYGONHEADER)(((LPSTR)outlinesData) + outlinesData->cb);

      contours.push_back(std::move(contour));
   }

   return contours;
}
Function to transform fixed point value into floating point value:
float fromFixed(FIXED fixed)
{
   // integer part + fractional part
   // fractional part is encoded with unsigned short
   return float(fixed.value) + float(fixed.fract) / USHRT_MAX;
}
Function to add new vertex to contour:
void appendToResult(float x, float y, std::vector<glm::vec2&gr; & out)
{
   // add new vertex to contour
   uint s = out.size();
   if(s>1)
   {
      // not same vertex as previous
      if(x != out[s-1].x || y != out[s-1].y)
      {
         out.push_back(glm::vec2(x, y));
      }
   }
   else
   {
      out.push_back(glm::vec2(x, y));
   }
}
Function parses and adds new line to contour:
void appendLine(POINTFX start, LPTTPOLYCURVE curCuve, std::vector<glm::vec2> & out)
{
   // add line
   for (WORD i = 0; i < curCuve->cpfx; i++)
   {
      float x = fromFixed(curCuve->apfx[i].x);
      float y = fromFixed(curCuve->apfx[i].y);
      appendToResult(x, y, out);
   }
}
Function parses and adds quadratic bezier curve to contour:
void appendQuadraticBezier(
   POINTFX start,
   LPTTPOLYCURVE curCuve,
   std::vector<glm::vec2> & out)
{
   // quadratic bezier curves are generated only when
   // GGO_NATIVE flag are passed to GetGlyphOutline()

   // control points of quadratic bezier curves
   std::vector<glm::vec3> controlPoints(3);

   // First control point
   controlPoints[0] = gp_Pnt2d(fromFixed(start.x), fromFixed(start.y));

   for (WORD i = 0; i < curCuve->cpfx; )
   {
      // Second control point
      controlPoints[1] = gp_Pnt2d(fromFixed(curCuve->apfx[i].x),
                  fromFixed(curCuve->apfx[i].y));
      i++;

      // Third control point
      if (i == (curCuve->cpfx - 1))
      {
         controlPoints[2] = glm::vec2(fromFixed(curCuve->apfx[i].x),
               fromFixed(curCuve->apfx[i].y));
         i++;
      }
      else
      {
         // third control point is between current and next
         controlPoints[2].x((fromFixed(curCuve->apfx[i-1].x) +
                  fromFixed(curCuve->apfx[i].x))/2);
         controlPoints[2].y((fromFixed(curCuve->apfx[i-1].y) +
                  fromFixed(curCuve->apfx[i].y))/2);
      }

      // divide bezier curve into smaller line segments
      {
         PS. You can use tesselation of curve that depends on curvature and size

         int detail = 5;
         float step = 1.0f / detail;

         for(int j=0; j<=detail; j++)
         {
            float t = step * j;
            float ti = 1.0f - t;

            float m0 = pow(t, 0) * pow(ti, 2);
            float m1 = pow(t, 1) * pow(ti, 1) * 2.0f;
            float m2 = pow(t, 2) * pow(ti, 0);

            p.x += controlPoints[0].x * m0 +
                  controlPoints[1].x * m1 + controlPoints[2].x * m2;
            p.y += controlPoints[0].y * m0 +
                  controlPoints[1].y * m1 + controlPoints[2].y * m2;

            appendToResult(p.x, p.y, out);
         }
      }

      // End of previous curve is start of next curve
      controlPoints[0] = controlPoints[2];
   }
}
Don't forget to remove font
if(m_impl->fontPtr)
{
   DeleteObject(m_impl->fontPtr);
}



Sun and Black Cat- Igor Dykhta (igor dykhta email) © 2007-2014