[ATSPI] Implementation of Hypertext and Hyperlink in text controls 29/265529/20
authorLukasz Oleksak <l.oleksak@samsung.com>
Thu, 30 Sep 2021 12:24:44 +0000 (14:24 +0200)
committerLukasz Oleksak <l.oleksak@samsung.com>
Mon, 22 Nov 2021 11:42:38 +0000 (12:42 +0100)
This patch exposes on dbus ATSPI Hypertext interface
for the following text controls: TextEditor, TextField and TextLabel.
Also it brings new class TextAnchor inheriting from Control which marks
the geometry of an anchor inside the text controls mentioned above and
which exposes on dbus ATSPI Hyperlink interface.

Change-Id: Ic46bcf7a3ddfe49b1723ebf8025fba6779fda05d

22 files changed:
automated-tests/src/dali-toolkit-internal/utc-Dali-Accessibility-Controls.cpp
dali-toolkit/devel-api/controls/accessible-impl.h
dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.cpp [new file with mode: 0644]
dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h [new file with mode: 0644]
dali-toolkit/devel-api/file.list
dali-toolkit/internal/controls/text-controls/common-text-utils.cpp
dali-toolkit/internal/controls/text-controls/common-text-utils.h
dali-toolkit/internal/controls/text-controls/text-anchor-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/controls/text-controls/text-anchor-impl.h [new file with mode: 0644]
dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp
dali-toolkit/internal/controls/text-controls/text-editor-impl.h
dali-toolkit/internal/controls/text-controls/text-editor-property-handler.cpp
dali-toolkit/internal/controls/text-controls/text-field-impl.cpp
dali-toolkit/internal/controls/text-controls/text-field-impl.h
dali-toolkit/internal/controls/text-controls/text-field-property-handler.cpp
dali-toolkit/internal/controls/text-controls/text-label-impl.cpp
dali-toolkit/internal/controls/text-controls/text-label-impl.h
dali-toolkit/internal/file.list
dali-toolkit/internal/text/text-controller-impl.cpp
dali-toolkit/internal/text/text-controller-impl.h
dali-toolkit/internal/text/text-controller.cpp
dali-toolkit/internal/text/text-controller.h

index 9243a0b..a30078a 100644 (file)
@@ -516,6 +516,39 @@ int UtcDaliAccessibilityBloomViewConstructor(void)
 }
 
 #include <dali-toolkit/internal/controls/text-controls/text-field-impl.h>
+#include <dali-toolkit/internal/controls/text-controls/text-anchor-impl.h>
+int UtcDaliAccessibilityTextAnchor(void)
+{
+  ToolkitTestApplication application;
+
+  auto textanchor = TextAnchor::New();
+  DALI_TEST_CHECK( textanchor );
+
+  auto textlabel = TextLabel::New();
+  DALI_TEST_CHECK( textlabel );
+
+  Dali::Accessibility::TestEnableSC( true );
+
+  textlabel.Add(textanchor);
+  auto accessible = Dali::Accessibility::Accessible::Get( textanchor );
+  DALI_TEST_CHECK( accessible );
+  auto hyperlink = dynamic_cast< Dali::Accessibility::Hyperlink* >( accessible );
+  DALI_TEST_CHECK( hyperlink );
+  textanchor.SetProperty( Toolkit::TextAnchor::Property::URI, "https://www.tizen.org" );
+  DALI_TEST_EQUALS( hyperlink->IsValid(), true, TEST_LOCATION );
+  auto action = dynamic_cast<Dali::Accessibility::Action*>( accessible );
+  // activation of valid hyperlink
+  DALI_TEST_CHECK( action->DoAction( "accessibilityActivated" ) );
+  // making hyperlink invalid
+  textanchor.SetProperty( Toolkit::TextAnchor::Property::URI, "" );
+  DALI_TEST_EQUALS( hyperlink->IsValid(), false, TEST_LOCATION );
+  DALI_TEST_CHECK( !action->DoAction( "accessibilityActivated" ) );
+
+  Dali::Accessibility::TestEnableSC( false );
+
+  END_TEST;
+}
+
 int UtcDaliAccessibilityTextField(void)
 {
   ToolkitTestApplication application;
@@ -566,6 +599,51 @@ int UtcDaliAccessibilityTextField(void)
   DALI_TEST_EQUALS(editabletext->DeleteText(1, 5), true, TEST_LOCATION);
   DALI_TEST_EQUALS(text->GetText(0, 2), "af", TEST_LOCATION);
 
+  auto hypertext = dynamic_cast< Dali::Accessibility::Hypertext* >( accessible );
+  DALI_TEST_CHECK( hypertext );
+  // text without the anchors markup and ENABLE_MARKUP property set (by default) to false
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION );
+  // text with the anchors markup and ENABLE_MARKUP property set (by default) to false
+  textfield.SetProperty( Toolkit::TextField::Property::TEXT, "12345<a href = 'https://www.tizen.org'>anchor1</a>12345<a href = 'https://www.tizen.org' >veryveryveryveryveryveryveryverylonganchor2</a>12345<a href = 'https://www.tizen.org'>anchor3</a>12345" );
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION );
+  // text with the anchors markup and ENABLE_MARKUP property set to true
+  textfield.SetProperty( Toolkit::TextField::Property::ENABLE_MARKUP, true);
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 3, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), 0, TEST_LOCATION ); //1st anchor index
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 17 ), 1, TEST_LOCATION ); //2nd anchor index
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 66 ), 2, TEST_LOCATION ); //3rd anchor index
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  auto hyperlink = hypertext->GetLink( 0 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 5, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 12, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetAnchorCount(), 1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetAnchorUri( 0 ), "https://www.tizen.org", TEST_LOCATION );
+  auto anchorAccessible = hyperlink->GetAnchorAccessible( 0 );
+  DALI_TEST_EQUALS( hyperlink == anchorAccessible, true, TEST_LOCATION );
+  hyperlink = hypertext->GetLink( 1 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 17, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 60, TEST_LOCATION );
+  hyperlink = hypertext->GetLink( 2 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 65, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 72, TEST_LOCATION );
+
   Dali::Accessibility::TestEnableSC( false );
 
   END_TEST;
@@ -622,6 +700,51 @@ int UtcDaliAccessibilityTextEditor(void)
   DALI_TEST_EQUALS(editabletext->DeleteText(1, 5), true, TEST_LOCATION);
   DALI_TEST_EQUALS(text->GetText(0, 2), "af", TEST_LOCATION);
 
+  auto hypertext = dynamic_cast< Dali::Accessibility::Hypertext* >( accessible );
+  DALI_TEST_CHECK( hypertext );
+  // text without the anchors markup and ENABLE_MARKUP property set (by default) to false
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION );
+  // text with the anchors markup and ENABLE_MARKUP property set (by default) to false
+  texteditor.SetProperty( Toolkit::TextEditor::Property::TEXT, "12345<a href = 'https://www.tizen.org'>anchor1</a>12345<a href = 'https://www.tizen.org' >veryveryveryveryveryveryveryverylonganchor2</a>12345<a href = 'https://www.tizen.org'>anchor3</a>12345" );
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION );
+  // text with the anchors markup and ENABLE_MARKUP property set to true
+  texteditor.SetProperty( Toolkit::TextEditor::Property::ENABLE_MARKUP, true);
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 3, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), 0, TEST_LOCATION ); //1st anchor index
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 17 ), 1, TEST_LOCATION ); //2nd anchor index
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 66 ), 2, TEST_LOCATION ); //3rd anchor index
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  auto hyperlink = hypertext->GetLink( 0 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 5, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 12, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetAnchorCount(), 1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetAnchorUri( 0 ), "https://www.tizen.org", TEST_LOCATION );
+  auto anchorAccessible = hyperlink->GetAnchorAccessible( 0 );
+  DALI_TEST_EQUALS( hyperlink == anchorAccessible, true, TEST_LOCATION );
+  hyperlink = hypertext->GetLink( 1 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 17, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 60, TEST_LOCATION );
+  hyperlink = hypertext->GetLink( 2 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 65, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 72, TEST_LOCATION );
+
   Dali::Accessibility::TestEnableSC( false );
 
   END_TEST;
