From b634fb98f936f83613f54de2fd80c81e762a6271 Mon Sep 17 00:00:00 2001 From: Flamefire Date: Sun, 24 Aug 2014 20:07:15 +0200 Subject: [PATCH] Add more options and improve splitting of text --- Output/Cover/Blue.xml | 67 ++-- Vocaluxe/Base/CNoCoverGenerator.cs | 527 +++++++++++++++++++++-------- VocaluxeLib/Extensions.cs | 22 +- VocaluxeLib/Structs.cs | 2 + 4 files changed, 425 insertions(+), 193 deletions(-) diff --git a/Output/Cover/Blue.xml b/Output/Cover/Blue.xml index 069bb79c..4448fe2b 100644 --- a/Output/Cover/Blue.xml +++ b/Output/Cover/Blue.xml @@ -6,41 +6,36 @@ flokuep - - - - %TEXT% - - Outline - - - 500 - - - 0110.5 - 24 - 100 - - EmptyCover.png - true - 0.8 - - - - - %TEXT% - - Outline - - - 500 - - - 0100.5 - 24 - 100 - - EmptyCover.png - + + + + + + %TEXT% + + Outline + + + + 500 + + + 1110.8 + + 24 + 24 + + 100 + + + 1111 + + + EmptyCover.png + + true + + 0.75 + diff --git a/Vocaluxe/Base/CNoCoverGenerator.cs b/Vocaluxe/Base/CNoCoverGenerator.cs index 9f5d8a97..36e49858 100644 --- a/Vocaluxe/Base/CNoCoverGenerator.cs +++ b/Vocaluxe/Base/CNoCoverGenerator.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; @@ -37,17 +38,27 @@ namespace Vocaluxe.Base { private readonly bool _Valid; private readonly string _Text; + private readonly SColorF _BGColor; private readonly SColorF _TextColor; - private readonly string _TextColorName; - private readonly float _TextMargin; - private readonly float _TextIndent; + private readonly int _TextMarginLeft; + private readonly int _TextMarginRight; + private readonly int _TextMarginTop; + private readonly int _TextMarginBottom; + private readonly int _TextIndent; private readonly int _TextSize; private readonly string _Font; private readonly EStyle _Style; private readonly string _Image; private readonly bool _ShowFirstCover; private readonly float _ImageAlpha; - private const float _LineSpace = 5; + private const int _LineSpace = 5; + + /// + /// Split lines after special chars, descending priority + /// + private static readonly char[] _SplitCharPreferences = {':', '/', '-'}; + private static readonly char[] _SplitCharAfter = {':', '/', '-', ' ', ')', '.', '*', ','}; + private static readonly char[] _SplitCharBefore = {'('}; public CNoCoverGenerator(CXMLReader xmlReader, string xPath, string basePath) { @@ -58,29 +69,85 @@ namespace Vocaluxe.Base _Valid &= xmlReader.GetValue(xPath + "/Text/Font", out _Font); _Valid &= xmlReader.TryGetEnumValue(xPath + "/Text/Style", ref _Style); _Valid &= xmlReader.TryGetIntValue(xPath + "/Text/Size", ref _TextSize); - if (xmlReader.GetValue(xPath + "/Text/Color", out _TextColorName)) - _Valid &= CBase.Theme.GetColor(_TextColorName, CBase.Theme.GetSkinIndex(-1), out _TextColor); + string colorName; + if (xmlReader.GetValue(xPath + "/Text/Color", out colorName)) + _Valid &= CBase.Theme.GetColor(colorName, CBase.Theme.GetSkinIndex(-1), out _TextColor); else _Valid &= xmlReader.TryGetColorFromRGBA(xPath + "/Text", ref _TextColor); - _Valid &= xmlReader.TryGetFloatValue(xPath + "/Text/Margin", ref _TextMargin); - if (!xmlReader.TryGetFloatValue(xPath + "/Text/Indent", ref _TextIndent)) - _TextIndent = 6 * _TextMargin; + int tmpMargin = 0; + bool marginSet = xmlReader.TryGetIntValue(xPath + "/Text/Margin", ref tmpMargin); + if (marginSet) + { + _TextMarginLeft = tmpMargin; + _TextMarginRight = tmpMargin; + _TextMarginTop = tmpMargin; + _TextMarginBottom = tmpMargin; + } + _Valid &= xmlReader.TryGetIntValue(xPath + "/Text/MarginLeft", ref _TextMarginLeft) || marginSet; + _Valid &= xmlReader.TryGetIntValue(xPath + "/Text/MarginRight", ref _TextMarginRight) || marginSet; + _Valid &= xmlReader.TryGetIntValue(xPath + "/Text/MarginTop", ref _TextMarginTop) || marginSet; + _Valid &= xmlReader.TryGetIntValue(xPath + "/Text/MarginBottom", ref _TextMarginBottom) || marginSet; + if (!xmlReader.TryGetIntValue(xPath + "/Text/Indent", ref _TextIndent)) + _TextIndent = 6 * (_TextMarginLeft + _TextMarginRight) / 2; + + if (xmlReader.GetValue(xPath + "/Background/Color", out colorName)) + _Valid &= CBase.Theme.GetColor(colorName, CBase.Theme.GetSkinIndex(-1), out _BGColor); + else + _Valid &= xmlReader.TryGetColorFromRGBA(xPath + "/Background", ref _BGColor); _Valid &= xmlReader.GetValue(xPath + "/Image", out _Image); string tmp; if (xmlReader.GetValue(xPath + "/ShowFirstCover", out tmp)) Boolean.TryParse(tmp, out _ShowFirstCover); else _ShowFirstCover = false; - if (!xmlReader.TryGetFloatValue(xPath + "/ImageAlpha", ref _ImageAlpha)) - _ImageAlpha = 0.5f; - else - _ImageAlpha = _ImageAlpha.Clamp(0f, 1f); + _ImageAlpha = !xmlReader.TryGetFloatValue(xPath + "/ImageAlpha", ref _ImageAlpha) ? 0.5f : _ImageAlpha.Clamp(0f, 1f); if (_Valid) { // ReSharper disable AssignNullToNotNullAttribute _Image = Path.Combine(basePath, _Image); // ReSharper restore AssignNullToNotNullAttribute _Valid = File.Exists(_Image); + GetCover("Astrid Lindgren (Madicken)", null); + GetCover("David Guetta ft. Flo Rida & Nicki Minaj", null); + } + } + + private class CTextElement + { + public readonly string Text; + public float Width, Height; + private readonly Graphics _Graphics; + private readonly Font _Font; + private float _WidthTrimmed = -1; + public float WidthTrimmed + { + get + { + if (_WidthTrimmed < 0f) + { + string text = Text.TrimEnd(null); + _WidthTrimmed = text.Length == Text.Length ? Width : _Graphics.MeasureString(text, _Font, -1, StringFormat.GenericTypographic).Width; + } + return _WidthTrimmed; + } + } + public int Line; + + public CTextElement(string text, Graphics g, Font font) + { + Text = text; + SizeF dimensions = g.MeasureString(text, font, -1, StringFormat.GenericTypographic); + Width = dimensions.Width; + Height = (dimensions.Height + CFonts.Height) / 2; + _Graphics = g; + _Font = font; + } + + public void AdjustSize(float factor) + { + Width *= factor; + _WidthTrimmed *= factor; + Height *= factor; } } @@ -89,48 +156,44 @@ namespace Vocaluxe.Base if (!_Valid) return null; text = CLanguage.Translate(_Text.Replace("%TEXT%", text)); - using (Bitmap bmp = new Bitmap(_Image)) + using (Bitmap bmpImage = new Bitmap(_Image)) + using (Bitmap bmp = new Bitmap(bmpImage.Width, bmpImage.Height, PixelFormat.Format32bppArgb)) using (Graphics g = Graphics.FromImage(bmp)) { g.SmoothingMode = SmoothingMode.AntiAlias; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.TextRenderingHint = TextRenderingHint.AntiAlias; + g.Clear(_BGColor.AsColor()); + + ImageAttributes ia = null; if (_ShowFirstCover && !String.IsNullOrEmpty(firstCoverPath) && File.Exists(firstCoverPath)) { - ColorMatrix cm = new ColorMatrix {Matrix33 = 1f - _ImageAlpha}; - ImageAttributes ia = new ImageAttributes(); - ia.SetColorMatrix(cm); using (Bitmap bmp2 = new Bitmap(firstCoverPath)) - g.DrawImage(bmp2, new Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, bmp2.Width, bmp2.Height, GraphicsUnit.Pixel, ia); + g.DrawImage(bmp2, new Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, bmp2.Width, bmp2.Height, GraphicsUnit.Pixel); + ColorMatrix cm = new ColorMatrix {Matrix33 = _ImageAlpha}; + ia = new ImageAttributes(); + ia.SetColorMatrix(cm); } + g.DrawImage(bmpImage, new Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, ia); + if (text != "") { - List lines = _SplitText(text); + IEnumerable textParts = _SplitText(text); CFonts.SetFont(_Font); CFonts.Style = _Style; - float allowedWidth = bmp.Width - 2 * _TextMargin; - if (lines.Count > 1) - allowedWidth -= _TextIndent; - float allowedHeight = bmp.Height - 2 * _TextMargin - _LineSpace * (lines.Count - 1); - CFonts.Height = allowedHeight / lines.Count; + CFonts.Height = bmp.Height - _TextMarginTop - _TextMarginBottom; Font fo = CFonts.GetFont(); - List sizes = lines.Select(line => g.MeasureString(line, fo, -1, StringFormat.GenericTypographic)).ToList(); - float maxWidth = sizes.Select(s => s.Width).Max(); - float maxHeight = sizes.Select(s => s.Height).Max(); - maxHeight = (maxHeight + CFonts.Height) / 2; - if (maxWidth > allowedWidth) - { - CFonts.Height *= allowedWidth / maxWidth; - maxHeight *= allowedWidth / maxWidth; - } - if (maxHeight > _TextSize) - { - CFonts.Height *= _TextSize / maxHeight; - maxHeight = _TextSize; - } + List elements = textParts.Select(line => new CTextElement(line, g, fo)).ToList(); + float factor = _DistributeText(elements, bmp.Width, bmp.Height); + foreach (CTextElement element in elements) + element.AdjustSize(factor); + CFonts.Height *= factor / (1f + CFonts.Outline); //Adjust for outline size fo = CFonts.GetFont(); + float maxHeight = elements.Select(el => el.Height).Max(); + int lineCount = elements.Last().Line + 1; + //Have to use size in em not pixels! float emSize = fo.Size * fo.FontFamily.GetCellAscent(fo.Style) / fo.FontFamily.GetEmHeight(fo.Style); float outlineSize = CFonts.Outline * CFonts.Height; @@ -142,134 +205,320 @@ namespace Vocaluxe.Base { pen.LineJoin = LineJoin.Round; pen.Alignment = PenAlignment.Outset; - float top = (allowedHeight - maxHeight * lines.Count) / 2; - for (int i = 0; i < lines.Count; i++) + float top = (bmp.Height - _TextMarginBottom - _TextMarginTop - maxHeight * lineCount) / 2 + _TextMarginTop; + int nextLineEl = 0; + for (int i = 0; i < lineCount; i++) { - string line = lines[i]; + int firstEl = nextLineEl; + for (; nextLineEl < elements.Count; nextLineEl++) + { + if (elements[nextLineEl].Line > i) + break; + } + + string line = elements.GetRange(firstEl, nextLineEl - firstEl).Aggregate("", (current, element) => current + element.Text); float left; - if (lines.Count == 1 || (i == 1 && lines.Count == 3)) + if (lineCount == 1 || (i == 1 && lineCount == 3)) { //Center Text if this is the only line or the middle line - float width = g.MeasureString(line, fo, -1, StringFormat.GenericTypographic).Width; - left = (bmp.Width - width) / 2; + float width = _GetWidth(elements, firstEl, nextLineEl - 1); + left = (bmp.Width - _TextMarginLeft - _TextMarginRight - width) / 2 + _TextMarginLeft; } - else if (i == lines.Count - 1) + else if (i == lineCount - 1) { //Place last line at right - float width = g.MeasureString(line, fo, -1, StringFormat.GenericTypographic).Width; - left = bmp.Width - width - _TextMargin; + float width = _GetWidth(elements, firstEl, nextLineEl - 1); + left = bmp.Width - width - _TextMarginRight; } else - left = _TextMargin; + left = _TextMarginLeft; //g.DrawString(line, fo, new SolidBrush(_TextColor.AsColor()), left, top, StringFormat.GenericTypographic); path.AddString(line, fo.FontFamily, (int)fo.Style, emSize, new PointF(left, top), StringFormat.GenericTypographic); top += maxHeight + _LineSpace; } g.DrawPath(pen, path); - // ReSharper disable ImpureMethodCallOnReadonlyValueField g.FillPath(new SolidBrush(_TextColor.AsColor()), path); - // ReSharper restore ImpureMethodCallOnReadonlyValueField } } return CDraw.AddTexture(bmp); } } - private static List _SplitText(string text) + /// + /// Distributes the text among max. 3 lines maximizing the height of the line
+ /// Sets the Line field in the elements + ///
+ /// Text with set Width and Height + /// Image Width + /// Image Height + /// Factor for resizing the text (Maximizing this factor means maximizing the height) + private float _DistributeText(List elements, int width, int height) { - text = text.Replace(".", ". ").Replace("-", "- ").Replace(" ", " "); - string[] parts = text.Split(' '); - List lines = new List(); - string line = ""; - bool appendingName = false; - foreach (string part in parts) - { - if (part.Length == 1 && !Char.IsLetter(part, 0)) - { - //Add non-letter chars to the end of the line (e.g. in "19 - 20") - line = (line != "" ? " " : "") + part; - appendingName = false; - } - else if (part.Length == 1 || (part.Length == 2 && part[1] == '.')) - { - //We have something like the initials (e.g. "J. R. R. Tolkien") - Keep them together - if (!appendingName && line != "") - { - //When we are not already appending those, add the last line and start a new one - lines.Add(line); - line = part; - } - line = (line != "" ? " " : "") + part; - appendingName = true; - } - else - { - if (line != "") - lines.Add(line); - line = part; - appendingName = false; - } - } - if (line != "") - lines.Add(line); + int availableWidth = width - _TextMarginLeft - _TextMarginRight; + int availableHeight = height - _TextMarginTop - _TextMarginBottom; - int i; - //If we have more than 3 lines try to distribute them evenly - if (lines.Count > 3) - { - int len = lines.Sum(el => el.Length) + lines.Count - 1; - int avgLen = (int)Math.Ceiling(len / 3d); - i = 1; - while (i < lines.Count) - { - string prev = lines[i - 1]; - string cur = lines[i]; - if (prev.Length + cur.Length <= avgLen) - { - lines[i - 1] += " " + cur; - lines.RemoveAt(i); - } - else - i++; - } - } + float textHeight = elements.Select(el => el.Height).Max(); - //Further reduce the lines by taking the longest line as the max length - i = 1; - while (i < lines.Count) - { - string prev = lines[i - 1]; - string cur = lines[i]; - if (prev.Length + cur.Length <= lines.Max(el => el.Length)) - { - lines[i - 1] += " " + cur; - lines.RemoveAt(i); - } - else - i++; - } + //Try 1 line: + float textWidth = _GetWidth(elements, 0); + int maxHeight = Math.Min(availableHeight, _TextSize); + float factorH = maxHeight / textHeight; + float factorW = availableWidth / textWidth; + if (factorH <= factorW) + return factorH; //Limited by Height + float factor1 = factorW; - //If we still have more than 3 lines be more agressive - while (lines.Count > 3) + //Try 2 lines + if (elements.Count == 1) { - //Join the shortest line with shortest neighbour - int minLen = lines.Min(el => el.Length); - for (i = 0; i < lines.Count; i++) - { - if (lines[i].Length != minLen) - continue; - int other; - if (i == 0) - other = 1; - else if (i == lines.Count - 1) - other = i - 1; - else if (lines[i - 1].Length < lines[i + 1].Length) - other = i - 1; - else - other = i + 1; - lines[i] += " " + lines[other]; - lines.RemoveAt(other); + //Only 1 element -> 1 line + return factor1; + } + availableWidth -= _TextIndent; + maxHeight = Math.Min(availableHeight / 2 - _LineSpace, _TextSize); + factorH = maxHeight / textHeight; + if (factorH <= factor1) + return factor1; //Cannot get any bigger with more lines + int splitEl = _GetSplitElement(elements, textWidth / 2); + Debug.Assert(splitEl >= 0 && splitEl < elements.Count - 1); + float width1 = _GetWidth(elements, 0, splitEl); + float width2 = _GetWidth(elements, splitEl + 1); + factorW = availableWidth / Math.Max(width1, width2); + if (factorH <= factorW) + { + // Assertion: factorH>factor1 (check above) && factorH<=factorW --> Min(factorH,factorW)>factor 1 + // but for 2 lines it is limited by height + // --> Use 2 lines + _SetLine(elements, splitEl + 1, elements.Count - 1, 1); + return factorH; + } + // factor2 < factorH && factorH>factor1 + float factor2 = factorW; + + //Try 3 lines + maxHeight = Math.Min(availableHeight / 3 - 2 * _LineSpace, _TextSize); + factorH = maxHeight / textHeight; + if (elements.Count == 2 || factorH <= Math.Max(factor1, factor2)) + { + //Only 2 elements or cannot get any bigger + if (factor2 <= factor1) + return factor1; + _SetLine(elements, splitEl + 1, elements.Count - 1, 1); + return factor2; + } + int splitEl21 = _GetSplitElement(elements, textWidth / 3, false); + int splitEl22 = _GetSplitElement(elements, textWidth / 3, true, splitEl21 + 1); + Debug.Assert(splitEl21 >= 0 && splitEl21 < splitEl22 && splitEl22 < elements.Count - 1); + float width21 = _GetWidth(elements, 0, splitEl21); + float width22 = _GetWidth(elements, splitEl21 + 1, splitEl22); + float width23 = _GetWidth(elements, splitEl22 + 1); + factorW = availableWidth / Math.Max(Math.Max(width21, width22), width23); + float factor3 = Math.Min(factorH, factorW); + if (factor3 > Math.Max(factor1, factor2)) + { + _SetLine(elements, splitEl21 + 1, splitEl22, 1); + _SetLine(elements, splitEl22 + 1, elements.Count - 1, 2); + return factor3; + } + if (factor2 > factor1) + { + _SetLine(elements, splitEl + 1, elements.Count - 1, 1); + return factor2; + } + return factor1; + } + + /// + /// Returns the width of the elements with index from start to end of the list (inclusive) if they are put on 1 line + /// + /// + /// + /// + private static float _GetWidth(List list, int start) + { + return _GetWidth(list, start, list.Count - 1); + } + + /// + /// Returns the width of the elements with index from start to end (inclusive) if they are put on 1 line + /// + /// + /// + /// + /// + private static float _GetWidth(List list, int start, int end) + { + if (start < 0) + start = 0; + if (end >= list.Count) + end = list.Count - 1; + if (start > end) + return 0f; + float width = list.GetRange(start, end - start).Select(el => el.Width).Sum(); + return width + list[end].WidthTrimmed; + } + + private static void _SetLine(List list, int start, int end, int line) + { + for (int i = start; i <= end; i++) + list[i].Line = line; + } + + /// + /// Find the element after which to split the line + /// + /// List with elements, width must be set + /// The requested line width + /// Split on one point or two + /// First element on line + /// Index of element AFTER which to split + private static int _GetSplitElement(List elements, float requestedWidth, bool singleSplit = true, int startElement = 0) + { + //Assert we have enough elements + Debug.Assert(singleSplit && elements.Count - startElement >= 2 || !singleSplit && elements.Count - startElement >= 3); + float curWidth = 0f; + int splitEl; + for (splitEl = startElement; splitEl < elements.Count; splitEl++) + { + curWidth += elements[splitEl].Width; + if (curWidth >= requestedWidth) break; + } + + // At this point we can either keep splitEl on this line for move it to the next line + // ==> line with splitEl is to long, without it is to short. so find the best option + // Check if line starts or ends with a long word, than put that on a single line + if (splitEl == startElement) + return splitEl; + // These 2 conditions also cover if the text fits on 1 line + if (!singleSplit && splitEl >= elements.Count - 2) + return elements.Count - 3; // Make sure we have 2 elements left if in multi split mode! + if (splitEl >= elements.Count - 1) + return elements.Count - 2; // Make sure we have 1 element left! + + float diffWith = curWidth - requestedWidth; + float diffWithout = requestedWidth - (curWidth - elements[splitEl].Width); + if (!singleSplit) + { + // If we split in 3 lines, add the error of the next 2 lines to the current error for both cases + int splitEl2 = _GetSplitElement(elements, requestedWidth, true, splitEl + 1); + int splitEl3 = _GetSplitElement(elements, requestedWidth, true, splitEl); + diffWith += Math.Abs(requestedWidth - _GetWidth(elements, splitEl + 1, splitEl2)) + Math.Abs(requestedWidth - _GetWidth(elements, splitEl2 + 1)); + diffWithout += Math.Abs(requestedWidth - _GetWidth(elements, splitEl, splitEl3)) + Math.Abs(requestedWidth - _GetWidth(elements, splitEl3 + 1)); + } + float diff = diffWith - diffWithout; + //Differences below this value are considered acceptable + float equalDist = requestedWidth * 0.025f; + if (diff > equalDist) + return splitEl - 1; // width error with element is much higher than without -> put element on next line + if (diff < -equalDist) + return splitEl; // width error with element is much less than without -> keep element on line + + //The real split would be somewhere in the middle of the element so we can move it to either line + string tmp = elements[splitEl].Text.TrimEnd(null); + char lastCharWith = tmp[tmp.Length - 1]; + tmp = elements[splitEl - 1].Text.TrimEnd(null); + char lastCharWithout = tmp[tmp.Length - 1]; + //Check if last chars are special chars + if (Char.IsLetterOrDigit(lastCharWith)) + { + if (Char.IsLetterOrDigit(lastCharWithout)) + return startElement == 0 ? splitEl : splitEl - 1; //Both are alphanumeric, favor longer first lines but shorter middle lines + return splitEl - 1; //Split after non-alphanumeric char + } + if (Char.IsLetterOrDigit(lastCharWithout)) + return splitEl; //Split after non-alphanumeric char + + int indexWith = Array.IndexOf(_SplitCharPreferences, lastCharWith); + int indexWithout = Array.IndexOf(_SplitCharPreferences, lastCharWithout); + if (indexWith <= indexWithout) + return startElement == 0 ? splitEl : splitEl - 1; //favor longer first lines but shorter middle lines + return splitEl - 1; + } + + /// + /// Split the text in parts that can then be distributet among the lines + /// + /// + /// + private static IEnumerable _SplitText(string text) + { + Debug.Assert(!String.IsNullOrWhiteSpace(text)); + + text = text.Trim().TrimMultipleWs(); + List lines = new List(); + if (text.Length == 1) + { + lines.Add(text); + return lines; + } + //Split the text on non-letter chars + int curStart = 0; + for (int i = 1; i < text.Length - 1; i++) + { + if ((Array.IndexOf(_SplitCharAfter, text[i]) >= 0 && (Char.IsLetterOrDigit(text, i + 1) || Array.IndexOf(_SplitCharBefore, text[i + 1]) >= 0)) || + (Char.IsLetterOrDigit(text, i) && Array.IndexOf(_SplitCharBefore, text[i + 1]) >= 0)) + { + lines.Add(text.Substring(curStart, i - curStart + 1)); + curStart = ++i; + } + } + //Add the rest + lines.Add(text.Substring(curStart)); + + //Check for initials (like J. R.R. Tolkien) and join them + curStart = -1; + string curText = ""; + for (int i = 0; i < lines.Count; i++) + { + string part = lines[i].Trim(); + if (part.Length == 2 && part[1] == '.') + { + //Initials found, continue or start new + if (curStart < 0) + curStart = i; + curText += lines[i]; + } + else if (curStart >= 0) + { + //Initials end + if (curStart < i - 1) + { + //More than 1 part + lines[curStart] = curText; + lines.RemoveRange(curStart + 1, i - curStart - 1); + i = curStart; + } + curText = ""; + curStart = -1; + } + } + //Handle last part + if (curStart >= 0) + { + //Initials end + if (curStart < lines.Count - 1) + { + //More than 1 part + lines[curStart] = curText; + lines.RemoveRange(curStart + 1, lines.Count - curStart - 1); + } + } + + // Remove single char lines + for (int i = 0; i < lines.Count; i++) + { + string part = lines[i]; + if (part.Trim().Length == 1) + { + lines.RemoveAt(i); + if (i == 0) + lines[0] = part + lines[0]; + else + { + lines[i - 1] += part; + i--; + } } } return lines; diff --git a/VocaluxeLib/Extensions.cs b/VocaluxeLib/Extensions.cs index f04d5f40..ae3f9c4c 100644 --- a/VocaluxeLib/Extensions.cs +++ b/VocaluxeLib/Extensions.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text.RegularExpressions; namespace VocaluxeLib { @@ -91,28 +92,13 @@ namespace VocaluxeLib return value.IndexOf(other, StringComparison.CurrentCultureIgnoreCase) >= 0; } + private static Regex _MultipleWhiteSpaceRegEx = new Regex(@" {2,}"); + public static string TrimMultipleWs(this string value) { if (string.IsNullOrEmpty(value)) return ""; - int start = 0; - int len = value.Length; - while (value[start] == ' ') - { - start++; - if (start >= len) - return ""; - } - if (start > 0) - start--; - int end = --len; - while (value[end] == ' ') - end--; - if (end < len) - end++; - if (end - start < len) - value = value.Substring(start, end - start + 1); - return value; + return _MultipleWhiteSpaceRegEx.Replace(value, " "); } /// diff --git a/VocaluxeLib/Structs.cs b/VocaluxeLib/Structs.cs index d0c73628..f2b09991 100644 --- a/VocaluxeLib/Structs.cs +++ b/VocaluxeLib/Structs.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Drawing; using System.Windows.Forms; using VocaluxeLib.Songs; @@ -48,6 +49,7 @@ namespace VocaluxeLib A = color.A; } + [Pure] public Color AsColor() { return Color.FromArgb((int)(A * 255), (int)(R * 255), (int)(G * 255), (int)(B * 255));