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