@@ -660,6 +783,51 @@ int UtcDaliAccessibilityTextLabel(void)
   DALI_TEST_EQUALS( text->SetRangeOfSelection( 1, 0, 1 ), false, TEST_LOCATION );
   DALI_TEST_EQUALS( text->RemoveSelection( 1 ), false, TEST_LOCATION );
 
+  auto hypertext = dynamic_cast< Dali::Accessibility::Hypertext* >( accessible );
+  DALI_TEST_CHECK( hypertext );
+  // text without the anchors markup and ENABLE_MARKUP property set (by default) to false
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION );
+  // text with the anchors markup and ENABLE_MARKUP property set (by default) to false
+  textlabel.SetProperty( Toolkit::TextLabel::Property::TEXT, "12345<a href = 'https://www.tizen.org'>anchor1</a>12345<a href = 'https://www.tizen.org' >veryveryveryveryveryveryveryverylonganchor2</a>12345<a href = 'https://www.tizen.org'>anchor3</a>12345" );
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION );
+  // text with the anchors markup and ENABLE_MARKUP property set to true
+  textlabel.SetProperty( Toolkit::TextLabel::Property::ENABLE_MARKUP, true);
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 3, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), 0, TEST_LOCATION ); //1st anchor index
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 17 ), 1, TEST_LOCATION ); //2nd anchor index
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 66 ), 2, TEST_LOCATION ); //3rd anchor index
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  auto hyperlink = hypertext->GetLink( 0 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 5, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 12, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetAnchorCount(), 1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetAnchorUri( 0 ), "https://www.tizen.org", TEST_LOCATION );
+  auto anchorAccessible = hyperlink->GetAnchorAccessible( 0 );
+  DALI_TEST_EQUALS( hyperlink == anchorAccessible, true, TEST_LOCATION );
+  hyperlink = hypertext->GetLink( 1 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 17, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 60, TEST_LOCATION );
+  hyperlink = hypertext->GetLink( 2 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 65, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 72, TEST_LOCATION );
+
   Dali::Accessibility::TestEnableSC( false );
 
   END_TEST;
index 8bd0714..ea80eef 100644 (file)
@@ -53,7 +53,7 @@ protected:
   bool mIsModal = false;
   bool mIsRoot = false;
 
-  Dali::Actor Self()
+  Dali::Actor Self() const
   {
     auto handle = mSelf.GetHandle();
 
diff --git a/dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.cpp b/dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.cpp
new file mode 100644 (file)
index 0000000..9b72f8a
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2021 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/devel-api/controls/text-controls/text-anchor-devel.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/controls/text-controls/text-anchor-impl.h>
+
+using namespace Dali;
+
+namespace Dali
+{
+namespace Toolkit
+{
+TextAnchor TextAnchor::New()
+{
+  return Internal::TextAnchor::New();
+}
+
+TextAnchor::TextAnchor()
+{
+}
+
+TextAnchor::TextAnchor(const TextAnchor& handle)
+: Control(handle)
+{
+}
+
+TextAnchor& TextAnchor::operator=(const TextAnchor& handle)
+{
+  if(&handle != this)
+  {
+    Control::operator=(handle);
+  }
+  return *this;
+}
+
+TextAnchor::~TextAnchor()
+{
+}
+
+TextAnchor TextAnchor::DownCast(BaseHandle handle)
+{
+  return Control::DownCast<TextAnchor, Internal::TextAnchor>(handle);
+}
+
+TextAnchor::TextAnchor(Internal::TextAnchor& implementation)
+: Control(implementation)
+{
+}
+
+TextAnchor::TextAnchor(Dali::Internal::CustomActor* internal)
+: Control(internal)
+{
+  VerifyCustomActorPointer<Internal::TextAnchor>(internal);
+}
+
+} // namespace Toolkit
+
+} // namespace Dali
diff --git a/dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h b/dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h
new file mode 100644 (file)
index 0000000..83fbc76
--- /dev/null
@@ -0,0 +1,140 @@
+#ifndef DALI_TOOLKIT_TEXT_ANCHOR_DEVEL_H
+#define DALI_TOOLKIT_TEXT_ANCHOR_DEVEL_H
+
+/*
+ * Copyright (c) 2021 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.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/controls/control.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Internal DALI_INTERNAL
+{
+class TextAnchor;
+}
+
+/**
+ * @brief A control which renders anchor (hyperlink) in hypertext.
+ */
+class DALI_TOOLKIT_API TextAnchor : public Control
+{
+public:
+  /**
+   * @brief The start and end property ranges for this control.
+   */
+  enum PropertyRange
+  {
+    PROPERTY_START_INDEX = Control::CONTROL_PROPERTY_END_INDEX + 1,
+    PROPERTY_END_INDEX   = PROPERTY_START_INDEX + 1000 ///< Reserve property indices
+  };
+
+  /**
+   * @brief An enumeration of properties belonging to the TextAnchor class.
+   */
+  struct Property
+  {
+    enum
+    {
+      /**
+       * @brief The index of a character in text at which an anchor starts.
+       * @details Name "startCharacterIndex", type INTEGER.
+       */
+      START_CHARACTER_INDEX = PROPERTY_START_INDEX,
+
+      /**
+       * @brief The index of a character in text that stands one position after the anchor's last character.
+       * @details Name "endCharacterIndex", type INTEGER.
+       */
+      END_CHARACTER_INDEX,
+
+      /**
+       * @brief The URI associated with an anchor.
+       * @details Name "uri", type STRING.
+       */
+      URI
+    };
+  };
+
+  /**
+   * @brief Creates the TextAnchor control.
+   * @return A handle to the TextAnchor control.
+   */
+  static TextAnchor New();
+
+  /**
+   * @brief Creates an empty handle.
+   */
+  TextAnchor();
+
+  /**
+   * @brief Copy constructor.
+   *
+   * @param[in] handle The handle to copy from.
+   */
+  TextAnchor(const TextAnchor& handle);
+
+  /**
+   * @brief Assignment operator.
+   *
+   * @param[in] handle The handle to copy from.
+   * @return A reference to this.
+   */
+  TextAnchor& operator=(const TextAnchor& handle);
+
+  /**
+   * @brief Destructor
+   *
+   * This is non-virtual since derived Handle types must not contain data or virtual methods.
+   */
+  ~TextAnchor();
+
+  /**
+   * @brief Downcast a handle to TextAnchor.
+   *
+   * If the BaseHandle points is a TextAnchor the downcast returns a valid handle.
+   * If not the returned handle is left empty.
+   *
+   * @param[in] handle Handle to an object
+   * @return handle to a TextAnchor or an empty handle
+   */
+  static TextAnchor DownCast(BaseHandle handle);
+
+public: // Not intended for application developers
+  /**
+   * @brief Creates a handle using the Toolkit::Internal implementation.
+   *
+   * @param[in] implementation The Control implementation.
+   */
+  DALI_INTERNAL TextAnchor(Internal::TextAnchor& implementation);
+
+  /**
+   * @brief Allows the creation of this Control from an Internal::CustomActor pointer.
+   *
+   * @param[in]  internal  A pointer to the internal CustomActor.
+   */
+  explicit DALI_INTERNAL TextAnchor(Dali::Internal::CustomActor* internal);
+
+}; // Class TextAnchor
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_TEXT_ANCHOR_DEVEL_H
index c88b7a0..3a40bc9 100755 (executable)
@@ -32,6 +32,7 @@ SET( devel_api_src_files
   ${devel_api_src_dir}/controls/shadow-view/shadow-view.cpp
   ${devel_api_src_dir}/controls/super-blur-view/super-blur-view.cpp
   ${devel_api_src_dir}/controls/table-view/table-view.cpp
+  ${devel_api_src_dir}/controls/text-controls/text-anchor-devel.cpp
   ${devel_api_src_dir}/controls/text-controls/text-editor-devel.cpp
   ${devel_api_src_dir}/controls/text-controls/text-field-devel.cpp
   ${devel_api_src_dir}/controls/text-controls/text-label-devel.cpp
@@ -217,6 +218,7 @@ SET( devel_api_super_blur_view_header_files
 )
 
 SET( devel_api_text_controls_header_files
+  ${devel_api_src_dir}/controls/text-controls/text-anchor-devel.h
   ${devel_api_src_dir}/controls/text-controls/text-editor-devel.h
   ${devel_api_src_dir}/controls/text-controls/text-field-devel.h
   ${devel_api_src_dir}/controls/text-controls/text-label-devel.h
index 7033e6d..696b44a 100644 (file)
 #include <dali/public-api/actors/layer.h>
 
 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
+#include <dali-toolkit/devel-api/controls/control-devel.h>
 #include <dali-toolkit/internal/controls/text-controls/common-text-utils.h>
 #include <dali-toolkit/internal/text/text-view.h>
 
 namespace Dali::Toolkit::Internal
 {
+void CommonTextUtils::SynchronizeTextAnchorsInParent(
+  Actor                             parent,
+  Text::ControllerPtr               controller,
+  std::vector<Toolkit::TextAnchor>& anchorActors)
+{
+  for(auto& anchorActor : anchorActors)
+  {
+    parent.Remove(anchorActor);
+  }
+  if(Dali::Accessibility::IsUp())
+  {
+    controller->GetAnchorActors(anchorActors);
+    for(auto& anchorActor : anchorActors)
+    {
+      parent.Add(anchorActor);
+    }
+  }
+}
+
 void CommonTextUtils::RenderText(
-  Actor                            textActor,
-  Text::RendererPtr                renderer,
-  Text::ControllerPtr              controller,
-  Text::DecoratorPtr               decorator,
-  float&                           alignmentOffset,
-  Actor&                           renderableActor,
-  Actor&                           backgroundActor,
-  Toolkit::Control&                stencil,
-  std::vector<Actor>&              clippingDecorationActors,
-  Text::Controller::UpdateTextType updateTextType)
+  Actor                             textActor,
+  Text::RendererPtr                 renderer,
+  Text::ControllerPtr               controller,
+  Text::DecoratorPtr                decorator,
+  float&                            alignmentOffset,
+  Actor&                            renderableActor,
+  Actor&                            backgroundActor,
+  Toolkit::Control&                 stencil,
+  std::vector<Actor>&               clippingDecorationActors,
+  std::vector<Toolkit::TextAnchor>& anchorActors,
+  Text::Controller::UpdateTextType  updateTextType)
 {
   Actor newRenderableActor;
 
@@ -126,6 +147,7 @@ void CommonTextUtils::RenderText(
         backgroundActor.LowerToBottom();
       }
     }
+    SynchronizeTextAnchorsInParent(textActor, controller, anchorActors);
   }
 }
 
index f9069a2..212f162 100644 (file)
@@ -16,7 +16,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
+#include <dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h>
 #include <dali-toolkit/internal/text/decorator/text-decorator.h>
 #include <dali-toolkit/internal/text/rendering/text-renderer.h>
 #include <dali-toolkit/internal/text/text-controller.h>
@@ -42,6 +42,7 @@ public:
    * @param[in,out] backgroundActor Actor for rendering background
    * @param[in,out] stencil Clipping actor
    * @param[in,out] clippingDecorationActors Clipping decoration actors
+   * @param[in,out] anchorActors Anchor actors
    * @param[in] updateTextType How the text has been updated
    */
   static void RenderText(
@@ -54,7 +55,19 @@ public:
     Actor&                           backgroundActor,
     Toolkit::Control&                stencil,
     std::vector<Actor>&              clippingDecorationActors,
+    std::vector<Toolkit::TextAnchor>& anchorActors,
     Text::Controller::UpdateTextType updateTextType);
+
+  /**
+   * Common method to synchronize TextAnchor actors with Anchor objects in text's logical model.
+   * @param[in] parent The actor that is a parent of anchor actors
+   * @param[in] controller pointer to the text controller
+   * @param[in,out] anchorActors Anchor actors
+   */
+  static void SynchronizeTextAnchorsInParent(
+    Actor                             parent,
+    Text::ControllerPtr               controller,
+    std::vector<Toolkit::TextAnchor>& anchorActors);
 };
 
 } // namespace Dali::Toolkit::Internal
