forked from unoplatform/uno
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Inport initial implementation from PR unoplatform#1796
- Loading branch information
1 parent
09edbee
commit beaef22
Showing
13 changed files
with
473 additions
and
127 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
using System; | ||
using System.IO; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Android.Graphics.Pdf; | ||
using Android.OS; | ||
using Windows.Foundation; | ||
|
||
namespace Windows.Data.Pdf; | ||
|
||
public partial class PdfDocument : IDisposable | ||
{ | ||
private readonly PdfRenderer _pdfRenderer; | ||
|
||
private PdfDocument(PdfRenderer pdfRenderer) | ||
{ | ||
this._pdfRenderer = pdfRenderer ?? throw new ArgumentNullException(nameof(pdfRenderer)); | ||
} | ||
|
||
public bool IsPasswordProtected => false; | ||
|
||
public uint PageCount => (uint)_pdfRenderer.PageCount; | ||
|
||
public PdfPage GetPage(uint pageIndex) | ||
{ | ||
if (pageIndex >= _pdfRenderer.PageCount) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(pageIndex), $"In this document the page index cannot be {pageIndex}. Page count is {_pdfRenderer.PageCount}"); | ||
} | ||
|
||
var pdfPage = _pdfRenderer.OpenPage((int)pageIndex); | ||
return new PdfPage(pdfPage); | ||
} | ||
|
||
public static IAsyncOperation<PdfDocument> LoadFromFileAsync(Storage.IStorageFile file) | ||
{ | ||
return LoadFromFileAsync(file, null); | ||
} | ||
|
||
public static IAsyncOperation<PdfDocument> LoadFromFileAsync(Storage.IStorageFile file, string password) | ||
{ | ||
if (!string.IsNullOrEmpty(password)) | ||
{ | ||
// password protected PDF files are not supported by PdfRenderer in Android | ||
throw new NotImplementedException("The member IAsyncOperation<PdfDocument> PdfDocument.LoadFromFileAsync(IStorageFile file, string password) is not implemented in Uno."); | ||
} | ||
|
||
if (file is null) | ||
{ | ||
throw new ArgumentNullException(nameof(file)); | ||
} | ||
|
||
var localpath = file.Path; | ||
var fileDescriptor = localpath.StartsWith('/') | ||
? ParcelFileDescriptor.Open(new Java.IO.File(localpath), ParcelFileMode.ReadOnly) | ||
: Android.App.Application.Context.ContentResolver.OpenFileDescriptor(Android.Net.Uri.Parse(localpath), "r"); | ||
var pdfRenderer = new PdfRenderer(fileDescriptor); | ||
var pdfDocument = new PdfDocument(pdfRenderer); | ||
|
||
return Task.FromResult(pdfDocument).AsAsyncOperation(); | ||
} | ||
|
||
public static IAsyncOperation<PdfDocument> LoadFromStreamAsync(Storage.Streams.IRandomAccessStream inputStream) | ||
{ | ||
return LoadFromStreamAsync(inputStream, default); | ||
} | ||
|
||
public static IAsyncOperation<PdfDocument> LoadFromStreamAsync(Storage.Streams.IRandomAccessStream inputStream, string password) | ||
{ | ||
if (!string.IsNullOrEmpty(password)) | ||
{ | ||
// password protected PDF files are not supported by PdfRenderer in Android | ||
throw new NotImplementedException("The member IAsyncOperation<PdfDocument> PdfDocument.LoadFromStreamAsync(IRandomAccessStream inputStream, string password) is not implemented in Uno."); | ||
} | ||
return AsyncOperation.FromTask(async ct => | ||
{ | ||
var parcel = await GetParcelFileDescriptorFromStreamAsync(inputStream.AsStream(), ct); | ||
var pdfRenderer = new PdfRenderer(parcel); | ||
var pdfDocument = new PdfDocument(pdfRenderer); | ||
return pdfDocument; | ||
}); | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
_pdfRenderer.Dispose(); | ||
} | ||
|
||
private static Task<ParcelFileDescriptor> GetParcelFileDescriptorFromStreamAsync(Stream input, CancellationToken token) => | ||
Task.Run(() => | ||
{ | ||
/* PdfRenderer https://developer.android.com/reference/android/graphics/pdf/PdfRenderer#PdfRenderer(android.os.ParcelFileDescriptor) | ||
* PdfRenderer needs a seek-able ParcelFileDescriptor, | ||
* to get it from a stream (without using hacks), | ||
* you need to copy the stream to a temporary file. | ||
*/ | ||
var fileName = Path.GetTempFileName(); | ||
var file = new Java.IO.File(fileName); | ||
file.DeleteOnExit(); | ||
var outputStream = new Java.IO.FileOutputStream(file); | ||
byte[] buf = new byte[4096]; | ||
var len = 0; | ||
while (!token.IsCancellationRequested && (len = input.Read(buf, 0, buf.Length)) > 0) | ||
{ | ||
outputStream.Write(buf, 0, len); | ||
} | ||
outputStream.Flush(); | ||
outputStream.Close(); | ||
return ParcelFileDescriptor.Open(file, ParcelFileMode.ReadOnly); | ||
}, token); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
using System; | ||
using System.IO; | ||
using System.Threading.Tasks; | ||
using Android.Graphics; | ||
using Android.Graphics.Pdf; | ||
using Uno; | ||
using Windows.Foundation; | ||
using Windows.Storage.Streams; | ||
using Windows.UI; | ||
using Color = Windows.UI.Color; | ||
using Rect = Windows.Foundation.Rect; | ||
|
||
namespace Windows.Data.Pdf; | ||
|
||
public partial class PdfPage : IDisposable | ||
{ | ||
private readonly PdfRenderer.Page _pdfPage; | ||
// It is an empirical value found by rendering through RenderTargetBitmap | ||
// in png and comparing the dimensions of the images thus obtained. | ||
private const float pointToPixelFactor = 96.00000000f / 72.00000000f; | ||
|
||
internal PdfPage(PdfRenderer.Page pdfPage) | ||
{ | ||
_pdfPage = pdfPage ?? throw new ArgumentNullException(nameof(pdfPage)); | ||
} | ||
|
||
[NotImplemented("__ANDROID__")] | ||
public PdfPageDimensions Dimensions { get; } = new PdfPageDimensions(); | ||
|
||
public uint Index => (uint)_pdfPage.Index; | ||
|
||
public Size Size => new Size(Math.Ceiling(_pdfPage.Width * pointToPixelFactor) + double.Epsilon, Math.Ceiling(_pdfPage.Height * pointToPixelFactor) + double.Epsilon); | ||
|
||
public IAsyncAction RenderToStreamAsync(IRandomAccessStream outputStream) | ||
{ | ||
if (outputStream is null) | ||
{ | ||
throw new ArgumentNullException(nameof(outputStream)); | ||
} | ||
var size = Size; | ||
var options = new PdfPageRenderOptions | ||
{ | ||
DestinationWidth = (uint)size.Width, | ||
DestinationHeight = (uint)size.Height | ||
}; | ||
return RenderToStreamAsync(outputStream, options); | ||
} | ||
|
||
public IAsyncAction RenderToStreamAsync(IRandomAccessStream outputStream, PdfPageRenderOptions options) | ||
{ | ||
#region Validate arguments | ||
if (outputStream is null) | ||
{ | ||
throw new ArgumentNullException(nameof(outputStream)); | ||
} | ||
if (options is null) | ||
{ | ||
throw new ArgumentNullException(nameof(options)); | ||
} | ||
if (options.DestinationWidth > int.MaxValue) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(options), $"PdfPageRenderOptions.DestinationWidth = {options.DestinationWidth}. Must be less than or equal to int.MaxValue"); | ||
} | ||
if (options.DestinationHeight > int.MaxValue) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(options), $"PdfPageRenderOptions.DestinationHeight = {options.DestinationHeight}. Must be less than or equal to int.MaxValue"); | ||
} | ||
#endregion | ||
|
||
return Task.Run(() => | ||
{ | ||
const int quality = 100; | ||
var bitmap = RenderInternal(options); | ||
bitmap.Compress(Bitmap.CompressFormat.Png, quality, outputStream.AsStream()); | ||
return bitmap; | ||
}).AsAsyncAction(); | ||
} | ||
|
||
private Bitmap RenderInternal(PdfPageRenderOptions options) | ||
{ | ||
var destination = new Size(options.DestinationWidth, options.DestinationHeight); | ||
|
||
var sourceRect = options.SourceRect; | ||
#region handle 0-width and 0-height | ||
if (destination.Width == 0 && destination.Height == 0) | ||
{ | ||
// destination size not set - render with the page original size | ||
destination = Size; | ||
} | ||
else if (destination.Width == 0) | ||
{ | ||
// destination width not set - calculate it based on height proportion | ||
var scale = destination.Height / Size.Height; | ||
destination = new Size(Size.Width * scale, destination.Height); | ||
} | ||
else if (destination.Height == 0) | ||
{ | ||
// destination height not set - calculate it based on width proportion | ||
var scale = destination.Width / Size.Width; | ||
destination = new Size(destination.Width, Size.Height * scale); | ||
} | ||
#endregion | ||
|
||
#region scale according to DPI | ||
var di = Graphics.Display.DisplayInformation.GetForCurrentView(); | ||
var dpi = di.LogicalDpi / 96.0f; | ||
destination = new Size(destination.Width * dpi, destination.Height * dpi); | ||
#endregion | ||
|
||
#region create bitmap | ||
var bitmap = Bitmap.CreateBitmap((int)destination.Width, (int)destination.Height, Bitmap.Config.Argb8888); | ||
RectF destinationRect = new Rect(new Foundation.Point(), destination); | ||
#endregion | ||
|
||
#region fill with background color | ||
using (var canvas = new Canvas(bitmap)) | ||
{ | ||
var color = options.BackgroundColor.Equals(default(Color)) ? Colors.White : options.BackgroundColor; | ||
canvas.DrawRect(destinationRect, new Paint() | ||
{ | ||
Color = color | ||
}); | ||
} | ||
#endregion | ||
|
||
// Render content | ||
_pdfPage.Render(bitmap, null, null, PdfRenderMode.ForDisplay); | ||
|
||
if (sourceRect.Width > 0 && sourceRect.Height > 0) | ||
{ | ||
using var oldbitmap = bitmap; | ||
bitmap = Bitmap.CreateBitmap(oldbitmap, (int)sourceRect.X, (int)sourceRect.Y, (int)sourceRect.Width, (int)sourceRect.Height, null, false); | ||
} | ||
|
||
return bitmap; | ||
} | ||
|
||
public IAsyncAction PreparePageAsync() => | ||
Task.CompletedTask.AsAsyncAction(); | ||
|
||
public void Dispose() | ||
{ | ||
_pdfPage.Close(); | ||
_pdfPage.Dispose(); | ||
} | ||
} |
Oops, something went wrong.