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