/// <summary>
 /// Конструктор.
 /// </summary>
 /// <param name="original">Оригинальное состояние.</param>
 public ReadonlyTextRenderAttributeState(ITextRenderAttributeState original)
 {
     var attr = original?.Attributes;
     if (attr != null)
     {
         foreach (var kv in attr)
         {
             Attributes[kv.Key] = kv.Value;
         }
     }
 }
        /// <summary>
        /// Выполнить текстовую команду.
        /// </summary>
        /// <param name="attributes">Атрибуты.</param>
        /// <param name="text">Текст.</param>
        /// <returns>Остаток текста.</returns>
        protected virtual string ExectuteText(ITextRenderAttributeState attributes, string text)
        {
            var words = Splitter.Split(text);
            var run = CheckLengths(attributes, words);
            var isAdded = false;
            Action old = null;
            var sb = new StringBuilder();
            foreach (var r in run)
            {
                if (!r.Item2)
                {
                    bool isThis = false;
                    if (!isAdded)
                    {
                        isAdded = true;
                        var ta = old;
                        if (ta != null)
                        {
                            ta();
                        }
                        else
                        {
                            if (FirstInLine)
                            {
                                r.Item3?.Invoke();
                                isThis = true;
                            }
                            else
                            {
                                NewLine(attributes);
                            }
                        }
                    }
                    if (!isThis)
                    {
                        sb.Append(r.Item1);
                    }
                }
                old = r.Item3;
            }
            if (!isAdded)
            {
                var ta = old;
                ta?.Invoke();
            }

            return sb.ToString();
        }
 /// <summary>
 /// Установить высоту линии по умолчанию.
 /// </summary>
 /// <param name="attributes">Атрибуты.</param>
 protected virtual void SetDefaultLineHeight(ITextRenderAttributeState attributes)
 {
     var size = Factory.MeasureCommand(new TextRenderCommand(attributes, new TextRenderTextContent("A")));
     if (LineHeight < size.Height)
     {
         LineHeight = size.Height;
     }
 }
 /// <summary>
 /// Начать новую строку.
 /// </summary>
 /// <param name="attributes">Атрибуты.</param>
 protected virtual void NewLine(ITextRenderAttributeState attributes)
 {
     Lines++;
     if (LineHeight < 0.01)
     {
         SetDefaultLineHeight(attributes);
     }
     Top = Top + LineHeight;
     LineHeight = 0;
     LineWidth = 0;
     FirstInLine = true;
     SynCanvasHeight();
 }
        protected IEnumerable<Tuple<string, bool, Action, bool>> CheckLengths(ITextRenderAttributeState attributes, IEnumerable<string> words)
        {
            var wordsArr = words.ToArray();
            var testCom = new TextRenderCommand(attributes, new TextRenderTextContent("a"));
            var charWidth = GetElementWidth(testCom);

            int idx0 = 0;
            string current = "";
            for (int i = 0; i < wordsArr.Length; i++)
            {
                var word = wordsArr[i];
                current += word;
                var len = current.Length * charWidth;
                if ((len + LineWidth) > RenderWidth)
                {
                    idx0 = i;
                    break;
                }
            }

            int idx1 = -1;

            for (int j = idx0; j >= 0; j--)
            {
                current = "";
                for (int i = 0; i <= j; i++)
                {
                    var word = wordsArr[i];
                    current += word;
                }
                var com = new TextRenderCommand(attributes, new TextRenderTextContent(current));
                var elWidth = GetElementWidth(com);
                if (!((elWidth + LineWidth) > RenderWidth))
                {
                    idx1 = j;
                    break;
                }
            }

            bool exceeded = false;
            current = "";
            for (int i = 0; i < wordsArr.Length; i++)
            {
                var word = wordsArr[i];
                current += word;
                if (exceeded)
                {
                    yield return new Tuple<string, bool, Action, bool>(word, false, null, true);
                }
                else
                {
                    var com = new TextRenderCommand(attributes, new TextRenderTextContent(current));
                    if (i > idx1)
                    {
                        var elWidth = GetElementWidth(com);
                        if ((elWidth + LineWidth) > RenderWidth)
                        {
                            exceeded = true;
                        }
                        yield return new Tuple<string, bool, Action, bool>(word, !exceeded, () => AddElementToCanvas(com), false);
                    }
                    else
                    {
                        yield return new Tuple<string, bool, Action, bool>(word, true, () => AddElementToCanvas(com), false);
                    }
                }
            }
        }
 /// <summary>
 /// Конструктор.
 /// </summary>
 /// <param name="attributes">Атрибуты.</param>
 /// <param name="content">Контент.</param>
 public TextRenderCommand(ITextRenderAttributeState attributes, ITextRenderContent content)
 {
     Attributes = attributes;
     Content = content;
 }
 /// <summary>
 /// Установить высоту линии по умолчанию.
 /// </summary>
 /// <param name="attributes">Атрибуты.</param>
 protected virtual void SetDefaultLineHeight(ITextRenderAttributeState attributes)
 {
     var el = Factory.Create(new TextRenderCommand(attributes, new TextRenderTextContent("A")));
     if (LineHeight < el.Height)
     {
         LineHeight = el.Height;
     }
 }
        protected IEnumerable<Tuple<string, bool, Func<FrameworkElement>, bool>> CheckLengths(ITextRenderAttributeState attributes, IEnumerable<string> words)
        {
            var wordsArr = GetRoughLengths(attributes, words).ToArray();
            int idx0 = 0;
            string current = "";
            double len = 0;
            for (int i = 0; i < wordsArr.Length; i++)
            {
                var word = wordsArr[i];
                current += word.Item1;
                len = word.Item2;
                if ((len + LineWidth) > TextCanvas.ActualWidth)
                {
                    idx0 = i;
                    break;
                }
            }
            int idx1 = -1;

            for (int j = idx0; j >= 0; j--)
            {
                current = "";
                for (int i = 0; i <= j; i++)
                {
                    var word = wordsArr[i].Item1;
                    current += word;
                }
                var com = new TextRenderCommand(attributes, new TextRenderTextContent(current));
                var key = GetElementKey(com);
                var elWidth = GetElementWidth(key, com);
                if (!((elWidth + LineWidth) > TextCanvas.ActualWidth))
                {
                    idx1 = j;
                    break;
                }
            }

            bool exceeded = false;
            current = "";
            for (int i = 0; i < wordsArr.Length; i++)
            {
                var word = wordsArr[i].Item1;
                current += word;
                if (exceeded)
                {
                    yield return new Tuple<string, bool, Func<FrameworkElement>, bool>(word, false, () => null, true);
                }
                else
                {
                    var com = new TextRenderCommand(attributes, new TextRenderTextContent(current));
                    var key = GetElementKey(com);
                    if (i > idx1)
                    {
                        var elWidth = GetElementWidth(key, com);
                        if ((elWidth + LineWidth) > TextCanvas.ActualWidth)
                        {
                            exceeded = true;
                        }
                        yield return new Tuple<string, bool, Func<FrameworkElement>, bool>(word, !exceeded, ExtractElement(key, com), false);
                    }
                    else
                    {
                        yield return new Tuple<string, bool, Func<FrameworkElement>, bool>(word, true, ExtractElement(key, com), false);
                    }
                }
            }
        }
 protected IEnumerable<Tuple<string, double>> GetRoughLengths(ITextRenderAttributeState attributes, IEnumerable<string> words)
 {
     var wordsArr = words.ToArray();
     var agg = wordsArr.Aggregate(new StringBuilder(), (sb, s) => sb.Append(s)).ToString();
     var aggCom = new TextRenderCommand(attributes, new TextRenderTextContent(agg));
     var aggKey = GetElementKey(aggCom);
     var totalWidth = GetElementWidth(aggKey, aggCom);
     var totalCount = agg.Length > 0 ? agg.Length : 1;
     int cnt = 0;
     foreach (var word in wordsArr)
     {
         cnt += word.Length;                
         yield return new Tuple<string, double>(word, totalWidth * cnt / totalCount);
     }
 }
 private double GetOWidth(ITextRenderAttributeState attributes, TextBlock r)
 {
     var key = GetCacheKey(new TextRenderCommand(attributes, new TextRenderTextContent("o")));
     if (!OWidthCache.ContainsKey(key))
     {
         var s2 = r.Text;
         r.Text = "o";
         r.Measure(new Size(0, 0));
         OWidthCache[key] = r.ActualWidth;
         r.Text = s2;
     }
     return OWidthCache[key];
 }