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

Зубков Андрей #164

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 0 additions & 1 deletion TagCloud/AppSettings/IAppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,4 @@ public interface IAppSettings
public bool UseRandomPalette { get; }
public string BackgroundColor { get; }
public string ForegroundColor { get; }
public string BoringWordsFile { get; }
}
13 changes: 5 additions & 8 deletions TagCloud/AppSettings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,24 @@ public class Settings : IAppSettings
[Option('f', "fontType", Default = "SansSerif", HelpText = "Font type of words")]
public string FontType { get; set; }

[Option('W', "width", Default = 1920, HelpText = "Width of cloud")]
[Option('W', "width", Default = 1920, HelpText = "Width of cloud in pixels")]
public int CloudWidth { get; set; }

[Option('H', "height", Default = 1080, HelpText = "Height of cloud")]
[Option('H', "height", Default = 1080, HelpText = "Height of cloud in pixels")]
public int CloudHeight { get; set; }

[Option('l', "layouter", Default = "Spiral", HelpText = "Cloud layouter algorithm")]
public string LayouterType { get; set; }

[Option('d', "density", Default = 1, HelpText = "Density of cloud")]
[Option('d', "density", Default = 1, HelpText = "Density of cloud, integer")]
public int CloudDensity { get; set; }

[Option('r', "randomPalette", Default = true, HelpText = "Use random colors")]
public bool UseRandomPalette { get; set; }

[Option("background", Default = "White", HelpText = "Cloud layouter algorithm")]
[Option("background", Default = "White", HelpText = "Bckground color name")]
public string BackgroundColor { get; set; }

[Option("foreground", Default = "Black", HelpText = "Cloud layouter algorithm")]
[Option("foreground", Default = "Black", HelpText = "Foreground color name")]
public string ForegroundColor { get; set; }

