From 93036d0a66ba92ea3ae3bd8d48f78a5b10b2bd71 Mon Sep 17 00:00:00 2001 From: qreaqtor Date: Thu, 26 Dec 2024 20:17:57 +0500 Subject: [PATCH] homework --- TagCloud/API/ConsoleAPI.cs | 45 ++++++ TagCloud/API/Handlers.cs | 29 ++++ TagCloud/API/HandlersConfig.cs | 81 +++++++++++ TagCloud/API/Messages.cs | 55 ++++++++ TagCloud/CloudDrawer/CloudDrawer.cs | 39 ++++++ TagCloud/CloudDrawer/ICloudDrawer.cs | 10 ++ .../CloudLayouter/CircularCloudLayouter.cs | 51 +++++++ TagCloud/CloudLayouter/ICloudLayouter.cs | 10 ++ TagCloud/Config/AppConfig.cs | 54 ++++++++ TagCloud/Config/DefaultConfigs.cs | 23 ++++ TagCloud/Models/CloudLayouterConfig.cs | 4 + TagCloud/Models/ColorScheme.cs | 14 ++ TagCloud/Models/FontConfig.cs | 6 + TagCloud/Models/ImageConfig.cs | 6 + TagCloud/Models/Word.cs | 4 + TagCloud/Models/WordTag.cs | 6 + TagCloud/Program.cs | 27 ++++ TagCloud/ReadWriter/ConsoleReadWriter.cs | 26 ++++ TagCloud/ReadWriter/IReadWriter.cs | 11 ++ TagCloud/TagCloud.csproj | 15 ++ TagCloud/TagCloudService/ITagCloudService.cs | 14 ++ TagCloud/TagCloudService/TagCloudService.cs | 52 +++++++ TagCloud/WordsProcessor/IWordProcessor.cs | 9 ++ TagCloud/WordsProcessor/WordProcessor.cs | 57 ++++++++ TagCloudTests/ConsoleAPI_Should.cs | 51 +++++++ TagCloudTests/TagCloudService_Should.cs | 82 +++++++++++ TagCloudTests/TagCloudTests.csproj | 30 ++++ TagCloudTests/WordProcessor_Should.cs | 129 ++++++++++++++++++ boringWords.txt | 3 + di.sln | 20 ++- global.json | 2 +- wordsData.txt | 22 +++ 32 files changed, 985 insertions(+), 2 deletions(-) create mode 100644 TagCloud/API/ConsoleAPI.cs create mode 100644 TagCloud/API/Handlers.cs create mode 100644 TagCloud/API/HandlersConfig.cs create mode 100644 TagCloud/API/Messages.cs create mode 100644 TagCloud/CloudDrawer/CloudDrawer.cs create mode 100644 TagCloud/CloudDrawer/ICloudDrawer.cs create mode 100644 TagCloud/CloudLayouter/CircularCloudLayouter.cs create mode 100644 TagCloud/CloudLayouter/ICloudLayouter.cs create mode 100644 TagCloud/Config/AppConfig.cs create mode 100644 TagCloud/Config/DefaultConfigs.cs create mode 100644 TagCloud/Models/CloudLayouterConfig.cs create mode 100644 TagCloud/Models/ColorScheme.cs create mode 100644 TagCloud/Models/FontConfig.cs create mode 100644 TagCloud/Models/ImageConfig.cs create mode 100644 TagCloud/Models/Word.cs create mode 100644 TagCloud/Models/WordTag.cs create mode 100644 TagCloud/Program.cs create mode 100644 TagCloud/ReadWriter/ConsoleReadWriter.cs create mode 100644 TagCloud/ReadWriter/IReadWriter.cs create mode 100644 TagCloud/TagCloud.csproj create mode 100644 TagCloud/TagCloudService/ITagCloudService.cs create mode 100644 TagCloud/TagCloudService/TagCloudService.cs create mode 100644 TagCloud/WordsProcessor/IWordProcessor.cs create mode 100644 TagCloud/WordsProcessor/WordProcessor.cs create mode 100644 TagCloudTests/ConsoleAPI_Should.cs create mode 100644 TagCloudTests/TagCloudService_Should.cs create mode 100644 TagCloudTests/TagCloudTests.csproj create mode 100644 TagCloudTests/WordProcessor_Should.cs create mode 100644 boringWords.txt create mode 100644 wordsData.txt diff --git a/TagCloud/API/ConsoleAPI.cs b/TagCloud/API/ConsoleAPI.cs new file mode 100644 index 00000000..97a98534 --- /dev/null +++ b/TagCloud/API/ConsoleAPI.cs @@ -0,0 +1,45 @@ +using TagCloud.CloudDrawer; +using TagCloud.Config; +using TagCloud.ReadWriter; +using TagCloud.TagCloudService; + +namespace TagCloud.API +{ + public class ConsoleAPI + { + private readonly IReadWriter readWriter; + private readonly ICloudDrawer drawer; + private readonly ITagCloudService tagCloudService; + + private AppConfig appConfig; + + public ConsoleAPI(IReadWriter reader, ICloudDrawer drawer, ITagCloudService tagCloudService, AppConfig appConfig) + { + this.readWriter = reader; + this.drawer = drawer; + this.tagCloudService = tagCloudService; + + this.appConfig = appConfig; + } + + public void Start() + { + var wordsPath = readWriter.ReadLine(Messages.BeforeWordsDataInput, Messages.FileNotFound, Handlers.GetPath); + var boringPath = readWriter.ReadLine(Messages.BeforeBoringWordsDataInput, Messages.FileNotFound, Handlers.GetPath); + + var set = readWriter.ReadLine(Messages.UseDefaultConfig, Messages.BadFormat, + x => (HandlersConfig.SetAppConfig.TryGetValue(x, out var set), set)); + + set(appConfig, readWriter); + + var wordsData = readWriter.ReadDataFromFile(wordsPath); + var boringWordsData = readWriter.ReadDataFromFile(boringPath); + + var words = tagCloudService.GetWordTags(wordsData, boringWordsData); + + drawer.DrawWordsAndSave(words); + + readWriter.WriteLine(Messages.Success); + } + } +} diff --git a/TagCloud/API/Handlers.cs b/TagCloud/API/Handlers.cs new file mode 100644 index 00000000..9235f8dc --- /dev/null +++ b/TagCloud/API/Handlers.cs @@ -0,0 +1,29 @@ +using System.Drawing; + +namespace TagCloud.API +{ + public static class Handlers + { + public static (bool, int) ParseInt(string input) => (int.TryParse(input, out var converted), converted); + + public static (bool, float) ParseFloat(string input) => (float.TryParse(input, out var converted), converted); + + public static (bool, string) GetPath(string input) => (File.Exists(input), input); + + public static (bool, Color) GetColorFromName(string input) + { + var color = Color.FromName(input); + return (color.IsKnownColor, color); + } + + public static (bool, string) GetFontFamilyName(string input) => (FontFamily.Families.Any(x => x.Name == input), input); + + public static (bool, TEnum) ParseEnum(string input) + { + if (Enum.TryParse(typeof(TEnum), input, true, out var enumValue)) + return (true, (TEnum)enumValue); + + return (false, default(TEnum)); + } + } +} diff --git a/TagCloud/API/HandlersConfig.cs b/TagCloud/API/HandlersConfig.cs new file mode 100644 index 00000000..d7b8939b --- /dev/null +++ b/TagCloud/API/HandlersConfig.cs @@ -0,0 +1,81 @@ +using System.Drawing; +using TagCloud.Config; +using TagCloud.ReadWriter; +using TagCloud.Models; + +namespace TagCloud.API +{ + public static class HandlersConfig + { + public static Dictionary> SetAppConfig + { + get + { + return new Dictionary>() + { + { "Да", SetDefaultConfig }, + { "Нет", SetCustomConfig }, + }; + } + } + + private static void SetDefaultConfig(AppConfig config, IReadWriter _) + { + config.CloudLayouterConfig = DefaultConfigs.DefaultCloudLayouterConfig; + config.FontConfig = DefaultConfigs.DefaultFontConfig; + config.ImageConfig = DefaultConfigs.DefaultImageConfig; + } + + private static void SetCustomConfig(AppConfig config, IReadWriter readWriter) + { + config.CloudLayouterConfig = GetCustomCloudLayouterConfig(readWriter); + config.FontConfig = GetCustomFontConfig(readWriter); + config.ImageConfig = GetCustomImageConfig(readWriter); + } + + private static ImageConfig GetCustomImageConfig(IReadWriter readWriter) + { + var width = readWriter.ReadLine(Messages.BeforeWidthInput, Messages.BadFormat, Handlers.ParseInt); + + var height = readWriter.ReadLine(Messages.BeforeHeightInput, Messages.BadFormat, Handlers.ParseInt); + + var background = readWriter.ReadLine(Messages.BeforeBackgroundInput, Messages.UnknownColor, Handlers.GetColorFromName); + + var countColors = readWriter.ReadLine(Messages.CountWordsColors, Messages.BadFormat, Handlers.ParseInt); + + var wordsColors = new Color[countColors]; + for (int i = 0; i< countColors; i++) + wordsColors[i] = readWriter.ReadLine(Messages.BeforeColorInput, Messages.UnknownColor, Handlers.GetColorFromName); + + var colorScheme = readWriter.ReadLine(Messages.ColorSchemeInput, Messages.UnknownColorScheme, Handlers.ParseEnum); + + return new ImageConfig(width, height, background, wordsColors, colorScheme); + } + + private static FontConfig GetCustomFontConfig(IReadWriter readWriter) + { + var fontFamily = readWriter.ReadLine(Messages.FontFamilyName, Messages.UnknownFontFamilyName, Handlers.GetFontFamilyName); + + var fontSize = readWriter.ReadLine(Messages.FontSize, Messages.BadFormat, Handlers.ParseFloat); + + var fontStyle = readWriter.ReadLine(Messages.FontStyle, Messages.UnknownFontStyle, Handlers.ParseEnum); + + var increase = readWriter.ReadLine(Messages.FontIncrease, Messages.BadFormat, Handlers.ParseInt); + + return new FontConfig(fontFamily, fontSize, fontStyle, increase); + } + + private static CloudLayouterConfig GetCustomCloudLayouterConfig(IReadWriter readWriter) + { + var radius = readWriter.ReadLine(Messages.BeforeRadiusInput, Messages.BadFormat, Handlers.ParseInt); + + var deltaRadius = readWriter.ReadLine(Messages.BeforeRadiusDeltaInput, Messages.BadFormat, Handlers.ParseInt); + + var angle = readWriter.ReadLine(Messages.BeforeAngleInput, Messages.BadFormat, Handlers.ParseInt); + + var deltaAngle = readWriter.ReadLine(Messages.BeforeAngleDeltaInput, Messages.BadFormat, Handlers.ParseInt); + + return new CloudLayouterConfig(radius, deltaRadius, angle, deltaAngle); + } + } +} diff --git a/TagCloud/API/Messages.cs b/TagCloud/API/Messages.cs new file mode 100644 index 00000000..b243641e --- /dev/null +++ b/TagCloud/API/Messages.cs @@ -0,0 +1,55 @@ +namespace TagCloud.API +{ + public static class Messages + { + public const string FileNotFound = "Файл не найден."; + + public const string BeforeWordsDataInput = "Введите путь до файла со словами облака:"; + + public const string BeforeBoringWordsDataInput = "Введите путь до файла со скучными словами:"; + + public const string BeforeWidthInput = "Введите ширину изображения:"; + + public const string BeforeHeightInput = "Введите высоту изображения:"; + + public const string UseDefaultConfig = "Использовать стандартные настройки? [Да/Нет]"; + + public const string BadFormat = "Неправильный формат ввода."; + + public const string BeforeBackgroundInput = "Введите цвет заднего фона:"; + + public const string BeforeColorInput = "Введите цвет:"; + + public const string UnknownColor = "Неизвестный цвет"; + + public const string CountWordsColors = "Введите кол-во цветов для слов:"; + + public const string FontFamilyName = "Введите название семейства шрифтов:"; + + public const string UnknownFontFamilyName = "Неизвестный шрифт"; + + public const string FontStyle = "Введите стиль шрифта:"; + + public const string UnknownFontStyle = "Неизвестный стиль шрифта"; + + public const string FontIncrease = "Введите увеличение размера шрифта:"; + + public const string FontSize = "Введите размер шрифта:"; + + public const string Filename = "result.png"; + + public const string Success = $"Файл {Filename} успешно сохранен!"; + + public const string BeforeRadiusInput = "Введите радиус для облака тегов:"; + + public const string BeforeRadiusDeltaInput = "Введите изменение радиуса для облака тегов:"; + + public const string BeforeAngleInput = "Введите угол для облака тегов:"; + + public const string BeforeAngleDeltaInput = "Введите изменение угла для облака тегов:"; + + public const string ColorSchemeInput = "Введите алгоритм расскраски слов:"; + + public const string UnknownColorScheme = "Неизвестный алгоритм расскраски слов:"; + } +} diff --git a/TagCloud/CloudDrawer/CloudDrawer.cs b/TagCloud/CloudDrawer/CloudDrawer.cs new file mode 100644 index 00000000..8d94c678 --- /dev/null +++ b/TagCloud/CloudDrawer/CloudDrawer.cs @@ -0,0 +1,39 @@ +using System.Drawing; +using TagCloud.API; +using TagCloud.Config; +using TagCloud.Models; + +namespace TagCloud.CloudDrawer +{ + public class CloudDrawer : ICloudDrawer + { + private readonly AppConfig appConfig; + + public CloudDrawer(AppConfig appConfig) + { + this.appConfig = appConfig; + } + + public void DrawWordsAndSave(IEnumerable words) + { + var imageConfig = appConfig.ImageConfig; + + var bitmap = new Bitmap(imageConfig.Width, imageConfig.Height); + + var graphics = Graphics.FromImage(bitmap); + + graphics.Clear(imageConfig.Background); + + var index = 0; + + foreach (var word in words) + { + var brush = new SolidBrush(imageConfig.WordColors[index % imageConfig.WordColors.Length]); + graphics.DrawString(word.Content, word.Font, brush, word.Rectangle); + index++; + } + + bitmap.Save(Messages.Filename); + } + } +} diff --git a/TagCloud/CloudDrawer/ICloudDrawer.cs b/TagCloud/CloudDrawer/ICloudDrawer.cs new file mode 100644 index 00000000..7136a920 --- /dev/null +++ b/TagCloud/CloudDrawer/ICloudDrawer.cs @@ -0,0 +1,10 @@ +using System.Drawing; +using TagCloud.Models; + +namespace TagCloud.CloudDrawer +{ + public interface ICloudDrawer + { + void DrawWordsAndSave(IEnumerable words); + } +} diff --git a/TagCloud/CloudLayouter/CircularCloudLayouter.cs b/TagCloud/CloudLayouter/CircularCloudLayouter.cs new file mode 100644 index 00000000..97bf86b8 --- /dev/null +++ b/TagCloud/CloudLayouter/CircularCloudLayouter.cs @@ -0,0 +1,51 @@ +using System.Drawing; +using TagCloud.Config; +using TagCloud.Models; + +namespace TagCloud.CircularCloudLayouter +{ + public class CircularCloudLayouter :ICloudLayouter + { + private AppConfig appConfig; + + public CircularCloudLayouter(AppConfig appConfig) + { + this.appConfig = appConfig; + } + + public RectangleF GetPossibleNextRectangle(IEnumerable cloudRectangles, SizeF rectangleSize) + { + if (rectangleSize.Width <= 0 || rectangleSize.Height <= 0) + throw new ArgumentException("the width and height of the rectangle must be positive numbers"); + + return FindPossibleNextRectangle(cloudRectangles, rectangleSize); + } + + private RectangleF FindPossibleNextRectangle(IEnumerable cloudRectangles, SizeF rectangleSize) + { + var cloudLayouterConfig = appConfig.CloudLayouterConfig; + var imageConfig = appConfig.ImageConfig; + + var center = new Point(imageConfig.Width / 2, imageConfig.Height / 2); + + var radius = cloudLayouterConfig.Radius; + var angle = cloudLayouterConfig.Angle; + + while (true) + { + var point = new Point( + (int)(center.X + radius * Math.Cos(angle)), + (int)(center.Y + radius * Math.Sin(angle)) + ); + + var possibleRectangle = new RectangleF(point, rectangleSize); + + if (!cloudRectangles.Any(textRectangle => textRectangle.Rectangle.IntersectsWith(possibleRectangle))) + return possibleRectangle; + + angle += cloudLayouterConfig.DeltaAngle; + radius += cloudLayouterConfig.DeltaRadius; + } + } + } +} diff --git a/TagCloud/CloudLayouter/ICloudLayouter.cs b/TagCloud/CloudLayouter/ICloudLayouter.cs new file mode 100644 index 00000000..2ff9e508 --- /dev/null +++ b/TagCloud/CloudLayouter/ICloudLayouter.cs @@ -0,0 +1,10 @@ +using System.Drawing; +using TagCloud.Models; + +namespace TagCloud.CircularCloudLayouter +{ + public interface ICloudLayouter + { + RectangleF GetPossibleNextRectangle(IEnumerable cloudRectangles, SizeF rectangleSize); + } +} diff --git a/TagCloud/Config/AppConfig.cs b/TagCloud/Config/AppConfig.cs new file mode 100644 index 00000000..dfaabfd7 --- /dev/null +++ b/TagCloud/Config/AppConfig.cs @@ -0,0 +1,54 @@ +using System.Drawing; +using TagCloud.Models; + +namespace TagCloud.Config +{ + public class AppConfig + { + public CloudLayouterConfig CloudLayouterConfig { get; set; } + + public FontConfig FontConfig { get; set; } + + public ImageConfig ImageConfig { get; set; } + + private Dictionary> ColorSchemas; + + private Random rnd; + + private int index; + + public AppConfig() + { + CloudLayouterConfig = DefaultConfigs.DefaultCloudLayouterConfig; + + FontConfig = DefaultConfigs.DefaultFontConfig; + + ImageConfig = DefaultConfigs.DefaultImageConfig; + + rnd = new Random(); + index = -1; + + ColorSchemas = new Dictionary> + { + {ColorScheme.Random, ColorSchemeRandom}, + {ColorScheme.RoundRobin, ColorSchemeRoundRobin}, + }; + } + + public Color GetNextColor() => ColorSchemas[ImageConfig.ColorScheme](); + + private Color ColorSchemeRandom() + { + var index = rnd.Next(0, ImageConfig.WordColors.Length); + + return ImageConfig.WordColors[index]; + } + + private Color ColorSchemeRoundRobin() + { + index = (index + 1) % ImageConfig.WordColors.Length; + + return ImageConfig.WordColors[index]; + } + } +} diff --git a/TagCloud/Config/DefaultConfigs.cs b/TagCloud/Config/DefaultConfigs.cs new file mode 100644 index 00000000..c6c05b5a --- /dev/null +++ b/TagCloud/Config/DefaultConfigs.cs @@ -0,0 +1,23 @@ +using System.Drawing; +using TagCloud.Models; + +namespace TagCloud.Config +{ + public static class DefaultConfigs + { + public static ImageConfig DefaultImageConfig + { + get { return new ImageConfig(800, 800, Color.Black, [Color.Red, Color.Blue, Color.Green, Color.White], ColorScheme.RoundRobin); } + } + + public static CloudLayouterConfig DefaultCloudLayouterConfig + { + get { return new CloudLayouterConfig(2, 5, 2, 5); } + } + + public static FontConfig DefaultFontConfig + { + get { return new FontConfig("GenericSerif", 14, FontStyle.Regular, 5); } + } + } +} diff --git a/TagCloud/Models/CloudLayouterConfig.cs b/TagCloud/Models/CloudLayouterConfig.cs new file mode 100644 index 00000000..55121f78 --- /dev/null +++ b/TagCloud/Models/CloudLayouterConfig.cs @@ -0,0 +1,4 @@ +namespace TagCloud.Models +{ + public record struct CloudLayouterConfig(int Radius, int DeltaRadius, int Angle, int DeltaAngle); +} diff --git a/TagCloud/Models/ColorScheme.cs b/TagCloud/Models/ColorScheme.cs new file mode 100644 index 00000000..7830e6b3 --- /dev/null +++ b/TagCloud/Models/ColorScheme.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TagCloud.Models +{ + public enum ColorScheme + { + RoundRobin, + Random + } +} diff --git a/TagCloud/Models/FontConfig.cs b/TagCloud/Models/FontConfig.cs new file mode 100644 index 00000000..eaa81bc2 --- /dev/null +++ b/TagCloud/Models/FontConfig.cs @@ -0,0 +1,6 @@ +using System.Drawing; + +namespace TagCloud.Models +{ + public record struct FontConfig(string FontFamily, float FontSize, FontStyle Style, int FontIncreaseByWordLevel); +} diff --git a/TagCloud/Models/ImageConfig.cs b/TagCloud/Models/ImageConfig.cs new file mode 100644 index 00000000..79dc2ec5 --- /dev/null +++ b/TagCloud/Models/ImageConfig.cs @@ -0,0 +1,6 @@ +using System.Drawing; + +namespace TagCloud.Models +{ + public record struct ImageConfig(int Width, int Height, Color Background, Color[] WordColors, ColorScheme ColorScheme); +} diff --git a/TagCloud/Models/Word.cs b/TagCloud/Models/Word.cs new file mode 100644 index 00000000..211b93d8 --- /dev/null +++ b/TagCloud/Models/Word.cs @@ -0,0 +1,4 @@ +namespace TagCloud.Models +{ + public record struct Word(string Content, float FontSize); +} diff --git a/TagCloud/Models/WordTag.cs b/TagCloud/Models/WordTag.cs new file mode 100644 index 00000000..a58e2d53 --- /dev/null +++ b/TagCloud/Models/WordTag.cs @@ -0,0 +1,6 @@ +using System.Drawing; + +namespace TagCloud.Models +{ + public record struct WordTag(RectangleF Rectangle, string Content, Font Font); +} diff --git a/TagCloud/Program.cs b/TagCloud/Program.cs new file mode 100644 index 00000000..4af95fe1 --- /dev/null +++ b/TagCloud/Program.cs @@ -0,0 +1,27 @@ +using Autofac; +using TagCloud.API; +using TagCloud.CircularCloudLayouter; +using TagCloud.CloudDrawer; +using TagCloud.Config; +using TagCloud.ReadWriter; +using TagCloud.Service; +using TagCloud.TagCloudService; +using TagCloud.WordsProcessor; +using static System.Formats.Asn1.AsnWriter; + +var builder = new ContainerBuilder(); + +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().AsSelf().SingleInstance(); + +var container = builder.Build(); + +var api = container.Resolve(); +api.Start(); \ No newline at end of file diff --git a/TagCloud/ReadWriter/ConsoleReadWriter.cs b/TagCloud/ReadWriter/ConsoleReadWriter.cs new file mode 100644 index 00000000..0fb3f102 --- /dev/null +++ b/TagCloud/ReadWriter/ConsoleReadWriter.cs @@ -0,0 +1,26 @@ +namespace TagCloud.ReadWriter +{ + public class ConsoleReadWriter : IReadWriter + { + public IEnumerable ReadDataFromFile(string path) => File.ReadAllLines(path).Select(line => line.Trim().ToLower()); + + public TOut ReadLine(string beforeInput, string badInput, Func check) + { + while (true) + { + Console.WriteLine(beforeInput); + + var input = Console.ReadLine().Trim(); + + var (ok, converted) = check(input); + + if (ok) + return converted; + + Console.WriteLine(badInput); + } + } + + public void WriteLine(string input) => Console.WriteLine(input); + } +} diff --git a/TagCloud/ReadWriter/IReadWriter.cs b/TagCloud/ReadWriter/IReadWriter.cs new file mode 100644 index 00000000..26ef7a85 --- /dev/null +++ b/TagCloud/ReadWriter/IReadWriter.cs @@ -0,0 +1,11 @@ +namespace TagCloud.ReadWriter +{ + public interface IReadWriter + { + IEnumerable ReadDataFromFile(string path); + + TOut ReadLine(string beforeInputMsg, string badInputMsg, Func check); + + void WriteLine(string msg); + } +} diff --git a/TagCloud/TagCloud.csproj b/TagCloud/TagCloud.csproj new file mode 100644 index 00000000..7bda4840 --- /dev/null +++ b/TagCloud/TagCloud.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/TagCloud/TagCloudService/ITagCloudService.cs b/TagCloud/TagCloudService/ITagCloudService.cs new file mode 100644 index 00000000..d8990244 --- /dev/null +++ b/TagCloud/TagCloudService/ITagCloudService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TagCloud.Models; + +namespace TagCloud.TagCloudService +{ + public interface ITagCloudService + { + IEnumerable GetWordTags(IEnumerable wordsData, IEnumerable boringWords); + } +} diff --git a/TagCloud/TagCloudService/TagCloudService.cs b/TagCloud/TagCloudService/TagCloudService.cs new file mode 100644 index 00000000..2aef48bd --- /dev/null +++ b/TagCloud/TagCloudService/TagCloudService.cs @@ -0,0 +1,52 @@ +using System.Drawing; +using TagCloud.CircularCloudLayouter; +using TagCloud.Config; +using TagCloud.Models; +using TagCloud.TagCloudService; +using TagCloud.WordsProcessor; + +namespace TagCloud.Service +{ + public class TagCloudService : ITagCloudService + { + private readonly ICloudLayouter cloudLayouter; + private readonly IWordProcessor wordProcessor; + + private readonly AppConfig appConfig; + + public TagCloudService(ICloudLayouter cloudLayouter, IWordProcessor wordProcessor, AppConfig appConfig) + { + this.cloudLayouter = cloudLayouter; + this.wordProcessor = wordProcessor; + + this.appConfig = appConfig; + } + + public IEnumerable GetWordTags(IEnumerable wordsData, IEnumerable boringWords) + { + var imageConfig = appConfig.ImageConfig; + var fontConfig = appConfig.FontConfig; + + var rectangles = new List(); + + var words = wordProcessor.GetProcessedData(wordsData, boringWords); + + var bitmap = new Bitmap(imageConfig.Width, imageConfig.Height); + + var graphics = Graphics.FromImage(bitmap); + + foreach (var word in words) + { + var font = new Font(fontConfig.FontFamily, word.FontSize, fontConfig.Style); + + var textSize = graphics.MeasureString(word.Content, font); + + var rectangle = cloudLayouter.GetPossibleNextRectangle(rectangles, textSize); + + rectangles.Add(new WordTag(rectangle, word.Content, font)); + } + + return rectangles; + } + } +} diff --git a/TagCloud/WordsProcessor/IWordProcessor.cs b/TagCloud/WordsProcessor/IWordProcessor.cs new file mode 100644 index 00000000..af751dd2 --- /dev/null +++ b/TagCloud/WordsProcessor/IWordProcessor.cs @@ -0,0 +1,9 @@ +using TagCloud.Models; + +namespace TagCloud.WordsProcessor +{ + public interface IWordProcessor + { + IEnumerable GetProcessedData(IEnumerable wordsData, IEnumerable boringWords); + } +} diff --git a/TagCloud/WordsProcessor/WordProcessor.cs b/TagCloud/WordsProcessor/WordProcessor.cs new file mode 100644 index 00000000..4eff8f5d --- /dev/null +++ b/TagCloud/WordsProcessor/WordProcessor.cs @@ -0,0 +1,57 @@ +using TagCloud.Config; +using TagCloud.Models; + +namespace TagCloud.WordsProcessor +{ + public class WordProcessor : IWordProcessor + { + private readonly AppConfig appConfig; + + public WordProcessor(AppConfig appConfig) + { + this.appConfig = appConfig; + } + + public IEnumerable GetProcessedData(IEnumerable wordsData, IEnumerable boringWords) + { + var cache = new Dictionary(); + + var succesWords = wordsData.Where(word => !boringWords.Contains(word)); + + foreach (var word in succesWords) + { + if (!cache.ContainsKey(word)) + cache[word] = 0; + + cache[word]++; + } + + var sortedWords = cache.OrderBy(wordCountPair => cache[wordCountPair.Key]); + + var prevCount = sortedWords.LastOrDefault().Value; + var level = 0; + + var stack = new Stack(); + + foreach (var (word, count) in sortedWords) + { + if (count - prevCount > 0) + level++; + + stack.Push(new Word(word, CalculateFontSize(level))); + + prevCount = count; + } + + return stack; + } + + private float CalculateFontSize(int sizeLevel) + { + var fontIncreaseByWordLevel = appConfig.FontConfig.FontIncreaseByWordLevel; + var defaultSize = appConfig.FontConfig.FontSize; + + return defaultSize + sizeLevel * fontIncreaseByWordLevel; + } + } +} diff --git a/TagCloudTests/ConsoleAPI_Should.cs b/TagCloudTests/ConsoleAPI_Should.cs new file mode 100644 index 00000000..fbe7b068 --- /dev/null +++ b/TagCloudTests/ConsoleAPI_Should.cs @@ -0,0 +1,51 @@ +using FakeItEasy; +using FluentAssertions; +using System.Drawing; +using TagCloud.API; +using TagCloud.CircularCloudLayouter; +using TagCloud.CloudDrawer; +using TagCloud.Config; +using TagCloud.Models; +using TagCloud.ReadWriter; +using TagCloud.Service; +using TagCloud.TagCloudService; +using TagCloud.WordsProcessor; + +namespace TagCloudTests +{ + [TestFixture] + public class ConsoleAPI_Should + { + private AppConfig appConfig; + + [SetUp] + public void SetUp() + { + appConfig = new AppConfig(); + } + + [Test] + public void ConsoleAPI_UseDefaultAppConfigTest() + { + var readerMock = A.Fake(); + var drawerMock = A.Fake(); + var tagCloudServiceMock = A.Fake(); + + Func)> configCheck = + x => (HandlersConfig.SetAppConfig.TryGetValue(x, out var set), set); + + A.CallTo(() => readerMock.ReadLine(null, null, null)).WithAnyArguments().Returns(string.Empty); + A.CallTo(() => readerMock.ReadLine(Messages.UseDefaultConfig, Messages.BadFormat, configCheck)).Returns(HandlersConfig.SetAppConfig["Да"]); + + var appConfig = new AppConfig(); + + var api = new ConsoleAPI(readerMock, drawerMock, tagCloudServiceMock, appConfig); + + api.Start(); + + var defaultAppConfig = new AppConfig(); + + appConfig.Should().BeEquivalentTo(defaultAppConfig); + } + } +} diff --git a/TagCloudTests/TagCloudService_Should.cs b/TagCloudTests/TagCloudService_Should.cs new file mode 100644 index 00000000..8a2d6b49 --- /dev/null +++ b/TagCloudTests/TagCloudService_Should.cs @@ -0,0 +1,82 @@ +using FluentAssertions; +using TagCloud.CircularCloudLayouter; +using TagCloud.Config; +using TagCloud.Models; +using TagCloud.Service; +using TagCloud.WordsProcessor; + +namespace TagCloudTests +{ + [TestFixture] + public class TagCloudService_Should + { + private TagCloudService tagCloudService; + + [SetUp] + public void SetUp() + { + var appConfig = new AppConfig(); + var cloudLayouter = new CircularCloudLayouter(appConfig); + var wordProcessor = new WordProcessor(appConfig); + + tagCloudService = new TagCloudService(cloudLayouter, wordProcessor, appConfig); + } + + [Test] + public void CreateAnEmptyLayout_WhenAllDataIsEmpty() + { + var result = tagCloudService.GetWordTags([], []); + + result.Should().BeEmpty(); + } + + [Test] + public void CreateLayoutWithoutIntersections_WhenNoRepeatedWords() + { + var wordsData = new List() + { + "яблоко", + "банан", + "груша", + }; + + var boringData = new List(); + + var wordTags = tagCloudService.GetWordTags(wordsData, boringData); + + IsIntersections(wordTags).Should().BeFalse(); + } + + [Test] + public void CreateLayoutWithoutIntersections_WithRepeatedWords() + { + var wordsData = new List() + { + "яблоко", + "банан", + "груша", + "яблоко", + "банан", + "яблоко" + }; + + var boringData = new List(); + + var wordTags = tagCloudService.GetWordTags(wordsData, boringData); + + IsIntersections(wordTags).Should().BeFalse(); + } + + public bool IsIntersections(IEnumerable wordTags) + { + var rectangles = wordTags.Select(x => x.Rectangle).ToList(); + + for (int i = 0; i < rectangles.Count; i++) + for (int j = i + 1; j < rectangles.Count; j++) + if (rectangles[i].IntersectsWith(rectangles[j])) + return true; + + return false; + } + } +} diff --git a/TagCloudTests/TagCloudTests.csproj b/TagCloudTests/TagCloudTests.csproj new file mode 100644 index 00000000..b6d27e5f --- /dev/null +++ b/TagCloudTests/TagCloudTests.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + + diff --git a/TagCloudTests/WordProcessor_Should.cs b/TagCloudTests/WordProcessor_Should.cs new file mode 100644 index 00000000..6c6662ec --- /dev/null +++ b/TagCloudTests/WordProcessor_Should.cs @@ -0,0 +1,129 @@ +using FluentAssertions; +using TagCloud.Config; +using TagCloud.Models; +using TagCloud.WordsProcessor; + +namespace TagCloudTests +{ + [TestFixture] + public class WordProcessor_Should + { + private WordProcessor wordProcessor; + private AppConfig appConfig; + + [SetUp] + public void SetUp() + { + appConfig = new AppConfig(); + + wordProcessor = new WordProcessor(appConfig); + } + + [Test] + public void GetProcessedData_WhenFileWithBoringWordsIsEmpty() + { + var wordsData = new List() + { + "", + "", + "", + }; + + var boringData = new List(); + + var processedData = wordProcessor.GetProcessedData(wordsData, boringData); + + processedData.Should().HaveCount(wordsData.Count); + } + + [Test] + public void GetProcessedData_WhenFileWithBoringWordsIsNotEmpty() + { + var wordsData = new List() + { + "", + "", + "", + }; + + var boringData = new List() + { + "", + "", + }; + + var processedData = wordProcessor.GetProcessedData(wordsData, boringData); + + processedData.Should().NotContain(word => boringData.Contains(word.Content)); + } + + [Test] + public void GetProcessedData_CorrectCalculateWordSizeLevel() + { + var wordsData = new List() + { + "", + "", + "", + "", + "", + "", + "", + "", + }; + + var boringData = new List(); + + var fontSize = appConfig.FontConfig.FontSize; + var increase = appConfig.FontConfig.FontIncreaseByWordLevel; + + var expectedData = new List() + { + new( "", fontSize), + new( "", fontSize + increase), + new( "", fontSize + 2 * increase), + }; + + var processedData = wordProcessor.GetProcessedData(wordsData, boringData); + + processedData.Should().BeEquivalentTo(expectedData); + } + + [Test] + public void GetProcessedData_InDescendingOrder() + { + var wordsData = new List() + { + "", + "", + "", + "", + "", + "", + "", + "", + }; + + var boringData = new List(); + + var fontSize = appConfig.FontConfig.FontSize; + var increase = appConfig.FontConfig.FontIncreaseByWordLevel; + + var processedData = wordProcessor.GetProcessedData(wordsData, boringData); + + processedData.Should().BeInDescendingOrder(word => word.FontSize); + } + + [Test] + public void ReturnEmptyProcessedData_WhenSourceWordFileIsEmpty() + { + var wordsData = new List(); + + var boringData = new List(); + + var processedData = wordProcessor.GetProcessedData(wordsData, boringData); + + processedData.Should().BeEmpty(); + } + } +} \ No newline at end of file diff --git a/boringWords.txt b/boringWords.txt new file mode 100644 index 00000000..578e8c8e --- /dev/null +++ b/boringWords.txt @@ -0,0 +1,3 @@ +sit +amet +elit diff --git a/di.sln b/di.sln index a50991da..2b4db890 100644 --- a/di.sln +++ b/di.sln @@ -1,6 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "di", "FractalPainter/di.csproj", "{4B70F6B3-5C20-40D2-BFC9-D95C18D65DD6}" +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35521.163 d17.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "di", "FractalPainter\di.csproj", "{4B70F6B3-5C20-40D2-BFC9-D95C18D65DD6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagCloud", "TagCloud\TagCloud.csproj", "{604D58F7-F220-4F8F-9837-A0C3EF11C623}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagCloudTests", "TagCloudTests\TagCloudTests.csproj", "{0C03B60C-46C4-4C3D-B80C-A118FFCF6513}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -12,5 +19,16 @@ Global {4B70F6B3-5C20-40D2-BFC9-D95C18D65DD6}.Debug|Any CPU.Build.0 = Debug|Any CPU {4B70F6B3-5C20-40D2-BFC9-D95C18D65DD6}.Release|Any CPU.ActiveCfg = Release|Any CPU {4B70F6B3-5C20-40D2-BFC9-D95C18D65DD6}.Release|Any CPU.Build.0 = Release|Any CPU + {604D58F7-F220-4F8F-9837-A0C3EF11C623}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {604D58F7-F220-4F8F-9837-A0C3EF11C623}.Debug|Any CPU.Build.0 = Debug|Any CPU + {604D58F7-F220-4F8F-9837-A0C3EF11C623}.Release|Any CPU.ActiveCfg = Release|Any CPU + {604D58F7-F220-4F8F-9837-A0C3EF11C623}.Release|Any CPU.Build.0 = Release|Any CPU + {0C03B60C-46C4-4C3D-B80C-A118FFCF6513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C03B60C-46C4-4C3D-B80C-A118FFCF6513}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C03B60C-46C4-4C3D-B80C-A118FFCF6513}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C03B60C-46C4-4C3D-B80C-A118FFCF6513}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection EndGlobal diff --git a/global.json b/global.json index 2ddda36c..4f85ebc6 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.0", + "version": "9.0.100", "rollForward": "latestMinor", "allowPrerelease": false } diff --git a/wordsData.txt b/wordsData.txt new file mode 100644 index 00000000..848085b3 --- /dev/null +++ b/wordsData.txt @@ -0,0 +1,22 @@ +Lorem +Lorem +dolor +sit +amet +сonsectetur +adipiscing +elit +Morbi +euismod +dapibus +auctor +Lorem +Lorem +Lorem +Lorem +сonsectetur +adipiscing +сonsectetur +adipiscing +dapibus +auctor