/* * Copyright (c) 2020 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using Tizen.NUI; using Tizen.NUI.BaseComponents; namespace Tizen.NUI.Utility { /// /// This is a class for stroing the text of a page. /// [EditorBrowsable(EditorBrowsableState.Never)] internal class PageData { public string PreviousTag { get; set; } public string EndTag { get; set; } public int StartOffset { get; set; } public int EndOffset { get; set; } } /// /// This is a class that stores information when parsing markup text. /// [EditorBrowsable(EditorBrowsableState.Never)] internal class TagData { public string TagName { get; set; } public string AttributeName { get; set; } public bool IsEndTag { get; set; } } /// /// This is utility class for paging very long text. /// [EditorBrowsable(EditorBrowsableState.Never)] public class TextPageUtil : Disposable { private static char LESS_THAN = '<'; private static char GREATER_THAN = '>'; private static char EQUAL = '='; private static char QUOTATION_MARK = '\''; private static char SLASH = '/'; // private static char BACK_SLASH = '\\'; // private static char AMPERSAND = '&'; // private static char HASH = '#'; // private static char SEMI_COLON = ';'; // private static char CHAR_ARRAY_END = '\0'; // private static char HEX_CODE = 'x'; private static byte WHITE_SPACE = 0x20; private int totalPageCnt; private List pageList; private List tagList; private StringReader stream; private List characterList; private string pageString; /// /// When text is inputed, the text is paging in the TextLabe size unit. /// The total number of pages. /// [EditorBrowsable(EditorBrowsableState.Never)] public int SetText(TextLabel label, string str) { if (label == null || str == null) return 0; // perform this operation to match the utf32 character used in native Dali. bool previousMarkup = label.EnableMarkup; label.EnableMarkup = false; label.Text = str; pageString = label.Text; label.EnableMarkup = previousMarkup; label.MultiLine = true; label.Ellipsis = false; int length = pageString.Length; int remainLength = length; int offset = 0; int cutOffIndex = 0; // init totalPageCnt = 0; pageList = new List(); tagList = new List(); characterList = new List(); stream = new StringReader(pageString); RendererParameters textParameters = new RendererParameters(); textParameters.Text = pageString; textParameters.HorizontalAlignment = label.HorizontalAlignment; textParameters.VerticalAlignment = label.VerticalAlignment; textParameters.FontFamily = label.FontFamily; textParameters.FontWeight = ""; textParameters.FontWidth = ""; textParameters.FontSlant = ""; textParameters.Layout = TextLayout.MultiLine; textParameters.TextColor = Color.Black; textParameters.FontSize = label.PointSize; textParameters.TextWidth = (uint)label.Size.Width; textParameters.TextHeight = (uint)label.Size.Height; textParameters.EllipsisEnabled = true; textParameters.MarkupEnabled = previousMarkup; textParameters.MinLineSize = label.MinLineSize; textParameters.Padding = label.Padding; Tizen.NUI.PropertyArray cutOffIndexArray = TextUtils.GetLastCharacterIndex(textParameters); uint count = cutOffIndexArray.Count(); for (uint i = 0; i < count; i++) { cutOffIndexArray.GetElementAt(i).Get(out cutOffIndex); // Gets the last index of text shown on the actual screen. // If markup is enabled, It should parse markup if (label.EnableMarkup) { int preOffset = offset; offset = MarkupProcess(offset, cutOffIndex - preOffset); remainLength -= (offset - preOffset); } //If markup is not enabled, parsing is not required. else { PageData pageData = new PageData(); pageData.StartOffset = offset; int cnt = (cutOffIndex - offset) < remainLength ? (cutOffIndex - offset) : remainLength; remainLength -= cnt; offset += cnt; pageData.EndOffset = offset; pageList.Add(pageData); } totalPageCnt++; if (offset <= 0 || remainLength <= 0) break; } textParameters.Dispose(); cutOffIndexArray.Dispose(); stream = null; return totalPageCnt; } /// /// Input the page number returns the text of the page. /// The text of the page. /// [EditorBrowsable(EditorBrowsableState.Never)] public string GetText(int pageNum) { if (pageNum > totalPageCnt || pageNum < 1) { Tizen.Log.Error("NUI", $"Out of Range total page count : {totalPageCnt}, input page number : {pageNum}\n"); return ""; } List dataList = pageList.GetRange(pageNum - 1, 1); foreach (PageData data in dataList) { int cnt = data.EndOffset - data.StartOffset; char[] charArray = new char[cnt]; pageString.CopyTo(data.StartOffset, charArray, 0, cnt); string pageText = data.PreviousTag + new String(charArray) + data.EndTag; return pageText; } return ""; } private void SkipWhiteSpace(ref int offset) { int character; while ((character = stream.Read()) != -1) { offset++; if (character == WHITE_SPACE) continue; else break; } } private bool IsTag(TagData tag, ref int offset) { List tagChaList = new List(); bool isTag = false; bool isQuotationOpen = false; bool attributesFound = false; tag.IsEndTag = false; bool isPreviousLessThan = true; bool isPreviousSlash = false; int character; tag.TagName = ""; tag.AttributeName = ""; // SkipWhiteSpace(ref offset); while ((!isTag) && ((character = stream.Read()) != -1)) { offset++; characterList.Add((char)character); if (!isQuotationOpen && (SLASH == character)) // '/' { if (isPreviousLessThan) { tag.IsEndTag = true; } else { // if the tag has a '/' it may be an end tag. isPreviousSlash = true; } isPreviousLessThan = false; // SkipWhiteSpace(ref offset); } else if (GREATER_THAN == character) // '>' { isTag = true; if (isPreviousSlash) { tag.IsEndTag = true; } if (!attributesFound) { tag.TagName = new String(tagChaList.ToArray()); } else { tag.AttributeName = new String(tagChaList.ToArray()); } isPreviousSlash = false; isPreviousLessThan = false; } else if (QUOTATION_MARK == character) { tagChaList.Add((char)character); isQuotationOpen = !isQuotationOpen; isPreviousSlash = false; isPreviousLessThan = false; } else if (WHITE_SPACE >= character || EQUAL == character) // ' ', '=' { // Let's save tag name. if (!attributesFound) { tag.TagName = new String(tagChaList.ToArray()); tagChaList.Clear(); } tagChaList.Add((char)character); // If the tag contains white spaces then it may have attributes. if (!isQuotationOpen) { attributesFound = true; } } else { tagChaList.Add((char)character); isPreviousSlash = false; isPreviousLessThan = false; } } return isTag; } private int MarkupProcess(int startOffset, int cutOffIndex) { int count = 0; int offset = startOffset; int character = 0; characterList.Clear(); PageData pageData = new PageData(); pageData.StartOffset = offset; // If the markup was previously open, the markup tag should be attached to the front. string tag = ""; foreach (TagData data in tagList) { tag += "<" + data.TagName + data.AttributeName + ">"; } pageData.PreviousTag = tag; bool isTag = false; while ((character = stream.Read()) != -1) { offset++; characterList.Add((char)character); TagData tagData = new TagData(); isTag = false; if (LESS_THAN == character) // '<' { isTag = IsTag(tagData, ref offset); } if (isTag) { if (tagData.IsEndTag) { int lastIndex = tagList.Count; tagList.RemoveAt(lastIndex - 1); } else { tagList.Add(tagData); } } else { count++; } if (count >= cutOffIndex) break; } // If the markup was previously open, you should attach the label tag. tag = ""; foreach (TagData data in tagList) { tag = "" + tag; } pageData.EndTag = tag; pageData.EndOffset = offset; pageList.Add(pageData); if (character == -1) offset = -1; return offset; } } }