private void PositionWordsHorizontally(float maxWidth, out float longestLineWidth) { fittedWords.Clear(); fittedWords.AddRange(words); longestLineWidth = 0; float x = 0; // We're using breakStart to join multiple words into one with int breakStart = 0; for (int i = 0; i < fittedWords.Count; i++) { var word = fittedWords[i].Clone(); word.LineBreak = word.ForceLineBreak; if (word.LineBreak) { x = 0; } if (i > 0 && !fittedWords[i - 1].IsNbsp && !word.IsNbsp) { breakStart = i; } word.Width = CalcWordWidth(word); var t = texts[word.TextIndex]; var isTextOrBullet = (t.Length > 0 && t[word.Start] > ' ') || IsBullet(word); if (x + word.Width > maxWidth && isTextOrBullet) { if (wordSplitAllowed || t.HasJapaneseChineseSymbols(word.Start, word.Length)) { var fittedCharsCount = CalcFittedCharactersCount(word, maxWidth - x); if (fittedCharsCount > 0) { var wordEnd = word.Start + fittedCharsCount; TextLineSplitter.AdjustLineBreakPosition(t, ref wordEnd, word.Start + word.Length - 1); if (wordEnd > word.Start) { fittedCharsCount = wordEnd - word.Start; } var newWord = word.Clone(); newWord.IsTagBegin = false; newWord.Start = word.Start + fittedCharsCount; newWord.Length = word.Length - fittedCharsCount; newWord.Width = CalcWordWidth(newWord); newWord.ForceLineBreak = true; word.Length = fittedCharsCount; word.Width = CalcWordWidth(word); word.X = x; fittedWords.Insert(i + 1, newWord); goto skip_default_placement; } } // Splitting by words var isWordContinue = breakStart < i || word.TextIndex > 0 && word.Start == 0 && t.Length > 0 && t[word.Start] > ' ' && (GetLastChar(texts[fittedWords[i - 1].TextIndex]) > ' ' || IsBullet(fittedWords[i - 1])); if (isWordContinue) { breakStart = Math.Min(breakStart, i - 1); fittedWords[breakStart].LineBreak = true; x = 0; Word prev = null; // It's required to save our cloned word fittedWords[i] = word; for (var j = breakStart; j <= i; j++) { prev = fittedWords[j]; prev.X = x; x += prev.Width; } word = prev ?? word; } else { word.X = 0; word.LineBreak = true; word.Width = CalcWordWidth(word); x = word.Width; } } else { word.X = x; x += word.Width; } skip_default_placement: if (overflowMode == TextOverflowMode.Ellipsis) { if (word.X == 0 && word.Width > maxWidth) { ClipWordWithEllipsis(word, maxWidth); } } if (isTextOrBullet) // buz: при автопереносе на концах строк остаются пробелы, они не должны влиять на значение длины строки { longestLineWidth = Math.Max(longestLineWidth, word.X + word.Width); } fittedWords[i] = word; } }
Test_TextLineSplitter() { Print( "Pull" ); Assert( "\nline1\nline2\rline3\r\nline4\r\rline6\n\nline8\nline9" .AsStream() .To( new TextLineSplitter() ) .SequenceEqual( Stream.Create( "", "line1", "line2", "line3", "line4", "", "line6", "", "line8", "line9" ) ) ); Print( "Push" ); var lines = new List< string >(); var sink = new TextLineSplitter() .To( lines.AsSink() ); "\nline1\nline2\rline3\r\nline4\r\rline6\n\nline8\nline9" .AsStream() .EmptyTo( sink ); sink.Dispose(); Assert( lines .AsStream() .SequenceEqual( Stream.Create( "", "line1", "line2", "line3", "line4", "", "line6", "", "line8", "line9" ) ) ); }