diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Plugin.cs index 4be000c..68d62d8 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Plugin.cs @@ -15,26 +15,17 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using QuickLook.Common.Helpers; using QuickLook.Common.Plugin; -using QuickLook.Plugin.HtmlViewer; -using QuickLook.Typography.OpenFont; using System; using System.IO; -using System.IO.Packaging; using System.Linq; -using System.Reflection; -using System.Text; using System.Windows; -using System.Windows.Resources; namespace QuickLook.Plugin.FontViewer; public class Plugin : IViewer { - private static readonly string _resourcePath = Path.Combine(SettingHelper.LocalDataPath, "QuickLook.Plugin.FontViewer"); - - private WebpagePanel _panel; + private WebfontPanel _panel; public int Priority => 0; @@ -56,122 +47,19 @@ public class Plugin : IViewer public void View(string path, ContextObject context) { - _panel = new WebpagePanel(); - - if (OSThemeHelper.AppsUseDarkTheme()) - { - // Invoke using reflection: WebView2.CreationProperties.AdditionalBrowserArguments - // This approach allows the library to avoid direct dependency on WebView2 - if (typeof(WebpagePanel).GetField("_webView", BindingFlags.NonPublic | BindingFlags.Instance) is FieldInfo fieldInfo) - { - object webView2 = fieldInfo.GetValue(_panel); - - if (webView2?.GetType().GetProperty("CreationProperties", BindingFlags.Public | BindingFlags.Instance) is PropertyInfo creationPropertiesProperty) - { - object creationProperties = creationPropertiesProperty.GetValue(webView2); - - if (creationProperties?.GetType().GetProperty("AdditionalBrowserArguments", BindingFlags.Public | BindingFlags.Instance) is PropertyInfo additionalBrowserArgumentsProperty) - { - string additionalBrowserArguments = (additionalBrowserArgumentsProperty.GetValue(creationProperties) as string) ?? string.Empty; - additionalBrowserArgumentsProperty.SetValue(creationProperties, additionalBrowserArguments + " --enable-features=WebContentsForceDark"); - } - } - } - } + _panel = new WebfontPanel(); + _panel.PreviewFont(path); context.ViewerContent = _panel; context.Title = Path.GetFileName(path); - - var html = GenerateFontHtml(path); - var htmlPath = Path.Combine(_resourcePath, "font2html.html"); - - if (!Directory.Exists(Path.GetDirectoryName(htmlPath))) - { - Directory.CreateDirectory(Path.GetDirectoryName(htmlPath)); - } - File.WriteAllText(htmlPath, html); - - _panel.FallbackPath = Path.GetDirectoryName(path); - _panel.NavigateToFile(htmlPath); - context.IsBusy = false; } - private string GenerateFontHtml(string path) - { - string fontFamilyName = FreeTypeApi.GetFontFamilyName(path); - StreamResourceInfo info = Application.GetResourceStream(new Uri($"pack://application:,,,/QuickLook.Plugin.FontViewer;component/Resources/font2html.html")); - using Stream stream = info?.Stream; - using StreamReader streamReader = new(stream, Encoding.UTF8); - string html = streamReader.ReadToEnd(); - - // src: url('xxx.eot'); - // src: url('xxx?#iefix') format('embedded-opentype'), - // url('xxx.woff') format('woff'), - // url('xxx.ttf') format('truetype'), - // url('xxx.svg#xxx') format('svg'); - var fileName = Path.GetFileName(path); - var fileExt = Path.GetExtension(fileName); - - string cssUrl = $"src: url('{fileName}')" - + fileExt switch - { - ".eot" => " format('embedded-opentype');", - ".woff" => " format('woff');", - ".woff2" => " format('woff2');", - ".ttf" => " format('truetype');", - ".otf" => " format('opentype');", - _ => ";", - }; - - if (string.IsNullOrEmpty(fontFamilyName)) - { - if (fileExt.ToLower().Equals(".woff2")) - { - fontFamilyName = Woff2.GetFontInfo(path)?.Name; - } - } - - // https://en.wikipedia.org/wiki/Pangram - string translationFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Translations.config"); - string pangram = TranslationHelper.Get("SAMPLE_TEXT", translationFile); - - html = html.Replace("--font-family;", $"font-family: '{fontFamilyName}';") - .Replace("--font-url;", cssUrl) - .Replace("{{h1}}", fontFamilyName ?? fileName) - .Replace("{{pangram}}", pangram ?? "The quick brown fox jumps over the lazy dog. 0123456789"); - - return html; - } - public void Cleanup() { GC.SuppressFinalize(this); - } -} - -file static class ResourcesProvider -{ - static ResourcesProvider() - { - if (!UriParser.IsKnownScheme("pack")) - { - _ = PackUriHelper.UriSchemePack; - } - } - - public static bool TryGetStream(Uri uri, out Stream stream) - { - try - { - StreamResourceInfo info = Application.GetResourceStream(uri); - stream = info?.Stream; - return true; - } - catch - { - } - stream = null; - return false; + + _panel?.Dispose(); + _panel = null; } } diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/QuickLook.Plugin.FontViewer.csproj b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/QuickLook.Plugin.FontViewer.csproj index ec2ee3a..1bb8b92 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/QuickLook.Plugin.FontViewer.csproj +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/QuickLook.Plugin.FontViewer.csproj @@ -52,14 +52,6 @@ prompt - - - Designer - PreserveNewest - - - - PreserveNewest @@ -81,6 +73,9 @@ + + all + @@ -96,6 +91,19 @@ + + + Designer + PreserveNewest + + + + + + QuickLook.Plugin.FontViewer.Resources.%(RecursiveDir)%(Filename)%(Extension) + + + Properties\GitVersion.cs diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/WebfontPanel.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/WebfontPanel.cs new file mode 100644 index 0000000..a44c539 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/WebfontPanel.cs @@ -0,0 +1,213 @@ +// 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 . + +using Microsoft.Web.WebView2.Core; +using QuickLook.Common.Helpers; +using QuickLook.Plugin.HtmlViewer; +using QuickLook.Typography.OpenFont; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace QuickLook.Plugin.FontViewer; + +public class WebfontPanel : WebpagePanel +{ + protected const string _resourcePrefix = "QuickLook.Plugin.FontViewer.Resources."; + protected internal static readonly Dictionary _resources = []; + protected byte[] _homePage; + + static WebfontPanel() + { + InitializeResources(); + } + + public WebfontPanel() + { + if (OSThemeHelper.AppsUseDarkTheme()) + { + _webView.CreationProperties.AdditionalBrowserArguments = "--enable-features=WebContentsForceDark"; + } + } + + protected static void InitializeResources() + { + if (_resources.Any()) return; + + var assembly = Assembly.GetExecutingAssembly(); + + foreach (var resourceName in assembly.GetManifestResourceNames()) + { + if (!resourceName.StartsWith(_resourcePrefix)) continue; + + var relativePath = resourceName.Substring(_resourcePrefix.Length); + if (relativePath.Equals("resources", StringComparison.OrdinalIgnoreCase)) continue; + + using var stream = assembly.GetManifestResourceStream(resourceName); + if (stream == null) continue; + var memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + _resources.Add($"/{relativePath.Replace('\\', '/')}", memoryStream.ToArray()); + } + } + + public void PreviewFont(string path) + { + FallbackPath = Path.GetDirectoryName(path); + + var html = GenerateFontHtml(path); + byte[] bytes = Encoding.UTF8.GetBytes(html); + _homePage = bytes; + + NavigateToUri(new Uri("file://quicklook/")); + } + + protected string GenerateFontHtml(string path) + { + string fontFamilyName = FreeTypeApi.GetFontFamilyName(path); + var html = ReadString("/font2html.html"); + + // src: url('xxx.eot'); + // src: url('xxx?#iefix') format('embedded-opentype'), + // url('xxx.woff') format('woff'), + // url('xxx.ttf') format('truetype'), + // url('xxx.svg#xxx') format('svg'); + var fileName = Path.GetFileName(path); + var fileExt = Path.GetExtension(fileName); + + string cssUrl = $"src: url('{fileName}')" + + fileExt switch + { + ".eot" => " format('embedded-opentype');", + ".woff" => " format('woff');", + ".woff2" => " format('woff2');", + ".ttf" => " format('truetype');", + ".otf" => " format('opentype');", + _ => ";", + }; + + if (string.IsNullOrEmpty(fontFamilyName)) + { + if (fileExt.ToLower().Equals(".woff2")) + { + fontFamilyName = Woff2.GetFontInfo(path)?.Name; + } + } + + // https://en.wikipedia.org/wiki/Pangram + string translationFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Translations.config"); + string pangram = TranslationHelper.Get("SAMPLE_TEXT", translationFile); + + html = html.Replace("--font-family;", $"font-family: '{fontFamilyName}';") + .Replace("--font-url;", cssUrl) + .Replace("{{h1}}", fontFamilyName ?? fileName) + .Replace("{{pangram}}", pangram ?? "The quick brown fox jumps over the lazy dog. 0123456789"); + + return html; + } + + protected override void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e) + { + if (e.IsSuccess) + { + _webView.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All); + + _webView.CoreWebView2.WebResourceRequested += (sender, args) => + { + Debug.WriteLine($"[{args.Request.Method}] {args.Request.Uri}"); + + try + { + var requestedUri = new Uri(args.Request.Uri); + + if (requestedUri.Scheme == "file") + { + if (requestedUri.AbsolutePath == "/") + { + 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 (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 + Debug.WriteLine(e); + } + }; + } + } + + public static bool ContainsKey(string key) + { + return _resources.ContainsKey(key); + } + + public static Stream ReadStream(string key) + { + byte[] bytes = _resources[key]; + return new MemoryStream(bytes); + } + + public static string ReadString(string key) + { + using var reader = new StreamReader(ReadStream(key), Encoding.UTF8); + return reader.ReadToEnd(); + } + + public static class MimeTypes + { + public const string Html = "text/html"; + public const string JavaScript = "application/javascript"; + public const string Css = "text/css"; + public const string Binary = "application/octet-stream"; + + public static string GetContentType(string extension = null) => $"Content-Type: {GetMimeType(extension)}"; + + public static string GetMimeType(string extension = null) => extension?.ToLowerInvariant() switch + { + ".js" => JavaScript, // Only handle known extensions from resources + ".css" => Css, + ".html" => Html, + _ => Binary, + }; + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/Plugin.cs index c9edc33..af9a83f 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/Plugin.cs @@ -15,11 +15,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using QuickLook.Common.Plugin; +using System; using System.IO; using System.Linq; using System.Windows; using System.Windows.Threading; -using QuickLook.Common.Plugin; namespace QuickLook.Plugin.HtmlViewer; @@ -63,6 +64,8 @@ public class Plugin : IViewer public void Cleanup() { + GC.SuppressFinalize(this); + _panel?.Dispose(); _panel = null; }