Skip to content
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
37 changes: 37 additions & 0 deletions Homework/TagCloud2/TagCloud2.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36804.6
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagCloudLibrary", "TagCloudLibrary\TagCloudLibrary.csproj", "{B31FD7D6-0B7C-45AE-BFA7-4B476F9BC962}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagCloudTests", "TagCloudTests\TagCloudTests.csproj", "{58B4FC78-D0EE-422D-B095-2D53BEE0B3CC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagCloudCLIApp", "TagCloudCLIApp\TagCloudCLIApp.csproj", "{5D39F8AF-24B7-4815-A70A-44ACF2A1E226}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B31FD7D6-0B7C-45AE-BFA7-4B476F9BC962}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B31FD7D6-0B7C-45AE-BFA7-4B476F9BC962}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B31FD7D6-0B7C-45AE-BFA7-4B476F9BC962}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B31FD7D6-0B7C-45AE-BFA7-4B476F9BC962}.Release|Any CPU.Build.0 = Release|Any CPU
{58B4FC78-D0EE-422D-B095-2D53BEE0B3CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{58B4FC78-D0EE-422D-B095-2D53BEE0B3CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58B4FC78-D0EE-422D-B095-2D53BEE0B3CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{58B4FC78-D0EE-422D-B095-2D53BEE0B3CC}.Release|Any CPU.Build.0 = Release|Any CPU
{5D39F8AF-24B7-4815-A70A-44ACF2A1E226}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5D39F8AF-24B7-4815-A70A-44ACF2A1E226}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5D39F8AF-24B7-4815-A70A-44ACF2A1E226}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5D39F8AF-24B7-4815-A70A-44ACF2A1E226}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D68EB29C-EA44-4F73-99D0-0CF62E7ED807}
EndGlobalSection
EndGlobal
57 changes: 57 additions & 0 deletions Homework/TagCloud2/TagCloudCLIApp/CommandLineArguments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using CommandLine;
using TagCloudLibrary.Preprocessor;

namespace TagCloudCLIApp;

internal class CommandLineArguments
{
[Option("min-font-size", Default = 20, Required = false, HelpText = "Set minimal font size in tag cloud.")]
public float MinFontSize { get; set; }

[Option("max-font-size", Default = 100, Required = false, HelpText = "Set maximal font size in tag cloud.")]
public float MaxFontSize { get; set; }

[Option("text-gap", Default = 12, Required = false, HelpText = "Set gap between text.")]
public float TextGap { get; set; }

[Option("picture-border-size", Default = 20, Required = false, HelpText = "Set border around picture.")]
public int PictureBorderSize { get; set; }

[Option("background-color", Default = "#000000", Required = false, HelpText = "Set backgroud color.")]
public string BackgroudColor { get; set; }

[Option("foreground-color", Default = null, Required = false, HelpText = "Set text color.")]
public string? ForegroundColor { get; set; }

[Option("font-family-name", Default = null, Required = false, HelpText = "Set font family name.")]
public string? FontFamilyName { get; set; }

[Option("image-width", Default = null, Required = false, HelpText = "Set image width.")]
public int? ImageWidth { get; set; }

[Option("image-height", Default = null, Required = false, HelpText = "Set image height.")]
public int? ImageHeight { get; set; }

[Option("image-format", Default = "Png", Required = false, HelpText = "Set image format.")]
public string ImageFormat { get; set; }

[Option(
"selected-parts-of-speech",
Default = new string[]
{
nameof(PartOfSpeech.Adjective),
nameof(PartOfSpeech.Adverb),
nameof(PartOfSpeech.Composite),
nameof(PartOfSpeech.Noun),
nameof(PartOfSpeech.Verb)
},
Required = false,
HelpText = "Sets the selected of the following parts of speech: Adjective, Adverb, PronominalAdverb, NumeralAdjective, AdjectivePronoun, Composite, Conjunction, Interjection, Numeral, Particle, Pretext, Noun, PronounNoun, Verb.")]
public IEnumerable<string> SelectedPartsOfSpeech { get; set; }

[Value(0, MetaName = nameof(WordsFilePath), Required = true, HelpText = "Specifies a path to file in the .txt format.")]
public string WordsFilePath { get; set; }

[Value(1, MetaName = nameof(ImageFilePath), Required = true, HelpText = "Specifies a path to file in the .png format.")]
public string ImageFilePath { get; set; }
}
151 changes: 151 additions & 0 deletions Homework/TagCloud2/TagCloudCLIApp/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
using Autofac;
using CommandLine;
using SkiaSharp;
using TagCloudLibrary;
using TagCloudLibrary.Layouter;
using TagCloudLibrary.Preprocessor;
using TagCloudLibrary.ResultPattern;
using TagCloudLibrary.Visualizer;

