Merge branch 'dev' into denizdemirkan/L

pull/18178/head
denizdemirkan 1 year ago
commit 1a1174abba

@ -5,7 +5,7 @@ using Volo.Abp.Cli.ProjectBuilding.Building.Steps;
namespace Volo.Abp.Cli.ProjectBuilding.Templates.App;
public abstract class AppNoLayersTemplateBase : AppTemplateBase
public abstract class AppNoLayersTemplateBase : TemplateInfo
{
protected AppNoLayersTemplateBase(string templateName)
: base(templateName)
@ -22,7 +22,24 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
public override IEnumerable<ProjectBuildPipelineStep> GetCustomSteps(ProjectBuildContext context)
{
var steps = base.GetCustomSteps(context).ToList();
SwitchDatabaseProvider(context, steps);
DeleteUnrelatedProjects(context, steps);
RemoveMigrations(context, steps);
RandomizeSslPorts(context, steps);
RandomizeStringEncryption(context, steps);
RandomizeAuthServerPassPhrase(context, steps);
UpdateNuGetConfig(context, steps);
ChangeConnectionString(context, steps);
ConfigureDockerFiles(context, steps);
ConfigureTheme(context, steps);
CleanupFolderHierarchy(context, steps);
return steps;
}
protected void SwitchDatabaseProvider(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
switch (context.BuildArgs.DatabaseProvider)
{
case DatabaseProvider.NotSpecified:
@ -49,7 +66,10 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
}
context.Symbols.Add($"dbms:{context.BuildArgs.DatabaseManagementSystem}");
}
protected void DeleteUnrelatedProjects(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
switch (context.BuildArgs.UiFramework)
{
case UiFramework.Angular:
@ -109,34 +129,47 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
default:
throw new AbpException("Unkown UI framework: " + context.BuildArgs.UiFramework);
}
}
protected void RandomizeSslPorts(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
if (context.BuildArgs.ExtraProperties.ContainsKey("no-random-port"))
{
return;
}
steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName/Migrations"));
steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations"));
RandomizeSslPorts(context, steps);
RandomizeStringEncryption(context, steps);
RandomizeAuthServerPassPhrase(context, steps);
UpdateNuGetConfig(context, steps);
ChangeConnectionString(context, steps);
ConfigureDockerFiles(context, steps);
ConfigureTheme(context, steps);
//todo: discuss blazor ports
steps.Add(new TemplateRandomSslPortStep(
new List<string>
{
"https://localhost:44300",
"https://localhost:44301",
"https://localhost:44302",
"https://localhost:44303",
"https://localhost:44304",
"https://localhost:44305",
"https://localhost:44306",
"https://localhost:44307",
"https://localhost:44308",
"https://localhost:44309",
"https://localhost:44310"
}
)
);
}
protected void CleanupFolderHierarchy(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
if (context.BuildArgs.UiFramework != UiFramework.Angular)
{
steps.Add(new MoveFolderStep("/aspnet-core/", "/"));
}
return steps;
}
private static void RemoveBlazorWasmProjects(List<ProjectBuildPipelineStep> steps)
protected void RemoveMigrations(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Server",
projectFolderPath: "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Client",
projectFolderPath: "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Shared",
projectFolderPath: "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Shared"));
steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly"));
steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName/Migrations"));
steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations"));
}
protected void ConfigureDockerFiles(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
@ -169,4 +202,173 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
break;
}
}
protected void RandomizeStringEncryption(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
steps.Add(new RandomizeStringEncryptionStep());
}
protected static void RandomizeAuthServerPassPhrase(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
steps.Add(new RandomizeAuthServerPassPhraseStep());
}
protected void UpdateNuGetConfig(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
steps.Add(new UpdateNuGetConfigStep("/aspnet-core/NuGet.Config"));
}
protected void ChangeConnectionString(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
if (context.BuildArgs.ConnectionString != null)
{
steps.Add(new ConnectionStringChangeStep());
}
if (IsPro())
{
steps.Add(new ConnectionStringRenameStep());
}
}
protected void ConfigureTheme(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
if (!context.BuildArgs.Theme.HasValue)
{
return;
}
if (context.BuildArgs.Theme != Theme.NotSpecified)
{
context.Symbols.Add(context.BuildArgs.Theme.Value.ToString().ToUpper());
}
if (context.BuildArgs.Theme == Theme.LeptonX)
{
steps.Add(new ChangeThemeStyleStep());
}
RemoveThemeLogoFolders(context, steps);
if (IsDefaultThemeForTemplate(context.BuildArgs))
{
return;
}
steps.Add(new ChangeThemeStep());
RemoveLeptonXThemePackagesFromPackageJsonFiles(steps, isProTemplate: IsPro(), uiFramework: context.BuildArgs.UiFramework);
}
private static void RemoveBlazorWasmProjects(List<ProjectBuildPipelineStep> steps)
{
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Server",
projectFolderPath: "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Client",
projectFolderPath: "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.WebAssembly.Shared",
projectFolderPath: "/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Shared"));
steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly"));
}
private void RemoveThemeLogoFolders(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
if (context.BuildArgs.Theme != Theme.Lepton && IsPro())
{
steps.Add(new RemoveFilesStep("/wwwroot/images/logo/lepton/"));
}
if (context.BuildArgs.Theme != Theme.LeptonX && context.BuildArgs.Theme != Theme.LeptonXLite)
{
steps.Add(new RemoveFilesStep("/wwwroot/images/logo/leptonx/"));
}
}
protected void SetDbmsSymbols(ProjectBuildContext context)
{
switch (context.BuildArgs.DatabaseManagementSystem)
{
case DatabaseManagementSystem.NotSpecified:
context.Symbols.Add("SqlServer");
break;
case DatabaseManagementSystem.SQLServer:
context.Symbols.Add("SqlServer");
break;
case DatabaseManagementSystem.MySQL:
context.Symbols.Add("MySql");
break;
case DatabaseManagementSystem.PostgreSQL:
context.Symbols.Add("PostgreSql");
break;
case DatabaseManagementSystem.Oracle:
context.Symbols.Add("Oracle");
break;
case DatabaseManagementSystem.OracleDevart:
context.Symbols.Add("Oracle");
break;
case DatabaseManagementSystem.SQLite:
context.Symbols.Add("SqLite");
break;
default:
throw new AbpException("Unknown Dbms: " + context.BuildArgs.DatabaseManagementSystem);
}
}
private static bool IsDefaultThemeForTemplate(ProjectBuildArgs args)
{
var templateThemes = new Dictionary<string, Theme>
{
{ AppNoLayersTemplate.TemplateName, AppNoLayersTemplate.DefaultTheme },
{ AppNoLayersProTemplate.TemplateName, AppNoLayersProTemplate.DefaultTheme }
};
return templateThemes.TryGetValue(args.TemplateName!, out var templateTheme) && templateTheme == args.Theme;
}
private static void RemoveLeptonXThemePackagesFromPackageJsonFiles(List<ProjectBuildPipelineStep> steps, bool isProTemplate, UiFramework uiFramework)
{
var mvcUiPackageName = isProTemplate ? "@volo/abp.aspnetcore.mvc.ui.theme.leptonx" : "@abp/aspnetcore.mvc.ui.theme.leptonxlite";
var packageJsonFilePaths = new List<string>
{
"/MyCompanyName.MyProjectName.Web/package.json",
"/MyCompanyName.MyProjectName.Web.Host/package.json",
"/MyCompanyName.MyProjectName/package.json",
"/MyCompanyName.MyProjectName.Host/package.json",
"/MyCompanyName.MyProjectName.Host.Mongo/package.json"
};
foreach (var packageJsonFilePath in packageJsonFilePaths)
{
steps.Add(new RemoveDependencyFromPackageJsonFileStep(packageJsonFilePath, mvcUiPackageName));
}
if (uiFramework == UiFramework.BlazorServer)
{
var blazorServerUiPackageName = isProTemplate ? "@volo/aspnetcore.components.server.leptonxtheme" : "@abp/aspnetcore.components.server.leptonxlitetheme";
var blazorServerPackageJsonFilePaths = new List<string>
{
"/MyCompanyName.MyProjectName.Blazor/package.json",
"/MyCompanyName.MyProjectName.Blazor.Server.Mongo/package.json"
};
foreach (var blazorServerPackageJsonFilePath in blazorServerPackageJsonFilePaths)
{
steps.Add(new RemoveDependencyFromPackageJsonFileStep(blazorServerPackageJsonFilePath, mvcUiPackageName));
steps.Add(new RemoveDependencyFromPackageJsonFileStep(blazorServerPackageJsonFilePath, blazorServerUiPackageName));
}
}
else if (uiFramework == UiFramework.Angular)
{
var ngUiPackageName = isProTemplate ? "@volosoft/abp.ng.theme.lepton-x" : "@abp/ng.theme.lepton-x";
var angularPackageJsonFilePaths = new List<string>
{
"/angular/package.json"
};
foreach (var angularPackageJsonFilePath in angularPackageJsonFilePaths)
{
steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, ngUiPackageName));
steps.Add(new RemoveDependencyFromPackageJsonFileStep(angularPackageJsonFilePath, "bootstrap-icons"));
}
}
}
}

