// 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; } } }