Release 8.0.0.15408
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Utility / TextPageUtil.cs
1 /*
2  * Copyright (c) 2020 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 using System;
19 using System.Collections.Generic;
20 using System.ComponentModel;
21 using System.IO;
22
23
24 using Tizen.NUI;
25 using Tizen.NUI.BaseComponents;
26
27 namespace Tizen.NUI.Utility
28 {
29
30   /// <summary>
31   /// This is a class for stroing the text of a page.
32   /// </summary>
33   [EditorBrowsable(EditorBrowsableState.Never)]
34   class PageData
35   {
36     public string previousTag  {get; set;}
37     public string endTag {get; set;}
38     public int startOffset {get; set;}
39     public int endOffset {get; set;}
40   }
41
42   /// <summary>
43   /// This is a class that stores information when parsing markup text.
44   /// </summary>
45   [EditorBrowsable(EditorBrowsableState.Never)]
46   class TagData
47   {
48     public string tagName {get; set;}
49     public string attributeName {get; set;}
50     public bool isEndTag {get; set;}
51   }
52
53   /// <summary>
54   /// This is utility class for paging very long text.
55   /// </summary>
56   [EditorBrowsable(EditorBrowsableState.Never)]
57   public class TextPageUtil
58   {
59     private static char LESS_THAN      = '<';
60     private static char GREATER_THAN   = '>';
61     private static char EQUAL          = '=';
62     private static char QUOTATION_MARK = '\'';
63     private static char SLASH          = '/';
64     private static char BACK_SLASH     = '\\';
65     private static char AMPERSAND      = '&';
66     private static char HASH           = '#';
67     private static char SEMI_COLON     = ';';
68     private static char CHAR_ARRAY_END = '\0';
69     private static char HEX_CODE       = 'x';
70     private static byte WHITE_SPACE    = 0x20;
71
72     private static int TEXTPAGE_TEXT_CHUNK = 20000;
73
74     private int totalPageCnt;
75
76     private List<PageData> pageList;
77     private List<TagData> tagList;
78     private StringReader stream;
79     private List<char> characterList;
80     private string pageString;
81
82     /// <summary>
83     /// When text is inputed, the text is paging in the TextLabe size unit.
84     /// <returns>The total number of pages.</returns>
85     /// </summary>
86     [EditorBrowsable(EditorBrowsableState.Never)]
87     public int SetText(TextLabel label, string str)
88     {
89       if(str == null) return 0;
90
91       // perform this operation to match the utf32 character used in native Dali.
92       bool previousMarkup = label.EnableMarkup;
93       label.EnableMarkup = false;
94       label.Text = str;
95       pageString = label.Text;
96       label.EnableMarkup = previousMarkup;
97       label.MultiLine = true;
98       label.Ellipsis = false;
99
100       int length = pageString.Length;
101       int remainLength = length;
102       int offset = 0;
103       int cutOffIndex = 0;
104
105       // init
106       pageList = new List<PageData>();
107       tagList = new List<TagData>();
108       characterList = new List<char>();
109
110       stream = new StringReader(pageString);
111
112       RendererParameters textParameters = new RendererParameters();
113       textParameters.Text = pageString;
114       textParameters.HorizontalAlignment = label.HorizontalAlignment;
115       textParameters.VerticalAlignment = label.VerticalAlignment;
116       textParameters.FontFamily = label.FontFamily;
117       textParameters.FontWeight = "";
118       textParameters.FontWidth = "";
119       textParameters.FontSlant = "";
120       textParameters.Layout = TextLayout.MultiLine;
121       textParameters.TextColor = Color.Black;
122       textParameters.FontSize = label.PointSize;
123       textParameters.TextWidth = (uint)label.Size.Width;
124       textParameters.TextHeight = (uint)label.Size.Height;
125       textParameters.EllipsisEnabled = true;
126       textParameters.MarkupEnabled = previousMarkup;
127
128
129       Tizen.NUI.PropertyArray cutOffIndexArray = TextUtils.GetLastCharacterIndex( textParameters );
130       uint count = cutOffIndexArray.Count();
131       for(uint i=0; i < count; i++)
132       {
133           cutOffIndexArray.GetElementAt(i).Get(out cutOffIndex); // Gets the last index of text shown on the actual screen.
134
135           // If markup is enabled, It should parse markup
136           if(label.EnableMarkup)
137           {
138             int preOffset = offset;
139             offset = MarkupProcess( offset, cutOffIndex - preOffset );
140             remainLength -= (offset - preOffset);
141           }
142           //If markup is not enabled, parsing is not required.
143           else
144           {
145             PageData pageData = new PageData();
146             pageData.startOffset = offset;
147             int cnt = (cutOffIndex - offset ) < remainLength ? (cutOffIndex - offset ) : remainLength;
148             remainLength -= cnt;
149             offset += cnt;
150             pageData.endOffset = offset;
151             pageList.Add(pageData);
152           }
153           totalPageCnt++;
154           if(offset <= 0 ) break;
155       }
156
157       stream = null;
158       return totalPageCnt;
159     }
160
161     /// <summary>
162     /// Input the page number returns the text of the page.
163     /// <returns>The text of the page.</returns>
164     /// </summary>
165     [EditorBrowsable(EditorBrowsableState.Never)]
166     public string GetText(int pageNum)
167     {
168       if( pageNum > totalPageCnt || pageNum < 1 ) {
169           Tizen.Log.Error("NUI", $"Out of Range total page count : {totalPageCnt}, input page number : {pageNum}\n");
170           return "";
171       }
172
173       List<PageData> dataList = pageList.GetRange(pageNum-1, 1);
174       foreach(PageData data in dataList)
175       {
176         int cnt = data.endOffset - data.startOffset;
177         char[] charArray = new char[cnt];
178         pageString.CopyTo(data.startOffset, charArray, 0, cnt);
179         string pageText = data.previousTag+new String(charArray)+data.endTag;
180         return pageText;
181       }
182       return "";
183     }
184
185     private void SkipWhiteSpace(ref int offset)
186     {
187       int character;
188       while( ( character = stream.Read()) != -1)
189       {
190         offset++;
191         if( character == WHITE_SPACE) continue;
192         else break;
193       }
194     }
195
196     private bool IsTag(TagData tag, ref int offset)
197     {
198       List<char> tagChaList = new List<char>();
199
200       bool isTag = false;
201       bool isQuotationOpen = false;
202       bool attributesFound = false;
203       tag.isEndTag = false;
204       bool isPreviousLessThan = true;
205       bool isPreviousSlash = false;
206
207       int character;
208
209       tag.tagName = "";
210       tag.attributeName = "";
211       // SkipWhiteSpace(ref offset);
212       while((!isTag) && ((character = stream.Read()) != -1)) {
213         offset++;
214         characterList.Add((char)character);
215         if( !isQuotationOpen && ( SLASH == character ) ) // '/'
216         {
217           if (isPreviousLessThan)
218           {
219             tag.isEndTag = true;
220           }
221           else
222           {
223             // if the tag has a '/' it may be an end tag.
224             isPreviousSlash = true;
225           }
226           isPreviousLessThan = false;
227           // SkipWhiteSpace(ref offset);
228         }
229         else if( GREATER_THAN == character ) // '>'
230         {
231           isTag = true;
232           if (isPreviousSlash)
233           {
234             tag.isEndTag = true;
235           }
236
237           if(!attributesFound) {
238             tag.tagName = new String(tagChaList.ToArray());
239           } else {
240             tag.attributeName = new String(tagChaList.ToArray());
241           }
242
243           isPreviousSlash = false;
244           isPreviousLessThan = false;
245         }
246         else if( QUOTATION_MARK == character )
247         {
248           tagChaList.Add((char)character);
249           isQuotationOpen = !isQuotationOpen;
250
251           isPreviousSlash = false;
252           isPreviousLessThan = false;
253         }
254         else if( WHITE_SPACE >= character || EQUAL == character ) // ' ', '='
255         {
256           // Let's save tag name.
257           if(!attributesFound) {
258             tag.tagName = new String(tagChaList.ToArray());
259             tagChaList.Clear();
260           }
261           tagChaList.Add((char)character);
262           // If the tag contains white spaces then it may have attributes.
263           if( !isQuotationOpen )
264           {
265             attributesFound = true;
266           }
267         }
268         else
269         {
270           tagChaList.Add((char)character);
271           isPreviousSlash = false;
272           isPreviousLessThan = false;
273         }
274       }
275       return isTag;
276     }
277
278
279     private int MarkupProcess(int startOffset, int cutOffIndex)
280     {
281
282       int count = 0;
283       int offset = startOffset;
284       int character = 0;
285       characterList.Clear();
286       PageData pageData = new PageData();
287
288       pageData.startOffset = offset;
289
290       bool isPreviousLessThan = false;
291       bool isPreviousSlash = false;
292
293       // If the markup was previously open, the markup tag should be attached to the front.
294       string tag ="";
295       foreach (TagData data in tagList)
296       {
297         tag += "<"+data.tagName+data.attributeName+">";
298       }
299       pageData.previousTag = tag;
300
301
302       bool isTag = false;
303       while( (character = stream.Read()) != -1 )
304       {
305         offset++;
306         characterList.Add((char)character);
307
308         TagData tagData = new TagData();
309         isTag = false;
310         if( LESS_THAN == character ) // '<'
311         {
312           isTag = IsTag(tagData, ref offset);
313         }
314
315         if(isTag) {
316           if(tagData.isEndTag) {
317             int lastIndex = tagList.Count;
318             tagList.RemoveAt(lastIndex-1);
319           } else {
320             tagList.Add(tagData);
321           }
322         } else {
323           count++;
324         }
325         if(count >= cutOffIndex) break;
326
327       }
328
329       // If the markup was previously open, you should attach the label tag.
330       tag ="";
331       foreach (TagData data in tagList)
332       {
333         tag = "</"+data.tagName+">" + tag;
334       }
335       pageData.endTag = tag;
336
337       pageData.endOffset = offset;
338       pageList.Add(pageData);
339
340       if(character == -1) offset = -1;
341       return offset;
342     }
343
344   }
345 }