Use own Pdf viewer implementation. wip on universal InfoPanel

This commit is contained in:
Paddy Xu
2017-04-16 01:18:54 +03:00
parent 7388c3874a
commit 431cf1f014
35 changed files with 2894 additions and 213 deletions

View File

@@ -1,40 +0,0 @@
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using MoonPdfLib;
using QuickLook.ExtensionMethods;
namespace QuickLook.Plugin.PDFViewer
{
public class Class1 : IViewer
{
public PluginType Type => PluginType.ByExtension | PluginType.ByContent;
public string[] SupportExtensions => new[] {".pdf"};
public bool CheckSupportByContent(byte[] sample)
{
return Encoding.ASCII.GetString(sample.Take(4).ToArray()) == "%PDF";
}
public void View(string path, ViewContentContainer container)
{
var pdfPanel = new MoonPdfPanel
{
ViewType = ViewType.SinglePage,
PageRowDisplay = PageRowDisplayType.ContinuousPageRows,
PageMargin = new Thickness(0, 2, 4, 2),
Background = new SolidColorBrush(Colors.LightGray)
};
container.SetContent(pdfPanel);
container.Dispatcher.Delay(100, o => pdfPanel.OpenFile(path));
//container.Dispatcher.Delay(200, o => pdfPanel.ZoomToWidth());
}
public void Close()
{
}
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Drawing;
using System.Runtime.InteropServices;
namespace QuickLook.Plugin.PDFViewer
{
internal static class DpiHelper
{
public const float DEFAULT_DPI = 96;
public static Dpi GetCurrentDpi()
{
Graphics g = Graphics.FromHwnd(IntPtr.Zero);
IntPtr desktop = g.GetHdc();
var dpi = new Dpi
{
HorizontalDpi = GetDeviceCaps(desktop, (int) DeviceCap.LOGPIXELSX),
VerticalDpi = GetDeviceCaps(desktop, (int) DeviceCap.LOGPIXELSY)
};
return dpi;
}
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern int GetDeviceCaps(IntPtr hDC, int nIndex);
public enum DeviceCap
{
/// <summary>
/// Logical pixels inch in X
/// </summary>
LOGPIXELSX = 88,
/// <summary>
/// Logical pixels inch in Y
/// </summary>
LOGPIXELSY = 90
}
}
internal class Dpi
{
public float HorizontalDpi { get; set; }
public float VerticalDpi { get; set; }
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace QuickLook.Plugin.PDFViewer
{
public static class Extensions
{
public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
foreach (var item in enumeration)
action(item);
}
public static T GetDescendantByType<T>(this Visual element) where T : class
{
if (element == null)
return default(T);
if (element.GetType() == typeof(T))
return element as T;
T foundElement = null;
(element as FrameworkElement)?.ApplyTemplate();
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var visual = VisualTreeHelper.GetChild(element, i) as Visual;
foundElement = visual.GetDescendantByType<T>();
if (foundElement != null)
break;
}
return foundElement;
}
[DllImport("gdi32")]
private static extern int DeleteObject(IntPtr o);
public static BitmapSource ToBitmapSource(this Bitmap source)
{
var ip = source.GetHbitmap();
BitmapSource bs = null;
try
{
var data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height),
ImageLockMode.ReadOnly, source.PixelFormat);
bs = BitmapSource.Create(source.Width, source.Height, source.HorizontalResolution,
source.VerticalResolution, PixelFormats.Bgr24, null,
data.Scan0, data.Stride * source.Height, data.Stride);
source.UnlockBits(data);
bs.Freeze();
}
finally
{
DeleteObject(ip);
}
return bs;
}
}
}

View File

