Skip to content

Commit

Permalink
image control and reworking ColumnLength into adding onto Length
Browse files Browse the repository at this point in the history
[major]
Kinda F-ed up the commits in here because i was lazy ... anyways, plenty of fancy things happening in this, mostly untested because i did not yet bother to write the tests. Gotta go to sleep now tho and want to commit this to get a clean state again.
  • Loading branch information
Marco Silipo committed Nov 30, 2023
1 parent e075e96 commit 3a5e049
Show file tree
Hide file tree
Showing 22 changed files with 681 additions and 323 deletions.
8 changes: 7 additions & 1 deletion X39.Solutions.PdfTemplate.sln
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fonts", "fonts", "{AC1F6F58
test\fonts\Nunito_Sans\NunitoSans-VariableFont_YTLC,opsz,wdth,wght.ttf = test\fonts\Nunito_Sans\NunitoSans-VariableFont_YTLC,opsz,wdth,wght.ttf
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "images", "images", "{41BE3CC0-0C65-4D1A-AD6C-90B01D132C45}"
ProjectSection(SolutionItems) = preProject
test\images\X.jpg = test\images\X.jpg
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -47,6 +52,7 @@ Global
{C62AB41B-AC9D-4946-84D7-A22A63B28CF0} = {E290E22A-D697-4B30-A067-AF972686C3B5}
{54D29B97-100B-4494-BA96-DF9CBA107F8B} = {578183F3-B424-4D0B-9B6B-1F22FBDB80C8}
{B7C9C376-E1AA-46D0-90C6-D6BEAEB658EC} = {A16F2F3F-BF45-4A18-8E4A-31B800226E47}
{AC1F6F58-BADB-4BA3-80E6-A4F938455800} = {A16F2F3F-BF45-4A18-8E4A-31B800226E47}
{AC1F6F58-BADB-4BA3-80E6-A4F938455800} = {578183F3-B424-4D0B-9B6B-1F22FBDB80C8}
{41BE3CC0-0C65-4D1A-AD6C-90B01D132C45} = {578183F3-B424-4D0B-9B6B-1F22FBDB80C8}
EndGlobalSection
EndGlobal
8 changes: 8 additions & 0 deletions source/X39.Solutions.PdfTemplate/Abstraction/CanvasImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,12 @@ public void DrawBitmap(byte[] bytes, Rectangle rectangle)
canvas.DrawBitmap(bitmap, rectangle);
});
}

public void DrawBitmap(SKBitmap bitmap, Rectangle rectangle)
{
_drawActions.Add((canvas) =>
{
canvas.DrawBitmap(bitmap, rectangle);
});
}
}
7 changes: 7 additions & 0 deletions source/X39.Solutions.PdfTemplate/Abstraction/ICanvas.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,11 @@ public interface ICanvas
/// <param name="bitmap">The bitmap to draw.</param>
/// <param name="rectangle">The region to draw the bitmap into.</param>
void DrawBitmap(byte[] bitmap, Rectangle rectangle);

/// <summary>
/// Draws a bitmap on the canvas.
/// </summary>
/// <param name="bitmap">The SkiaSharp bitmap to draw.</param>
/// <param name="arrangementInner">The region to draw the bitmap into.</param>
void DrawBitmap(SKBitmap bitmap, Rectangle arrangementInner);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ namespace X39.Solutions.PdfTemplate.Controls.Base;
/// <summary>
/// Base class for alignable controls that can contain other controls.
/// </summary>
public abstract class AlignableContentControl : AlignableControl, IContentControl
[PublicAPI]
public abstract class AlignableContentControl : AlignableControl, IContentControl, IAsyncDisposable
{
private readonly List<IControl> _children = new();

/// <summary>
/// The children of this content control.
/// </summary>
Expand Down Expand Up @@ -43,4 +44,30 @@ public abstract class AlignableContentControl : AlignableControl, IContentContro

/// <inheritdoc />
public abstract bool CanAdd(Type type);

/// <summary>
/// Dispose pattern method, that can be overridden by derived classes.
/// </summary>
/// <remarks>
/// Always call the base implementation!
/// </remarks>
protected virtual async ValueTask DisposeAsyncCore()
{
foreach (var child in _children)
{
// ReSharper disable once SuspiciousTypeConversion.Global
if (child is IAsyncDisposable asyncDisposable)
await asyncDisposable.DisposeAsync().ConfigureAwait(false);
// ReSharper disable once SuspiciousTypeConversion.Global
else if (child is IDisposable disposable)
disposable.Dispose();
}
}

/// <inheritdoc />
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore();
GC.SuppressFinalize(this);
}
}
128 changes: 128 additions & 0 deletions source/X39.Solutions.PdfTemplate/Controls/ImageControl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using System.Globalization;
using SkiaSharp;
using X39.Solutions.PdfTemplate.Abstraction;
using X39.Solutions.PdfTemplate.Attributes;
using X39.Solutions.PdfTemplate.Controls.Base;
using X39.Solutions.PdfTemplate.Data;
using X39.Solutions.PdfTemplate.Services.ResourceResolver;

