82da12ffa1bd89f5b8baddd812c33c42c3a06a39
[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 : Disposable
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 int totalPageCnt;
73
74     private List<PageData> pageList;
75     private List<TagData> tagList;
76     private StringReader stream;
77     private List<char> characterList;
78     private string pageString;
79
80     /// <summary>
81     /// When text is inputed, the text is paging in the TextLabe size unit.
82     /// <returns>The total number of pages.</returns>
83     /// </summary>
84     [EditorBrowsable(EditorBrowsableState.Never)]
85     public int SetText(TextLabel label, string str)
86     {
87       if(label == null || str == null) return 0;
88
89       // perform this operation to match the utf32 character used in native Dali.
90       bool previousMarkup = label.EnableMarkup;
91       label.EnableMarkup = false;
92       label.Text = str;
93       pageString = label.Text;
94       label.EnableMarkup = previousMarkup;
95       label.MultiLine = true;
96       label.Ellipsis = false;
97
98       int length = pageString.Length;
99       int remainLength = length;
100       int offset = 0;
101       int cutOffIndex = 0;
102
103       // init
104       totalPageCnt = 0;
105       pageList = new List<PageData>();
106       tagList = new List<TagData>();
107       characterList = new List<char>();
108
109       stream = new StringReader(pageString);
110
111       RendererParameters textParameters = new RendererParameters();
112       textParameters.Text = pageString;
113       textParameters.HorizontalAlignment = label.HorizontalAlignment;
114       textParameters.VerticalAlignment = label.VerticalAlignment;
115       textParameters.FontFamily = label.FontFamily;
116       textParameters.FontWeight = "";
117       textParameters.FontWidth = "";
118       textParameters.FontSlant = "";
119       textParameters.Layout = TextLayout.MultiLine;
120       textParameters.TextColor = Color.Black;
121       textParameters.FontSize = label.PointSize;
122       textParameters.TextWidth = (uint)label.Size.Width;
123       textParameters.TextHeight = (uint)label.Size.Height;
124       textParameters.EllipsisEnabled = true;
125       textParameters.MarkupEnabled = previousMarkup;
126       textParameters.MinLineSize = label.MinLineSize;
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 || remainLength <= 0 ) break;
155       }
156
157       textParameters.Dispose();
158       cutOffIndexArray.Dispose();
159       stream = null;
160       return totalPageCnt;
161     }
162
163     /// <summary>
164     /// Input the page number returns the text of the page.
165     /// <returns>The text of the page.</returns>
166     /// </summary>
167     [EditorBrowsable(EditorBrowsableState.Never)]
168     public string GetText(int pageNum)
169     {
170       if( pageNum > totalPageCnt || pageNum < 1 ) {
171           Tizen.Log.Error("NUI", $"Out of Range total page count : {totalPageCnt}, input page number : {pageNum}\n");
172           return "";
173       }
174
175       List<PageData> dataList = pageList.GetRange(pageNum-1, 1);
176       foreach(PageData data in dataList)
177       {
178         int cnt = data.endOffset - data.startOffset;
179         char[] charArray = new char[cnt];
180         pageString.CopyTo(data.startOffset, charArray, 0, cnt);
181         string pageText = data.previousTag+new String(charArray)+data.endTag;
182         return pageText;
183       }
184       return "";
185     }
186
187     private void SkipWhiteSpace(ref int offset)
188     {
189       int character;
190       while( ( character = stream.Read()) != -1)
191       {
192         offset++;
193         if( character == WHITE_SPACE) continue;
194         else break;
195       }
196     }
197
198     private bool IsTag(TagData tag, ref int offset)
199     {
200       List<char> tagChaList = new List<char>();
201
202       bool isTag = false;
203       bool isQuotationOpen = false;
204       bool attributesFound = false;
205       tag.isEndTag = false;
206       bool isPreviousLessThan = true;
207       bool isPreviousSlash = false;
208
209       int character;
210
211       tag.tagName = "";
212       tag.attributeName = "";
213       // SkipWhiteSpace(ref offset);
214       while((!isTag) && ((character = stream.Read()) != -1)) {
215         offset++;
216         characterList.Add((char)character);
217         if( !isQuotationOpen && ( SLASH == character ) ) // '/'
218         {
219           if (isPreviousLessThan)
220           {
221             tag.isEndTag = true;
222           }
223           else
224           {
225             // if the tag has a '/' it may be an end tag.
226             isPreviousSlash = true;
227           }
228           isPreviousLessThan = false;
229           // SkipWhiteSpace(ref offset);
230         }
231         else if( GREATER_THAN == character ) // '>'
232         {
233           isTag = true;
234           if (isPreviousSlash)
235           {
236             tag.isEndTag = true;
237           }
238
239           if(!attributesFound) {
240             tag.tagName = new String(tagChaList.ToArray());
241           } else {
242             tag.attributeName = new String(tagChaList.ToArray());
243           }
244
245           isPreviousSlash = false;
246           isPreviousLessThan = false;
247         }
248         else if( QUOTATION_MARK == character )
249         {
250           tagChaList.Add((char)character);
251           isQuotationOpen = !isQuotationOpen;
252
253           isPreviousSlash = false;
254           isPreviousLessThan = false;
255         }
256         else if( WHITE_SPACE >= character || EQUAL == character ) // ' ', '='
257         {
258           // Let's save tag name.
259           if(!attributesFound) {
260             tag.tagName = new String(tagChaList.ToArray());
261             tagChaList.Clear();
262           }
263           tagChaList.Add((char)character);
264           // If the tag contains white spaces then it may have attributes.
265           if( !isQuotationOpen )
266           {
267             attributesFound = true;
268           }
269         }
270         else
271         {
272           tagChaList.Add((char)character);
273           isPreviousSlash = false;
274           isPreviousLessThan = false;
275         }
276       }
277       return isTag;
278     }
279
280
281     private int MarkupProcess(int startOffset, int cutOffIndex)
282     {
283
284       int count = 0;
285       int offset = startOffset;
286       int character = 0;
287       characterList.Clear();
288       PageData pageData = new PageData();
289
290       pageData.startOffset = offset;
291
292       // If the markup was previously open, the markup tag should be attached to the front.
293       string tag ="";
294       foreach (TagData data in tagList)
295       {
296         tag += "<"+data.tagName+data.attributeName+">";
297       }
298       pageData.previousTag = tag;
299
300
301       bool isTag = false;
302       while( (character = stream.Read()) != -1 )
303       {
304         offset++;
305         characterList.Add((char)character);
306
307         TagData tagData = new TagData();
308         isTag = false;
309         if( LESS_THAN == character ) // '<'
310         {
311           isTag = IsTag(tagData, ref offset);
312         }
313
314         if(isTag) {
315           if(tagData.isEndTag) {
316             int lastIndex = tagList.Count;
317             tagList.RemoveAt(lastIndex-1);
318           } else {
319             tagList.Add(tagData);
320           }
321         } else {
322           count++;
323         }
324         if(count >= cutOffIndex) break;
325
326       }
327
328       // If the markup was previously open, you should attach the label tag.
329       tag ="";
330       foreach (TagData data in tagList)
331       {
332         tag = "</"+data.tagName+">" + tag;
333       }
334       pageData.endTag = tag;
335
336       pageData.endOffset = offset;
337       pageList.Add(pageData);
338
339       if(character == -1) offset = -1;
340       return offset;
341     }
342
343   }
344 }