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

Бочаров Александр #12

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c6cd6bc
init(TagCloud|TagCloudTests): инициализировал TagCloud с тестами
Geratoptus Dec 17, 2024
3d5fbcd
init(TagCloud): набросок архитектуры
Geratoptus Dec 17, 2024
5b49d2c
add(CircularCloudLayouter): перенес решение из прошлого задания с тес…
Geratoptus Dec 17, 2024
4a9234e
add(FileReader.cs): реализовал FileReader
Geratoptus Dec 17, 2024
b962771
add(FileReaderTest.cs): добавил тест для FileReader'а
Geratoptus Dec 17, 2024
299a1cc
feat(TagCloud|TagCloudTests): поставил Hunspell для работы со словами
Geratoptus Dec 17, 2024
c398841
feat(TagCloud|TagCloudTests): поставил Autofac для настройки зависимо…
Geratoptus Dec 17, 2024
aa27044
feat(LowerCaseFilter.cs): написал фильтр на регистр
Geratoptus Dec 17, 2024
49082ad
feat(BoringWordsFilter.cs): написал фильт на скучные слова
Geratoptus Dec 17, 2024
7489c62
add(FilterTests): написал тесты для фильтров
Geratoptus Dec 17, 2024
b2d30b2
feat(TagCloud): вынес клиента в отдельный проект
Geratoptus Dec 18, 2024
891ea0e
feat(TagCloud): добавил Options для консоли
Geratoptus Dec 18, 2024
46bc1b2
feat(TagCloud): добавил зависимости от BitmapGenerator'а и от Circula…
Geratoptus Dec 18, 2024
d3ff1b0
fix(CircularCloudLayouter): понял, что для этой задачи лучше начинать…
Geratoptus Dec 18, 2024
4fedef7
add(ImageSaver): выделил сохранялку картинок
Geratoptus Dec 18, 2024
bc29220
feat(BitmapGenerator): внедрил BitmapSettings.cs
Geratoptus Dec 18, 2024
618da34
add(CsvFileReader.cs): добавил ридера для .csv файлов
Geratoptus Dec 18, 2024
6e45f06
add(WordFileReader.cs): добавил ридера для .docx файлов
Geratoptus Dec 18, 2024
954b0d5
feat(Options.cs): добавил настройку культуры для .csv и разделил imag…
Geratoptus Dec 18, 2024
ba7c531
feat(CloudGenerator.cs): работающий CloudGenerator
Geratoptus Dec 18, 2024
49a3f7f
feat(SettingsFactory): добавил билдеры для новых ридеров и сейвера
Geratoptus Dec 18, 2024
0b6ccaf
feat(Program.cs): добавил настройку всех новых зависимостей
Geratoptus Dec 18, 2024
673b1fb
feat(TagCloudClient): добавил примеры работы, в csproj подгрузил все …
Geratoptus Dec 18, 2024
3982fce
feat(TagClodTests): добавил тесты
Geratoptus Dec 18, 2024
bec6bbf
feat(TagClodTests): добавил тесты
Geratoptus Dec 18, 2024
b510d67
add(IOptions|SettingsBuilder): вынес настройку зависимостей в отдельн…
Geratoptus Dec 28, 2024
dbde033
refactor(Program|SettingsFactory):
Geratoptus Dec 28, 2024
ae4cb13
refactor(FermatSpiralPointsGeneratorTest): оказывается я пивной бочонок
Geratoptus Dec 28, 2024
3a97138
Merge remote-tracking branch 'origin/TagCloud' into TagCloud
Geratoptus Dec 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions TagCloud/CloudGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using TagCloud.WordsFilter;
using TagCloud.WordsReader;
using TagCloud.ImageGenerator;
using TagCloud.ImageSaver;

namespace TagCloud;

