Skip to content

Commit

Permalink
Add DpiRelativeToBounds option
Browse files Browse the repository at this point in the history
  • Loading branch information
sungaila committed Jun 25, 2024
1 parent 47d348c commit 779fece
Show file tree
Hide file tree
Showing 18 changed files with 174 additions and 93 deletions.
94 changes: 43 additions & 51 deletions src/PDFtoImage/Conversion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,10 +325,6 @@ public static SKBitmap ToImage(Stream pdfStream, bool leaveOpen = false, string?
if (options == default)
options = new();

// correct the width and height for the given dpi
// but only if both width and height are not specified (so the original sizes are corrected)
var correctFromDpi = options.Width == null && options.Height == null;

NativeMethods.FPDF renderFlags = default;

if (options.WithAnnotations)
Expand All @@ -347,16 +343,21 @@ public static SKBitmap ToImage(Stream pdfStream, bool leaveOpen = false, string?
if (page >= pdfDocument.PageSizes.Count)
throw new ArgumentOutOfRangeException(nameof(page), $"The page number {page} does not exist. Highest page number available is {pdfDocument.PageSizes.Count - 1}.");

var currentWidth = (float?)options.Width;
var currentHeight = (float?)options.Height;
var pageSize = pdfDocument.PageSizes[page];

// correct aspect ratio if requested
if (options.WithAspectRatio)
AdjustForAspectRatio(ref currentWidth, ref currentHeight, pageSize);

// Internals.PdfDocument -> Image
return pdfDocument.Render(page, currentWidth ?? pageSize.Width, currentHeight ?? pageSize.Height, options.Dpi, options.Dpi, options.Rotation, renderFlags, options.WithFormFill, correctFromDpi, options.BackgroundColor ?? SKColors.White, options.Bounds, options.UseTiling);
return pdfDocument.Render(
page,
options.Width,
options.Height,
options.Dpi,
options.Dpi,
options.Rotation,
renderFlags,
options.WithFormFill,
options.BackgroundColor ?? SKColors.White,
options.Bounds,
options.UseTiling,
options.WithAspectRatio,
options.DpiRelativeToBounds);
}

/// <summary>
Expand Down Expand Up @@ -564,10 +565,6 @@ public static IEnumerable<SKBitmap> ToImages(Stream pdfStream, bool leaveOpen =
if (options == default)
options = new();

// correct the width and height for the given dpi
// but only if both width and height are not specified (so the original sizes are corrected)
var correctFromDpi = options.Width == null && options.Height == null;

NativeMethods.FPDF renderFlags = default;

if (options.WithAnnotations)
Expand All @@ -585,16 +582,21 @@ public static IEnumerable<SKBitmap> ToImages(Stream pdfStream, bool leaveOpen =

for (int i = 0; i < pdfDocument.PageSizes.Count; i++)
{
var currentWidth = (float?)options.Width;
var currentHeight = (float?)options.Height;
var pageSize = pdfDocument.PageSizes[i];

// correct aspect ratio if requested
if (options.WithAspectRatio)
AdjustForAspectRatio(ref currentWidth, ref currentHeight, pageSize);

// Internals.PdfDocument -> Image
yield return pdfDocument.Render(i, currentWidth ?? pageSize.Width, currentHeight ?? pageSize.Height, options.Dpi, options.Dpi, options.Rotation, renderFlags, options.WithFormFill, correctFromDpi, options.BackgroundColor ?? SKColors.White, options.Bounds, options.UseTiling);
yield return pdfDocument.Render(
i,
options.Width,
options.Height,
options.Dpi,
options.Dpi,
options.Rotation,
renderFlags,
options.WithFormFill,
options.BackgroundColor ?? SKColors.White,
options.Bounds,
options.UseTiling,
options.WithAspectRatio,
options.DpiRelativeToBounds);
}
}

Expand Down Expand Up @@ -657,10 +659,6 @@ public static async IAsyncEnumerable<SKBitmap> ToImagesAsync(Stream pdfStream, b
if (options == default)
options = new();

// correct the width and height for the given dpi
// but only if both width and height are not specified (so the original sizes are corrected)
var correctFromDpi = options.Width == null && options.Height == null;

NativeMethods.FPDF renderFlags = default;

if (options.WithAnnotations)
Expand All @@ -680,16 +678,22 @@ public static async IAsyncEnumerable<SKBitmap> ToImagesAsync(Stream pdfStream, b
{
cancellationToken.ThrowIfCancellationRequested();

var currentWidth = (float?)options.Width;
var currentHeight = (float?)options.Height;
var pageSize = pdfDocument.PageSizes[i];

// correct aspect ratio if requested
if (options.WithAspectRatio)
AdjustForAspectRatio(ref currentWidth, ref currentHeight, pageSize);

// Internals.PdfDocument -> Image
yield return await Task.Run(() => pdfDocument.Render(i, currentWidth ?? pageSize.Width, currentHeight ?? pageSize.Height, options.Dpi, options.Dpi, options.Rotation, renderFlags, options.WithFormFill, correctFromDpi, options.BackgroundColor ?? SKColors.White, options.Bounds, options.UseTiling, cancellationToken), cancellationToken);
yield return await Task.Run(() => pdfDocument.Render(
i,
options.Width,
options.Height,
options.Dpi,
options.Dpi,
options.Rotation,
renderFlags,
options.WithFormFill,
options.BackgroundColor ?? SKColors.White,
options.Bounds,
options.UseTiling,
options.WithAspectRatio,
options.DpiRelativeToBounds,
cancellationToken), cancellationToken);
}
}
#endif
Expand Down Expand Up @@ -741,17 +745,5 @@ internal static void SaveImpl(Stream stream, SKEncodedImageFormat format, Stream
using var bitmap = ToImage(pdfStream, leaveOpen, password, page, options);
bitmap.Encode(stream, format, 100);
}

private static void AdjustForAspectRatio(ref float? width, ref float? height, SizeF pageSize)
{
if (width == null && height != null)
{
width = pageSize.Width / pageSize.Height * height.Value;
}
else if (width != null && height == null)
{
height = pageSize.Height / pageSize.Width * width.Value;
}
}
}
}
7 changes: 6 additions & 1 deletion src/PDFtoImage/IRenderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,18 @@ public interface IRenderOptions
SKColor? BackgroundColor { get; init; }

/// <summary>
/// Specifies the bounds for the page relative to <see cref="Conversion.GetPageSizes(string,string)"/>. This can be used for clipping (bounds inside of page) or additional margins (bounds outside of page).
/// Specifies the bounds for the page relative to <see cref="Conversion.GetPageSizes(string,string)"/>. This can be used for clipping (bounds inside of page) or additional margins (bounds outside of page). The bound units are relative to the PDF size (at 72 DPI).
/// </summary>
RectangleF? Bounds { get; init; }

/// <summary>
/// Specifies that the PDF should be rendered as several segments and merged into the final image. This can help in cases where the output image is too large, causing corrupted images (e.g. missing text) or crashes.
/// </summary>
bool UseTiling { get; init; }

/// <summary>
/// Specifies that <see cref="Dpi"/> and <see cref="WithAspectRatio"/> will be calculated relative to <see cref="Bounds"/> instead of the original PDF.
/// </summary>
bool DpiRelativeToBounds { get; init; }
}
}
77 changes: 55 additions & 22 deletions src/PDFtoImage/Internals/PdfDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ namespace PDFtoImage.Internals
/// <summary>
/// Provides functionality to render a PDF document.
/// </summary>
internal sealed class PdfDocument : IDisposable
internal struct PdfDocument : IDisposable
{
private bool _disposed;
private PdfFile? _file;
private PdfFile _file;

/// <summary>
/// Initializes a new instance of the PdfDocument class with the provided stream.
Expand Down Expand Up @@ -50,31 +50,53 @@ private PdfDocument(Stream stream, string? password, bool disposeStream)
private const int MaxTileWidth = 4000;
private const int MaxTileHeight = 4000;

/// <summary>
/// Renders a page of the PDF document to an image.
/// </summary>
/// <param name="page">Number of the page to render.</param>
/// <param name="width">Width of the rendered image.</param>
/// <param name="height">Height of the rendered image.</param>
/// <param name="dpiX">Horizontal DPI.</param>
/// <param name="dpiY">Vertical DPI.</param>
/// <param name="rotate">Rotation.</param>
/// <param name="flags">Flags used to influence the rendering.</param>
/// <param name="renderFormFill">Render form fills.</param>
/// <param name="correctFromDpi">Change <paramref name="width"/> and <paramref name="height"/> depending on the given <paramref name="dpiX"/> and <paramref name="dpiY"/>.</param>
/// <param name="backgroundColor">The background color used for the output.</param>
/// <param name="bounds">Specifies the bounds for the page relative to <see cref="Conversion.GetPageSizes(string,string)"/>. This can be used for clipping (bounds inside of page) or additional margins (bounds outside of page).</param>
/// <param name="useTiling"></param>
/// <param name="cancellationToken"></param>
/// <returns>The rendered image.</returns>
public SKBitmap Render(int page, float width, float height, float dpiX, float dpiY, PdfRotation rotate, NativeMethods.FPDF flags, bool renderFormFill, bool correctFromDpi, SKColor backgroundColor, RectangleF? bounds, bool useTiling, CancellationToken cancellationToken = default)
public readonly SKBitmap Render(int page, float? requestedWidth, float? requestedHeight, float dpiX, float dpiY, PdfRotation rotate, NativeMethods.FPDF flags, bool renderFormFill, SKColor backgroundColor, RectangleF? bounds, bool useTiling, bool withAspectRatio, bool dpiRelativeToBounds, CancellationToken cancellationToken = default)
{
if (_disposed)
throw new ObjectDisposedException(GetType().Name);

// correct the width and height for the given dpi
// but only if both width and height are not specified (so the original sizes are corrected)
var correctFromDpi = requestedWidth == null && requestedHeight == null;

var originalWidth = PageSizes[page].Width;
var originalHeight = PageSizes[page].Height;

if (withAspectRatio && !(dpiRelativeToBounds && bounds.HasValue))
{
AdjustForAspectRatio(ref requestedWidth, ref requestedHeight, PageSizes[page]);
}

float width = requestedWidth ?? originalWidth;
float height = requestedHeight ?? originalHeight;

if (dpiRelativeToBounds && bounds.HasValue)
{
float? boundsWidth = requestedWidth != null ? requestedWidth : null;
float? boundsHeight = requestedHeight != null ? requestedHeight : null;

if (withAspectRatio)
{
AdjustForAspectRatio(ref boundsWidth, ref boundsHeight, new SizeF(bounds.Value.Width, bounds.Value.Height));
}

if (requestedWidth == null)
{
width = boundsWidth ?? bounds.Value.Width;
}

if (requestedHeight == null)
{
height = boundsHeight ?? bounds.Value.Height;
}

bounds = new RectangleF(
bounds.Value.X * (width / originalWidth),
bounds.Value.Y * (height / originalHeight),
bounds.Value.Width,
bounds.Value.Height);
}

if (rotate == PdfRotation.Rotate90 || rotate == PdfRotation.Rotate270)
{
(width, height) = (height, width);
Expand Down Expand Up @@ -200,6 +222,18 @@ public SKBitmap Render(int page, float width, float height, float dpiX, float dp
return bitmap;
}

private static void AdjustForAspectRatio(ref float? width, ref float? height, SizeF pageSize)
{
if (width == null && height != null)
{
width = pageSize.Width / pageSize.Height * height.Value;
}
else if (width != null && height == null)
{
height = pageSize.Height / pageSize.Width * width.Value;
}
}

private static SKBitmap RenderSubset(PdfFile file, int page, float width, float height, PdfRotation rotate, NativeMethods.FPDF flags, bool renderFormFill, SKColor backgroundColor, RectangleF? bounds, float originalWidth, float originalHeight, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
Expand Down Expand Up @@ -261,8 +295,7 @@ private void Dispose(bool disposing)
{
if (!_disposed && disposing)
{
_file?.Dispose();
_file = null;
_file.Dispose();

_disposed = true;
}
Expand Down
7 changes: 3 additions & 4 deletions src/PDFtoImage/Internals/PdfFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace PDFtoImage.Internals
{
Expand All @@ -13,7 +12,7 @@ namespace PDFtoImage.Internals
#pragma warning disable IDE0056 // Use index operator
#pragma warning disable IDE0057 // Use range operator
#endif
internal sealed class PdfFile : IDisposable
internal struct PdfFile : IDisposable
{
private IntPtr _document;
private IntPtr _form;
Expand Down Expand Up @@ -53,7 +52,7 @@ public PdfFile(Stream stream, string? password, bool disposeStream)
_disposeStream=disposeStream;
}

public bool RenderPDFPageToBitmap(int pageNumber, IntPtr bitmapHandle, int boundsOriginX, int boundsOriginY, int boundsWidth, int boundsHeight, int rotate, NativeMethods.FPDF flags, bool renderFormFill)
public readonly bool RenderPDFPageToBitmap(int pageNumber, IntPtr bitmapHandle, int boundsOriginX, int boundsOriginY, int boundsWidth, int boundsHeight, int rotate, NativeMethods.FPDF flags, bool renderFormFill)
{
if (_disposed)
throw new ObjectDisposedException(GetType().Name);
Expand Down Expand Up @@ -87,7 +86,7 @@ public List<SizeF> GetPDFDocInfo()
return result;
}

public SizeF GetPDFDocInfo(int pageNumber)
public readonly SizeF GetPDFDocInfo(int pageNumber)
{
NativeMethods.FPDF_GetPageSizeByIndex(_document, pageNumber, out double width, out double height);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ PDFtoImage.RenderOptions.Bounds.get -> System.Drawing.RectangleF?
PDFtoImage.RenderOptions.Bounds.init -> void
PDFtoImage.RenderOptions.Dpi.get -> int
PDFtoImage.RenderOptions.Dpi.init -> void
PDFtoImage.RenderOptions.DpiRelativeToBounds.get -> bool
PDFtoImage.RenderOptions.DpiRelativeToBounds.init -> void
PDFtoImage.RenderOptions.Height.get -> int?
PDFtoImage.RenderOptions.Height.init -> void
PDFtoImage.RenderOptions.RenderOptions() -> void
PDFtoImage.RenderOptions.RenderOptions(int Dpi = 300, int? Width = null, int? Height = null, bool WithAnnotations = false, bool WithFormFill = false, bool WithAspectRatio = false, PDFtoImage.PdfRotation Rotation = PDFtoImage.PdfRotation.Rotate0, PDFtoImage.PdfAntiAliasing AntiAliasing = PDFtoImage.PdfAntiAliasing.All, SkiaSharp.SKColor? BackgroundColor = null, System.Drawing.RectangleF? Bounds = null, bool UseTiling = false) -> void
PDFtoImage.RenderOptions.RenderOptions(int Dpi = 300, int? Width = null, int? Height = null, bool WithAnnotations = false, bool WithFormFill = false, bool WithAspectRatio = false, PDFtoImage.PdfRotation Rotation = PDFtoImage.PdfRotation.Rotate0, PDFtoImage.PdfAntiAliasing AntiAliasing = PDFtoImage.PdfAntiAliasing.All, SkiaSharp.SKColor? BackgroundColor = null, System.Drawing.RectangleF? Bounds = null, bool UseTiling = false, bool DpiRelativeToBounds = false) -> void
PDFtoImage.RenderOptions.Rotation.get -> PDFtoImage.PdfRotation
PDFtoImage.RenderOptions.Rotation.init -> void
PDFtoImage.RenderOptions.Width.get -> int?
Expand All @@ -33,6 +35,8 @@ PDFtoImage.IRenderOptions.Bounds.get -> System.Drawing.RectangleF?
PDFtoImage.IRenderOptions.Bounds.init -> void
PDFtoImage.IRenderOptions.Dpi.get -> int
PDFtoImage.IRenderOptions.Dpi.init -> void
PDFtoImage.IRenderOptions.DpiRelativeToBounds.get -> bool
PDFtoImage.IRenderOptions.DpiRelativeToBounds.init -> void
PDFtoImage.IRenderOptions.Height.get -> int?
PDFtoImage.IRenderOptions.Height.init -> void
PDFtoImage.IRenderOptions.Rotation.get -> PDFtoImage.PdfRotation
Expand Down
6 changes: 5 additions & 1 deletion src/PDFtoImage/PublicAPI/net462/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ PDFtoImage.RenderOptions.Bounds.get -> System.Drawing.RectangleF?
PDFtoImage.RenderOptions.Bounds.init -> void
PDFtoImage.RenderOptions.Dpi.get -> int
PDFtoImage.RenderOptions.Dpi.init -> void
PDFtoImage.RenderOptions.DpiRelativeToBounds.get -> bool
PDFtoImage.RenderOptions.DpiRelativeToBounds.init -> void
PDFtoImage.RenderOptions.Height.get -> int?
PDFtoImage.RenderOptions.Height.init -> void
PDFtoImage.RenderOptions.RenderOptions() -> void
PDFtoImage.RenderOptions.RenderOptions(int Dpi = 300, int? Width = null, int? Height = null, bool WithAnnotations = false, bool WithFormFill = false, bool WithAspectRatio = false, PDFtoImage.PdfRotation Rotation = PDFtoImage.PdfRotation.Rotate0, PDFtoImage.PdfAntiAliasing AntiAliasing = PDFtoImage.PdfAntiAliasing.All, SkiaSharp.SKColor? BackgroundColor = null, System.Drawing.RectangleF? Bounds = null, bool UseTiling = false) -> void
PDFtoImage.RenderOptions.RenderOptions(int Dpi = 300, int? Width = null, int? Height = null, bool WithAnnotations = false, bool WithFormFill = false, bool WithAspectRatio = false, PDFtoImage.PdfRotation Rotation = PDFtoImage.PdfRotation.Rotate0, PDFtoImage.PdfAntiAliasing AntiAliasing = PDFtoImage.PdfAntiAliasing.All, SkiaSharp.SKColor? BackgroundColor = null, System.Drawing.RectangleF? Bounds = null, bool UseTiling = false, bool DpiRelativeToBounds = false) -> void
PDFtoImage.RenderOptions.Rotation.get -> PDFtoImage.PdfRotation
PDFtoImage.RenderOptions.Rotation.init -> void
PDFtoImage.RenderOptions.Width.get -> int?
Expand All @@ -33,6 +35,8 @@ PDFtoImage.IRenderOptions.Bounds.get -> System.Drawing.RectangleF?
PDFtoImage.IRenderOptions.Bounds.init -> void
PDFtoImage.IRenderOptions.Dpi.get -> int
PDFtoImage.IRenderOptions.Dpi.init -> void
PDFtoImage.IRenderOptions.DpiRelativeToBounds.get -> bool
PDFtoImage.IRenderOptions.DpiRelativeToBounds.init -> void
PDFtoImage.IRenderOptions.Height.get -> int?
PDFtoImage.IRenderOptions.Height.init -> void
PDFtoImage.IRenderOptions.Rotation.get -> PDFtoImage.PdfRotation
Expand Down
Loading

0 comments on commit 779fece

Please sign in to comment.