namespace X39.Solutions.PdfTemplate.Controls;

/// <summary>
/// A control that draws an image.
/// </summary>
[Control(Constants.ControlsNamespace)]
public sealed class ImageControl : AlignableControl, IInitializeAsync, IDisposable
{
private readonly IResourceResolver _resourceResolver;

/// <summary>
/// Creates a new <see cref="ImageControl"/>.
/// </summary>
/// <param name="resourceResolver">The <see cref="IResourceResolver"/> to use.</param>
public ImageControl(IResourceResolver resourceResolver)
{
_resourceResolver = resourceResolver;
}

/// <summary>
/// The source of the image to draw.
/// </summary>
/// <remarks>
/// This always has to be resolved, using a <see cref="IResourceResolver"/>.
/// The default implementation of <see cref="IResourceResolver"/> is <see cref="DefaultResourceResolver"/>.
/// It will only accept base64 encoded images for security reasons!
/// Make sure to provide your own <see cref="IResourceResolver"/> if you
/// want to use other sources, like a file path.
/// </remarks>
[Parameter]
public string Source { get; set; } = string.Empty;

/// <summary>
/// The width of the image.
/// </summary>
[Parameter]
public Length Width { get; set; } = new();

/// <summary>
/// The height of the image.
/// </summary>
[Parameter]
public Length Height { get; set; } = new();

private SKBitmap? _bitmap;

/// <inheritdoc />
public async Task InitializeAsync(CancellationToken cancellationToken = default)
{
var image = await _resourceResolver.ResolveImageAsync(Source, cancellationToken)
.ConfigureAwait(false);
_bitmap = SKBitmap.Decode(image);
}

/// <inheritdoc />
public void Dispose()
{
_bitmap?.Dispose();
}

/// <inheritdoc />
protected override Size DoMeasure(
float dpi,
in Size fullPageSize,
in Size framedPageSize,
in Size remainingSize,
CultureInfo cultureInfo)
{
var width = Width.ToPixels(framedPageSize.Width, dpi);
var bitmapWidth = _bitmap?.Width ?? 0F;
var height = Height.ToPixels(framedPageSize.Height, dpi);
var bitmapHeight = _bitmap?.Height ?? 0F;
return new Size(
Width.Unit is ELengthUnit.Auto
? Height.Unit is ELengthUnit.Auto
? bitmapWidth
: bitmapWidth / bitmapHeight * height
: width,
Height.Unit is ELengthUnit.Auto
? Width.Unit is ELengthUnit.Auto
? bitmapHeight
: bitmapHeight / bitmapWidth * width
: height
);
}

/// <inheritdoc />
protected override Size DoArrange(
float dpi,
in Size fullPageSize,
in Size framedPageSize,
in Size remainingSize,
CultureInfo cultureInfo)
{
var width = Width.ToPixels(framedPageSize.Width, dpi);
var bitmapWidth = _bitmap?.Width ?? 0F;
var height = Height.ToPixels(framedPageSize.Height, dpi);
var bitmapHeight = _bitmap?.Height ?? 0F;
return new Size(
Width.Unit is ELengthUnit.Auto
? Height.Unit is ELengthUnit.Auto
? bitmapWidth
: bitmapWidth / bitmapHeight * height
: width,
Height.Unit is ELengthUnit.Auto
? Width.Unit is ELengthUnit.Auto
? bitmapHeight
: bitmapHeight / bitmapWidth * width
: height
);
}

/// <inheritdoc />
protected override void DoRender(ICanvas canvas, float dpi, in Size parentSize, CultureInfo cultureInfo)
{
if (_bitmap is null)
return;
canvas.DrawBitmap(_bitmap, ArrangementInner - Arrangement);
}
}
50 changes: 26 additions & 24 deletions source/X39.Solutions.PdfTemplate/Controls/TableControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ HorizontalAlignment is EHorizontalAlignment.Stretch

var outWidths = CalculateWidths(
desiredTotalWidth,
dpi,
CellWidths.Values
.Select((q) => (q.columnLength, q.desiredWitdth))
.ToArray());
Expand All @@ -99,30 +100,26 @@ HorizontalAlignment is EHorizontalAlignment.Stretch

