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