namespace TagCloudCLIApp;

internal class Program
{
private static IContainer Container { get; set; }

static void Main(string[] args)
{
Parser.Default.ParseArguments<CommandLineArguments>(args)
.WithParsed(Run)
.WithNotParsed(HandleParseError);
}

private static void Run(CommandLineArguments args)
{
SetUpContainer(args)
.Then(_ => Result.Of(() => GetWordsFromFile(args.WordsFilePath)))
.Then(GetTagCloudImage)
.Then(r =>
SaveTagCloudImage(
args.ImageFilePath,
r,
args.ImageFormat))
.OnFail(e => Console.Error.WriteLine($"Critical Error: {e}"));
}

private static Result<None> SetUpContainer(CommandLineArguments args)
{
var builder = new ContainerBuilder();
return RegisterWordPreprocessorOptions(args, builder)
.Then(_ => Result.OfAction(() => RegisterTagCloudOptions(args, builder)))
.Then(_ => RegisterTagCloudVisualizerOptions(args, builder))
.Then(_ =>
{

builder.RegisterType<WordPreprocessor>().As<IWordPreprocessor>();
builder.Register(c => new CircularCloudLayouter(new())).As<ICloudLayouter>();
builder.RegisterType<TagCloudVisualizer>().As<ITagCloudVisualizer>();
builder.RegisterType<TagCloud>().AsSelf();

Container = builder.Build();
});
}

private static Result<None> RegisterTagCloudVisualizerOptions(CommandLineArguments args, ContainerBuilder builder)
{
if (!SKColor.TryParse(args.BackgroudColor, out var backgroundColor))
return Result.Fail<None>("Incorrect background color.");

if (!SKColor.TryParse(args.ForegroundColor, out var foregroundColor))
return Result.Fail<None>("Incorrect foreground color.");

var tagCloudVisualizerOptions = new TagCloudVisualizerOptions(
args.ImageWidth,
args.ImageHeight,
args.PictureBorderSize,
backgroundColor,
args.ForegroundColor is null ? null : foregroundColor);
builder.RegisterInstance(tagCloudVisualizerOptions);

return Result.Ok();
}

private static void RegisterTagCloudOptions(CommandLineArguments args, ContainerBuilder builder)
{
var tagCloudOptions = new TagCloudOptions(
args.FontFamilyName is null ? null : SKTypeface.FromFamilyName(args.FontFamilyName),
args.MinFontSize,
args.MaxFontSize,
args.TextGap);
builder.RegisterInstance(tagCloudOptions);
}

private static Result<None> RegisterWordPreprocessorOptions(CommandLineArguments args, ContainerBuilder builder)
{
var partsOfSpeech = GetAndValidatePartsOfSpeech(args.SelectedPartsOfSpeech);
var uniquePartsOfSpeech = new HashSet<PartOfSpeech>();
foreach (var partOfSpeech in partsOfSpeech)
{
if (partOfSpeech.IsSuccess)
uniquePartsOfSpeech.Add(partOfSpeech.GetValueOrThrow());
else
Console.WriteLine($"Warn: {partOfSpeech.Error}.");
}

var wordPreprocessorOptions = new WordPreprocessorOptions(uniquePartsOfSpeech);
builder.RegisterInstance(wordPreprocessorOptions);

return Result.Ok();
}

private static void HandleParseError(IEnumerable<Error> errs)
{
if (errs.IsVersion())
{
Console.WriteLine("Version Request");
return;
}

if (errs.IsHelp())
{
Console.WriteLine("Help Request");
return;
}
Console.WriteLine("Parser Fail");
}

private static IEnumerable<Result<PartOfSpeech>> GetAndValidatePartsOfSpeech(IEnumerable<string> partsOfSpeech)
{
foreach (var partOfSpeech in partsOfSpeech)
yield return Enum.TryParse<PartOfSpeech>(partOfSpeech, out var result)
? result
: Result.Fail<PartOfSpeech>(
$"The part of speech ({partOfSpeech}) is not one of the defined parts of speech.");
}

private static Result<SKImage> GetTagCloudImage(string[] words)
{
using var scope = Container.BeginLifetimeScope();
var cloud = scope.Resolve<TagCloud>();

return cloud
.BuildTagTree(words)
.Then(_ => cloud.CreateImage());
}

private static string[] GetWordsFromFile(string path) => File.ReadAllText(path).Split();

private static Result<None> SaveTagCloudImage(string path, SKImage image, string format)
{
if (!Enum.TryParse<SKEncodedImageFormat>(format, out var imageFormat))
return Result.Fail<None>("Incorrect image encode format.");

using var data = image.Encode(imageFormat, 100);
using var stream = File.OpenWrite(path);
if (data is null)
return Result.Fail<None>($"Can't encode bitmap into format {format}.");

data.SaveTo(stream);
return Result.Ok();
}
}
19 changes: 19 additions & 0 deletions Homework/TagCloud2/TagCloudCLIApp/TagCloudCLIApp.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<ItemGroup>
<PackageReference Include="Autofac" Version="9.0.0" />
<PackageReference Include="CommandLineParser" Version="2.9.1" />
</ItemGroup>

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

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using SkiaSharp;
using TagCloudLibrary.ResultPattern;
using static System.Math;

