Skip to content

Gdi+ capability check revision #511

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Source/Svg.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@
</ItemGroup>

<!-- Mac specific include -->
<!--ItemGroup>
<!-- <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.2'">
<PackageReference Include="runtime.osx.10.10-x64.CoreCompat.System.Drawing" Version="5.6.20" />
</ItemGroup-->
</ItemGroup> -->

<ItemGroup>
<Compile Remove=".\External\ExCSS\Parser.generated.cs" />
Expand Down
316 changes: 170 additions & 146 deletions Source/SvgDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,68 @@ public virtual TSvgElement GetElementById<TSvgElement>(string id) where TSvgElem
return (this.GetElementById(id) as TSvgElement);
}

/// <summary>
/// Validate whether the system has GDI+ capabilities (non Windows related).
/// </summary>
/// <returns>Boolean whether the system is capable of using GDI+</returns>
public static bool SystemIsGdiPlusCapable()
{
try
{
EnsureSystemIsGdiPlusCapable();
}
catch(SvgGdiPlusCannotBeLoadedException)
{
return false;
}
return true;
}

/// <summary>
/// Ensure that the running system is GDI capable, if not this will yield a
/// SvgGdiPlusCannotBeLoadedException exception.
/// </summary>
public static void EnsureSystemIsGdiPlusCapable()
{
try
{
var matrix = new Matrix(0, 0, 0, 0, 0, 0);
}
// GDI+ loading errors will result in TypeInitializationExceptions,
// for readability we will catch and wrap the error
catch (Exception e)
{
if (ExceptionCaughtIsGdiPlusRelated(e))
{
// Throw only the customized exception if we are sure GDI+ is causing the problem
throw new SvgGdiPlusCannotBeLoadedException(e);
}
//Ignore any other form of error
}
}

/// <summary>
/// Check if the current exception or one of its children is the targeted GDI+ exception.
/// It can be hidden in one of the InnerExceptions, so we need to iterate over them.
/// </summary>
/// <param name="e">The exception to validate against the GDI+ check</param>
private static bool ExceptionCaughtIsGdiPlusRelated(Exception e)
{
var currE = e;
int cnt = 0; // Keep track of depth to prevent endless-loops
while (currE != null && cnt < 10)
{
var typeException = currE as DllNotFoundException;
if (typeException?.Message?.LastIndexOf("libgdiplus", StringComparison.OrdinalIgnoreCase) > -1)
{
return true;
}
currE = currE.InnerException;
cnt++;
}
return false;
}

/// <summary>
/// Opens the document at the specified path and loads the SVG contents.
/// </summary>
Expand Down Expand Up @@ -280,181 +342,143 @@ public static SvgDocument Open(string path)

private static T Open<T>(XmlReader reader) where T : SvgDocument, new()
{
try
{
var elementStack = new Stack<SvgElement>();
bool elementEmpty;
SvgElement element = null;
SvgElement parent;
T svgDocument = null;
var elementFactory = new SvgElementFactory();
EnsureSystemIsGdiPlusCapable(); //Validate whether the GDI+ can be loaded, this will yield an exception if not
var elementStack = new Stack<SvgElement>();
bool elementEmpty;
SvgElement element = null;
SvgElement parent;
T svgDocument = null;
var elementFactory = new SvgElementFactory();

var styles = new List<ISvgNode>();
var styles = new List<ISvgNode>();

while (reader.Read())
while (reader.Read())
{
try
{
try
switch (reader.NodeType)
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
// Does this element have a value or children
// (Must do this check here before we progress to another node)
elementEmpty = reader.IsEmptyElement;
// Create element
if (elementStack.Count > 0)
{
element = elementFactory.CreateElement(reader, svgDocument);
}
else
{
svgDocument = elementFactory.CreateDocument<T>(reader);
element = svgDocument;
}
case XmlNodeType.Element:
// Does this element have a value or children
// (Must do this check here before we progress to another node)
elementEmpty = reader.IsEmptyElement;
// Create element
if (elementStack.Count > 0)
{
element = elementFactory.CreateElement(reader, svgDocument);
}
else
{
svgDocument = elementFactory.CreateDocument<T>(reader);
element = svgDocument;
}

// Add to the parents children
if (elementStack.Count > 0)
// Add to the parents children
if (elementStack.Count > 0)
{
parent = elementStack.Peek();
if (parent != null && element != null)
{
parent = elementStack.Peek();
if (parent != null && element != null)
{
parent.Children.Add(element);
parent.Nodes.Add(element);
}
parent.Children.Add(element);
parent.Nodes.Add(element);
}
}

// Push element into stack
elementStack.Push(element);
// Push element into stack
elementStack.Push(element);

// Need to process if the element is empty
if (elementEmpty)
{
goto case XmlNodeType.EndElement;
}
// Need to process if the element is empty
if (elementEmpty)
{
goto case XmlNodeType.EndElement;
}

break;
case XmlNodeType.EndElement:
break;
case XmlNodeType.EndElement:

// Pop the element out of the stack
element = elementStack.Pop();
// Pop the element out of the stack
element = elementStack.Pop();

if (element.Nodes.OfType<SvgContentNode>().Any())
{
element.Content = (from e in element.Nodes select e.Content).Aggregate((p, c) => p + c);
}
else
{
element.Nodes.Clear(); // No sense wasting the space where it isn't needed
}
if (element.Nodes.OfType<SvgContentNode>().Any())
{
element.Content = (from e in element.Nodes select e.Content).Aggregate((p, c) => p + c);
}
else
{
element.Nodes.Clear(); // No sense wasting the space where it isn't needed
}

var unknown = element as SvgUnknownElement;
if (unknown != null && unknown.ElementName == "style")
{
styles.Add(unknown);
}
break;
case XmlNodeType.CDATA:
case XmlNodeType.Text:
element = elementStack.Peek();
element.Nodes.Add(new SvgContentNode() { Content = reader.Value });
break;
case XmlNodeType.EntityReference:
reader.ResolveEntity();
element = elementStack.Peek();
element.Nodes.Add(new SvgContentNode() { Content = reader.Value });
break;
}
}
catch (Exception exc)
{
Trace.TraceError(exc.Message);
if (ExceptionCaughtIsGdiPlusRelated(exc)) { throw; } // GDI+ errors should be rethrown
var unknown = element as SvgUnknownElement;
if (unknown != null && unknown.ElementName == "style")
{
styles.Add(unknown);
}
break;
case XmlNodeType.CDATA:
case XmlNodeType.Text:
element = elementStack.Peek();
element.Nodes.Add(new SvgContentNode() { Content = reader.Value });
break;
case XmlNodeType.EntityReference:
reader.ResolveEntity();
element = elementStack.Peek();
element.Nodes.Add(new SvgContentNode() { Content = reader.Value });
break;
}
}
catch (Exception exc)
{
Trace.TraceError(exc.Message);
}
}

