Merge "Add underline to anchor in the markup processor" into devel/master
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / markup-processor.cpp
1 /*
2  * Copyright (c) 2022 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // FILE HEADER
19 #include <dali-toolkit/internal/text/markup-processor.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/integration-api/debug.h>
23 #include <climits> // for ULONG_MAX
24 #include <functional>
25
26 // INTERNAL INCLUDES
27 #include <dali-toolkit/internal/text/character-set-conversion.h>
28 #include <dali-toolkit/internal/text/markup-processor-anchor.h>
29 #include <dali-toolkit/internal/text/markup-processor-background.h>
30 #include <dali-toolkit/internal/text/markup-processor-color.h>
31 #include <dali-toolkit/internal/text/markup-processor-embedded-item.h>
32 #include <dali-toolkit/internal/text/markup-processor-font.h>
33 #include <dali-toolkit/internal/text/markup-processor-helper-functions.h>
34 #include <dali-toolkit/internal/text/markup-processor-span.h>
35 #include <dali-toolkit/internal/text/markup-processor-strikethrough.h>
36 #include <dali-toolkit/internal/text/markup-processor-underline.h>
37 #include <dali-toolkit/internal/text/xhtml-entities.h>
38
39 namespace Dali
40 {
41 namespace Toolkit
42 {
43 namespace Text
44 {
45 namespace
46 {
47 // HTML-ISH tag and attribute constants.
48 // Note they must be lower case in order to make the comparison to work
49 // as the parser converts all the read tags to lower case.
50 const std::string XHTML_COLOR_TAG("color");
51 const std::string XHTML_FONT_TAG("font");
52 const std::string XHTML_B_TAG("b");
53 const std::string XHTML_I_TAG("i");
54 const std::string XHTML_U_TAG("u");
55 const std::string XHTML_SHADOW_TAG("shadow");
56 const std::string XHTML_GLOW_TAG("glow");
57 const std::string XHTML_OUTLINE_TAG("outline");
58 const std::string XHTML_ITEM_TAG("item");
59 const std::string XHTML_ANCHOR_TAG("a");
60 const std::string XHTML_BACKGROUND_TAG("background");
61 const std::string XHTML_SPAN_TAG("span");
62 const std::string XHTML_STRIKETHROUGH_TAG("s");
63
64 const char LESS_THAN      = '<';
65 const char GREATER_THAN   = '>';
66 const char EQUAL          = '=';
67 const char QUOTATION_MARK = '\'';
68 const char SLASH          = '/';
69 const char BACK_SLASH     = '\\';
70 const char AMPERSAND      = '&';
71 const char HASH           = '#';
72 const char SEMI_COLON     = ';';
73 const char CHAR_ARRAY_END = '\0';
74 const char HEX_CODE       = 'x';
75
76 const char WHITE_SPACE = 0x20; // ASCII value of the white space.
77
78 // Range 1 0x0u < XHTML_DECIMAL_ENTITY_RANGE <= 0xD7FFu
79 // Range 2 0xE000u < XHTML_DECIMAL_ENTITY_RANGE <= 0xFFFDu
80 // Range 3 0x10000u < XHTML_DECIMAL_ENTITY_RANGE <= 0x10FFFFu
81 const unsigned long XHTML_DECIMAL_ENTITY_RANGE[] = {0x0u, 0xD7FFu, 0xE000u, 0xFFFDu, 0x10000u, 0x10FFFFu};
82
83 const unsigned int MAX_NUM_OF_ATTRIBUTES = 5u;  ///< The font tag has the 'family', 'size' 'weight', 'width' and 'slant' attrubutes.
84 const unsigned int DEFAULT_VECTOR_SIZE   = 16u; ///< Default size of run vectors.
85
86 #if defined(DEBUG_ENABLED)
87 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_MARKUP_PROCESSOR");
88 #endif
89
90 typedef VectorBase::SizeType RunIndex;
91
92 /**
93  * @brief Struct used to retrieve the style runs from the mark-up string.
94  */
95 template<typename StyleStackType>
96 struct StyleStack
97 {
98   Vector<StyleStackType> stack;    ///< Use a vector as a style stack.
99   unsigned int           topIndex; ///< Points the top of the stack.
100
101   StyleStack()
102   : stack(),
103     topIndex(0u)
104   {
105     stack.Resize(DEFAULT_VECTOR_SIZE);
106   }
107
108   void Push(StyleStackType item)
109   {
110     // Check if there is space inside the style stack.
111     const VectorBase::SizeType size = stack.Count();
112     if(topIndex >= size)
113     {
114       // Resize the style stack.
115       stack.Resize(2u * size);
116     }
117
118     // Set the item in the top of the stack.
119     *(stack.Begin() + topIndex) = item;
120
121     // Reposition the pointer to the top of the stack.
122     ++topIndex;
123   }
124
125   StyleStackType Pop()
126   {
127     // Pop the top of the stack.
128     --topIndex;
129     return *(stack.Begin() + topIndex);
130   }
131 };
132
133 /**
134  * @brief Struct used to retrieve spans from the mark-up string.
135  */
136 struct Span
137 {
138   RunIndex colorRunIndex;
139   RunIndex fontRunIndex;
140   bool     isColorDefined;
141   bool     isFontDefined;
142 };
143
144 /**
145  * @brief Initializes a font run description to its defaults.
146  *
147  * @param[in,out] fontRun The font description run to initialize.
148  */
149 void Initialize(FontDescriptionRun& fontRun)
150 {
151   fontRun.characterRun.characterIndex     = 0u;
152   fontRun.characterRun.numberOfCharacters = 0u;
153   fontRun.familyName                      = NULL;
154   fontRun.familyLength                    = 0u;
155   fontRun.weight                          = TextAbstraction::FontWeight::NORMAL;
156   fontRun.width                           = TextAbstraction::FontWidth::NORMAL;
157   fontRun.slant                           = TextAbstraction::FontSlant::NORMAL;
158   fontRun.size                            = 0u;
159   fontRun.familyDefined                   = false;
160   fontRun.weightDefined                   = false;
161   fontRun.widthDefined                    = false;
162   fontRun.slantDefined                    = false;
163   fontRun.sizeDefined                     = false;
164 }
165
166 /**
167  * @brief Initializes a color run description to its defaults.
168  *
169  * @param[in,out] colorRun The font description run to initialize.
170  */
171 void Initialize(ColorRun& colorRun)
172 {
173   colorRun.characterRun.characterIndex     = 0u;
174   colorRun.characterRun.numberOfCharacters = 0u;
175 }
176
177 /**
178  * @brief Initializes a underlined character run to its defaults.
179  *
180  * @param[in,out] underlinedCharacterRun The underelined character run to initialize.
181  */
182 void Initialize(UnderlinedCharacterRun& underlinedCharacterRun)
183 {
184   underlinedCharacterRun.characterRun.characterIndex     = 0u;
185   underlinedCharacterRun.characterRun.numberOfCharacters = 0u;
186 }
187
188 /**
189  * @brief Initializes a span to its defaults.
190  *
191  * @param[in,out] span The span to be initialized.
192  */
193 void Initialize(Span& span)
194 {
195   span.colorRunIndex  = 0u;
196   span.isColorDefined = false;
197   span.fontRunIndex   = 0u;
198   span.isFontDefined  = false;
199 }
200
201 /**
202  * @brief Initializes a strikethrough character run to its defaults.
203  *
204  * @param[in,out] strikethroughCharacterRun The strikethrough character run to initialize.
205  */
206 void Initialize(StrikethroughCharacterRun& strikethroughCharacterRun)
207 {
208   strikethroughCharacterRun.characterRun.characterIndex     = 0u;
209   strikethroughCharacterRun.characterRun.numberOfCharacters = 0u;
210   strikethroughCharacterRun.isColorSet                      = false;
211 }
212
213 /**
214  * @brief Splits the tag string into the tag name and its attributes.
215  *
216  * The attributes are stored in a vector in the tag.
217  *
218  * @param[in,out] tag The tag.
219  */
220 void ParseAttributes(Tag& tag)
221 {
222   if(tag.buffer == NULL)
223   {
224     return;
225   }
226
227   tag.attributes.Resize(MAX_NUM_OF_ATTRIBUTES);
228
229   // Find first the tag name.
230   bool isQuotationOpen = false;
231
232   const char*       tagBuffer    = tag.buffer;
233   const char* const tagEndBuffer = tagBuffer + tag.length;
234   tag.length                     = 0u;
235   for(; tagBuffer < tagEndBuffer; ++tagBuffer)
236   {
237     const char character = *tagBuffer;
238     if(WHITE_SPACE < character)
239     {
240       ++tag.length;
241     }
242     else
243     {
244       // Stops counting the length of the tag when a white space is found.
245       // @note a white space is the WHITE_SPACE character and anything below as 'tab', 'return' or 'control characters'.
246       break;
247     }
248   }
249   SkipWhiteSpace(tagBuffer, tagEndBuffer);
250
251   // Find the attributes.
252   unsigned int attributeIndex = 0u;
253   const char*  nameBuffer     = NULL;
254   const char*  valueBuffer    = NULL;
255   Length       nameLength     = 0u;
256   Length       valueLength    = 0u;
257
258   bool   addToNameValue     = true;
259   Length numberOfWhiteSpace = 0u;
260   for(; tagBuffer < tagEndBuffer; ++tagBuffer)
261   {
262     const char character = *tagBuffer;
263     if((WHITE_SPACE >= character) && !isQuotationOpen)
264     {
265       if(NULL != valueBuffer)
266       {
267         // Remove white spaces at the end of the value.
268         valueLength -= numberOfWhiteSpace;
269       }
270
271       if((NULL != nameBuffer) && (NULL != valueBuffer))
272       {
273         // Every time a white space is found, a new attribute is created and stored in the attributes vector.
274         Attribute& attribute = *(tag.attributes.Begin() + attributeIndex);
275         ++attributeIndex;
276
277         attribute.nameBuffer  = nameBuffer;
278         attribute.valueBuffer = valueBuffer;
279         attribute.nameLength  = nameLength;
280         attribute.valueLength = valueLength;
281
282         nameBuffer  = NULL;
283         valueBuffer = NULL;
284         nameLength  = 0u;
285         valueLength = 0u;
286
287         addToNameValue = true; // next read characters will be added to the name.
288       }
289     }
290     else if(EQUAL == character) // '='
291     {
292       addToNameValue = false; // next read characters will be added to the value.
293       SkipWhiteSpace(tagBuffer, tagEndBuffer);
294     }
295     else if(QUOTATION_MARK == character) // '\''
296     {
297       // Do not add quotation marks to neither name nor value.
298       isQuotationOpen = !isQuotationOpen;
299
300       if(isQuotationOpen)
301       {
302         ++tagBuffer;
303         SkipWhiteSpace(tagBuffer, tagEndBuffer);
304         --tagBuffer;
305       }
306     }
307     else
308     {
309       // Adds characters to the name or the value.
310       if(addToNameValue)
311       {
312         if(NULL == nameBuffer)
313         {
314           nameBuffer = tagBuffer;
315         }
316         ++nameLength;
317       }
318       else
319       {
320         if(isQuotationOpen)
321         {
322           if(WHITE_SPACE >= character)
323           {
324             ++numberOfWhiteSpace;
325           }
326           else
327           {
328             numberOfWhiteSpace = 0u;
329           }
330         }
331         if(NULL == valueBuffer)
332         {
333           valueBuffer = tagBuffer;
334         }
335         ++valueLength;
336       }
337     }
338   }
339
340   if(NULL != valueBuffer)
341   {
342     // Remove white spaces at the end of the value.
343     valueLength -= numberOfWhiteSpace;
344   }
345
346   if((NULL != nameBuffer) && (NULL != valueBuffer))
347   {
348     // Checks if the last attribute needs to be added.
349     Attribute& attribute = *(tag.attributes.Begin() + attributeIndex);
350     ++attributeIndex;
351
352     attribute.nameBuffer  = nameBuffer;
353     attribute.valueBuffer = valueBuffer;
354     attribute.nameLength  = nameLength;
355     attribute.valueLength = valueLength;
356   }
357
358   // Resize the vector of attributes.
359   tag.attributes.Resize(attributeIndex);
360 }
361
362 /**
363  * @brief It parses a tag and its attributes if the given iterator @e it is pointing at a tag beginning.
364  *
365  * @param[in,out] markupStringBuffer The mark-up string buffer. It's a const iterator pointing the current character.
366  * @param[in] markupStringEndBuffer Pointer to one character after the end of the mark-up string buffer.
367  * @param[out] tag The tag with its attributes.
368  *
369  * @return @e true if the iterator @e it is pointing a mark-up tag. Otherwise @e false.
370  */
371 bool IsTag(const char*&      markupStringBuffer,
372            const char* const markupStringEndBuffer,
373            Tag&              tag)
374 {
375   bool isTag              = false;
376   bool isQuotationOpen    = false;
377   bool attributesFound    = false;
378   tag.isEndTag            = false;
379   bool isPreviousLessThan = false;
380   bool isPreviousSlash    = false;
381
382   const char character = *markupStringBuffer;
383   if(LESS_THAN == character) // '<'
384   {
385     tag.buffer         = NULL;
386     tag.length         = 0u;
387     isPreviousLessThan = true;
388
389     // if the iterator is pointing to a '<' character, then check if it's a mark-up tag is needed.
390     ++markupStringBuffer;
391     if(markupStringBuffer < markupStringEndBuffer)
392     {
393       SkipWhiteSpace(markupStringBuffer, markupStringEndBuffer);
394
395       for(; (!isTag) && (markupStringBuffer < markupStringEndBuffer); ++markupStringBuffer)
396       {
397         const char character = *markupStringBuffer;
398
399         if(!isQuotationOpen && (SLASH == character)) // '/'
400         {
401           if(isPreviousLessThan)
402           {
403             tag.isEndTag = true;
404           }
405           else
406           {
407             // if the tag has a '/' it may be an end tag.
408             isPreviousSlash = true;
409           }
410
411           isPreviousLessThan = false;
412           if((markupStringBuffer + 1u < markupStringEndBuffer) && (WHITE_SPACE >= *(markupStringBuffer + 1u)))
413           {
414             ++markupStringBuffer;
415             SkipWhiteSpace(markupStringBuffer, markupStringEndBuffer);
416             --markupStringBuffer;
417           }
418         }
419         else if(GREATER_THAN == character) // '>'
420         {
421           isTag = true;
422           if(isPreviousSlash)
423           {
424             tag.isEndTag = true;
425           }
426
427           isPreviousSlash    = false;
428           isPreviousLessThan = false;
429         }
430         else if(QUOTATION_MARK == character)
431         {
432           isQuotationOpen = !isQuotationOpen;
433           ++tag.length;
434
435           isPreviousSlash    = false;
436           isPreviousLessThan = false;
437         }
438         else if(WHITE_SPACE >= character) // ' '
439         {
440           // If the tag contains white spaces then it may have attributes.
441           if(!isQuotationOpen)
442           {
443             attributesFound = true;
444           }
445           ++tag.length;
446         }
447         else
448         {
449           if(NULL == tag.buffer)
450           {
451             tag.buffer = markupStringBuffer;
452           }
453
454           // If it's not any of the 'special' characters then just add it to the tag string.
455           ++tag.length;
456
457           isPreviousSlash    = false;
458           isPreviousLessThan = false;
459         }
460       }
461     }
462
463     // If the tag string has white spaces, then parse the attributes is needed.
464     if(attributesFound)
465     {
466       ParseAttributes(tag);
467     }
468   }
469
470   return isTag;
471 }
472
473 /**
474  * @brief Returns length of XHTML entity by parsing the text. It also determines if it is XHTML entity or not.
475  *
476  * @param[in] markupStringBuffer The mark-up string buffer. It's a const iterator pointing the current character.
477  * @param[in] markupStringEndBuffer Pointing to end of mark-up string buffer.
478  *
479  * @return Length of markupText in case of XHTML entity otherwise return 0.
480  */
481 unsigned int GetXHTMLEntityLength(const char*&      markupStringBuffer,
482                                   const char* const markupStringEndBuffer)
483 {
484   char character = *markupStringBuffer;
485   if(AMPERSAND == character) // '&'
486   {
487     // if the iterator is pointing to a '&' character, then check for ';' to find end to XHTML entity.
488     ++markupStringBuffer;
489     if(markupStringBuffer < markupStringEndBuffer)
490     {
491       unsigned int len = 1u;
492       for(; markupStringBuffer < markupStringEndBuffer; ++markupStringBuffer)
493       {
494         character = *markupStringBuffer;
495         ++len;
496         if(SEMI_COLON == character) // ';'
497         {
498           // found end of XHTML entity
499           ++markupStringBuffer;
500           return len;
501         }
502         else if((AMPERSAND == character) || (BACK_SLASH == character) || (LESS_THAN == character))
503         {
504           return 0;
505         }
506       }
507     }
508   }
509   return 0;
510 }
511
512 /**
513  * @brief It parses a XHTML string which has hex/decimal entity and fill its corresponging utf-8 string.
514  *
515  * @param[in] markupText The mark-up text buffer.
516  * @param[out] utf-8 text Corresponding to markup Text
517  *
518  * @return true if string is successfully parsed otherwise false
519  */
520 bool XHTMLNumericEntityToUtf8(const char* markupText, char* utf8)
521 {
522   bool result = false;
523
524   if(NULL != markupText)
525   {
526     bool isHex = false;
527
528     // check if hex or decimal entity
529     if((CHAR_ARRAY_END != *markupText) && (HEX_CODE == *markupText))
530     {
531       isHex = true;
532       ++markupText;
533     }
534
535     char*         end = NULL;
536     unsigned long l   = strtoul(markupText, &end, (isHex ? 16 : 10)); // l contains UTF-32 code in case of correct XHTML entity
537
538     // check for valid XHTML numeric entities (between '#' or "#x" and ';')
539     if((l > 0) && (l < ULONG_MAX) && (*end == SEMI_COLON)) // in case wrong XHTML entity is set eg. "&#23abcdefs;" in that case *end will be 'a'
540     {
541       /* characters XML 1.1 permits */
542       if(((XHTML_DECIMAL_ENTITY_RANGE[0] < l) && (l <= XHTML_DECIMAL_ENTITY_RANGE[1])) ||
543          ((XHTML_DECIMAL_ENTITY_RANGE[2] <= l) && (l <= XHTML_DECIMAL_ENTITY_RANGE[3])) ||
544          ((XHTML_DECIMAL_ENTITY_RANGE[4] <= l) && (l <= XHTML_DECIMAL_ENTITY_RANGE[5])))
545       {
546         // Convert UTF32 code to UTF8
547         Utf32ToUtf8(reinterpret_cast<const uint32_t* const>(&l), 1, reinterpret_cast<uint8_t*>(utf8));
548         result = true;
549       }
550     }
551   }
552   return result;
553 }
554
555 /**
556  * @brief Processes a particular tag for the required run (color-run, font-run or underlined-character-run).
557  *
558  * @tparam RunType Whether ColorRun , FontDescriptionRun or UnderlinedCharacterRun
559  *
560  * @param[in/out] runsContainer The container containing all the runs
561  * @param[in/out] styleStack The style stack
562  * @param[in] tag The tag we are currently processing
563  * @param[in] characterIndex The current character index
564  * @param[in/out] runIndex The run index
565  * @param[in/out] tagReference The tagReference we should increment/decrement
566  * @param[in] parameterSettingFunction This function will be called to set run specific parameters
567  */
568 template<typename RunType>
569 void ProcessTagForRun(
570   Vector<RunType>&                          runsContainer,
571   StyleStack<RunIndex>&                     styleStack,
572   const Tag&                                tag,
573   const CharacterIndex                      characterIndex,
574   RunIndex&                                 runIndex,
575   int&                                      tagReference,
576   std::function<void(const Tag&, RunType&)> parameterSettingFunction)
577 {
578   if(!tag.isEndTag)
579   {
580     // Create a new run.
581     RunType run;
582     Initialize(run);
583
584     // Fill the run with the parameters.
585     run.characterRun.characterIndex = characterIndex;
586     parameterSettingFunction(tag, run);
587
588     // Push the run in the logical model.
589     runsContainer.PushBack(run);
590
591     // Push the index of the run into the stack.
592     styleStack.Push(runIndex);
593
594     // Point the next free run.
595     ++runIndex;
596
597     // Increase reference
598     ++tagReference;
599   }
600   else
601   {
602     if(tagReference > 0)
603     {
604       // Pop the top of the stack and set the number of characters of the run.
605       RunType& run                        = *(runsContainer.Begin() + styleStack.Pop());
606       run.characterRun.numberOfCharacters = characterIndex - run.characterRun.characterIndex;
607       --tagReference;
608     }
609   }
610 }
611
612 /**
613  * @brief Processes the item tag
614  *
615  * @param[in/out] markupProcessData The markup process data
616  * @param[in] tag The current tag
617  * @param[in/out] characterIndex The current character index
618  */
619 void ProcessItemTag(
620   MarkupProcessData& markupProcessData,
621   const Tag          tag,
622   CharacterIndex&    characterIndex)
623 {
624   if(tag.isEndTag)
625   {
626     // Create an embedded item instance.
627     EmbeddedItem item;
628     item.characterIndex = characterIndex;
629     ProcessEmbeddedItem(tag, item);
630
631     markupProcessData.items.PushBack(item);
632
633     // Insert white space character that will be replaced by the item.
634     markupProcessData.markupProcessedText.append(1u, WHITE_SPACE);
635     ++characterIndex;
636   }
637 }
638
639 /**
640  * @brief Processes the anchor tag
641  *
642  * @param[in/out] markupProcessData The markup process data
643  * @param[in] tag The current tag
644  * @param[in/out] characterIndex The current character index
645  */
646 void ProcessAnchorTag(
647   MarkupProcessData& markupProcessData,
648   const Tag          tag,
649   CharacterIndex&    characterIndex)
650 {
651   if(!tag.isEndTag)
652   {
653     // Create an anchor instance.
654     Anchor anchor;
655     anchor.startIndex = characterIndex;
656     anchor.endIndex   = 0u;
657     ProcessAnchor(tag, anchor);
658     markupProcessData.anchors.PushBack(anchor);
659   }
660   else
661   {
662     // Update end index.
663     unsigned int count = markupProcessData.anchors.Count();
664     if(count > 0)
665     {
666       markupProcessData.anchors[count - 1].endIndex = characterIndex;
667     }
668   }
669 }
670
671 /**
672  * @brief Processes span tag for the color-run & font-run.
673  *
674  * @param[in] spanTag The tag we are currently processing
675  * @param[in/out] spanStack The spans stack
676  * @param[int/out] colorRuns The container containing all the color runs
677  * @param[int/out] fontRuns The container containing all the font description runs
678  * @param[in/out] colorRunIndex The color run index
679  * @param[in/out] fontRunIndex The font run index
680  * @param[in] characterIndex The current character index
681  * @param[in] tagReference The tagReference we should increment/decrement
682  */
683 void ProcessSpanForRun(
684   const Tag&                  spanTag,
685   StyleStack<Span>&           spanStack,
686   Vector<ColorRun>&           colorRuns,
687   Vector<FontDescriptionRun>& fontRuns,
688   RunIndex&                   colorRunIndex,
689   RunIndex&                   fontRunIndex,
690   const CharacterIndex        characterIndex,
691   int&                        tagReference)
692 {
693   if(!spanTag.isEndTag)
694   {
695     // Create a new run.
696     ColorRun colorRun;
697     Initialize(colorRun);
698
699     FontDescriptionRun fontRun;
700     Initialize(fontRun);
701
702     Span span;
703     Initialize(span);
704
705     // Fill the run with the parameters.
706     colorRun.characterRun.characterIndex = characterIndex;
707     fontRun.characterRun.characterIndex  = characterIndex;
708
709     span.colorRunIndex = colorRunIndex;
710     span.fontRunIndex  = fontRunIndex;
711
712     ProcessSpanTag(spanTag, colorRun, fontRun, span.isColorDefined, span.isFontDefined);
713
714     // Push the span into the stack.
715     spanStack.Push(span);
716
717     // Point the next free run.
718     if(span.isColorDefined)
719     {
720       // Push the run in the logical model.
721       colorRuns.PushBack(colorRun);
722       ++colorRunIndex;
723     }
724
725     if(span.isFontDefined)
726     {
727       // Push the run in the logical model.
728       fontRuns.PushBack(fontRun);
729       ++fontRunIndex;
730     }
731
732     // Increase reference
733     ++tagReference;
734   }
735   else
736   {
737     if(tagReference > 0)
738     {
739       // Pop the top of the stack and set the number of characters of the run.
740       Span span = spanStack.Pop();
741
742       if(span.isColorDefined)
743       {
744         ColorRun& colorRun                       = *(colorRuns.Begin() + span.colorRunIndex);
745         colorRun.characterRun.numberOfCharacters = characterIndex - colorRun.characterRun.characterIndex;
746       }
747
748       if(span.isFontDefined)
749       {
750         FontDescriptionRun& fontRun             = *(fontRuns.Begin() + span.fontRunIndex);
751         fontRun.characterRun.numberOfCharacters = characterIndex - fontRun.characterRun.characterIndex;
752       }
753
754       --tagReference;
755     }
756   }
757 }
758
759 /**
760  * @brief Resizes the model's vectors
761  *
762  * @param[in/out] markupProcessData The markup process data
763  * @param[in] fontRunIndex The font run index
764  * @param[in] colorRunIndex The color run index
765  * @param[in] underlinedCharacterRunIndex The underlined character run index
766  * @param[in] backgroundRunIndex The background run index
767  */
768 void ResizeModelVectors(MarkupProcessData& markupProcessData, const RunIndex fontRunIndex, const RunIndex colorRunIndex, const RunIndex underlinedCharacterRunIndex, const RunIndex backgroundRunIndex)
769 {
770   markupProcessData.fontRuns.Resize(fontRunIndex);
771   markupProcessData.colorRuns.Resize(colorRunIndex);
772   markupProcessData.underlinedCharacterRuns.Resize(underlinedCharacterRunIndex);
773   markupProcessData.backgroundColorRuns.Resize(backgroundRunIndex);
774
775 #ifdef DEBUG_ENABLED
776   for(unsigned int i = 0; i < colorRunIndex; ++i)
777   {
778     ColorRun& run = markupProcessData.colorRuns[i];
779     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "run[%d] index: %d, length: %d, color %f,%f,%f,%f\n", i, run.characterRun.characterIndex, run.characterRun.numberOfCharacters, run.color.r, run.color.g, run.color.b, run.color.a);
780   }
781 #endif
782 }
783
784 /**
785  * @brief Processes the markup string buffer
786  *
787  * @param[in/out] markupProcessData The markup process data
788  * @param[in/out] markupStringBuffer The markup string buffer pointer
789  * @param[in] markupStringEndBuffer The markup string end buffer pointer
790  * @param[in/out] characterIndex The current character index
791  */
792 void ProcessMarkupStringBuffer(
793   MarkupProcessData& markupProcessData,
794   const char*&       markupStringBuffer,
795   const char* const  markupStringEndBuffer,
796   CharacterIndex&    characterIndex)
797 {
798   unsigned char character    = *markupStringBuffer;
799   const char*   markupBuffer = markupStringBuffer;
800   unsigned char count        = GetUtf8Length(character);
801   char          utf8[8];
802
803   if((BACK_SLASH == character) && (markupStringBuffer + 1u < markupStringEndBuffer))
804   {
805     // Adding < , >  or & special character.
806     const unsigned char nextCharacter = *(markupStringBuffer + 1u);
807     if((LESS_THAN == nextCharacter) || (GREATER_THAN == nextCharacter) || (AMPERSAND == nextCharacter))
808     {
809       character = nextCharacter;
810       ++markupStringBuffer;
811
812       count        = GetUtf8Length(character);
813       markupBuffer = markupStringBuffer;
814     }
815   }
816   else // checking if contains XHTML entity or not
817   {
818     const unsigned int len = GetXHTMLEntityLength(markupStringBuffer, markupStringEndBuffer);
819
820     // Parse markupStringTxt if it contains XHTML Entity between '&' and ';'
821     if(len > 0)
822     {
823       char* entityCode = NULL;
824       bool  result     = false;
825       count            = 0;
826
827       // Checking if XHTML Numeric Entity
828       if(HASH == *(markupBuffer + 1u))
829       {
830         entityCode = &utf8[0];
831         // markupBuffer is currently pointing to '&'. By adding 2u to markupBuffer it will point to numeric string by skipping "&#'
832         result = XHTMLNumericEntityToUtf8((markupBuffer + 2u), entityCode);
833       }
834       else // Checking if XHTML Named Entity
835       {
836         entityCode = const_cast<char*>(NamedEntityToUtf8(markupBuffer, len));
837         result     = (entityCode != NULL);
838       }
839       if(result)
840       {
841         markupBuffer = entityCode; //utf8 text assigned to markupBuffer
842         character    = markupBuffer[0];
843       }
844       else
845       {
846         DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Not valid XHTML entity : (%.*s) \n", len, markupBuffer);
847         markupBuffer = NULL;
848       }
849     }
850     else // in case string conatins Start of XHTML Entity('&') but not its end character(';')
851     {
852       if(character == AMPERSAND)
853       {
854         markupBuffer = NULL;
855         DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Not Well formed XHTML content \n");
856       }
857     }
858   }
859
860   if(markupBuffer != NULL)
861   {
862     const unsigned char numberOfBytes = GetUtf8Length(character);
863     markupProcessData.markupProcessedText.push_back(character);
864
865     for(unsigned char i = 1u; i < numberOfBytes; ++i)
866     {
867       ++markupBuffer;
868       markupProcessData.markupProcessedText.push_back(*markupBuffer);
869     }
870
871     ++characterIndex;
872     markupStringBuffer += count;
873   }
874 }
875
876 } // namespace
877
878 void ProcessMarkupString(const std::string& markupString, MarkupProcessData& markupProcessData)
879 {
880   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "markupString: %s\n", markupString.c_str());
881
882   // Reserve space for the plain text.
883   const Length markupStringSize = markupString.size();
884   markupProcessData.markupProcessedText.reserve(markupStringSize);
885
886   // Stores a struct with the index to the first character of the run, the type of run and its parameters.
887   StyleStack<RunIndex> styleStack;
888
889   // Stores a struct with the index to the first character of the color run & color font for the span.
890   StyleStack<Span> spanStack;
891
892   // Points the next free position in the vector of runs.
893   RunIndex colorRunIndex                  = 0u;
894   RunIndex fontRunIndex                   = 0u;
895   RunIndex underlinedCharacterRunIndex    = 0u;
896   RunIndex backgroundRunIndex             = 0u;
897   RunIndex strikethroughCharacterRunIndex = 0u;
898
899   // check tag reference
900   int colorTagReference      = 0u;
901   int fontTagReference       = 0u;
902   int iTagReference          = 0u;
903   int bTagReference          = 0u;
904   int uTagReference          = 0u;
905   int backgroundTagReference = 0u;
906   int spanTagReference       = 0u;
907   int sTagReference          = 0u;
908
909   // Give an initial default value to the model's vectors.
910   markupProcessData.colorRuns.Reserve(DEFAULT_VECTOR_SIZE);
911   markupProcessData.fontRuns.Reserve(DEFAULT_VECTOR_SIZE);
912   markupProcessData.underlinedCharacterRuns.Reserve(DEFAULT_VECTOR_SIZE);
913   markupProcessData.backgroundColorRuns.Reserve(DEFAULT_VECTOR_SIZE);
914
915   // Get the mark-up string buffer.
916   const char*       markupStringBuffer    = markupString.c_str();
917   const char* const markupStringEndBuffer = markupStringBuffer + markupStringSize;
918
919   Tag            tag;
920   CharacterIndex characterIndex = 0u;
921   for(; markupStringBuffer < markupStringEndBuffer;)
922   {
923     tag.attributes.Clear();
924     if(IsTag(markupStringBuffer,
925              markupStringEndBuffer,
926              tag))
927     {
928       if(TokenComparison(XHTML_COLOR_TAG, tag.buffer, tag.length))
929       {
930         ProcessTagForRun<ColorRun>(
931           markupProcessData.colorRuns, styleStack, tag, characterIndex, colorRunIndex, colorTagReference, [](const Tag& tag, ColorRun& run) { ProcessColorTag(tag, run); });
932       } // <color></color>
933       else if(TokenComparison(XHTML_I_TAG, tag.buffer, tag.length))
934       {
935         ProcessTagForRun<FontDescriptionRun>(
936           markupProcessData.fontRuns, styleStack, tag, characterIndex, fontRunIndex, iTagReference, [](const Tag&, FontDescriptionRun& fontRun) {
937             fontRun.slant        = TextAbstraction::FontSlant::ITALIC;
938             fontRun.slantDefined = true;
939           });
940       } // <i></i>
941       else if(TokenComparison(XHTML_U_TAG, tag.buffer, tag.length))
942       {
943         ProcessTagForRun<UnderlinedCharacterRun>(
944           markupProcessData.underlinedCharacterRuns, styleStack, tag, characterIndex, underlinedCharacterRunIndex, uTagReference, [](const Tag& tag, UnderlinedCharacterRun& run) { ProcessUnderlineTag(tag, run); });
945       } // <u></u>
946       else if(TokenComparison(XHTML_B_TAG, tag.buffer, tag.length))
947       {
948         ProcessTagForRun<FontDescriptionRun>(
949           markupProcessData.fontRuns, styleStack, tag, characterIndex, fontRunIndex, bTagReference, [](const Tag&, FontDescriptionRun& fontRun) {
950             fontRun.weight        = TextAbstraction::FontWeight::BOLD;
951             fontRun.weightDefined = true;
952           });
953       } // <b></b>
954       else if(TokenComparison(XHTML_FONT_TAG, tag.buffer, tag.length))
955       {
956         ProcessTagForRun<FontDescriptionRun>(
957           markupProcessData.fontRuns, styleStack, tag, characterIndex, fontRunIndex, fontTagReference, [](const Tag& tag, FontDescriptionRun& fontRun) { ProcessFontTag(tag, fontRun); });
958       } // <font></font>
959       else if(TokenComparison(XHTML_ANCHOR_TAG, tag.buffer, tag.length))
960       {
961         /* Anchor */
962         ProcessAnchorTag(markupProcessData, tag, characterIndex);
963         /* Color */
964         ProcessTagForRun<ColorRun>(
965           markupProcessData.colorRuns, styleStack, tag, characterIndex, colorRunIndex, colorTagReference, [](const Tag& tag, ColorRun& run) {
966             run.color = Color::BLUE;
967             ProcessColorTag(tag, run);
968           });
969         /* Underline */
970         ProcessTagForRun<UnderlinedCharacterRun>(
971           markupProcessData.underlinedCharacterRuns, styleStack, tag, characterIndex, underlinedCharacterRunIndex, uTagReference, [](const Tag& tag, UnderlinedCharacterRun& run) {
972             run.properties.color = Color::BLUE;
973             run.properties.colorDefined = true;
974             ProcessUnderlineTag(tag, run);
975           });
976       } // <a href=https://www.tizen.org>tizen</a>
977       else if(TokenComparison(XHTML_SHADOW_TAG, tag.buffer, tag.length))
978       {
979         // TODO: If !tag.isEndTag, then create a new shadow run.
980         //       else Pop the top of the stack and set the number of characters of the run.
981       } // <shadow></shadow>
982       else if(TokenComparison(XHTML_GLOW_TAG, tag.buffer, tag.length))
983       {
984         // TODO: If !tag.isEndTag, then create a new glow run.
985         //       else Pop the top of the stack and set the number of characters of the run.
986       } // <glow></glow>
987       else if(TokenComparison(XHTML_OUTLINE_TAG, tag.buffer, tag.length))
988       {
989         // TODO: If !tag.isEndTag, then create a new outline run.
990         //       else Pop the top of the stack and set the number of characters of the run.
991       } // <outline></outline>
992       else if(TokenComparison(XHTML_ITEM_TAG, tag.buffer, tag.length))
993       {
994         ProcessItemTag(markupProcessData, tag, characterIndex);
995       }
996       else if(TokenComparison(XHTML_BACKGROUND_TAG, tag.buffer, tag.length))
997       {
998         ProcessTagForRun<ColorRun>(
999           markupProcessData.backgroundColorRuns, styleStack, tag, characterIndex, backgroundRunIndex, backgroundTagReference, [](const Tag& tag, ColorRun& run) { ProcessBackground(tag, run); });
1000       }
1001       else if(TokenComparison(XHTML_SPAN_TAG, tag.buffer, tag.length))
1002       {
1003         ProcessSpanForRun(tag, spanStack, markupProcessData.colorRuns, markupProcessData.fontRuns, colorRunIndex, fontRunIndex, characterIndex, spanTagReference);
1004       }
1005       else if(TokenComparison(XHTML_STRIKETHROUGH_TAG, tag.buffer, tag.length))
1006       {
1007         ProcessTagForRun<StrikethroughCharacterRun>(
1008           markupProcessData.strikethroughCharacterRuns, styleStack, tag, characterIndex, strikethroughCharacterRunIndex, sTagReference, [](const Tag& tag, StrikethroughCharacterRun& run) { ProcessStrikethroughTag(tag, run); });
1009       } // <s></s>
1010     }   // end if( IsTag() )
1011     else if(markupStringBuffer < markupStringEndBuffer)
1012     {
1013       ProcessMarkupStringBuffer(markupProcessData, markupStringBuffer, markupStringEndBuffer, characterIndex);
1014     }
1015   }
1016
1017   // Resize the model's vectors.
1018   ResizeModelVectors(markupProcessData, fontRunIndex, colorRunIndex, underlinedCharacterRunIndex, backgroundRunIndex);
1019 }
1020
1021 } // namespace Text
1022
1023 } // namespace Toolkit
1024
1025 } // namespace Dali