public class CloudGenerator(
IImageSaver saver,
IWordsReader reader,
BitmapGenerator imageGenerator,
IEnumerable<IWordsFilter> filters)
{
private const int MinFontSize = 10;
private const int MaxFontSize = 80;
public string GenerateTagCloud()
{
var words = reader.ReadWords();

var freqDict = filters
.Aggregate(words, (c, f) => f.ApplyFilter(c))
.GroupBy(w => w)
.OrderByDescending(g => g.Count())
.ToDictionary(g => g.Key, g => g.Count());

var maxFreq = freqDict.Values.Max();
var tagsList = freqDict.Select(pair => ToWordTag(pair, maxFreq)).ToList();

return saver.Save(imageGenerator.GenerateWindowsBitmap(tagsList));
}

private static int TransformFreqToSize(int freq, int maxFreq)
=> (int)(MinFontSize + (float)freq / maxFreq * (MaxFontSize - MinFontSize));

private static WordTag ToWordTag(KeyValuePair<string, int> pair, int maxFreq)
=> new(pair.Key, TransformFreqToSize(pair.Value, maxFreq));
}
10 changes: 10 additions & 0 deletions TagCloud/CloudLayouter/Extensions/EnumeratorExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace TagCloud.CloudLayouter.Extensions;

public static class EnumeratorExtension
{
public static IEnumerable<T> ToIEnumerable<T>(this IEnumerator<T> enumerator) {
while ( enumerator.MoveNext() ) {
yield return enumerator.Current;
}
}
}
23 changes: 23 additions & 0 deletions TagCloud/CloudLayouter/Extensions/RandomExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Drawing;

namespace TagCloud.CloudLayouter.Extensions;

public static class RandomExtension
{
public static Size RandomSize(this Random random, int minValue=1, int maxValue=int.MaxValue)
{
if (minValue <= 0)
throw new ArgumentOutOfRangeException(nameof(minValue), "minValue must be positive");
if (minValue > maxValue)
throw new ArgumentOutOfRangeException(nameof(minValue), "minValue must be less than maxValue");


return new Size(random.Next(minValue, maxValue), random.Next(minValue, maxValue));
}


public static Point RandomPoint(this Random random, int minValue=int.MinValue, int maxValue=int.MaxValue)
{
return new Point(random.Next(minValue, maxValue), random.Next(minValue, maxValue));
}
}
25 changes: 25 additions & 0 deletions TagCloud/CloudLayouter/Extensions/RectangleExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Drawing;

namespace TagCloud.CloudLayouter.Extensions;

public static class RectangleExtension
{
public static Rectangle CreateRectangleWithCenter(this Rectangle rectangle, Point center, Size rectangleSize)
{
var x = center.X - rectangleSize.Width / 2;
var y = center.Y - rectangleSize.Height / 2;
return new Rectangle(x, y, rectangleSize.Width, rectangleSize.Height);
}

public static double GetDistanceToMostRemoteCorner(this Rectangle rectangle, Point startingPoint)
{
Point[] corners = [
new(rectangle.X, rectangle.Y),
new(rectangle.X + rectangle.Width, rectangle.Y),
new(rectangle.X, rectangle.Y + rectangle.Height),
new(rectangle.X + rectangle.Width, rectangle.Y + rectangle.Height)];
return corners.Max(corner =>
Math.Sqrt((startingPoint.X - corner.X) * (startingPoint.X - corner.X) +
(startingPoint.Y - corner.Y) * (startingPoint.Y - corner.Y)));
}
}
8 changes: 8 additions & 0 deletions TagCloud/CloudLayouter/ICloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Drawing;

namespace TagCloud.CloudLayouter;

public interface ICloudLayouter
{
public Rectangle PutNextRectangle(Size rectangleSize);
}
42 changes: 42 additions & 0 deletions TagCloud/CloudLayouter/PointLayouter/CircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Drawing;
using TagCloud.CloudLayouter.Extensions;
using TagCloud.CloudLayouter.PointLayouter.Generators;
using TagCloud.CloudLayouter.PointLayouter.Settings;

namespace TagCloud.CloudLayouter.PointLayouter;

