Add SVG Thumbnail Provider for Windows Explorer (#5048)

* Add SVG Thumbnail Provider

* Some cleanup

* Small settings changes

* Update PowerToys.sln

Remove Any CPU entries

* Fix project configuration issues

* Fix bad merge

* Update output path for SVG thumbnail provider

* Sync with latest
This commit is contained in:
Chris Davis
2020-07-21 16:27:12 -07:00
committed by GitHub
parent 8e5a877997
commit 40d2ef4f0a
28 changed files with 1610 additions and 687 deletions

View File

@@ -0,0 +1,178 @@
// 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.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using Moq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Common.ComInterlop;
using SvgThumbnailProvider;
namespace SvgThumbnailProviderUnitTests
{
[TestClass]
public class SvgThumbnailProviderTests
{
[TestMethod]
public void LoadSimpleSVG_ShouldReturnNonNullBitmap()
{
var svgBuilder = new StringBuilder();
svgBuilder.AppendLine("<svg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">");
svgBuilder.AppendLine("\t<circle cx=\"50\" cy=\"50\" r=\"50\">");
svgBuilder.AppendLine("\t</circle>");
svgBuilder.AppendLine("</svg>");
Bitmap thumbnail = SvgThumbnailProvider.SvgThumbnailProvider.GetThumbnail(svgBuilder.ToString(), 256);
Assert.IsTrue(thumbnail != null);
}
[TestMethod]
public void CheckBlockedElements_ShouldReturnNullBitmap_IfBlockedElementsIsPresentInNestedLevel()
{
var svgBuilder = new StringBuilder();
svgBuilder.AppendLine("<svg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">");
svgBuilder.AppendLine("\t<circle cx=\"50\" cy=\"50\" r=\"50\">");
svgBuilder.AppendLine("\t\t<script>alert(\"valid-message\")</script>");
svgBuilder.AppendLine("\t</circle>");
svgBuilder.AppendLine("</svg>");
Bitmap thumbnail = SvgThumbnailProvider.SvgThumbnailProvider.GetThumbnail(svgBuilder.ToString(), 256);
Assert.IsTrue(thumbnail == null);
}
[TestMethod]
public void CheckNoSvg_ShouldReturnNullBitmap()
{
var svgBuilder = new StringBuilder();
svgBuilder.AppendLine("<p>foo</p>");
Bitmap thumbnail = SvgThumbnailProvider.SvgThumbnailProvider.GetThumbnail(svgBuilder.ToString(), 256);
Assert.IsTrue(thumbnail == null);
}
[TestMethod]
public void CheckNoSvgEmptyString_ShouldReturnNullBitmap()
{
Bitmap thumbnail = SvgThumbnailProvider.SvgThumbnailProvider.GetThumbnail("", 256);
Assert.IsTrue(thumbnail == null);
}
[TestMethod]
public void CheckNoSvgNullString_ShouldReturnNullBitmap()
{
Bitmap thumbnail = SvgThumbnailProvider.SvgThumbnailProvider.GetThumbnail(null, 256);
Assert.IsTrue(thumbnail == null);
}
[TestMethod]
public void CheckZeroSizedThumbnail_ShouldReturnNullBitmap()
{
string content = "<svg></svg>";
Bitmap thumbnail = SvgThumbnailProvider.SvgThumbnailProvider.GetThumbnail(content, 0);
Assert.IsTrue(thumbnail == null);
}
[TestMethod]
public void CheckBlockedElements_ShouldReturnBitmap_HTMLWrapped()
{
var svgBuilder = new StringBuilder();
svgBuilder.AppendLine("<html>");
svgBuilder.AppendLine("<head>");
svgBuilder.AppendLine("<meta http-equiv=\"X-UA-Compatible\" content=\"IE=Edge\">");
svgBuilder.AppendLine("<meta http-equiv=\"Content-Type\" content=\"text/html\" charset=\"utf-8\">");
svgBuilder.AppendLine("</head>");
svgBuilder.AppendLine("<body>");
svgBuilder.AppendLine("<svg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">");
svgBuilder.AppendLine("<circle cx=\"50\" cy=\"50\" r=\"50\">");
svgBuilder.AppendLine("</circle>");
svgBuilder.AppendLine("</svg>");
svgBuilder.AppendLine("</body>");
svgBuilder.AppendLine("</html>");
Bitmap thumbnail = SvgThumbnailProvider.SvgThumbnailProvider.GetThumbnail(svgBuilder.ToString(), 256);
Assert.IsTrue(thumbnail != null);
}
[TestMethod]
public void GetThumbnail_ValidStreamSVG()
{
var svgBuilder = new StringBuilder();
svgBuilder.AppendLine("<svg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">");
svgBuilder.AppendLine("<circle cx=\"50\" cy=\"50\" r=\"50\">");
svgBuilder.AppendLine("</circle>");
svgBuilder.AppendLine("</svg>");
SvgThumbnailProvider.SvgThumbnailProvider provider = new SvgThumbnailProvider.SvgThumbnailProvider();
provider.Initialize(GetMockStream(svgBuilder.ToString()), 0);
IntPtr bitmap;
WTS_ALPHATYPE alphaType;
provider.GetThumbnail(256, out bitmap, out alphaType);
Assert.IsTrue(bitmap != IntPtr.Zero);
Assert.IsTrue(alphaType == WTS_ALPHATYPE.WTSAT_RGB);
}
[TestMethod]
public void GetThumbnail_ValidStreamHTML()
{
var svgBuilder = new StringBuilder();
svgBuilder.AppendLine("<html>");
svgBuilder.AppendLine("<head>");
svgBuilder.AppendLine("<meta http-equiv=\"X-UA-Compatible\" content=\"IE=Edge\">");
svgBuilder.AppendLine("<meta http-equiv=\"Content-Type\" content=\"text/html\" charset=\"utf-8\">");
svgBuilder.AppendLine("</head>");
svgBuilder.AppendLine("<body>");
svgBuilder.AppendLine("<svg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">");
svgBuilder.AppendLine("<circle cx=\"50\" cy=\"50\" r=\"50\">");
svgBuilder.AppendLine("</circle>");
svgBuilder.AppendLine("</svg>");
svgBuilder.AppendLine("</body>");
svgBuilder.AppendLine("</html>");
SvgThumbnailProvider.SvgThumbnailProvider provider = new SvgThumbnailProvider.SvgThumbnailProvider();
provider.Initialize(GetMockStream(svgBuilder.ToString()), 0);
IntPtr bitmap;
WTS_ALPHATYPE alphaType;
provider.GetThumbnail(256, out bitmap, out alphaType);
Assert.IsTrue(bitmap != IntPtr.Zero);
Assert.IsTrue(alphaType == WTS_ALPHATYPE.WTSAT_RGB);
}
private IStream GetMockStream(string streamData)
{
var mockStream = new Mock<IStream>();
var streamBytes = Encoding.UTF8.GetBytes(streamData);
var streamMock = new Mock<IStream>();
var firstCall = true;
streamMock
.Setup(x => x.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
.Callback<byte[], int, IntPtr>((buffer, countToRead, bytesReadPtr) =>
{
if (firstCall)
{
Array.Copy(streamBytes, 0, buffer, 0, streamBytes.Length);
Marshal.WriteInt32(bytesReadPtr, streamBytes.Length);
firstCall = false;
}
else
{
Marshal.WriteInt32(bytesReadPtr, 0);
}
});
return streamMock.Object;
}
}
}

View File

@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\..\..\..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>UnitTests_SvgThumbnailProvider</RootNamespace>
<AssemblyName>UnitTests-SvgThumbnailProvider</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\..\..\..\packages\Castle.Core.4.4.0\lib\net45\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\..\..\packages\MSTest.TestFramework.2.1.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\..\..\packages\MSTest.TestFramework.2.1.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.14.0.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\..\..\..\packages\Moq.4.14.5\lib\net45\Moq.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\..\..\packages\System.Runtime.CompilerServices.Unsafe.4.5.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\..\..\packages\System.Threading.Tasks.Extensions.4.5.1\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="SvgThumbnailProviderTests.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Common\PreviewHandlerCommon.csproj">
<Project>{af2349b8-e5b6-4004-9502-687c1c7730b1}</Project>
<Name>PreviewHandlerCommon</Name>
</ProjectReference>
<ProjectReference Include="..\SvgThumbnailProvider\SvgThumbnailProvider.csproj">
<Project>{8ffe09da-fa4f-4ee1-b3a2-ad5497fbd1ad}</Project>
<Name>SvgThumbnailProvider</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.targets'))" />
</Target>
<Import Project="..\..\..\..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\..\..\..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.targets')" />
</Project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Castle.Core" version="4.4.0" targetFramework="net472" />
<package id="Moq" version="4.14.5" targetFramework="net472" />
<package id="MSTest.TestAdapter" version="2.1.1" targetFramework="net472" />
<package id="MSTest.TestFramework" version="2.1.1" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.0" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.1" targetFramework="net472" />
</packages>