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

Шестопалов Андрей #33

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
116 changes: 116 additions & 0 deletions TagsCloudContainer.Tests/CircularCloudContainerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System.Drawing;
using System.Windows.Forms;
using FluentAssertions;
using NUnit.Framework;

namespace TagsCloudContainer.Tests;

[TestFixture]
public class CircularCloudContainerTests
{
private CircularCloudLayouter circularCloudLayouter;

[SetUp]
public void Setup()
{
circularCloudLayouter = new CircularCloudLayouter(Point.Empty);
var spiral = new ArchimedeanSpiral(Point.Empty);
circularCloudLayouter.Spiral = spiral;
}

[Test]
public void Constructor_SetCenterCorrectly_WhenInitialized()
{
var center = Point.Empty;
var cloudCenter = circularCloudLayouter.Center;

cloudCenter.Should().Be(center);
}

[Test]
public void CloudSizeIsZero_WhenInitialized()
{
var actualSize = circularCloudLayouter.Size();

actualSize.Should().Be(Size.Empty);
}

[Test]
public void CloudSizeEqualsFirstTagSize_WhenPuttingFirstTag()
{
var font = new Font("Arial", 25);
const string text = "text";
var expectsdRectangleSize = TextRenderer.MeasureText(text, font);

circularCloudLayouter.PutNextTag(text, 3);
var actualRectangleSize = circularCloudLayouter.Size();

actualRectangleSize.Should().Be(expectsdRectangleSize);
}

[Test]
public void CloudSizeIsCloseToCircleShape_WhenPuttingManyTags()
{
for (var i = 0; i < 100; i++)
{
circularCloudLayouter.PutNextTag("test", 4);
}

var actualSize = circularCloudLayouter.Size();
var aspectRatio = (double)actualSize.Width / actualSize.Height;

aspectRatio.Should().BeInRange(0.5, 2.0);
}

[TestCase(0, TestName = "NoTags_WhenInitialized")]
[TestCase(1, TestName = "SingleTag_WhenPuttingFirstTag")]
[TestCase(10, TestName = "MultipleTags_WhenPuttingALotOfTags")]
public void CloudContains(int rectangleCount)
{
for (var i = 0; i < rectangleCount; i++)
{
circularCloudLayouter.PutNextTag("test", 2);
}

circularCloudLayouter.Tags.Count.Should().Be(rectangleCount);
}

[Test]
public void PutNextRectangle_ThrowException_WhenCountIsNotPositive()
{
var putIncorrectRectangle = () => circularCloudLayouter.PutNextTag("test", -5);

putIncorrectRectangle.Should().Throw<ArgumentException>();
}

[Test]
public void PutNextRectangle_PlacesFirstRectangleInCenter()
{
var center = Point.Empty;
var firstRectangle = circularCloudLayouter.PutNextTag("text", 3);

var rectangleCenter = new Point(
firstRectangle.Left + firstRectangle.Width / 2,
firstRectangle.Top - firstRectangle.Height / 2);

rectangleCenter.Should().Be(center);
}

[Test]
public void PutNextRectangle_CloudTagsIsNotIntersect_WhenPuttingALotOfTags()
{
circularCloudLayouter.PutNextTag("test", 3);
circularCloudLayouter.PutNextTag("test", 2);
circularCloudLayouter.PutNextTag("test", 1);

var tags = circularCloudLayouter.Tags;
for (var i = 0; i < tags.Count; i++)
{
var currentRectangle = tags[i].Rectangle;
tags
.Where((_, j) => j != i)
.All(otherTag => !currentRectangle.IntersectsWith(otherTag.Rectangle))
.Should().BeTrue();
}
}
}
23 changes: 23 additions & 0 deletions TagsCloudContainer.Tests/TagsCloudContainer.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net48</TargetFramework>

This comment was marked as resolved.

<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>10</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="7.0.0" />
<PackageReference Include="NUnit" Version="4.3.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\TagsCloudContainer\TagsCloudContainer.csproj" />
</ItemGroup>

<ItemGroup>
<Reference Include="System.Windows.Forms" />
</ItemGroup>

</Project>
63 changes: 63 additions & 0 deletions TagsCloudContainer/App.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Drawing;

namespace TagsCloudContainer;

public class App
{
public static string GetFileNameFromUser()
{
while (true)

This comment was marked as resolved.

{
Console.Write("Введите название файла с текстом: ");
var fileName = Console.ReadLine();

if (!string.IsNullOrEmpty(fileName) && File.Exists(fileName))
{
return fileName;
}

Console.WriteLine("Файл не найден. Попробуйте снова.");
}
}

public static (int width, int height) GetImageDimensionsFromUser()
{
while (true)
{
Console.Write("Введите размер изображения (ширина высота): ");

This comment was marked as resolved.

var input = Console.ReadLine()?.Split(' ');

if (input?.Length == 2 &&
int.TryParse(input[0], out int width) &&
int.TryParse(input[1], out int height) &&
width > 0 && height > 0)
{
return (width, height);
}

Console.WriteLine("Некорректный ввод. Убедитесь, что вы ввели два положительных целых числа.");
}
}

public static string GetFontNameFromUser()
{
Console.Write("Введите название шрифта: ");
return Console.ReadLine() ?? "Arial";

This comment was marked as resolved.

}

public static Color GetColorFromUser(string prompt)

This comment was marked as resolved.

{
while (true)
{
Console.Write(prompt);
var colorInput = Console.ReadLine();

if (Enum.TryParse(colorInput, true, out KnownColor knownColor))
{
return Color.FromKnownColor(knownColor);
}

Console.WriteLine("Некорректное название цвета. Попробуйте снова.");
}
}
}
135 changes: 135 additions & 0 deletions TagsCloudContainer/CircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using System.Drawing;
using System.Windows.Forms;
using TagsCloudContainer.Extensions;