@@ -0,0 +1,181 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace QuickLook.Plugin.PDFViewer
{
internal class LibMuPdf
{
public static Bitmap RenderPage(IntPtr context, IntPtr document, IntPtr page, double zoomFactor)
{
var pageBound = new Rectangle();
var ctm = new Matrix();
var pix = IntPtr.Zero;
var dev = IntPtr.Zero;
NativeMethods.BoundPage(document, page, ref pageBound);
var currentDpi = DpiHelper.GetCurrentDpi();
var zoomX = zoomFactor * (currentDpi.HorizontalDpi / DpiHelper.DEFAULT_DPI);
var zoomY = zoomFactor * (currentDpi.VerticalDpi / DpiHelper.DEFAULT_DPI);
// gets the size of the page and multiplies it with zoom factors
var width = (int) (pageBound.Width * zoomX);
var height = (int) (pageBound.Height * zoomY);
// sets the matrix as a scaling matrix (zoomX,0,0,zoomY,0,0)
ctm.A = (float)zoomX;
ctm.D = (float)zoomY;
// creates a pixmap the same size as the width and height of the page
pix = NativeMethods.NewPixmap(context, NativeMethods.LookupDeviceColorSpace(context, "DeviceRGB"), width,
height);
// sets white color as the background color of the pixmap
NativeMethods.ClearPixmap(context, pix, 0xFF);
// creates a drawing device
dev = NativeMethods.NewDrawDevice(context, pix);
// draws the page on the device created from the pixmap
NativeMethods.RunPage(document, page, dev, ref ctm, IntPtr.Zero);
NativeMethods.FreeDevice(dev); // frees the resources consumed by the device
dev = IntPtr.Zero;
// creates a colorful bitmap of the same size of the pixmap
var bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
var imageData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, width, height), ImageLockMode.ReadWrite,
bmp.PixelFormat);
unsafe
{
// converts the pixmap data to Bitmap data
var ptrSrc = (byte*) NativeMethods.GetSamples(context, pix); // gets the rendered data from the pixmap
var ptrDest = (byte*) imageData.Scan0;
for (var y = 0; y < height; y++)
{
var pl = ptrDest;
var sl = ptrSrc;
for (var x = 0; x < width; x++)
{
//Swap these here instead of in MuPDF because most pdf images will be rgb or cmyk.
//Since we are going through the pixels one by one anyway swap here to save a conversion from rgb to bgr.
pl[2] = sl[0]; //b-r
pl[1] = sl[1]; //g-g
pl[0] = sl[2]; //r-b
//sl[3] is the alpha channel, we will skip it here
pl += 3;
sl += 4;
}
ptrDest += imageData.Stride;
ptrSrc += width * 4;
}
}
bmp.UnlockBits(imageData);
NativeMethods.DropPixmap(context, pix);
bmp.SetResolution(currentDpi.HorizontalDpi, currentDpi.VerticalDpi);
return bmp;
}
public struct Rectangle
{
public float Left, Top, Right, Bottom;
public float Width => Right - Left;
public float Height => Bottom - Top;
}
public struct Matrix
{
public float A, B, C, D, E, F;
public Matrix(float a, float b, float c, float d, float e, float f)
{
A = a;
B = b;
C = c;
D = d;
E = e;
F = f;
}
}
internal struct NativePage
{
public Matrix Ctm;
public Rectangle MediaBox;
public int Rotate;
}
internal class NativeMethods
{
private const uint FZ_STORE_DEFAULT = 256 << 20;
private const string DLL = "libmupdf.dll";
// please modify the version number to match the FZ_VERSION definition in "fitz\version.h" file
private const string MuPDFVersion = "1.6";
[DllImport(DLL, EntryPoint = "fz_new_context_imp", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr NewContext(IntPtr alloc, IntPtr locks, uint max_store, string version);
public static IntPtr NewContext()
{
return NewContext(IntPtr.Zero, IntPtr.Zero, FZ_STORE_DEFAULT, MuPDFVersion);
}
[DllImport(DLL, EntryPoint = "fz_free_context", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr FreeContext(IntPtr ctx);
[DllImport(DLL, EntryPoint = "fz_open_file_w", CharSet = CharSet.Unicode,
CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr OpenFile(IntPtr ctx, string fileName);
[DllImport(DLL, EntryPoint = "pdf_open_document_with_stream", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr OpenDocumentStream(IntPtr ctx, IntPtr stm);
[DllImport(DLL, EntryPoint = "fz_close", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr CloseStream(IntPtr stm);
[DllImport(DLL, EntryPoint = "pdf_close_document", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr CloseDocument(IntPtr doc);
[DllImport(DLL, EntryPoint = "pdf_count_pages", CallingConvention = CallingConvention.Cdecl)]
public static extern int CountPages(IntPtr doc);
[DllImport(DLL, EntryPoint = "pdf_bound_page", CallingConvention = CallingConvention.Cdecl)]
public static extern void BoundPage(IntPtr doc, IntPtr page, ref Rectangle bound);
[DllImport(DLL, EntryPoint = "fz_clear_pixmap_with_value", CallingConvention = CallingConvention.Cdecl)]
public static extern void ClearPixmap(IntPtr ctx, IntPtr pix, int byteValue);
[DllImport(DLL, EntryPoint = "fz_lookup_device_colorspace", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr LookupDeviceColorSpace(IntPtr ctx, string colorspace);
[DllImport(DLL, EntryPoint = "fz_free_device", CallingConvention = CallingConvention.Cdecl)]
public static extern void FreeDevice(IntPtr dev);
[DllImport(DLL, EntryPoint = "pdf_free_page", CallingConvention = CallingConvention.Cdecl)]
public static extern void FreePage(IntPtr doc, IntPtr page);
[DllImport(DLL, EntryPoint = "pdf_load_page", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr LoadPage(IntPtr doc, int pageNumber);
[DllImport(DLL, EntryPoint = "fz_new_draw_device", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr NewDrawDevice(IntPtr ctx, IntPtr pix);
[DllImport(DLL, EntryPoint = "fz_new_pixmap", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr NewPixmap(IntPtr ctx, IntPtr colorspace, int width, int height);
[DllImport(DLL, EntryPoint = "pdf_run_page", CallingConvention = CallingConvention.Cdecl)]
public static extern void RunPage(IntPtr doc, IntPtr page, IntPtr dev, ref Matrix transform, IntPtr cookie);
[DllImport(DLL, EntryPoint = "fz_drop_pixmap", CallingConvention = CallingConvention.Cdecl)]
public static extern void DropPixmap(IntPtr ctx, IntPtr pix);
[DllImport(DLL, EntryPoint = "fz_pixmap_samples", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr GetSamples(IntPtr ctx, IntPtr pix);
}
}
}

View File

@@ -0,0 +1,113 @@
using System;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
namespace QuickLook.Plugin.PDFViewer
{
public sealed class PreviewMouseWheelMonitor : IDisposable
{
private readonly UIElement _canvas;
private readonly Dispatcher _dispatcher;
private readonly int _sensitivity;
private bool _disposed;
private volatile bool _inactive;
private AutoResetEvent _resetMonitorEvent;
private volatile bool _stopped;
public PreviewMouseWheelMonitor(UIElement canvas, int sensitivity)
{
_canvas = canvas;
_canvas.PreviewMouseWheel += (s, e) => RaisePreviewMouseWheel(e);
_sensitivity = sensitivity;
_dispatcher = Dispatcher.CurrentDispatcher;
_resetMonitorEvent = new AutoResetEvent(false);
_disposed = false;
_inactive = true;
_stopped = true;
var monitor = new Thread(Monitor) {IsBackground = true};
monitor.Start();
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
DetachEventHandlers();
if (_resetMonitorEvent != null)
{
_resetMonitorEvent.Close();
_resetMonitorEvent = null;
}
}
}
public event EventHandler<MouseWheelEventArgs> PreviewMouseWheel;
public event EventHandler<EventArgs> PreviewMouseWheelStarted;
public event EventHandler<EventArgs> PreviewMouseWheelStopped;
private void Monitor()
{
while (!_disposed)
{
if (_inactive) // if wheel is still inactive...
{
_resetMonitorEvent.WaitOne(_sensitivity / 10); // ...wait negligibly small quantity of time...
continue; // ...and check again
}
// otherwise, if wheel is active...
_inactive = true; // ...purposely change the state to inactive
_resetMonitorEvent.WaitOne(_sensitivity); // wait...
if (_inactive
) // ...and after specified time check if the state is still not re-activated inside mouse wheel event
RaiseMouseWheelStopped();
}
}
private void RaisePreviewMouseWheel(MouseWheelEventArgs args)
{
if (_stopped)
RaiseMouseWheelStarted();
_inactive = false;
if (PreviewMouseWheel != null)
PreviewMouseWheel(_canvas, args);
}
private void RaiseMouseWheelStarted()
{
_stopped = false;
if (PreviewMouseWheelStarted != null)
PreviewMouseWheelStarted(_canvas, new EventArgs());
}
private void RaiseMouseWheelStopped()
{
_stopped = true;
if (PreviewMouseWheelStopped != null)
_dispatcher.Invoke(() => PreviewMouseWheelStopped(_canvas,
new
EventArgs())); // invoked on cached dispatcher for convenience (because fired from non-UI thread)
}
private void DetachEventHandlers()
{
if (PreviewMouseWheel != null)
foreach (var handler in PreviewMouseWheel.GetInvocationList().Cast<EventHandler<MouseWheelEventArgs>>())
PreviewMouseWheel -= handler;
if (PreviewMouseWheelStarted != null)
foreach (var handler in PreviewMouseWheelStarted.GetInvocationList().Cast<EventHandler<EventArgs>>())
PreviewMouseWheelStarted -= handler;
if (PreviewMouseWheelStopped != null)
foreach (var handler in PreviewMouseWheelStopped.GetInvocationList().Cast<EventHandler<EventArgs>>())
PreviewMouseWheelStopped -= handler;
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace QuickLook.Plugin.PDFViewer
{
public sealed class PageIdToImageConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 2)
throw new Exception("PageIdToImageConverter");
var zoom = 0.5f;
if (parameter != null)
float.TryParse((string) parameter, out zoom);
var handle = values[0] as PdfFile;
if (handle == null) return null;
var pageId = (int) values[1];
if (pageId < 0) return null;
return handle.GetPage(pageId, zoom).ToBitmapSource();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Drawing;
using Size = System.Windows.Size;
namespace QuickLook.Plugin.PDFViewer
{
public class PdfFile : IDisposable
{
private readonly IntPtr _ctx;
private readonly IntPtr _doc;
private readonly IntPtr _stm;
public PdfFile(string path)
{
_ctx = LibMuPdf.NativeMethods.NewContext();
_stm = LibMuPdf.NativeMethods.OpenFile(_ctx, path);
_doc = LibMuPdf.NativeMethods.OpenDocumentStream(_ctx, _stm);
TotalPages = LibMuPdf.NativeMethods.CountPages(_doc);
}
public int TotalPages { get; }
public void Dispose()
{
LibMuPdf.NativeMethods.CloseDocument(_doc);
LibMuPdf.NativeMethods.CloseStream(_stm);
LibMuPdf.NativeMethods.FreeContext(_ctx);
}
public bool IsLastPage(int pageId)
{
return pageId >= TotalPages;
}
public Size GetPageSize(int pageId, double zoomFactor)
{
if (pageId < 0 || pageId >= TotalPages)
throw new OverflowException(
$"Page id {pageId} should greater or equal than 0 and less than total page count {TotalPages}.");
var p = LibMuPdf.NativeMethods.LoadPage(_doc, pageId);
var realSize = new LibMuPdf.Rectangle();
LibMuPdf.NativeMethods.BoundPage(_doc, p, ref realSize);
var size = new Size
{
Width = realSize.Right * zoomFactor,
Height = realSize.Bottom * zoomFactor
};
LibMuPdf.NativeMethods.FreePage(_doc, p);
return size;
}
public Bitmap GetPage(int pageId, double zoomFactor)
{
if (pageId < 0 || pageId >= TotalPages)
throw new OverflowException(
$"Page id {pageId} should greater or equal than 0 and less than total page count {TotalPages}.");
var p = LibMuPdf.NativeMethods.LoadPage(_doc, pageId);
var bmp = LibMuPdf.RenderPage(_ctx, _doc, p, zoomFactor);
LibMuPdf.NativeMethods.FreePage(_doc, p);
return bmp;
}
}
}

View File

@@ -0,0 +1,71 @@
<UserControl x:Class="QuickLook.Plugin.PDFViewer.PdfViewerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:QuickLook.Plugin.PDFViewer"
mc:Ignorable="d"
x:Name="thisPdfViewer"
UseLayoutRounding="True"
d:DesignHeight="476.974"
d:DesignWidth="720.29">
<UserControl.Resources>
<ResourceDictionary>
<local:PageIdToImageConverter x:Key="PageIdToImageConverter" />
<Style x:Key="ListBoxItemStyleNoFocusedBorder" TargetType="{x:Type ListBoxItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="listThumbnails" Grid.Column="0" VirtualizingPanel.ScrollUnit="Item"
ScrollViewer.IsDeferredScrollingEnabled="False"
SelectedIndex="0"
Focusable="False"
Background="#9FFFFFFF"
ItemsSource="{Binding PageIds, ElementName=thisPdfViewer}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" BorderThickness="0,0,1,0"
ItemContainerStyle="{Binding Mode=OneWay, Source={StaticResource ListBoxItemStyleNoFocusedBorder}}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid MaxHeight="150"
MaxWidth="{Binding ViewportWidth, Mode=Default, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ScrollViewer}}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="*" />
<RowDefinition Height="10" />
</Grid.RowDefinitions>
<Border Grid.Row="1" Grid.Column="1" BorderThickness="1" BorderBrush="#FFE1E1E1">
<Image>
<Image.Source>
<MultiBinding Converter="{StaticResource PageIdToImageConverter}">
<Binding Path="PdfHandleForThumbnails" ElementName="thisPdfViewer" />
<Binding />
</MultiBinding>
</Image.Source>
</Image>
</Border>
<!--
<Label Grid.Row="1" Grid.Column="1" Content="{Binding Mode=OneWay, Converter={StaticResource MathConverter}, ConverterParameter=@VALUE+1}" FontSize="14" />
-->
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Column="1" Background="#DFEFEFEF">
<ScrollViewer x:Name="pageViewPanel" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Focusable="False">
<Image x:Name="pageViewPanelImage" Stretch="None" RenderOptions.BitmapScalingMode="NearestNeighbor">
</Image>
</ScrollViewer>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,253 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace QuickLook.Plugin.PDFViewer
{
/// <summary>
/// Interaction logic for PdfViewer.xaml
/// </summary>
public partial class PdfViewerControl : UserControl, INotifyPropertyChanged, IDisposable
{
private PreviewMouseWheelMonitor _whellMonitor;
public PdfViewerControl()
{
InitializeComponent();
DataContext = this;
PageIds = new ObservableCollection<int>();
}
public ObservableCollection<int> PageIds { get; set; }
public PdfFile PdfHandleForThumbnails { get; private set; }
public PdfFile PdfHandle { get; private set; }
public bool PdfLoaded { get; private set; }
public double ZoomFactor { get; set; }
public int TotalPages => PdfHandle.TotalPages;
public int CurrectPage
{
get => listThumbnails.SelectedIndex;
set
{
listThumbnails.SelectedIndex = value;
listThumbnails.ScrollIntoView(listThumbnails.SelectedItem);
CurrentPageChanged?.Invoke(this, new EventArgs());
}
}
public void Dispose()
{
_whellMonitor.Dispose();
PdfHandleForThumbnails?.Dispose();
PdfHandle?.Dispose();
}
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler CurrentPageChanged;
private void NavigatePage(object sender, MouseWheelEventArgs e)
{
if (!PdfLoaded)
return;
if (Keyboard.Modifiers != ModifierKeys.None)
return;
if (e.Delta > 0) // up
{
if (pageViewPanel.VerticalOffset != 0) return;
PrevPage();
e.Handled = true;
}
else // down
{
if (pageViewPanel.VerticalOffset != pageViewPanel.ScrollableHeight) return;
NextPage();
e.Handled = true;
}
}
private void NextPage()
{
if (CurrectPage < PdfHandle.TotalPages - 1)
{
CurrectPage++;
pageViewPanel.ScrollToTop();
}
}
private void PrevPage()
{
if (CurrectPage > 0)
{
CurrectPage--;
pageViewPanel.ScrollToBottom();
}
}
private void ReRenderCurrentPageLowQuality(double viewZoom, bool fromCenter)
{
if (pageViewPanelImage.Source == null)
return;
var position = fromCenter
? new Point(pageViewPanelImage.Source.Width / 2, pageViewPanelImage.Source.Height / 2)
: Mouse.GetPosition(pageViewPanelImage);
pageViewPanelImage.LayoutTransform = new ScaleTransform(viewZoom, viewZoom);
// critical for calcuating offset
pageViewPanel.ScrollToHorizontalOffset(0);
pageViewPanel.ScrollToVerticalOffset(0);
UpdateLayout();
var offset = pageViewPanelImage.TranslatePoint(position, pageViewPanel) - Mouse.GetPosition(pageViewPanel);
pageViewPanel.ScrollToHorizontalOffset(offset.X);
pageViewPanel.ScrollToVerticalOffset(offset.Y);
UpdateLayout();
}
private void ReRenderCurrentPage()
{
if (!PdfLoaded)
return;
var image = PdfHandle.GetPage(CurrectPage, ZoomFactor).ToBitmapSource();
pageViewPanelImage.Source = image;
pageViewPanelImage.Width = pageViewPanelImage.Source.Width;
pageViewPanelImage.Height = pageViewPanelImage.Source.Height;
// reset view zoom factor
pageViewPanelImage.LayoutTransform = new ScaleTransform();
GC.Collect();
}
private void UpdatePageViewWhenSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!PdfLoaded)
return;
if (CurrectPage == -1)
return;
ReRenderCurrentPage();
}
private void ZoomToFit()
{
if (!PdfLoaded)
return;
var size = PdfHandle.GetPageSize(CurrectPage, 1d);
var factor = Math.Min(pageViewPanel.ActualWidth / size.Width, pageViewPanel.ActualHeight / size.Height);
ZoomFactor = factor;
ReRenderCurrentPage();
}
public Size GetDesiredControlSizeByFirstPage(string path)
{
var tempHandle = new PdfFile(path);
var size = tempHandle.GetPageSize(0, 1d);
tempHandle.Dispose();
size.Width += /*listThumbnails.ActualWidth*/ 150 + 1;
return size;
}
public void LoadPdf(string path)
{
PageIds.Clear();
_whellMonitor?.Dispose();
PdfHandleForThumbnails = new PdfFile(path);
PdfHandle = new PdfFile(path);
PdfLoaded = true;
// fill thumbnails list
Enumerable.Range(0, PdfHandle.TotalPages).ForEach(PageIds.Add);
OnPropertyChanged(nameof(PageIds));
CurrectPage = 0;
// calculate zoom factor for first page
ZoomToFit();
// register events
listThumbnails.SelectionChanged += UpdatePageViewWhenSelectionChanged;
//pageViewPanel.SizeChanged += ReRenderCurrentPageWhenSizeChanged;
pageViewPanel.PreviewMouseWheel += NavigatePage;
StartMouseWhellDelayedZoomMonitor(pageViewPanel);
}
private void StartMouseWhellDelayedZoomMonitor(UIElement ui)
{
if (_whellMonitor == null)
_whellMonitor = new PreviewMouseWheelMonitor(ui, 100);
var newZoom = 1d;
var scrolling = false;
_whellMonitor.PreviewMouseWheelStarted += (sender, e) =>
{
if ((Keyboard.Modifiers & ModifierKeys.Control) == 0)
return;
newZoom = ZoomFactor;
scrolling = true;
};
_whellMonitor.PreviewMouseWheel += (sender, e) =>
{
if ((Keyboard.Modifiers & ModifierKeys.Control) == 0)
return;
e.Handled = true;
newZoom = newZoom + e.Delta / 120 * 0.1;
newZoom = Math.Max(newZoom, 0.2);
newZoom = Math.Min(newZoom, 3);
ReRenderCurrentPageLowQuality(newZoom / ZoomFactor, false);
};
_whellMonitor.PreviewMouseWheelStopped += (sender, e) =>
{
if (!scrolling)
return;
ZoomFactor = newZoom;
ReRenderCurrentPage();
};
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -0,0 +1,53 @@
using System.IO;
using System.Text;
namespace QuickLook.Plugin.PDFViewer
{
public class Plugin : IViewer
{
private PdfViewerControl _pdfControl;
public int Priority => 9999;
public bool CanHandle(string path)
{
if (Directory.Exists(path))
return false;
if (File.Exists(path) && Path.GetExtension(path).ToLower() == ".pdf")
return true;
using (var br = new BinaryReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
{
return Encoding.ASCII.GetString(br.ReadBytes(4)) == "%PDF";
}
}
public void View(string path, ViewContentContainer container)
{
_pdfControl = new PdfViewerControl();
var desiredSize = _pdfControl.GetDesiredControlSizeByFirstPage(path);
desiredSize.Width += 150; // add thumbnails column
container.SetPreferedSizeFit(desiredSize, 0.7);
container.SetContent(_pdfControl);
_pdfControl.Loaded += (sender, e) =>
{
_pdfControl.LoadPdf(path);
container.Title = $"{Path.GetFileName(path)} (1 / {_pdfControl.TotalPages})";
_pdfControl.CurrentPageChanged += (sender2, e2) => container.Title =
$"{Path.GetFileName(path)} ({_pdfControl.CurrectPage + 1} / {_pdfControl.TotalPages})";
};
}
public void Close()
{
_pdfControl.Dispose();
_pdfControl = null;
}
}
}

View File

@@ -16,31 +16,29 @@
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Build\Debug\Plugins\PDFViewer\</OutputPath>
<OutputPath>..\Build\Debug\Plugins\QuickLook.Plugin.PDFViewer\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\Build\Release\Plugins\PDFViewer\</OutputPath>
<OutputPath>..\Build\Release\Plugins\QuickLook.Plugin.PDFViewer\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="MoonPdfLib, Version=0.3.0.0, Culture=neutral, processorArchitecture=x86">
<HintPath>..\packages\MoonPdfLib-x86.0.3.0\lib\MoonPdfLib.dll</HintPath>
</Reference>
<Reference Include="MouseKeyboardActivityMonitor, Version=3.0.1.29653, Culture=neutral, processorArchitecture=x86">
<HintPath>..\packages\MoonPdfLib-x86.0.3.0\lib\MouseKeyboardActivityMonitor.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Xaml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
@@ -51,7 +49,16 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="Class1.cs" />
<Compile Include="PdfViewerControl.xaml.cs">
<DependentUpon>PdfViewerControl.xaml</DependentUpon>
</Compile>
<Compile Include="Plugin.cs" />
<Compile Include="DpiHelpers.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="LibMuPdf.cs" />
<Compile Include="MouseWheelMonitor.cs" />
<Compile Include="PageIdToImageConverter.cs" />
<Compile Include="PdfFile.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
@@ -62,13 +69,15 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="MoonPdfLib\README" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="libmupdf.dll">
<Content Include="LibMuPdf.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Page Include="PdfViewerControl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MoonPdfLib-x86" version="0.3.0" targetFramework="net452" />
</packages>