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