@ -235,7 +235,7 @@ public abstract class AppTemplateBase : TemplateInfo
steps.Add(new ChangeThemeStep());
RemoveLeptonXThemePackagesFromPackageJsonFiles(steps, isProTemplate: IsPro(), uiFramework: context.BuildArgs.UiFramework);
}
protected void SetDbmsSymbols(ProjectBuildContext context)
{
switch (context.BuildArgs.DatabaseManagementSystem)
@ -265,7 +265,7 @@ public abstract class AppTemplateBase : TemplateInfo
throw new AbpException("Unknown Dbms: " + context.BuildArgs.DatabaseManagementSystem);
}
}
private void RemoveThemeLogoFolders(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
if (context.BuildArgs.Theme != Theme.Lepton && IsPro())
@ -284,9 +284,7 @@ public abstract class AppTemplateBase : TemplateInfo
var templateThemes = new Dictionary<string, Theme>
{
{ AppTemplate.TemplateName, AppTemplate.DefaultTheme },
{ AppProTemplate.TemplateName, AppProTemplate.DefaultTheme },
{ AppNoLayersTemplate.TemplateName, AppNoLayersTemplate.DefaultTheme },
{ AppNoLayersProTemplate.TemplateName, AppNoLayersProTemplate.DefaultTheme }
{ AppProTemplate.TemplateName, AppProTemplate.DefaultTheme }
};
return templateThemes.TryGetValue(args.TemplateName!, out var templateTheme) && templateTheme == args.Theme;
@ -304,9 +302,7 @@ public abstract class AppTemplateBase : TemplateInfo
"/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json",
"/MyCompanyName.MyProjectName.HttpApi.Host/package.json",
"/MyCompanyName.MyProjectName.AuthServer/package.json",
"/MyCompanyName.MyProjectName/package.json",
"/MyCompanyName.MyProjectName.Host/package.json",
"/MyCompanyName.MyProjectName.Host.Mongo/package.json"
"/MyCompanyName.MyProjectName/package.json"
};
foreach (var packageJsonFilePath in packageJsonFilePaths)
@ -320,8 +316,7 @@ public abstract class AppTemplateBase : TemplateInfo
var blazorServerPackageJsonFilePaths = new List<string>
{
"/MyCompanyName.MyProjectName.Blazor/package.json",
"/MyCompanyName.MyProjectName.Blazor.Server.Tiered/package.json",
"/MyCompanyName.MyProjectName.Blazor.Server.Mongo/package.json"
"/MyCompanyName.MyProjectName.Blazor.Server.Tiered/package.json"
};
foreach (var blazorServerPackageJsonFilePath in blazorServerPackageJsonFilePaths)