public class CircularCloudLayouter(Point layoutCenter, IPointsGenerator pointsGenerator) : ICloudLayouter
{
private const string FiniteGeneratorExceptionMessage =
"В конструктор CircularCloudLayouter был передан конечный генератор точек";

private readonly List<Point> _placedPoints = [];
private readonly List<Rectangle> _layoutRectangles = [];

public CircularCloudLayouter(Point layoutCenter, double radius, double angleOffset) :
this(layoutCenter, new FermatSpiralPointsGenerator(radius, angleOffset))
{
}

public CircularCloudLayouter(PointLayouterSettings settings)
: this(settings.Center, settings.Generator)
{
}

public Rectangle PutNextRectangle(Size rectangleSize)
{
var rectangle = pointsGenerator
.GeneratePoints(layoutCenter)
.Except(_placedPoints)
.Select(point => new Rectangle()
.CreateRectangleWithCenter(point, rectangleSize))
.FirstOrDefault(rectangle => !_layoutRectangles.Any(rectangle.IntersectsWith));

if (rectangle.IsEmpty)
throw new InvalidOperationException(FiniteGeneratorExceptionMessage);

_placedPoints.Add(rectangle.Location - rectangleSize / 2);
_layoutRectangles.Add(rectangle);
return rectangle;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Drawing;
using TagCloud.CloudLayouter.PointLayouter.Settings.Generators;

namespace TagCloud.CloudLayouter.PointLayouter.Generators;

public class FermatSpiralPointsGenerator : IPointsGenerator
{
private readonly double _angleOffset;
private readonly double _radius;

private double OffsetPerRadian => _radius / (2 * Math.PI);

public FermatSpiralPointsGenerator(double radius, double angleOffset)
{
if (radius <= 0)
throw new ArgumentException("radius must be greater than 0", nameof(radius));
if (angleOffset <= 0)
throw new ArgumentException("angleOffset must be greater than 0", nameof(angleOffset));

_angleOffset = angleOffset * Math.PI / 180;
_radius = radius;
}

public FermatSpiralPointsGenerator(FermatSpiralSettings settings)
: this(settings.Radius, settings.AngleOffset)
{
}

public IEnumerable<Point> GeneratePoints(Point spiralCenter)
{
double angle = 0;

while (true)
{
yield return GetPointByPolarCoordinates(spiralCenter, angle);
angle += _angleOffset;
}
// ReSharper disable once IteratorNeverReturns
}

private Point GetPointByPolarCoordinates(Point spiralCenter, double angle)
{
var radiusVector = OffsetPerRadian * angle;

var x = (int)Math.Round(
radiusVector * Math.Cos(angle) + spiralCenter.X);
var y = (int)Math.Round(
radiusVector * Math.Sin(angle) + spiralCenter.Y);

return new Point(x, y);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Drawing;

namespace TagCloud.CloudLayouter.PointLayouter.Generators;

public interface IPointsGenerator
{
public IEnumerable<Point> GeneratePoints(Point startPoint);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace TagCloud.CloudLayouter.PointLayouter.Settings.Generators;

public record FermatSpiralSettings(double Radius, double AngleOffset);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using System.Drawing;
using TagCloud.CloudLayouter.PointLayouter.Generators;

namespace TagCloud.CloudLayouter.PointLayouter.Settings;

public record PointLayouterSettings(Point Center, IPointsGenerator Generator);
36 changes: 36 additions & 0 deletions TagCloud/ImageGenerator/BitmapGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Drawing;
using TagCloud.CloudLayouter;

namespace TagCloud.ImageGenerator;

#pragma warning disable CA1416
public class BitmapGenerator(Size size, FontFamily family, Color background, Color foreground, ICloudLayouter layouter)
{
public BitmapGenerator(BitmapSettings settings, ICloudLayouter layouter)
: this(settings.Sizes, settings.Font, settings.BackgroundColor, settings.ForegroundColor, layouter)
{}

public Bitmap GenerateWindowsBitmap(List<WordTag> tags)
{
var bitmap = new Bitmap(size.Width, size.Height);
using var graphics = Graphics.FromImage(bitmap);

graphics.Clear(background);
var brush = new SolidBrush(foreground);

foreach (var tag in tags)
{
var font = new Font(family, tag.FontSize);
var wordSize = CeilSize(graphics.MeasureString(tag.Word, font));

var positionRect = layouter.PutNextRectangle(wordSize);
graphics.DrawString(tag.Word, font, brush, positionRect);
}

return bitmap;
}

private static Size CeilSize(SizeF size)
=> new((int)size.Width + 1, (int)size.Height + 1);
}
#pragma warning restore CA1416
9 changes: 9 additions & 0 deletions TagCloud/ImageGenerator/BitmapSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Drawing;

namespace TagCloud.ImageGenerator;

public record BitmapSettings(
Size Sizes,
FontFamily Font,
Color BackgroundColor,
Color ForegroundColor);
3 changes: 3 additions & 0 deletions TagCloud/ImageGenerator/WordTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace TagCloud.ImageGenerator;

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

namespace TagCloud.ImageSaver;

#pragma warning disable CA1416
public class BitmapFileSaver(string imageName, string imageFormat) : IImageSaver
{
private readonly List<string> _supportedFormats = ["png", "jpg", "jpeg", "bmp"];

public BitmapFileSaver(FileSaveSettings settings)
: this(settings.ImageName, settings.ImageFormat)
{ }

public string Save(Bitmap image)
{
if (!_supportedFormats.Contains(imageFormat))
throw new ArgumentException($"Unsupported image format: {imageFormat}");

var fullImageName = $"{imageName}.{imageFormat}";
image.Save(fullImageName);
return Path.Combine(Directory.GetCurrentDirectory(), fullImageName);
}
}
#pragma warning restore CA1416
3 changes: 3 additions & 0 deletions TagCloud/ImageSaver/FileSaveSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace TagCloud.ImageSaver;

public record FileSaveSettings(string ImageName, string ImageFormat);
Copy link

Choose a reason for hiding this comment

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

А если мы захотим сделать сохранение на яндекс диск, то какие settings нужно будет передать?

Copy link

Choose a reason for hiding this comment

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

Я к тому, что, возможно, передавать Settings в конструкторе - может стать узким горлышком.
Но сразу скажу, что нет, просто хочу послушать твои мысли)

8 changes: 8 additions & 0 deletions TagCloud/ImageSaver/IImageSaver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Drawing;

namespace TagCloud.ImageSaver;

public interface IImageSaver
{
public string Save(Bitmap image);
}
31 changes: 31 additions & 0 deletions TagCloud/TagCloud.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<ItemGroup>
<Content Include="bin\Debug\net8.0\TagCloud.deps.json" />
<Content Include="bin\Debug\net8.0\TagCloud.dll" />
<Content Include="bin\Debug\net8.0\TagCloud.exe" />
<Content Include="bin\Debug\net8.0\TagCloud.pdb" />
<Content Include="bin\Debug\net8.0\TagCloud.runtimeconfig.json" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Autofac" Version="8.1.1" />
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="CsvHelper" Version="33.0.1" />
<PackageReference Include="DocX" Version="3.0.1" />
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
<PackageReference Include="WeCantSpell.Hunspell" Version="5.2.1" />
</ItemGroup>

<ItemGroup>
<None Include="bin\Debug\net8.0\Dictionaries\enUS.aff" />
<None Include="bin\Debug\net8.0\Dictionaries\enUS.dic" />
</ItemGroup>

</Project>
31 changes: 31 additions & 0 deletions TagCloud/WordsFilter/BoringWordsFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using WeCantSpell.Hunspell;

namespace TagCloud.WordsFilter;

public class BoringWordsFilter : IWordsFilter
{
private readonly WordList wordList = WordList.CreateFromFiles(
"./Dictionaries/enUS.dic",
"./Dictionaries/enUS.aff");

public List<string> ApplyFilter(List<string> words)
=> words.Where(w => !IsBoring(w)).ToList();

private WordEntryDetail[] CheckDetails(string word)
{
var details = wordList.CheckDetails(word);
return wordList[string.IsNullOrEmpty(details.Root) ? word : details.Root];
}

private bool IsBoring(string word)
{
var details = CheckDetails(word);

if (details.Length != 0 && details[0].Morphs.Count != 0)
{
var po = details[0].Morphs[0];
return po is "po:pronoun" or "po:preposition" or "po:determiner" or "po:conjunction";
}
return false;
}
}
Loading