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
36 changes: 36 additions & 0 deletions TagsCloudContainer/ContainerBootstrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Autofac;
using TagsCloudContainer.Сlients;
using TagsCloudContainer.Сlients.Console;
using TagsCloudContainer.Сlients.Interfaces;

namespace TagsCloudContainer;

public static class ContainerBootstrapper
{
public static IContainer Build()
{
var builder = new ContainerBuilder();

builder.RegisterType<AutofacTagCloudGeneratorFactory>()
.As<ITagCloudGeneratorFactory>()
.InstancePerDependency();

builder.RegisterType<ClientSelectionParser>()
.As<IClientSelectionParser>()
.SingleInstance();

builder.RegisterType<ClientStrategySelector>()
.AsSelf()
.SingleInstance();

builder.RegisterType<ConsoleClient>()
.As<IClient>()
.InstancePerDependency();

builder.RegisterType<ConsoleClientStrategy>()
.As<IClientStrategy>()
.SingleInstance();

return builder.Build();
}
}
16 changes: 16 additions & 0 deletions TagsCloudContainer/Core/CircularCloudLayouterWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using SixLabors.ImageSharp;
using TagCloud;
using TagsCloudContainer.Core.Interfaces;

namespace TagsCloudContainer.Core;

public class CircularCloudLayouterWrapper(Point center) : ICircularCloudLayouterWrapper
{
private readonly CircularCloudLayouter inner = new(center.ToDrawingPoint());

public Rectangle PutNextRectangle(Size rectangleSize)
{
var rect = inner.PutNextRectangle(rectangleSize.ToDrawingSize());
return rect.ToImageSharpRectangle();
}
}
74 changes: 74 additions & 0 deletions TagsCloudContainer/Core/CloudPositionedTags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using SixLabors.Fonts;
using TagsCloudContainer.Core.Domains;
using TagsCloudContainer.Core.FrequencySizingStrategies;
using TagsCloudContainer.Core.Interfaces;
using TagsCloudContainer.Result;

namespace TagsCloudContainer.Core;

public class CloudPositionedTags(
ICircularCloudLayouterWrapper cloudLayouter,
ITagSizeCalculator tagSizeCalculator)
: ICloudPositionedTags
{
private static readonly IReadOnlyDictionary<bool, IFrequencySizingStrategy> Strategies =
new IFrequencySizingStrategy[]
{
new DirectFrequencySizingStrategy(),
new InvertedFrequencySizingStrategy()
}.ToDictionary(s => s.Inverted);

public Result<IEnumerable<PositionedTag>> GetPositionedTags(
IEnumerable<Tag> tags,
float minFontSize,
float maxFontSize,
bool invertSizeByFrequency,
FontFamily ff)
{
var tagList = tags.ToList();

if (!tagList.Any())
return Result<IEnumerable<PositionedTag>>.Failure("Tag list is empty.");

return FrequencyRange.Get(tagList)
.Bind(freq =>
FontRange.Normalize(minFontSize, maxFontSize)
.Map(font =>
{
var (minFreq, maxFreq) = freq;
var (minFont, maxFont) = font;

var strategy = Strategies[invertSizeByFrequency];

return strategy.Order(tagList)
.Select(tag =>
{
var fontSize = GetFontSize(
tag.Frequency,
minFont,
maxFont,
minFreq,
maxFreq,
strategy);

var size = tagSizeCalculator.GetSize(tag, fontSize, ff);
var rect = cloudLayouter.PutNextRectangle(size);

return new PositionedTag(tag, rect, fontSize);
})
.ToList()
.AsEnumerable();
}));
}


private static float GetFontSize(int frequency, float minFontSize,
float maxFontSize, int minFreq, int maxFreq,
IFrequencySizingStrategy strategy)
{
var avg = (minFontSize + maxFontSize) / 2f;

var normalized = Normalizer.Normalize(frequency, minFreq, maxFreq).OrElse(avg);
return strategy.Scale(minFontSize, maxFontSize, normalized);
}
}
89 changes: 89 additions & 0 deletions TagsCloudContainer/Core/CloudRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using TagsCloudContainer.Core.Domains;
using TagsCloudContainer.Core.Interfaces;
using TagsCloudContainer.Result;

namespace TagsCloudContainer.Core;

