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