private static IReadOnlyCollection<float> CalculateWidths(
float totalWidth,
float dpi,
IReadOnlyCollection<(ColumnLength length, float desiredWidth)> columns)
{
var totalPixelWidth = columns
.Where((q) => q.length.Unit == EColumnUnit.Pixel)
.Select((q) => q.length.Value)
.DefaultIfEmpty()
.Sum();
var totalPercentWidth = columns
.Where((q) => q.length.Unit == EColumnUnit.Percent)
.Select((q) => q.length.Value * totalWidth)
var totalFixedWidth = columns
.Where((q) => q.length.Unit == EColumnUnit.Lenght && q.length.Length?.Unit != ELengthUnit.Auto)
.Select((q) => q.length.Length?.ToPixels(totalWidth, dpi))
.NotNull()
.DefaultIfEmpty()
.Sum();

var remainingWidth = totalWidth;
remainingWidth -= totalPixelWidth;
remainingWidth -= totalPercentWidth;
remainingWidth -= totalFixedWidth;

var totalParts = columns
.Where((q) => q.length.Unit == EColumnUnit.Part)
.Where((q) => q.length.Unit == EColumnUnit.Parts)
.Select((q) => q.length.Value)
.DefaultIfEmpty()
.Sum();
var totalAutoWidth = columns
.Where((q) => q.length.Unit == EColumnUnit.Auto)
.Where((q) => q.length is {Unit: EColumnUnit.Lenght, Length.Unit: ELengthUnit.Auto})
.Select((q) => q.desiredWidth)
.Sum();

Expand All @@ -138,10 +135,12 @@ private static IReadOnlyCollection<float> CalculateWidths(
{
outWidths[index] = length.Unit switch
{
EColumnUnit.Auto => desiredWidth * autoCoef,
EColumnUnit.Pixel => length.Value,
EColumnUnit.Part => partWidth * length.Value,
EColumnUnit.Percent => totalWidth * length.Value,
EColumnUnit.Parts => partWidth * length.Value ?? 0F,
EColumnUnit.Lenght => length.Length?.Unit switch
{
ELengthUnit.Auto => desiredWidth * autoCoef,
_ => length.Length?.ToPixels(totalWidth, dpi) ?? 0F,
},
_ => throw new InvalidEnumArgumentException(
nameof(length.Unit),
(int) length.Unit,
Expand All @@ -155,26 +154,28 @@ private static IReadOnlyCollection<float> CalculateWidths(
{
var max = totalWidth / columns.Count;
var larger = columns
.Where(x => x.length.Unit == EColumnUnit.Auto)
.Where(x => x.length is {Unit: EColumnUnit.Lenght, Length.Unit: ELengthUnit.Auto})
.Where(x => x.desiredWidth > max)
.DefaultIfEmpty()
.Sum(x => x.desiredWidth);
var remainingWidthWithoutLarger = remainingWidth - larger;
var remainingCount = columns
.Where(x => x.length.Unit == EColumnUnit.Auto)
.Where(x => x.length is {Unit: EColumnUnit.Lenght, Length.Unit: ELengthUnit.Auto})
.Count(x => x.desiredWidth <= max);
var newWidth = remainingWidthWithoutLarger / remainingCount;
var outWidths = new float[columns.Count];
foreach (var ((length, desiredWidth), index) in columns.Indexed())
{
outWidths[index] = length.Unit switch
{
EColumnUnit.Auto => desiredWidth > max || autoCoef < 1.0F
? desiredWidth * autoCoef
: newWidth,
EColumnUnit.Pixel => length.Value,
EColumnUnit.Part => 0F, // We don't have any parts in this branch
EColumnUnit.Percent => totalWidth * length.Value,
EColumnUnit.Parts => 0F, // We don't have any parts in this branch
EColumnUnit.Lenght => length.Length?.Unit switch
{
ELengthUnit.Auto => desiredWidth > max || autoCoef < 1.0F
? desiredWidth * autoCoef
: newWidth,
_ => length.Length?.ToPixels(totalWidth, dpi) ?? 0F,
},
_ => throw new InvalidEnumArgumentException(
nameof(length.Unit),
(int) length.Unit,
Expand All @@ -196,6 +197,7 @@ protected override Size DoArrange(
{
var outWidths = CalculateWidths(
framedPageSize.Width,
dpi,
CellWidths.Values
.Select((q) => (q.columnLength, q.desiredWitdth))
.ToArray());
Expand Down
Loading

0 comments on commit 3a5e049

Please sign in to comment.