namespace TagCloudLibrary.Layouter;

public class CircularCloudLayouter(SKPoint center) : ICloudLayouter
{
private const double radiusStep = 1;
private const double angleStep = .01;
private double radius = 0;
private double angle = 0;
private readonly List<SKRect> placedRectangles = [];

public Result<SKRect> PutNextRectangle(SKSize rectangleSize)
{
if (rectangleSize.Width <= 0 || rectangleSize.Height <= 0)
return Result.Fail<SKRect>("Recatngle size should be greater then zero.");

var rectangle = FindRectangleWithCorrectPosition(rectangleSize);
placedRectangles.Add(rectangle);
return rectangle;
}

private SKRect FindRectangleWithCorrectPosition(SKSize size)
{
if (radius == 0)
{
radius += radiusStep;
return SKRect.Create(center - new SKSize(size.Width / 2, size.Height / 2), size);
}

while (!CanPlaceRectangle(CreateRectangleAwayFromCenter(center, angle, radius, size)))
{
angle += angleStep;

if (angle > 2 * PI)
{
angle = 0;
radius += radiusStep;
}
}

return PullRectangleToCenter(size);
}

private bool CanPlaceRectangle(SKRect rectangle)
{
foreach (var placedRectangle in placedRectangles)
if (rectangle.IntersectsWith(placedRectangle))
return false;

return true;
}

/// <summary>
/// Pulls a rectangle toward the center of the cloud until it is centered (radius + circumscribingCircleRadius = 0) or intersects with another rectangle.
/// </summary>
private SKRect PullRectangleToCenter(SKSize size)
{
var currentRadius = radius;
var circumscribingCircleRadius = GetCircumscribingCircleRadius(size);

while (currentRadius > -circumscribingCircleRadius
&& CanPlaceRectangle(CreateRectangleAwayFromCenter(center, angle, currentRadius, size)))
currentRadius -= radiusStep;

currentRadius += radiusStep;
return CreateRectangleAwayFromCenter(center, angle, currentRadius, size);
}

/// <summary>
/// The method creates a rectangle at a distance from the cloud center to the circumscribed circle of a rectangle.
/// </summary>
private static SKRect CreateRectangleAwayFromCenter(SKPoint center, double angle, double distance, SKSize size)
{
var circumscribingCircleRadius = GetCircumscribingCircleRadius(size);
var location = CreatePointAwayFromCenter(center, angle, distance + circumscribingCircleRadius) - new SKSize(size.Width / 2, size.Height / 2);

return SKRect.Create(location, size);
}

private static double GetCircumscribingCircleRadius(SKSize size)
=> Sqrt(
size.Width / 2 * (size.Width / 2)
+ size.Height / 2 * (size.Height / 2));

private static SKPoint CreatePointAwayFromCenter(SKPoint center, double angle, double distance)
=> new(
center.X + (float)(distance * Cos(angle)),
center.Y + (float)(distance * Sin(angle)));
}
9 changes: 9 additions & 0 deletions Homework/TagCloud2/TagCloudLibrary/Layouter/ICloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using SkiaSharp;
using TagCloudLibrary.ResultPattern;

namespace TagCloudLibrary.Layouter;

public interface ICloudLayouter
{
Result<SKRect> PutNextRectangle(SKSize rectangleSize);
}
5 changes: 5 additions & 0 deletions Homework/TagCloud2/TagCloudLibrary/Layouter/PlacedText.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using SkiaSharp;

namespace TagCloudLibrary.Layouter;

public record class PlacedText(string Text, SKFont Font, SKRect Place);
12 changes: 12 additions & 0 deletions Homework/TagCloud2/TagCloudLibrary/Preprocessor/AnalysisInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;

namespace TagCloudLibrary.Preprocessor;

public class AnalysisInfo
{
[JsonPropertyName("lex")]
public string Lexeme { get; set; }

[JsonPropertyName("gr")]
public string Gramme { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using TagCloudLibrary.ResultPattern;

namespace TagCloudLibrary.Preprocessor;

public interface IWordPreprocessor
{
Result<List<string>> Process(IEnumerable<string> words);
}
Loading