+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/internal/text/multi-language-support-impl.h>
+
+// INTERNAL INCLUDES
+#include <dali/public-api/adaptor-framework/singleton-service.h>
+#include <dali-toolkit/public-api/text/logical-model.h>
+#include <dali-toolkit/public-api/text/font-run.h>
+#include <dali-toolkit/public-api/text/script.h>
+#include <dali-toolkit/public-api/text/script-run.h>
+#include <dali/integration-api/debug.h>
+
+namespace Dali
+{
+
+namespace Toolkit
+{
+
+namespace Text
+{
+
+namespace Internal
+{
+
+/**
+ * @brief Retrieves the font Id from the font run for a given character's @p index.
+ *
+ * If the character's index exceeds the current font run it increases the iterator to get the next one.
+ *
+ * @param[in] index The character's index.
+ * @param[in,out] fontRunIt Iterator to the current font run.
+ * @param[in] fontRunEndIt Iterator to one after the last font run.
+ *
+ * @return The font id.
+ */
+FontId GetFontId( Length index,
+ Vector<FontRun>::ConstIterator& fontRunIt,
+ const Vector<FontRun>::ConstIterator& fontRunEndIt )
+{
+ FontId fontId = 0u;
+
+ if( fontRunIt != fontRunEndIt )
+ {
+ const FontRun& fontRun = *fontRunIt;
+
+ if( ( index >= fontRun.characterRun.characterIndex ) &&
+ ( index < fontRun.characterRun.characterIndex + fontRun.characterRun.numberOfCharacters ) )
+ {
+ fontId = fontRun.fontId;
+ }
+
+ if( index == fontRun.characterRun.characterIndex + fontRun.characterRun.numberOfCharacters )
+ {
+ // All the characters of the current run have been traversed. Get the next one for the next iteration.
+ ++fontRunIt;
+ }
+ }
+
+ return fontId;
+}
+
+/**
+ * @brief Retrieves the script Id from the script run for a given character's @p index.
+ *
+ * If the character's index exceeds the current script run it increases the iterator to get the next one.
+ *
+ * @param[in] index The character's index.
+ * @param[in,out] scriptRunIt Iterator to the current font run.
+ * @param[in] scriptRunEndIt Iterator to one after the last script run.
+ *
+ * @return The script.
+ */
+Script GetScript( Length index,
+ Vector<ScriptRun>::ConstIterator& scriptRunIt,
+ const Vector<ScriptRun>::ConstIterator& scriptRunEndIt )
+{
+ Script script = TextAbstraction::UNKNOWN;
+
+ if( scriptRunIt != scriptRunEndIt )
+ {
+ const ScriptRun& scriptRun = *scriptRunIt;
+
+ if( ( index >= scriptRun.characterRun.characterIndex ) &&
+ ( index < scriptRun.characterRun.characterIndex + scriptRun.characterRun.numberOfCharacters ) )
+ {
+ script = scriptRun.script;
+ }
+
+ if( index == scriptRun.characterRun.characterIndex + scriptRun.characterRun.numberOfCharacters )
+ {
+ // All the characters of the current run have been traversed. Get the next one for the next iteration.
+ ++scriptRunIt;
+ }
+ }
+
+ return script;
+}
+
+bool ValidateFontsPerScript::FindValidFont( FontId fontId ) const
+{
+ for( Vector<FontId>::ConstIterator it = mValidFonts.Begin(),
+ endIt = mValidFonts.End();
+ it != endIt;
+ ++it )
+ {
+ if( fontId == *it )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+MultilanguageSupport::MultilanguageSupport()
+: mDefaultFontPerScriptCache(),
+ mValidFontsPerScriptCache()
+{
+ // Initializes the default font cache to zero (invalid font).
+ // Reserves space to cache the default fonts and access them with the script as an index.
+ mDefaultFontPerScriptCache.Resize( TextAbstraction::UNKNOWN, 0u );
+
+ // Initializes the valid fonts cache to NULL (no valid fonts).
+ // Reserves space to cache the valid fonts and access them with the script as an index.
+ mValidFontsPerScriptCache.Resize( TextAbstraction::UNKNOWN, NULL );
+}
+
+MultilanguageSupport::~MultilanguageSupport()
+{
+ // Destroy the valid fonts per script cache.
+
+ for( Vector<ValidateFontsPerScript*>::Iterator it = mValidFontsPerScriptCache.Begin(),
+ endIt = mValidFontsPerScriptCache.End();
+ it != endIt;
+ ++it )
+ {
+ delete *it;
+ }
+}
+
+Text::MultilanguageSupport MultilanguageSupport::Get()
+{
+ Text::MultilanguageSupport multilanguageSupportHandle;
+
+ SingletonService service( SingletonService::Get() );
+ if( service )
+ {
+ // Check whether the singleton is already created
+ Dali::BaseHandle handle = service.GetSingleton( typeid( Text::MultilanguageSupport ) );
+ if( handle )
+ {
+ // If so, downcast the handle
+ MultilanguageSupport* impl = dynamic_cast< Internal::MultilanguageSupport* >( handle.GetObjectPtr() );
+ multilanguageSupportHandle = Text::MultilanguageSupport( impl );
+ }
+ else // create and register the object
+ {
+ multilanguageSupportHandle = Text::MultilanguageSupport( new MultilanguageSupport );
+ service.Register( typeid( multilanguageSupportHandle ), multilanguageSupportHandle );
+ }
+ }
+
+ return multilanguageSupportHandle;
+}
+
+void MultilanguageSupport::SetScripts( LogicalModel& model )
+{
+ // 1) Retrieve the text from the model.
+ const Length numberOfCharacters = model.GetNumberOfCharacters();
+
+ if( 0u == numberOfCharacters )
+ {
+ // Nothing to do if there are no characters.
+ return;
+ }
+
+ Vector<Character> text;
+ text.Resize( numberOfCharacters );
+
+ model.GetText( 0u,
+ text.Begin(),
+ numberOfCharacters );
+
+ // 2) Traverse all characters and set the scripts.
+
+ // Stores the current script run.
+ ScriptRun currentScriptRun;
+ currentScriptRun.characterRun.characterIndex = 0u;
+ currentScriptRun.characterRun.numberOfCharacters = 0u;
+ currentScriptRun.script = TextAbstraction::UNKNOWN;
+
+ // Temporary stores the script runs.
+ std::vector<ScriptRun> scriptRuns;
+ scriptRuns.reserve( numberOfCharacters << 2u ); // To reduce the number of reallocations.
+
+ for( Vector<Character>::ConstIterator it = text.Begin(),
+ endIt = text.End();
+ it != endIt;
+ ++it )
+ {
+ const Character character = *it;
+
+ Script script = GetCharacterScript( character );
+
+ if( TextAbstraction::UNKNOWN == script )
+ {
+ script = TextAbstraction::LATIN;
+ DALI_ASSERT_DEBUG( !"MultilanguageSupport::SetScripts. Unkown script!" );
+ }
+
+ if( script != currentScriptRun.script )
+ {
+ // Current run needs to be stored and a new one initialized.
+
+ if( 0u != currentScriptRun.characterRun.numberOfCharacters )
+ {
+ // Store the script run.
+ scriptRuns.push_back( currentScriptRun );
+ }
+
+ // Initialize the new one.
+ currentScriptRun.characterRun.characterIndex = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
+ currentScriptRun.characterRun.numberOfCharacters = 0u;
+ currentScriptRun.script = script;
+ }
+
+ // Add one more character to the run.
+ ++currentScriptRun.characterRun.numberOfCharacters;
+ }
+
+ if( 0u != currentScriptRun.characterRun.numberOfCharacters )
+ {
+ // Store the last run.
+ scriptRuns.push_back( currentScriptRun );
+ }
+
+ // 3) Set the script runs into the model.
+
+ model.SetScripts( &scriptRuns[0u],
+ scriptRuns.size() );
+}
+
+void MultilanguageSupport::ValidateFonts( LogicalModel& model )
+{
+ // 1) Retrieve the text from the model.
+ const Length numberOfCharacters = model.GetNumberOfCharacters();
+
+ if( 0u == numberOfCharacters )
+ {
+ // Nothing to do if there are no characters.
+ return;
+ }
+
+ Vector<Character> text;
+ text.Resize( numberOfCharacters );
+
+ Character* textBuffer = text.Begin();
+ model.GetText( 0u,
+ textBuffer,
+ numberOfCharacters );
+
+ // 2) Retrieve any font previously set.
+
+ const Length numberOfFontRuns = model.GetNumberOfFontRuns( 0u, numberOfCharacters );
+
+ Vector<FontRun> fontRuns;
+ fontRuns.Reserve( numberOfFontRuns );
+
+ FontRun* fontRunsBuffer = fontRuns.Begin();
+ model.GetFontRuns( fontRunsBuffer,
+ 0u,
+ numberOfCharacters );
+
+ // 3) Retrieve the scripts from the model.
+
+ const Length numberOfScriptRuns = model.GetNumberOfScriptRuns( 0u, numberOfCharacters );
+
+ Vector<ScriptRun> scriptRuns;
+ scriptRuns.Reserve( numberOfScriptRuns );
+
+ ScriptRun* scriptRunsBuffer = scriptRuns.Begin();
+ model.GetScriptRuns( scriptRunsBuffer,
+ 0u,
+ numberOfCharacters );
+
+ // 4) Traverse the characters and validate/set the fonts.
+
+ // Get the caches.
+ FontId* defaultFontPerScriptCacheBuffer = mDefaultFontPerScriptCache.Begin();
+ ValidateFontsPerScript** validFontsPerScriptCacheBuffer = mValidFontsPerScriptCache.Begin();
+
+ // Stores the validated font runs.
+ Vector<FontRun> validatedFontRuns;
+ validatedFontRuns.Reserve( numberOfFontRuns );
+
+ // Initializes a validated font run.
+ FontRun currentFontRun;
+ currentFontRun.characterRun.characterIndex = 0u;
+ currentFontRun.characterRun.numberOfCharacters = 0u;
+ currentFontRun.fontId = 0u;
+ currentFontRun.isDefault = false;
+
+ // Get the font client.
+ TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
+
+ // Iterators of the font and script runs.
+ Vector<FontRun>::ConstIterator fontRunIt = fontRuns.Begin();
+ Vector<FontRun>::ConstIterator fontRunEndIt = fontRuns.End();
+ Vector<ScriptRun>::ConstIterator scriptRunIt = scriptRuns.Begin();
+ Vector<ScriptRun>::ConstIterator scriptRunEndIt = scriptRuns.End();
+
+ for( Length index = 0u; index < numberOfCharacters; ++index )
+ {
+ // Get the character.
+ const Character character = *( textBuffer + index );
+
+ // Get the font for the character.
+ FontId fontId = GetFontId( index,
+ fontRunIt,
+ fontRunEndIt );
+
+ // Get the script for the character.
+ Script script = GetScript( index,
+ scriptRunIt,
+ scriptRunEndIt );
+
+ if( TextAbstraction::UNKNOWN == script )
+ {
+ DALI_LOG_WARNING( "MultilanguageSupport::ValidateFonts. Unknown script!" );
+ script = TextAbstraction::LATIN;
+ }
+
+ // Whether the font being validated is a default one not set by the user.
+ const bool isDefault = ( 0u == fontId );
+
+ // The default font point size.
+ PointSize26Dot6 pointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
+
+ if( !isDefault )
+ {
+ // Validate if the font set by the user supports the character.
+
+ // Check first in the caches.
+
+ // The user may have set the default font. Check it. Otherwise check in the valid fonts cache.
+ if( fontId != *( defaultFontPerScriptCacheBuffer + script ) )
+ {
+ // Check in the valid fonts cache.
+ ValidateFontsPerScript* validateFontsPerScript = *( validFontsPerScriptCacheBuffer + script );
+ if( NULL != validateFontsPerScript )
+ {
+ if( !validateFontsPerScript->FindValidFont( fontId ) )
+ {
+ // Use the font client to validate the font.
+ const GlyphIndex glyphIndex = fontClient.GetGlyphIndex( fontId, character );
+
+ if( 0u == glyphIndex )
+ {
+ // Get the point size of the current font. It will be used to get a default font id.
+ pointSize = fontClient.GetPointSize( fontId );
+
+ // The font is not valid. Set to zero and a default one will be set.
+ fontId = 0u;
+ }
+ else
+ {
+ // Add the font to the valid font cache.
+ validateFontsPerScript->mValidFonts.PushBack( fontId );
+ }
+ }
+ }
+ else
+ {
+ // Use the font client to validate the font.
+ const GlyphIndex glyphIndex = fontClient.GetGlyphIndex( fontId, character );
+
+ if( 0u == glyphIndex )
+ {
+ // Get the point size of the current font. It will be used to get a default font id.
+ pointSize = fontClient.GetPointSize( fontId );
+
+ // The font is not valid. Set to zero and a default one will be set.
+ fontId = 0u;
+ }
+ else
+ {
+ // Add the font to the valid font cache.
+ validateFontsPerScript = new ValidateFontsPerScript();
+ *( validFontsPerScriptCacheBuffer + script ) = validateFontsPerScript;
+
+ validateFontsPerScript->mValidFonts.PushBack( fontId );
+ }
+ }
+ }
+ }
+
+ // The font has not been validated. Find a default one.
+ if( 0u == fontId )
+ {
+ // The character has no font assigned. Get a default one from the cache
+ fontId = *( defaultFontPerScriptCacheBuffer + script );
+
+ // If the cache has not a default font, get one from the font client.
+ if( 0u == fontId )
+ {
+ // Find a default font.
+ fontId = fontClient.FindDefaultFont( character, pointSize );
+
+ // Cache the font.
+ *( defaultFontPerScriptCacheBuffer + script ) = fontId;
+ }
+ }
+
+ // The font is now validated.
+
+ if( ( fontId != currentFontRun.fontId ) ||
+ ( isDefault != currentFontRun.isDefault ) )
+ {
+ // Current run needs to be stored and a new one initialized.
+
+ if( 0u != currentFontRun.characterRun.numberOfCharacters )
+ {
+ // Store the font run.
+ validatedFontRuns.PushBack( currentFontRun );
+ }
+
+ // Initialize the new one.
+ currentFontRun.characterRun.characterIndex = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
+ currentFontRun.characterRun.numberOfCharacters = 0u;
+ currentFontRun.fontId = fontId;
+ currentFontRun.isDefault = isDefault;
+ }
+
+ // Add one more character to the run.
+ ++currentFontRun.characterRun.numberOfCharacters;
+ }
+
+ if( 0u != currentFontRun.characterRun.numberOfCharacters )
+ {
+ // Store the last run.
+ validatedFontRuns.PushBack( currentFontRun );
+ }
+
+ // 5) Sets the validated font runs to the model.
+ model.SetFonts( validatedFontRuns.Begin(),
+ validatedFontRuns.Count() );
+}
+
+} // namespace Internal
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali