Add color tag for text markup anchor 48/299948/2
authorBowon Ryu <bowon.ryu@samsung.com>
Thu, 12 Oct 2023 11:51:14 +0000 (20:51 +0900)
committerBowon Ryu <bowon.ryu@samsung.com>
Fri, 13 Oct 2023 01:14:43 +0000 (10:14 +0900)
"<a color='blue' clicked-color='red' href='https://www.tizen.org'>TIZEN</a>"

user can set color and clicked color in the anchor tag.
if not set, default color is applied.

Change-Id: I6ed67b3ae4bec414e306d46bc2b70d4c7a87cdf7
Signed-off-by: Bowon Ryu <bowon.ryu@samsung.com>
automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp
dali-toolkit/internal/text/anchor.h
dali-toolkit/internal/text/controller/text-controller-event-handler.cpp
dali-toolkit/internal/text/markup-processor/markup-processor-anchor.cpp
dali-toolkit/internal/text/markup-processor/markup-processor-anchor.h
dali-toolkit/internal/text/markup-processor/markup-processor.cpp
dali-toolkit/internal/text/markup-tags-and-attributes.h

index 8db73dd..b7ddaa7 100644 (file)
@@ -2485,7 +2485,7 @@ int UtcDaliToolkitTextlabelAnchorClicked(void)
 
   // sets anchor text
   label.SetProperty(TextLabel::Property::ENABLE_MARKUP, true);
-  label.SetProperty(TextLabel::Property::TEXT, "<a href='https://www.tizen.org'>TIZEN</a>");
+  label.SetProperty(TextLabel::Property::TEXT, "<a color='red' clicked-color='green' href='https://www.tizen.org'>TIZEN</a>");
   label.SetProperty(Actor::Property::SIZE, Vector2(100.f, 50.f));
   label.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
   label.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
index d2c7384..f23f08a 100644 (file)
@@ -35,6 +35,11 @@ struct Anchor
   CharacterIndex startIndex; ///< The character's start index of the anchor within the string.
   CharacterIndex endIndex;   ///< The character's end index of the anchor within the string.
   char*          href;       ///< The url path
+
+  bool     isClicked    = false;               ///< Whether the anchor is clicked or not.
+  Vector4  clickedColor = Color::DARK_MAGENTA; ///< The color of the anchor when clicked.
+  uint32_t colorRunIndex;                      ///< RunIndex of color run, used to change color when clicked.
+  uint32_t underlinedCharacterRunIndex;        ///< RunIndex of underline run, used to change color when clicked.
 };
 
 } // namespace Text
index 78b1693..8e175b1 100644 (file)
@@ -392,14 +392,37 @@ void Controller::EventHandler::AnchorEvent(Controller& controller, float x, floa
                                                CharacterHitTest::TAP,
                                                matchedCharacter);
 
-  for(const auto& anchor : controller.mImpl->mModel->mLogicalModel->mAnchors)
+  for(auto& anchor : controller.mImpl->mModel->mLogicalModel->mAnchors)
   {
     // Anchor clicked if the calculated cursor position is within the range of anchor.
     if(cursorPosition >= anchor.startIndex && cursorPosition < anchor.endIndex)
     {
-      if(controller.mImpl->mAnchorControlInterface && anchor.href)
+      if(controller.mImpl->mAnchorControlInterface)
       {
-        std::string href(anchor.href);
+        if(!anchor.isClicked)
+        {
+          anchor.isClicked = true;
+          // TODO: in mutable text, the anchor color and underline run index should be able to be updated.
+          if(!controller.IsEditable())
+          {
+            if(controller.mImpl->mModel->mLogicalModel->mColorRuns.Count() > anchor.colorRunIndex)
+            {
+              ColorRun& colorRun = *(controller.mImpl->mModel->mLogicalModel->mColorRuns.Begin() + anchor.colorRunIndex);
+              colorRun.color = anchor.clickedColor;
+            }
+            if(controller.mImpl->mModel->mLogicalModel->mUnderlinedCharacterRuns.Count() > anchor.underlinedCharacterRunIndex)
+            {
+              UnderlinedCharacterRun& underlineRun = *(controller.mImpl->mModel->mLogicalModel->mUnderlinedCharacterRuns.Begin() + anchor.underlinedCharacterRunIndex);
+              underlineRun.properties.color = anchor.clickedColor;
+            }
+
+            controller.mImpl->ClearFontData();
+            controller.mImpl->mOperationsPending = static_cast<OperationsMask>(controller.mImpl->mOperationsPending | COLOR);
+            controller.mImpl->RequestRelayout();
+          }
+        }
+
+        std::string href = anchor.href == nullptr ? "" : anchor.href;
         controller.mImpl->mAnchorControlInterface->AnchorClicked(href);
         break;
       }
index 87789e3..d958d19 100644 (file)
@@ -24,6 +24,8 @@
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/internal/text/anchor.h>
+#include <dali-toolkit/internal/text/markup-processor/markup-processor-color.h>
+#include <dali-toolkit/internal/text/markup-processor/markup-processor-underline.h>
 #include <dali-toolkit/internal/text/markup-processor/markup-processor-helper-functions.h>
 #include <dali-toolkit/internal/text/markup-tags-and-attributes.h>
 
@@ -33,10 +35,12 @@ namespace Toolkit
 {
 namespace Text
 {
-void ProcessAnchor(const Tag& tag, Anchor& anchor)
-{
-  anchor.href = nullptr;
 
+void ProcessAnchorTag(const Tag&              tag,
+                      Anchor&                 anchor,
+                      ColorRun&               colorRun,
+                      UnderlinedCharacterRun& underlinedCharacterRun)
+{
   for(auto&& attribute : tag.attributes)
   {
     if(TokenComparison(MARKUP::ANCHOR_ATTRIBUTES::HREF, attribute.nameBuffer, attribute.nameLength))
@@ -47,6 +51,15 @@ void ProcessAnchor(const Tag& tag, Anchor& anchor)
       anchor.href[hrefLength - 1] = '\0';
       // The memory is freed when the font run is removed from the logical model.
     }
+    else if(TokenComparison(MARKUP::ANCHOR_ATTRIBUTES::COLOR, attribute.nameBuffer, attribute.nameLength))
+    {
+      ProcessColor(attribute, colorRun);
+      ProcessColorAttribute(attribute, underlinedCharacterRun);
+    }
+    else if(TokenComparison(MARKUP::ANCHOR_ATTRIBUTES::CLICKED_COLOR, attribute.nameBuffer, attribute.nameLength))
+    {
+      ColorStringToVector4(attribute.valueBuffer, attribute.valueLength, anchor.clickedColor);
+    }
   }
 }
 
index 5559e0a..1fca0c0 100644 (file)
@@ -18,6 +18,9 @@
  *
  */
 
+#include <dali-toolkit/internal/text/color-run.h>
+#include <dali-toolkit/internal/text/underlined-character-run.h>
+
 namespace Dali
 {
 
@@ -35,8 +38,13 @@ struct Anchor;
  *
  * @param[in] tag The anchor tag and its attributes.
  * @param[in,out] anchor The anchor.
+ * @param[out] colorRun the color run to be filled.
+ * @param[out] underlinedCharacterRun the underlined character run to be filled.
  */
-void ProcessAnchor( const Tag& tag, Anchor& anchor );
+void ProcessAnchorTag(const Tag&              tag,
+                      Anchor&                 anchor,
+                      ColorRun&               colorRun,
+                      UnderlinedCharacterRun& underlinedCharacterRun);
 
 } // namespace Text
 
index b19cdb1..67037e4 100644 (file)
@@ -139,6 +139,15 @@ struct Span
 };
 
 /**
+ * @brief Struct used to retrieve anchors from the mark-up string.
+ */
+struct AnchorForStack
+{
+  RunIndex colorRunIndex;
+  RunIndex underlinedCharacterRunIndex;
+};
+
+/**
  * @brief Initializes a font run description to its defaults.
  *
  * @param[in,out] fontRun The font description run to initialize.
@@ -210,6 +219,17 @@ void Initialize(Span& span)
 }
 
 /**
+ * @brief Initializes a anchor to its defaults.
+ *
+ * @param[in,out] anchor The anchor to be initialized.
+ */
+void Initialize(AnchorForStack& anchor)
+{
+  anchor.colorRunIndex               = 0u;
+  anchor.underlinedCharacterRunIndex = 0u;
+}
+
+/**
  * @brief Initializes a strikethrough character run to its defaults.
  *
  * @param[in,out] strikethroughCharacterRun The strikethrough character run to initialize.
@@ -695,38 +715,6 @@ void ProcessParagraphTag(
 }
 
 /**
- * @brief Processes the anchor tag
- *
- * @param[in/out] markupProcessData The markup process data
- * @param[in] tag The current tag
- * @param[in/out] characterIndex The current character index
- */
-void ProcessAnchorTag(
-  MarkupProcessData& markupProcessData,
-  const Tag          tag,
-  CharacterIndex&    characterIndex)
-{
-  if(!tag.isEndTag)
-  {
-    // Create an anchor instance.
-    Anchor anchor;
-    anchor.startIndex = characterIndex;
-    anchor.endIndex   = 0u;
-    ProcessAnchor(tag, anchor);
-    markupProcessData.anchors.PushBack(anchor);
-  }
-  else
-  {
-    // Update end index.
-    unsigned int count = markupProcessData.anchors.Count();
-    if(count > 0)
-    {
-      markupProcessData.anchors[count - 1].endIndex = characterIndex;
-    }
-  }
-}
-
-/**
  * @brief Processes span tag for the color-run & font-run.
  *
  * @param[in] spanTag The tag we are currently processing
@@ -911,6 +899,105 @@ void ProcessSpanForRun(
 }
 
 /**
+ * @brief Processes anchor tag for the color-run & underline-run.
+ *
+ * @param[in,out] markupProcessData The markup process data
+ * @param[in] tag The tag we are currently processing
+ * @param[in,out] anchorStack The anchors stack
+ * @param[in,out] colorRuns The container containing all the color runs
+ * @param[in,out] underlinedCharacterRuns The container containing all the underlined character runs
+ * @param[in,out] colorRunIndex The color run index
+ * @param[in,out] underlinedCharacterRunIndex The underlined character run index
+ * @param[in] characterIndex The current character index
+ * @param[in] tagReference The tagReference we should increment/decrement
+ */
+void ProcessAnchorForRun(
+  MarkupProcessData&                    markupProcessData,
+  const Tag&                            tag,
+  StyleStack<AnchorForStack>&           anchorStack,
+  Vector<ColorRun>&                     colorRuns,
+  Vector<UnderlinedCharacterRun>&       underlinedCharacterRuns,
+  RunIndex&                             colorRunIndex,
+  RunIndex&                             underlinedCharacterRunIndex,
+  const CharacterIndex                  characterIndex,
+  int&                                  tagReference)
+{
+  if(!tag.isEndTag)
+  {
+    // Create an anchor instance.
+    Anchor anchor;
+    anchor.href                        = nullptr;
+    anchor.startIndex                  = characterIndex;
+    anchor.endIndex                    = 0u;
+    anchor.colorRunIndex               = colorRunIndex;
+    anchor.underlinedCharacterRunIndex = underlinedCharacterRunIndex;
+
+    // Create a new run.
+    ColorRun colorRun;
+    Initialize(colorRun);
+
+    UnderlinedCharacterRun underlinedCharacterRun;
+    Initialize(underlinedCharacterRun);
+
+    AnchorForStack anchorForStack;
+    Initialize(anchorForStack);
+
+    // Fill the run with the parameters.
+    colorRun.characterRun.characterIndex               = characterIndex;
+    underlinedCharacterRun.characterRun.characterIndex = characterIndex;
+
+    anchorForStack.colorRunIndex               = colorRunIndex;
+    anchorForStack.underlinedCharacterRunIndex = underlinedCharacterRunIndex;
+
+    // Init default color
+    colorRun.color                                 = Color::MEDIUM_BLUE;
+    underlinedCharacterRun.properties.color        = Color::MEDIUM_BLUE;
+    underlinedCharacterRun.properties.colorDefined = true;
+
+    ProcessAnchorTag(tag, anchor, colorRun, underlinedCharacterRun);
+
+    markupProcessData.anchors.PushBack(anchor);
+
+    // Push the anchor into the stack.
+    anchorStack.Push(anchorForStack);
+
+    // Push the run in the logical model.
+    colorRuns.PushBack(colorRun);
+    ++colorRunIndex;
+
+    // Push the run in the logical model.
+    underlinedCharacterRuns.PushBack(underlinedCharacterRun);
+    ++underlinedCharacterRunIndex;
+
+    // Increase reference
+    ++tagReference;
+  }
+  else
+  {
+    if(tagReference > 0)
+    {
+      // Update end index.
+      unsigned int count = markupProcessData.anchors.Count();
+      if(count > 0)
+      {
+        markupProcessData.anchors[count - 1].endIndex = characterIndex;
+      }
+
+      // Pop the top of the stack and set the number of characters of the run.
+      AnchorForStack anchorForStack = anchorStack.Pop();
+
+      ColorRun& colorRun                       = *(colorRuns.Begin() + anchorForStack.colorRunIndex);
+      colorRun.characterRun.numberOfCharacters = characterIndex - colorRun.characterRun.characterIndex;
+
+      UnderlinedCharacterRun& underlinedCharacterRun         = *(underlinedCharacterRuns.Begin() + anchorForStack.underlinedCharacterRunIndex);
+      underlinedCharacterRun.characterRun.numberOfCharacters = characterIndex - underlinedCharacterRun.characterRun.characterIndex;
+
+      --tagReference;
+    }
+  }
+}
+
+/**
  * @brief Resizes the model's vectors
  *
  * @param[inout] markupProcessData The markup process data
@@ -1060,6 +1147,8 @@ void ProcessMarkupString(const std::string& markupString, MarkupProcessData& mar
   // Stores a struct with the index to the first character of the color run & color font for the span.
   StyleStack<Span> spanStack;
 
+  StyleStack<AnchorForStack> anchorStack;
+
   // Points the next free position in the vector of runs.
   RunIndex colorRunIndex                     = 0u;
   RunIndex fontRunIndex                      = 0u;
@@ -1080,6 +1169,7 @@ void ProcessMarkupString(const std::string& markupString, MarkupProcessData& mar
   int sTagReference                = 0u;
   int pTagReference                = 0u;
   int characterSpacingTagReference = 0u;
+  int aTagReference                = 0u;
 
   // Give an initial default value to the model's vectors.
   markupProcessData.colorRuns.Reserve(DEFAULT_VECTOR_SIZE);
@@ -1135,21 +1225,15 @@ void ProcessMarkupString(const std::string& markupString, MarkupProcessData& mar
       } // <font></font>
       else if(TokenComparison(MARKUP::TAG::ANCHOR, tag.buffer, tag.length))
       {
-        /* Anchor */
-        ProcessAnchorTag(markupProcessData, tag, characterIndex);
-        /* Color */
-        ProcessTagForRun<ColorRun>(
-          markupProcessData.colorRuns, styleStack, tag, characterIndex, colorRunIndex, colorTagReference, [](const Tag& tag, ColorRun& run) {
-            run.color = Color::BLUE;
-            ProcessColorTag(tag, run);
-          });
-        /* Underline */
-        ProcessTagForRun<UnderlinedCharacterRun>(
-          markupProcessData.underlinedCharacterRuns, styleStack, tag, characterIndex, underlinedCharacterRunIndex, uTagReference, [](const Tag& tag, UnderlinedCharacterRun& run) {
-            run.properties.color        = Color::BLUE;
-            run.properties.colorDefined = true;
-            ProcessUnderlineTag(tag, run);
-          });
+        ProcessAnchorForRun(markupProcessData,
+                            tag,
+                            anchorStack,
+                            markupProcessData.colorRuns,
+                            markupProcessData.underlinedCharacterRuns,
+                            colorRunIndex,
+                            underlinedCharacterRunIndex,
+                            characterIndex,
+                            aTagReference);
       } // <a href=https://www.tizen.org>tizen</a>
       else if(TokenComparison(MARKUP::TAG::SHADOW, tag.buffer, tag.length))
       {
index 0f75d6a..0cc21c0 100644 (file)
@@ -706,6 +706,16 @@ namespace ANCHOR_ATTRIBUTES
  */
 static const std::string HREF("href");
 
+/**
+ * @brief Sets the color for the characters and underlines inside the element.
+ */
+static const std::string COLOR("color");
+
+/**
+ * @brief Sets the clicked color for the characters and underlines inside the element.
+ */
+static const std::string CLICKED_COLOR("clicked-color");
+
 } // namespace ANCHOR_ATTRIBUTES
 
 } // namespace MARKUP