Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Махлонов Дмитрий #210

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
40 changes: 40 additions & 0 deletions TagCloud/TagCloud.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34728.123
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagCloud", "TagCloud\TagCloud.csproj", "{2AF38A2D-EE49-4C1D-A38F-7B524AF31ACA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagCloudTests", "TagCloudTests\TagCloudTests.csproj", "{68CB9437-2F08-4A23-A165-ECB72F1C0E07}"
ProjectSection(ProjectDependencies) = postProject
{F41DEF5A-5A77-4DD8-8987-05AAF41B37F7} = {F41DEF5A-5A77-4DD8-8987-05AAF41B37F7}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagCloudClients", "TagCloudClients\TagCloudClients.csproj", "{F41DEF5A-5A77-4DD8-8987-05AAF41B37F7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2AF38A2D-EE49-4C1D-A38F-7B524AF31ACA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2AF38A2D-EE49-4C1D-A38F-7B524AF31ACA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2AF38A2D-EE49-4C1D-A38F-7B524AF31ACA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2AF38A2D-EE49-4C1D-A38F-7B524AF31ACA}.Release|Any CPU.Build.0 = Release|Any CPU
{68CB9437-2F08-4A23-A165-ECB72F1C0E07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68CB9437-2F08-4A23-A165-ECB72F1C0E07}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68CB9437-2F08-4A23-A165-ECB72F1C0E07}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68CB9437-2F08-4A23-A165-ECB72F1C0E07}.Release|Any CPU.Build.0 = Release|Any CPU
{F41DEF5A-5A77-4DD8-8987-05AAF41B37F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F41DEF5A-5A77-4DD8-8987-05AAF41B37F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F41DEF5A-5A77-4DD8-8987-05AAF41B37F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F41DEF5A-5A77-4DD8-8987-05AAF41B37F7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {697D441B-1F34-4177-90F6-6EB0176AF803}
EndGlobalSection
EndGlobal
31 changes: 31 additions & 0 deletions TagCloud/TagCloud/BitmapGenerators/BitmapGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Drawing;
using TagCloud.CloudLayouter;
using TagCloud.SettingsProviders;

namespace TagCloud.BitmapGenerators;

public class BitmapGenerator(
maakhhh marked this conversation as resolved.
Show resolved Hide resolved
ICloudLayouter layouter, ISettingsProvider<BitmapGeneratorSettings> settingsProvider) : IBitmapGenerator
{
public Bitmap GenerateBitmapFromWords(IEnumerable<CloudWord> words)
{
var padding = 1.5f;
var settings = settingsProvider.GetSettings();
var bitmap = new Bitmap(settings.ImageSize.Width, settings.ImageSize.Height);
using var graphics = Graphics.FromImage(bitmap);

graphics.Clear(settings.BackgroundColor);
using var brush = new SolidBrush(settings.WordColor);

foreach (var word in words)
{
using var font = new Font(settings.FontFamily, word.FontSize);
var size = graphics.MeasureString(word.Word, font);
var position = layouter.PutNextRectangle(size.ToSize());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется bitmapGenerator должен только рисовать картинку, а не генерировать размеры и позиции для прямоугольников. Предлагаю эту информацию вынести в CloudWord или в какой-нибудь DrowingCloudWord

var textPosition = new PointF(position.X + padding, position.Y + padding);
graphics.DrawString(word.Word, font, brush, textPosition);
}

return bitmap;
}
}
25 changes: 25 additions & 0 deletions TagCloud/TagCloud/BitmapGenerators/BitmapGeneratorSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Drawing;

namespace TagCloud.BitmapGenerators;

public record BitmapGeneratorSettings
{
public Size ImageSize { get; private set; } = new Size(1080, 1920);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Из-за private set настройки стали по сути бесполезны, ведь ими нельзя управлять. Почему решил сделать именно так?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

задумывал настройки неизменяемым объектом, если что-то нужно поменять - создаем новый объект настроек

public Color BackgroundColor { get; private set; } = Color.White;
public Color WordColor { get; private set; } = Color.Black;
public FontFamily FontFamily { get; private set; } = new FontFamily("Arial");

public BitmapGeneratorSettings() { }

public BitmapGeneratorSettings(
maakhhh marked this conversation as resolved.
Show resolved Hide resolved
Size imageSize,
Color backgroudColor,
Color wordColor,
FontFamily fontFamily)
{
ImageSize = imageSize;
BackgroundColor = backgroudColor;
WordColor = wordColor;
FontFamily = fontFamily;
}
}
5 changes: 5 additions & 0 deletions TagCloud/TagCloud/BitmapGenerators/CloudWord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using System.Drawing;
maakhhh marked this conversation as resolved.
Show resolved Hide resolved

namespace TagCloud.BitmapGenerators;

public record CloudWord(string Word, int FontSize);
8 changes: 8 additions & 0 deletions TagCloud/TagCloud/BitmapGenerators/IBitmapGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Drawing;

namespace TagCloud.BitmapGenerators;

public interface IBitmapGenerator
{
public Bitmap GenerateBitmapFromWords(IEnumerable<CloudWord> words);
maakhhh marked this conversation as resolved.
Show resolved Hide resolved
}
15 changes: 15 additions & 0 deletions TagCloud/TagCloud/CloudImageSavers/CloudImageSaver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Drawing;
using TagCloud.SettingsProviders;

namespace TagCloud.CloudImageSavers;

public class CloudImageSaver(ISettingsProvider<SaveSettings> settingsProvider) : ICloudImageSaver
maakhhh marked this conversation as resolved.
Show resolved Hide resolved
{
public string Save(Bitmap image)
{
var settings = settingsProvider.GetSettings();
var filename = $"{settings.FileName}.{settings.Format.ToString().ToLower()}";
image.Save(filename, settings.Format);
return Path.Combine(Directory.GetCurrentDirectory(), filename);
}
}
8 changes: 8 additions & 0 deletions TagCloud/TagCloud/CloudImageSavers/ICloudImageSaver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Drawing;

namespace TagCloud.CloudImageSavers;

public interface ICloudImageSaver
{
public string Save(Bitmap image);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А куда сохранять? На мой взгляд это уже не столько настройки конкретной реализации, сколько правила поведения в целом

}
17 changes: 17 additions & 0 deletions TagCloud/TagCloud/CloudImageSavers/SaveSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Drawing.Imaging;

namespace TagCloud.CloudImageSavers;

public record SaveSettings
{
public string FileName { get; private set; } = "output";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Аналогично BitmapGeneratorSettings и во всех остальных настройках. Почему решил сделать так и про конструктор?

public ImageFormat Format { get; private set; } = ImageFormat.Png;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, это будет работать. Но совет на будущее - стараться не транслировать насквозь своей библиотеки что-то из зависимостей. Если захочешь переписать логику, то придётся отказаться и от такого параметра. А пользователи его используют. Лучше создавать свою прослойку моделей, которые уже трансформировать во что захочется


public SaveSettings() { }

public SaveSettings(string fileName, ImageFormat format)
{
FileName = fileName;
Format = format;
}
}
44 changes: 44 additions & 0 deletions TagCloud/TagCloud/CloudLayouter/CircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Drawing;

namespace TagCloud.CloudLayouter;
public class CircularCloudLayouter : ICloudLayouter
{
private readonly List<Rectangle> rectangles;
private readonly IEnumerator<Point> pointEnumerator;

public CircularCloudLayouter(IPositionGenerator generator)
{
rectangles = new();
pointEnumerator = generator.GetPositions().GetEnumerator();
}

public List<Rectangle> GetRectangles() => rectangles;

public Rectangle PutNextRectangle(Size rectangleSize)
{
if (rectangleSize.Width <= 0 || rectangleSize.Height <= 0)
throw new ArgumentException(
$"{nameof(rectangleSize)} должен иметь высоту и ширину больше нуля, передано ({rectangleSize.Width} {rectangleSize.Height})"
);

Rectangle rectangle;

do
rectangle = PutRectangleInNextPosition(rectangleSize);
while (rectangles.Any(r => r.IntersectsWith(rectangle)));

rectangles.Add(rectangle);
return rectangle;
}

private Rectangle PutRectangleInNextPosition(Size rectagleSize)
{
pointEnumerator.MoveNext();
var centerOfRectangle = pointEnumerator.Current;
var rectanglePosition = GetPositionFromCenter(centerOfRectangle, rectagleSize);
return new(rectanglePosition, rectagleSize);
}

private Point GetPositionFromCenter(Point center, Size size) =>
new(center.X - size.Width / 2, center.Y - size.Height / 2);
}
9 changes: 9 additions & 0 deletions TagCloud/TagCloud/CloudLayouter/ICloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Drawing;

namespace TagCloud.CloudLayouter;

public interface ICloudLayouter
{
public List<Rectangle> GetRectangles();
public Rectangle PutNextRectangle(Size rectangleSize);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Drawing;

namespace TagCloud.CloudLayouter;

public interface IPositionGenerator
{
public IEnumerable<Point> GetPositions();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Drawing;

namespace TagCloud.CloudLayouter.PositionGenerator;

public class SpiralGeneratorSettings
{
public double AngleOffset { get; private set; }
public double SpiralStep { get; private set; }
public Point Center { get; private set; }

public SpiralGeneratorSettings(double angleOffset, double spiralStep, Point center)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(angleOffset);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(spiralStep);

AngleOffset = angleOffset;
SpiralStep = spiralStep;
Center = center;
}

public SpiralGeneratorSettings() : this(0.5, 0.1, new(540, 960))
{

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Drawing;
using TagCloud.CloudLayouter.PositionGenerator;
using TagCloud.SettingsProviders;

namespace TagCloud.CloudLayouter;

public class SpiralPositionGenerator : IPositionGenerator
{
private readonly ISettingsProvider<SpiralGeneratorSettings> settingsProvider;

public SpiralPositionGenerator(ISettingsProvider<SpiralGeneratorSettings> settingsProvider)
{
this.settingsProvider = settingsProvider;
}

public IEnumerable<Point> GetPositions()
{
var settings = settingsProvider.GetSettings();
int x, y;
double radius, angle = 0;

while (true)
{
radius = settings.SpiralStep * angle;
x = (int)(settings.Center.X + radius * Math.Cos(angle));
y = (int)(settings.Center.Y + radius * Math.Sin(angle));

yield return new(x, y);

angle += settings.AngleOffset;
}
}
}
6 changes: 6 additions & 0 deletions TagCloud/TagCloud/SettingsProviders/ISettingsHolder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace TagCloud.SettingsProviders;

public interface ISettingsHolder<in T> where T : new()
{
public void SetSettings(T settings);
}
6 changes: 6 additions & 0 deletions TagCloud/TagCloud/SettingsProviders/ISettingsProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace TagCloud.SettingsProviders;

public interface ISettingsProvider<out T> where T : new()
{
public T GetSettings();
}
13 changes: 13 additions & 0 deletions TagCloud/TagCloud/SettingsProviders/SettingsProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace TagCloud.SettingsProviders;

public class SettingsProvider<T> : ISettingsProvider<T>, ISettingsHolder<T>
where T : new()
{
private T settings = new();
public T GetSettings() => settings;

public void SetSettings(T settings)
{
this.settings = settings;
}
}
23 changes: 23 additions & 0 deletions TagCloud/TagCloud/TagCloud.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<Compile Remove="TextFilters\Dictionaries\**" />
<EmbeddedResource Remove="TextFilters\Dictionaries\**" />
<None Remove="TextFilters\Dictionaries\**" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="CsvHelper" Version="33.0.1" />
<PackageReference Include="DocumentFormat.OpenXml" Version="3.2.0" />
<PackageReference Include="System.Drawing.Common" Version="9.0.0" />
<PackageReference Include="WeCantSpell.Hunspell" Version="5.2.1" />
</ItemGroup>

</Project>
57 changes: 57 additions & 0 deletions TagCloud/TagCloud/TagCloudImageGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using TagCloud.BitmapGenerators;
using TagCloud.CloudImageSavers;
using TagCloud.TextFilters;
using TagCloud.TextReader;
using TagCloud.TextSplitters;

namespace TagCloud;

public class TagCloudImageGenerator
{
private readonly TextReaderProvider readerProvider;
private readonly ICloudImageSaver saver;
private readonly IBitmapGenerator bitmapGenerator;
private readonly ITextSplitter splitter;
private readonly IEnumerable<ITextFilter> filters;
private const int MAX_FONTSIZE = 100;
private const int MIN_FONTSIZE = 8;

public TagCloudImageGenerator(
TextReaderProvider readerProvider,
ITextSplitter splitter,
ICloudImageSaver saver,
IBitmapGenerator bitmapGenerator,
IEnumerable<ITextFilter> filters)
{
this.splitter = splitter;
this.readerProvider = readerProvider;
this.saver = saver;
this.bitmapGenerator = bitmapGenerator;
this.filters = filters;
}

public string GenerateCloud()
{
var reader = readerProvider.GetActualReader();
var words = splitter.Split(reader.Read());

var wordsFrequency = filters
.Aggregate(words, (word, filter) => filter.Apply(word))
.GroupBy(w => w)
.ToDictionary(words => words.Key, words => words.Count());

var minWordCount = wordsFrequency.Values.Min();
var maxWordCount = wordsFrequency.Values.Max();

var cloudWords = wordsFrequency
.Select(w => new CloudWord(w.Key, GetWordFontSize(
w.Value, minWordCount, maxWordCount)));

var bitmap = bitmapGenerator.GenerateBitmapFromWords(cloudWords);
return saver.Save(bitmap);
}

private int GetWordFontSize(int freqCount, int minWordCount, int maxWordCount) =>
MIN_FONTSIZE + (MAX_FONTSIZE - MIN_FONTSIZE)
* (freqCount - minWordCount) / (maxWordCount - minWordCount);
}
Loading