mirror of
https://github.com/QL-Win/QuickLook.git
synced 2025-09-18 22:52:37 +00:00
Use own Pdf viewer implementation. wip on universal InfoPanel
This commit is contained in:
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
46
QuickLook.Plugin.PDFViewer/DpiHelpers.cs
Normal file
46
QuickLook.Plugin.PDFViewer/DpiHelpers.cs
Normal 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; }
|
||||
}
|
||||
}
|
68
QuickLook.Plugin.PDFViewer/Extensions.cs
Normal file
68
QuickLook.Plugin.PDFViewer/Extensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
181
QuickLook.Plugin.PDFViewer/LibMuPdf.cs
Normal file
181
QuickLook.Plugin.PDFViewer/LibMuPdf.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
113
QuickLook.Plugin.PDFViewer/MouseWheelMonitor.cs
Normal file
113
QuickLook.Plugin.PDFViewer/MouseWheelMonitor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
32
QuickLook.Plugin.PDFViewer/PageIdToImageConverter.cs
Normal file
32
QuickLook.Plugin.PDFViewer/PageIdToImageConverter.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
73
QuickLook.Plugin.PDFViewer/PdfFile.cs
Normal file
73
QuickLook.Plugin.PDFViewer/PdfFile.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
71
QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml
Normal file
71
QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml
Normal 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>
|
253
QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml.cs
Normal file
253
QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
53
QuickLook.Plugin.PDFViewer/Plugin.cs
Normal file
53
QuickLook.Plugin.PDFViewer/Plugin.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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>
|
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<packages>
|
||||
<package id="MoonPdfLib-x86" version="0.3.0" targetFramework="net452" />
|
||||
</packages>
|
Reference in New Issue
Block a user