Merge "Added New methods to cater for second step initialization" into devel/master
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / markup-processor.cpp
1 /*
2  * Copyright (c) 2015 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 // INTERNAL INCLUDES
22 #include <dali-toolkit/internal/text/character-set-conversion.h>
23 #include <dali-toolkit/internal/text/markup-processor-color.h>
24 #include <dali-toolkit/internal/text/markup-processor-font.h>
25 #include <dali-toolkit/internal/text/markup-processor-helper-functions.h>
26
27 namespace Dali
28 {
29
30 namespace Toolkit
31 {
32
33 namespace Text
34 {
35
36 namespace
37 {
38 // HTML-ISH tag and attribute constants.
39 // Note they must be lower case in order to make the comparison to work
40 // as the parser converts all the read tags to lower case.
41 const std::string XHTML_COLOR_TAG("color");
42 const std::string XHTML_FONT_TAG("font");
43 const std::string XHTML_B_TAG("b");
44 const std::string XHTML_I_TAG("i");
45 const std::string XHTML_U_TAG("u");
46 const std::string XHTML_SHADOW_TAG("shadow");
47 const std::string XHTML_GLOW_TAG("glow");
48 const std::string XHTML_OUTLINE_TAG("outline");
49
50 const char LESS_THAN      = '<';
51 const char GREATER_THAN   = '>';
52 const char EQUAL          = '=';
53 const char QUOTATION_MARK = '\'';
54 const char SLASH          = '/';
55 const char BACK_SLASH     = '\\';
56
57 const char WHITE_SPACE    = 0x20; // ASCII value of the white space.
58
59 const unsigned int MAX_NUM_OF_ATTRIBUTES =  5u; ///< The font tag has the 'family', 'size' 'weight', 'width' and 'slant' attrubutes.
60 const unsigned int DEFAULT_VECTOR_SIZE   = 16u; ///< Default size of run vectors.
61
62 /**
63  * @brief Struct used to retrieve the style runs from the mark-up string.
64  */
65 struct StyleStack
66 {
67   typedef VectorBase::SizeType RunIndex;
68
69   Vector<RunIndex>  stack;    ///< Use a vector as a style stack. Stores the indices pointing where the run is stored inside the logical model.
70   unsigned int topIndex; ///< Points the top of the stack.
71
72   StyleStack()
73   : stack(),
74     topIndex( 0u )
75   {
76     stack.Resize( DEFAULT_VECTOR_SIZE );
77   }
78
79   void Push( RunIndex index )
80   {
81     // Check if there is space inside the style stack.
82     const VectorBase::SizeType size = stack.Count();
83     if( topIndex >= size )
84     {
85       // Resize the style stack.
86       stack.Resize( 2u * size );
87     }
88
89     // Set the run index in the top of the stack.
90     *( stack.Begin() + topIndex ) = index;
91
92     // Reposition the pointer to the top of the stack.
93     ++topIndex;
94   }
95
96   RunIndex Pop()
97   {
98     // Pop the top of the stack.
99     --topIndex;
100     return *( stack.Begin() + topIndex );
101   }
102 };
103
104 /**
105  * @brief Initializes a font run description to its defaults.
106  *
107  * @param[in,out] fontRun The font description run to initialize.
108  */
109 void Initialize( FontDescriptionRun& fontRun )
110 {
111   fontRun.characterRun.characterIndex = 0u;
112   fontRun.characterRun.numberOfCharacters = 0u;
113   fontRun.familyName = NULL;
114   fontRun.familyLength = 0u;
115   fontRun.weight = TextAbstraction::FontWeight::NORMAL;
116   fontRun.width = TextAbstraction::FontWidth::NORMAL;
117   fontRun.slant = TextAbstraction::FontSlant::NORMAL;
118   fontRun.size = 0u;
119   fontRun.familyDefined = false;
120   fontRun.weightDefined = false;
121   fontRun.widthDefined = false;
122   fontRun.slantDefined = false;
123   fontRun.sizeDefined = false;
124 }
125
126 /**
127  * @brief Splits the tag string into the tag name and its attributes.
128  *
129  * The attributes are stored in a vector in the tag.
130  *
131  * @param[in,out] tag The tag.
132  */
133 void ParseAttributes( Tag& tag )
134 {
135   tag.attributes.Resize( MAX_NUM_OF_ATTRIBUTES );
136
137   // Find first the tag name.
138   bool isQuotationOpen = false;
139
140   const char* tagBuffer = tag.buffer;
141   const char* const tagEndBuffer = tagBuffer + tag.length;
142   tag.length = 0u;
143   for( ; tagBuffer < tagEndBuffer; ++tagBuffer )
144   {
145     const char character = *tagBuffer;
146     if( WHITE_SPACE < character )
147     {
148       ++tag.length;
149     }
150     else
151     {
152       // Stops counting the length of the tag when a white space is found.
153       // @note a white space is the WHITE_SPACE character and anything below as 'tab', 'return' or 'control characters'.
154       break;
155     }
156   }
157   SkipWhiteSpace( tagBuffer, tagEndBuffer );
158
159   // Find the attributes.
160   unsigned int attributeIndex = 0u;
161   const char* nameBuffer = NULL;
162   const char* valueBuffer = NULL;
163   Length nameLength = 0u;
164   Length valueLength = 0u;
165
166   bool addToNameValue = true;
167   Length numberOfWhiteSpace = 0u;
168   for( ; tagBuffer < tagEndBuffer; ++tagBuffer )
169   {
170     const char character = *tagBuffer;
171     if( ( WHITE_SPACE >= character ) && !isQuotationOpen )
172     {
173       if( NULL != valueBuffer )
174       {
175         // Remove white spaces at the end of the value.
176         valueLength -= numberOfWhiteSpace;
177       }
178
179       if( ( NULL != nameBuffer ) && ( NULL != valueBuffer ) )
180       {
181         // Every time a white space is found, a new attribute is created and stored in the attributes vector.
182         Attribute& attribute = *( tag.attributes.Begin() + attributeIndex );
183         ++attributeIndex;
184
185         attribute.nameBuffer = nameBuffer;
186         attribute.valueBuffer = valueBuffer;
187         attribute.nameLength = nameLength;
188         attribute.valueLength = valueLength;
189
190         nameBuffer = NULL;
191         valueBuffer = NULL;
192         nameLength = 0u;
193         valueLength = 0u;
194
195         addToNameValue = true; // next read characters will be added to the name.
196       }
197     }
198     else if( EQUAL == character ) // '='
199     {
200       addToNameValue = false; // next read characters will be added to the value.
201       SkipWhiteSpace( tagBuffer, tagEndBuffer );
202     }
203     else if( QUOTATION_MARK == character ) // '\''
204     {
205       // Do not add quotation marks to neither name nor value.
206       isQuotationOpen = !isQuotationOpen;
207
208       if( isQuotationOpen )
209       {
210         ++tagBuffer;
211         SkipWhiteSpace( tagBuffer, tagEndBuffer );
212         --tagBuffer;
213       }
214     }
215     else
216     {
217       // Adds characters to the name or the value.
218       if( addToNameValue )
219       {
220         if( NULL == nameBuffer )
221         {
222           nameBuffer = tagBuffer;
223         }
224         ++nameLength;
225       }
226       else
227       {
228         if( isQuotationOpen )
229         {
230           if( WHITE_SPACE >= character )
231           {
232             ++numberOfWhiteSpace;
233           }
234           else
235           {
236             numberOfWhiteSpace = 0u;
237           }
238         }
239         if( NULL == valueBuffer )
240         {
241           valueBuffer = tagBuffer;
242         }
243         ++valueLength;
244       }
245     }
246   }
247
248   if( NULL != valueBuffer )
249   {
250     // Remove white spaces at the end of the value.
251     valueLength -= numberOfWhiteSpace;
252   }
253
254   if( ( NULL != nameBuffer ) && ( NULL != valueBuffer ) )
255   {
256     // Checks if the last attribute needs to be added.
257     Attribute& attribute = *( tag.attributes.Begin() + attributeIndex );
258     ++attributeIndex;
259
260     attribute.nameBuffer = nameBuffer;
261     attribute.valueBuffer = valueBuffer;
262     attribute.nameLength = nameLength;
263     attribute.valueLength = valueLength;
264   }
265
266   // Resize the vector of attributes.
267   tag.attributes.Resize( attributeIndex );
268 }
269
270 /**
271  * @brief It parses a tag and its attributes if the given iterator @e it is pointing at a tag beginning.
272  *
273  * @param[in,out] markupStringBuffer The mark-up string buffer. It's a const iterator pointing the current character.
274  * @param[in] markupStringEndBuffer Pointer to one character after the end of the mark-up string buffer.
275  * @param[out] tag The tag with its attributes.
276  *
277  * @return @e true if the iterator @e it is pointing a mark-up tag. Otherwise @e false.
278  */
279 bool IsTag( const char*& markupStringBuffer,
280             const char* const markupStringEndBuffer,
281             Tag& tag )
282 {
283   bool isTag = false;
284   bool isQuotationOpen = false;
285   bool attributesFound = false;
286   tag.isEndTag = false;
287
288   const char character = *markupStringBuffer;
289   if( LESS_THAN == character ) // '<'
290   {
291     tag.buffer = NULL;
292     tag.length = 0u;
293
294     // if the iterator is pointing to a '<' character, then check if it's a mark-up tag is needed.
295     ++markupStringBuffer;
296     if( markupStringBuffer < markupStringEndBuffer )
297     {
298       SkipWhiteSpace( markupStringBuffer, markupStringEndBuffer );
299
300       for( ; ( !isTag ) && ( markupStringBuffer < markupStringEndBuffer ); ++markupStringBuffer )
301       {
302         const char character = *markupStringBuffer;
303
304         if( SLASH == character ) // '/'
305         {
306           // if the tag has a '/' then it's an end or empty tag.
307           tag.isEndTag = true;
308
309           if( ( markupStringBuffer + 1u < markupStringEndBuffer ) && ( WHITE_SPACE >= *( markupStringBuffer + 1u ) ) && ( !isQuotationOpen ) )
310           {
311             ++markupStringBuffer;
312             SkipWhiteSpace( markupStringBuffer, markupStringEndBuffer );
313             --markupStringBuffer;
314           }
315         }
316         else if( GREATER_THAN == character ) // '>'
317         {
318           isTag = true;
319         }
320         else if( QUOTATION_MARK == character )
321         {
322           isQuotationOpen = !isQuotationOpen;
323           ++tag.length;
324         }
325         else if( WHITE_SPACE >= character ) // ' '
326         {
327           // If the tag contains white spaces then it may have attributes.
328           if( !isQuotationOpen )
329           {
330             attributesFound = true;
331           }
332           ++tag.length;
333         }
334         else
335         {
336           if( NULL == tag.buffer )
337           {
338             tag.buffer = markupStringBuffer;
339           }
340
341           // If it's not any of the 'special' characters then just add it to the tag string.
342           ++tag.length;
343         }
344       }
345     }
346
347     // If the tag string has white spaces, then parse the attributes is needed.
348     if( attributesFound )
349     {
350       ParseAttributes( tag );
351     }
352   }
353
354   return isTag;
355 }
356
357 } // namespace
358
359 void ProcessMarkupString( const std::string& markupString, MarkupProcessData& markupProcessData )
360 {
361   // Reserve space for the plain text.
362   const Length markupStringSize = markupString.size();
363   markupProcessData.markupProcessedText.reserve( markupStringSize );
364
365   // Stores a struct with the index to the first character of the run, the type of run and its parameters.
366   StyleStack styleStack;
367
368   // Points the next free position in the vector of runs.
369   StyleStack::RunIndex colorRunIndex = 0u;
370   StyleStack::RunIndex fontRunIndex = 0u;
371
372   // Give an initial default value to the model's vectors.
373   markupProcessData.colorRuns.Reserve( DEFAULT_VECTOR_SIZE );
374   markupProcessData.fontRuns.Reserve( DEFAULT_VECTOR_SIZE );
375
376   // Get the mark-up string buffer.
377   const char* markupStringBuffer = markupString.c_str();
378   const char* const markupStringEndBuffer = markupStringBuffer + markupStringSize;
379
380   Tag tag;
381   CharacterIndex characterIndex = 0u;
382   for( ; markupStringBuffer < markupStringEndBuffer; )
383   {
384     if( IsTag( markupStringBuffer,
385                markupStringEndBuffer,
386                tag ) )
387     {
388       if( TokenComparison( XHTML_COLOR_TAG, tag.buffer, tag.length ) )
389       {
390         if( !tag.isEndTag )
391         {
392           // Create a new color run.
393           ColorRun colorRun;
394           colorRun.characterRun.numberOfCharacters = 0u;
395
396           // Set the start character index.
397           colorRun.characterRun.characterIndex = characterIndex;
398
399           // Fill the run with the attributes.
400           ProcessColorTag( tag, colorRun );
401
402           // Push the color run in the logical model.
403           markupProcessData.colorRuns.PushBack( colorRun );
404
405           // Push the index of the run into the stack.
406           styleStack.Push( colorRunIndex );
407
408           // Point the next color run.
409           ++colorRunIndex;
410         }
411         else
412         {
413           // Pop the top of the stack and set the number of characters of the run.
414           ColorRun& colorRun = *( markupProcessData.colorRuns.Begin() + styleStack.Pop() );
415           colorRun.characterRun.numberOfCharacters = characterIndex - colorRun.characterRun.characterIndex;
416         }
417       } // <color></color>
418       else if( TokenComparison( XHTML_I_TAG, tag.buffer, tag.length ) )
419       {
420         if( !tag.isEndTag )
421         {
422           // Create a new font run.
423           FontDescriptionRun fontRun;
424           Initialize( fontRun );
425
426           // Fill the run with the parameters.
427           fontRun.characterRun.characterIndex = characterIndex;
428           fontRun.slant = TextAbstraction::FontSlant::ITALIC;
429           fontRun.slantDefined = true;
430
431           // Push the font run in the logical model.
432           markupProcessData.fontRuns.PushBack( fontRun );
433
434           // Push the index of the run into the stack.
435           styleStack.Push( fontRunIndex );
436
437           // Point the next free font run.
438           ++fontRunIndex;
439         }
440         else
441         {
442           // Pop the top of the stack and set the number of characters of the run.
443           FontDescriptionRun& fontRun = *( markupProcessData.fontRuns.Begin() + styleStack.Pop() );
444           fontRun.characterRun.numberOfCharacters = characterIndex - fontRun.characterRun.characterIndex;
445         }
446       } // <i></i>
447       else if( TokenComparison( XHTML_U_TAG, tag.buffer, tag.length ) )
448       {
449         if( !tag.isEndTag )
450         {
451           // Create a new underline run.
452         }
453         else
454         {
455           // Pop the top of the stack and set the number of characters of the run.
456         }
457       } // <u></u>
458       else if( TokenComparison( XHTML_B_TAG, tag.buffer, tag.length ) )
459       {
460         if( !tag.isEndTag )
461         {
462           // Create a new font run.
463           FontDescriptionRun fontRun;
464           Initialize( fontRun );
465
466           // Fill the run with the parameters.
467           fontRun.characterRun.characterIndex = characterIndex;
468           fontRun.weight = TextAbstraction::FontWeight::BOLD;
469           fontRun.weightDefined = true;
470
471           // Push the font run in the logical model.
472           markupProcessData.fontRuns.PushBack( fontRun );
473
474           // Push the index of the run into the stack.
475           styleStack.Push( fontRunIndex );
476
477           // Point the next free font run.
478           ++fontRunIndex;
479         }
480         else
481         {
482           // Pop the top of the stack and set the number of characters of the run.
483           FontDescriptionRun& fontRun = *( markupProcessData.fontRuns.Begin() + styleStack.Pop() );
484           fontRun.characterRun.numberOfCharacters = characterIndex - fontRun.characterRun.characterIndex;
485         }
486       } // <b></b>
487       else if( TokenComparison( XHTML_FONT_TAG, tag.buffer, tag.length ) )
488       {
489         if( !tag.isEndTag )
490         {
491           // Create a new font run.
492           FontDescriptionRun fontRun;
493           Initialize( fontRun );
494
495           // Fill the run with the parameters.
496           fontRun.characterRun.characterIndex = characterIndex;
497
498           ProcessFontTag( tag, fontRun );
499
500           // Push the font run in the logical model.
501           markupProcessData.fontRuns.PushBack( fontRun );
502
503           // Push the index of the run into the stack.
504           styleStack.Push( fontRunIndex );
505
506           // Point the next free font run.
507           ++fontRunIndex;
508         }
509         else
510         {
511           // Pop the top of the stack and set the number of characters of the run.
512           FontDescriptionRun& fontRun = *( markupProcessData.fontRuns.Begin() + styleStack.Pop() );
513           fontRun.characterRun.numberOfCharacters = characterIndex - fontRun.characterRun.characterIndex;
514         }
515       } // <font></font>
516       else if( TokenComparison( XHTML_SHADOW_TAG, tag.buffer, tag.length ) )
517       {
518         if( !tag.isEndTag )
519         {
520           // Create a new shadow run.
521         }
522         else
523         {
524           // Pop the top of the stack and set the number of characters of the run.
525         }
526       } // <shadow></shadow>
527       else if( TokenComparison( XHTML_GLOW_TAG, tag.buffer, tag.length ) )
528       {
529         if( !tag.isEndTag )
530         {
531           // Create a new glow run.
532         }
533         else
534         {
535           // Pop the top of the stack and set the number of characters of the run.
536         }
537       } // <glow></glow>
538       else if( TokenComparison( XHTML_OUTLINE_TAG, tag.buffer, tag.length ) )
539       {
540         if( !tag.isEndTag )
541         {
542           // Create a new outline run.
543         }
544         else
545         {
546           // Pop the top of the stack and set the number of characters of the run.
547         }
548       } // <outline></outline>
549     }  // end if( IsTag() )
550     else
551     {
552       unsigned char character = *markupStringBuffer;
553
554       if( ( BACK_SLASH == character ) && ( markupStringBuffer + 1u < markupStringEndBuffer ) )
555       {
556         // Adding < or > special character.
557         const unsigned char nextCharacter = *( markupStringBuffer + 1u );
558         if( ( LESS_THAN == nextCharacter ) || ( GREATER_THAN == nextCharacter ) )
559         {
560           character = nextCharacter;
561           ++markupStringBuffer;
562         }
563       }
564
565       const unsigned char numberOfBytes = GetUtf8Length( character );
566
567       markupProcessData.markupProcessedText.push_back( character );
568       for( unsigned char i = 1u; i < numberOfBytes; ++i )
569       {
570         ++markupStringBuffer;
571         markupProcessData.markupProcessedText.push_back( *markupStringBuffer );
572       }
573
574       ++characterIndex;
575       ++markupStringBuffer;
576     }
577   }
578
579   // Resize the model's vectors.
580   if( 0u == fontRunIndex )
581   {
582     markupProcessData.fontRuns.Clear();
583   }
584   else
585   {
586     markupProcessData.fontRuns.Resize( fontRunIndex );
587   }
588
589   if( 0u == colorRunIndex )
590   {
591     markupProcessData.colorRuns.Clear();
592   }
593   else
594   {
595     markupProcessData.colorRuns.Resize( colorRunIndex );
596   }
597 }
598
599 } // namespace Text
600
601 } // namespace Toolkit
602
603 } // namespace Dali