@ -14,6 +14,11 @@ public class BlazorAppsettingsFilePortChangeForSeparatedAuthServersStep : Projec
StringComparison.InvariantCultureIgnoreCase)
);
if (appsettingsFile == null)
{
return;
}
appsettingsFile.NormalizeLineEndings();
var lines = appsettingsFile.GetLines();

@ -73,6 +73,11 @@ public class RemoveUnnecessaryPortsStep : ProjectBuildPipelineStep
.FirstOrDefault(f =>
f.Name.Contains("MyCompanyName.MyProjectName.DbMigrator") && f.Name.EndsWith("appsettings.json"));
if (dbMigratorAppSettings == null)
{
return;
}
var appSettingsJsonObject = JObject.Parse(dbMigratorAppSettings.Content);
var authServerJsonObject = (JObject)appSettingsJsonObject?["IdentityServer"] ?? (JObject)appSettingsJsonObject["OpenIddict"];
var clientsJsonObject = (JObject)authServerJsonObject?["Clients"] ?? (JObject)authServerJsonObject?["Applications"];

@ -1,19 +1,17 @@
using System;
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;
using ImageMagick;
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public class CaptchaOptions
{
public Color[] TextColor { get; set; } = new Color[]
public MagickColor[] TextColor { get; set; } = new MagickColor[]
{
Color.Blue, Color.Black, Color.Black, Color.Brown, Color.Gray, Color.Green
MagickColors.Blue, MagickColors.Black, MagickColors.Black, MagickColors.Brown, MagickColors.Gray, MagickColors.Green
};
public Color[] DrawLinesColor { get; set; } = new Color[]
public MagickColor[] DrawLinesColor { get; set; } = new MagickColor[]
{
Color.Blue, Color.Black, Color.Black, Color.Brown, Color.Gray, Color.Green
MagickColors.Blue, MagickColors.Black, MagickColors.Black, MagickColors.Brown, MagickColors.Gray, MagickColors.Green
};
public float MinLineThickness { get; set; } = 0.7f;
@ -26,15 +24,15 @@ public class CaptchaOptions
public ushort NoiseRate { get; set; } = 500;
public Color[] NoiseRateColor { get; set; } = new Color[] { Color.Gray };
public MagickColor[] NoiseRateColor { get; set; } = new MagickColor[] { MagickColors.Gray };
public byte FontSize { get; set; } = 32;
public FontStyle FontStyle { get; set; } = FontStyle.Regular;
public FontStyleType FontStyle { get; set; } = FontStyleType.Normal;
public EncoderTypes EncoderType { get; set; } = EncoderTypes.Png;
public IImageEncoder Encoder => RandomTextGenerator.GetEncoder(EncoderType);
public MagickFormat Encoder => RandomTextGenerator.GetEncoder(EncoderType);
public byte DrawLines { get; set; } = 2;

@ -1,21 +1,19 @@
using System;
using System.Security.Cryptography;
using System.Text;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using ImageMagick;
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public static class RandomTextGenerator
{
private static readonly char[] AllowedChars = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVXYZW23456789".ToCharArray();
public static IImageEncoder GetEncoder(EncoderTypes encoderType)
public static MagickFormat GetEncoder(EncoderTypes encoderType)
{
IImageEncoder encoder = encoderType switch
var encoder = encoderType switch
{
EncoderTypes.Png => new PngEncoder(),
EncoderTypes.Jpeg => new JpegEncoder(),
EncoderTypes.Png => MagickFormat.Png,
EncoderTypes.Jpeg => MagickFormat.Jpeg,
_ => throw new ArgumentException($"Encoder '{encoderType}' not found!")
};

@ -1,21 +1,13 @@
using System;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Volo.Abp;
using Volo.CmsKit.Localization;
using Microsoft.Extensions.Localization;
using Volo.Abp.DependencyInjection;
using Color = SixLabors.ImageSharp.Color;
using PointF = SixLabors.ImageSharp.PointF;
using Volo.Abp.Caching;
using Microsoft.Extensions.Caching.Distributed;
using ImageMagick;
namespace Volo.CmsKit.Public.Web.Security.Captcha;
@ -108,93 +100,79 @@ public class SimpleMathsCaptchaGenerator : ITransientDependency
private byte[] GenerateInternal(string stringText, CaptchaOptions options)
{
byte[] result;
var random = new Random();
var fontName = MagickNET.FontNames.First();
var drawables = new Drawables()
.Font(fontName, options.FontStyle, FontWeight.Normal, FontStretch.Normal)
.FontPointSize(options.FontSize)
.StrokeColor(MagickColors.Transparent);
var size = (ushort)(drawables.FontTypeMetrics(stringText)?.TextWidth ?? 0);
using var image = new MagickImage(MagickColors.White, size + 15, options.Height);
using (var image = new Image<Rgba32>(options.Width, options.Height))
double position = 0;
var startWith = (byte)random.Next(5, 10);
foreach (var character in stringText)
{
float position = 0;
var random = new Random();
var startWith = (byte)random.Next(5, 10);
image.Mutate(ctx => ctx.BackgroundColor(Color.Transparent));
var fontFamily = SystemFonts.Families
.FirstOrDefault(x => x.GetAvailableStyles().Contains(options.FontStyle), SystemFonts.Families.First())
.Name;
var font = SystemFonts.CreateFont(fontFamily, options.FontSize, options.FontStyle);
foreach (var character in stringText)
{
var text = character.ToString();
var color = options.TextColor[random.Next(0, options.TextColor.Length)];
var location = new PointF(startWith + position, random.Next(6, 13));
image.Mutate(ctx => ctx.DrawText(text, font, color, location));
position += TextMeasurer.MeasureSize(character.ToString(), new TextOptions (font)
{
Origin = location
}).Width;
}
var text = character.ToString();
var color = options.TextColor[random.Next(0, options.TextColor.Length)];
drawables.FillColor(new MagickColor(color.R, color.G, color.B, color.A))
.Text(startWith + position,
RandomTextGenerator.GenerateNextFloat(image.BaseHeight / 2.3, image.BaseHeight / 1.7), text);
//add rotation
var rotation = GetRotation(options);
image.Mutate(ctx => ctx.Transform(rotation));
position += drawables.FontTypeMetrics(text)?.TextWidth ?? 0;
}
// add rotation
var rotation = GetRotation(options);
drawables.Rotation(rotation);
// add the dynamic image to original image
var size = (ushort)TextMeasurer.MeasureSize(stringText, new TextOptions(font)).Width;
var img = new Image<Rgba32>(size + 15, options.Height);
img.Mutate(ctx => ctx.BackgroundColor(Color.White));
drawables.Draw(image);
Parallel.For(0, options.DrawLines, i =>
Parallel.For(0, options.DrawLines, _ =>
{
// ReSharper disable once AccessToDisposedClosure
if (image is { IsDisposed: false })
{
var x0 = random.Next(0, random.Next(0, 30));
var y0 = random.Next(10, img.Height);
var y0 = random.Next(10, image.Height);
var x1 = random.Next(30, img.Width);
var y1 = random.Next(0, img.Height);
var x1 = random.Next(30, image.Width);
var y1 = random.Next(0, image.Height);
img.Mutate(ctx =>
ctx.DrawLine(options.TextColor[random.Next(0, options.TextColor.Length)],
RandomTextGenerator.GenerateNextFloat(options.MinLineThickness, options.MaxLineThickness),
new PointF[] { new PointF(x0, y0), new PointF(x1, y1) })
);
});
img.Mutate(ctx => ctx.DrawImage(image, 0.80f));
image.Draw(new Drawables()
.StrokeColor(options.DrawLinesColor[random.Next(0, options.DrawLinesColor.Length)])
.StrokeWidth(RandomTextGenerator.GenerateNextFloat(options.MinLineThickness,
options.MaxLineThickness))
.Line(x0, y0, x1, y1));
}
});
Parallel.For(0, options.NoiseRate, _ =>
Parallel.For(0, options.NoiseRate, _ =>
{
if (image is { IsDisposed: false })
{
var x0 = random.Next(0, img.Width - 1);
var y0 = random.Next(0, img.Height - 1);
img.Mutate(
ctx => ctx
.DrawLine(options.NoiseRateColor[random.Next(0, options.NoiseRateColor.Length)],
RandomTextGenerator.GenerateNextFloat(0.5, 1.5),
new PointF[] { new Vector2(x0, y0), new Vector2(x0 + 0.005f, y0 + 0.005f) })
var x = random.Next(0, image.Width);
var y = random.Next(0, image.Height);
image.Draw(new Drawables()
.FillColor(options.NoiseRateColor[random.Next(0, options.NoiseRateColor.Length)])
.Point(x, y)
);
});
img.Mutate(x =>
{
x.Resize(options.Width, options.Height);
});
using (var ms = new MemoryStream())
{
img.Save(ms, options.Encoder);
result = ms.ToArray();
}
}
});
return result;
image.Resize(new MagickGeometry(options.Width, options.Height) { IgnoreAspectRatio = true });
return image.ToByteArray(options.Encoder);
}
private static AffineTransformBuilder GetRotation(CaptchaOptions options)
private double GetRotation(CaptchaOptions options)
{
var random = new Random();
var width = random.Next(10, options.Width);
var height = random.Next(10, options.Height);
var pointF = new PointF(width, height);
var rotationDegrees = random.Next(0, options.MaxRotationDegrees);
return new AffineTransformBuilder().PrependRotationDegrees(rotationDegrees, pointF);
return rotationDegrees;
}
}

@ -18,8 +18,7 @@
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" />
<PackageReference Include="Markdig.Signed" />
<PackageReference Include="HtmlSanitizer" />
<PackageReference Include="SixLabors.ImageSharp"/>
<PackageReference Include="SixLabors.ImageSharp.Drawing" />
<PackageReference Include="Magick.NET-Q16-AnyCPU" />
</ItemGroup>
<ItemGroup>

Loading…
Cancel
Save