if (styles.Any())
if (styles.Any())
{
var cssTotal = styles.Select((s) => s.Content).Aggregate((p, c) => p + Environment.NewLine + c);
var cssParser = new Parser();
var sheet = cssParser.Parse(cssTotal);
AggregateSelectorList aggList;
IEnumerable<BaseSelector> selectors;
IEnumerable<SvgElement> elemsToStyle;

foreach (var rule in sheet.StyleRules)
{
var cssTotal = styles.Select((s) => s.Content).Aggregate((p, c) => p + Environment.NewLine + c);
var cssParser = new Parser();
var sheet = cssParser.Parse(cssTotal);
AggregateSelectorList aggList;
IEnumerable<BaseSelector> selectors;
IEnumerable<SvgElement> elemsToStyle;

foreach (var rule in sheet.StyleRules)
aggList = rule.Selector as AggregateSelectorList;
if (aggList != null && aggList.Delimiter == ",")
{
aggList = rule.Selector as AggregateSelectorList;
if (aggList != null && aggList.Delimiter == ",")
{
selectors = aggList;
}
else
{
selectors = Enumerable.Repeat(rule.Selector, 1);
}
selectors = aggList;
}
else
{
selectors = Enumerable.Repeat(rule.Selector, 1);
}

foreach (var selector in selectors)
foreach (var selector in selectors)
{
try
{
try
{
var rootNode = new NonSvgElement();
rootNode.Children.Add(svgDocument);
var rootNode = new NonSvgElement();
rootNode.Children.Add(svgDocument);

elemsToStyle = rootNode.QuerySelectorAll(rule.Selector.ToString(), elementFactory);
foreach (var elem in elemsToStyle)
elemsToStyle = rootNode.QuerySelectorAll(rule.Selector.ToString(), elementFactory);
foreach (var elem in elemsToStyle)
{
foreach (var decl in rule.Declarations)
{
foreach (var decl in rule.Declarations)
{
elem.AddStyle(decl.Name, decl.Term.ToString(), rule.Selector.GetSpecificity());
}
elem.AddStyle(decl.Name, decl.Term.ToString(), rule.Selector.GetSpecificity());
}
}
catch (Exception ex)
{
Trace.TraceWarning(ex.Message);
if (ExceptionCaughtIsGdiPlusRelated(ex)) { throw; } // GDI+ errors should be rethrown
}
}
catch (Exception ex)
{
Trace.TraceWarning(ex.Message);
}
}
}

if (svgDocument != null) FlushStyles(svgDocument);
return svgDocument;
}
// GDI+ loading errors will result in TypeInitializationExceptions,
// for readability we will catch and wrap the error
catch (Exception e)
{
if (ExceptionCaughtIsGdiPlusRelated(e))
{
// Throw only the customized exception if we are sure GDI+ is causing the problem
throw new SvgGdiPlusCannotBeLoadedException(e);
}
// No wrapping, just rethrow the exception
throw;
}
}

/// <summary>
/// Check if the current exception or one of its children is the targeted GDI+ exception.
/// It can be hidden in one of the InnerExceptions, so we need to iterate over them.
/// </summary>
/// <param name="e">The exception to validate against the GDI+ check</param>
private static bool ExceptionCaughtIsGdiPlusRelated(Exception e)
{
var currE = e;
int cnt = 0; // Keep track of depth to prevent endless-loops
while (currE != null && cnt < 10)
{
var typeException = currE as DllNotFoundException;
if (typeException?.Message?.LastIndexOf("libgdiplus", StringComparison.OrdinalIgnoreCase) > -1)
{
return true;
}
currE = currE.InnerException;
cnt++;
}
return false;
if (svgDocument != null) FlushStyles(svgDocument);
return svgDocument;
}

private static void FlushStyles(SvgElement elem)
Expand Down
26 changes: 26 additions & 0 deletions Tests/Svg.UnitTests/GdiPlusTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using NUnit.Framework;

namespace Svg.UnitTests
{

/// <summary>
/// Simple testing of the GDI+ capabilities, just to ensure whether the checks are inmplemented correctly
/// </summary>
[TestFixture]
public class GdiPlusTests : SvgTestHelper
{
[Test]
public void GdiPlus_QueryCapability_YieldsTrue()
{
Assert.True(SvgDocument.SystemIsGdiPlusCapable(), "The gdiplus check should yield true, please validate gdi+ capabilities");
}

[Test]
public void GdiPlus_EnsureCapability_YieldsNoError()
{
SvgDocument.EnsureSystemIsGdiPlusCapable();
//This should not cause problems
}

}
}