Follow formatting NUI
[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             {
172                 Tizen.Log.Error("NUI", $"Out of Range total page count : {totalPageCnt}, input page number : {pageNum}\n");
173                 return "";
174             }
175
176             List<PageData> dataList = pageList.GetRange(pageNum - 1, 1);
177             foreach (PageData data in dataList)
178             {
179                 int cnt = data.endOffset - data.startOffset;
180                 char[] charArray = new char[cnt];
181                 pageString.CopyTo(data.startOffset, charArray, 0, cnt);
182                 string pageText = data.previousTag + new String(charArray) + data.endTag;
183                 return pageText;
184             }
185             return "";
186         }
187
188         private void SkipWhiteSpace(ref int offset)
189         {
190             int character;
191             while ((character = stream.Read()) != -1)
192             {
193                 offset++;
194                 if (character == WHITE_SPACE) continue;
195                 else break;
196             }
197         }
198
199         private bool IsTag(TagData tag, ref int offset)
200         {
201             List<char> tagChaList = new List<char>();
202
203             bool isTag = false;
204             bool isQuotationOpen = false;
205             bool attributesFound = false;
206             tag.isEndTag = false;
207             bool isPreviousLessThan = true;
208             bool isPreviousSlash = false;
209
210             int character;
211
212             tag.tagName = "";
213             tag.attributeName = "";
214             // SkipWhiteSpace(ref offset);
215             while ((!isTag) && ((character = stream.Read()) != -1))
216             {
217                 offset++;
218                 characterList.Add((char)character);
219                 if (!isQuotationOpen && (SLASH == character)) // '/'
220                 {
221                     if (isPreviousLessThan)
222                     {
223                         tag.isEndTag = true;
224                     }
225                     else
226                     {
227                         // if the tag has a '/' it may be an end tag.
228                         isPreviousSlash = true;
229                     }
230                     isPreviousLessThan = false;
231                     // SkipWhiteSpace(ref offset);
232                 }
233                 else if (GREATER_THAN == character) // '>'
234                 {
235                     isTag = true;
236                     if (isPreviousSlash)
237                     {
238                         tag.isEndTag = true;
239                     }
240
241                     if (!attributesFound)
242                     {
243                         tag.tagName = new String(tagChaList.ToArray());
244                     }
245                     else
246                     {
247                         tag.attributeName = new String(tagChaList.ToArray());
248                     }
249
250                     isPreviousSlash = false;
251                     isPreviousLessThan = false;
252                 }
253                 else if (QUOTATION_MARK == character)
254                 {
255                     tagChaList.Add((char)character);
256                     isQuotationOpen = !isQuotationOpen;
257
258                     isPreviousSlash = false;
259                     isPreviousLessThan = false;
260                 }
261                 else if (WHITE_SPACE >= character || EQUAL == character) // ' ', '='
262                 {
263                     // Let's save tag name.
264                     if (!attributesFound)
265                     {
266                         tag.tagName = new String(tagChaList.ToArray());
267                         tagChaList.Clear();
268                     }
269                     tagChaList.Add((char)character);
270                     // If the tag contains white spaces then it may have attributes.
271                     if (!isQuotationOpen)
272                     {
273                         attributesFound = true;
274                     }
275                 }
276                 else
277                 {
278                     tagChaList.Add((char)character);
279                     isPreviousSlash = false;
280                     isPreviousLessThan = false;
281                 }
282             }
283             return isTag;
284         }
285
286
287         private int MarkupProcess(int startOffset, int cutOffIndex)
288         {
289
290             int count = 0;
291             int offset = startOffset;
292             int character = 0;
293             characterList.Clear();
294             PageData pageData = new PageData();
295
296             pageData.startOffset = offset;
297
298             // If the markup was previously open, the markup tag should be attached to the front.
299             string tag = "";
300             foreach (TagData data in tagList)
301             {
302                 tag += "<" + data.tagName + data.attributeName + ">";
303             }
304             pageData.previousTag = tag;
305
306
307             bool isTag = false;
308             while ((character = stream.Read()) != -1)
309             {
310                 offset++;
311                 characterList.Add((char)character);
312
313                 TagData tagData = new TagData();
314                 isTag = false;
315                 if (LESS_THAN == character) // '<'
316                 {
317                     isTag = IsTag(tagData, ref offset);
318                 }
319
320                 if (isTag)
321                 {
322                     if (tagData.isEndTag)
323                     {
324                         int lastIndex = tagList.Count;
325                         tagList.RemoveAt(lastIndex - 1);
326                     }
327                     else
328                     {
329                         tagList.Add(tagData);
330                     }
331                 }
332                 else
333                 {
334                     count++;
335                 }
336                 if (count >= cutOffIndex) break;
337
338             }
339
340             // If the markup was previously open, you should attach the label tag.
341             tag = "";
342             foreach (TagData data in tagList)
343             {
344                 tag = "</" + data.tagName + ">" + tag;
345             }
346             pageData.endTag = tag;
347
348             pageData.endOffset = offset;
349             pageList.Add(pageData);
350
351             if (character == -1) offset = -1;
352             return offset;
353         }
354
355     }
356 }