public class CloudRenderer : ICloudRenderer
{
public Result<Image<Rgba32>> Render(
TagCloudGenerationRequest request,
IReadOnlyCollection<PositionedTag> positionedTags)
{
if (positionedTags.Count == 0)
return Result<Image<Rgba32>>.Failure(
"Cannot render tag cloud: no tags to render.");

var s = request.LayoutSettings;
var img = new Image<Rgba32>(
s.ImageSize.Width,
s.ImageSize.Height,
request.BackgroundColor);

return BuildRenderContext(request, positionedTags)
.Bind(ctx =>
{
img.Mutate(i =>
{
foreach (var (tag, rect, fontSize) in positionedTags)
{
var font = ctx.FontFamily.CreateFont(fontSize);
var origin = GetCenteredOrigin(tag.Word, font, rect);
var options = new RichTextOptions(font)
{
Origin = origin,
WrappingLength = float.PositiveInfinity
};

i.DrawText(options, tag.Word, ctx.TextColor);
}
});

return Result<Image<Rgba32>>.Success(img);
});
}

private static Result<RenderContext> BuildRenderContext(
TagCloudGenerationRequest request,
IReadOnlyCollection<PositionedTag> positionedTags)
{
var s = request.LayoutSettings;

var minFreq = positionedTags.Min(p => p.Tag.Frequency);
var maxFreq = positionedTags.Max(p => p.Tag.Frequency);

var fontFamilyResult = FontFamilyResolver.Resolve(request.Font);
if (!fontFamilyResult.IsSuccess)
return Result<RenderContext>.Failure(fontFamilyResult.Error ?? Result<RenderContext>.UnknownError);

var fontFamily = fontFamilyResult.Value;
return Result<RenderContext>.Success(
new RenderContext(
FontFamily: fontFamily,
MinFontSize: s.MinFontSize,
MaxFontSize: s.MaxFontSize,
MinFreq: minFreq,
MaxFreq: maxFreq,
TextColor: request.TextColor
));
}

private static PointF GetCenteredOrigin(string text, Font font, Rectangle rect)
{
var bounds = TextMeasurer.MeasureBounds(text, new TextOptions(font)
{
WrappingLength = float.PositiveInfinity
});

var x = rect.X + (rect.Width - bounds.Width) / 2f - bounds.X;
var y = rect.Y + (rect.Height - bounds.Height) / 2f - bounds.Y;

return new PointF(x, y);
}

}
48 changes: 48 additions & 0 deletions TagsCloudContainer/Core/CompositeWordsPreprocessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using TagsCloudContainer.Core.Interfaces;
using TagsCloudContainer.Result;

namespace TagsCloudContainer.Core;

public class CompositeWordsPreprocessor(
IWordNormalizer normalizer,
IEnumerable<IWordsFilter> filters) : IWordsPreprocessor
{
public Result<IEnumerable<string>> Process(IEnumerable<string> words)
{
var result = new List<string>();

foreach (var word in words.Select(normalizer.Normalize))
{
if (string.IsNullOrWhiteSpace(word))
continue;

var keepResult = ShouldKeepWord(word);

if (!keepResult.IsSuccess)
return Result<IEnumerable<string>>.Failure(
keepResult.Error ?? Result<IEnumerable<string>>.UnknownError);

if (keepResult.Value)
result.Add(word);
}

return Result<IEnumerable<string>>.Success(result);
}

private Result<bool> ShouldKeepWord(string word)
{
foreach (var filter in filters)
{
var keepResult = filter.ShouldKeep(word);

if (!keepResult.IsSuccess)
return Result<bool>.Failure(
keepResult.Error ?? Result<bool>.UnknownError);

if (!keepResult.Value)
return Result<bool>.Success(false);
}

return Result<bool>.Success(true);
}
}
10 changes: 10 additions & 0 deletions TagsCloudContainer/Core/Domains/LayoutSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using SixLabors.ImageSharp;

namespace TagsCloudContainer.Core.Domains;

public class LayoutSettings
{
public Size ImageSize { get; init; }
public required float MinFontSize { get; init; }
public required float MaxFontSize { get; init; }
}
18 changes: 18 additions & 0 deletions TagsCloudContainer/Core/Domains/Normilizedalue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace TagsCloudContainer.Core.Domains;

public readonly struct NormalizedValue(float value)
{
public static readonly NormalizedValue EqualFrequencies = new(float.NaN);

public float OrElse(float avg)
{
var isNan = float.IsNaN(value);

var f = value;
return new Dictionary<bool, Func<float>>
{
[true] = () => avg,
[false] = () => f
}[isNan]();
}
}
5 changes: 5 additions & 0 deletions TagsCloudContainer/Core/Domains/PositionedTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using SixLabors.ImageSharp;

namespace TagsCloudContainer.Core.Domains;

public record PositionedTag(Tag Tag, Rectangle Rectangle, float FontSize);
13 changes: 13 additions & 0 deletions TagsCloudContainer/Core/Domains/RenderContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using SixLabors.Fonts;
using SixLabors.ImageSharp;

namespace TagsCloudContainer.Core.Domains;

public record RenderContext(
FontFamily FontFamily,
float MinFontSize,
float MaxFontSize,
int MinFreq,
int MaxFreq,
Color TextColor
);
7 changes: 7 additions & 0 deletions TagsCloudContainer/Core/Domains/SourceSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace TagsCloudContainer.Core.Domains;

public class SourceSettings(string path, string format)
{
public string Path { get; } = path;
public string Format { get; } = format;
}
3 changes: 3 additions & 0 deletions TagsCloudContainer/Core/Domains/Tag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace TagsCloudContainer.Core.Domains;

public record Tag(string Word, int Frequency);
17 changes: 17 additions & 0 deletions TagsCloudContainer/Core/Domains/TagCloudGenerationRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using SixLabors.ImageSharp;

namespace TagsCloudContainer.Core.Domains;

public class TagCloudGenerationRequest
{
public required SourceSettings SourceSettings { get; init; }
public required LayoutSettings LayoutSettings { get; init; }
public required string OutputPath { get; init; }
public Color TextColor { get; init; }
public Color BackgroundColor { get; init; }
public required string OutputFormat { get; init; }

public required string Font { get; init; }

public bool Desc { get; init; }
}
Loading