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 Січ 2013]

Формування рядка тексту з окремих символів. Розрахунок kerning та advance width

У попередніх уроках показано, як дістати контури символів шрифта через WinAPI та тріангуляцію контурів символів. А в цьому уроці буде показано як об'єднати декілька контурів символів у стрічку тексту. Треба врахувати ширину символів (зсув перед символом, зсув після символа, ширина символа), а також змінну відстань між символами (кернінг).
Kerning
Кернінг (kerning) - додаткова відстань (може бути від'ємна) між двома символами. Наприклад, для символів VA кернінг від'ємний, так як форма V дозволяє розмістити A ближче. Кернінг є необовязковим і між багатьма парами символів він рівний 0. Кернінг для шрифта зберігається в формі пари символів і додаткового зсуву між ними. Наступний приклад показує як завантажити значення кернінгу для малих та великих латинських букв.
float additionalPadding = 0;
std::map<std::string, float> kerningValues;

// зберегти всі необхідні значення кернінга
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 };

         // ключ - пара символів
         std::stringstream ss;
         ss << syms[0]; ss << syms[1];
         std::string key;
         ss >> key;

         // зберегти кернінг
         bool toSave = true;
         for(int c=0; c<2; c++)
         {
            // перевірити чи символ серед символів, для яких треба зберегти кернінг
            if(!((syms[c]>='A' && syms[c]<='Z') ||
                (syms[c]>='a' && syms[c]<='z')))
            {
               toSave = false;
            }
         }

         if(toSave)
         {
            kerningValues[key] = float(pairs[i].iKernAmount);
         }
      }
   }
}
В наступній функції показано як можна дістати значення кернінгу зі збереженого раніше:
float getKerning(char currentSymbol, char nextSymbol)
{
   // ключ - пара символів
   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
Для кожного символа визначено три ширини: додаткова ширина зліва (left side bearing), додаткова ширина зправа (right side bearing) і сама ширина символа (width). Додаткові ширини можуть бути від'ємними. Сума трьох ширин називається advance width і відповідає відстані між початком одного символа до початку наступного символа (без врахування кернінга). Наступна функція запитує у WinAPI ці значення і повертає їх.
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;
}
Формування стрічки символів
Тепер ми маємо усі необхідні відстані, щоб об'єданати декілька символів у рядок тексту. Відстань між початком одного символа до початку наступного рівна advance width плюс кернінг між цими двома символами. Рузультат формується таким чином, що кожен наступний символ зсувається вправо відносто попереднього символа на цю сумарну відстань.
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;

   // пройтися по кожному символу
   for(uint i=0; i < textLength; i++)
   {
      char symbol = text[i];
      if(symbol == 0)
      {
         continue;
      }

      дістати контури для символа
      std::vector<std::vector<glm::vec2>> contour = getSymbolOutlines(symbol);
      Дістати advance width для символа
      ABCFLOAT abc = getSymbolWidths(symbol);
      float advanceWidth = abc.abcfA + abc.abcfB + abc.abcfC;

      if(contour.size() > 0)
      {
         // збільшити розмір буфера з результатами
         uint newDataSize = contour.size();
         uint baseOffset = out.size();
         out.resize(out.size() + newDataSize);

         // зсунути новий символ відносно попереднього
         for(uint j=0; j < newDataSize; j++)
         {
            out[baseOffset + j].x = contour[j].x + offset;
            out[baseOffset + j].y = contour[j].x;
         }
      }

      // врахувати кернінг для наступного символа
      if(i < textLength-1 || textLength==1)
      {
         offset += getKerning(symbol, text[i+1]);
      }

      // базовий advance width зсув
      offset += advanceWidth;
   }

   return out;
}



Sun and Black Cat- Ігор Дихта (igor dykhta email) © 2007-2014