Files
PowerToys/src/modules/peek/Peek.FilePreviewer/Previewers/WebBrowserPreviewer/WebBrowserPreviewer.cs
leileizhang d32ea86314 [Peek] Use WebView2 for SVG preview to improve compatibility (#44209)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Improves SVG preview compatibility in Peek by using WebView2 instead of
`SvgImageSource`.
## Problem

`SvgImageSource` has limited SVG feature support and fails to render
SVGs with advanced features like `clipPath`, complex gradients, or
certain namespace configurations. This results in black previews or
icon-only display for many SVG files.

## Solution

Render SVG files using WebView2 which provides full SVG specification
support through the browser engine.

<img width="1973" height="1314" alt="image"
src="https://github.com/user-attachments/assets/a4eb2ff5-d76f-4f7f-87e3-6404e18b2b09"
/>

<img width="1997" height="1358" alt="image"
src="https://github.com/user-attachments/assets/7ce4dd69-7fba-447e-8499-d37af3f02c4d"
/>
<!-- Please review the items on the PR checklist before submitting-->

## PR Checklist

- [x] Closes: #44193
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-12-11 11:58:01 +08:00

173 lines
5.7 KiB
C#

// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Dispatching;
using Peek.Common.Constants;
using Peek.Common.Extensions;
using Peek.Common.Helpers;
using Peek.Common.Models;
using Peek.FilePreviewer.Models;
using Peek.FilePreviewer.Previewers.Interfaces;
namespace Peek.FilePreviewer.Previewers
{
public partial class WebBrowserPreviewer : ObservableObject, IBrowserPreviewer, IDisposable
{
private readonly IPreviewSettings _previewSettings;
private static readonly HashSet<string> _supportedFileTypes = new()
{
// Web
".html",
".htm",
// Document
".pdf",
// Markdown
".md",
// SVG - using WebView2 for better compatibility with complex SVGs
// (e.g., from Adobe Illustrator, Inkscape)
".svg",
};
[ObservableProperty]
private Uri? preview;
[ObservableProperty]
private PreviewState state;
[ObservableProperty]
private bool isDevFilePreview;
[ObservableProperty]
private bool customContextMenu;
private bool disposed;
public WebBrowserPreviewer(IFileSystemItem file, IPreviewSettings previewSettings)
{
_previewSettings = previewSettings;
File = file;
Dispatcher = DispatcherQueue.GetForCurrentThread();
}
~WebBrowserPreviewer()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual async void Dispose(bool disposing)
{
if (!this.disposed)
{
await Microsoft.PowerToys.FilePreviewCommon.Helper.CleanupTempDirAsync(TempFolderPath.Path);
disposed = true;
}
}
private IFileSystemItem File { get; }
public bool IsPreviewLoaded => Preview != null;
private DispatcherQueue Dispatcher { get; }
private Task<bool>? DisplayInfoTask { get; set; }
public Task<PreviewSize> GetPreviewSizeAsync(CancellationToken cancellationToken)
{
return Task.FromResult(new PreviewSize { MonitorSize = null });
}
public async Task LoadPreviewAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
State = PreviewState.Loading;
await LoadDisplayInfoAsync(cancellationToken);
if (HasFailedLoadingPreview())
{
State = PreviewState.Error;
}
}
public Task<bool> LoadDisplayInfoAsync(CancellationToken cancellationToken)
{
return TaskExtension.RunSafe(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
await Dispatcher.RunOnUiThread(async () =>
{
bool isHtml = File.Extension == ".html" || File.Extension == ".htm";
bool isMarkdown = File.Extension == ".md";
bool isSvg = File.Extension == ".svg";
bool supportedByMonaco = MonacoHelper.SupportedMonacoFileTypes.Contains(File.Extension);
bool useMonaco = supportedByMonaco && !isHtml && !isMarkdown && !isSvg;
IsDevFilePreview = supportedByMonaco;
CustomContextMenu = useMonaco;
if (useMonaco)
{
var raw = await ReadHelper.Read(File.Path.ToString());
Preview = new Uri(MonacoHelper.PreviewTempFile(raw, File.Extension, TempFolderPath.Path, _previewSettings.SourceCodeTryFormat, _previewSettings.SourceCodeWrapText, _previewSettings.SourceCodeStickyScroll, _previewSettings.SourceCodeFontSize, _previewSettings.SourceCodeMinimap));
}
else if (isMarkdown)
{
var raw = await ReadHelper.Read(File.Path.ToString());
Preview = new Uri(MarkdownHelper.PreviewTempFile(raw, File.Path, TempFolderPath.Path));
}
else if (isSvg)
{
// SVG files are rendered directly by WebView2 for better compatibility
// with complex SVGs from Adobe Illustrator, Inkscape, etc.
IsDevFilePreview = false;
Preview = new Uri(File.Path);
}
else
{
// Simple html file to preview. Shouldn't do things like enabling scripts or using a virtual mapped directory.
IsDevFilePreview = false;
Preview = new Uri(File.Path);
}
});
});
}
public async Task CopyAsync()
{
await Dispatcher.RunOnUiThread(async () =>
{
var storageItem = await File.GetStorageItemAsync();
ClipboardHelper.SaveToClipboard(storageItem);
});
}
public static bool IsItemSupported(IFileSystemItem item)
{
return _supportedFileTypes.Contains(item.Extension) || MonacoHelper.SupportedMonacoFileTypes.Contains(item.Extension);
}
private bool HasFailedLoadingPreview()
{
return !(DisplayInfoTask?.Result ?? true);
}
}
}