diff --git a/dali-toolkit/internal/controls/text-controls/text-anchor-impl.cpp b/dali-toolkit/internal/controls/text-controls/text-anchor-impl.cpp
new file mode 100644 (file)
index 0000000..2fde91c
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2021 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/controls/text-controls/text-anchor-impl.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/actors/actor-devel.h>
+#include <dali/devel-api/object/property-helper-devel.h>
+#include <dali/integration-api/debug.h>
+#include <dali/public-api/common/dali-common.h>
+#include <dali/public-api/object/type-registry-helper.h>
+
+// INTERNAL INCLUDES
+
+// DEVEL INCLUDES
+#include <dali-toolkit/devel-api/controls/control-devel.h>
+
+using namespace Dali::Toolkit::Text;
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Internal
+{
+namespace
+{
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
+#endif
+
+// Type registration
+BaseHandle Create()
+{
+  return Toolkit::TextAnchor::New();
+}
+
+// clang-format off
+// Setup properties, signals and actions using the type-registry.
+DALI_TYPE_REGISTRATION_BEGIN(Toolkit::TextAnchor, Toolkit::Control, Create);
+
+DALI_PROPERTY_REGISTRATION(Toolkit, TextAnchor, "startCharacterIndex", INTEGER, START_CHARACTER_INDEX)
+DALI_PROPERTY_REGISTRATION(Toolkit, TextAnchor, "endCharacterIndex",   INTEGER, END_CHARACTER_INDEX  )
+DALI_PROPERTY_REGISTRATION(Toolkit, TextAnchor, "uri",                 STRING,  URI        )
+
+DALI_TYPE_REGISTRATION_END()
+// clang-format on
+
+} // namespace
+
+Toolkit::TextAnchor TextAnchor::New()
+{
+  // Create the implementation, temporarily owned by this handle on stack
+  IntrusivePtr<TextAnchor> impl = new TextAnchor();
+
+  // Pass ownership to CustomActor handle
+  Toolkit::TextAnchor handle(*impl);
+
+  // Second-phase init of the implementation
+  // This can only be done after the CustomActor connection has been made...
+  impl->Initialize();
+
+  return handle;
+}
+
+Property::Value TextAnchor::GetProperty(BaseObject* object, Property::Index index)
+{
+  Property::Value value;
+
+  Toolkit::TextAnchor anchor = Toolkit::TextAnchor::DownCast(Dali::BaseHandle(object));
+
+  if(anchor)
+  {
+    TextAnchor& impl(GetImpl(anchor));
+
+    switch(index)
+    {
+      case Toolkit::TextAnchor::Property::START_CHARACTER_INDEX:
+      {
+        value = impl.mStartCharacterIndex;
+        break;
+      }
+      case Toolkit::TextAnchor::Property::END_CHARACTER_INDEX:
+      {
+        value = impl.mEndCharacterIndex;
+        break;
+      }
+      case Toolkit::TextAnchor::Property::URI:
+      {
+        value = impl.mUri;
+        break;
+      }
+    }
+  }
+
+  return value;
+}
+
+void TextAnchor::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
+{
+  Toolkit::TextAnchor anchor = Toolkit::TextAnchor::DownCast(Dali::BaseHandle(object));
+
+  if(anchor)
+  {
+    TextAnchor& impl(GetImpl(anchor));
+    switch(index)
+    {
+      case Toolkit::TextAnchor::Property::START_CHARACTER_INDEX:
+      {
+        value.Get(impl.mStartCharacterIndex);
+        break;
+      }
+
+      case Toolkit::TextAnchor::Property::END_CHARACTER_INDEX:
+      {
+        value.Get(impl.mEndCharacterIndex);
+        break;
+      }
+
+      case Toolkit::TextAnchor::Property::URI:
+      {
+        value.Get(impl.mUri);
+        break;
+      }
+    }
+  }
+}
+
+void TextAnchor::OnInitialize()
+{
+  Actor self = Self();
+
+  // Enable highlightability
+  self.SetProperty(Toolkit::DevelControl::Property::ACCESSIBILITY_HIGHLIGHTABLE, true);
+
+  DevelControl::SetAccessibilityConstructor(self, [](Dali::Actor actor) {
+    return std::unique_ptr<Dali::Accessibility::Accessible>(
+      new AccessibleImpl(actor, Dali::Accessibility::Role::LINK));
+  });
+}
+
+TextAnchor::TextAnchor()
+: Control(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT))
+{
+}
+
+TextAnchor::~TextAnchor()
+{
+}
+
+int32_t TextAnchor::AccessibleImpl::GetEndIndex() const
+{
+  auto self = Toolkit::TextAnchor::DownCast(Self());
+  return self.GetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX).Get<int>();
+}
+
+int32_t TextAnchor::AccessibleImpl::GetStartIndex() const
+{
+  auto self = Toolkit::TextAnchor::DownCast(Self());
+  return self.GetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX).Get<int>();
+}
+
+int32_t TextAnchor::AccessibleImpl::GetAnchorCount() const
+{
+  return 1;
+}
+
+Dali::Accessibility::Accessible* TextAnchor::AccessibleImpl::GetAnchorAccessible(int32_t anchorIndex) const
+{
+  return Control::Impl::GetAccessibilityObject(Self());
+}
+
+std::string TextAnchor::AccessibleImpl::GetAnchorUri(int32_t anchorIndex) const
+{
+  auto self = Toolkit::TextAnchor::DownCast(Self());
+  return self.GetProperty(Toolkit::TextAnchor::Property::URI).Get<std::string>();
+}
+
+bool TextAnchor::AccessibleImpl::IsValid() const
+{
+  return !GetAnchorUri(0).empty();
+}
+
+bool TextAnchor::OnAccessibilityActivated()
+{
+  auto uri = Self().GetProperty(Toolkit::TextAnchor::Property::URI).Get<std::string>();
+  if(!uri.empty())
+  {
+    Dali::Actor                                  current                             = Self();
+    Dali::Toolkit::Text::AnchorControlInterface* parentImplementationAnchorInterface = nullptr;
+    while(!current.GetProperty<bool>(Actor::Property::IS_ROOT) && !parentImplementationAnchorInterface)
+    {
+      Dali::Actor            parentAsActor        = current.GetParent();
+      Dali::CustomActor      parentAsCustomActor  = Dali::CustomActor::DownCast(parentAsActor);
+      Dali::CustomActorImpl& parentImplementation = parentAsCustomActor.GetImplementation();
+      parentImplementationAnchorInterface         = dynamic_cast<Dali::Toolkit::Text::AnchorControlInterface*>(&parentImplementation);
+      current                                     = parentAsActor;
+    }
+    if(parentImplementationAnchorInterface)
+    {
+      parentImplementationAnchorInterface->AnchorClicked(uri);
+      return true;
+    }
+    else
+    {
+      DALI_LOG_ERROR("TextAnchor::OnAccessibilityActivate cannot find ancestor actor implementing Dali::Toolkit::Text::AnchorControlInterface.\n");
+    }
+  }
+  return false;
+}
+
+} // namespace Internal
+
+} // namespace Toolkit
+
+} // namespace Dali
diff --git a/dali-toolkit/internal/controls/text-controls/text-anchor-impl.h b/dali-toolkit/internal/controls/text-controls/text-anchor-impl.h
new file mode 100644 (file)
index 0000000..b590862
--- /dev/null
@@ -0,0 +1,167 @@
+#ifndef DALI_TOOLKIT_INTERNAL_TEXT_ANCHOR_H
+#define DALI_TOOLKIT_INTERNAL_TEXT_ANCHOR_H
+
+/*
+ * Copyright (c) 2021 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.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <dali-toolkit/public-api/controls/control-impl.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h>
+#include <dali-toolkit/internal/controls/control/control-data-impl.h>
+#include <dali-toolkit/internal/text/text-anchor-control-interface.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Internal
+{
+/**
+ * @brief A control which renders anchor (hyperlink) in hypertext.
+ */
+class TextAnchor : public Control
+{
+public:
+  /**
+   * @copydoc Dali::Toollkit::TextAnchor::New()
+   */
+  static Toolkit::TextAnchor New();
+
+  // Properties
+
+  /**
+   * @brief Called when a property of an object of this type is set.
+   *
+   * @param[in] object The object whose property is set.
+   * @param[in] index The property index.
+   * @param[in] value The new property value.
+   */
+  static void SetProperty(BaseObject* object, Property::Index index, const Property::Value& value);
+
+  /**
+   * @brief Called to retrieve a property of an object of this type.
+   *
+   * @param[in] object The object whose property is to be retrieved.
+   * @param[in] index The property index.
+   * @return The current value of the property.
+   */
+  static Property::Value GetProperty(BaseObject* object, Property::Index index);
+
+private: // From Control
+  /**
+   * @copydoc Control::OnInitialize()
+   */
+  void OnInitialize() override;
+
+  /**
+   * @copydoc Control::OnPropertySet()
+   */
+  // void OnPropertySet(Property::Index index, const Property::Value& propertyValue) override;
+
+  /**
+   * @copydoc Control::OnAccessibilityActivated()
+   */
+  bool OnAccessibilityActivated() override;
+
+private: // Implementation
+  /**
+   * Construct a new TextAnchor.
+   */
+  TextAnchor();
+
+  /**
+   * A reference counted object may only be deleted by calling Unreference()
+   */
+  virtual ~TextAnchor();
+
+private:
+  // Undefined copy constructor and assignment operators
+  TextAnchor(const TextAnchor&);
+  TextAnchor& operator=(const TextAnchor& rhs);
+
+  //Data
+  int         mStartCharacterIndex;
+  int         mEndCharacterIndex;
+  std::string mUri;
+
+protected:
+  /**
+   * @brief This structure is to connect TextAnchor with Accessible functions.
+   */
+  struct AccessibleImpl : public DevelControl::AccessibleImpl,
+                          public virtual Dali::Accessibility::Hyperlink
+  {
+    using DevelControl::AccessibleImpl::AccessibleImpl;
+    /**
+     * @copydoc Dali::Accessibility::Hyperlink::GetEndIndex()
+     */
+    int32_t GetEndIndex() const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hyperlink::GetStartIndex()
+     */
+    int32_t GetStartIndex() const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hyperlink::GetAnchorCount()
+     */
+    int32_t GetAnchorCount() const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hyperlink::GetAnchorAccessible()
+     */
+    Accessible* GetAnchorAccessible(int32_t anchorIndex) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hyperlink::GetAnchorUri()
+     */
+    std::string GetAnchorUri(int32_t anchorIndex) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hyperlink::IsValid()
+     */
+    bool IsValid() const override;
+  };
+};
+
+inline Toolkit::Internal::TextAnchor& GetImpl(Toolkit::TextAnchor& textAnchor)
+{
+  DALI_ASSERT_ALWAYS(textAnchor);
+
+  Dali::RefObject& handle = textAnchor.GetImplementation();
+
+  return static_cast<Toolkit::Internal::TextAnchor&>(handle);
+}
+
+inline const Toolkit::Internal::TextAnchor& GetImpl(const Toolkit::TextAnchor& textAnchor)
+{
+  DALI_ASSERT_ALWAYS(textAnchor);
+
+  const Dali::RefObject& handle = textAnchor.GetImplementation();
+
+  return static_cast<const Toolkit::Internal::TextAnchor&>(handle);
+}
+
+} // namespace Internal
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_INTERNAL_TEXT_ANCHOR_H
index 0259471..44c1176 100644 (file)
@@ -478,6 +478,11 @@ Toolkit::TextEditor::ScrollStateChangedSignalType& TextEditor::ScrollStateChange
   return mScrollStateChangedSignal;
 }
 
