diff --git a/TagsCloudPainter/CloudDrawer/CloudDrawer.cs b/TagsCloudPainter/CloudDrawer/CloudDrawer.cs new file mode 100644 index 000000000..470537fbe --- /dev/null +++ b/TagsCloudPainter/CloudDrawer/CloudDrawer.cs @@ -0,0 +1,57 @@ +using System.Drawing; +using System.Drawing.Drawing2D; +using TagsCloudPainter.Settings.Cloud; +using TagsCloudPainter.Settings.Tag; + +namespace TagsCloudPainter.Drawer; + +public class CloudDrawer : ICloudDrawer +{ + private readonly ICloudSettings cloudSettings; + private readonly ITagSettings tagSettings; + + public CloudDrawer(ITagSettings tagSettings, ICloudSettings cloudSettings) + { + this.tagSettings = tagSettings ?? throw new ArgumentNullException(nameof(tagSettings)); + this.cloudSettings = cloudSettings ?? throw new ArgumentNullException(nameof(cloudSettings)); + } + + public Bitmap DrawCloud(TagsCloud cloud, int imageWidth, int imageHeight) + { + if (cloud.Tags.Count == 0) + throw new ArgumentException("rectangles are empty"); + if (imageWidth <= 0 || imageHeight <= 0) + throw new ArgumentException("either width or height of rectangle size is not positive"); + + var drawingScale = CalculateObjectDrawingScale(cloud.GetWidth(), cloud.GetHeight(), imageWidth, imageHeight); + var bitmap = new Bitmap(imageWidth, imageHeight); + using var graphics = Graphics.FromImage(bitmap); + using var pen = new Pen(tagSettings.TagColor); + { + graphics.TranslateTransform(-cloud.Center.X, -cloud.Center.Y); + graphics.ScaleTransform(drawingScale, drawingScale, MatrixOrder.Append); + graphics.TranslateTransform(cloud.Center.X, cloud.Center.Y, MatrixOrder.Append); + graphics.Clear(cloudSettings.BackgroundColor); + foreach (var tag in cloud.Tags) + { + var font = new Font(tagSettings.TagFontName, tag.Item1.FontSize); + graphics.DrawString(tag.Item1.Value, font, pen.Brush, tag.Item2.Location); + } + } + ; + return bitmap; + } + + public static float CalculateObjectDrawingScale(float width, float height, float imageWidth, float imageHeight) + { + var scale = 1f; + var scaleAccuracy = 0.05f; + var widthScale = scale; + var heightScale = scale; + if (width * scale > imageWidth) + widthScale = imageWidth / width - scaleAccuracy; + if (height * scale > imageHeight) + heightScale = imageHeight / height - scaleAccuracy; + return Math.Min(widthScale, heightScale); + } +} \ No newline at end of file diff --git a/TagsCloudPainter/CloudDrawer/ICloudDrawer.cs b/TagsCloudPainter/CloudDrawer/ICloudDrawer.cs new file mode 100644 index 000000000..6a8365cdb --- /dev/null +++ b/TagsCloudPainter/CloudDrawer/ICloudDrawer.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +namespace TagsCloudPainter.Drawer; + +public interface ICloudDrawer +{ + public Bitmap DrawCloud(TagsCloud cloud, int imageWidth, int imageHeight); +} \ No newline at end of file diff --git a/TagsCloudPainter/CloudLayouter/ICloudLayouter.cs b/TagsCloudPainter/CloudLayouter/ICloudLayouter.cs new file mode 100644 index 000000000..659f25e1d --- /dev/null +++ b/TagsCloudPainter/CloudLayouter/ICloudLayouter.cs @@ -0,0 +1,11 @@ +using System.Drawing; +using TagsCloudPainter.Tags; + +namespace TagsCloudPainter.CloudLayouter; + +public interface ICloudLayouter : IResetable +{ + Rectangle PutNextTag(Tag tag); + TagsCloud GetCloud(); + void PutTags(List tags); +} \ No newline at end of file diff --git a/TagsCloudPainter/CloudLayouter/TagsCloudLayouter.cs b/TagsCloudPainter/CloudLayouter/TagsCloudLayouter.cs new file mode 100644 index 000000000..65b8f7791 --- /dev/null +++ b/TagsCloudPainter/CloudLayouter/TagsCloudLayouter.cs @@ -0,0 +1,65 @@ +using System.Drawing; +using TagsCloudPainter.Extensions; +using TagsCloudPainter.FormPointer; +using TagsCloudPainter.Settings.Cloud; +using TagsCloudPainter.Settings.Tag; +using TagsCloudPainter.Sizer; +using TagsCloudPainter.Tags; + +namespace TagsCloudPainter.CloudLayouter; + +public class TagsCloudLayouter : ICloudLayouter +{ + private readonly ICloudSettings cloudSettings; + private readonly IFormPointer formPointer; + private readonly IStringSizer stringSizer; + private readonly ITagSettings tagSettings; + private TagsCloud cloud; + + public TagsCloudLayouter( + ICloudSettings cloudSettings, + IFormPointer formPointer, + ITagSettings tagSettings, + IStringSizer stringSizer) + { + this.cloudSettings = cloudSettings ?? throw new ArgumentNullException(nameof(cloudSettings)); + this.formPointer = formPointer ?? throw new ArgumentNullException(nameof(formPointer)); + this.tagSettings = tagSettings ?? throw new ArgumentNullException(nameof(tagSettings)); + this.stringSizer = stringSizer ?? throw new ArgumentNullException(); + cloud = new TagsCloud(cloudSettings.CloudCenter, []); + } + + public Rectangle PutNextTag(Tag tag) + { + var tagSize = stringSizer.GetStringSize(tag.Value, tagSettings.TagFontName, tag.FontSize); + if (tagSize.Height <= 0 || tagSize.Width <= 0) + throw new ArgumentException("either width or height of rectangle size is not possitive"); + + var nextRectangle = formPointer.GetNextPoint().GetRectangle(tagSize); + while (cloud.Tags.Any(pair => pair.Item2.IntersectsWith(nextRectangle))) + nextRectangle = formPointer.GetNextPoint().GetRectangle(tagSize); + + cloud.AddTag(tag, nextRectangle); + + return nextRectangle; + } + + public void PutTags(List tags) + { + if (tags.Count == 0) + throw new ArgumentException("пустые размеры"); + foreach (var tag in tags) + PutNextTag(tag); + } + + public TagsCloud GetCloud() + { + return new TagsCloud(cloud.Center, cloud.Tags); + } + + public void Reset() + { + formPointer.Reset(); + cloud = new TagsCloud(cloudSettings.CloudCenter, []); + } +} \ No newline at end of file diff --git a/TagsCloudPainter/Extensions/PointExtension.cs b/TagsCloudPainter/Extensions/PointExtension.cs new file mode 100644 index 000000000..97cb37bc1 --- /dev/null +++ b/TagsCloudPainter/Extensions/PointExtension.cs @@ -0,0 +1,14 @@ +using System.Drawing; + +namespace TagsCloudPainter.Extensions; + +public static class PointExtension +{ + public static Rectangle GetRectangle(this Point center, Size size) + { + var x = center.X - size.Width / 2; + var y = center.Y - size.Height / 2; + + return new Rectangle(new Point(x, y), size); + } +} \ No newline at end of file diff --git a/TagsCloudPainter/Extensions/RectangleExtensions.cs b/TagsCloudPainter/Extensions/RectangleExtensions.cs new file mode 100644 index 000000000..11939e807 --- /dev/null +++ b/TagsCloudPainter/Extensions/RectangleExtensions.cs @@ -0,0 +1,13 @@ +using System.Drawing; + +namespace TagsCloudPainter.Extensions; + +public static class RectangleExtensions +{ + public static Point GetCenter(this Rectangle rectangle) + { + var x = rectangle.X; + var y = rectangle.Y; + return new Point(x + rectangle.Width / 2, y + rectangle.Height / 2); + } +} \ No newline at end of file diff --git a/TagsCloudPainter/FileReader/DocFileReader.cs b/TagsCloudPainter/FileReader/DocFileReader.cs new file mode 100644 index 000000000..4eb8c4597 --- /dev/null +++ b/TagsCloudPainter/FileReader/DocFileReader.cs @@ -0,0 +1,17 @@ +using Spire.Doc; + +namespace TagsCloudPainter.FileReader; + +public class DocFileReader : IFileReader +{ + public HashSet SupportedExtensions => new() { ".doc", ".docx" }; + + public string ReadFile(string path) + { + var doc = new Document(); + doc.LoadFromFile(path); + var text = doc.GetText(); + var lastIndexOfSpirePart = text.IndexOf(Environment.NewLine); + return text.Substring(lastIndexOfSpirePart + 2).Trim(); + } +} \ No newline at end of file diff --git a/TagsCloudPainter/FileReader/IFileReader.cs b/TagsCloudPainter/FileReader/IFileReader.cs new file mode 100644 index 000000000..62f7f12c7 --- /dev/null +++ b/TagsCloudPainter/FileReader/IFileReader.cs @@ -0,0 +1,7 @@ +namespace TagsCloudPainter.FileReader; + +public interface IFileReader +{ + public HashSet SupportedExtensions { get; } + public string ReadFile(string path); +} \ No newline at end of file diff --git a/TagsCloudPainter/FileReader/IFormatFileReader.cs b/TagsCloudPainter/FileReader/IFormatFileReader.cs new file mode 100644 index 000000000..c5ccab016 --- /dev/null +++ b/TagsCloudPainter/FileReader/IFormatFileReader.cs @@ -0,0 +1,6 @@ +namespace TagsCloudPainter.FileReader; + +public interface IFormatFileReader +{ + public TFormat ReadFile(string path); +} \ No newline at end of file diff --git a/TagsCloudPainter/FileReader/TextFileReader.cs b/TagsCloudPainter/FileReader/TextFileReader.cs new file mode 100644 index 000000000..677976f97 --- /dev/null +++ b/TagsCloudPainter/FileReader/TextFileReader.cs @@ -0,0 +1,26 @@ +namespace TagsCloudPainter.FileReader; + +public class TextFileReader : IFormatFileReader +{ + private readonly IEnumerable fileReaders; + + public TextFileReader(IEnumerable fileReaders) + { + this.fileReaders = fileReaders; + } + + public string ReadFile(string path) + { + if (!File.Exists(path)) + throw new FileNotFoundException(); + + var fileExtension = Path.GetExtension(path); + var fileReader = + fileReaders.FirstOrDefault(fileReader => fileReader.SupportedExtensions.Contains(fileExtension)); + + return fileReader is not null + ? fileReader.ReadFile(path) + : throw new ArgumentException($"Incorrect file extension {fileExtension}. " + + $"Supported file extensions: txt, doc, docx"); + } +} \ No newline at end of file diff --git a/TagsCloudPainter/FileReader/TxtFileReader.cs b/TagsCloudPainter/FileReader/TxtFileReader.cs new file mode 100644 index 000000000..9e9c3450e --- /dev/null +++ b/TagsCloudPainter/FileReader/TxtFileReader.cs @@ -0,0 +1,11 @@ +namespace TagsCloudPainter.FileReader; + +public class TxtFileReader : IFileReader +{ + public HashSet SupportedExtensions => new() { ".txt" }; + + public string ReadFile(string path) + { + return File.ReadAllText(path).Trim(); + } +} \ No newline at end of file diff --git a/TagsCloudPainter/FormPointer/ArchimedeanSpiralPointer.cs b/TagsCloudPainter/FormPointer/ArchimedeanSpiralPointer.cs new file mode 100644 index 000000000..943189c69 --- /dev/null +++ b/TagsCloudPainter/FormPointer/ArchimedeanSpiralPointer.cs @@ -0,0 +1,41 @@ +using System.Drawing; +using TagsCloudPainter.Settings.Cloud; +using TagsCloudPainter.Settings.FormPointer; + +namespace TagsCloudPainter.FormPointer; + +public class ArchimedeanSpiralPointer : IFormPointer +{ + private readonly ICloudSettings cloudSettings; + private readonly ISpiralPointerSettings spiralPointerSettings; + private double сurrentDifference; + + public ArchimedeanSpiralPointer(ICloudSettings cloudSettings, ISpiralPointerSettings spiralPointerSettings) + { + if (spiralPointerSettings.Step <= 0 + || spiralPointerSettings.RadiusConst <= 0 + || spiralPointerSettings.AngleConst <= 0) + throw new ArgumentException("either step or radius or angle is not possitive"); + this.cloudSettings = cloudSettings ?? throw new ArgumentNullException(nameof(cloudSettings)); + this.spiralPointerSettings = + spiralPointerSettings ?? throw new ArgumentNullException(nameof(spiralPointerSettings)); + сurrentDifference = 0; + } + + private double Angle => сurrentDifference * spiralPointerSettings.AngleConst; + private double Radius => сurrentDifference * spiralPointerSettings.RadiusConst; + + public Point GetNextPoint() + { + сurrentDifference += spiralPointerSettings.Step; + var x = cloudSettings.CloudCenter.X + (int)(Radius * Math.Cos(Angle)); + var y = cloudSettings.CloudCenter.Y + (int)(Radius * Math.Sin(Angle)); + + return new Point(x, y); + } + + public void Reset() + { + сurrentDifference = 0; + } +} \ No newline at end of file diff --git a/TagsCloudPainter/FormPointer/IFormPointer.cs b/TagsCloudPainter/FormPointer/IFormPointer.cs new file mode 100644 index 000000000..ca3120df2 --- /dev/null +++ b/TagsCloudPainter/FormPointer/IFormPointer.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +namespace TagsCloudPainter.FormPointer; + +public interface IFormPointer : IResetable +{ + Point GetNextPoint(); +} \ No newline at end of file diff --git a/TagsCloudPainter/IResetable.cs b/TagsCloudPainter/IResetable.cs new file mode 100644 index 000000000..0bb4cef54 --- /dev/null +++ b/TagsCloudPainter/IResetable.cs @@ -0,0 +1,6 @@ +namespace TagsCloudPainter; + +public interface IResetable +{ + void Reset(); +} \ No newline at end of file diff --git a/TagsCloudPainter/Parser/BoringTextParser.cs b/TagsCloudPainter/Parser/BoringTextParser.cs new file mode 100644 index 000000000..1e0e8372d --- /dev/null +++ b/TagsCloudPainter/Parser/BoringTextParser.cs @@ -0,0 +1,32 @@ +using TagsCloudPainter.Settings; + +namespace TagsCloudPainter.Parser; + +public class BoringTextParser : ITextParser +{ + private static readonly string[] _separators = [" ", ". ", ", ", "; ", "-", "—", Environment.NewLine]; + private readonly ITextSettings textSettings; + + public BoringTextParser(ITextSettings textSettings) + { + this.textSettings = textSettings ?? throw new ArgumentNullException(nameof(textSettings)); + } + + public List ParseText(string text) + { + var boringWords = GetBoringWords(textSettings.BoringText); + var words = text.Split(_separators, StringSplitOptions.RemoveEmptyEntries); + return words.Select(word => word.ToLower()).Where(word => !boringWords.Contains(word)).ToList(); + } + + public HashSet GetBoringWords(string text) + { + var words = text.Split(Environment.NewLine); + var boringWords = new HashSet(); + + foreach (var word in words) + boringWords.Add(word.ToLower()); + + return boringWords; + } +} \ No newline at end of file diff --git a/TagsCloudPainter/Parser/ITextParser.cs b/TagsCloudPainter/Parser/ITextParser.cs new file mode 100644 index 000000000..8f5f3bf96 --- /dev/null +++ b/TagsCloudPainter/Parser/ITextParser.cs @@ -0,0 +1,6 @@ +namespace TagsCloudPainter.Parser; + +public interface ITextParser +{ + List ParseText(string text); +} \ No newline at end of file diff --git a/TagsCloudPainter/Settings/Cloud/CloudSettings.cs b/TagsCloudPainter/Settings/Cloud/CloudSettings.cs new file mode 100644 index 000000000..0303aaa9d --- /dev/null +++ b/TagsCloudPainter/Settings/Cloud/CloudSettings.cs @@ -0,0 +1,9 @@ +using System.Drawing; + +namespace TagsCloudPainter.Settings.Cloud; + +public class CloudSettings : ICloudSettings +{ + public Point CloudCenter { get; set; } + public Color BackgroundColor { get; set; } +} \ No newline at end of file diff --git a/TagsCloudPainter/Settings/Cloud/ICloudSettings.cs b/TagsCloudPainter/Settings/Cloud/ICloudSettings.cs new file mode 100644 index 000000000..5f003a91f --- /dev/null +++ b/TagsCloudPainter/Settings/Cloud/ICloudSettings.cs @@ -0,0 +1,9 @@ +using System.Drawing; + +namespace TagsCloudPainter.Settings.Cloud; + +public interface ICloudSettings +{ + public Point CloudCenter { get; set; } + public Color BackgroundColor { get; set; } +} \ No newline at end of file diff --git a/TagsCloudPainter/Settings/FormPointer/ISpiralPointerSettings.cs b/TagsCloudPainter/Settings/FormPointer/ISpiralPointerSettings.cs new file mode 100644 index 000000000..0f7761089 --- /dev/null +++ b/TagsCloudPainter/Settings/FormPointer/ISpiralPointerSettings.cs @@ -0,0 +1,8 @@ +namespace TagsCloudPainter.Settings.FormPointer; + +public interface ISpiralPointerSettings +{ + public double Step { get; set; } + public double RadiusConst { get; set; } + public double AngleConst { get; set; } +} \ No newline at end of file diff --git a/TagsCloudPainter/Settings/FormPointer/SpiralPointerSettings.cs b/TagsCloudPainter/Settings/FormPointer/SpiralPointerSettings.cs new file mode 100644 index 000000000..54bb6105a --- /dev/null +++ b/TagsCloudPainter/Settings/FormPointer/SpiralPointerSettings.cs @@ -0,0 +1,8 @@ +namespace TagsCloudPainter.Settings.FormPointer; + +public class SpiralPointerSettings : ISpiralPointerSettings +{ + public double Step { get; set; } = 1; + public double RadiusConst { get; set; } = 1; + public double AngleConst { get; set; } = 1; +} \ No newline at end of file diff --git a/TagsCloudPainter/Settings/Tag/ITagSettings.cs b/TagsCloudPainter/Settings/Tag/ITagSettings.cs new file mode 100644 index 000000000..3670f6362 --- /dev/null +++ b/TagsCloudPainter/Settings/Tag/ITagSettings.cs @@ -0,0 +1,10 @@ +using System.Drawing; + +namespace TagsCloudPainter.Settings.Tag; + +public interface ITagSettings +{ + public int TagFontSize { get; set; } + public string TagFontName { get; set; } + public Color TagColor { get; set; } +} \ No newline at end of file diff --git a/TagsCloudPainter/Settings/Tag/TagSettings.cs b/TagsCloudPainter/Settings/Tag/TagSettings.cs new file mode 100644 index 000000000..fc1d91244 --- /dev/null +++ b/TagsCloudPainter/Settings/Tag/TagSettings.cs @@ -0,0 +1,11 @@ +using System.Drawing; +using System.Drawing.Text; + +namespace TagsCloudPainter.Settings.Tag; + +public class TagSettings : ITagSettings +{ + public int TagFontSize { get; set; } + public string TagFontName { get; set; } = new InstalledFontCollection().Families.FirstOrDefault().Name; + public Color TagColor { get; set; } +} \ No newline at end of file diff --git a/TagsCloudPainter/Settings/Text/ITextSettings.cs b/TagsCloudPainter/Settings/Text/ITextSettings.cs new file mode 100644 index 000000000..a7c852cbf --- /dev/null +++ b/TagsCloudPainter/Settings/Text/ITextSettings.cs @@ -0,0 +1,6 @@ +namespace TagsCloudPainter.Settings; + +public interface ITextSettings +{ + public string BoringText { get; set; } +} \ No newline at end of file diff --git a/TagsCloudPainter/Settings/Text/TextSettings.cs b/TagsCloudPainter/Settings/Text/TextSettings.cs new file mode 100644 index 000000000..ed514c209 --- /dev/null +++ b/TagsCloudPainter/Settings/Text/TextSettings.cs @@ -0,0 +1,6 @@ +namespace TagsCloudPainter.Settings; + +public class TextSettings : ITextSettings +{ + public string BoringText { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/TagsCloudPainter/Sizer/IStringSizer.cs b/TagsCloudPainter/Sizer/IStringSizer.cs new file mode 100644 index 000000000..4cca958b4 --- /dev/null +++ b/TagsCloudPainter/Sizer/IStringSizer.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +namespace TagsCloudPainter.Sizer; + +public interface IStringSizer +{ + Size GetStringSize(string value, string fontName, float fontSize); +} \ No newline at end of file diff --git a/TagsCloudPainter/Sizer/StringSizer.cs b/TagsCloudPainter/Sizer/StringSizer.cs new file mode 100644 index 000000000..76dffbb66 --- /dev/null +++ b/TagsCloudPainter/Sizer/StringSizer.cs @@ -0,0 +1,16 @@ +using System.Drawing; + +namespace TagsCloudPainter.Sizer; + +public class StringSizer : IStringSizer +{ + public Size GetStringSize(string value, string fontName, float fontSize) + { + using var graphics = Graphics.FromHwnd(IntPtr.Zero); + using var font = new Font(fontName, fontSize); + { + var size = graphics.MeasureString(value, font).ToSize(); + return size; + } + } +} \ No newline at end of file diff --git a/TagsCloudPainter/Tags/ITagsBuilder.cs b/TagsCloudPainter/Tags/ITagsBuilder.cs new file mode 100644 index 000000000..dab92a984 --- /dev/null +++ b/TagsCloudPainter/Tags/ITagsBuilder.cs @@ -0,0 +1,6 @@ +namespace TagsCloudPainter.Tags; + +public interface ITagsBuilder +{ + public List GetTags(List words); +} \ No newline at end of file diff --git a/TagsCloudPainter/Tags/Tag.cs b/TagsCloudPainter/Tags/Tag.cs new file mode 100644 index 000000000..bdf1617c8 --- /dev/null +++ b/TagsCloudPainter/Tags/Tag.cs @@ -0,0 +1,16 @@ +namespace TagsCloudPainter.Tags; + +public class Tag +{ + public Tag(string value, float fontSize, int count) + { + ArgumentException.ThrowIfNullOrEmpty(value, nameof(value)); + Value = value; + FontSize = fontSize; + Count = count; + } + + public string Value { get; private set; } + public float FontSize { get; private set; } + public int Count { get; private set; } +} \ No newline at end of file diff --git a/TagsCloudPainter/Tags/TagsBuilder.cs b/TagsCloudPainter/Tags/TagsBuilder.cs new file mode 100644 index 000000000..57632c48e --- /dev/null +++ b/TagsCloudPainter/Tags/TagsBuilder.cs @@ -0,0 +1,44 @@ +using TagsCloudPainter.Settings.Tag; + +namespace TagsCloudPainter.Tags; + +public class TagsBuilder : ITagsBuilder +{ + private readonly ITagSettings _settings; + + public TagsBuilder(ITagSettings settings) + { + _settings = settings ?? throw new ArgumentNullException(nameof(settings)); + } + + public List GetTags(List words) + { + var countedWords = CountWords(words); + var tags = new List(); + + foreach (var wordWithCount in countedWords) + { + var tagFontSize = GetTagFontSize(_settings.TagFontSize, wordWithCount.Value, countedWords.Count); + var tag = new Tag(wordWithCount.Key, tagFontSize, wordWithCount.Value); + tags.Add(tag); + } + + return tags; + } + + private static Dictionary CountWords(List words) + { + var countedWords = new Dictionary(); + + foreach (var word in words) + if (!countedWords.TryAdd(word, 1)) + countedWords[word] += 1; + + return countedWords; + } + + private static float GetTagFontSize(int fontSize, int tagCount, int wordsAmount) + { + return (float)tagCount / wordsAmount * fontSize * 100; + } +} \ No newline at end of file diff --git a/TagsCloudPainter/TagsCloud.cs b/TagsCloudPainter/TagsCloud.cs new file mode 100644 index 000000000..a46fe130a --- /dev/null +++ b/TagsCloudPainter/TagsCloud.cs @@ -0,0 +1,31 @@ +using System.Drawing; +using TagsCloudPainter.Tags; + +namespace TagsCloudPainter; + +public class TagsCloud +{ + public TagsCloud(Point center, List<(Tag, Rectangle)> tags) + { + Center = center; + Tags = tags ?? []; + } + + public Point Center { get; private set; } + public List<(Tag, Rectangle)> Tags { get; } + + public void AddTag(Tag tag, Rectangle rectangle) + { + Tags.Add((tag, rectangle)); + } + + public int GetWidth() + { + return Tags.Max(pair => pair.Item2.X) - Tags.Min(pair => pair.Item2.X); + } + + public int GetHeight() + { + return Tags.Max(pair => pair.Item2.Y) - Tags.Min(pair => pair.Item2.Y); + } +} \ No newline at end of file diff --git a/TagsCloudPainter/TagsCloudPainter.csproj b/TagsCloudPainter/TagsCloudPainter.csproj new file mode 100644 index 000000000..5d7a5062b --- /dev/null +++ b/TagsCloudPainter/TagsCloudPainter.csproj @@ -0,0 +1,15 @@ + + + + Library + net8.0 + enable + enable + + + + + + + + diff --git a/TagsCloudPainterApplication/Actions/DrawTagCloudAction.cs b/TagsCloudPainterApplication/Actions/DrawTagCloudAction.cs new file mode 100644 index 000000000..adbb28b2c --- /dev/null +++ b/TagsCloudPainterApplication/Actions/DrawTagCloudAction.cs @@ -0,0 +1,108 @@ +using TagsCloudPainter; +using TagsCloudPainter.CloudLayouter; +using TagsCloudPainter.Drawer; +using TagsCloudPainter.FileReader; +using TagsCloudPainter.Parser; +using TagsCloudPainter.Tags; +using TagsCloudPainterApplication.Infrastructure; +using TagsCloudPainterApplication.Infrastructure.Settings; +using TagsCloudPainterApplication.Infrastructure.Settings.FilesSource; +using TagsCloudPainterApplication.Infrastructure.Settings.Image; +using TagsCloudPainterApplication.Infrastructure.Settings.TagsCloud; + +namespace TagsCloudPainterApplication.Actions; + +public class DrawTagCloudAction : IUiAction +{ + private readonly ICloudDrawer cloudDrawer; + private readonly ICloudLayouter cloudLayouter; + private readonly IFilesSourceSettings filesSourceSettings; + private readonly IImageHolder imageHolder; + private readonly IImageSettings imageSettings; + private readonly Palette palette; + private readonly ITagsBuilder tagsBuilder; + private readonly ITagsCloudSettings tagsCloudSettings; + private readonly IFormatFileReader textFileReader; + private readonly ITextParser textParser; + + public DrawTagCloudAction( + IImageSettings imageSettings, + ITagsCloudSettings tagsCloudSettings, + IFilesSourceSettings filesSourceSettings, + IImageHolder imageHolder, + ICloudDrawer cloudDrawer, + ICloudLayouter cloudLayouter, + ITagsBuilder tagsBuilder, + ITextParser textParser, + IFormatFileReader textFileReader, + Palette palette) + { + this.cloudDrawer = cloudDrawer ?? throw new ArgumentNullException(nameof(cloudDrawer)); + this.cloudLayouter = cloudLayouter ?? throw new ArgumentNullException(nameof(cloudLayouter)); + this.tagsBuilder = tagsBuilder ?? throw new ArgumentNullException(nameof(tagsBuilder)); + this.textParser = textParser ?? throw new ArgumentNullException(nameof(textParser)); + this.textFileReader = textFileReader ?? throw new ArgumentNullException(nameof(textFileReader)); + this.imageSettings = imageSettings ?? throw new ArgumentNullException(nameof(imageSettings)); + this.tagsCloudSettings = tagsCloudSettings ?? throw new ArgumentNullException(nameof(tagsCloudSettings)); + this.imageHolder = imageHolder ?? throw new ArgumentNullException(nameof(imageHolder)); + this.filesSourceSettings = filesSourceSettings ?? throw new ArgumentNullException(nameof(filesSourceSettings)); + this.palette = palette ?? throw new ArgumentNullException(nameof(palette)); + } + + public string Category => "Облако тэгов"; + + public string Name => "Нарисовать"; + + public string Description => "Нарисовать облако тэгов"; + + public void Perform() + { + var wordsFilePath = GetFilePath(); + if (string.IsNullOrEmpty(wordsFilePath)) + return; + + SettingsForm.For(tagsCloudSettings).ShowDialog(); + tagsCloudSettings.CloudSettings.BackgroundColor = palette.BackgroundColor; + tagsCloudSettings.TagSettings.TagColor = palette.PrimaryColor; + + var wordsText = textFileReader.ReadFile(wordsFilePath); + tagsCloudSettings.TextSettings.BoringText = textFileReader.ReadFile(filesSourceSettings.BoringTextFilePath); + var parsedWords = textParser.ParseText(wordsText); + var cloud = GetCloud(parsedWords); + DrawCloud(cloud); + } + + private static string GetFilePath() + { + OpenFileDialog fileDialog = new() + { + InitialDirectory = Environment.CurrentDirectory, + Filter = + "Текстовый файл txt (*.txt)|*.txt|Текстовый файл doc (*.doc)|*.doc|Текстовый файл docx (*.docx)|*.docx", + FilterIndex = 0, + RestoreDirectory = true + }; + fileDialog.ShowDialog(); + return fileDialog.FileName; + } + + private TagsCloud GetCloud(List words) + { + var tags = tagsBuilder.GetTags(words); + cloudLayouter.Reset(); + cloudLayouter.PutTags(tags); + var cloud = cloudLayouter.GetCloud(); + return cloud; + } + + private void DrawCloud(TagsCloud cloud) + { + using var bitmap = cloudDrawer.DrawCloud(cloud, imageSettings.Width, imageSettings.Height); + using (var graphics = imageHolder.StartDrawing()) + { + graphics.DrawImage(bitmap, new Point(0, 0)); + } + + imageHolder.UpdateUi(); + } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Actions/FileSourceSettingsAction.cs b/TagsCloudPainterApplication/Actions/FileSourceSettingsAction.cs new file mode 100644 index 000000000..c8a5d9c61 --- /dev/null +++ b/TagsCloudPainterApplication/Actions/FileSourceSettingsAction.cs @@ -0,0 +1,23 @@ +using TagsCloudPainterApplication.Infrastructure.Settings; +using TagsCloudPainterApplication.Infrastructure.Settings.FilesSource; + +namespace TagsCloudPainterApplication.Actions; + +public class FileSourceSettingsAction : IUiAction +{ + private readonly IFilesSourceSettings filesSourceSettings; + + public FileSourceSettingsAction(IFilesSourceSettings filesSourceSettings) + { + this.filesSourceSettings = filesSourceSettings ?? throw new ArgumentNullException(nameof(filesSourceSettings)); + } + + public string Category => "Настройки"; + public string Name => "Ресурсы"; + public string Description => "Укажите ресурсы"; + + public void Perform() + { + SettingsForm.For(filesSourceSettings).ShowDialog(); + } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Actions/IUiAction.cs b/TagsCloudPainterApplication/Actions/IUiAction.cs new file mode 100644 index 000000000..870b077fd --- /dev/null +++ b/TagsCloudPainterApplication/Actions/IUiAction.cs @@ -0,0 +1,9 @@ +namespace TagsCloudPainterApplication.Actions; + +public interface IUiAction +{ + string Category { get; } + string Name { get; } + string Description { get; } + void Perform(); +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Actions/ImageSettingsAction.cs b/TagsCloudPainterApplication/Actions/ImageSettingsAction.cs new file mode 100644 index 000000000..d79b96ef5 --- /dev/null +++ b/TagsCloudPainterApplication/Actions/ImageSettingsAction.cs @@ -0,0 +1,23 @@ +using TagsCloudPainterApplication.Infrastructure.Settings; +using TagsCloudPainterApplication.Infrastructure.Settings.Image; + +namespace TagsCloudPainterApplication.Actions; + +public class ImageSettingsAction : IUiAction +{ + private readonly IImageSettings imageSettings; + + public ImageSettingsAction(IImageSettings imageSettings) + { + this.imageSettings = imageSettings ?? throw new ArgumentNullException(nameof(imageSettings)); + } + + public string Category => "Настройки"; + public string Name => "Изображение"; + public string Description => "Укажите размер изображения"; + + public void Perform() + { + SettingsForm.For(imageSettings).ShowDialog(); + } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Actions/PaletteSettingsAction.cs b/TagsCloudPainterApplication/Actions/PaletteSettingsAction.cs new file mode 100644 index 000000000..6f4909449 --- /dev/null +++ b/TagsCloudPainterApplication/Actions/PaletteSettingsAction.cs @@ -0,0 +1,23 @@ +using TagsCloudPainterApplication.Infrastructure; +using TagsCloudPainterApplication.Infrastructure.Settings; + +namespace TagsCloudPainterApplication.Actions; + +public class PaletteSettingsAction : IUiAction +{ + private readonly Palette palette; + + public PaletteSettingsAction(Palette palette) + { + this.palette = palette ?? throw new ArgumentNullException(nameof(palette)); + } + + public string Category => "Настройки"; + public string Name => "Палитра..."; + public string Description => "Цвета для рисования облака тэгов"; + + public void Perform() + { + SettingsForm.For(palette).ShowDialog(); + } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Actions/SaveImageAction.cs b/TagsCloudPainterApplication/Actions/SaveImageAction.cs new file mode 100644 index 000000000..1e905de5f --- /dev/null +++ b/TagsCloudPainterApplication/Actions/SaveImageAction.cs @@ -0,0 +1,32 @@ +using TagsCloudPainterApplication.Infrastructure; + +namespace TagsCloudPainterApplication.Actions; + +public class SaveImageAction : IUiAction +{ + private readonly IImageHolder imageHolder; + + public SaveImageAction(IImageHolder imageHolder) + { + this.imageHolder = imageHolder ?? throw new ArgumentNullException(nameof(imageHolder)); + } + + public string Category => "Файл"; + public string Name => "Сохранить..."; + public string Description => "Сохранить изображение в файл"; + + public void Perform() + { + var dialog = new SaveFileDialog + { + CheckFileExists = false, + InitialDirectory = Path.GetFullPath(Environment.CurrentDirectory), + DefaultExt = "png", + FileName = "image.png", + Filter = "Изображения (*.png)|*.png|Изображения (*.jpeg)|*.jpeg|Изображения (*.bmp)|*.bmp|Изображения (*.gif)|*.gif" + }; + var res = dialog.ShowDialog(); + if (res == DialogResult.OK) + imageHolder.SaveImage(dialog.FileName); + } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Actions/UiActionExtensions.cs b/TagsCloudPainterApplication/Actions/UiActionExtensions.cs new file mode 100644 index 000000000..93c3f1e27 --- /dev/null +++ b/TagsCloudPainterApplication/Actions/UiActionExtensions.cs @@ -0,0 +1,29 @@ +namespace TagsCloudPainterApplication.Actions; + +public static class UiActionExtensions +{ + public static ToolStripItem[] ToMenuItems(this IUiAction[] actions) + { + var items = actions.GroupBy(a => a.Category) + .Select(g => CreateTopLevelMenuItem(g.Key, g.ToList())) + .Cast() + .ToArray(); + return items; + } + + private static ToolStripMenuItem CreateTopLevelMenuItem(string name, IList items) + { + var menuItems = items.Select(a => a.ToMenuItem()).ToArray(); + return new ToolStripMenuItem(name, null, menuItems); + } + + public static ToolStripItem ToMenuItem(this IUiAction action) + { + return + new ToolStripMenuItem(action.Name, null, (sender, args) => action.Perform()) + { + ToolTipText = action.Description, + Tag = action + }; + } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/App.config b/TagsCloudPainterApplication/App.config new file mode 100644 index 000000000..4ea15faf7 --- /dev/null +++ b/TagsCloudPainterApplication/App.config @@ -0,0 +1,56 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + Data\BoringWords.txt + + + 800 + + + 600 + + + 32 + + + Arial + + + 0.1 + + + 0.5 + + + 1 + + + + \ No newline at end of file diff --git a/TagsCloudPainterApplication/Data/BoringWords.txt b/TagsCloudPainterApplication/Data/BoringWords.txt new file mode 100644 index 000000000..30d46d215 --- /dev/null +++ b/TagsCloudPainterApplication/Data/BoringWords.txt @@ -0,0 +1,238 @@ +к +в +на +по +с +из +у +от +за +до +перед +через +со +о +об +при +под +как +если +что +чтобы +когда +где +потому что +поскольку +или +либо +да +нет +также +тоже +но +однако +а +и +да +ли +же +ибо +то +что +как +чтобы +если +пока +после +пока +после +так +также +тоже +потому что +поэтому +поэтому что +вот +вон +вроде +как будто +как будто бы +как бы +как бы то ни было +и так далее +так и +так что +так как +так что +то есть +даже +еще +бы +лишь бы +раз +однако +все-таки +все же +тем более +более того +по-прежнему +потому что +поэтому +следовательно +таким образом +в результате +так что +к тому же +благодаря +несмотря на +вопреки +вследствие +напротив +несмотря на то что +в силу +в виду +кроме +как +ради +из-за +без +для +против +в обмен на +в замен +наряду с +среди +помимо +в течение +на протяжении +в течение +вплоть до +во время +в преддверии +ввиду +вслед за +вдоль +возле +в глубь +в глубину +в направлении +в направлении к +в направлении от +вокруг +вокруг по периметру +в отличие от +в соответствии с +в связи с +в пределах +в предприятие +в случае +в случае если +в сравнении с +в среде +в условиях +в ходе +в честь +в память о +в отношении +в разрезе +в поддержку +в свете +в зависимости от +в результате +в целях +в интересах +в глазах +в поисках +вместо +вопреки +вдоль +внутри +вне +впереди +внизу +вверху +возле +вокруг +внутри +впереди +в сравнении с +в отличие от +вслед за +внутрь +вперед +ввысь +вдоль +вовне +вглубь +впереди +вокруг +внутрь +вниз +вверх +вдоль +возле +впереди +во имя +в пользу +в случае +в следствие +в связи с +в целях +в зависимости от +в отношении +в память о +в виду +в наказание +в пользу +в противном случае +в пользу +в результате +в служение +в свою очередь +в отличие от +в сравнении с +в целях +в следствие +в свете +в целях +вперед +вниз +вверх +вдоль +вне +вокруг +впереди +вперед +внутри +внутри +в сравнении с +в отличие от +вопреки +вслед за +внутрь +вверх +впереди +внутрь +вслед за +внутрь +вверх +вокруг +вглубь +внутрь +вперед +впереди +внутрь +вниз +вверх +вокруг +внутри +вверх +вниз +вглубь +вдоль +вперед +вокруг +вокруг +впереди +вниз +вверх \ No newline at end of file diff --git a/TagsCloudPainterApplication/Infrastructure/IImageHolder.cs b/TagsCloudPainterApplication/Infrastructure/IImageHolder.cs new file mode 100644 index 000000000..d9ae5826e --- /dev/null +++ b/TagsCloudPainterApplication/Infrastructure/IImageHolder.cs @@ -0,0 +1,9 @@ +namespace TagsCloudPainterApplication.Infrastructure; + +public interface IImageHolder +{ + Size GetImageSize(); + Graphics StartDrawing(); + void UpdateUi(); + void SaveImage(string fileName); +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Infrastructure/Palette.cs b/TagsCloudPainterApplication/Infrastructure/Palette.cs new file mode 100644 index 000000000..c5974ca60 --- /dev/null +++ b/TagsCloudPainterApplication/Infrastructure/Palette.cs @@ -0,0 +1,8 @@ +namespace TagsCloudPainterApplication.Infrastructure; + +public class Palette +{ + public Color PrimaryColor { get; set; } = Color.Yellow; + public Color SecondaryColor { get; set; } = Color.Red; + public Color BackgroundColor { get; set; } = Color.DarkBlue; +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Infrastructure/Settings/FilesSource/FilesSourceSettings.cs b/TagsCloudPainterApplication/Infrastructure/Settings/FilesSource/FilesSourceSettings.cs new file mode 100644 index 000000000..a882ab112 --- /dev/null +++ b/TagsCloudPainterApplication/Infrastructure/Settings/FilesSource/FilesSourceSettings.cs @@ -0,0 +1,19 @@ +using TagsCloudPainterApplication.Properties; + +namespace TagsCloudPainterApplication.Infrastructure.Settings.FilesSource; + +public class FilesSourceSettings : IFilesSourceSettings +{ + private string boringTextFilePath; + + public FilesSourceSettings(IAppSettings settings) + { + BoringTextFilePath = settings.BoringTextFilePath; + } + + public string BoringTextFilePath + { + get => boringTextFilePath; + set => boringTextFilePath = value ?? boringTextFilePath; + } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Infrastructure/Settings/FilesSource/IFilesSourceSettings.cs b/TagsCloudPainterApplication/Infrastructure/Settings/FilesSource/IFilesSourceSettings.cs new file mode 100644 index 000000000..76955e9a7 --- /dev/null +++ b/TagsCloudPainterApplication/Infrastructure/Settings/FilesSource/IFilesSourceSettings.cs @@ -0,0 +1,6 @@ +namespace TagsCloudPainterApplication.Infrastructure.Settings.FilesSource; + +public interface IFilesSourceSettings +{ + public string BoringTextFilePath { get; set; } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Infrastructure/Settings/Image/IImageSettings.cs b/TagsCloudPainterApplication/Infrastructure/Settings/Image/IImageSettings.cs new file mode 100644 index 000000000..1e34d23b3 --- /dev/null +++ b/TagsCloudPainterApplication/Infrastructure/Settings/Image/IImageSettings.cs @@ -0,0 +1,7 @@ +namespace TagsCloudPainterApplication.Infrastructure.Settings.Image; + +public interface IImageSettings +{ + public int Width { get; set; } + public int Height { get; set; } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Infrastructure/Settings/Image/ImageSettings.cs b/TagsCloudPainterApplication/Infrastructure/Settings/Image/ImageSettings.cs new file mode 100644 index 000000000..7501ae41f --- /dev/null +++ b/TagsCloudPainterApplication/Infrastructure/Settings/Image/ImageSettings.cs @@ -0,0 +1,15 @@ +using TagsCloudPainterApplication.Properties; + +namespace TagsCloudPainterApplication.Infrastructure.Settings.Image; + +public class ImageSettings : IImageSettings +{ + public ImageSettings(IAppSettings settings) + { + Width = settings.ImageWidth; + Height = settings.ImageHeight; + } + + public int Width { get; set; } + public int Height { get; set; } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Infrastructure/Settings/SettingsForm.cs b/TagsCloudPainterApplication/Infrastructure/Settings/SettingsForm.cs new file mode 100644 index 000000000..c49ec7b21 --- /dev/null +++ b/TagsCloudPainterApplication/Infrastructure/Settings/SettingsForm.cs @@ -0,0 +1,35 @@ +namespace TagsCloudPainterApplication.Infrastructure.Settings; + +public static class SettingsForm +{ + public static SettingsForm For(TSettings settings) + { + return new SettingsForm(settings); + } +} + +public class SettingsForm : Form +{ + public SettingsForm(TSettings settings) + { + var okButton = new Button + { + Text = "OK", + DialogResult = DialogResult.OK, + Dock = DockStyle.Bottom + }; + Controls.Add(okButton); + Controls.Add(new PropertyGrid + { + SelectedObject = settings, + Dock = DockStyle.Fill + }); + AcceptButton = okButton; + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + Text = "Настройки"; + } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Infrastructure/Settings/TagsCloud/ITagsCloudSettings.cs b/TagsCloudPainterApplication/Infrastructure/Settings/TagsCloud/ITagsCloudSettings.cs new file mode 100644 index 000000000..7629e792e --- /dev/null +++ b/TagsCloudPainterApplication/Infrastructure/Settings/TagsCloud/ITagsCloudSettings.cs @@ -0,0 +1,26 @@ +using TagsCloudPainter.Settings; +using TagsCloudPainter.Settings.Cloud; +using TagsCloudPainter.Settings.FormPointer; +using TagsCloudPainter.Settings.Tag; + +namespace TagsCloudPainterApplication.Infrastructure.Settings.TagsCloud; + +public interface ITagsCloudSettings +{ + public ICloudSettings CloudSettings { get; } + public ITagSettings TagSettings { get; } + public ISpiralPointerSettings SpiralPointerSettings { get; } + public ITextSettings TextSettings { get; } + + public int TagFontSize { get; set; } + + public string TagFontName { get; set; } + + public Point CloudCenter { get; set; } + + public double PointerStep { get; set; } + + public double PointerRadiusConst { get; set; } + + public double PointerAngleConst { get; set; } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Infrastructure/Settings/TagsCloud/TagsCloudSettings.cs b/TagsCloudPainterApplication/Infrastructure/Settings/TagsCloud/TagsCloudSettings.cs new file mode 100644 index 000000000..6ff3c07d4 --- /dev/null +++ b/TagsCloudPainterApplication/Infrastructure/Settings/TagsCloud/TagsCloudSettings.cs @@ -0,0 +1,70 @@ +using System.ComponentModel; +using TagsCloudPainter.Settings; +using TagsCloudPainter.Settings.Cloud; +using TagsCloudPainter.Settings.FormPointer; +using TagsCloudPainter.Settings.Tag; +using TagsCloudPainterApplication.Properties; + +namespace TagsCloudPainterApplication.Infrastructure.Settings.TagsCloud; + +public class TagsCloudSettings : ITagsCloudSettings +{ + public TagsCloudSettings( + ICloudSettings cloudSettings, + ITagSettings tagSettings, + ISpiralPointerSettings spiralPointerSettings, + ITextSettings textSettings, + IAppSettings appSettings) + { + CloudSettings = cloudSettings ?? throw new ArgumentNullException(nameof(cloudSettings)); + TagSettings = tagSettings ?? throw new ArgumentNullException(nameof(tagSettings)); + SpiralPointerSettings = spiralPointerSettings ?? throw new ArgumentNullException(nameof(spiralPointerSettings)); + TextSettings = textSettings ?? throw new ArgumentNullException(nameof(textSettings)); + TagFontSize = appSettings.TagFontSize; + TagFontName = appSettings.TagFontName; + PointerStep = appSettings.PointerStep; + PointerRadiusConst = appSettings.PointerRadiusConst; + PointerAngleConst = appSettings.PointerAngleConst; + } + + [Browsable(false)] public ICloudSettings CloudSettings { get; } + [Browsable(false)] public ITagSettings TagSettings { get; } + [Browsable(false)] public ISpiralPointerSettings SpiralPointerSettings { get; } + [Browsable(false)] public ITextSettings TextSettings { get; } + + public int TagFontSize + { + get => TagSettings.TagFontSize; + set => TagSettings.TagFontSize = value; + } + + public string TagFontName + { + get => TagSettings.TagFontName; + set => TagSettings.TagFontName = value ?? TagSettings.TagFontName; + } + + public Point CloudCenter + { + get => CloudSettings.CloudCenter; + set => CloudSettings.CloudCenter = value; + } + + public double PointerStep + { + get => SpiralPointerSettings.Step; + set => SpiralPointerSettings.Step = value; + } + + public double PointerRadiusConst + { + get => SpiralPointerSettings.RadiusConst; + set => SpiralPointerSettings.RadiusConst = value; + } + + public double PointerAngleConst + { + get => SpiralPointerSettings.AngleConst; + set => SpiralPointerSettings.AngleConst = value; + } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/MainForm.Designer.cs b/TagsCloudPainterApplication/MainForm.Designer.cs new file mode 100644 index 000000000..4362dc1e9 --- /dev/null +++ b/TagsCloudPainterApplication/MainForm.Designer.cs @@ -0,0 +1,39 @@ +namespace TagsCloudPainterApplication +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Text = "Form1"; + } + + #endregion + } +} diff --git a/TagsCloudPainterApplication/MainForm.cs b/TagsCloudPainterApplication/MainForm.cs new file mode 100644 index 000000000..243f2b555 --- /dev/null +++ b/TagsCloudPainterApplication/MainForm.cs @@ -0,0 +1,23 @@ +using TagsCloudPainterApplication.Actions; +using TagsCloudPainterApplication.Infrastructure.Settings.Image; + +namespace TagsCloudPainterApplication; + +public partial class MainForm : Form +{ + public MainForm(IUiAction[] actions, IImageSettings imageSettings, PictureBoxImageHolder pictureBox) + { + ArgumentNullException.ThrowIfNull(actions); + ArgumentNullException.ThrowIfNull(imageSettings); + ArgumentNullException.ThrowIfNull(pictureBox); + + ClientSize = new Size(imageSettings.Width, imageSettings.Height); + + var mainMenu = new MenuStrip(); + mainMenu.Items.AddRange(actions.ToMenuItems()); + Controls.Add(mainMenu); + + pictureBox.Dock = DockStyle.Fill; + Controls.Add(pictureBox); + } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/MainForm.resx b/TagsCloudPainterApplication/MainForm.resx new file mode 100644 index 000000000..d4ad6f224 --- /dev/null +++ b/TagsCloudPainterApplication/MainForm.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + \ No newline at end of file diff --git a/TagsCloudPainterApplication/PictureBoxImageHolder.cs b/TagsCloudPainterApplication/PictureBoxImageHolder.cs new file mode 100644 index 000000000..bce6eb567 --- /dev/null +++ b/TagsCloudPainterApplication/PictureBoxImageHolder.cs @@ -0,0 +1,57 @@ +using System.Drawing.Imaging; +using TagsCloudPainterApplication.Infrastructure; +using TagsCloudPainterApplication.Infrastructure.Settings.Image; + +namespace TagsCloudPainterApplication; + +public class PictureBoxImageHolder : PictureBox, IImageHolder +{ + private readonly Lazy imageSettings; + + public PictureBoxImageHolder(Lazy imageSettings) + { + this.imageSettings = imageSettings ?? throw new ArgumentNullException(nameof(imageSettings)); + } + + public Size GetImageSize() + { + return GetImage().Size; + } + + public Graphics StartDrawing() + { + return Graphics.FromImage(GetImage()); + } + + public void UpdateUi() + { + Refresh(); + Application.DoEvents(); + } + + public void SaveImage(string fileName) + { + var imageFormat = GetImageFormat(Path.GetExtension(fileName)); + GetImage().Save(fileName, imageFormat); + } + + public Image GetImage() + { + if (Image == null || Image.Width != imageSettings.Value.Width || Image.Height != imageSettings.Value.Height) + Image = new Bitmap(imageSettings.Value.Width, imageSettings.Value.Height, PixelFormat.Format24bppRgb); + + return Image; + } + + private static ImageFormat GetImageFormat(string extension) + { + var prop = typeof(ImageFormat) + .GetProperties().Where(p => + p.Name.Equals(extension.Replace(".", ""), StringComparison.InvariantCultureIgnoreCase)) + .FirstOrDefault(); + + return prop is not null + ? prop.GetValue(prop) as ImageFormat + : throw new ArgumentException($"there is no image format with {extension} extension"); + } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Program.cs b/TagsCloudPainterApplication/Program.cs new file mode 100644 index 000000000..cb24aed79 --- /dev/null +++ b/TagsCloudPainterApplication/Program.cs @@ -0,0 +1,79 @@ +using Autofac; +using TagsCloudPainter.CloudLayouter; +using TagsCloudPainter.Drawer; +using TagsCloudPainter.FileReader; +using TagsCloudPainter.FormPointer; +using TagsCloudPainter.Parser; +using TagsCloudPainter.Settings; +using TagsCloudPainter.Settings.Cloud; +using TagsCloudPainter.Settings.FormPointer; +using TagsCloudPainter.Settings.Tag; +using TagsCloudPainter.Sizer; +using TagsCloudPainter.Tags; +using TagsCloudPainterApplication.Actions; +using TagsCloudPainterApplication.Infrastructure; +using TagsCloudPainterApplication.Infrastructure.Settings.FilesSource; +using TagsCloudPainterApplication.Infrastructure.Settings.Image; +using TagsCloudPainterApplication.Infrastructure.Settings.TagsCloud; +using TagsCloudPainterApplication.Properties; + +namespace TagsCloudPainterApplication; + +internal static class Program +{ + /// + /// The main entry point for the application. + /// + [STAThread] + private static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + var builder = new ContainerBuilder(); + builder.RegisterModule(new TagsCloudPainterLibModule()); + builder.RegisterModule(new ApplicationModule()); + var container = builder.Build(); + Application.Run(container.Resolve()); + } +} + +public class ApplicationModule : Module +{ + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType().AsSelf().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().AsSelf(); + } +} + +public class TagsCloudPainterLibModule : Module +{ + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As>().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Properties/IAppSettings.cs b/TagsCloudPainterApplication/Properties/IAppSettings.cs new file mode 100644 index 000000000..6808ef473 --- /dev/null +++ b/TagsCloudPainterApplication/Properties/IAppSettings.cs @@ -0,0 +1,13 @@ +namespace TagsCloudPainterApplication.Properties; + +public interface IAppSettings +{ + public string BoringTextFilePath { get; set; } + public int ImageWidth { get; set; } + public int ImageHeight { get; set; } + public int TagFontSize { get; set; } + public string TagFontName { get; set; } + public double PointerStep { get; set; } + public double PointerRadiusConst { get; set; } + public double PointerAngleConst { get; set; } +} \ No newline at end of file diff --git a/TagsCloudPainterApplication/Properties/Resources.Designer.cs b/TagsCloudPainterApplication/Properties/Resources.Designer.cs new file mode 100644 index 000000000..876edd598 --- /dev/null +++ b/TagsCloudPainterApplication/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// Этот код создан программой. +// Исполняемая версия:4.0.30319.42000 +// +// Изменения в этом файле могут привести к неправильной работе и будут потеряны в случае +// повторной генерации кода. +// +//------------------------------------------------------------------------------ + +namespace TagsCloudPainterApplication.Properties { + using System; + + + /// + /// Класс ресурса со строгой типизацией для поиска локализованных строк и т.д. + /// + // Этот класс создан автоматически классом StronglyTypedResourceBuilder + // с помощью такого средства, как ResGen или Visual Studio. + // Чтобы добавить или удалить член, измените файл .ResX и снова запустите ResGen + // с параметром /str или перестройте свой проект VS. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Возвращает кэшированный экземпляр ResourceManager, использованный этим классом. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TagsCloudPainterApplication.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Перезаписывает свойство CurrentUICulture текущего потока для всех + /// обращений к ресурсу с помощью этого класса ресурса со строгой типизацией. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/TagsCloudPainterApplication/Properties/Resources.resx b/TagsCloudPainterApplication/Properties/Resources.resx new file mode 100644 index 000000000..3627ee652 --- /dev/null +++ b/TagsCloudPainterApplication/Properties/Resources.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + \ No newline at end of file diff --git a/TagsCloudPainterApplication/Properties/Settings.Designer.cs b/TagsCloudPainterApplication/Properties/Settings.Designer.cs new file mode 100644 index 000000000..d00b43c13 --- /dev/null +++ b/TagsCloudPainterApplication/Properties/Settings.Designer.cs @@ -0,0 +1,149 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace TagsCloudPainterApplication.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] + public sealed partial class AppSettings : global::System.Configuration.ApplicationSettingsBase, IAppSettings + { + + private static AppSettings defaultInstance = ((AppSettings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new AppSettings()))); + + public static AppSettings Default + { + get + { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Data\\BoringWords.txt")] + public string BoringTextFilePath + { + get + { + return ((string)(this["boringTextFilePath"])); + } + set + { + this["boringTextFilePath"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("800")] + public int ImageWidth + { + get + { + return ((int)(this["imageWidth"])); + } + set + { + this["imageWidth"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("600")] + public int ImageHeight + { + get + { + return ((int)(this["imageHeight"])); + } + set + { + this["imageHeight"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("32")] + public int TagFontSize + { + get + { + return ((int)(this["tagFontSize"])); + } + set + { + this["tagFontSize"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Arial")] + public string TagFontName + { + get + { + return ((string)(this["tagFontName"])); + } + set + { + this["tagFontName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0.1")] + public double PointerStep + { + get + { + return ((double)(this["pointerStep"])); + } + set + { + this["pointerStep"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0.5")] + public double PointerRadiusConst + { + get + { + return ((double)(this["pointerRadiusConst"])); + } + set + { + this["pointerRadiusConst"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public double PointerAngleConst + { + get + { + return ((double)(this["pointerAngleConst"])); + } + set + { + this["pointerAngleConst"] = value; + } + } + } +} diff --git a/TagsCloudPainterApplication/Properties/Settings.settings b/TagsCloudPainterApplication/Properties/Settings.settings new file mode 100644 index 000000000..6a5822734 --- /dev/null +++ b/TagsCloudPainterApplication/Properties/Settings.settings @@ -0,0 +1,31 @@ + + + + + + Data\BoringWords.txt + + + 800 + + + 600 + + + 32 + + + Arial + + + 0.1 + + + 0.5 + + + 1 + + + \ No newline at end of file diff --git a/TagsCloudPainterApplication/TagsCloudPainterApplication.csproj b/TagsCloudPainterApplication/TagsCloudPainterApplication.csproj new file mode 100644 index 000000000..6af042650 --- /dev/null +++ b/TagsCloudPainterApplication/TagsCloudPainterApplication.csproj @@ -0,0 +1,45 @@ + + + + WinExe + net8.0-windows7.0 + enable + true + enable + false + + + + + + + + + + + + + Component + + + True + True + Settings.settings + + + + + + Always + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + \ No newline at end of file diff --git a/TagsCloudPainterApplication/packages.config b/TagsCloudPainterApplication/packages.config new file mode 100644 index 000000000..c2bcb1ec2 --- /dev/null +++ b/TagsCloudPainterApplication/packages.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/TagsCloudPainterApplicationTests/DependencyInjectionTests.cs b/TagsCloudPainterApplicationTests/DependencyInjectionTests.cs new file mode 100644 index 000000000..767b57b2b --- /dev/null +++ b/TagsCloudPainterApplicationTests/DependencyInjectionTests.cs @@ -0,0 +1,105 @@ +using Autofac; +using TagsCloudPainter.CloudLayouter; +using TagsCloudPainter.Drawer; +using TagsCloudPainter.FileReader; +using TagsCloudPainter.FormPointer; +using TagsCloudPainter.Parser; +using TagsCloudPainter.Settings; +using TagsCloudPainter.Settings.Cloud; +using TagsCloudPainter.Settings.FormPointer; +using TagsCloudPainter.Settings.Tag; +using TagsCloudPainter.Tags; +using TagsCloudPainterApplication; +using TagsCloudPainterApplication.Actions; +using TagsCloudPainterApplication.Infrastructure; +using TagsCloudPainterApplication.Infrastructure.Settings.FilesSource; +using TagsCloudPainterApplication.Infrastructure.Settings.Image; +using TagsCloudPainterApplication.Infrastructure.Settings.TagsCloud; +using TagsCloudPainterApplication.Properties; + +namespace TagsCloudPainterApplicationTests; + +[TestFixture] +public class DependencyInjectionTests +{ + [SetUp] + public void Setup() + { + var builder = new ContainerBuilder(); + builder.RegisterModule(); + builder.RegisterModule(); + var container = builder.Build(); + scope = container.BeginLifetimeScope(); + } + + [TearDown] + public void TearDown() + { + scope.Dispose(); + } + + private ILifetimeScope scope; + + private static IEnumerable InstancePerLifetimeScopeDependencesTypes => new[] + { + new TestCaseData(typeof(IFormPointer)), + new TestCaseData(typeof(ICloudLayouter)), + new TestCaseData(typeof(IUiAction)), + new TestCaseData(typeof(MainForm)) + }; + + private static IEnumerable SingleInstanceDependencesTypes => new[] + { + new TestCaseData(typeof(ITagSettings)), + new TestCaseData(typeof(ICloudDrawer)), + new TestCaseData(typeof(IFormatFileReader)), + new TestCaseData(typeof(ITextParser)), + new TestCaseData(typeof(ITagsBuilder)), + new TestCaseData(typeof(ITextSettings)), + new TestCaseData(typeof(ICloudSettings)), + new TestCaseData(typeof(ISpiralPointerSettings)), + new TestCaseData(typeof(Palette)), + new TestCaseData(typeof(IImageSettings)), + new TestCaseData(typeof(IFilesSourceSettings)), + new TestCaseData(typeof(ITagsCloudSettings)), + new TestCaseData(typeof(PictureBoxImageHolder)), + new TestCaseData(typeof(IImageHolder)), + new TestCaseData(typeof(IAppSettings)) + }; + + [TestCaseSource(nameof(SingleInstanceDependencesTypes))] + [TestCaseSource(nameof(InstancePerLifetimeScopeDependencesTypes))] + public void Dependence_SouldResolve(Type dependenceType) + { + Assert.DoesNotThrow(() => scope.Resolve(dependenceType)); + } + + [TestCaseSource(nameof(SingleInstanceDependencesTypes))] + [TestCaseSource(nameof(InstancePerLifetimeScopeDependencesTypes))] + public void Dependence_SouldBeNotNull(Type dependenceType) + { + var dependence = scope.Resolve(dependenceType); + Assert.That(dependence, Is.Not.Null); + } + + [TestCaseSource(nameof(SingleInstanceDependencesTypes))] + public void SingleInstanceDependence_ShouldResolveSameReferenceInDifferentScopes(Type dependenceType) + { + using var childScope = scope.BeginLifetimeScope(); + var dependence1 = scope.Resolve(dependenceType); + var dependence2 = childScope.Resolve(dependenceType); + + Assert.That(dependence2, Is.SameAs(dependence1)); + } + + [TestCaseSource(nameof(InstancePerLifetimeScopeDependencesTypes))] + public void InstancePerLifetimeScopeDependence_ShouldResolveDifferentReferencesInDifferentScopes( + Type dependenceType) + { + using var childScope = scope.BeginLifetimeScope(); + var dependence1 = scope.Resolve(dependenceType); + var dependence2 = childScope.Resolve(dependenceType); + + Assert.That(dependence2, Is.Not.EqualTo(dependence1)); + } +} \ No newline at end of file diff --git a/TagsCloudPainterApplicationTests/GlobalUsings.cs b/TagsCloudPainterApplicationTests/GlobalUsings.cs new file mode 100644 index 000000000..cefced496 --- /dev/null +++ b/TagsCloudPainterApplicationTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/TagsCloudPainterApplicationTests/TagsCloudPainterApplicationTests.csproj b/TagsCloudPainterApplicationTests/TagsCloudPainterApplicationTests.csproj new file mode 100644 index 000000000..51017c60d --- /dev/null +++ b/TagsCloudPainterApplicationTests/TagsCloudPainterApplicationTests.csproj @@ -0,0 +1,25 @@ + + + + net8.0-windows + enable + enable + + false + true + Library + + + + + + + + + + + + + + + diff --git a/Tests/ArchimedeanSpiralTests.cs b/Tests/ArchimedeanSpiralTests.cs new file mode 100644 index 000000000..4ac3ab3f4 --- /dev/null +++ b/Tests/ArchimedeanSpiralTests.cs @@ -0,0 +1,25 @@ +using System.Drawing; +using TagsCloudPainter.FormPointer; +using TagsCloudPainter.Settings.Cloud; +using TagsCloudPainter.Settings.FormPointer; + +namespace TagsCloudPainterTests; + +[TestFixture] +public class ArchimedeanSpiralTests +{ + private static IEnumerable ConstructorArgumentException => new[] + { + new TestCaseData(new Point(1, 1), 0, 1, 1).SetName("WhenGivenNotPositiveStep"), + new TestCaseData(new Point(1, 1), 1, 0, 1).SetName("WhenGivenNotPositiveRadius"), + new TestCaseData(new Point(1, 1), 1, 1, 0).SetName("WhenGivenNotPositiveAngle") + }; + + [TestCaseSource(nameof(ConstructorArgumentException))] + public void Constructor_ShouldThrowArgumentException(Point center, double step, double radius, double angle) + { + var cloudSettings = new CloudSettings { CloudCenter = center }; + var pointerSettings = new SpiralPointerSettings { AngleConst = angle, RadiusConst = radius, Step = step }; + Assert.Throws(() => new ArchimedeanSpiralPointer(cloudSettings, pointerSettings)); + } +} \ No newline at end of file diff --git a/Tests/BoringTextParserTests.cs b/Tests/BoringTextParserTests.cs new file mode 100644 index 000000000..d27b35f62 --- /dev/null +++ b/Tests/BoringTextParserTests.cs @@ -0,0 +1,55 @@ +using FluentAssertions; +using TagsCloudPainter.Parser; +using TagsCloudPainter.Settings; + +namespace TagsCloudPainterTests; + +[TestFixture] +public class BoringTextParserTests +{ + [SetUp] + public void Setup() + { + boringText = $"что{Environment.NewLine}и{Environment.NewLine}в"; + textSettings = new TextSettings { BoringText = boringText }; + boringTextParser = new BoringTextParser(textSettings); + } + + private ITextSettings textSettings; + private BoringTextParser boringTextParser; + private string boringText; + + [Test] + public void ParseText_ShouldReturnWordsListWithoutBoringWords() + { + var boringWords = boringTextParser + .GetBoringWords( + boringText) + .ToHashSet(); + var parsedText = boringTextParser.ParseText("Скучные Слова что в и"); + var isBoringWordsInParsedText = parsedText.Where(boringWords.Contains).Any(); + isBoringWordsInParsedText.Should().BeFalse(); + } + + [Test] + public void ParseText_ShouldReturnNotEmptyWordsList_WhenPassedNotEmptyText() + { + var parsedText = boringTextParser.ParseText("Скучные Слова что в и"); + parsedText.Count.Should().BeGreaterThan(0); + } + + [Test] + public void ParseText_ShouldReturnWordsInLowerCase() + { + var parsedText = boringTextParser.ParseText("Скучные Слова что в и"); + var isAnyWordNotLowered = parsedText.Any(word => !word.Equals(word, StringComparison.CurrentCultureIgnoreCase)); + isAnyWordNotLowered.Should().BeFalse(); + } + + [Test] + public void ParseText_ShouldReturnWordsListWithTheSameAmountAsInText() + { + var parsedText = boringTextParser.ParseText("Скучные Слова что в и"); + parsedText.Count.Should().Be(2); + } +} \ No newline at end of file diff --git a/Tests/CloudDrawerTests.cs b/Tests/CloudDrawerTests.cs new file mode 100644 index 000000000..a3c092e7d --- /dev/null +++ b/Tests/CloudDrawerTests.cs @@ -0,0 +1,62 @@ +using System.Drawing; +using TagsCloudPainter; +using TagsCloudPainter.Drawer; +using TagsCloudPainter.Settings.Cloud; +using TagsCloudPainter.Settings.Tag; +using TagsCloudPainter.Tags; + +namespace TagsCloudPainterTests; + +[TestFixture] +public class CloudDrawerTests +{ + [SetUp] + public void Setup() + { + var cloudSettings = new CloudSettings { CloudCenter = new Point(0, 0), BackgroundColor = Color.White }; + var tagSettings = new TagSettings { TagFontSize = 32, TagColor = Color.Black }; + drawer = new CloudDrawer(tagSettings, cloudSettings); + } + + private ICloudDrawer drawer; + + private static IEnumerable DrawArgumentException => new[] + { + new TestCaseData(new TagsCloud(new Point(0, 0), + new List<(Tag, Rectangle)> { (new Tag("a", 1, 1), new Rectangle(1, 1, 1, 1)) }), 0, 1) + .SetName("WhenGivenNotPositiveImageWidth"), + new TestCaseData(new TagsCloud(new Point(0, 0), + new List<(Tag, Rectangle)> { (new Tag("a", 1, 1), new Rectangle(1, 1, 1, 1)) }), 1, 0) + .SetName("WhenGivenNotPositiveImageHeight"), + new TestCaseData(new TagsCloud(new Point(0, 0), + new List<(Tag, Rectangle)> { (new Tag("a", 1, 1), new Rectangle(1, 1, 1, 1)) }), 0, 0) + .SetName("WhenGivenNotPositiveImageHeightAndWidth"), + new TestCaseData(new TagsCloud(new Point(0, 0), new List<(Tag, Rectangle)>()), 1, 1) + .SetName("WhenGivenCloudWithEmptyTagsDictionary") + }; + + [TestCaseSource(nameof(DrawArgumentException))] + public void Draw_ShouldThrowArgumentException(TagsCloud cloud, int width, int height) + { + Assert.Throws(() => drawer.DrawCloud(cloud, width, height)); + } + + private static IEnumerable DrawNoException => new[] + { + new TestCaseData(new TagsCloud(new Point(5, 5), + new List<(Tag, Rectangle)> { (new Tag("abcdadg", 10, 1), new Rectangle(5, 5, 20, 3)) }), 10, 10) + .SetName("WhenCloudWidthIsGreaterThanImageWidth"), + new TestCaseData(new TagsCloud(new Point(5, 5), + new List<(Tag, Rectangle)> { (new Tag("abcdadg", 10, 1), new Rectangle(5, 5, 3, 20)) }), 10, 10) + .SetName("WhenCloudHeightIsGreaterThanImageHeight"), + new TestCaseData(new TagsCloud(new Point(5, 5), + new List<(Tag, Rectangle)> { (new Tag("abcdadg", 10, 1), new Rectangle(5, 5, 20, 20)) }), 10, 10) + .SetName("WhenCloudIsBiggerThanImage") + }; + + [TestCaseSource(nameof(DrawNoException))] + public void Draw_ShouldNotThrow(TagsCloud cloud, int width, int height) + { + Assert.DoesNotThrow(() => drawer.DrawCloud(cloud, width, height)); + } +} \ No newline at end of file diff --git a/Tests/GlobalUsings.cs b/Tests/GlobalUsings.cs new file mode 100644 index 000000000..cefced496 --- /dev/null +++ b/Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/Tests/TagsBuilderTests.cs b/Tests/TagsBuilderTests.cs new file mode 100644 index 000000000..4ec57e7c8 --- /dev/null +++ b/Tests/TagsBuilderTests.cs @@ -0,0 +1,45 @@ +using FluentAssertions; +using TagsCloudPainter.Settings.Tag; +using TagsCloudPainter.Tags; + +namespace TagsCloudPainterTests; + +[TestFixture] +public class TagsBuilderTests +{ + [SetUp] + public void Setup() + { + var tagSettings = new TagSettings { TagFontSize = 32 }; + tagsBuilder = new TagsBuilder(tagSettings); + } + + private TagsBuilder tagsBuilder; + + [Test] + public void GetTags_ShouldReturnTagsWithGivenWords() + { + var words = new List { "tag" }; + var tags = tagsBuilder.GetTags(words); + + tags[0].Value.Should().Be("tag"); + } + + [Test] + public void GetTags_ShouldReturnTagsWithDifferentValues() + { + var words = new List { "tag", "tag" }; + var tags = tagsBuilder.GetTags(words); + + tags.Count.Should().Be(1); + } + + [Test] + public void GetTags_ShouldReturnTagsWithCorrectCount() + { + var words = new List { "tag", "tag" }; + var tags = tagsBuilder.GetTags(words); + + tags[0].Count.Should().Be(2); + } +} \ No newline at end of file diff --git a/Tests/TagsCloudLayouterTests.cs b/Tests/TagsCloudLayouterTests.cs new file mode 100644 index 000000000..2ecb25dfd --- /dev/null +++ b/Tests/TagsCloudLayouterTests.cs @@ -0,0 +1,100 @@ +using System.Drawing; +using FakeItEasy; +using FluentAssertions; +using TagsCloudPainter.CloudLayouter; +using TagsCloudPainter.Extensions; +using TagsCloudPainter.FormPointer; +using TagsCloudPainter.Settings.Cloud; +using TagsCloudPainter.Settings.FormPointer; +using TagsCloudPainter.Settings.Tag; +using TagsCloudPainter.Sizer; +using TagsCloudPainter.Tags; + +namespace TagsCloudPainterTests; + +[TestFixture] +public class TagsCloudLayouterTests +{ + [SetUp] + public void Setup() + { + var cloudSettings = new CloudSettings { CloudCenter = new Point(0, 0) }; + tagSettings = new TagSettings { TagFontSize = 32 }; + var pointerSettings = new SpiralPointerSettings { AngleConst = 1, RadiusConst = 0.5, Step = 0.1 }; + var formPointer = new ArchimedeanSpiralPointer(cloudSettings, pointerSettings); + stringSizer = A.Fake(); + A.CallTo(() => stringSizer.GetStringSize(A.Ignored, A.Ignored, A.Ignored)) + .Returns(new Size(10, 10)); + tagsCloudLayouter = new TagsCloudLayouter(cloudSettings, formPointer, tagSettings, stringSizer); + } + + private TagsCloudLayouter tagsCloudLayouter; + private ITagSettings tagSettings; + private IStringSizer stringSizer; + + private static IEnumerable PutNextTagArgumentException => new[] + { + new TestCaseData(new Size(0, 10)).SetName("WidthNotPossitive"), + new TestCaseData(new Size(10, 0)).SetName("HeightNotPossitive"), + new TestCaseData(new Size(0, 0)).SetName("HeightAndWidthNotPossitive") + }; + + [TestCaseSource(nameof(PutNextTagArgumentException))] + public void PutNextRectangle_ShouldThrowArgumentException_WhenGivenTagWith(Size size) + { + A.CallTo(() => stringSizer.GetStringSize(A.Ignored, A.Ignored, A.Ignored)) + .Returns(size); + Assert.Throws(() => tagsCloudLayouter.PutNextTag(new Tag("a", 2, 1))); + } + + [Test] + public void PutNextTag_ShouldReturnRectangleOfTheTagValueSize() + { + var tag = new Tag("ads", 10, 5); + var tagSize = stringSizer.GetStringSize(tag.Value, tagSettings.TagFontName, tag.FontSize); + + var resultRectangle = tagsCloudLayouter.PutNextTag(tag); + + resultRectangle.Size.Should().Be(tagSize); + } + + [Test] + public void PutNextTag_ShouldReturnRectangleThatDoesNotIntersectWithAlreadyPutOnes() + { + var firstTag = new Tag("ads", 10, 5); + var secondTag = new Tag("ads", 10, 5); + var firstPutRectangle = tagsCloudLayouter.PutNextTag(firstTag); + var secondPutRectangle = tagsCloudLayouter.PutNextTag(secondTag); + + var doesRectanglesIntersect = firstPutRectangle.IntersectsWith(secondPutRectangle); + + doesRectanglesIntersect.Should().BeFalse(); + } + + [Test] + public void PutNextRectangle_ShouldPutRectangleWithCenterInTheCloudCenter() + { + var center = tagsCloudLayouter.GetCloud().Center; + var tag = new Tag("ads", 10, 5); + var firstRectangle = tagsCloudLayouter.PutNextTag(tag); + var firstRectangleCenter = firstRectangle.GetCenter(); + + firstRectangleCenter.Should().Be(center); + } + + [Test] + public void PutTags_ThrowsArgumentNullException_WhenGivenEmptyDictionary() + { + Assert.Throws(() => tagsCloudLayouter.PutTags([])); + } + + [Test] + public void GetCloud_ReturnsAsManyTagsAsWasPut() + { + tagsCloudLayouter.PutNextTag(new Tag("ads", 10, 5)); + tagsCloudLayouter.PutNextTag(new Tag("ads", 10, 5)); + var rectanglesAmount = tagsCloudLayouter.GetCloud().Tags.Count; + + rectanglesAmount.Should().Be(2); + } +} \ No newline at end of file diff --git a/Tests/TagsCloudPainterTests.csproj b/Tests/TagsCloudPainterTests.csproj new file mode 100644 index 000000000..1db4569f2 --- /dev/null +++ b/Tests/TagsCloudPainterTests.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + diff --git a/Tests/TextFileReaderTests.cs b/Tests/TextFileReaderTests.cs new file mode 100644 index 000000000..1f92d3278 --- /dev/null +++ b/Tests/TextFileReaderTests.cs @@ -0,0 +1,47 @@ +using TagsCloudPainter.FileReader; + +namespace TagsCloudPainterTests; + +[TestFixture] +public class TextFileReaderTests +{ + [SetUp] + public void Setup() + { + var fileReaders = new List { new TxtFileReader(), new DocFileReader() }; + textFileReader = new TextFileReader(fileReaders); + } + + private TextFileReader textFileReader; + + private static IEnumerable ReadTextFiles => new[] + { + new TestCaseData(@$"{Environment.CurrentDirectory}..\..\..\..\TextFiles\testFile.txt") + .Returns("Товарищи! постоянное информационно-пропагандистское обеспечение нашей " + + $"деятельности играет важную роль в формировании форм развития.{Environment.NewLine}{Environment.NewLine}" + + "Значимость этих проблем настолько очевидна, что укрепление и развитие.") + .SetName("WhenPassedTxtFile"), + new TestCaseData(@$"{Environment.CurrentDirectory}..\..\..\..\TextFiles\testFile.docx") + .Returns("Товарищи! постоянное информационно-пропагандистское обеспечение нашей " + + $"деятельности играет важную роль в формировании форм развития.{Environment.NewLine}{Environment.NewLine}" + + "Значимость этих проблем настолько очевидна, что укрепление и развитие.") + .SetName("WhenPassedDocxFile"), + new TestCaseData(@$"{Environment.CurrentDirectory}..\..\..\..\TextFiles\testFile.doc") + .Returns("Товарищи! постоянное информационно-пропагандистское обеспечение нашей " + + $"деятельности играет важную роль в формировании форм развития.{Environment.NewLine}{Environment.NewLine}" + + "Значимость этих проблем настолько очевидна, что укрепление и развитие.") + .SetName("WhenPassedDocFile") + }; + + [TestCaseSource(nameof(ReadTextFiles))] + public string ReadFile_ShouldReturnFileText(string path) + { + return textFileReader.ReadFile(path); + } + + [Test] + public void ReadFile_ThrowsFileNotFoundExceptio_WhenPassedNonexistentPath() + { + Assert.Throws(() => textFileReader.ReadFile("")); + } +} \ No newline at end of file diff --git a/Tests/TextFiles/testFile.doc b/Tests/TextFiles/testFile.doc new file mode 100644 index 000000000..c7408256d Binary files /dev/null and b/Tests/TextFiles/testFile.doc differ diff --git a/Tests/TextFiles/testFile.docx b/Tests/TextFiles/testFile.docx new file mode 100644 index 000000000..0d1ed791b Binary files /dev/null and b/Tests/TextFiles/testFile.docx differ diff --git a/Tests/TextFiles/testFile.txt b/Tests/TextFiles/testFile.txt new file mode 100644 index 000000000..c96293518 --- /dev/null +++ b/Tests/TextFiles/testFile.txt @@ -0,0 +1,3 @@ +Товарищи! постоянное информационно-пропагандистское обеспечение нашей деятельности играет важную роль в формировании форм развития. + +Значимость этих проблем настолько очевидна, что укрепление и развитие. \ No newline at end of file diff --git a/di.sln b/di.sln index b27b7c05d..0d1add06c 100644 --- a/di.sln +++ b/di.sln @@ -1,6 +1,17 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FractalPainter", "FractalPainter\FractalPainter.csproj", "{4D70883B-6F8B-4166-802F-8EDC9BE93199}" +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34330.188 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FractalPainter", "FractalPainter\FractalPainter.csproj", "{4D70883B-6F8B-4166-802F-8EDC9BE93199}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagsCloudPainter", "TagsCloudPainter\TagsCloudPainter.csproj", "{BE869D12-2ABD-4F38-BCC8-AD8B808E0F9A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagsCloudPainterApplication", "TagsCloudPainterApplication\TagsCloudPainterApplication.csproj", "{BE46FCA0-8EE5-40F4-9FB5-E9ABFA0200EE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagsCloudPainterTests", "Tests\TagsCloudPainterTests.csproj", "{61D758B1-A9F4-4E48-B1F9-0CA1637BA9A0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagsCloudPainterApplicationTests", "TagsCloudPainterApplicationTests\TagsCloudPainterApplicationTests.csproj", "{506CF64F-0A6E-4055-A905-537BE33FB146}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -12,5 +23,27 @@ Global {4D70883B-6F8B-4166-802F-8EDC9BE93199}.Debug|Any CPU.Build.0 = Debug|Any CPU {4D70883B-6F8B-4166-802F-8EDC9BE93199}.Release|Any CPU.ActiveCfg = Release|Any CPU {4D70883B-6F8B-4166-802F-8EDC9BE93199}.Release|Any CPU.Build.0 = Release|Any CPU + {BE869D12-2ABD-4F38-BCC8-AD8B808E0F9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE869D12-2ABD-4F38-BCC8-AD8B808E0F9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE869D12-2ABD-4F38-BCC8-AD8B808E0F9A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE869D12-2ABD-4F38-BCC8-AD8B808E0F9A}.Release|Any CPU.Build.0 = Release|Any CPU + {BE46FCA0-8EE5-40F4-9FB5-E9ABFA0200EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE46FCA0-8EE5-40F4-9FB5-E9ABFA0200EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE46FCA0-8EE5-40F4-9FB5-E9ABFA0200EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE46FCA0-8EE5-40F4-9FB5-E9ABFA0200EE}.Release|Any CPU.Build.0 = Release|Any CPU + {61D758B1-A9F4-4E48-B1F9-0CA1637BA9A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61D758B1-A9F4-4E48-B1F9-0CA1637BA9A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61D758B1-A9F4-4E48-B1F9-0CA1637BA9A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61D758B1-A9F4-4E48-B1F9-0CA1637BA9A0}.Release|Any CPU.Build.0 = Release|Any CPU + {506CF64F-0A6E-4055-A905-537BE33FB146}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {506CF64F-0A6E-4055-A905-537BE33FB146}.Debug|Any CPU.Build.0 = Debug|Any CPU + {506CF64F-0A6E-4055-A905-537BE33FB146}.Release|Any CPU.ActiveCfg = Release|Any CPU + {506CF64F-0A6E-4055-A905-537BE33FB146}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F33C482A-B97D-48B7-A2A3-020D3D102334} EndGlobalSection EndGlobal