sunLoadingImage
whowedImag
decoration left 1
decoration left 2
transhome
transprojects
transgallery
transarticles
decoration rigth
English

Show/Hide search bar
black cat logo variable logo
[24 Jan 2013]

Assemble text for rendering from individual symbols. Kerning and advance width.

Previous lessons show how to get contours of symbols from WinAPI and how to triangulate contours of symbols. This tutorial shows how to assemble text string from contours of multipse symbols. You have to take into account width of the symbols (left side bearing, right side bearing, width of the symbol), and variable distance between symbols (kerning).
Kerning
Kerning is a variable distance (may be negative) between two symbols. For example, for VA symbols kerning is negative, as shape of V allows to place shape of A closer. For many pairs of symbols kerning is not defined. Kerning can be saved in form of two concatenated symbols and distance between these symbols. Following code snippet shows how to load kerning values for required symbols.
float additionalPadding = 0;
std::map<std::string, float> kerningValues;

// save all required values of kerning
void cacheKerning()
{
   HDC hdc = CreateCompatibleDC(NULL);
   if(hdc != NULL)
   {
      SelectObject(hdc, fontPtr);

      int extraPadding = GetTextCharacterExtra(hdc);
      if(extraPadding != 0x8000000)
      {
         additionalPadding = float(GetTextCharacterExtra(hdc));
      }

      DWORD numberOfKerningPairs = GetKerningPairs(hdc, INT_MAX, NULL);
      std::unique_ptr<KERNINGPAIR[]> pairs(new KERNINGPAIR[numberOfKerningPairs]);
      GetKerningPairs(hdc, numberOfKerningPairs, pairs.get());
      DeleteDC(hdc);

      for(DWORD i=0; i<numberOfKerningPairs; i++)
      {
         char syms[2] = { pairs[i].wFirst, pairs[i].wSecond };

         // key - pair of symbols
         std::stringstream ss;
         ss << syms[0]; ss << syms[1];
         std::string key;
         ss >> key;

         // save kerning
         bool toSave = true;
         for(int c=0; c<2; c++)
         {
            // check whether symbol is among required symbols
            if(!((syms[c]>='A' && syms[c]<='Z') ||
                (syms[c]>='a' && syms[c]<='z')))
            {
               toSave = false;
            }
         }

         if(toSave)
         {
            kerningValues[key] = float(pairs[i].iKernAmount);
         }
      }
   }
}
Function returns kerning value between current and next symbol:
float getKerning(char currentSymbol, char nextSymbol)
{
   // key - pair of symbols
   std::wstringstream ss;
   ss << currentSymbol;
   ss << nextSymbol;

   std::wstring key;
   ss >> key;

   auto it = kerningValues.find(key);
   if(it == kerningValues.end())
   {
      return 0;
   }

   return (float)it->second;
}
Advance width
There are three widths defined for each symbol: left side bearing, right side bearing and width of symbol. Left and right side bearings may be negative. Sum of these widths is called advance width and it is equal to distance between start of one symbol to start of next symbol (without kerning). Following function queries WinAPI about these values and returns them:
ABCFLOAT getSymbolWidths(char symbol) const
{
   ABCFLOAT out;
   out.abcfA = 0;
   out.abcfB = 0;
   out.abcfC = 0;

   HDC hdc = CreateCompatibleDC(NULL);
   if(hdc != NULL)
   {
      SelectObject(hdc, fontPtr);
      GetCharABCWidthsFloat(hdc, symbol, symbol, &out);
      DeleteDC(hdc);
   }

   return out;
}
Assemble text string from symbols
Now we have all required distances to combine multiple symbols into a string of text. Distance from start of one symbol to end of second symbol is equal to sum of advance width and kerning between those two symbols. Each following symbol is shifted by this distance to the right relative to the start of previous symbol.
std::vector<glm::vec2> getTextOutlines(const std::wstring & text)
{
   std::vector<float> out(0);

   uint textLength = text.length();
   double offset = 0;
   double minOffsetX = 0;
   double maxOffsetX = 0;

   // handle each symbol
   for(uint i=0; i < textLength; i++)
   {
      char symbol = text[i];
      if(symbol == 0)
      {
         continue;
      }

      // get contours for symbol
      std::vector<std::vector<glm::vec2>> contour = getSymbolOutlines(symbol);
      // get advance width for symbol
      ABCFLOAT abc = getSymbolWidths(symbol);
      float advanceWidth = abc.abcfA + abc.abcfB + abc.abcfC;

      if(contour.size() > 0)
      {
         // increase size of the buffer with results
         uint newDataSize = contour.size();
         uint baseOffset = out.size();
         out.resize(out.size() + newDataSize);

         // shift symbol to the right
         for(uint j=0; j < newDataSize; j++)
         {
            out[baseOffset + j].x = contour[j].x + offset;
            out[baseOffset + j].y = contour[j].x;
         }
      }

      // take into account kerning
      if(i < textLength-1 || textLength==1)
      {
         offset += getKerning(symbol, text[i+1]);
      }

      // basic advance width offset
      offset += advanceWidth;
   }

   return out;
}



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