[NUI] Add Padding parameter for TextPageUtil (#2332)
[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     internal 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     internal 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             textParameters.Padding = label.Padding;
128
129
130             Tizen.NUI.PropertyArray cutOffIndexArray = TextUtils.GetLastCharacterIndex(textParameters);
131             uint count = cutOffIndexArray.Count();
132             for (uint i = 0; i < count; i++)
133             {
134                 cutOffIndexArray.GetElementAt(i).Get(out cutOffIndex); // Gets the last index of text shown on the actual screen.
135
136                 // If markup is enabled, It should parse markup
137                 if (label.EnableMarkup)
138                 {
139                     int preOffset = offset;
140                     offset = MarkupProcess(offset, cutOffIndex - preOffset);
141                     remainLength -= (offset - preOffset);
142                 }
143                 //If markup is not enabled, parsing is not required.
144                 else
145                 {
146                     PageData pageData = new PageData();
147                     pageData.StartOffset = offset;
148                     int cnt = (cutOffIndex - offset) < remainLength ? (cutOffIndex - offset) : remainLength;
149                     remainLength -= cnt;
150                     offset += cnt;
151                     pageData.EndOffset = offset;
152                     pageList.Add(pageData);
153                 }
154                 totalPageCnt++;
155                 if (offset <= 0 || remainLength <= 0) break;
156             }
157
158             textParameters.Dispose();
159             cutOffIndexArray.Dispose();
160             stream = null;
161             return totalPageCnt;
162         }
163
164         /// <summary>
165         /// Input the page number returns the text of the page.
166         /// <returns>The text of the page.</returns>
167         /// </summary>
168         [EditorBrowsable(EditorBrowsableState.Never)]
169         public string GetText(int pageNum)
170         {
171             if (pageNum > totalPageCnt || pageNum < 1)
172             {
173                 Tizen.Log.Error("NUI", $"Out of Range total page count : {totalPageCnt}, input page number : {pageNum}\n");
174                 return "";
175             }
176
177             List<PageData> dataList = pageList.GetRange(pageNum - 1, 1);
178             foreach (PageData data in dataList)
179             {
180                 int cnt = data.EndOffset - data.StartOffset;
181                 char[] charArray = new char[cnt];
182                 pageString.CopyTo(data.StartOffset, charArray, 0, cnt);
183                 string pageText = data.PreviousTag + new String(charArray) + data.EndTag;
184                 return pageText;
185             }
186             return "";
187         }
188
189         private void SkipWhiteSpace(ref int offset)
190         {
191             int character;
192             while ((character = stream.Read()) != -1)
193             {
194                 offset++;
195                 if (character == WHITE_SPACE) continue;
196                 else break;
197             }
198         }
199
200         private bool IsTag(TagData tag, ref int offset)
201         {
202             List<char> tagChaList = new List<char>();
203
204             bool isTag = false;
205             bool isQuotationOpen = false;
206             bool attributesFound = false;
207             tag.IsEndTag = false;
208             bool isPreviousLessThan = true;
209             bool isPreviousSlash = false;
210
211             int character;
212
213             tag.TagName = "";
214             tag.AttributeName = "";
215             // SkipWhiteSpace(ref offset);
216             while ((!isTag) && ((character = stream.Read()) != -1))
217             {
218                 offset++;
219                 characterList.Add((char)character);
220                 if (!isQuotationOpen && (SLASH == character)) // '/'
221                 {
222                     if (isPreviousLessThan)
223                     {
224                         tag.IsEndTag = true;
225                     }
226                     else
227                     {
228                         // if the tag has a '/' it may be an end tag.
229                         isPreviousSlash = true;
230                     }
231                     isPreviousLessThan = false;
232                     // SkipWhiteSpace(ref offset);
233                 }
234                 else if (GREATER_THAN == character) // '>'
235                 {
236                     isTag = true;
237                     if (isPreviousSlash)
238                     {
239                         tag.IsEndTag = true;
240                     }
241
242                     if (!attributesFound)
243                     {
244                         tag.TagName = new String(tagChaList.ToArray());
245                     }
246                     else
247                     {
248                         tag.AttributeName = new String(tagChaList.ToArray());
249                     }
250
251                     isPreviousSlash = false;
252                     isPreviousLessThan = false;
253                 }
254                 else if (QUOTATION_MARK == character)
255                 {
256                     tagChaList.Add((char)character);
257                     isQuotationOpen = !isQuotationOpen;
258
259                     isPreviousSlash = false;
260                     isPreviousLessThan = false;
261                 }
262                 else if (WHITE_SPACE >= character || EQUAL == character) // ' ', '='
263                 {
264                     // Let's save tag name.
265                     if (!attributesFound)
266                     {
267                         tag.TagName = new String(tagChaList.ToArray());
268                         tagChaList.Clear();
269                     }
270                     tagChaList.Add((char)character);
271                     // If the tag contains white spaces then it may have attributes.
272                     if (!isQuotationOpen)
273                     {
274                         attributesFound = true;
275                     }
276                 }
277                 else
278                 {
279                     tagChaList.Add((char)character);
280                     isPreviousSlash = false;
281                     isPreviousLessThan = false;
282                 }
283             }
284             return isTag;
285         }
286
287
288         private int MarkupProcess(int startOffset, int cutOffIndex)
289         {
290
291             int count = 0;
292             int offset = startOffset;
293             int character = 0;
294             characterList.Clear();
295             PageData pageData = new PageData();
296
297             pageData.StartOffset = offset;
298
299             // If the markup was previously open, the markup tag should be attached to the front.
300             string tag = "";
301             foreach (TagData data in tagList)
302             {
303                 tag += "<" + data.TagName + data.AttributeName + ">";
304             }
305             pageData.PreviousTag = tag;
306
307
308             bool isTag = false;
309             while ((character = stream.Read()) != -1)
310             {
311                 offset++;
312                 characterList.Add((char)character);
313
314                 TagData tagData = new TagData();
315                 isTag = false;
316                 if (LESS_THAN == character) // '<'
317                 {
318                     isTag = IsTag(tagData, ref offset);
319                 }
320
321                 if (isTag)
322                 {
323                     if (tagData.IsEndTag)
324                     {
325                         int lastIndex = tagList.Count;
326                         tagList.RemoveAt(lastIndex - 1);
327                     }
328                     else
329                     {
330                         tagList.Add(tagData);
331                     }
332                 }
333                 else
334                 {
335                     count++;
336                 }
337                 if (count >= cutOffIndex) break;
338
339             }
340
341             // If the markup was previously open, you should attach the label tag.
342             tag = "";
343             foreach (TagData data in tagList)
344             {
345                 tag = "</" + data.TagName + ">" + tag;
346             }
347             pageData.EndTag = tag;
348
349             pageData.EndOffset = offset;
350             pageList.Add(pageData);
351
352             if (character == -1) offset = -1;
353             return offset;
354         }
355
356     }
357 }