Skip to content

Commit

Permalink
SvgPreviewControl: Add Background so that white .svg are visible (#25397
Browse files Browse the repository at this point in the history
)

* 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
  • Loading branch information
zanseb committed Apr 21, 2023
1 parent 3164e03 commit 3a63a08
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 6 deletions.
74 changes: 74 additions & 0 deletions src/modules/previewpane/SvgPreviewHandler/Settings.cs
@@ -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;
}
}
}
}
}
@@ -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,
};
}
}
}
15 changes: 12 additions & 3 deletions src/modules/previewpane/SvgPreviewHandler/SvgPreviewControl.cs
Expand Up @@ -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;
Expand All @@ -10,6 +11,7 @@
using Microsoft.PowerToys.Telemetry;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
using SvgPreviewHandler;

namespace Microsoft.PowerToys.PreviewHandler.Svg
{
Expand All @@ -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>
Expand Down Expand Up @@ -220,19 +227,21 @@ private void AddWebViewControl(string svgData)
_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);
Expand Down
Expand Up @@ -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>

Expand Down
@@ -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,
}
}
@@ -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,
}
}
16 changes: 16 additions & 0 deletions src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs
Expand Up @@ -7,13 +7,17 @@
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
{
public class PowerPreviewProperties
{
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;

Expand All @@ -32,6 +36,15 @@ public bool EnableSvgPreview
}
}

[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")]
Expand Down Expand Up @@ -210,6 +223,9 @@ public bool EnableStlThumbnail

public PowerPreviewProperties()
{
SvgBackgroundColorMode = new IntProperty(DefaultSvgBackgroundColorMode);
SvgBackgroundSolidColor = new StringProperty(DefaultSvgBackgroundSolidColor);
SvgBackgroundCheckeredShade = new IntProperty(DefaultSvgBackgroundCheckeredShade);
StlThumbnailColor = new StringProperty(DefaultStlThumbnailColor);
MonacoPreviewMaxFileSize = new IntProperty(DefaultMonacoMaxFileSize);
}
Expand Down
27 changes: 27 additions & 0 deletions src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
Expand Up @@ -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>
Expand Down

0 comments on commit 3a63a08

Please sign in to comment.