// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices.ComTypes;
using System.Windows.Forms;
using Common;
using Common.Utilities;
using Microsoft.PowerToys.PreviewHandler.Pdf.Properties;
using Microsoft.PowerToys.PreviewHandler.Pdf.Telemetry.Events;
using Microsoft.PowerToys.Telemetry;
using Windows.Data.Pdf;
using Windows.Storage.Streams;
using Windows.UI.ViewManagement;
namespace Microsoft.PowerToys.PreviewHandler.Pdf
{
///
/// Win Form Implementation for Pdf Preview Handler.
///
public class PdfPreviewHandlerControl : FormHandlerControl
{
///
/// RichTextBox control to display error message.
///
private RichTextBox _infoBar;
///
/// FlowLayoutPanel control to display the image of the pdf.
///
private FlowLayoutPanel _flowLayoutPanel;
///
/// Use UISettings to get system colors and scroll bar size.
///
private static UISettings _uISettings = new UISettings();
///
/// Initializes a new instance of the class.
///
public PdfPreviewHandlerControl()
{
SetBackgroundColor(GetBackgroundColor());
}
///
/// Start the preview on the Control.
///
/// Stream reference to access source file.
public override void DoPreview(T dataSource)
{
this.SuspendLayout();
try
{
using (var dataStream = new ReadonlyStream(dataSource as IStream))
{
var memStream = new MemoryStream();
dataStream.CopyTo(memStream);
memStream.Position = 0;
try
{
// AsRandomAccessStream() extension method from System.Runtime.WindowsRuntime
var pdf = PdfDocument.LoadFromStreamAsync(memStream.AsRandomAccessStream()).GetAwaiter().GetResult();
if (pdf.PageCount > 0)
{
InvokeOnControlThread(() =>
{
_flowLayoutPanel = new FlowLayoutPanel
{
AutoScroll = true,
AutoSize = true,
Dock = DockStyle.Fill,
FlowDirection = FlowDirection.TopDown,
WrapContents = false,
};
_flowLayoutPanel.Resize += FlowLayoutPanel_Resize;
// Only show first 10 pages.
for (uint i = 0; i < pdf.PageCount && i < 10; i++)
{
using (var page = pdf.GetPage(i))
{
var image = PageToImage(page);
var picturePanel = new Panel()
{
Name = "picturePanel",
Margin = new Padding(6, 6, 6, 0),
Size = CalculateSize(image),
BorderStyle = BorderStyle.FixedSingle,
};
var picture = new PictureBox
{
Dock = DockStyle.Fill,
Image = image,
SizeMode = PictureBoxSizeMode.Zoom,
};
picturePanel.Controls.Add(picture);
_flowLayoutPanel.Controls.Add(picturePanel);
}
}
if (pdf.PageCount > 10)
{
var messageBox = new RichTextBox
{
Name = "messageBox",
Text = Resources.PdfMorePagesMessage,
BackColor = Color.LightYellow,
Dock = DockStyle.Fill,
Multiline = true,
ReadOnly = true,
ScrollBars = RichTextBoxScrollBars.None,
BorderStyle = BorderStyle.None,
};
messageBox.ContentsResized += RTBContentsResized;
_flowLayoutPanel.Controls.Add(messageBox);
}
Controls.Add(_flowLayoutPanel);
});
}
}
#pragma warning disable CA1031 // Password protected files throws an generic Exception
catch (Exception ex)
#pragma warning restore CA1031
{
if (ex.Message.Contains("Unable to update the password. The value provided as the current password is incorrect.", StringComparison.Ordinal))
{
InvokeOnControlThread(() =>
{
Controls.Clear();
_infoBar = GetTextBoxControl(Resources.PdfPasswordProtectedError);
Controls.Add(_infoBar);
});
}
else
{
throw;
}
}
finally
{
memStream.Dispose();
}
}
PowerToysTelemetry.Log.WriteEvent(new PdfFilePreviewed());
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
PowerToysTelemetry.Log.WriteEvent(new PdfFilePreviewError { Message = ex.Message });
InvokeOnControlThread(() =>
{
Controls.Clear();
_infoBar = GetTextBoxControl(Resources.PdfNotPreviewedError);
Controls.Add(_infoBar);
});
}
finally
{
base.DoPreview(dataSource);
}
this.ResumeLayout(false);
this.PerformLayout();
}
///
/// Resize the Panels on FlowLayoutPanel resize based on the size of the image.
///
/// sender (not used)
/// args (not used)
private void FlowLayoutPanel_Resize(object sender, EventArgs e)
{
this.SuspendLayout();
_flowLayoutPanel.SuspendLayout();
foreach (Panel panel in _flowLayoutPanel.Controls.Find("picturePanel", false))
{
var pictureBox = panel.Controls[0] as PictureBox;
var image = pictureBox.Image;
panel.Size = CalculateSize(image);
}
_flowLayoutPanel.ResumeLayout(false);
this.ResumeLayout(false);
}
///
/// Transform the PdfPage to an Image.
///
/// The page to transform to an Image.
/// An object of type
private Image PageToImage(PdfPage page)
{
Image imageOfPage;
using (var stream = new InMemoryRandomAccessStream())
{
page.RenderToStreamAsync(stream, new PdfPageRenderOptions()
{
DestinationWidth = (uint)this.ClientSize.Width,
}).GetAwaiter().GetResult();
imageOfPage = Image.FromStream(stream.AsStream());
}
return imageOfPage;
}
///
/// Calculate the size of the control based on the size of the image/pdf page.
///
/// Image of pdf page.
/// New size off the panel.
private Size CalculateSize(Image pdfImage)
{
var hasScrollBar = _flowLayoutPanel.VerticalScroll.Visible;
// Add 12px margin to the image by making it 12px smaller.
int width = this.ClientSize.Width - 12;
// If the vertical scroll bar is visible, make the image smaller.
var scrollBarSizeWidth = (int)_uISettings.ScrollBarSize.Width;
if (hasScrollBar && width > scrollBarSizeWidth)
{
width -= scrollBarSizeWidth;
}
int originalWidth = pdfImage.Width;
int originalHeight = pdfImage.Height;
float percentWidth = (float)width / originalWidth;
int newHeight = (int)(originalHeight * percentWidth);
return new Size(width, newHeight);
}
///
/// Get the system background color, based on the selected theme.
///
/// An object of type .
private static Color GetBackgroundColor()
{
var systemBackgroundColor = _uISettings.GetColorValue(UIColorType.Background);
return Color.FromArgb(systemBackgroundColor.A, systemBackgroundColor.R, systemBackgroundColor.G, systemBackgroundColor.B);
}
///
/// Gets a textbox control.
///
/// Message to be displayed in textbox.
/// An object of type .
private RichTextBox GetTextBoxControl(string message)
{
var textBox = new RichTextBox
{
Text = message,
BackColor = Color.LightYellow,
Multiline = true,
Dock = DockStyle.Top,
ReadOnly = true,
ScrollBars = RichTextBoxScrollBars.None,
BorderStyle = BorderStyle.None,
};
textBox.ContentsResized += RTBContentsResized;
return textBox;
}
///
/// Callback when RichTextBox is resized.
///
/// Reference to resized control.
/// Provides data for the resize event.
private void RTBContentsResized(object sender, ContentsResizedEventArgs e)
{
var richTextBox = (RichTextBox)sender;
// Add 5px extra height to the textbox.
richTextBox.Height = e.NewRectangle.Height + 5;
}
}
}