+void TextEditor::OnAccessibilityStatusChanged()
+{
+  CommonTextUtils::SynchronizeTextAnchorsInParent(Self(), mController, mAnchorActors);
+}
+
 void TextEditor::OnInitialize()
 {
   Actor self = Self();
@@ -568,6 +573,9 @@ void TextEditor::OnInitialize()
     return std::unique_ptr<Dali::Accessibility::Accessible>(
       new AccessibleImpl(actor, Dali::Accessibility::Role::ENTRY));
   });
+
+  Accessibility::Bridge::EnabledSignal().Connect(this, &TextEditor::OnAccessibilityStatusChanged);
+  Accessibility::Bridge::DisabledSignal().Connect(this, &TextEditor::OnAccessibilityStatusChanged);
 }
 
 void TextEditor::OnStyleChange(Toolkit::StyleManager styleManager, StyleChange::Type change)
@@ -724,7 +732,7 @@ void TextEditor::OnRelayout(const Vector2& size, RelayoutContainer& container)
 
 void TextEditor::RenderText(Text::Controller::UpdateTextType updateTextType)
 {
-  CommonTextUtils::RenderText(Self(), mRenderer, mController, mDecorator, mAlignmentOffset, mRenderableActor, mBackgroundActor, mStencil, mClippingDecorationActors, updateTextType);
+  CommonTextUtils::RenderText(Self(), mRenderer, mController, mDecorator, mAlignmentOffset, mRenderableActor, mBackgroundActor, mStencil, mClippingDecorationActors, mAnchorActors, updateTextType);
   if(mRenderableActor)
   {
     ApplyScrollPosition();
@@ -1540,6 +1548,30 @@ bool TextEditor::AccessibleImpl::SetTextContents(std::string newContents)
   return true;
 }
 
+int32_t TextEditor::AccessibleImpl::GetLinkCount() const
+{
+  auto self = Toolkit::TextEditor::DownCast(Self());
+  return Dali::Toolkit::GetImpl(self).mAnchorActors.size();
+}
+
+Accessibility::Hyperlink* TextEditor::AccessibleImpl::GetLink(int32_t linkIndex) const
+{
+  if(linkIndex < 0 || linkIndex >= GetLinkCount())
+  {
+    return nullptr;
+  }
+  auto self        = Toolkit::TextEditor::DownCast(Self());
+  auto anchorActor = Dali::Toolkit::GetImpl(self).mAnchorActors[linkIndex];
+  return dynamic_cast<Accessibility::Hyperlink*>(Dali::Accessibility::Accessible::Get(anchorActor));
+}
+
+int32_t TextEditor::AccessibleImpl::GetLinkIndex(int32_t characterOffset) const
+{
+  auto self       = Toolkit::TextEditor::DownCast(Self());
+  auto controller = Dali::Toolkit::GetImpl(self).GetTextController();
+  return controller->GetAnchorIndex(static_cast<size_t>(characterOffset));
+}
+
 } // namespace Internal
 
 } // namespace Toolkit
index 8c5d7be..e4b5fa5 100644 (file)
@@ -473,6 +473,9 @@ private: // Implementation
   // Connection needed to re-render text, when a text editor returns to the scene.
   void OnSceneConnect(Dali::Actor actor);
 
+  // Needed to synchronize TextAnchor actors with Anchor objects in text's logical model
+  void OnAccessibilityStatusChanged();
+
 private: // Data
   // Signals
   Toolkit::TextEditor::TextChangedSignalType                mTextChangedSignal;
@@ -485,17 +488,18 @@ private: // Data
   Toolkit::DevelTextEditor::SelectionChangedSignalType      mSelectionChangedSignal;
   Toolkit::DevelTextEditor::SelectionClearedSignalType      mSelectionClearedSignal;
 
-  InputMethodContext            mInputMethodContext;
-  Text::ControllerPtr           mController;
-  Text::RendererPtr             mRenderer;
-  Text::DecoratorPtr            mDecorator;
-  Text::TextVerticalScrollerPtr mTextVerticalScroller;
-  Toolkit::Control              mStencil;
-  Toolkit::ScrollBar            mScrollBar;
-  Dali::Animation               mAnimation; ///< Scroll indicator Show/Hide Animation.
-  Dali::TimePeriod              mAnimationPeriod;
-  std::vector<Actor>            mClippingDecorationActors; ///< Decoration actors which need clipping.
-  Dali::InputMethodOptions      mInputMethodOptions;
+  InputMethodContext               mInputMethodContext;
+  Text::ControllerPtr              mController;
+  Text::RendererPtr                mRenderer;
+  Text::DecoratorPtr               mDecorator;
+  Text::TextVerticalScrollerPtr    mTextVerticalScroller;
+  Toolkit::Control                 mStencil;
+  Toolkit::ScrollBar               mScrollBar;
+  Dali::Animation                  mAnimation; ///< Scroll indicator Show/Hide Animation.
+  Dali::TimePeriod                 mAnimationPeriod;
+  std::vector<Actor>               mClippingDecorationActors; ///< Decoration actors which need clipping.
+  std::vector<Toolkit::TextAnchor> mAnchorActors;
+  Dali::InputMethodOptions         mInputMethodOptions;
 
   Actor         mRenderableActor;
   Actor         mActiveLayer;
