SvgPreviewControl: Add Background so that white .svg are visible (#25397)

* SvgPreviewControl: Add checkered background so that white .svg are visible

* SvgPreviewControl: Move preview generation logic into own class

* SvgPreviewControl: Add possibility to configure background of svg preview pane via the settings ui

* SvgPreviewControl: Take user configuration into consideration when generating svg preview

* SvgPreviewControl: Do not generate preview file, if the actual size is under the 2mb limiation of WebView2

* SvgPreviewControl: Introduce SvgPreviewColorMode enumeration instead of using magic values

* SvgPreviewControl: Add additional checkered pattern shades

* SvgPreviewControl: Use newly introduced enums as default values
This commit is contained in:
Sebastian Zanoni
2023-04-21 18:54:57 +02:00
committed by GitHub
parent 3164e03ad4
commit 3a63a088ed
11 changed files with 328 additions and 6 deletions

View File

@@ -0,0 +1,74 @@
// 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 Microsoft.PowerToys.Settings.UI.Library;
namespace SvgPreviewHandler
{
internal sealed class Settings
{
private static readonly SettingsUtils ModuleSettings = new SettingsUtils();
public int ColorMode
{
get
{
try
{
return ModuleSettings.GetSettings<PowerPreviewSettings>(PowerPreviewSettings.ModuleName).Properties.SvgBackgroundColorMode.Value;
}
catch (FileNotFoundException)
{
return PowerPreviewProperties.DefaultSvgBackgroundColorMode;
}
}
}
public Color SolidColor
{
get
{
try
{
var colorString = ModuleSettings.GetSettings<PowerPreviewSettings>(PowerPreviewSettings.ModuleName).Properties.SvgBackgroundSolidColor.Value;
return ColorTranslator.FromHtml(colorString);
}
catch (FileNotFoundException)
{
return ColorTranslator.FromHtml(PowerPreviewProperties.DefaultSvgBackgroundSolidColor);
}
}
}
public Color ThemeColor
{
get
{
if (Common.UI.ThemeManager.GetWindowsBaseColor().ToLowerInvariant() == "dark")
{
return ColorTranslator.FromHtml("#1e1e1e");
}
else
{
return Color.White;
}
}
}
public int CheckeredShade
{
get
{
try
{
return ModuleSettings.GetSettings<PowerPreviewSettings>(PowerPreviewSettings.ModuleName).Properties.SvgBackgroundCheckeredShade.Value;
}
catch (FileNotFoundException)
{
return PowerPreviewProperties.DefaultSvgBackgroundCheckeredShade;
}
}
}
}
}

View File

@@ -0,0 +1,64 @@
// 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.Globalization;
using Settings.UI.Library.Enumerations;
namespace SvgPreviewHandler
{
internal sealed class SvgHTMLPreviewGenerator
{
private const string CheckeredBackgroundShadeLight = """
url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV/TiiIVBzOIOGSogmBBVMRRqlgEC6Wt0KqDyaVf0KQhSXFxFFwLDn4sVh1cnHV1cBUEwQ8QVxcnRRcp8X9JoUWMB8f9eHfvcfcOEBoVplmhCUDTbTMVj0nZ3KrU/YoQRIQxBlFmlpFIL2bgO77uEeDrXZRn+Z/7c/SpeYsBAYl4jhmmTbxBPLNpG5z3iUVWklXic+Jxky5I/Mh1xeM3zkWXBZ4pmpnUPLFILBU7WOlgVjI14mniiKrplC9kPVY5b3HWKjXWuid/YTivr6S5TnMYcSwhgSQkKKihjApsRGnVSbGQov2Yj3/I9SfJpZCrDEaOBVShQXb94H/wu1urMDXpJYVjQNeL43yMAN27QLPuON/HjtM8AYLPwJXe9lcbwOwn6fW2FjkC+reBi+u2puwBlzvA4JMhm7IrBWkKhQLwfkbflAMGboHeNa+31j5OH4AMdbV8AxwcAqNFyl73eXdPZ2//nmn19wOEPHKuuso0oQAAAAlwSFlzAAAuIwAALiMBeKU/dgAAAAd0SU1FB+cEFAwrEI+z8/sAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAL0lEQVQoz2O8e/cuAwzcv38fzlZUVMQqzsRAIqC9BhZi3I0sPhj9QIy7R+OB5hoACxUaWr81wGUAAAAASUVORK5CYII=');
""";
private const string CheckeredBackgroundShadeMedium = """
url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV/TiiIVBzOIOGSogmBBVMRRqlgEC6Wt0KqDyaVf0KQhSXFxFFwLDn4sVh1cnHV1cBUEwQ8QVxcnRRcp8X9JoUWMB8f9eHfvcfcOEBoVplmhCUDTbTMVj0nZ3KrU/YoQRIQxBlFmlpFIL2bgO77uEeDrXZRn+Z/7c/SpeYsBAYl4jhmmTbxBPLNpG5z3iUVWklXic+Jxky5I/Mh1xeM3zkWXBZ4pmpnUPLFILBU7WOlgVjI14mniiKrplC9kPVY5b3HWKjXWuid/YTivr6S5TnMYcSwhgSQkKKihjApsRGnVSbGQov2Yj3/I9SfJpZCrDEaOBVShQXb94H/wu1urMDXpJYVjQNeL43yMAN27QLPuON/HjtM8AYLPwJXe9lcbwOwn6fW2FjkC+reBi+u2puwBlzvA4JMhm7IrBWkKhQLwfkbflAMGboHeNa+31j5OH4AMdbV8AxwcAqNFyl73eXdPZ2//nmn19wOEPHKuuso0oQAAAAlwSFlzAAAuIwAALiMBeKU/dgAAAAd0SU1FB+cEFA0AJje78TwAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAALklEQVQoz2O8e/cuAwwsX74czo6MjMQqzsRAIqC9BhZi3I0sPgj9wDgaD4PCDwBglRs7Q+IL6QAAAABJRU5ErkJggg==');
""";
private const string CheckeredBackgroundShadeDark = """
url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV/TiiIVBzOIOGSogmBBVMRRqlgEC6Wt0KqDyaVf0KQhSXFxFFwLDn4sVh1cnHV1cBUEwQ8QVxcnRRcp8X9JoUWMB8f9eHfvcfcOEBoVplmhCUDTbTMVj0nZ3KrU/YoQRIQxBlFmlpFIL2bgO77uEeDrXZRn+Z/7c/SpeYsBAYl4jhmmTbxBPLNpG5z3iUVWklXic+Jxky5I/Mh1xeM3zkWXBZ4pmpnUPLFILBU7WOlgVjI14mniiKrplC9kPVY5b3HWKjXWuid/YTivr6S5TnMYcSwhgSQkKKihjApsRGnVSbGQov2Yj3/I9SfJpZCrDEaOBVShQXb94H/wu1urMDXpJYVjQNeL43yMAN27QLPuON/HjtM8AYLPwJXe9lcbwOwn6fW2FjkC+reBi+u2puwBlzvA4JMhm7IrBWkKhQLwfkbflAMGboHeNa+31j5OH4AMdbV8AxwcAqNFyl73eXdPZ2//nmn19wOEPHKuuso0oQAAAAlwSFlzAAAuIwAALiMBeKU/dgAAAAd0SU1FB+cEFA0CCa5crucAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAL0lEQVQoz2NsaWlhgIETJ07A2RYWFljFmRhIBLTXwEKMu5HFB6MfiHH3aDzQXAMACHkZChhWp4QAAAAASUVORK5CYII=');
""";
private const string HtmlTemplateSolidColor = """
<html>
<body style="background-color: {0}">
{1}
</body>
</html>
""";
private const string HtmlTemplateCheckered = """
<html>
<body style="background-image: {0}">
{1}
</body>
</html>
""";
private readonly Settings settings = new();
public string GeneratePreview(string svgData)
{
var colorMode = (SvgPreviewColorMode)settings.ColorMode;
return colorMode switch
{
SvgPreviewColorMode.SolidColor => string.Format(CultureInfo.InvariantCulture, HtmlTemplateSolidColor, ColorTranslator.ToHtml(settings.SolidColor), svgData),
SvgPreviewColorMode.Checkered => string.Format(CultureInfo.InvariantCulture, HtmlTemplateCheckered, GetConfiguredCheckeredShadeImage(), svgData),
SvgPreviewColorMode.Default or _ => string.Format(CultureInfo.InvariantCulture, HtmlTemplateSolidColor, ColorTranslator.ToHtml(settings.ThemeColor), svgData),
};
}
private string GetConfiguredCheckeredShadeImage()
{
var checkeredShade = (SvgPreviewCheckeredShade)settings.CheckeredShade;
return checkeredShade switch
{
SvgPreviewCheckeredShade.Light=> CheckeredBackgroundShadeLight,
SvgPreviewCheckeredShade.Medium => CheckeredBackgroundShadeMedium,
SvgPreviewCheckeredShade.Dark or _ => CheckeredBackgroundShadeDark,
};
}
}
}

View File

@@ -2,6 +2,7 @@
// 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.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using Common;
@@ -10,6 +11,7 @@ using Microsoft.PowerToys.PreviewHandler.Svg.Telemetry.Events;
using Microsoft.PowerToys.Telemetry;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
using SvgPreviewHandler;
namespace Microsoft.PowerToys.PreviewHandler.Svg
{
@@ -18,6 +20,11 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
/// </summary>
public class SvgPreviewControl : FormHandlerControl
{
/// <summary>
/// Generator for the actual preview file
/// </summary>
private readonly SvgHTMLPreviewGenerator _previewGenerator = new();
/// <summary>
/// WebView2 Control to display Svg.
/// </summary>
@@ -220,19 +227,21 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
_browser.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);
_browser.CoreWebView2.WebResourceRequested += CoreWebView2_BlockExternalResources;
string generatedPreview = _previewGenerator.GeneratePreview(svgData);
// WebView2.NavigateToString() limitation
// See https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2.navigatetostring?view=webview2-dotnet-1.0.864.35#remarks
// While testing the limit, it turned out it is ~1.5MB, so to be on a safe side we go for 1.5m bytes
if (svgData.Length > 1_500_000)
if (generatedPreview.Length > 1_500_000)
{
string filename = _webView2UserDataFolder + "\\" + Guid.NewGuid().ToString() + ".html";
File.WriteAllText(filename, svgData);
File.WriteAllText(filename, generatedPreview);
_localFileURI = new Uri(filename);
_browser.Source = _localFileURI;
}
else
{
_browser.NavigateToString(svgData);
_browser.NavigateToString(generatedPreview);
}
Controls.Add(_browser);

View File

@@ -59,6 +59,7 @@
<ProjectReference Include="..\..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
<ProjectReference Include="..\common\PreviewHandlerCommon.csproj" />
</ItemGroup>

View File

@@ -0,0 +1,13 @@
// 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.
namespace Settings.UI.Library.Enumerations
{
public enum SvgPreviewCheckeredShade
{
Light,
Medium,
Dark,
}
}

View File

@@ -0,0 +1,13 @@
// 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.
namespace Settings.UI.Library.Enumerations
{
public enum SvgPreviewColorMode
{
Default,
SolidColor,
Checkered,
}
}

View File

@@ -7,6 +7,7 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.Telemetry;
using Microsoft.PowerToys.Telemetry;
using Settings.UI.Library.Enumerations;
namespace Microsoft.PowerToys.Settings.UI.Library
{
@@ -14,6 +15,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
public const string DefaultStlThumbnailColor = "#FFC924";
public const int DefaultMonacoMaxFileSize = 50;
public const int DefaultSvgBackgroundColorMode = (int)SvgPreviewColorMode.Default;
public const string DefaultSvgBackgroundSolidColor = "#FFFFFF";
public const int DefaultSvgBackgroundCheckeredShade = (int)SvgPreviewCheckeredShade.Light;
private bool enableSvgPreview = true;
@@ -32,6 +36,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
[JsonPropertyName("svg-previewer-background-color-mode")]
public IntProperty SvgBackgroundColorMode { get; set; }
[JsonPropertyName("svg-previewer-background-solid-color")]
public StringProperty SvgBackgroundSolidColor { get; set; }
[JsonPropertyName("svg-previewer-background-checkered-shade")]
public IntProperty SvgBackgroundCheckeredShade { get; set; }
private bool enableSvgThumbnail = true;
[JsonPropertyName("svg-thumbnail-toggle-setting")]
@@ -210,6 +223,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public PowerPreviewProperties()
{
SvgBackgroundColorMode = new IntProperty(DefaultSvgBackgroundColorMode);
SvgBackgroundSolidColor = new StringProperty(DefaultSvgBackgroundSolidColor);
SvgBackgroundCheckeredShade = new IntProperty(DefaultSvgBackgroundCheckeredShade);
StlThumbnailColor = new StringProperty(DefaultStlThumbnailColor);
MonacoPreviewMaxFileSize = new IntProperty(DefaultMonacoMaxFileSize);
}

View File

@@ -762,6 +762,33 @@
<value>.svg</value>
<comment>File extension, should not be altered</comment>
</data>
<data name="FileExplorerPreview_Preview_SVG_Color_Mode.Header" xml:space="preserve">
<value>Color mode</value>
</data>
<data name="FileExplorerPreview_Preview_SVG_Color_Mode_Default.Content" xml:space="preserve">
<value>Windows default</value>
</data>
<data name="FileExplorerPreview_Preview_SVG_Color_Solid_Color.Content" xml:space="preserve">
<value>Solid color</value>
</data>
<data name="FileExplorerPreview_Preview_SVG_Checkered_Shade.Content" xml:space="preserve">
<value>Checkered pattern</value>
</data>
<data name="FileExplorerPreview_Preview_SVG_Background_Color.Header" xml:space="preserve">
<value>Color</value>
</data>
<data name="FileExplorerPreview_Preview_SVG_Checkered_Shade_Mode.Header" xml:space="preserve">
<value>Checkered shade</value>
</data>
<data name="FileExplorerPreview_Preview_SVG_Checkered_Shade_1.Content" xml:space="preserve">
<value>Light</value>
</data>
<data name="FileExplorerPreview_Preview_SVG_Checkered_Shade_2.Content" xml:space="preserve">
<value>Medium</value>
</data>
<data name="FileExplorerPreview_Preview_SVG_Checkered_Shade_3.Content" xml:space="preserve">
<value>Dark</value>
</data>
<data name="FileExplorerPreview_ToggleSwitch_Preview_PDF.Header" xml:space="preserve">
<value>Portable Document Format</value>
<comment>File type, do not translate</comment>

View File

@@ -8,6 +8,7 @@ using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Settings.UI.Library.Enumerations;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
@@ -60,6 +61,10 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_svgRenderIsEnabled = Settings.Properties.EnableSvgPreview;
}
_svgBackgroundColorMode = Settings.Properties.SvgBackgroundColorMode.Value;
_svgBackgroundSolidColor = Settings.Properties.SvgBackgroundSolidColor.Value;
_svgBackgroundCheckeredShade = Settings.Properties.SvgBackgroundCheckeredShade.Value;
_svgThumbnailEnabledGpoRuleConfiguration = GPOWrapper.GetConfiguredSvgThumbnailsEnabledValue();
if (_svgThumbnailEnabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _svgThumbnailEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
{
@@ -166,6 +171,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private GpoRuleConfigured _svgRenderEnabledGpoRuleConfiguration;
private bool _svgRenderEnabledStateIsGPOConfigured;
private bool _svgRenderIsEnabled;
private int _svgBackgroundColorMode;
private string _svgBackgroundSolidColor;
private int _svgBackgroundCheckeredShade;
private GpoRuleConfigured _mdRenderEnabledGpoRuleConfiguration;
private bool _mdRenderEnabledStateIsGPOConfigured;
@@ -227,6 +235,78 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public int SVGRenderBackgroundColorMode
{
get
{
return _svgBackgroundColorMode;
}
set
{
if (value != _svgBackgroundColorMode)
{
_svgBackgroundColorMode = value;
Settings.Properties.SvgBackgroundColorMode.Value = value;
RaisePropertyChanged();
RaisePropertyChanged(nameof(IsSvgBackgroundColorVisible));
RaisePropertyChanged(nameof(IsSvgCheckeredShadeVisible));
}
}
}
public bool IsSvgBackgroundColorVisible
{
get
{
return (SvgPreviewColorMode)SVGRenderBackgroundColorMode == SvgPreviewColorMode.SolidColor;
}
}
public string SVGRenderBackgroundSolidColor
{
get
{
return _svgBackgroundSolidColor;
}
set
{
if (value != _svgBackgroundSolidColor)
{
_svgBackgroundSolidColor = value;
Settings.Properties.SvgBackgroundSolidColor = value;
RaisePropertyChanged();
}
}
}
public bool IsSvgCheckeredShadeVisible
{
get
{
return (SvgPreviewColorMode)SVGRenderBackgroundColorMode == SvgPreviewColorMode.Checkered;
}
}
public int SVGRenderBackgroundCheckeredShade
{
get
{
return _svgBackgroundCheckeredShade;
}
set
{
if (value != _svgBackgroundCheckeredShade)
{
_svgBackgroundCheckeredShade = value;
Settings.Properties.SvgBackgroundCheckeredShade.Value = value;
RaisePropertyChanged();
}
}
}
public bool IsSVGRenderEnabledGpoConfigured
{
get => _svgRenderEnabledStateIsGPOConfigured;

View File

@@ -115,7 +115,7 @@
IsOn="{x:Bind IsShown, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
OffContent=""
OnContent="" />
<Button
x:Uid="More_Options_Button"
Grid.Column="1"

View File

@@ -22,14 +22,39 @@
IsOpen="True"
IsTabStop="True"
Severity="Warning" />
<labs:SettingsCard
<labs:SettingsExpander
x:Uid="FileExplorerPreview_ToggleSwitch_Preview_SVG"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource SymbolThemeFontFamily}, Glyph=&#xE91B;}"
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsSVGRenderEnabledGpoConfigured, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch
x:Uid="ToggleSwitch"
IsOn="{x:Bind Mode=TwoWay, Path=ViewModel.SVGRenderIsEnabled}" />
</labs:SettingsCard>
<labs:SettingsExpander.Items>
<labs:SettingsCard x:Uid="FileExplorerPreview_Preview_SVG_Color_Mode">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.SVGRenderBackgroundColorMode, Mode=TwoWay}"
IsEnabled="{x:Bind ViewModel.SVGRenderIsEnabled, Mode=OneWay}">
<ComboBoxItem x:Uid="FileExplorerPreview_Preview_SVG_Color_Mode_Default" />
<ComboBoxItem x:Uid="FileExplorerPreview_Preview_SVG_Color_Solid_Color" />
<ComboBoxItem x:Uid="FileExplorerPreview_Preview_SVG_Checkered_Shade" />
</ComboBox>
</labs:SettingsCard>
<labs:SettingsCard x:Uid="FileExplorerPreview_Preview_SVG_Background_Color"
Visibility="{x:Bind ViewModel.IsSvgBackgroundColorVisible, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<controls:ColorPickerButton
IsEnabled="{x:Bind ViewModel.SVGRenderIsEnabled, Mode=OneWay}"
SelectedColor="{x:Bind Path=ViewModel.SVGRenderBackgroundSolidColor, Mode=TwoWay}" />
</labs:SettingsCard>
<labs:SettingsCard x:Uid="FileExplorerPreview_Preview_SVG_Checkered_Shade_Mode"
Visibility="{x:Bind ViewModel.IsSvgCheckeredShadeVisible, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.SVGRenderBackgroundCheckeredShade, Mode=TwoWay}"
IsEnabled="{x:Bind ViewModel.SVGRenderIsEnabled, Mode=OneWay}">
<ComboBoxItem x:Uid="FileExplorerPreview_Preview_SVG_Checkered_Shade_1" />
<ComboBoxItem x:Uid="FileExplorerPreview_Preview_SVG_Checkered_Shade_2" />
<ComboBoxItem x:Uid="FileExplorerPreview_Preview_SVG_Checkered_Shade_3" />
</ComboBox>
</labs:SettingsCard>
</labs:SettingsExpander.Items>
</labs:SettingsExpander>
<InfoBar
x:Uid="GPO_IsSettingForced"