Add logs to check glyphs retrieved from HarfBuzz while shaping complex text
[platform/core/uifw/dali-adaptor.git] / text / dali / internal / text-abstraction / shaping-impl.cpp
index fed5d29..4d5fd6e 100644 (file)
 
 // INTERNAL INCLUDES
 #include <singleton-service-impl.h>
+#include <dali/devel-api/text-abstraction/font-client.h>
+#include <dali/devel-api/text-abstraction/glyph-info.h>
+#include <dali/integration-api/debug.h>
+
+// EXTERNAL INCLUDES
+#include <harfbuzz/hb.h>
+#include <harfbuzz/hb-ft.h>
+
+#include <ft2build.h>
+#include <iostream>
+
+namespace
+{
+
+const static uint8_t U2 = 2u;
+const static uint8_t U3 = 3u;
+const static uint8_t U4 = 4u;
+
+uint32_t GetNumberOfUtf8Bytes( const uint32_t* const utf32, uint32_t numberOfCharacters )
+{
+  uint32_t numberOfBytes = 0u;
+
+  const uint32_t* begin = utf32;
+  const uint32_t* end = utf32 + numberOfCharacters;
+
+  for( ; begin < end; ++begin )
+  {
+    const uint32_t code = *begin;
+
+    if( code < 0x80u )
+    {
+      ++numberOfBytes;
+    }
+    else if( code < 0x800u )
+    {
+      numberOfBytes += U2;
+    }
+    else if( code < 0x10000u )
+    {
+      numberOfBytes += U3;
+    }
+    else if( code < 0x200000u )
+    {
+      numberOfBytes += U4;
+    }
+  }
+
+  return numberOfBytes;
+}
+
+uint32_t Utf32ToUtf8( const uint32_t* const utf32, uint32_t numberOfCharacters, uint8_t* utf8 )
+{
+  const uint32_t* begin = utf32;
+  const uint32_t* end = utf32 + numberOfCharacters;
+
+  uint8_t* utf8Begin = utf8;
+
+  for( ; begin < end; ++begin )
+  {
+    const uint32_t code = *begin;
+
+    if( code < 0x80u )
+    {
+      *utf8++ = code;
+    }
+    else if( code < 0x800u )
+    {
+      *utf8++ = static_cast<uint8_t>(   code >> 6u )           | 0xc0u; // lead byte for 2 byte sequence
+      *utf8++ = static_cast<uint8_t>(   code          & 0x3f ) | 0x80u; // continuation byte
+    }
+    else if( code < 0x10000u )
+    {
+      *utf8++ = static_cast<uint8_t>(   code >> 12u )          | 0xe0u; // lead byte for 2 byte sequence
+      *utf8++ = static_cast<uint8_t>( ( code >> 6u )  & 0x3f ) | 0x80u; // continuation byte
+      *utf8++ = static_cast<uint8_t>(   code          & 0x3f ) | 0x80u; // continuation byte
+    }
+    else if( code < 0x200000u )
+    {
+      *utf8++ = static_cast<uint8_t>(   code >> 18u )          | 0xf0u; // lead byte for 2 byte sequence
+      *utf8++ = static_cast<uint8_t>( ( code >> 12u ) & 0x3f ) | 0x80u; // continuation byte
+      *utf8++ = static_cast<uint8_t>( ( code >> 6u )  & 0x3f ) | 0x80u; // continuation byte
+      *utf8++ = static_cast<uint8_t>(   code          & 0x3f ) | 0x80u; // continuation byte
+    }
+  }
+
+  return utf8 - utf8Begin;
+}
+
+void Utf32ToUtf8( const uint32_t* const utf32, uint32_t numberOfCharacters, std::string& utf8 )
+{
+  utf8.clear();
+
+  uint32_t numberOfBytes = GetNumberOfUtf8Bytes( &utf32[0], numberOfCharacters );
+  utf8.resize( numberOfBytes );
+
+  // This is a bit horrible but std::string returns a (signed) char*
+  Utf32ToUtf8( utf32, numberOfCharacters, reinterpret_cast<uint8_t*>(&utf8[0]) );
+}
+
+}
 
 namespace Dali
 {
@@ -30,15 +130,293 @@ namespace TextAbstraction
 namespace Internal
 {
 
+const char*        DEFAULT_LANGUAGE = "en";
+const unsigned int DEFAULT_LANGUAGE_LENGTH = 2u;
+const float        FROM_266 = 1.0f / 64.0f;
+
+const hb_script_t SCRIPT_TO_HARFBUZZ[] =
+{
+  HB_SCRIPT_COMMON,
+
+  HB_SCRIPT_COMMON, // ASCII_DIGITS
+  HB_SCRIPT_COMMON, // ASCII_PS
+
+  HB_SCRIPT_COMMON, // C1_CONTROLS
+  HB_SCRIPT_COMMON, // C1_PS
+  HB_SCRIPT_COMMON, // C1_MATH
+  HB_SCRIPT_COMMON, // SML_P
+  HB_SCRIPT_COMMON, // PHONETIC_U
+  HB_SCRIPT_COMMON, // PHONETIC_SS
+  HB_SCRIPT_COMMON, // NUMERIC_SS
+  HB_SCRIPT_COMMON, // LETTER_LIKE
+  HB_SCRIPT_COMMON, // NUMBER_FORMS
+  HB_SCRIPT_COMMON, // FRACTIONS_NF
+  HB_SCRIPT_COMMON, // NON_LATIN_LED
+  HB_SCRIPT_COMMON, // HWFW_S
+
+  HB_SCRIPT_CYRILLIC,
+  HB_SCRIPT_GREEK,
+  HB_SCRIPT_LATIN,
+
+  HB_SCRIPT_ARABIC,
+  HB_SCRIPT_HEBREW,
+
+  HB_SCRIPT_ARMENIAN,
+  HB_SCRIPT_GEORGIAN,
+
+  HB_SCRIPT_HAN,
+  HB_SCRIPT_HANGUL,
+  HB_SCRIPT_HIRAGANA,
+  HB_SCRIPT_KATAKANA,
+  HB_SCRIPT_BOPOMOFO,
+
+  HB_SCRIPT_BENGALI,
+  HB_SCRIPT_MYANMAR,
+  HB_SCRIPT_DEVANAGARI,
+  HB_SCRIPT_GUJARATI,
+  HB_SCRIPT_GURMUKHI,
+  HB_SCRIPT_KANNADA,
+  HB_SCRIPT_MALAYALAM,
+  HB_SCRIPT_ORIYA,
+  HB_SCRIPT_SINHALA,
+  HB_SCRIPT_TAMIL,
+  HB_SCRIPT_TELUGU,
+
+  HB_SCRIPT_LAO,
+  HB_SCRIPT_THAI,
+  HB_SCRIPT_KHMER,
+  HB_SCRIPT_JAVANESE,
+  HB_SCRIPT_SUNDANESE,
+
+  HB_SCRIPT_ETHIOPIC,
+  HB_SCRIPT_OL_CHIKI,
+  HB_SCRIPT_TAGALOG,
+  HB_SCRIPT_MEETEI_MAYEK,
+
+  HB_SCRIPT_UNKNOWN, // EMOJI
+  HB_SCRIPT_UNKNOWN, // SYMBOLS1
+  HB_SCRIPT_UNKNOWN, // SYMBOLS2
+  HB_SCRIPT_UNKNOWN, // SYMBOLS3
+  HB_SCRIPT_UNKNOWN, // SYMBOLS4
+  HB_SCRIPT_UNKNOWN, // SYMBOLS5
+  HB_SCRIPT_UNKNOWN
+};
+
+struct Shaping::Plugin
+{
+  Plugin()
+  : mFreeTypeLibrary( NULL ),
+    mIndices(),
+    mAdvance(),
+    mCharacterMap(),
+    mFontId( 0u )
+  {
+  }
+
+  ~Plugin()
+  {
+    FT_Done_FreeType( mFreeTypeLibrary );
+  }
+
+  void Initialize()
+  {
+    int error = FT_Init_FreeType( &mFreeTypeLibrary );
+    if( FT_Err_Ok != error )
+    {
+      DALI_LOG_ERROR( "FreeType Init error: %d\n", error );
+    }
+  }
+
+  Length Shape( const Character* const text,
+                Length numberOfCharacters,
+                FontId fontId,
+                Script script )
+  {
+    // Clear previoursly shaped texts.
+    mIndices.Clear();
+    mAdvance.Clear();
+    mCharacterMap.Clear();
+    mOffset.Clear();
+    mFontId = fontId;
+
+    // Reserve some space to avoid reallocations.
+    const Length numberOfGlyphs = static_cast<Length>( 1.3f * static_cast<float>( numberOfCharacters ) );
+    mIndices.Reserve( numberOfGlyphs );
+    mAdvance.Reserve( numberOfGlyphs );
+    mCharacterMap.Reserve( numberOfGlyphs );
+    mOffset.Reserve( 2u * numberOfGlyphs );
+
+    TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
+
+    // Get the font's path file name from the font Id.
+    FontDescription fontDescription;
+    fontClient.GetDescription( fontId, fontDescription );
+
+    // Create a FreeType font's face.
+    FT_Face face;
+    FT_Error retVal = FT_New_Face( mFreeTypeLibrary, fontDescription.path.c_str(), 0u, &face );
+    if( FT_Err_Ok != retVal )
+    {
+      DALI_LOG_ERROR( "Failed to open face: %s\n", fontDescription.path.c_str() );
+      return 0u;
+    }
+
+    unsigned int horizontalDpi = 0u;
+    unsigned int verticalDpi = 0u;
+    fontClient.GetDpi( horizontalDpi, verticalDpi );
+
+    FT_Set_Char_Size( face,
+                      0u,
+                      fontClient.GetPointSize( fontId ),
+                      horizontalDpi,
+                      verticalDpi );
+
+    /* Get our harfbuzz font struct */
+    hb_font_t* harfBuzzFont;
+    harfBuzzFont = hb_ft_font_create( face, NULL );
+
+    /* Create a buffer for harfbuzz to use */
+    hb_buffer_t* harfBuzzBuffer = hb_buffer_create();
+
+    const bool rtlDirection = IsRightToLeftScript( script );
+    hb_buffer_set_direction( harfBuzzBuffer,
+                             rtlDirection ? HB_DIRECTION_RTL : HB_DIRECTION_LTR ); /* or LTR */
+
+    hb_buffer_set_script( harfBuzzBuffer,
+                          SCRIPT_TO_HARFBUZZ[ script ] ); /* see hb-unicode.h */
+
+
+    char* currentLocale = setlocale(LC_MESSAGES,NULL);
+
+    std::istringstream stringStream( currentLocale );
+    std::string localeString;
+    std::getline(stringStream, localeString, '_');
+    hb_buffer_set_language( harfBuzzBuffer, hb_language_from_string( localeString.c_str(), localeString.size() ) );
+
+    /* Layout the text */
+    hb_buffer_add_utf32( harfBuzzBuffer, text, numberOfCharacters, 0u, numberOfCharacters );
+
+    hb_shape( harfBuzzFont, harfBuzzBuffer, NULL, 0u );
+
+    std::string currentText;
+    Utf32ToUtf8( text, numberOfCharacters, currentText );
+
+    DALI_LOG_RELEASE_INFO( "Shape: currentText: %s, font: %s, pointSize: %d\n", currentText.c_str(), fontDescription.path.c_str(), static_cast<int>( fontClient.GetPointSize( fontId ) * FROM_266 ) );
+
+    /* Get glyph data */
+    unsigned int glyphCount;
+    hb_glyph_info_t* glyphInfo = hb_buffer_get_glyph_infos( harfBuzzBuffer, &glyphCount );
+    hb_glyph_position_t *glyphPositions = hb_buffer_get_glyph_positions( harfBuzzBuffer, &glyphCount );
+    const GlyphIndex lastGlyphIndex = glyphCount - 1u;
+    for( GlyphIndex i = 0u; i < glyphCount; )
+    {
+      if( rtlDirection )
+      {
+        // If the direction is right to left, Harfbuzz retrieves the glyphs in the visual order.
+        // The glyphs are needed in the logical order to layout the text in lines.
+        // Do not change the order of the glyphs if they belong to the same cluster.
+        GlyphIndex rtlIndex = lastGlyphIndex - i;
+
+        unsigned int cluster = glyphInfo[rtlIndex].cluster;
+        unsigned int previousCluster = cluster;
+        Length numberOfGlyphsInCluster = 0u;
+
+        while( ( cluster == previousCluster ) )
+        {
+          ++numberOfGlyphsInCluster;
+          previousCluster = cluster;
+
+          if( rtlIndex > 0u )
+          {
+            --rtlIndex;
+
+            cluster = glyphInfo[rtlIndex].cluster;
+          }
+          else
+          {
+            break;
+          }
+        }
+
+        rtlIndex = lastGlyphIndex - ( i + ( numberOfGlyphsInCluster - 1u ) );
+
+        for( GlyphIndex j = 0u; j < numberOfGlyphsInCluster; ++j )
+        {
+          const GlyphIndex index = rtlIndex + j;
+
+          mIndices.PushBack( glyphInfo[index].codepoint );
+          mAdvance.PushBack( floor( glyphPositions[index].x_advance * FROM_266 ) );
+          mCharacterMap.PushBack( glyphInfo[index].cluster );
+          mOffset.PushBack( floor( glyphPositions[index].x_offset * FROM_266 ) );
+          mOffset.PushBack( floor( glyphPositions[index].y_offset * FROM_266 ) );
+        }
+
+        i += numberOfGlyphsInCluster;
+      }
+      else
+      {
+        mIndices.PushBack( glyphInfo[i].codepoint );
+        mAdvance.PushBack( floor( glyphPositions[i].x_advance * FROM_266 ) );
+        mCharacterMap.PushBack( glyphInfo[i].cluster );
+        mOffset.PushBack( floor( glyphPositions[i].x_offset * FROM_266 ) );
+        mOffset.PushBack( floor( glyphPositions[i].y_offset * FROM_266 ) );
+
+        DALI_LOG_RELEASE_INFO( "glyphIndex: %u, glyph.advance: %f, glyph.xBearing: %f\n", glyphInfo[i].codepoint, floor( glyphPositions[i].x_advance * FROM_266 ), floor( glyphPositions[i].x_offset * FROM_266 ) );
+
+        ++i;
+      }
+    }
+
+    /* Cleanup */
+    hb_buffer_destroy( harfBuzzBuffer );
+    hb_font_destroy( harfBuzzFont );
+    FT_Done_Face( face );
+
+    return mIndices.Count();
+  }
+
+  void GetGlyphs( GlyphInfo* glyphInfo,
+                  CharacterIndex* glyphToCharacterMap )
+  {
+    Vector<CharacterIndex>::ConstIterator indicesIt = mIndices.Begin();
+    Vector<float>::ConstIterator advanceIt = mAdvance.Begin();
+    Vector<float>::ConstIterator offsetIt = mOffset.Begin();
+    Vector<CharacterIndex>::ConstIterator characterMapIt = mCharacterMap.Begin();
+
+    for( GlyphIndex index = 0u, size = mIndices.Count(); index < size; ++index )
+    {
+      GlyphInfo& glyph = *( glyphInfo + index );
+      CharacterIndex& glyphToCharacter = *( glyphToCharacterMap + index );
+
+      glyph.fontId = mFontId;
+      glyph.index = *( indicesIt + index );
+      glyph.advance = *( advanceIt + index );
+
+      const GlyphIndex offsetIndex = 2u * index;
+      glyph.xBearing = *( offsetIt + offsetIndex );
+      glyph.yBearing = *( offsetIt + offsetIndex + 1u );
+
+      glyphToCharacter = *( characterMapIt + index );
+    }
+  }
+
+  FT_Library             mFreeTypeLibrary;
+
+  Vector<CharacterIndex> mIndices;
+  Vector<float>          mAdvance;
+  Vector<float>          mOffset;
+  Vector<CharacterIndex> mCharacterMap;
+  FontId                 mFontId;
+};
+
 Shaping::Shaping()
 : mPlugin( NULL )
 {
-
 }
 
 Shaping::~Shaping()
 {
-
+  delete mPlugin;
 }
 
 TextAbstraction::Shaping Shaping::Get()
@@ -71,12 +449,30 @@ Length Shaping::Shape( const Character* const text,
                        FontId fontId,
                        Script script )
 {
-  return 0u;
+  CreatePlugin();
+
+  return mPlugin->Shape( text,
+                         numberOfCharacters,
+                         fontId,
+                         script );
 }
 
 void Shaping::GetGlyphs( GlyphInfo* glyphInfo,
                          CharacterIndex* glyphToCharacterMap )
 {
+  CreatePlugin();
+
+  mPlugin->GetGlyphs( glyphInfo,
+                      glyphToCharacterMap );
+}
+
+void Shaping::CreatePlugin()
+{
+  if( !mPlugin )
+  {
+    mPlugin = new Plugin();
+    mPlugin->Initialize();
+  }
 }
 
 } // namespace Internal