@@ -529,7 +533,8 @@ private: // Data
    */
   struct AccessibleImpl : public DevelControl::AccessibleImpl,
                           public virtual Dali::Accessibility::Text,
-                          public virtual Dali::Accessibility::EditableText
+                          public virtual Dali::Accessibility::EditableText,
+                          public virtual Dali::Accessibility::Hypertext
   {
     using DevelControl::AccessibleImpl::AccessibleImpl;
 
@@ -607,6 +612,21 @@ private: // Data
      * @copydoc Dali::Accessibility::EditableText::DeleteText()
      */
     bool DeleteText(size_t startPosition, size_t endPosition) override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLink()
+     */
+    Accessibility::Hyperlink* GetLink(int32_t linkIndex) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLinkIndex()
+     */
+    int32_t GetLinkIndex(int32_t characterOffset) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLinkCount()
+     */
+    int32_t GetLinkCount() const override;
   };
 };
 
index 62dfbda..f35c169 100644 (file)
@@ -15,6 +15,7 @@
  */
 
 #include <dali-toolkit/internal/controls/text-controls/text-editor-property-handler.h>
+#include <dali-toolkit/internal/controls/text-controls/common-text-utils.h>
 
 #include <dali-toolkit/devel-api/focus-manager/keyinput-focus-manager.h>
 
@@ -311,6 +312,7 @@ void TextEditor::PropertyHandler::SetProperty(Toolkit::TextEditor textEditor, Pr
       DALI_LOG_INFO(gTextEditorLogFilter, Debug::General, "TextEditor %p ENABLE_MARKUP %d\n", impl.mController.Get(), enableMarkup);
 
       impl.mController->SetMarkupProcessorEnabled(enableMarkup);
+      CommonTextUtils::SynchronizeTextAnchorsInParent(textEditor, impl.mController, impl.mAnchorActors);
       break;
     }
     case Toolkit::TextEditor::Property::INPUT_COLOR:
index b48faa0..2f4ef60 100644 (file)
@@ -463,6 +463,11 @@ DevelTextField::SelectionClearedSignalType& TextField::SelectionClearedSignal()
   return mSelectionClearedSignal;
 }
 
+void TextField::OnAccessibilityStatusChanged()
+{
+  CommonTextUtils::SynchronizeTextAnchorsInParent(Self(), mController, mAnchorActors);
+}
+
 void TextField::OnInitialize()
 {
   Actor self = Self();
@@ -541,6 +546,9 @@ void TextField::OnInitialize()
     return std::unique_ptr<Dali::Accessibility::Accessible>(
       new AccessibleImpl(actor, Dali::Accessibility::Role::ENTRY));
   });
+
+  Accessibility::Bridge::EnabledSignal().Connect(this, &TextField::OnAccessibilityStatusChanged);
+  Accessibility::Bridge::DisabledSignal().Connect(this, &TextField::OnAccessibilityStatusChanged);
 }
 
 void TextField::OnStyleChange(Toolkit::StyleManager styleManager, StyleChange::Type change)
@@ -702,7 +710,7 @@ Text::ControllerPtr TextField::GetTextController()
 
 void TextField::RenderText(Text::Controller::UpdateTextType updateTextType)
 {
-  CommonTextUtils::RenderText(Self(), mRenderer, mController, mDecorator, mAlignmentOffset, mRenderableActor, mBackgroundActor, mStencil, mClippingDecorationActors, updateTextType);
+  CommonTextUtils::RenderText(Self(), mRenderer, mController, mDecorator, mAlignmentOffset, mRenderableActor, mBackgroundActor, mStencil, mClippingDecorationActors, mAnchorActors, updateTextType);
 }
 
 void TextField::OnKeyInputFocusGained()
@@ -1414,6 +1422,30 @@ bool TextField::AccessibleImpl::SetTextContents(std::string newContents)
   return true;
 }
 
+int32_t TextField::AccessibleImpl::GetLinkCount() const
+{
+  auto self = Toolkit::TextField::DownCast(Self());
+  return Dali::Toolkit::GetImpl(self).mAnchorActors.size();
+}
+
+Accessibility::Hyperlink* TextField::AccessibleImpl::GetLink(int32_t linkIndex) const
+{
+  if(linkIndex < 0 || linkIndex >= GetLinkCount())
+  {
+    return nullptr;
+  }
+  auto self        = Toolkit::TextField::DownCast(Self());
+  auto anchorActor = Dali::Toolkit::GetImpl(self).mAnchorActors[linkIndex];
+  return dynamic_cast<Accessibility::Hyperlink*>(Dali::Accessibility::Accessible::Get(anchorActor));
+}
+
+int32_t TextField::AccessibleImpl::GetLinkIndex(int32_t characterOffset) const
+{
+  auto self       = Toolkit::TextField::DownCast(Self());
+  auto controller = Dali::Toolkit::GetImpl(self).GetTextController();
+  return controller->GetAnchorIndex(static_cast<size_t>(characterOffset));
+}
+
 } // namespace Internal
 
 } // namespace Toolkit
index b65e953..7e18a82 100644 (file)
@@ -429,6 +429,9 @@ private: // Implementation
   // Connection needed to re-render text, when a Text Field returns to the scene.
   void OnSceneConnect(Dali::Actor actor);
 
+  // Needed to synchronize TextAnchor actors with Anchor objects in text's logical model
+  void OnAccessibilityStatusChanged();
+
 private: // Data
   // Signals
   Toolkit::TextField::TextChangedSignalType                mTextChangedSignal;
@@ -440,13 +443,14 @@ private: // Data
   Toolkit::DevelTextField::SelectionChangedSignalType      mSelectionChangedSignal;
   Toolkit::DevelTextField::SelectionClearedSignalType      mSelectionClearedSignal;
 
-  InputMethodContext       mInputMethodContext;
-  Text::ControllerPtr      mController;
-  Text::RendererPtr        mRenderer;
-  Text::DecoratorPtr       mDecorator;
-  Toolkit::Control         mStencil;                  ///< For EXCEED_POLICY_CLIP
-  std::vector<Actor>       mClippingDecorationActors; ///< Decoration actors which need clipping.
-  Dali::InputMethodOptions mInputMethodOptions;
+  InputMethodContext               mInputMethodContext;
+  Text::ControllerPtr              mController;
+  Text::RendererPtr                mRenderer;
+  Text::DecoratorPtr               mDecorator;
+  Toolkit::Control                 mStencil;                  ///< For EXCEED_POLICY_CLIP
+  std::vector<Actor>               mClippingDecorationActors; ///< Decoration actors which need clipping.
+  std::vector<Toolkit::TextAnchor> mAnchorActors;
+  Dali::InputMethodOptions         mInputMethodOptions;
 
   Actor         mRenderableActor;
   Actor         mActiveLayer;
@@ -477,7 +481,8 @@ protected:
    */
   struct AccessibleImpl : public DevelControl::AccessibleImpl,
                           public virtual Dali::Accessibility::Text,