namespace TagsCloudContainer;

public class CircularCloudLayouter

This comment was marked as resolved.

{
public readonly Point Center;
public List<Tag> Tags { get; }
public Color? BackgroundColor { get; private set; }
public Color? TextColor { get; private set; }
public string? FontName { get; private set; }
public ISpiral Spiral { get; set; } = null!;

public CircularCloudLayouter(Point center)
{
Center = center;
Tags = [];
}

public CircularCloudLayouterVisualizer CreateView(int width, int height)
{
return new CircularCloudLayouterVisualizer(this, new Size(width, height));
}

public CircularCloudLayouter SetFontName(string font)

Choose a reason for hiding this comment

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

Предлагаю эти методы по инициализации вынести в какой-нибудь отдельный класс типа CircularCloudLayouterFactory, а тут оставить только расставление тэгов, чтобы ответственность класса слишком сильно не раздувать.

{
if (font.FontExists())
{
FontName = font;
}

return this;
}

public CircularCloudLayouter SetBackgroundColor(Color color)
{
BackgroundColor = color;
return this;
}

public CircularCloudLayouter SetTextColor(Color color)
{
TextColor = color;
return this;
}

public CircularCloudLayouter PutTags(Dictionary<string,int> words)
{
var sortedWords = words.OrderByDescending(pair => pair.Value);

foreach (var word in sortedWords)
{
PutNextTag(word.Key, word.Value);
}

return this;
}

public Rectangle PutNextTag(string text, int count)
{
if (count < 1)
{
throw new ArgumentException(nameof(count));
}

var font = new Font(FontName ?? "Arial", count * 5 + 10);
var rectangleSize = TextRenderer.MeasureText(text, font);

Rectangle newRectangle;

do
{
var location = Spiral.GetNextPoint();
location.Offset(-rectangleSize.Width / 2, rectangleSize.Height / 2);
newRectangle = new Rectangle(location, rectangleSize);
}
while (Tags.Select(tag => tag.Rectangle).IsIntersectsWithAny(newRectangle));

newRectangle = ShiftRectangleToCenter(newRectangle);

var tag = new Tag(text, font, newRectangle);
Tags.Add(tag);
return newRectangle;
}

public Size Size()
{
if (Tags.Count == 0)
return System.Drawing.Size.Empty;

var left = Tags.Select(tag => tag.Rectangle).Min(rectangle => rectangle.Left);
var right = Tags.Select(tag => tag.Rectangle).Max(rectangle => rectangle.Right);
var top = Tags.Select(tag => tag.Rectangle).Min(rectangle => rectangle.Top);
var bottom = Tags.Select(tag => tag.Rectangle).Max(rectangle => rectangle.Bottom);

return new Size(right - left, bottom - top);
}

private Rectangle ShiftRectangleToCenter(Rectangle rectangle)
{
var directionToCenter = GetDirectionToCenter(rectangle);
while (directionToCenter != Point.Empty)
{
var nextRectangle = MoveRectangle(rectangle, directionToCenter);
if (Tags.Select(tag => tag.Rectangle).IsIntersectsWithAny(nextRectangle))
break;

rectangle = nextRectangle;
directionToCenter = GetDirectionToCenter(rectangle);
}

return rectangle;
}

private Point GetDirectionToCenter(Rectangle rectangle)
{
var rectangleCenter = new Point(
rectangle.Left + rectangle.Width / 2,
rectangle.Top - rectangle.Height / 2);

return new Point(
Math.Sign(Center.X - rectangleCenter.X),
Math.Sign(Center.Y - rectangleCenter.Y)
);
}

private static Rectangle MoveRectangle(Rectangle rectangle, Point direction)
{
return new Rectangle(
new Point(rectangle.X + direction.X, rectangle.Y + direction.Y),
rectangle.Size);
}
}
38 changes: 38 additions & 0 deletions TagsCloudContainer/CircularCloudLayouterVisualizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Drawing;

namespace TagsCloudContainer;

public class CircularCloudLayouterVisualizer
{
private CircularCloudLayouter layouter;
private Size size;

public CircularCloudLayouterVisualizer(CircularCloudLayouter layouter, Size bitmapSize)
{
this.layouter = layouter;
size = bitmapSize;
}

public CircularCloudLayouter SaveImage(string filePath)
{
using var bitmap = new Bitmap(size.Width, size.Height);
using var graphics = Graphics.FromImage(bitmap);

graphics.Clear(layouter.BackgroundColor ?? Color.White);

var centerBitmap = new Point(size.Width / 2, size.Height / 2);
var offsetBitmap = new Point(centerBitmap.X - layouter.Center.X, centerBitmap.Y - layouter.Center.Y);

var brush = new SolidBrush(layouter.TextColor ?? Color.Black);

foreach (var rectangle in layouter.Tags)
{
rectangle.Rectangle.Offset(offsetBitmap);
graphics.DrawString(rectangle.Text, rectangle.Font, brush, rectangle.Rectangle);
}

bitmap.Save(filePath);

return layouter;
}
}
11 changes: 11 additions & 0 deletions TagsCloudContainer/Extensions/FontExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Drawing;

namespace TagsCloudContainer.Extensions;

public static class FontExtensions
{
public static bool FontExists(this string fontName)
{
return FontFamily.Families.Any(f => f.Name.Equals(fontName, StringComparison.OrdinalIgnoreCase));

This comment was marked as resolved.

}
}
Loading