[Option("boringWordsFile", Default = null, HelpText = "Cloud layouter algorithm")]
public string BoringWordsFile { get; set; }
}
22 changes: 7 additions & 15 deletions TagCloud/Configurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,16 @@ public class Configurator
{
public static IAppSettings Parse(string[] args, ContainerBuilder builder)
{
var settings = Parser.Default.ParseArguments<Settings>(args).WithParsed(o =>
var sets = Parser.Default.ParseArguments<Settings>(args);
if (sets as Parsed<Settings> == null)
Environment.Exit(-1);
var settings = sets.WithParsed(o =>
{
if (o.UseRandomPalette)
builder.RegisterType<RandomPalette>().As<IPalette>();
else
builder.Register(p =>
new CustomPalette(Color.FromName(o.ForegroundColor), Color.FromName(o.BackgroundColor)));
var filter = new WordFilter().UsingFilter((word) => word.Length > 3);
if (string.IsNullOrEmpty(o.BoringWordsFile))
builder.Register(c => filter).As<IFilter>();
else
{
var boringWords = new TxtReader().ReadLines(o.BoringWordsFile);
builder.Register(c => filter.UsingFilter((word) => !boringWords.Contains(word)));
}
});

return settings.Value;
Expand All @@ -45,20 +40,17 @@ public static ContainerBuilder BuildWithSettings(IAppSettings settings, Containe
builder.RegisterType<CloudDrawer>().As<IDrawer>();
builder.RegisterType<WordRankerByFrequency>().As<IWordRanker>();
builder.RegisterType<DefaultPreprocessor>().As<IPreprocessor>();
builder.RegisterType<SpiralGenerator>().As<IPointGenerator>();
builder.RegisterType<CirclesGenerator>().As<IPointGenerator>();

builder.RegisterType<ConsoleUI>().As<IUserInterface>();

builder.Register(c => new WordFilter().UsingFilter((word) => word.Length > 3)).As<IFilter>();
builder.Register(c =>
new SpiralGenerator(new Point(settings.CloudWidth / 2, settings.CloudWidth / 2), settings.CloudDensity))
.As<IPointGenerator>();
builder.Register(c => new CirclesGenerator(new Point(settings.CloudWidth / 2, settings.CloudWidth / 2)))
.As<IPointGenerator>();
builder.Register(c => new FileReaderProvider(c.Resolve<IEnumerable<IFileReader>>())).As<IFileReaderProvider>();
builder.Register(c => new PointGeneratorProvider(c.Resolve<IEnumerable<IPointGenerator>>()))
.As<IPointGeneratorProvider>();

builder.Register(c => settings).AsImplementedInterfaces();
builder.Register(c => settings).As<IAppSettings>();

return builder;
}
Expand Down
43 changes: 30 additions & 13 deletions TagCloud/Drawer/CloudDrawer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Drawing;
using ResultOf;
using TagCloud.AppSettings;
using TagCloud.Layouter;
using TagCloud.PointGenerator;
Expand All @@ -7,41 +8,51 @@ namespace TagCloud.Drawer;

public class CloudDrawer : IDrawer
{
private readonly ILayouter layouter;
private readonly IPointGeneratorProvider pointGeneratorProvider;
private readonly IPalette palette;
private readonly IAppSettings appSettings;
private int minimalRank;
private int maximalRank;
private const int MaximalFontSize = 50;
private const int LengthSizeMultiplier = 35;

public CloudDrawer(IPointGeneratorProvider pointGenerator, IPalette palette, IAppSettings appSettings)
public CloudDrawer(IPointGeneratorProvider pointGeneratorProvider, IPalette palette, IAppSettings appSettings)
{
layouter = new CloudLayouter(pointGenerator.CreateGenerator(appSettings.LayouterType));
this.pointGeneratorProvider = pointGeneratorProvider;
this.palette = palette;
this.appSettings = appSettings;
}

public Bitmap DrawTagCloud(IEnumerable<(string word, int rank)> words)
public Result<Bitmap> DrawTagCloud(IEnumerable<(string word, int rank)> words)
{
var pointGenerator = pointGeneratorProvider.CreateGenerator(appSettings.LayouterType).Value;
var layouter = new CloudLayouter(pointGenerator);
var tags = PlaceWords(words, layouter);

return !ValidateImageBorders(tags)
? Result.Fail<Bitmap>(
$"Tags don't fit to given image size of {appSettings.CloudWidth}x{appSettings.CloudHeight}")
Copy link

Choose a reason for hiding this comment

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

Аналогичный момент, а что нужно сделать пользователю, чтобы это исправить? Уменьшить / увеличить размеры? Может быть сразу подсказать, какой размер изображения получился сейчас?

: Result.Ok(DrawTags(tags));
}

private Bitmap DrawTags(IEnumerable<Tag> tags)
{
var tags = PlaceWords(words);
var imageSize = new Size(appSettings.CloudWidth, appSettings.CloudHeight);
var shift = GetImageShift(layouter.Rectangles);
var image = new Bitmap(imageSize.Width, imageSize.Height);
using var graphics = Graphics.FromImage(image);
using var background = new SolidBrush(palette.BackgroudColor);
graphics.FillRectangle(background, 0, 0, imageSize.Width, imageSize.Height);
foreach (var tag in tags)
{
var shiftedCoordinates = new PointF(tag.Position.X - shift.Width, tag.Position.Y - shift.Height);
var pointFCoordinates = new PointF(tag.Position.X, tag.Position.Y);
using var brush = new SolidBrush(palette.ForegroundColor);
graphics.DrawString(tag.Value, new Font(appSettings.FontType, tag.FontSize), brush, shiftedCoordinates);
graphics.DrawString(tag.Value, new Font(appSettings.FontType, tag.FontSize), brush, pointFCoordinates);
}

return image;
}

private IList<Tag> PlaceWords(IEnumerable<(string word, int rank)> words)
private IEnumerable<Tag> PlaceWords(IEnumerable<(string word, int rank)> words, ILayouter layouter)
{
maximalRank = words.First().rank;
minimalRank = words.Last().rank - 1;
Expand Down Expand Up @@ -69,11 +80,17 @@ private int CalculateWordBoxLength(int length, int fontSize)
return (int)Math.Round(length * LengthSizeMultiplier * ((double)fontSize / MaximalFontSize));
}

private static Size GetImageShift(IList<Rectangle> rectangles)
private bool ValidateImageBorders(IEnumerable<Tag> tags)
{
var minX = rectangles.Min(rectangle => rectangle.Left);
var minY = rectangles.Min(rectangle => rectangle.Top);
var tagsPositions = tags.Select(t => t.Position);
var minX = tagsPositions.Min(p => p.Left);
var maxX = tagsPositions.Max(p => p.Right);
var minY = tagsPositions.Min(p => p.Top);
var maxY = tagsPositions.Max(p => p.Bottom);

var width = maxX - minX;
var height = maxY - minY;

return new Size(minX, minY);
return width <= appSettings.CloudWidth && height <= appSettings.CloudHeight;
}
}
3 changes: 2 additions & 1 deletion TagCloud/Drawer/IDrawer.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using System.Drawing;
using ResultOf;

namespace TagCloud.Drawer;

public interface IDrawer
{
Bitmap DrawTagCloud(IEnumerable<(string word, int rank)> words);
Result<Bitmap> DrawTagCloud(IEnumerable<(string word, int rank)> words);
}
27 changes: 22 additions & 5 deletions TagCloud/FileReader/DocReader.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
using ResultOf;
using Spire.Doc;

namespace TagCloud.FileReader;

public class DocReader : IFileReader
{
public IEnumerable<string> ReadLines(string inputPath)
public IList<string> GetAvailableExtensions() => new List<string>() { "doc", "docx" };

public Result<IEnumerable<string>> ReadLines(string inputPath)
{
if (!File.Exists(inputPath))
throw new ArgumentException("Source file doesn't exist");
return FileExists(inputPath, out var error)
? Result.Ok(ReadFile(inputPath))
: Result.Fail<IEnumerable<string>>(error);
}

var document = new Document(inputPath, FileFormat.Auto);
private IEnumerable<string> ReadFile(string inputPath)
{
using var document = new Document(inputPath, FileFormat.Auto);
var text = document.GetText();

return text.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(1);
}

public IList<string> GetAvailableExtensions() => new List<string>() { "doc", "docx" };
private bool FileExists(string inputPath, out string error)
Copy link

Choose a reason for hiding this comment

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

С out параметрами нужно быть осторожным, они не являются панацеей. out параметры нельзя использовать, если метод асинхронный, компилятор не даст даже написать такой метод, а асинхронного кода в продакшне будет предостаточно. Если есть хоть небольшой намек на то, что метод может стать асинхронным, лучше сразу перейти на использование других подходов.

Тут, например, можно было использовать все тот же паттерн Result

{
if (!File.Exists(inputPath))
{
error = $"File {inputPath} doesn't exist";
return false;
}

error = string.Empty;
return true;
}
}
10 changes: 6 additions & 4 deletions TagCloud/FileReader/FileReaderProvider.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using ResultOf;

namespace TagCloud.FileReader;

public class FileReaderProvider : IFileReaderProvider
Expand All @@ -9,12 +11,12 @@ public FileReaderProvider(IEnumerable<IFileReader> readers)
this.readers = ArrangeReaders(readers);
}

public IFileReader CreateReader(string inputPath)
public Result<IFileReader> CreateReader(string inputPath)
{
var extension = inputPath.Split(".").Last();
if (readers.ContainsKey(extension))
return readers[extension];
throw new ArgumentException($"{extension} file type is not supported");
return readers.ContainsKey(extension)
? Result.Ok(readers[extension])
: Result.Fail<IFileReader>($"Reading of file {inputPath} with extension {extension} is not supported");
Copy link

Choose a reason for hiding this comment

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

Я думаю принцип ты понял)

}

private Dictionary<string, IFileReader> ArrangeReaders(IEnumerable<IFileReader> readers)
Expand Down
4 changes: 3 additions & 1 deletion TagCloud/FileReader/IFileReader.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using ResultOf;

namespace TagCloud.FileReader;

public interface IFileReader
{
IEnumerable<string> ReadLines(string inputPath);
Result<IEnumerable<string>> ReadLines(string inputPath);

IList<string> GetAvailableExtensions();
}
4 changes: 3 additions & 1 deletion TagCloud/FileReader/IFileReaderProvider.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using ResultOf;

namespace TagCloud.FileReader;

public interface IFileReaderProvider
{
IFileReader CreateReader(string inputPath);
Result<IFileReader> CreateReader(string inputPath);
}
24 changes: 16 additions & 8 deletions TagCloud/FileReader/TxtReader.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
using System.Xml.XPath;
using ResultOf;
using Spire.Doc;

namespace TagCloud.FileReader;

public class TxtReader : IFileReader
{
private List<string> extensions = new() { "txt" };
public IList<string> GetAvailableExtensions() => new List<string>() { "txt" };

public IEnumerable<string> ReadLines(string inputPath)
public Result<IEnumerable<string>> ReadLines(string inputPath)
{
if (!File.Exists(inputPath))
throw new ArgumentException("Source file doesn't exist");

return File.ReadLines(inputPath);
return FileExists(inputPath, out var error)
? Result.Ok(File.ReadLines(inputPath))
: Result.Fail<IEnumerable<string>>(error);
}

public IList<string> GetAvailableExtensions()
private bool FileExists(string inputPath, out string error)
Copy link

Choose a reason for hiding this comment

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

Хм, точно такой же метод находится в классе DocReader, один-в-один, давай вынесем в отдельный статический класс-утилиту и заиспользуем в двух местах

{
return extensions;
if (!File.Exists(inputPath))
{
error = $"File {inputPath} doesn't exist";
return false;
}

error = string.Empty;
return true;
}
}
12 changes: 5 additions & 7 deletions TagCloud/PointGenerator/CirclesGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
using System.Drawing;
using TagCloud.AppSettings;

namespace TagCloud.PointGenerator;

public class CirclesGenerator : IPointGenerator
{
private readonly Point startPoint;
private int density;
private readonly double angleShift;
private const double angleShift = 0.01;
private double currentAngle;

public string GeneratorName => "Circular";

public CirclesGenerator(Point startPoint, int density = 1, double angleShift = 0.01)
public CirclesGenerator(IAppSettings settings)
{
if (startPoint.X < 0 || startPoint.Y < 0)
throw new ArgumentException("Circle center point coordinates should be non-negative");
this.startPoint = startPoint;
this.density = density * 200;
this.angleShift = angleShift;
startPoint = new Point(settings.CloudWidth/2, settings.CloudHeight/2);
density = settings.CloudDensity * 200;
}

public Point GetNextPoint()
Expand Down
4 changes: 3 additions & 1 deletion TagCloud/PointGenerator/IPointGeneratorProvider.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using ResultOf;

namespace TagCloud.PointGenerator;

public interface IPointGeneratorProvider
{
IPointGenerator CreateGenerator(string generatorName);
Result<IPointGenerator> CreateGenerator(string generatorName);
}
10 changes: 6 additions & 4 deletions TagCloud/PointGenerator/PointGeneratorProvider.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using ResultOf;

namespace TagCloud.PointGenerator;

public class PointGeneratorProvider : IPointGeneratorProvider
Expand All @@ -9,11 +11,11 @@ public PointGeneratorProvider(IEnumerable<IPointGenerator> generators)
registeredGenerators = ArrangeLayouters(generators);
}

public IPointGenerator CreateGenerator(string generatorName)
public Result<IPointGenerator> CreateGenerator(string generatorName)
{
if (registeredGenerators.ContainsKey(generatorName))
return registeredGenerators[generatorName];
throw new ArgumentException($"{generatorName} layouter is not supported");
return registeredGenerators.ContainsKey(generatorName)
? Result.Ok(registeredGenerators[generatorName])
: Result.Fail<IPointGenerator>($"{generatorName} layouter is not supported");
Copy link

Choose a reason for hiding this comment

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

Тут было бы прикольно ещё дать пользователю сразу подсказку, а какие тогда поддерживаются?

}

private Dictionary<string, IPointGenerator> ArrangeLayouters(IEnumerable<IPointGenerator> generators)
Expand Down
Loading