-                          public virtual Dali::Accessibility::EditableText
+                          public virtual Dali::Accessibility::EditableText,
+                          public virtual Dali::Accessibility::Hypertext
   {
     using DevelControl::AccessibleImpl::AccessibleImpl;
 
@@ -555,6 +560,21 @@ protected:
      * @copydoc Dali::Accessibility::EditableText::DeleteText()
      */
     bool DeleteText(size_t startPosition, size_t endPosition) override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLink()
+     */
+    Accessibility::Hyperlink* GetLink(int32_t linkIndex) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLinkIndex()
+     */
+    int32_t GetLinkIndex(int32_t characterOffset) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLinkCount()
+     */
+    int32_t GetLinkCount() const override;
   };
 };
 
index db08d3a..dc253f0 100644 (file)
@@ -15,6 +15,7 @@
  */
 
 #include <dali-toolkit/internal/controls/text-controls/text-field-property-handler.h>
+#include <dali-toolkit/internal/controls/text-controls/common-text-utils.h>
 
 #include <dali-toolkit/devel-api/focus-manager/keyinput-focus-manager.h>
 #include <dali-toolkit/devel-api/text/rendering-backend.h>
@@ -408,6 +409,7 @@ void TextField::PropertyHandler::SetProperty(Toolkit::TextField textField, Prope
       DALI_LOG_INFO(gTextFieldLogFilter, Debug::General, "TextField %p ENABLE_MARKUP %d\n", impl.mController.Get(), enableMarkup);
 
       impl.mController->SetMarkupProcessorEnabled(enableMarkup);
+      CommonTextUtils::SynchronizeTextAnchorsInParent(textField, impl.mController, impl.mAnchorActors);
       break;
     }
     case Toolkit::TextField::Property::INPUT_FONT_FAMILY:
index a98ef9b..22990d9 100644 (file)
@@ -37,6 +37,7 @@
 #include <dali-toolkit/internal/text/text-effects-style.h>
 #include <dali-toolkit/internal/text/text-font-style.h>
 #include <dali-toolkit/internal/text/text-view.h>
+#include <dali-toolkit/internal/controls/text-controls/common-text-utils.h>
 #include <dali-toolkit/public-api/text/text-enumerations.h>
 
 #include <dali-toolkit/devel-api/controls/control-devel.h>
@@ -850,6 +851,9 @@ void TextLabel::OnInitialize()
     return std::unique_ptr<Dali::Accessibility::Accessible>(
       new AccessibleImpl(actor, Dali::Accessibility::Role::LABEL));
   });
+
+  Accessibility::Bridge::EnabledSignal().Connect(this, &TextLabel::OnAccessibilityStatusChanged);
+  Accessibility::Bridge::DisabledSignal().Connect(this, &TextLabel::OnAccessibilityStatusChanged);
 }
 
 void TextLabel::OnStyleChange(Toolkit::StyleManager styleManager, StyleChange::Type change)
@@ -937,6 +941,12 @@ void TextLabel::OnPropertySet(Property::Index index, const Property::Value& prop
       }
       break;
     }
+    case Toolkit::TextLabel::Property::TEXT:
+    case Toolkit::TextLabel::Property::ENABLE_MARKUP:
+    {
+      CommonTextUtils::SynchronizeTextAnchorsInParent(Self(), mController, mAnchorActors);
+      break;
+    }
     default:
     {
       Control::OnPropertySet(index, propertyValue); // up call to control for non-handled properties
@@ -1110,6 +1120,11 @@ void TextLabel::EmitTextFitChangedSignal()
   mTextFitChangedSignal.Emit(handle);
 }
 
+void TextLabel::OnAccessibilityStatusChanged()
+{
+  CommonTextUtils::SynchronizeTextAnchorsInParent(Self(), mController, mAnchorActors);
+}
+
 TextLabel::TextLabel()
 : Control(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT)),
   mRenderingBackend(DEFAULT_RENDERING_BACKEND),
