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