mirror of
https://github.com/QL-Win/QuickLook.git
synced 2025-09-11 17:59:17 +00:00
Add Lottie Files animation preview support
This commit is contained in:
@@ -124,55 +124,47 @@ public class WebfontPanel : WebpagePanel
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
|
protected override void WebView_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs args)
|
||||||
{
|
{
|
||||||
if (e.IsSuccess)
|
Debug.WriteLine($"[{args.Request.Method}] {args.Request.Uri}");
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_webView.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);
|
var requestedUri = new Uri(args.Request.Uri);
|
||||||
|
|
||||||
_webView.CoreWebView2.WebResourceRequested += (sender, args) =>
|
if (requestedUri.Scheme == "file")
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"[{args.Request.Method}] {args.Request.Uri}");
|
if (requestedUri.AbsolutePath == "/")
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var requestedUri = new Uri(args.Request.Uri);
|
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
||||||
|
new MemoryStream(_homePage), 200, "OK", MimeTypes.GetContentType(".html"));
|
||||||
|
args.Response = response;
|
||||||
|
}
|
||||||
|
else if (ContainsKey(requestedUri.AbsolutePath))
|
||||||
|
{
|
||||||
|
var stream = ReadStream(requestedUri.AbsolutePath);
|
||||||
|
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
||||||
|
stream, 200, "OK", MimeTypes.GetContentType(Path.GetExtension(requestedUri.AbsolutePath)));
|
||||||
|
args.Response = response;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var localPath = _fallbackPath + requestedUri.AbsolutePath.Replace('/', '\\');
|
||||||
|
|
||||||
if (requestedUri.Scheme == "file")
|
if (File.Exists(localPath))
|
||||||
{
|
{
|
||||||
if (requestedUri.AbsolutePath == "/")
|
var fileStream = File.OpenRead(localPath);
|
||||||
{
|
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
||||||
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
fileStream, 200, "OK", MimeTypes.GetContentType());
|
||||||
new MemoryStream(_homePage), 200, "OK", MimeTypes.GetContentType(".html"));
|
args.Response = response;
|
||||||
args.Response = response;
|
|
||||||
}
|
|
||||||
else if (ContainsKey(requestedUri.AbsolutePath))
|
|
||||||
{
|
|
||||||
var stream = ReadStream(requestedUri.AbsolutePath);
|
|
||||||
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
|
||||||
stream, 200, "OK", MimeTypes.GetContentType(Path.GetExtension(requestedUri.AbsolutePath)));
|
|
||||||
args.Response = response;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var localPath = _fallbackPath + requestedUri.AbsolutePath.Replace('/', '\\');
|
|
||||||
|
|
||||||
if (File.Exists(localPath))
|
|
||||||
{
|
|
||||||
var fileStream = File.OpenRead(localPath);
|
|
||||||
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
|
||||||
fileStream, 200, "OK", MimeTypes.GetContentType());
|
|
||||||
args.Response = response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
}
|
||||||
{
|
}
|
||||||
// We don't need to feel burdened by any exceptions
|
catch (Exception e)
|
||||||
Debug.WriteLine(e);
|
{
|
||||||
}
|
// We don't need to feel burdened by any exceptions
|
||||||
};
|
Debug.WriteLine(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -176,51 +176,52 @@ public class WebpagePanel : UserControl
|
|||||||
if (e.IsSuccess)
|
if (e.IsSuccess)
|
||||||
{
|
{
|
||||||
_webView.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);
|
_webView.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);
|
||||||
|
_webView.CoreWebView2.WebResourceRequested += WebView_WebResourceRequested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_webView.CoreWebView2.WebResourceRequested += (sender, args) =>
|
protected virtual void WebView_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs args)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_fallbackPath) || !Directory.Exists(_fallbackPath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var requestedUri = new Uri(args.Request.Uri);
|
||||||
|
|
||||||
|
// Check if the request is for a local file
|
||||||
|
if (requestedUri.Scheme == "file" && !File.Exists(requestedUri.LocalPath))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(_fallbackPath) || !Directory.Exists(_fallbackPath))
|
// Try loading from fallback directory
|
||||||
{
|
var fileName = Path.GetFileName(requestedUri.LocalPath);
|
||||||
return;
|
var fileDirectoryName = Path.GetDirectoryName(requestedUri.LocalPath);
|
||||||
}
|
|
||||||
|
|
||||||
try
|
// Convert the primary path to fallback path
|
||||||
|
if (fileDirectoryName.StartsWith(_primaryPath))
|
||||||
{
|
{
|
||||||
var requestedUri = new Uri(args.Request.Uri);
|
var fallbackFilePath = Path.Combine(
|
||||||
|
_fallbackPath.Trim('/', '\\'), // Make it combinable
|
||||||
|
fileDirectoryName.Substring(_primaryPath.Length).Trim('/', '\\'), // Make it combinable
|
||||||
|
fileName
|
||||||
|
);
|
||||||
|
|
||||||
// Check if the request is for a local file
|
if (File.Exists(fallbackFilePath))
|
||||||
if (requestedUri.Scheme == "file" && !File.Exists(requestedUri.LocalPath))
|
|
||||||
{
|
{
|
||||||
// Try loading from fallback directory
|
// Serve the file from the fallback directory
|
||||||
var fileName = Path.GetFileName(requestedUri.LocalPath);
|
var fileStream = File.OpenRead(fallbackFilePath);
|
||||||
var fileDirectoryName = Path.GetDirectoryName(requestedUri.LocalPath);
|
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
||||||
|
fileStream, 200, "OK", "Content-Type: application/octet-stream");
|
||||||
// Convert the primary path to fallback path
|
args.Response = response;
|
||||||
if (fileDirectoryName.StartsWith(_primaryPath))
|
|
||||||
{
|
|
||||||
var fallbackFilePath = Path.Combine(
|
|
||||||
_fallbackPath.Trim('/', '\\'), // Make it combinable
|
|
||||||
fileDirectoryName.Substring(_primaryPath.Length).Trim('/', '\\'), // Make it combinable
|
|
||||||
fileName
|
|
||||||
);
|
|
||||||
|
|
||||||
if (File.Exists(fallbackFilePath))
|
|
||||||
{
|
|
||||||
// Serve the file from the fallback directory
|
|
||||||
var fileStream = File.OpenRead(fallbackFilePath);
|
|
||||||
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
|
||||||
fileStream, 200, "OK", "Content-Type: application/octet-stream");
|
|
||||||
args.Response = response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
}
|
||||||
{
|
}
|
||||||
// We don't need to feel burdened by any exceptions
|
catch (Exception e)
|
||||||
Debug.WriteLine(e);
|
{
|
||||||
}
|
// We don't need to feel burdened by any exceptions
|
||||||
};
|
Debug.WriteLine(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,6 +18,7 @@
|
|||||||
using QuickLook.Common.Helpers;
|
using QuickLook.Common.Helpers;
|
||||||
using QuickLook.Common.Plugin;
|
using QuickLook.Common.Plugin;
|
||||||
using QuickLook.Plugin.ImageViewer.AnimatedImage.Providers;
|
using QuickLook.Plugin.ImageViewer.AnimatedImage.Providers;
|
||||||
|
using QuickLook.Plugin.ImageViewer.Webview;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -48,7 +49,7 @@ public class Plugin : IViewer
|
|||||||
".pbm", ".pcx", ".pef", ".pgm", ".png", ".pnm", ".ppm", ".psb", ".psd", ".ptx", ".pxn",
|
".pbm", ".pcx", ".pef", ".pgm", ".png", ".pnm", ".ppm", ".psb", ".psd", ".ptx", ".pxn",
|
||||||
".qoi",
|
".qoi",
|
||||||
".r3d", ".raf", ".raw", ".rw2", ".rwl", ".rwz",
|
".r3d", ".raf", ".raw", ".rw2", ".rwl", ".rwz",
|
||||||
".sr2", ".srf", ".srw", ".svg", ".svga", ".svgz",
|
".sr2", ".srf", ".srw", ".svg", ".svgz",
|
||||||
".tga", ".tif", ".tiff",
|
".tga", ".tif", ".tiff",
|
||||||
".wdp", ".webp", ".wmf",
|
".wdp", ".webp", ".wmf",
|
||||||
".x3f", ".xcf", ".xbm", ".xpm",
|
".x3f", ".xcf", ".xbm", ".xpm",
|
||||||
@@ -57,8 +58,8 @@ public class Plugin : IViewer
|
|||||||
private ImagePanel _ip;
|
private ImagePanel _ip;
|
||||||
private MetaProvider _meta;
|
private MetaProvider _meta;
|
||||||
|
|
||||||
private SvgImagePanel _ipSvg;
|
private IWebImagePanel _ipWeb;
|
||||||
private SvgMetaProvider _metaSvg;
|
private IWebMetaProvider _metaWeb;
|
||||||
|
|
||||||
public int Priority => 0;
|
public int Priority => 0;
|
||||||
|
|
||||||
@@ -100,44 +101,20 @@ public class Plugin : IViewer
|
|||||||
typeof(ImageMagickProvider)));
|
typeof(ImageMagickProvider)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsWellKnownImageExtension(string path)
|
|
||||||
{
|
|
||||||
return WellKnownImageExtensions.Contains(Path.GetExtension(path.ToLower()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanHandle(string path)
|
public bool CanHandle(string path)
|
||||||
{
|
{
|
||||||
|
if (WebHandler.TryCanHandle(path))
|
||||||
|
return true;
|
||||||
|
|
||||||
// Disabled due mishandling text file types e.g., "*.config".
|
// Disabled due mishandling text file types e.g., "*.config".
|
||||||
// Only check extension for well known image and animated image types.
|
// Only check extension for well known image and animated image types.
|
||||||
return !Directory.Exists(path) && IsWellKnownImageExtension(path);
|
return !Directory.Exists(path) && WellKnownImageExtensions.Contains(Path.GetExtension(path).ToLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Prepare(string path, ContextObject context)
|
public void Prepare(string path, ContextObject context)
|
||||||
{
|
{
|
||||||
if (path.EndsWith(".svg", StringComparison.OrdinalIgnoreCase))
|
if (WebHandler.TryPrepare(path, context, out _metaWeb))
|
||||||
{
|
|
||||||
if (SettingHelper.Get("RenderSvgWeb", true, "QuickLook.Plugin.ImageViewer"))
|
|
||||||
{
|
|
||||||
_metaSvg = new SvgMetaProvider(path);
|
|
||||||
var sizeSvg = _metaSvg.GetSize();
|
|
||||||
|
|
||||||
if (!sizeSvg.IsEmpty)
|
|
||||||
context.SetPreferredSizeFit(sizeSvg, 0.8d);
|
|
||||||
else
|
|
||||||
context.PreferredSize = new Size(800, 600);
|
|
||||||
|
|
||||||
context.Theme = (Themes)SettingHelper.Get("LastTheme", 1, "QuickLook.Plugin.ImageViewer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (path.EndsWith(".svga", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
_metaSvg = new SvgMetaProvider(path);
|
|
||||||
|
|
||||||
context.PreferredSize = new Size(800, 600);
|
|
||||||
context.Theme = (Themes)SettingHelper.Get("LastTheme", 1, "QuickLook.Plugin.ImageViewer");
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
_meta = new MetaProvider(path);
|
_meta = new MetaProvider(path);
|
||||||
|
|
||||||
@@ -153,25 +130,8 @@ public class Plugin : IViewer
|
|||||||
|
|
||||||
public void View(string path, ContextObject context)
|
public void View(string path, ContextObject context)
|
||||||
{
|
{
|
||||||
if (path.EndsWith(".svg", StringComparison.OrdinalIgnoreCase)
|
if (WebHandler.TryView(path, context, _metaWeb, out _ipWeb))
|
||||||
|| path.EndsWith(".svga", StringComparison.OrdinalIgnoreCase))
|
return;
|
||||||
{
|
|
||||||
if (SettingHelper.Get("RenderSvgWeb", true, "QuickLook.Plugin.ImageViewer"))
|
|
||||||
{
|
|
||||||
_ipSvg = new SvgImagePanel();
|
|
||||||
_ipSvg.PreviewSvg(path);
|
|
||||||
|
|
||||||
var sizeSvg = _metaSvg.GetSize();
|
|
||||||
|
|
||||||
context.ViewerContent = _ipSvg;
|
|
||||||
context.Title = sizeSvg.IsEmpty
|
|
||||||
? $"{Path.GetFileName(path)}"
|
|
||||||
: $"{sizeSvg.Width}×{sizeSvg.Height}: {Path.GetFileName(path)}";
|
|
||||||
|
|
||||||
context.IsBusy = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_ip = new ImagePanel(context, _meta);
|
_ip = new ImagePanel(context, _meta);
|
||||||
var size = _meta.GetSize();
|
var size = _meta.GetSize();
|
||||||
@@ -197,7 +157,7 @@ public class Plugin : IViewer
|
|||||||
_ip?.Dispose();
|
_ip?.Dispose();
|
||||||
_ip = null;
|
_ip = null;
|
||||||
|
|
||||||
_ipSvg?.Dispose();
|
_ipWeb?.Dispose();
|
||||||
_ipSvg = null;
|
_ipWeb = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -55,6 +55,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
|
||||||
<PackageReference Include="QuickLook.ImageGlass.WebP" Version="1.4.0">
|
<PackageReference Include="QuickLook.ImageGlass.WebP" Version="1.4.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@@ -91,7 +92,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Resources\svg*">
|
<EmbeddedResource Include="Resources\Web\*">
|
||||||
<LogicalName>QuickLook.Plugin.ImageViewer.Resources.%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
|
<LogicalName>QuickLook.Plugin.ImageViewer.Resources.%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>LottieFiles Preview</title>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
height:100%;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bm {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="bm"></div>
|
||||||
|
<script src="https://unpkg.com/lottie-web/build/player/lottie.min.js"></script>
|
||||||
|
<script src="lottie2html.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* SvgaViewer: Provides SVGA animation preview with the following features.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
* - Requires the following HTML structure:
|
||||||
|
* <canvas id="canvas">
|
||||||
|
* </canvas>
|
||||||
|
* - SVGA file path is obtained via chrome.webview.hostObjects.external.GetPath()
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Loads and plays SVGA animation files
|
||||||
|
* - Uses SVGA.js library for parsing and playback
|
||||||
|
* - Automatically starts playback after loading
|
||||||
|
* - Handles asynchronous loading and mounting of SVGA files
|
||||||
|
*/
|
||||||
|
class LottieViewer {
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play Lottie files.
|
||||||
|
* @async
|
||||||
|
*/
|
||||||
|
async play() {
|
||||||
|
const path = await chrome.webview.hostObjects.external.GetPath();
|
||||||
|
lottie.loadAnimation({
|
||||||
|
container: document.getElementById('bm'),
|
||||||
|
renderer: 'svg',
|
||||||
|
loop: true,
|
||||||
|
autoplay: true,
|
||||||
|
path: 'https://' + path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the Lottie viewer and play
|
||||||
|
new LottieViewer().play();
|
@@ -1,79 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace QuickLook.Plugin.ImageViewer;
|
|
||||||
|
|
||||||
public class SvgMetaProvider(string path)
|
|
||||||
{
|
|
||||||
private readonly string _path = path;
|
|
||||||
private Size _size = Size.Empty;
|
|
||||||
|
|
||||||
public Size GetSize()
|
|
||||||
{
|
|
||||||
if (_size != Size.Empty)
|
|
||||||
{
|
|
||||||
return _size;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!File.Exists(_path))
|
|
||||||
{
|
|
||||||
return _size;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_path.EndsWith(".svg", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var svgContent = File.ReadAllText(_path);
|
|
||||||
var svg = XElement.Parse(svgContent);
|
|
||||||
XNamespace ns = svg.Name.Namespace;
|
|
||||||
|
|
||||||
string widthAttr = svg.Attribute("width")?.Value;
|
|
||||||
string heightAttr = svg.Attribute("height")?.Value;
|
|
||||||
|
|
||||||
float? width = TryParseSvgLength(widthAttr);
|
|
||||||
float? height = TryParseSvgLength(heightAttr);
|
|
||||||
|
|
||||||
if (width.HasValue && height.HasValue)
|
|
||||||
{
|
|
||||||
_size = new Size { Width = width.Value, Height = height.Value };
|
|
||||||
}
|
|
||||||
|
|
||||||
string viewBoxAttr = svg.Attribute("viewBox")?.Value;
|
|
||||||
if (!string.IsNullOrEmpty(viewBoxAttr))
|
|
||||||
{
|
|
||||||
var parts = viewBoxAttr.Split([' ', ','], StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
if (parts.Length == 4 &&
|
|
||||||
float.TryParse(parts[2], out float vbWidth) &&
|
|
||||||
float.TryParse(parts[3], out float vbHeight))
|
|
||||||
{
|
|
||||||
_size = new Size { Width = vbWidth, Height = vbHeight };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.WriteLine(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _size;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float? TryParseSvgLength(string input)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(input))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var match = Regex.Match(input.Trim(), @"^([\d.]+)(px|pt|mm|cm|in|em|ex|%)?$", RegexOptions.IgnoreCase);
|
|
||||||
if (match.Success && float.TryParse(match.Groups[1].Value, out float value))
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright © 2017-2025 QL-Win Contributors
|
||||||
|
//
|
||||||
|
// This file is part of QuickLook program.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.ImageViewer.Webview;
|
||||||
|
|
||||||
|
internal interface IWebImagePanel : IDisposable
|
||||||
|
{
|
||||||
|
public void Preview(string path);
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright © 2017-2025 QL-Win Contributors
|
||||||
|
//
|
||||||
|
// This file is part of QuickLook program.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.ImageViewer.Webview;
|
||||||
|
|
||||||
|
internal interface IWebMetaProvider
|
||||||
|
{
|
||||||
|
public Size GetSize();
|
||||||
|
}
|
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright © 2017-2025 QL-Win Contributors
|
||||||
|
//
|
||||||
|
// This file is part of QuickLook program.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.ImageViewer.Webview.Lottie;
|
||||||
|
|
||||||
|
internal static class LottieDetector
|
||||||
|
{
|
||||||
|
public static bool IsVaild(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jsonString = File.ReadAllText(path);
|
||||||
|
|
||||||
|
// No exception will be thrown here
|
||||||
|
var jsonLottie = LottieParser.Parse<Dictionary<string, object>>(jsonString);
|
||||||
|
|
||||||
|
if (jsonLottie != null
|
||||||
|
&& jsonLottie.ContainsKey("v")
|
||||||
|
&& jsonLottie.ContainsKey("fr")
|
||||||
|
&& jsonLottie.ContainsKey("ip")
|
||||||
|
&& jsonLottie.ContainsKey("op")
|
||||||
|
&& jsonLottie.ContainsKey("layers"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If any exception occurs, assume it's not a valid Lottie file
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,97 @@
|
|||||||
|
// Copyright © 2017-2025 QL-Win Contributors
|
||||||
|
//
|
||||||
|
// This file is part of QuickLook program.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.ImageViewer.Webview.Lottie;
|
||||||
|
|
||||||
|
internal static class LottieExtractor
|
||||||
|
{
|
||||||
|
public static string GetJsonContent(string path)
|
||||||
|
{
|
||||||
|
using var fileStream = File.OpenRead(path);
|
||||||
|
using var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read);
|
||||||
|
|
||||||
|
var manifestEntry = zipArchive.GetEntry("manifest.json");
|
||||||
|
List<string> idEntries = [];
|
||||||
|
|
||||||
|
if (manifestEntry != null)
|
||||||
|
{
|
||||||
|
using var manifestStream = manifestEntry.Open();
|
||||||
|
using var manifestReader = new StreamReader(manifestStream, Encoding.UTF8);
|
||||||
|
string content = manifestReader.ReadToEnd();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(content))
|
||||||
|
{
|
||||||
|
var manifestJson = LottieParser.Parse<Dictionary<string, object>>(content);
|
||||||
|
|
||||||
|
if (manifestJson.ContainsKey("animations"))
|
||||||
|
{
|
||||||
|
object animations = manifestJson["animations"];
|
||||||
|
|
||||||
|
if (manifestJson["animations"] is IEnumerable<object> animationsEnumerable)
|
||||||
|
{
|
||||||
|
foreach (var animationsItem in animationsEnumerable.ToArray())
|
||||||
|
{
|
||||||
|
if (animationsItem is Dictionary<string, object> animationsItemDict)
|
||||||
|
{
|
||||||
|
if (animationsItemDict.ContainsKey("id"))
|
||||||
|
{
|
||||||
|
idEntries.Add($"animations/{animationsItemDict["id"]}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read animations error from manifest.json and fallback to read all entries
|
||||||
|
if (idEntries.Count == 0)
|
||||||
|
{
|
||||||
|
foreach (var entry in zipArchive.Entries)
|
||||||
|
{
|
||||||
|
if (entry.FullName.StartsWith("animations"))
|
||||||
|
{
|
||||||
|
idEntries.Add(entry.FullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the all animations
|
||||||
|
if (idEntries.Count > 0)
|
||||||
|
{
|
||||||
|
// I don't know if there are multiple animations
|
||||||
|
// But only support the first animation
|
||||||
|
var idEntry = $"{idEntries[0]}.json";
|
||||||
|
var animationEntry = zipArchive.GetEntry(idEntry);
|
||||||
|
|
||||||
|
if (animationEntry != null)
|
||||||
|
{
|
||||||
|
using var jsonStream = animationEntry.Open();
|
||||||
|
using var jsonReader = new StreamReader(jsonStream, Encoding.UTF8);
|
||||||
|
return jsonReader.ReadToEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,78 @@
|
|||||||
|
// Copyright © 2017-2025 QL-Win Contributors
|
||||||
|
//
|
||||||
|
// This file is part of QuickLook program.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using Microsoft.Web.WebView2.Core;
|
||||||
|
using QuickLook.Plugin.ImageViewer.Webview.Svg;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.ImageViewer.Webview.Lottie;
|
||||||
|
|
||||||
|
public class LottieImagePanel : SvgImagePanel
|
||||||
|
{
|
||||||
|
public override void Preview(string path)
|
||||||
|
{
|
||||||
|
FallbackPath = Path.GetDirectoryName(path);
|
||||||
|
|
||||||
|
ObjectForScripting ??= new ScriptHandler(path);
|
||||||
|
|
||||||
|
_homePage = _resources["/lottie2html.html"];
|
||||||
|
NavigateToUri(new Uri("file://quicklook/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void WebView_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var requestedUri = new Uri(args.Request.Uri);
|
||||||
|
|
||||||
|
if ((requestedUri.Scheme == "https" || requestedUri.Scheme == "http")
|
||||||
|
&& requestedUri.AbsolutePath.EndsWith(".lottie", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var localPath = Uri.UnescapeDataString($"{requestedUri.Authority}:{requestedUri.AbsolutePath}".Replace('/', '\\'));
|
||||||
|
|
||||||
|
if (localPath.StartsWith(_fallbackPath, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (File.Exists(localPath))
|
||||||
|
{
|
||||||
|
var content = LottieExtractor.GetJsonContent(localPath);
|
||||||
|
byte[] byteArray = Encoding.UTF8.GetBytes(content);
|
||||||
|
var stream = new MemoryStream(byteArray);
|
||||||
|
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
||||||
|
stream, 200, "OK",
|
||||||
|
$"""
|
||||||
|
Access-Control-Allow-Origin: *
|
||||||
|
Content-Type: {MimeTypes.GetMimeType()}
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
args.Response = response;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// We don't need to feel burdened by any exceptions
|
||||||
|
Debug.WriteLine(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
base.WebView_WebResourceRequested(sender, args);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright © 2017-2025 QL-Win Contributors
|
||||||
|
//
|
||||||
|
// This file is part of QuickLook program.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.ImageViewer.Webview.Lottie;
|
||||||
|
|
||||||
|
public class LottieMetaProvider(string path) : IWebMetaProvider
|
||||||
|
{
|
||||||
|
private readonly string _path = path;
|
||||||
|
private Size _size = Size.Empty;
|
||||||
|
|
||||||
|
public Size GetSize()
|
||||||
|
{
|
||||||
|
if (_size != Size.Empty)
|
||||||
|
{
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(_path))
|
||||||
|
{
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jsonString = LottieExtractor.GetJsonContent(_path);
|
||||||
|
var jsonLottie = LottieParser.Parse<Dictionary<string, object>>(jsonString);
|
||||||
|
|
||||||
|
if (jsonLottie.ContainsKey("w")
|
||||||
|
&& jsonLottie.ContainsKey("h")
|
||||||
|
&& double.TryParse(jsonLottie["w"].ToString(), out double width)
|
||||||
|
&& double.TryParse(jsonLottie["h"].ToString(), out double height))
|
||||||
|
{
|
||||||
|
return _size = new Size(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// That's fine, just return the default size.
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Size(800, 600);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,369 @@
|
|||||||
|
// Copyright © 2017-2025 QL-Win Contributors
|
||||||
|
//
|
||||||
|
// This file is part of QuickLook program.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.ImageViewer.Webview.Lottie;
|
||||||
|
|
||||||
|
internal static class LottieParser
|
||||||
|
{
|
||||||
|
[ThreadStatic] private static Stack<List<string>> splitArrayPool = null!;
|
||||||
|
[ThreadStatic] private static StringBuilder stringBuilder = null!;
|
||||||
|
[ThreadStatic] private static Dictionary<Type, Dictionary<string, FieldInfo>> fieldInfoCache = null!;
|
||||||
|
[ThreadStatic] private static Dictionary<Type, Dictionary<string, PropertyInfo>> propertyInfoCache = null!;
|
||||||
|
|
||||||
|
public static T Parse<T>(string json)
|
||||||
|
{
|
||||||
|
// Initialize, if needed, the ThreadStatic variables
|
||||||
|
propertyInfoCache ??= [];
|
||||||
|
fieldInfoCache ??= [];
|
||||||
|
stringBuilder ??= new StringBuilder();
|
||||||
|
splitArrayPool ??= new Stack<List<string>>();
|
||||||
|
|
||||||
|
//Remove all whitespace not within strings to make parsing simpler
|
||||||
|
stringBuilder.Length = 0;
|
||||||
|
for (int i = 0; i < json.Length; i++)
|
||||||
|
{
|
||||||
|
char c = json[i];
|
||||||
|
if (c == '"')
|
||||||
|
{
|
||||||
|
i = AppendUntilStringEnd(true, i, json);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (char.IsWhiteSpace(c))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
stringBuilder.Append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Parse the thing!
|
||||||
|
return (T)ParseValue(typeof(T), stringBuilder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int AppendUntilStringEnd(bool appendEscapeCharacter, int startIdx, string json)
|
||||||
|
{
|
||||||
|
stringBuilder.Append(json[startIdx]);
|
||||||
|
for (int i = startIdx + 1; i < json.Length; i++)
|
||||||
|
{
|
||||||
|
if (json[i] == '\\')
|
||||||
|
{
|
||||||
|
if (appendEscapeCharacter)
|
||||||
|
stringBuilder.Append(json[i]);
|
||||||
|
stringBuilder.Append(json[i + 1]);
|
||||||
|
i++;//Skip next character as it is escaped
|
||||||
|
}
|
||||||
|
else if (json[i] == '"')
|
||||||
|
{
|
||||||
|
stringBuilder.Append(json[i]);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
stringBuilder.Append(json[i]);
|
||||||
|
}
|
||||||
|
return json.Length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Splits { <value>:<value>, <value>:<value> } and [ <value>, <value> ] into a list of <value> strings
|
||||||
|
static List<string> Split(string json)
|
||||||
|
{
|
||||||
|
List<string> splitArray = splitArrayPool.Count > 0 ? splitArrayPool.Pop() : [];
|
||||||
|
splitArray.Clear();
|
||||||
|
if (json.Length == 2)
|
||||||
|
return splitArray;
|
||||||
|
int parseDepth = 0;
|
||||||
|
stringBuilder.Length = 0;
|
||||||
|
for (int i = 1; i < json.Length - 1; i++)
|
||||||
|
{
|
||||||
|
switch (json[i])
|
||||||
|
{
|
||||||
|
case '[':
|
||||||
|
case '{':
|
||||||
|
parseDepth++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ']':
|
||||||
|
case '}':
|
||||||
|
parseDepth--;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
i = AppendUntilStringEnd(true, i, json);
|
||||||
|
continue;
|
||||||
|
case ',':
|
||||||
|
case ':':
|
||||||
|
if (parseDepth == 0)
|
||||||
|
{
|
||||||
|
splitArray.Add(stringBuilder.ToString());
|
||||||
|
stringBuilder.Length = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
stringBuilder.Append(json[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
splitArray.Add(stringBuilder.ToString());
|
||||||
|
|
||||||
|
return splitArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static object ParseValue(Type type, string json)
|
||||||
|
{
|
||||||
|
if (type == typeof(string))
|
||||||
|
{
|
||||||
|
if (json.Length <= 2)
|
||||||
|
return string.Empty;
|
||||||
|
StringBuilder parseStringBuilder = new(json.Length);
|
||||||
|
for (int i = 1; i < json.Length - 1; ++i)
|
||||||
|
{
|
||||||
|
if (json[i] == '\\' && i + 1 < json.Length - 1)
|
||||||
|
{
|
||||||
|
int j = "\"\\nrtbf/".IndexOf(json[i + 1]);
|
||||||
|
if (j >= 0)
|
||||||
|
{
|
||||||
|
parseStringBuilder.Append("\"\\\n\r\t\b\f/"[j]);
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (json[i + 1] == 'u' && i + 5 < json.Length - 1)
|
||||||
|
{
|
||||||
|
if (uint.TryParse(json.Substring(i + 2, 4), System.Globalization.NumberStyles.AllowHexSpecifier, null, out uint c))
|
||||||
|
{
|
||||||
|
parseStringBuilder.Append((char)c);
|
||||||
|
i += 5;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parseStringBuilder.Append(json[i]);
|
||||||
|
}
|
||||||
|
return parseStringBuilder.ToString();
|
||||||
|
}
|
||||||
|
if (type.IsPrimitive)
|
||||||
|
{
|
||||||
|
var result = Convert.ChangeType(json, type, System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (type == typeof(decimal))
|
||||||
|
{
|
||||||
|
decimal.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out decimal result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (type == typeof(DateTime))
|
||||||
|
{
|
||||||
|
DateTime.TryParse(json.Replace("\"", ""), System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out DateTime result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (json == "null")
|
||||||
|
{
|
||||||
|
return null!;
|
||||||
|
}
|
||||||
|
if (type.IsEnum)
|
||||||
|
{
|
||||||
|
if (json[0] == '"')
|
||||||
|
json = json.Substring(1, json.Length - 2);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Enum.Parse(type, json, false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type.IsArray)
|
||||||
|
{
|
||||||
|
Type arrayType = type.GetElementType();
|
||||||
|
if (json[0] != '[' || json[json.Length - 1] != ']')
|
||||||
|
return null!;
|
||||||
|
|
||||||
|
List<string> elems = Split(json);
|
||||||
|
Array newArray = Array.CreateInstance(arrayType, elems.Count);
|
||||||
|
for (int i = 0; i < elems.Count; i++)
|
||||||
|
newArray.SetValue(ParseValue(arrayType, elems[i]), i);
|
||||||
|
splitArrayPool.Push(elems);
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
|
||||||
|
{
|
||||||
|
Type listType = type.GetGenericArguments()[0];
|
||||||
|
if (json[0] != '[' || json[json.Length - 1] != ']')
|
||||||
|
return null!;
|
||||||
|
|
||||||
|
List<string> elems = Split(json);
|
||||||
|
var list = (IList)type.GetConstructor([typeof(int)]).Invoke([elems.Count]);
|
||||||
|
for (int i = 0; i < elems.Count; i++)
|
||||||
|
list.Add(ParseValue(listType, elems[i]));
|
||||||
|
splitArrayPool.Push(elems);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||||
|
{
|
||||||
|
Type keyType, valueType;
|
||||||
|
{
|
||||||
|
Type[] args = type.GetGenericArguments();
|
||||||
|
keyType = args[0];
|
||||||
|
valueType = args[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
//Refuse to parse dictionary keys that aren't of type string
|
||||||
|
if (keyType != typeof(string))
|
||||||
|
return null!;
|
||||||
|
//Must be a valid dictionary element
|
||||||
|
if (json[0] != '{' || json[json.Length - 1] != '}')
|
||||||
|
return null!;
|
||||||
|
//The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
|
||||||
|
List<string> elems = Split(json);
|
||||||
|
if (elems.Count % 2 != 0)
|
||||||
|
return null!;
|
||||||
|
|
||||||
|
var dictionary = (IDictionary)type.GetConstructor([typeof(int)]).Invoke([elems.Count / 2]);
|
||||||
|
for (int i = 0; i < elems.Count; i += 2)
|
||||||
|
{
|
||||||
|
if (elems[i].Length <= 2)
|
||||||
|
continue;
|
||||||
|
string keyValue = elems[i].Substring(1, elems[i].Length - 2);
|
||||||
|
object val = ParseValue(valueType, elems[i + 1]);
|
||||||
|
dictionary[keyValue] = val;
|
||||||
|
}
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
if (type == typeof(object))
|
||||||
|
{
|
||||||
|
return ParseAnonymousValue(json);
|
||||||
|
}
|
||||||
|
if (json[0] == '{' && json[json.Length - 1] == '}')
|
||||||
|
{
|
||||||
|
return ParseObject(type, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
static object ParseAnonymousValue(string json)
|
||||||
|
{
|
||||||
|
if (json.Length == 0)
|
||||||
|
return null!;
|
||||||
|
if (json[0] == '{' && json[json.Length - 1] == '}')
|
||||||
|
{
|
||||||
|
List<string> elems = Split(json);
|
||||||
|
if (elems.Count % 2 != 0)
|
||||||
|
return null!;
|
||||||
|
var dict = new Dictionary<string, object>(elems.Count / 2);
|
||||||
|
for (int i = 0; i < elems.Count; i += 2)
|
||||||
|
dict[elems[i].Substring(1, elems[i].Length - 2)] = ParseAnonymousValue(elems[i + 1]);
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
if (json[0] == '[' && json[json.Length - 1] == ']')
|
||||||
|
{
|
||||||
|
List<string> items = Split(json);
|
||||||
|
var finalList = new List<object>(items.Count);
|
||||||
|
for (int i = 0; i < items.Count; i++)
|
||||||
|
finalList.Add(ParseAnonymousValue(items[i]));
|
||||||
|
return finalList;
|
||||||
|
}
|
||||||
|
if (json[0] == '"' && json[json.Length - 1] == '"')
|
||||||
|
{
|
||||||
|
string str = json.Substring(1, json.Length - 2);
|
||||||
|
return str.Replace("\\", string.Empty);
|
||||||
|
}
|
||||||
|
if (char.IsDigit(json[0]) || json[0] == '-')
|
||||||
|
{
|
||||||
|
if (json.Contains("."))
|
||||||
|
{
|
||||||
|
double.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int.TryParse(json, out int result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (json == "true")
|
||||||
|
return true;
|
||||||
|
if (json == "false")
|
||||||
|
return false;
|
||||||
|
// handles json == "null" as well as invalid JSON
|
||||||
|
return null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Dictionary<string, T> CreateMemberNameDictionary<T>(T[] members) where T : MemberInfo
|
||||||
|
{
|
||||||
|
Dictionary<string, T> nameToMember = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
for (int i = 0; i < members.Length; i++)
|
||||||
|
{
|
||||||
|
T member = members[i];
|
||||||
|
if (member.IsDefined(typeof(IgnoreDataMemberAttribute), true))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string name = member.Name;
|
||||||
|
if (member.IsDefined(typeof(DataMemberAttribute), true))
|
||||||
|
{
|
||||||
|
DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute), true);
|
||||||
|
if (!string.IsNullOrEmpty(dataMemberAttribute.Name))
|
||||||
|
name = dataMemberAttribute.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
nameToMember.Add(name, member);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nameToMember;
|
||||||
|
}
|
||||||
|
|
||||||
|
static object ParseObject(Type type, string json)
|
||||||
|
{
|
||||||
|
object instance = FormatterServices.GetUninitializedObject(type);
|
||||||
|
|
||||||
|
//The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
|
||||||
|
List<string> elems = Split(json);
|
||||||
|
if (elems.Count % 2 != 0)
|
||||||
|
return instance;
|
||||||
|
|
||||||
|
if (!fieldInfoCache.TryGetValue(type, out Dictionary<string, FieldInfo> nameToField))
|
||||||
|
{
|
||||||
|
nameToField = CreateMemberNameDictionary(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
|
||||||
|
fieldInfoCache.Add(type, nameToField);
|
||||||
|
}
|
||||||
|
if (!propertyInfoCache.TryGetValue(type, out Dictionary<string, PropertyInfo> nameToProperty))
|
||||||
|
{
|
||||||
|
nameToProperty = CreateMemberNameDictionary(type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
|
||||||
|
propertyInfoCache.Add(type, nameToProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < elems.Count; i += 2)
|
||||||
|
{
|
||||||
|
if (elems[i].Length <= 2)
|
||||||
|
continue;
|
||||||
|
string key = elems[i].Substring(1, elems[i].Length - 2);
|
||||||
|
string value = elems[i + 1];
|
||||||
|
|
||||||
|
if (nameToField.TryGetValue(key, out FieldInfo fieldInfo))
|
||||||
|
fieldInfo.SetValue(instance, ParseValue(fieldInfo.FieldType, value));
|
||||||
|
else if (nameToProperty.TryGetValue(key, out PropertyInfo propertyInfo))
|
||||||
|
propertyInfo.SetValue(instance, ParseValue(propertyInfo.PropertyType, value), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
@@ -29,9 +29,9 @@ using System.Runtime.InteropServices;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace QuickLook.Plugin.ImageViewer;
|
namespace QuickLook.Plugin.ImageViewer.Webview.Svg;
|
||||||
|
|
||||||
public class SvgImagePanel : WebpagePanel
|
public class SvgImagePanel : WebpagePanel, IWebImagePanel
|
||||||
{
|
{
|
||||||
protected const string _resourcePrefix = "QuickLook.Plugin.ImageViewer.Resources.";
|
protected const string _resourcePrefix = "QuickLook.Plugin.ImageViewer.Resources.";
|
||||||
protected internal static readonly Dictionary<string, byte[]> _resources = [];
|
protected internal static readonly Dictionary<string, byte[]> _resources = [];
|
||||||
@@ -93,80 +93,77 @@ public class SvgImagePanel : WebpagePanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PreviewSvg(string path)
|
public virtual void Preview(string path)
|
||||||
{
|
{
|
||||||
FallbackPath = Path.GetDirectoryName(path);
|
FallbackPath = Path.GetDirectoryName(path);
|
||||||
|
|
||||||
ObjectForScripting ??= new ScriptHandler(path);
|
ObjectForScripting ??= new ScriptHandler(path);
|
||||||
|
|
||||||
_homePage = _resources[path.EndsWith(".svga", StringComparison.OrdinalIgnoreCase) ? "/svga2html.html" : "/svg2html.html"];
|
_homePage = _resources["/svg2html.html"];
|
||||||
NavigateToUri(new Uri("file://quicklook/"));
|
NavigateToUri(new Uri("file://quicklook/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
|
protected override void WebView_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs args)
|
||||||
{
|
{
|
||||||
if (e.IsSuccess)
|
Debug.WriteLine($"[{args.Request.Method}] {args.Request.Uri}");
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_webView.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);
|
var requestedUri = new Uri(args.Request.Uri);
|
||||||
|
|
||||||
_webView.CoreWebView2.WebResourceRequested += (sender, args) =>
|
if (requestedUri.Scheme == "file")
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"[{args.Request.Method}] {args.Request.Uri}");
|
if (requestedUri.AbsolutePath == "/")
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var requestedUri = new Uri(args.Request.Uri);
|
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
||||||
|
new MemoryStream(_homePage), 200, "OK", MimeTypes.GetContentTypeHeader(".html"));
|
||||||
|
args.Response = response;
|
||||||
|
}
|
||||||
|
else if (ContainsKey(requestedUri.AbsolutePath))
|
||||||
|
{
|
||||||
|
var stream = ReadStream(requestedUri.AbsolutePath);
|
||||||
|
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
||||||
|
stream, 200, "OK", MimeTypes.GetContentTypeHeader(Path.GetExtension(requestedUri.AbsolutePath)));
|
||||||
|
args.Response = response;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var localPath = _fallbackPath + requestedUri.AbsolutePath.Replace('/', '\\');
|
||||||
|
|
||||||
if (requestedUri.Scheme == "file")
|
if (File.Exists(localPath))
|
||||||
{
|
{
|
||||||
if (requestedUri.AbsolutePath == "/")
|
var fileStream = File.OpenRead(localPath);
|
||||||
{
|
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
||||||
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
fileStream, 200, "OK", MimeTypes.GetContentTypeHeader());
|
||||||
new MemoryStream(_homePage), 200, "OK", MimeTypes.GetContentType(".html"));
|
args.Response = response;
|
||||||
args.Response = response;
|
|
||||||
}
|
|
||||||
else if (ContainsKey(requestedUri.AbsolutePath))
|
|
||||||
{
|
|
||||||
var stream = ReadStream(requestedUri.AbsolutePath);
|
|
||||||
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
|
||||||
stream, 200, "OK", MimeTypes.GetContentType(Path.GetExtension(requestedUri.AbsolutePath)));
|
|
||||||
args.Response = response;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var localPath = _fallbackPath + requestedUri.AbsolutePath.Replace('/', '\\');
|
|
||||||
|
|
||||||
if (File.Exists(localPath))
|
|
||||||
{
|
|
||||||
var fileStream = File.OpenRead(localPath);
|
|
||||||
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
|
||||||
fileStream, 200, "OK", MimeTypes.GetContentType());
|
|
||||||
args.Response = response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (requestedUri.Scheme == "https")
|
|
||||||
{
|
|
||||||
var localPath = $"{requestedUri.Authority}:{requestedUri.AbsolutePath}".Replace('/', '\\');
|
|
||||||
|
|
||||||
if (localPath.StartsWith(_fallbackPath, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
if (File.Exists(localPath))
|
|
||||||
{
|
|
||||||
var fileStream = File.OpenRead(localPath);
|
|
||||||
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
|
||||||
fileStream, 200, "OK", MimeTypes.GetContentType() + "\r\nAccess-Control-Allow-Origin: *");
|
|
||||||
args.Response = response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
}
|
||||||
|
else if (requestedUri.Scheme == "https" || requestedUri.Scheme == "http")
|
||||||
|
{
|
||||||
|
var localPath = Uri.UnescapeDataString($"{requestedUri.Authority}:{requestedUri.AbsolutePath}".Replace('/', '\\'));
|
||||||
|
|
||||||
|
if (localPath.StartsWith(_fallbackPath, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// We don't need to feel burdened by any exceptions
|
if (File.Exists(localPath))
|
||||||
Debug.WriteLine(e);
|
{
|
||||||
|
var fileStream = File.OpenRead(localPath);
|
||||||
|
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
||||||
|
fileStream, 200, "OK",
|
||||||
|
$"""
|
||||||
|
Access-Control-Allow-Origin: *
|
||||||
|
Content-Type: {MimeTypes.GetMimeType()}
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
args.Response = response;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// We don't need to feel burdened by any exceptions
|
||||||
|
Debug.WriteLine(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,14 +188,16 @@ public class SvgImagePanel : WebpagePanel
|
|||||||
{
|
{
|
||||||
public const string Html = "text/html";
|
public const string Html = "text/html";
|
||||||
public const string JavaScript = "application/javascript";
|
public const string JavaScript = "application/javascript";
|
||||||
|
public const string Json = "application/json";
|
||||||
public const string Css = "text/css";
|
public const string Css = "text/css";
|
||||||
public const string Binary = "application/octet-stream";
|
public const string Binary = "application/octet-stream";
|
||||||
|
|
||||||
public static string GetContentType(string extension = null) => $"Content-Type: {GetMimeType(extension)}";
|
public static string GetContentTypeHeader(string extension = null) => $"Content-Type: {GetMimeType(extension)}";
|
||||||
|
|
||||||
public static string GetMimeType(string extension = null) => extension?.ToLowerInvariant() switch
|
public static string GetMimeType(string extension = null) => extension?.ToLowerInvariant() switch
|
||||||
{
|
{
|
||||||
".js" => JavaScript, // Only handle known extensions from resources
|
".js" => JavaScript, // Only handle known extensions from resources
|
||||||
|
".json" => Json,
|
||||||
".css" => Css,
|
".css" => Css,
|
||||||
".html" => Html,
|
".html" => Html,
|
||||||
_ => Binary,
|
_ => Binary,
|
||||||
@@ -217,11 +216,6 @@ public sealed class ScriptHandler(string path)
|
|||||||
return await Task.FromResult(new Uri(Path).AbsolutePath);
|
return await Task.FromResult(new Uri(Path).AbsolutePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetUri()
|
|
||||||
{
|
|
||||||
return await Task.FromResult(new Uri(Path).AbsoluteUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> GetSvgContent()
|
public async Task<string> GetSvgContent()
|
||||||
{
|
{
|
||||||
if (File.Exists(Path))
|
if (File.Exists(Path))
|
@@ -0,0 +1,93 @@
|
|||||||
|
// Copyright © 2017-2025 QL-Win Contributors
|
||||||
|
//
|
||||||
|
// This file is part of QuickLook program.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.ImageViewer.Webview.Svg;
|
||||||
|
|
||||||
|
public class SvgMetaProvider(string path) : IWebMetaProvider
|
||||||
|
{
|
||||||
|
private readonly string _path = path;
|
||||||
|
private Size _size = Size.Empty;
|
||||||
|
|
||||||
|
public Size GetSize()
|
||||||
|
{
|
||||||
|
if (_size != Size.Empty)
|
||||||
|
{
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(_path))
|
||||||
|
{
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var svgContent = File.ReadAllText(_path);
|
||||||
|
var svg = XElement.Parse(svgContent);
|
||||||
|
XNamespace ns = svg.Name.Namespace;
|
||||||
|
|
||||||
|
string widthAttr = svg.Attribute("width")?.Value;
|
||||||
|
string heightAttr = svg.Attribute("height")?.Value;
|
||||||
|
|
||||||
|
float? width = TryParseSvgLength(widthAttr);
|
||||||
|
float? height = TryParseSvgLength(heightAttr);
|
||||||
|
|
||||||
|
if (width.HasValue && height.HasValue)
|
||||||
|
{
|
||||||
|
_size = new Size { Width = width.Value, Height = height.Value };
|
||||||
|
}
|
||||||
|
|
||||||
|
string viewBoxAttr = svg.Attribute("viewBox")?.Value;
|
||||||
|
if (!string.IsNullOrEmpty(viewBoxAttr))
|
||||||
|
{
|
||||||
|
var parts = viewBoxAttr.Split([' ', ','], StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
if (parts.Length == 4 &&
|
||||||
|
float.TryParse(parts[2], out float vbWidth) &&
|
||||||
|
float.TryParse(parts[3], out float vbHeight))
|
||||||
|
{
|
||||||
|
_size = new Size { Width = vbWidth, Height = vbHeight };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.WriteLine(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float? TryParseSvgLength(string input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(input))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var match = Regex.Match(input.Trim(), @"^([\d.]+)(px|pt|mm|cm|in|em|ex|%)?$", RegexOptions.IgnoreCase);
|
||||||
|
if (match.Success && float.TryParse(match.Groups[1].Value, out float value))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright © 2017-2025 QL-Win Contributors
|
||||||
|
//
|
||||||
|
// This file is part of QuickLook program.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using QuickLook.Plugin.ImageViewer.Webview.Svg;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.ImageViewer.Webview.Svga;
|
||||||
|
|
||||||
|
public class SvgaImagePanel : SvgImagePanel
|
||||||
|
{
|
||||||
|
public override void Preview(string path)
|
||||||
|
{
|
||||||
|
FallbackPath = Path.GetDirectoryName(path);
|
||||||
|
|
||||||
|
ObjectForScripting ??= new ScriptHandler(path);
|
||||||
|
|
||||||
|
_homePage = _resources["/svga2html.html"];
|
||||||
|
NavigateToUri(new Uri("file://quicklook/"));
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright © 2017-2025 QL-Win Contributors
|
||||||
|
//
|
||||||
|
// This file is part of QuickLook program.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.ImageViewer.Webview.Svga;
|
||||||
|
|
||||||
|
internal class SvgaMetaProvider(string path) : IWebMetaProvider
|
||||||
|
{
|
||||||
|
private readonly string _path = path;
|
||||||
|
private Size _size = Size.Empty;
|
||||||
|
|
||||||
|
public Size GetSize()
|
||||||
|
{
|
||||||
|
if (_size != Size.Empty)
|
||||||
|
{
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(_path))
|
||||||
|
{
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
return new Size(800, 600);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,123 @@
|
|||||||
|
// Copyright © 2017-2025 QL-Win Contributors
|
||||||
|
//
|
||||||
|
// This file is part of QuickLook program.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using QuickLook.Common.Helpers;
|
||||||
|
using QuickLook.Common.Plugin;
|
||||||
|
using QuickLook.Plugin.ImageViewer.Webview.Lottie;
|
||||||
|
using QuickLook.Plugin.ImageViewer.Webview.Svg;
|
||||||
|
using QuickLook.Plugin.ImageViewer.Webview.Svga;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.ImageViewer.Webview;
|
||||||
|
|
||||||
|
internal static class WebHandler
|
||||||
|
{
|
||||||
|
public static bool TryCanHandle(string path)
|
||||||
|
{
|
||||||
|
if (path.EndsWith(".lottie.json", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return Path.GetExtension(path).ToLower() switch
|
||||||
|
{
|
||||||
|
".svg" => SettingHelper.Get("RenderSvgWeb", true, "QuickLook.Plugin.ImageViewer"),
|
||||||
|
".svga" or ".lottie" => true,
|
||||||
|
".json" => LottieDetector.IsVaild(path), // Check for Lottie files
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryPrepare(string path, ContextObject context, out IWebMetaProvider metaWeb)
|
||||||
|
{
|
||||||
|
string ext = Path.GetExtension(path).ToLower();
|
||||||
|
|
||||||
|
if (ext == ".svg" || ext == ".svga"
|
||||||
|
|| ext == ".lottie" || ext == ".json")
|
||||||
|
{
|
||||||
|
if (ext == ".svg")
|
||||||
|
{
|
||||||
|
if (!SettingHelper.Get("RenderSvgWeb", true, "QuickLook.Plugin.ImageViewer"))
|
||||||
|
{
|
||||||
|
metaWeb = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metaWeb = ext switch
|
||||||
|
{
|
||||||
|
".svg" => new SvgMetaProvider(path),
|
||||||
|
".svga" => new SvgaMetaProvider(path),
|
||||||
|
".lottie" or ".json" => new LottieMetaProvider(path),
|
||||||
|
_ => throw new NotSupportedException($"Unsupported file type: {ext}")
|
||||||
|
};
|
||||||
|
var sizeSvg = metaWeb.GetSize();
|
||||||
|
|
||||||
|
if (!sizeSvg.IsEmpty)
|
||||||
|
context.SetPreferredSizeFit(sizeSvg, 0.8d);
|
||||||
|
else
|
||||||
|
context.PreferredSize = new Size(800, 600);
|
||||||
|
|
||||||
|
context.Theme = (Themes)SettingHelper.Get("LastTheme", 1, "QuickLook.Plugin.ImageViewer");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
metaWeb = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryView(string path, ContextObject context, IWebMetaProvider metaWeb, out IWebImagePanel ipWeb)
|
||||||
|
{
|
||||||
|
string ext = Path.GetExtension(path).ToLower();
|
||||||
|
|
||||||
|
if (ext == ".svg" || ext == ".svga"
|
||||||
|
|| ext == ".lottie" || ext == ".json")
|
||||||
|
{
|
||||||
|
if (ext == ".svg")
|
||||||
|
{
|
||||||
|
if (!SettingHelper.Get("RenderSvgWeb", true, "QuickLook.Plugin.ImageViewer"))
|
||||||
|
{
|
||||||
|
ipWeb = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ipWeb = ext switch
|
||||||
|
{
|
||||||
|
".svg" => new SvgImagePanel(),
|
||||||
|
".svga" => new SvgaImagePanel(),
|
||||||
|
".lottie" or ".json" => new LottieImagePanel(),
|
||||||
|
_ => throw new NotSupportedException($"Unsupported file type: {ext}")
|
||||||
|
};
|
||||||
|
|
||||||
|
ipWeb.Preview(path);
|
||||||
|
|
||||||
|
var sizeSvg = metaWeb.GetSize();
|
||||||
|
|
||||||
|
context.ViewerContent = ipWeb;
|
||||||
|
context.Title = sizeSvg.IsEmpty
|
||||||
|
? $"{Path.GetFileName(path)}"
|
||||||
|
: $"{sizeSvg.Width}×{sizeSvg.Height}: {Path.GetFileName(path)}";
|
||||||
|
|
||||||
|
context.IsBusy = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipWeb = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@@ -83,55 +83,47 @@ public class MarkdownPanel : WebpagePanel
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
|
protected override void WebView_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs args)
|
||||||
{
|
{
|
||||||
if (e.IsSuccess)
|
Debug.WriteLine($"[{args.Request.Method}] {args.Request.Uri}");
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_webView.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);
|
var requestedUri = new Uri(args.Request.Uri);
|
||||||
|
|
||||||
_webView.CoreWebView2.WebResourceRequested += (sender, args) =>
|
if (requestedUri.Scheme == "file")
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"[{args.Request.Method}] {args.Request.Uri}");
|
if (requestedUri.AbsolutePath == "/")
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var requestedUri = new Uri(args.Request.Uri);
|
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
||||||
|
new MemoryStream(_homePage), 200, "OK", MimeTypes.GetContentType(".html"));
|
||||||
|
args.Response = response;
|
||||||
|
}
|
||||||
|
else if (ContainsKey(requestedUri.AbsolutePath))
|
||||||
|
{
|
||||||
|
var stream = ReadStream(requestedUri.AbsolutePath);
|
||||||
|
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
||||||
|
stream, 200, "OK", MimeTypes.GetContentType(Path.GetExtension(requestedUri.AbsolutePath)));
|
||||||
|
args.Response = response;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var localPath = _fallbackPath + requestedUri.AbsolutePath.Replace('/', '\\');
|
||||||
|
|
||||||
if (requestedUri.Scheme == "file")
|
if (File.Exists(localPath))
|
||||||
{
|
{
|
||||||
if (requestedUri.AbsolutePath == "/")
|
var fileStream = File.OpenRead(localPath);
|
||||||
{
|
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
||||||
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
fileStream, 200, "OK", MimeTypes.GetContentType());
|
||||||
new MemoryStream(_homePage), 200, "OK", MimeTypes.GetContentType(".html"));
|
args.Response = response;
|
||||||
args.Response = response;
|
|
||||||
}
|
|
||||||
else if (ContainsKey(requestedUri.AbsolutePath))
|
|
||||||
{
|
|
||||||
var stream = ReadStream(requestedUri.AbsolutePath);
|
|
||||||
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
|
||||||
stream, 200, "OK", MimeTypes.GetContentType(Path.GetExtension(requestedUri.AbsolutePath)));
|
|
||||||
args.Response = response;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var localPath = _fallbackPath + requestedUri.AbsolutePath.Replace('/', '\\');
|
|
||||||
|
|
||||||
if (File.Exists(localPath))
|
|
||||||
{
|
|
||||||
var fileStream = File.OpenRead(localPath);
|
|
||||||
var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse(
|
|
||||||
fileStream, 200, "OK", MimeTypes.GetContentType());
|
|
||||||
args.Response = response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
}
|
||||||
{
|
}
|
||||||
// We don't need to feel burdened by any exceptions
|
catch (Exception e)
|
||||||
Debug.WriteLine(e);
|
{
|
||||||
}
|
// We don't need to feel burdened by any exceptions
|
||||||
};
|
Debug.WriteLine(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user