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