diff --git a/Client/Client.csproj b/Client/Client.csproj
new file mode 100644
index 00000000..57477690
--- /dev/null
+++ b/Client/Client.csproj
@@ -0,0 +1,26 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+
+
diff --git a/Client/DependencyInjection.cs b/Client/DependencyInjection.cs
new file mode 100644
index 00000000..4be2c332
--- /dev/null
+++ b/Client/DependencyInjection.cs
@@ -0,0 +1,69 @@
+using Autofac;
+using TagsCloudContainer;
+using TagsCloudContainer.ColorProviders;
+using TagsCloudContainer.Configuration;
+using TagsCloudContainer.PointGenerators;
+using TagsCloudContainer.StringParsers;
+using TagsCloudContainer.TagGenerator;
+using TagsCloudContainer.TextProcessor;
+using TagsCloudContainer.TextProviders;
+using TagsCloudContainer.WordFilters;
+
+namespace Client;
+
+public class DependencyInjection
+{
+ public static IContainer BuildContainer(Config config)
+ {
+ var container = new ContainerBuilder();
+ container.RegisterInstance(config).AsSelf();
+
+ container.RegisterType(config.SupportedReadingFormats[Path.GetExtension(config.FilePath)])
+ .As()
+ .WithParameter("filePath", config.FilePath)
+ .SingleInstance();
+ container.RegisterType()
+ .As()
+ .SingleInstance();
+
+ container.RegisterType(config.PointGenerator)
+ .As()
+ .SingleInstance();
+
+ if (config.Color != null)
+ container.RegisterType()
+ .As()
+ .WithParameter("color", config.Color)
+ .SingleInstance();
+ else
+ container.RegisterType()
+ .As()
+ .SingleInstance();
+
+ container.RegisterType()
+ .As()
+ .SingleInstance();
+
+ container.RegisterType()
+ .As()
+ .WithParameter("defaultFont", config.Font)
+ .SingleInstance();
+
+
+
+ container.RegisterType().As()
+ .SingleInstance();
+ container.RegisterType().As()
+ .SingleInstance();
+ container.RegisterType().As()
+ .SingleInstance();
+
+ container.RegisterType()
+ .AsSelf()
+ .WithParameter("fileName", config.PicturePath)
+ .WithParameter("startPoint", config.StartPoint)
+ .SingleInstance();
+
+ return container.Build();
+ }
+}
\ No newline at end of file
diff --git a/Client/Program.cs b/Client/Program.cs
new file mode 100644
index 00000000..f9db896b
--- /dev/null
+++ b/Client/Program.cs
@@ -0,0 +1,133 @@
+using System.Drawing;
+using System.Reflection;
+using TagsCloudContainer.Configuration;
+using TagsCloudContainer.PointGenerators;
+using TagsCloudContainer.TextProviders;
+using TagsCloudContainer;
+using Autofac;
+
+namespace Client
+{
+ internal class Program
+ {
+ static void Main()
+ {
+ var config = new Config();
+
+ ConfigureSupportedReadingFormats(config);
+ ConfigureFileSource(config);
+ ConfigureCloudView(config);
+ ConfigureColor(config);
+ ConfigurePathToSave(config);
+ ConfigureStartPoint(config);
+ ConfigureFont(config);
+
+ var container = DependencyInjection.BuildContainer(config);
+ using var scope = container.BeginLifetimeScope();
+ scope.Resolve().DrawPicture();
+ Console.WriteLine($"результат сохранен в {config.PicturePath}");
+ }
+
+ private static void ConfigureSupportedReadingFormats(Config config)
+ {
+ Console.WriteLine("Поддерживаются следующие форматы файлов для чтения:");
+ var textProviders = FindImplemetations();
+ foreach (var point in textProviders)
+ Console.WriteLine("\t" + point.Key);
+ config.SupportedReadingFormats = textProviders;
+ }
+
+ private static void ConfigureFont(Config config)
+ {
+ config.Font = new Font("arial", 12);
+ }
+
+ private static void ConfigurePathToSave(Config config)
+ {
+ Console.WriteLine("Введите полный путь и название файла для сохранения");
+ var inp = Console.ReadLine();
+ config.PicturePath = inp.Length == 0 ? "1.bmp" : inp;
+ }
+
+ private static void ConfigureStartPoint(Config config)
+ {
+ Console.WriteLine("Введите координаты центра поля для рисования" +
+ "\n При некорректном вводе координаты центра составят ( 1000, 1000)");
+ var xLine = ReadValue("Координата Х");
+ var yLine = ReadValue("Координата Y");
+ if (int.TryParse(xLine, out var xResult) &&
+ int.TryParse(yLine, out var yResult))
+ config.StartPoint = new Point(xResult, yResult);
+ config.StartPoint = new Point(1000, 1000);
+ }
+
+ private static void ConfigureFileSource(Config config)
+ {
+ Console.WriteLine("Введите имя файла источника тэгов");
+ var inp = Console.ReadLine();
+ config.FilePath = inp.Length == 0 ? @"TestFile.txt" : inp;
+ }
+
+ private static string GetLabel(RainbowColors color)
+ {
+ var fieldInfo = color.GetType().GetField(color.ToString());
+ var attribute = (LabelAttribute)Attribute.GetCustomAttribute(fieldInfo, typeof(LabelAttribute));
+
+ return attribute.LabelText;
+ }
+
+ private static void ConfigureColor(Config config)
+ {
+ Console.WriteLine("Выборите цвет из возможных:");
+ var colors = Enum.GetValues(typeof(RainbowColors))
+ .Cast()
+ .ToDictionary(color => GetLabel(color).ToLower(), color => color);
+
+ foreach (var color in colors)
+ Console.WriteLine("\t" + color.Key);
+
+ Console.WriteLine("В случае неправильного ввода - цвет будет выбираться случайным образом");
+ var inp = Console.ReadLine().ToLower();
+ if (colors.TryGetValue(inp, out var colorName))
+ {
+ config.Color = Color.FromName(colorName.ToString());
+ Console.WriteLine($"Выбран {inp} цвет");
+ }
+ else
+ Console.WriteLine("Цвет будет выбираться случайно");
+
+ }
+
+ private static void ConfigureCloudView(Config config)
+ {
+ Console.WriteLine("Выберите внешний вид облака из возможных:");
+ var pointGenerators = FindImplemetations();
+ foreach (var point in pointGenerators)
+ Console.WriteLine("\t" + point.Key);
+ Console.WriteLine("Введите, соблюдая орфографию");
+ var pointGenerator = Console.ReadLine().ToLower();
+ if (pointGenerators.TryGetValue(pointGenerator, out var pointGeneratorName))
+ config.PointGenerator = pointGeneratorName;
+ else
+ {
+ Console.WriteLine("Такой формы не предусмотрено");
+ ConfigureCloudView(config);
+ }
+ }
+
+ private static Dictionary FindImplemetations()
+ {
+ var assembly = Assembly.LoadFrom("TagsCloudContainer.dll");
+ var type = typeof(T);
+ return assembly.GetTypes()
+ .Where(t => type.IsAssignableFrom(t) && !t.IsInterface)
+ .ToDictionary(x => x.GetCustomAttribute().LabelText.ToLower(), x => x);
+ }
+
+ private static string? ReadValue(string? argName = null)
+ {
+ Console.Write($"{argName ?? ""}: ");
+ return Console.ReadLine();
+ }
+ }
+}
diff --git a/Client/TestFile.txt b/Client/TestFile.txt
new file mode 100644
index 00000000..b72e73dd
--- /dev/null
+++ b/Client/TestFile.txt
@@ -0,0 +1,2 @@
+корзина корзина корзина корзина фрукты фрукты фрукты овощи овощи овощи яблоки груши бананы смородина персики картофель свекла
+морковь он я ты она за по
\ No newline at end of file
diff --git a/Client/test.doc b/Client/test.doc
new file mode 100644
index 00000000..ff5bcc6f
Binary files /dev/null and b/Client/test.doc differ
diff --git a/Client/test.docx b/Client/test.docx
new file mode 100644
index 00000000..e504defc
Binary files /dev/null and b/Client/test.docx differ
diff --git a/TagsCloudContainer.Tests/CloudLayoutShould.cs b/TagsCloudContainer.Tests/CloudLayoutShould.cs
new file mode 100644
index 00000000..c99dd167
--- /dev/null
+++ b/TagsCloudContainer.Tests/CloudLayoutShould.cs
@@ -0,0 +1,45 @@
+using FluentAssertions;
+using System.Drawing;
+using TagsCloudContainer.PointGenerators;
+
+
+namespace TagsCloudContainer.Tests
+{
+ [TestFixture]
+ public class CloudLayoutShould
+ {
+ [TestCase(1, 2, TestName = "Odd coordinate value results in an even size value")]
+ [TestCase(2, 5, TestName = "Even coordinate value results in an odd size value")]
+ public void MakeRightSizeLayout(int coordinateValue, int sizeValue)
+ {
+ var center = new Point(coordinateValue, coordinateValue);
+ var size = new Size(sizeValue, sizeValue);
+
+ var layout = new CloudLayout(center, new ArchemedianSpiral());
+
+ layout.Size.Should().BeEquivalentTo(size);
+ }
+
+ [TestCase(-1, 1, TestName = "Negative X")]
+ [TestCase(1, -1, TestName = "Negative Y")]
+ [TestCase(0, 1, TestName = "Zero X")]
+ [TestCase(1, 0, TestName = "Zero Y")]
+ public void GetOnlyPositiveCenterCoordinates(int x, int y)
+ {
+ Action makeLayout = () => new CloudLayout(new Point(x, y), new ArchemedianSpiral());
+
+ makeLayout.Should().Throw()
+ .WithMessage("Center coordinates values have to be greater than Zero");
+ }
+
+ [Test]
+ public void PutNextRectangle_ShouldKeepEnteredSize()
+ {
+ var layout = new CloudLayout(new Point(5, 5), new ArchemedianSpiral());
+ var enteredSize = new Size(3, 4);
+ var returnedSize = layout.PutNextRectangle(enteredSize).Size;
+
+ returnedSize.Should().BeEquivalentTo(enteredSize);
+ }
+ }
+}
diff --git a/TagsCloudContainer.Tests/IPointGeneratorShould.cs b/TagsCloudContainer.Tests/IPointGeneratorShould.cs
new file mode 100644
index 00000000..7d0b0061
--- /dev/null
+++ b/TagsCloudContainer.Tests/IPointGeneratorShould.cs
@@ -0,0 +1,44 @@
+using FluentAssertions;
+using System.Drawing;
+using TagsCloudContainer.PointGenerators;
+
+namespace TagsCloudContainer.Tests
+{
+ [TestFixture]
+ public class IPointGeneratorShould
+ {
+ [TestCaseSource(nameof(TestCases))]
+ public void GeneratePoints_MovingAwayFromTheStartFor(IPointGenerator pointGenerator)
+ {
+ var start = new Point(0, 0);
+ var points = pointGenerator.GeneratePoints(start);
+ var nearPoint = points.ElementAt(100);
+ var farPoint = points.ElementAt(1000);
+
+ DistanceBetween(start, nearPoint).Should().BeLessThan(DistanceBetween(start, farPoint));
+ }
+
+ [TestCaseSource(nameof(TestCases))]
+ public void GeneratePoints_ReturnsStartAsFirstPointFor(IPointGenerator pointGenerator)
+ {
+ var start = new Point(100, 100);
+ var firstReturned = pointGenerator.GeneratePoints(start)
+ .First();
+
+ firstReturned.Should().BeEquivalentTo(start);
+ }
+
+ private static IEnumerable TestCases()
+ {
+ yield return new ArchemedianSpiral();
+ yield return new HeartShaped();
+ yield return new DeltaShaped();
+ }
+
+ private static int DistanceBetween(Point start, Point destination)
+ {
+ return (int)Math.Sqrt((start.X - destination.X) * (start.X - destination.X) +
+ (start.Y - destination.Y) * (start.Y - destination.Y));
+ }
+ }
+}
diff --git a/TagsCloudContainer.Tests/TagGeneratorShould.cs b/TagsCloudContainer.Tests/TagGeneratorShould.cs
new file mode 100644
index 00000000..3ab47e91
--- /dev/null
+++ b/TagsCloudContainer.Tests/TagGeneratorShould.cs
@@ -0,0 +1,24 @@
+using FluentAssertions;
+using TagsCloudContainer.ColorProviders;
+using TagsCloudContainer.StringParsers;
+using TagsCloudContainer.TextProviders;
+using TagsCloudContainer.WordFilters;
+
+namespace TagsCloudContainer.Tests
+{
+ public class TagGeneratorShould
+ {
+ [Test]
+ public void SetRightFontSize()
+ {
+ var processor = new TextProcessor.TextProcessor(
+ new TxtTextProvider(@"TextFile1.txt"), new RegexParser(), new ToLowerFilter(), new BoringWordFilter());
+ var words = processor.WordFrequencies();
+ var generator = new TagGenerator.TagGenerator(new RandomColorProvider(), new System.Drawing.Font("arial", 12));
+ var result = generator.GenerateTags(words).First();
+
+ result.Font.Name.Should().Be("Arial");
+ result.Font.Size.Should().Be(36);
+ }
+ }
+}
diff --git a/TagsCloudContainer.Tests/TagsCloudContainer.Tests.csproj b/TagsCloudContainer.Tests/TagsCloudContainer.Tests.csproj
new file mode 100644
index 00000000..6ff8d572
--- /dev/null
+++ b/TagsCloudContainer.Tests/TagsCloudContainer.Tests.csproj
@@ -0,0 +1,35 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
diff --git a/TagsCloudContainer.Tests/TextFile1.txt b/TagsCloudContainer.Tests/TextFile1.txt
new file mode 100644
index 00000000..4bd0f5da
--- /dev/null
+++ b/TagsCloudContainer.Tests/TextFile1.txt
@@ -0,0 +1 @@
+он ОНА ОНО корзина творог печенье Корзина ++ ,- = корЗина по ха за
\ No newline at end of file
diff --git a/TagsCloudContainer.Tests/TextProcessorShould.cs b/TagsCloudContainer.Tests/TextProcessorShould.cs
new file mode 100644
index 00000000..07a5cfa9
--- /dev/null
+++ b/TagsCloudContainer.Tests/TextProcessorShould.cs
@@ -0,0 +1,21 @@
+using FluentAssertions;
+using TagsCloudContainer.StringParsers;
+using TagsCloudContainer.TextProviders;
+using TagsCloudContainer.WordFilters;
+
+namespace TagsCloudContainer.Tests
+{
+ public class TextProcessorShould
+ {
+ [Test]
+ public void Process()
+ {
+ var result = new TextProcessor.TextProcessor(
+ new TxtTextProvider(@"TextFile1.txt"), new RegexParser(), new ToLowerFilter(), new BoringWordFilter(), new ShortWordFilter()).WordFrequencies();
+
+ result.Count.Should().Be(3);
+
+ result.MaxBy(word => word.Value).Value.Should().Be(3);
+ }
+ }
+}
diff --git a/TagsCloudContainer.Tests/TxtTextProviderShould.cs b/TagsCloudContainer.Tests/TxtTextProviderShould.cs
new file mode 100644
index 00000000..461f2818
--- /dev/null
+++ b/TagsCloudContainer.Tests/TxtTextProviderShould.cs
@@ -0,0 +1,23 @@
+using FluentAssertions;
+using TagsCloudContainer.TextProviders;
+
+namespace TagsCloudContainer.Tests
+{
+ public class TxtTextProviderShould
+ {
+ private TxtTextProvider _provider;
+ [SetUp]
+ public void Setup()
+ {
+ _provider = new TxtTextProvider("NotExisted.txt");
+ }
+
+ [Test]
+ public void ThrowExceptionIfFileNotFounded()
+ {
+ Action act = () => _provider.ReadFile();
+
+ act.Should().Throw();
+ }
+ }
+}
\ No newline at end of file
diff --git a/TagsCloudContainer/CloudLayout.cs b/TagsCloudContainer/CloudLayout.cs
new file mode 100644
index 00000000..bcc26d51
--- /dev/null
+++ b/TagsCloudContainer/CloudLayout.cs
@@ -0,0 +1,65 @@
+using System.Drawing;
+using TagsCloudContainer.PointGenerators;
+
+namespace TagsCloudContainer
+{
+ public class CloudLayout
+ {
+ private readonly Point Center;
+ public readonly Size Size;
+ private readonly IEnumerable _points;
+ private List Rectangles { get; set; }
+
+
+ public CloudLayout(Point center, IPointGenerator pointGenerator)
+ {
+ if (center.X <= 0 || center.Y <= 0)
+ throw new ArgumentException("Center coordinates values have to be greater than Zero");
+ Center = center;
+ Size = CountSize(center);
+ Rectangles = [];
+ _points = pointGenerator.GeneratePoints(Center);
+ }
+
+ public CloudLayout(Size size, IPointGenerator pointGenerator)
+ {
+ Size = size;
+ Center = FindCenter(size);
+ Rectangles = [];
+ _points = pointGenerator.GeneratePoints(Center);
+ }
+
+
+ private Size CountSize(Point center)
+ {
+ var width = (center.X % 2 == 0) ? center.X * 2 + 1 : Center.X * 2;
+ var height = (center.Y % 2 == 0) ? center.Y * 2 + 1 : center.Y * 2;
+ return new Size(width, height);
+ }
+
+ private static Point FindCenter(Size size)
+ {
+ return new Point(size.Width / 2, size.Height / 2);
+ }
+
+ public Rectangle PutNextRectangle(Size rectangleSize)
+ {
+ foreach (var point in _points)
+ {
+ var supposed = new Rectangle(new Point(point.X - rectangleSize.Width / 2, point.Y - rectangleSize.Height / 2),
+ rectangleSize);
+ if (IntersectsWithAnyOther(supposed, Rectangles))
+ continue;
+ Rectangles.Add(supposed);
+ return supposed;
+ }
+ throw new ArgumentException("Not Enough Points Generated");
+ }
+
+ public static bool IntersectsWithAnyOther(Rectangle supposed, List others)
+ {
+ return others.Any(x => x.IntersectsWith(supposed));
+ }
+ }
+}
+
diff --git a/TagsCloudContainer/ColorProviders/ColorProvider.cs b/TagsCloudContainer/ColorProviders/ColorProvider.cs
new file mode 100644
index 00000000..07b3e61e
--- /dev/null
+++ b/TagsCloudContainer/ColorProviders/ColorProvider.cs
@@ -0,0 +1,13 @@
+using System.Drawing;
+using System.Runtime.CompilerServices;
+
+namespace TagsCloudContainer.ColorProviders;
+
+public class ColorProvider : IColorProvider
+{
+ [CompilerGenerated] private readonly Color _color;
+
+ public ColorProvider(Color color) => _color = color;
+
+ public Color GetColor() => _color;
+}
\ No newline at end of file
diff --git a/TagsCloudContainer/ColorProviders/IColorProvider.cs b/TagsCloudContainer/ColorProviders/IColorProvider.cs
new file mode 100644
index 00000000..682ceaa1
--- /dev/null
+++ b/TagsCloudContainer/ColorProviders/IColorProvider.cs
@@ -0,0 +1,8 @@
+using System.Drawing;
+
+namespace TagsCloudContainer.ColorProviders;
+
+public interface IColorProvider
+{
+ Color GetColor();
+}
\ No newline at end of file
diff --git a/TagsCloudContainer/ColorProviders/RandomColorProvider.cs b/TagsCloudContainer/ColorProviders/RandomColorProvider.cs
new file mode 100644
index 00000000..2d186faa
--- /dev/null
+++ b/TagsCloudContainer/ColorProviders/RandomColorProvider.cs
@@ -0,0 +1,11 @@
+using System.Drawing;
+
+namespace TagsCloudContainer.ColorProviders;
+
+public class RandomColorProvider : IColorProvider
+{
+ public Color GetColor()
+ {
+ return Color.FromArgb(Random.Shared.Next(50, 255), Random.Shared.Next(0, 255), Random.Shared.Next(0, 255), Random.Shared.Next(0, 255));
+ }
+}
\ No newline at end of file
diff --git a/TagsCloudContainer/Configuration/Config.cs b/TagsCloudContainer/Configuration/Config.cs
new file mode 100644
index 00000000..664ab43f
--- /dev/null
+++ b/TagsCloudContainer/Configuration/Config.cs
@@ -0,0 +1,21 @@
+using System.Drawing;
+
+namespace TagsCloudContainer.Configuration;
+
+public class Config
+{
+ public Type PointGenerator { get; set; }
+
+ public Color? Color { get; set; }
+
+ public string FilePath { get; set; }
+
+ public string PicturePath { get; set; }
+
+ public Point StartPoint { get; set; }
+
+ public Font Font { get; set; }
+
+ public Dictionary SupportedReadingFormats { get; set; }
+
+}
\ No newline at end of file
diff --git a/TagsCloudContainer/LabelAttribute.cs b/TagsCloudContainer/LabelAttribute.cs
new file mode 100644
index 00000000..263149f8
--- /dev/null
+++ b/TagsCloudContainer/LabelAttribute.cs
@@ -0,0 +1,7 @@
+namespace TagsCloudContainer
+{
+ public class LabelAttribute(string labelText) : Attribute
+ {
+ public string LabelText { get; set; } = labelText;
+ }
+}
diff --git a/TagsCloudContainer/PictureMaker.cs b/TagsCloudContainer/PictureMaker.cs
new file mode 100644
index 00000000..1b58c696
--- /dev/null
+++ b/TagsCloudContainer/PictureMaker.cs
@@ -0,0 +1,42 @@
+using System.Drawing;
+using TagsCloudContainer.PointGenerators;
+using TagsCloudContainer.TagGenerator;
+using TagsCloudContainer.TextProcessor;
+
+namespace TagsCloudContainer;
+
+public class PictureMaker
+{
+ private readonly IPointGenerator _pointGenerator;
+ private readonly IEnumerable _tags;
+ private readonly string _fileName;
+ private readonly Point _startPoint;
+
+ public PictureMaker(IPointGenerator pointGenerator, ITagsGenerator tagGenerator,
+ ITextProcessor textProcessor, string fileName, Point startPoint)
+ {
+ _pointGenerator = pointGenerator;
+ _tags = tagGenerator.GenerateTags(textProcessor.WordFrequencies());
+ _fileName = fileName;
+ _startPoint = startPoint;
+ }
+
+ public void DrawPicture()
+ {
+ var layout = new CloudLayout(_startPoint, _pointGenerator);
+ using var image = new Bitmap(layout.Size.Width, layout.Size.Height);
+ foreach (var tag in _tags)
+ {
+ var rectangle = layout.PutNextRectangle(tag.Frame);
+ DrawTag(image, rectangle, tag);
+ }
+ image.Save(_fileName);
+ }
+
+ private static void DrawTag(Bitmap image, Rectangle rectangle, Tag tag)
+ {
+ using var brush = new SolidBrush(tag.Color);
+ using var formGraphics = Graphics.FromImage(image);
+ formGraphics.DrawString(tag.Word.Value, tag.Font, brush, rectangle.Location);
+ }
+}
\ No newline at end of file
diff --git a/TagsCloudContainer/PointGenerators/ArchemedianSpiral.cs b/TagsCloudContainer/PointGenerators/ArchemedianSpiral.cs
new file mode 100644
index 00000000..a942a18b
--- /dev/null
+++ b/TagsCloudContainer/PointGenerators/ArchemedianSpiral.cs
@@ -0,0 +1,23 @@
+using System.Drawing;
+
+namespace TagsCloudContainer.PointGenerators
+{
+ [Label("Спираль")]
+ public class ArchemedianSpiral : IPointGenerator
+ {
+ public IEnumerable GeneratePoints(Point start)
+ {
+ var zoom = 1;
+ var spiralStep = 0.0;
+ yield return start;
+ while (true)
+ {
+ spiralStep += Math.PI / 180;
+ var x = start.X + (int)(zoom * spiralStep * Math.Cos(spiralStep));
+ var y = start.Y + (int)(zoom * spiralStep * Math.Sin(spiralStep));
+ var next = new Point(x, y);
+ yield return next;
+ }
+ }
+ }
+}
diff --git a/TagsCloudContainer/PointGenerators/DeltaShaped.cs b/TagsCloudContainer/PointGenerators/DeltaShaped.cs
new file mode 100644
index 00000000..5e54e480
--- /dev/null
+++ b/TagsCloudContainer/PointGenerators/DeltaShaped.cs
@@ -0,0 +1,35 @@
+using System.Drawing;
+
+namespace TagsCloudContainer.PointGenerators
+{
+ [Label("Стринги")]
+ public class DeltaShaped : IPointGenerator
+ {
+ public IEnumerable GeneratePoints(Point start)
+ {
+ var zoom = 5;
+ yield return start;
+ while (true)
+ {
+ foreach (var pair in Delta())
+ {
+ var x = start.X + (int)(zoom * pair.Item1);
+ var y = start.Y + (int)(zoom * pair.Item2);
+ var next = new Point(x, y);
+ yield return next;
+ }
+ zoom += 2;
+ }
+ }
+
+ public static IEnumerable<(double, double)> Delta()
+ {
+ for (var t = 0.0; t < 2 * Math.PI; t += Math.PI / 180)
+ {
+ var x = 2 * Math.Cos(t) + Math.Cos(2 * t);
+ var y = 2 * Math.Sin(t) - Math.Sin(2 * t);
+ yield return (x, y);
+ }
+ }
+ }
+}
diff --git a/TagsCloudContainer/PointGenerators/HeartShaped.cs b/TagsCloudContainer/PointGenerators/HeartShaped.cs
new file mode 100644
index 00000000..6c09c918
--- /dev/null
+++ b/TagsCloudContainer/PointGenerators/HeartShaped.cs
@@ -0,0 +1,35 @@
+using System.Drawing;
+
+namespace TagsCloudContainer.PointGenerators
+{
+ [Label("Сердце")]
+ public class HeartShaped : IPointGenerator
+ {
+ public IEnumerable GeneratePoints(Point start)
+ {
+ var zoom = 1;
+ yield return start;
+ while (true)
+ {
+ foreach (var pair in Heart())
+ {
+ var x = start.X + (int)(zoom * pair.Item1);
+ var y = start.Y + (int)(zoom * pair.Item2);
+ var next = new Point(x, y);
+ yield return next;
+ }
+ zoom += 1;
+ }
+ }
+
+ public static IEnumerable<(double, double)> Heart()
+ {
+ for (var t = 0.0; t < 2 * Math.PI; t += Math.PI / 180)
+ {
+ var x = 16 * Math.Sin(t) * Math.Sin(t) * Math.Sin(t);
+ var y = -13 * Math.Cos(t) + 5 * Math.Cos(2 * t) + 2 * Math.Cos(3 * t) + Math.Cos(4 * t);
+ yield return (x, y);
+ }
+ }
+ }
+}
diff --git a/TagsCloudContainer/PointGenerators/IPointGenerator.cs b/TagsCloudContainer/PointGenerators/IPointGenerator.cs
new file mode 100644
index 00000000..ad4ad966
--- /dev/null
+++ b/TagsCloudContainer/PointGenerators/IPointGenerator.cs
@@ -0,0 +1,9 @@
+using System.Drawing;
+
+namespace TagsCloudContainer.PointGenerators
+{
+ public interface IPointGenerator
+ {
+ IEnumerable GeneratePoints(Point start);
+ }
+}
diff --git a/TagsCloudContainer/RainbowColors.cs b/TagsCloudContainer/RainbowColors.cs
new file mode 100644
index 00000000..ace69583
--- /dev/null
+++ b/TagsCloudContainer/RainbowColors.cs
@@ -0,0 +1,20 @@
+namespace TagsCloudContainer
+{
+ public enum RainbowColors
+ {
+ [Label("Красный")]
+ Red,
+ [Label("Оранжевый")]
+ Orange,
+ [Label("Желтый")]
+ Yellow,
+ [Label("Зеленый")]
+ Green,
+ [Label("Голубой")]
+ Blue,
+ [Label("Синий")]
+ Indigo,
+ [Label("Фиолетовый")]
+ Violet,
+ }
+}
diff --git a/TagsCloudContainer/StringParsers/IStringParser.cs b/TagsCloudContainer/StringParsers/IStringParser.cs
new file mode 100644
index 00000000..f85d252c
--- /dev/null
+++ b/TagsCloudContainer/StringParsers/IStringParser.cs
@@ -0,0 +1,7 @@
+namespace TagsCloudContainer.StringParsers
+{
+ public interface IStringParser
+ {
+ IEnumerable GetWordsFromString(string input);
+ }
+}
diff --git a/TagsCloudContainer/StringParsers/RegexParser.cs b/TagsCloudContainer/StringParsers/RegexParser.cs
new file mode 100644
index 00000000..9887a74f
--- /dev/null
+++ b/TagsCloudContainer/StringParsers/RegexParser.cs
@@ -0,0 +1,15 @@
+using System.Text.RegularExpressions;
+
+namespace TagsCloudContainer.StringParsers
+{
+ public class RegexParser : IStringParser
+ {
+ private readonly Regex _regex = new("\\b(?:\\w|-)+\\b", RegexOptions.Compiled);
+ public IEnumerable GetWordsFromString(string input)
+ {
+ return _regex.Matches(input)
+ .Cast()
+ .Select(w => new Word(w.Value));
+ }
+ }
+}
diff --git a/TagsCloudContainer/Tag.cs b/TagsCloudContainer/Tag.cs
new file mode 100644
index 00000000..2dd79ff4
--- /dev/null
+++ b/TagsCloudContainer/Tag.cs
@@ -0,0 +1,5 @@
+using System.Drawing;
+
+namespace TagsCloudContainer;
+
+public record Tag(Word Word, Font Font, Color Color, Size Frame);
diff --git a/TagsCloudContainer/TagGenerator/ITagsGenerator.cs b/TagsCloudContainer/TagGenerator/ITagsGenerator.cs
new file mode 100644
index 00000000..421c4f02
--- /dev/null
+++ b/TagsCloudContainer/TagGenerator/ITagsGenerator.cs
@@ -0,0 +1,7 @@
+namespace TagsCloudContainer.TagGenerator
+{
+ public interface ITagsGenerator
+ {
+ IEnumerable GenerateTags(Dictionary wordsDictionary);
+ }
+}
diff --git a/TagsCloudContainer/TagGenerator/TagGenerator.cs b/TagsCloudContainer/TagGenerator/TagGenerator.cs
new file mode 100644
index 00000000..dff3a334
--- /dev/null
+++ b/TagsCloudContainer/TagGenerator/TagGenerator.cs
@@ -0,0 +1,38 @@
+using System.Drawing;
+using TagsCloudContainer.ColorProviders;
+
+namespace TagsCloudContainer.TagGenerator
+{
+ public class TagGenerator : ITagsGenerator
+ {
+ private readonly IColorProvider _colorProvider;
+ private readonly Graphics _graphics;
+ private readonly Font _defaultFont;
+
+
+ public TagGenerator(IColorProvider colorProvider, Font defaultFont )
+ {
+ _colorProvider = colorProvider;
+ _graphics = Graphics.FromImage(new Bitmap(1, 1));
+ _defaultFont = defaultFont;
+ }
+
+ public IEnumerable GenerateTags(Dictionary wordsDictionary)
+ {
+ return wordsDictionary
+ .Select(kvp => new Tag(kvp.Key, SetFont(_defaultFont, kvp.Value), _colorProvider.GetColor(),
+ SetFrameSize(kvp.Key, SetFont(_defaultFont, kvp.Value), 1, _graphics)));
+ }
+
+ private static Size SetFrameSize(Word word, Font font, int frameGap, Graphics graphics)
+ {
+ var rect = graphics.MeasureString(word.Value, font).ToSize();
+ return new Size(rect.Width + frameGap, rect.Height + frameGap);
+ }
+
+ private static Font SetFont(Font font, int amount)
+ {
+ return new Font(font.FontFamily, font.Size * amount);
+ }
+ }
+}
diff --git a/TagsCloudContainer/TagsCloudContainer.csproj b/TagsCloudContainer/TagsCloudContainer.csproj
new file mode 100644
index 00000000..e35483a8
--- /dev/null
+++ b/TagsCloudContainer/TagsCloudContainer.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Library
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
diff --git a/TagsCloudContainer/TagsCloudContainer.sln b/TagsCloudContainer/TagsCloudContainer.sln
new file mode 100644
index 00000000..8274b4ed
--- /dev/null
+++ b/TagsCloudContainer/TagsCloudContainer.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.11.35327.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagsCloudContainer", "TagsCloudContainer.csproj", "{9A86D0EB-2E44-4B2D-A9C9-BCF4C6037DE4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagsCloudContainer.Tests", "..\TagsCloudContainer.Tests\TagsCloudContainer.Tests.csproj", "{A6199F1E-60D0-4BC3-8A43-F0E8D0A8F7E7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "..\Client\Client.csproj", "{D35DD45C-AA81-4D59-BA31-261C006DB97E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {9A86D0EB-2E44-4B2D-A9C9-BCF4C6037DE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9A86D0EB-2E44-4B2D-A9C9-BCF4C6037DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9A86D0EB-2E44-4B2D-A9C9-BCF4C6037DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9A86D0EB-2E44-4B2D-A9C9-BCF4C6037DE4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A6199F1E-60D0-4BC3-8A43-F0E8D0A8F7E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A6199F1E-60D0-4BC3-8A43-F0E8D0A8F7E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A6199F1E-60D0-4BC3-8A43-F0E8D0A8F7E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A6199F1E-60D0-4BC3-8A43-F0E8D0A8F7E7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D35DD45C-AA81-4D59-BA31-261C006DB97E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D35DD45C-AA81-4D59-BA31-261C006DB97E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D35DD45C-AA81-4D59-BA31-261C006DB97E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D35DD45C-AA81-4D59-BA31-261C006DB97E}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {37859005-F9FC-4CB8-81DF-92A65CC08048}
+ EndGlobalSection
+EndGlobal
diff --git a/TagsCloudContainer/TextProcessor/ITextProcessor.cs b/TagsCloudContainer/TextProcessor/ITextProcessor.cs
new file mode 100644
index 00000000..b22fb0fd
--- /dev/null
+++ b/TagsCloudContainer/TextProcessor/ITextProcessor.cs
@@ -0,0 +1,7 @@
+namespace TagsCloudContainer.TextProcessor
+{
+ public interface ITextProcessor
+ {
+ public Dictionary WordFrequencies();
+ }
+}
diff --git a/TagsCloudContainer/TextProcessor/TextProcessor.cs b/TagsCloudContainer/TextProcessor/TextProcessor.cs
new file mode 100644
index 00000000..03f2fe10
--- /dev/null
+++ b/TagsCloudContainer/TextProcessor/TextProcessor.cs
@@ -0,0 +1,17 @@
+using TagsCloudContainer.StringParsers;
+using TagsCloudContainer.TextProviders;
+using TagsCloudContainer.WordFilters;
+
+namespace TagsCloudContainer.TextProcessor;
+
+public class TextProcessor(ITextProvider provider, IStringParser parser,
+ params IWordFilter[] filters) : ITextProcessor
+{
+ public Dictionary WordFrequencies()
+ {
+ var words = parser.GetWordsFromString(provider.ReadFile());
+ return filters.Aggregate(words, (current, filter) => filter.Process(current))
+ .GroupBy(word => word)
+ .ToDictionary(group => group.Key, group => group.Count());
+ }
+}
\ No newline at end of file
diff --git a/TagsCloudContainer/TextProviders/DocTextProvider.cs b/TagsCloudContainer/TextProviders/DocTextProvider.cs
new file mode 100644
index 00000000..cfdd9cc1
--- /dev/null
+++ b/TagsCloudContainer/TextProviders/DocTextProvider.cs
@@ -0,0 +1,24 @@
+using NPOI.HWPF;
+
+namespace TagsCloudContainer.TextProviders;
+
+[Label(".doc")]
+public class DocTextProvider : ITextProvider
+{
+ private readonly string _filePath;
+
+ public DocTextProvider(string filePath)
+ {
+ _filePath = filePath;
+ }
+
+ public string ReadFile()
+ {
+ if (!File.Exists(_filePath))
+ throw new FileNotFoundException();
+ using var stream = new FileStream(_filePath, FileMode.Open, FileAccess.Read);
+ var document = new HWPFDocument(stream);
+ var range = document.GetRange();
+ return range.Text;
+ }
+}
\ No newline at end of file
diff --git a/TagsCloudContainer/TextProviders/DocXTextProvider.cs b/TagsCloudContainer/TextProviders/DocXTextProvider.cs
new file mode 100644
index 00000000..e4e83b22
--- /dev/null
+++ b/TagsCloudContainer/TextProviders/DocXTextProvider.cs
@@ -0,0 +1,22 @@
+using Xceed.Words.NET;
+
+namespace TagsCloudContainer.TextProviders;
+
+[Label(".docx")]
+public class DocXTextProvider : ITextProvider
+{
+ private readonly string _filePath;
+
+ public DocXTextProvider(string filePath)
+ {
+ _filePath = filePath;
+ }
+
+ public string ReadFile()
+ {
+ if (!File.Exists(_filePath))
+ throw new FileNotFoundException();
+ using var document = DocX.Load(_filePath);
+ return document.Text;
+ }
+}
\ No newline at end of file
diff --git a/TagsCloudContainer/TextProviders/ITextProvider.cs b/TagsCloudContainer/TextProviders/ITextProvider.cs
new file mode 100644
index 00000000..cb7a29b5
--- /dev/null
+++ b/TagsCloudContainer/TextProviders/ITextProvider.cs
@@ -0,0 +1,6 @@
+namespace TagsCloudContainer.TextProviders;
+
+public interface ITextProvider
+{
+ public string ReadFile();
+}
\ No newline at end of file
diff --git a/TagsCloudContainer/TextProviders/TxtTextProvider.cs b/TagsCloudContainer/TextProviders/TxtTextProvider.cs
new file mode 100644
index 00000000..1d774fb6
--- /dev/null
+++ b/TagsCloudContainer/TextProviders/TxtTextProvider.cs
@@ -0,0 +1,19 @@
+namespace TagsCloudContainer.TextProviders;
+
+[Label(".txt")]
+public class TxtTextProvider : ITextProvider
+{
+ private readonly string _filePath;
+
+ public TxtTextProvider(string filePath)
+ {
+ _filePath = filePath;
+ }
+
+ public string ReadFile()
+ {
+ if (!File.Exists(_filePath))
+ throw new FileNotFoundException();
+ return File.ReadAllText(_filePath);
+ }
+}
\ No newline at end of file
diff --git a/TagsCloudContainer/Word.cs b/TagsCloudContainer/Word.cs
new file mode 100644
index 00000000..45b2877b
--- /dev/null
+++ b/TagsCloudContainer/Word.cs
@@ -0,0 +1,4 @@
+namespace TagsCloudContainer
+{
+ public record Word(string Value);
+}
diff --git a/TagsCloudContainer/WordFilters/BoringWordFilter.cs b/TagsCloudContainer/WordFilters/BoringWordFilter.cs
new file mode 100644
index 00000000..30e1fd1e
--- /dev/null
+++ b/TagsCloudContainer/WordFilters/BoringWordFilter.cs
@@ -0,0 +1,30 @@
+namespace TagsCloudContainer.WordFilters
+{
+ public class BoringWordFilter : IWordFilter
+ {
+ private readonly HashSet _forbiddenWords =
+ [
+ "я",
+ "мы",
+ "он",
+ "она",
+ "оно",
+ "они"
+ ];
+
+ public void AddBoringWord(Word word)
+ {
+ _forbiddenWords.Add(word.Value);
+ }
+
+ public void AddBoringWord(string word)
+ {
+ _forbiddenWords.Add(word);
+ }
+
+ public IEnumerable Process(IEnumerable words)
+ {
+ return words.Where(w => !_forbiddenWords.Contains(w.Value));
+ }
+ }
+}
diff --git a/TagsCloudContainer/WordFilters/IWordFilter.cs b/TagsCloudContainer/WordFilters/IWordFilter.cs
new file mode 100644
index 00000000..e5e6bcd4
--- /dev/null
+++ b/TagsCloudContainer/WordFilters/IWordFilter.cs
@@ -0,0 +1,7 @@
+namespace TagsCloudContainer.WordFilters
+{
+ public interface IWordFilter
+ {
+ IEnumerable Process(IEnumerable words);
+ }
+}
diff --git a/TagsCloudContainer/WordFilters/ShortWordFilter.cs b/TagsCloudContainer/WordFilters/ShortWordFilter.cs
new file mode 100644
index 00000000..90aa7144
--- /dev/null
+++ b/TagsCloudContainer/WordFilters/ShortWordFilter.cs
@@ -0,0 +1,10 @@
+namespace TagsCloudContainer.WordFilters
+{
+ public class ShortWordFilter : IWordFilter
+ {
+ public IEnumerable Process(IEnumerable words)
+ {
+ return words.Where(w => w.Value.Length > 2);
+ }
+ }
+}
diff --git a/TagsCloudContainer/WordFilters/ToLowerFilter.cs b/TagsCloudContainer/WordFilters/ToLowerFilter.cs
new file mode 100644
index 00000000..9252c8d1
--- /dev/null
+++ b/TagsCloudContainer/WordFilters/ToLowerFilter.cs
@@ -0,0 +1,9 @@
+namespace TagsCloudContainer.WordFilters;
+
+public class ToLowerFilter : IWordFilter
+{
+ public IEnumerable Process(IEnumerable words)
+ {
+ return words.Select(w => new Word(w.Value.ToLower()));
+ }
+}
\ No newline at end of file