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

Рыбин Леонид #16

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 38 additions & 0 deletions TagCloud/CloudGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using TagCloud.ImageGenerator;
using TagCloud.ImageSaver;
using TagCloud.WordsFilter;
using TagCloud.WordsReader;

namespace TagCloud;

public class CloudGenerator(
IImageSaver saver,
IWordsReader reader,
BitmapGenerator imageGenerator,
IEnumerable<IWordsFilter> filters)
{
private const int MIN_FONT_SIZE = 10;
private const int MAX_FONT_SIZE = 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)(MIN_FONT_SIZE + (float)freq / maxFreq * (MAX_FONT_SIZE - MIN_FONT_SIZE));

private static WordTag ToWordTag(KeyValuePair<string, int> pair, int maxFreq)
=> new(pair.Key, TransformFreqToSize(pair.Value, maxFreq));
}
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);
}
44 changes: 44 additions & 0 deletions TagCloud/CloudLayouter/PointLayouter/PointCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Drawing;
using TagCloud.CloudLayouter.PointLayouter.PointGenerator;
using TagCloud.CloudLayouter.Settings;

namespace TagCloud.CloudLayouter.PointLayouter;

public class PointCloudLayouter(Point center, IPointGenerator pointGenerator) : ICloudLayouter
{
private readonly List<Point> placedPoints = [];
private readonly List<Rectangle> placedRectangles = [];

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

public Point Center { get; } = center;

public Rectangle PutNextRectangle(Size rectangleSize)
{
Rectangle placedRect;
try
{
placedRect = pointGenerator.StartFrom(Center)
.Except(placedPoints)
.Select(p => CreateRectangle(p, rectangleSize))
.First(r => !placedRectangles.Any(r.IntersectsWith));
}
catch (InvalidOperationException)
{
throw new ArgumentException("There are no more points in generator");
}

placedRectangles.Add(placedRect);
placedPoints.Add(placedRect.Location - placedRect.Size / 2);

return placedRect;
}

private static Rectangle CreateRectangle(Point center, Size rectangleSize)
{
var rectangleUpperLeft = center - rectangleSize / 2;
return new Rectangle(rectangleUpperLeft, rectangleSize);
}
}
24 changes: 24 additions & 0 deletions TagCloud/CloudLayouter/PointLayouter/PointGenerator/Direction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace TagCloud.CloudLayouter.PointLayouter.PointGenerator;

public enum Direction
{
Up,
Right,
Down,
Left,
}

internal static class DirectionExtensions
{
public static Direction AntiClockwiseRotate(this Direction direction)
{
return direction switch
{
Direction.Up => Direction.Left,
Direction.Left => Direction.Down,
Direction.Down => Direction.Right,
Direction.Right => Direction.Up,
_ => throw new ArgumentOutOfRangeException(nameof(direction), direction, null)
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Drawing;
using TagCloud.CloudLayouter.Settings.Generators;

namespace TagCloud.CloudLayouter.PointLayouter.PointGenerator.Generators;

public class PolarArchimedesSpiral : IPointGenerator
{
public double Radius { get; }
public double AngleOffset { get; }

public PolarArchimedesSpiral(PolarSpiralSettings settings)
: this(settings.Radius, settings.AngleOffset)
{ }

public PolarArchimedesSpiral(double radius, double angleOffset)
{
if (radius <= 0 || angleOffset <= 0)
{
var argName = radius <= 0 ? nameof(radius) : nameof(angleOffset);
throw new ArgumentException("Spiral params should be positive.", argName);
}

Radius = radius;
AngleOffset = angleOffset * Math.PI / 180;
}

public IEnumerable<Point> StartFrom(Point startPoint)
{
var currentAngle = 0.0;
while (true)
{
var polarCoordinate = Radius / (2 * Math.PI) * currentAngle;

var xOffset = (int)Math.Round(polarCoordinate * Math.Cos(currentAngle));
var yOffset = (int)Math.Round(polarCoordinate * Math.Sin(currentAngle));

yield return startPoint + new Size(xOffset, yOffset);

currentAngle += AngleOffset;
}
// ReSharper disable once IteratorNeverReturns
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Drawing;
using TagCloud.CloudLayouter.Settings.Generators;

namespace TagCloud.CloudLayouter.PointLayouter.PointGenerator.Generators;

public class SquareArchimedesSpiral : IPointGenerator
{
public int Step { get; }

public SquareArchimedesSpiral(SquareSpiralSettings settings)
: this(settings.Step)
{ }

public SquareArchimedesSpiral(int step)
{
if (step <= 0)
throw new ArgumentException("Step should be positive number");

Step = step;
}

public IEnumerable<Point> StartFrom(Point startPoint)
{
var neededPoints = 1;
var pointsToPlace = 1;
var direction = Direction.Up;
var currentPoint = startPoint;

while (true)
{
yield return currentPoint;

pointsToPlace--;
currentPoint += GetOffsetSize(direction);

if (pointsToPlace == 0)
{
direction = direction.AntiClockwiseRotate();
if (direction is Direction.Up or Direction.Down) neededPoints++;
pointsToPlace = neededPoints;
}
}
}

private Size GetOffsetSize(Direction direction) => direction switch
{
Direction.Up => new Size(0, Step),
Direction.Right => new Size(Step, 0),
Direction.Down => new Size(0, -Step),
Direction.Left => new Size(-Step, 0),
_ => throw new ArgumentOutOfRangeException(nameof(direction))
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Drawing;

namespace TagCloud.CloudLayouter.PointLayouter.PointGenerator;

public interface IPointGenerator
{
/*
* Infinite point generator
*/
public IEnumerable<Point> StartFrom(Point startPoint);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace TagCloud.CloudLayouter.Settings.Generators;

public record PolarSpiralSettings(double Radius, double AngleOffset);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace TagCloud.CloudLayouter.Settings.Generators;

public enum PossibleGenerators
{
POLAR_SPIRAL,
SQUARE_SPIRAL,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace TagCloud.CloudLayouter.Settings.Generators;

public record SquareSpiralSettings(int Step);
6 changes: 6 additions & 0 deletions TagCloud/CloudLayouter/Settings/PointLayouterSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using System.Drawing;
using TagCloud.CloudLayouter.PointLayouter.PointGenerator;

namespace TagCloud.CloudLayouter.Settings;

public record PointLayouterSettings(Point Center, IPointGenerator Generator);
35 changes: 35 additions & 0 deletions TagCloud/ImageGenerator/BitmapGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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);
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);
}
2 changes: 2 additions & 0 deletions TagCloud/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
26 changes: 26 additions & 0 deletions TagCloud/TagCloud.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

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

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

<ItemGroup>
<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>

</Project>
Loading