@@ -1170,8 +1185,8 @@ bool TextLabel::AccessibleImpl::SetCursorOffset(size_t offset)
 
 Dali::Accessibility::Range TextLabel::AccessibleImpl::GetTextAtOffset(size_t offset, Dali::Accessibility::TextBoundary boundary)
 {
-  auto self = Toolkit::TextLabel::DownCast(Self());
-  auto text = self.GetProperty(Toolkit::TextLabel::Property::TEXT).Get<std::string>();
+  auto self     = Toolkit::TextLabel::DownCast(Self());
+  auto text     = self.GetProperty(Toolkit::TextLabel::Property::TEXT).Get<std::string>();
   auto textSize = text.size();
 
   auto range = Dali::Accessibility::Range{};
@@ -1192,7 +1207,7 @@ Dali::Accessibility::Range TextLabel::AccessibleImpl::GetTextAtOffset(size_t off
     case Dali::Accessibility::TextBoundary::LINE:
     {
       auto textString = text.c_str();
-      auto breaks = std::vector<char>(textSize, 0);
+      auto breaks     = std::vector<char>(textSize, 0);
 
       if(boundary == Dali::Accessibility::TextBoundary::WORD)
       {
@@ -1203,7 +1218,7 @@ Dali::Accessibility::Range TextLabel::AccessibleImpl::GetTextAtOffset(size_t off
         Accessibility::Accessible::FindLineSeparationsUtf8(reinterpret_cast<const utf8_t*>(textString), textSize, "", breaks.data());
       }
 
-      auto index = 0u;
+      auto index   = 0u;
       auto counter = 0u;
       while(index < textSize && counter <= offset)
       {
@@ -1267,8 +1282,8 @@ Dali::Accessibility::Range TextLabel::AccessibleImpl::GetRangeOfSelection(size_t
     return {};
   }
 
-  auto self  = Toolkit::TextLabel::DownCast(Self());
-  auto controller = Dali::Toolkit::GetImpl(self).GetTextController();
+  auto        self       = Toolkit::TextLabel::DownCast(Self());
+  auto        controller = Dali::Toolkit::GetImpl(self).GetTextController();
   std::string value{};
   controller->RetrieveSelection(value);
   auto indices = controller->GetSelectionIndexes();
@@ -1302,6 +1317,30 @@ bool TextLabel::AccessibleImpl::SetRangeOfSelection(size_t selectionIndex, size_
   return true;
 }
 
+int32_t TextLabel::AccessibleImpl::GetLinkCount() const
+{
+  auto self = Toolkit::TextLabel::DownCast(Self());
+  return Dali::Toolkit::GetImpl(self).mAnchorActors.size();
+}
+
+Accessibility::Hyperlink* TextLabel::AccessibleImpl::GetLink(int32_t linkIndex) const
+{
+  if(linkIndex < 0 || linkIndex >= GetLinkCount())
+  {
+    return nullptr;
+  }
+  auto self        = Toolkit::TextLabel::DownCast(Self());
+  auto anchorActor = Dali::Toolkit::GetImpl(self).mAnchorActors[linkIndex];
+  return dynamic_cast<Accessibility::Hyperlink*>(Dali::Accessibility::Accessible::Get(anchorActor));
+}
+
+int32_t TextLabel::AccessibleImpl::GetLinkIndex(int32_t characterOffset) const
+{
+  auto self       = Toolkit::TextLabel::DownCast(Self());
+  auto controller = Dali::Toolkit::GetImpl(self).GetTextController();
+  return controller->GetAnchorIndex(static_cast<size_t>(characterOffset));
+}
+
 } // namespace Internal
 
 } // namespace Toolkit
index d345d21..5c9a6b1 100644 (file)
@@ -198,6 +198,7 @@ private:
    * @brief Emits TextFitChanged signal.
    */
   void EmitTextFitChangedSignal();
+  void OnAccessibilityStatusChanged();
 
 private: // Data
   Text::ControllerPtr   mController;
@@ -205,6 +206,8 @@ private: // Data
 
   Toolkit::Visual::Base mVisual;
 
+  std::vector<Toolkit::TextAnchor> mAnchorActors;
+
   // Signals
   Toolkit::DevelTextLabel::AnchorClickedSignalType mAnchorClickedSignal;
   Toolkit::DevelTextLabel::TextFitChangedSignalType mTextFitChangedSignal;
@@ -217,7 +220,8 @@ protected:
    * @brief This structure is to connect TextLabel with Accessible functions.
    */
   struct AccessibleImpl : public DevelControl::AccessibleImpl,
-                          public virtual Dali::Accessibility::Text
+                          public virtual Dali::Accessibility::Text,
+                          public virtual Dali::Accessibility::Hypertext
   {
     using DevelControl::AccessibleImpl::AccessibleImpl;
 
@@ -270,6 +274,21 @@ protected:
      * @copydoc Dali::Accessibility::Text::GetNamePropertyIndex()
      */
     Property::Index GetNamePropertyIndex() override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLink()
+     */
+    Accessibility::Hyperlink* GetLink(int32_t linkIndex) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLinkIndex()
+     */
+    int32_t GetLinkIndex(int32_t characterOffset) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLinkCount()
+     */
+    int32_t GetLinkCount() const override;
   };
 };
 
index 855df54..b9c7d23 100644 (file)
@@ -102,6 +102,7 @@ SET( toolkit_src_files
    ${toolkit_src_dir}/controls/super-blur-view/super-blur-view-impl.cpp
    ${toolkit_src_dir}/controls/table-view/table-view-impl.cpp
    ${toolkit_src_dir}/controls/text-controls/common-text-utils.cpp
+   ${toolkit_src_dir}/controls/text-controls/text-anchor-impl.cpp
    ${toolkit_src_dir}/controls/text-controls/text-editor-impl.cpp
    ${toolkit_src_dir}/controls/text-controls/text-editor-property-handler.cpp
    ${toolkit_src_dir}/controls/text-controls/text-field-impl.cpp
index 8a1dd76..fcd3bed 100644 (file)
@@ -1619,6 +1619,60 @@ float Controller::Impl::GetVerticalScrollPosition()
   return mEventData ? -mModel->mScrollPosition.y : 0.0f;
 }
 
+Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
+{
+  //TODO
+  return Vector3(10.f, 10.f, 10.f);
+}
+
+Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
+{
+  //TODO
+  return Vector2(10.f, 10.f);
+}
+
+Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
+{
+  auto actor = Toolkit::TextAnchor::New();
+  actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
+  actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
+  const Vector3 anchorPosition = GetAnchorPosition(anchor);
+  actor.SetProperty(Actor::Property::POSITION, anchorPosition);
+  const Vector2 anchorSize = GetAnchorSize(anchor);
+  actor.SetProperty(Actor::Property::SIZE, anchorSize);
+  std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
+  actor.SetProperty(Actor::Property::NAME, anchorText);
+  actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
+  actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
+  actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
+  return actor;
+}
+
+void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
+{
+  /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
+           we need to create and destroy potentially many actors. Some optimization can be considered here.
+           Maybe a "dirty" flag in mLogicalModel? */
+  anchorActors.clear();
+  for(auto& anchor : mModel->mLogicalModel->mAnchors)
+  {
+    auto actor = CreateAnchorActor(anchor);
+    anchorActors.push_back(actor);
+  }
+}
+
+int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
+{
+  Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
+
+  while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
+  {
+    it++;
+  }
+
+  return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
+}
+
 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
 {
   //Underlined character runs for markup-processor
index 814f3a8..e49971a 100644 (file)
@@ -841,6 +841,49 @@ struct Controller::Impl
    */
   void ResetScrollPosition();
 
+  /**
+   * @brief Resets a provided vector with actors that marks the position of anchors in markup enabled text
+   *
+   * @param[out] anchorActors the vector of actor (empty collection if no anchors available).
+   */
+  void GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors);
+
+  /**
+   * @brief Return an index of first anchor in the anchor vector whose boundaries includes given character offset
+   *
+   * @param[in] characterOffset A position in text coords.
+   *
+   * @return the 0-based index in anchor vector (-1 if an anchor not found)
+   */
+  int32_t GetAnchorIndex(size_t characterOffset) const;
+
+  /**
+   * @brief Return the geometrical position of an anchor relative to the parent origin point.
+   *
+   * @param[in] anchor An anchor.
+   *
+   * @return The x, y, z coordinates of an anchor.
+   */
+  Vector3 GetAnchorPosition(Anchor anchor) const;
+
+  /**
+   * @brief Return the size of an anchor expresed as a vector containing anchor's width and height.
+   *
+   * @param[in] anchor An anchor.
+   *
+   * @return The width and height of an anchor.
+   */
+  Vector2 GetAnchorSize(Anchor anchor) const;
+
+  /**
+   * @brief Return the actor representing an anchor.
+   *
+   * @param[in] anchor An anchor.
+   *
+   * @return The actor representing an anchor.
+   */
+  Toolkit::TextAnchor CreateAnchorActor(Anchor anchor);
+
 public:
   /**
    * @brief Gets implementation from the controller handle.
index 4ec306b..88eb9ef 100644 (file)
@@ -1644,6 +1644,16 @@ Actor Controller::CreateBackgroundActor()
   return mImpl->CreateBackgroundActor();
 }
 
+void Controller::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
+{
+  mImpl->GetAnchorActors(anchorActors);
+}
+
+int Controller::GetAnchorIndex(size_t characterOffset)
+{
+  return mImpl->GetAnchorIndex(characterOffset);
+}
+
 Controller::Controller(ControlInterface*           controlInterface,
                        EditableControlInterface*   editableControlInterface,
                        SelectableControlInterface* selectableControlInterface,
index a5bc8b4..1752e3a 100644 (file)
@@ -25,6 +25,7 @@
 // INTERNAL INCLUDES
 #include <dali-toolkit/devel-api/controls/text-controls/text-label-devel.h>
 #include <dali-toolkit/devel-api/controls/text-controls/text-selection-popup-callback-interface.h>
+#include <dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h>
 #include <dali-toolkit/devel-api/text/text-enumerations-devel.h>
 #include <dali-toolkit/internal/text/decorator/text-decorator.h>
 #include <dali-toolkit/internal/text/hidden-text.h>
@@ -1757,6 +1758,22 @@ public: // Text-input Event Queuing.
    */
   CharacterIndex GetCursorPosition();
 
+  /**
+   * @brief Resets a provided vector with actors that marks the position of anchors in markup enabled text
+   *
+   * @param[out] anchorActors the vector of actor (empty collection if no anchors available).
+   */
+  void GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors);
+
+  /**
+   * @brief Return an index of first anchor in the anchor vector whose boundaries includes given character offset
+   *
+   * @param[in] characterOffset A position in text coords.
+   *
+   * @return the index in anchor vector (-1 if an anchor not found)
+   */
+  int GetAnchorIndex(size_t characterOffset);
+
 protected: // Inherit from Text::Decorator::ControllerInterface.
   /**
    * @copydoc Dali::Toolkit::Text::Decorator::ControllerInterface::GetTargetSize()