diff --git a/.gitmodules b/.gitmodules
index 63bc5f5de3..24ccfc3259 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,7 @@
[submodule "src/Perspex.ReactiveUI/src"]
path = src/Perspex.ReactiveUI/src
url = https://github.com/reactiveui/ReactiveUI.git
+[submodule "src/Perspex.HtmlRenderer/external"]
+ path = src/Perspex.HtmlRenderer/external
+ url = https://github.com/Perspex/HTML-Renderer.git
+ branch = perspex-pcl
diff --git a/Perspex.sln b/Perspex.sln
index de7af4b229..54c810f636 100644
--- a/Perspex.sln
+++ b/Perspex.sln
@@ -94,6 +94,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Markup.Xaml.UnitTes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Cairo.RenderTests", "tests\Perspex.RenderTests\Perspex.Cairo.RenderTests.csproj", "{E106CF37-4066-4615-B684-172A6D30B058}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.HtmlRenderer", "src\Perspex.HtmlRenderer\Perspex.HtmlRenderer.csproj", "{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -224,6 +226,10 @@ Global
{E106CF37-4066-4615-B684-172A6D30B058}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E106CF37-4066-4615-B684-172A6D30B058}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E106CF37-4066-4615-B684-172A6D30B058}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/samples/TestApplication/Program.cs b/samples/TestApplication/Program.cs
index de07b8d692..54f7686e8c 100644
--- a/samples/TestApplication/Program.cs
+++ b/samples/TestApplication/Program.cs
@@ -2,11 +2,13 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.IO;
using System.Reactive.Linq;
using Perspex;
using Perspex.Animation;
using Perspex.Collections;
using Perspex.Controls;
+using Perspex.Controls.Html;
using Perspex.Controls.Primitives;
using Perspex.Controls.Shapes;
using Perspex.Controls.Templates;
@@ -219,6 +221,7 @@ namespace TestApplication
{
ButtonsTab(),
TextTab(),
+ HtmlTab(),
ImagesTab(),
ListsTab(),
LayoutTab(),
@@ -365,6 +368,30 @@ namespace TestApplication
return result;
}
+ private static TabItem HtmlTab()
+ {
+ var htmlText =
+ new StreamReader(typeof (Program).Assembly.GetManifestResourceStream("TestApplication.html.htm"))
+ .ReadToEnd();
+ return new TabItem
+ {
+ Header = "Html",
+ Content = new ScrollViewer()
+ {
+ Width = 600,
+ MaxHeight = 600,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ CanScrollHorizontally = false,
+ VerticalScrollBarVisibility = ScrollBarVisibility.Visible,
+ Content =
+ new HtmlLabel()
+ {
+ Text = htmlText
+ }
+ }
+ };
+ }
+
private static TabItem TextTab()
{
return new TabItem
diff --git a/samples/TestApplication/TestApplication.csproj b/samples/TestApplication/TestApplication.csproj
index 1a3a6287fc..463e885e2a 100644
--- a/samples/TestApplication/TestApplication.csproj
+++ b/samples/TestApplication/TestApplication.csproj
@@ -103,6 +103,10 @@
{7062AE20-5DCC-4442-9645-8195BDECE63E}
Perspex.Diagnostics
+
+ {5fb2b005-0a7f-4dad-add4-3ed01444e63d}
+ Perspex.HtmlRenderer
+
{62024B2D-53EB-4638-B26B-85EEAA54866E}
Perspex.Input
@@ -144,6 +148,7 @@
PreserveNewest
+
diff --git a/samples/TestApplication/html.htm b/samples/TestApplication/html.htm
new file mode 100644
index 0000000000..85da0cdb87
--- /dev/null
+++ b/samples/TestApplication/html.htm
@@ -0,0 +1,128 @@
+
+
+ Intro
+
+
+
+
+ HTML Renderer Project - Perspex port
+
+ Beta support
+
+
+
+
+
+ Lightweight (~300K).
+ High performance and low memory footprint.
+ Extendable and configurable.
+
+
+ Limitations
+
+
+ - All HTML end tags marked as
+ optional should be there. No problem with tags marked as forbidden.
+
+
+
+ On the roadmap
+ Of course it's not quite finished yet. Here are some of the important things to
+ do.
+
+ - Better performance
+ - Support of position CSS property
+ - Support of height and min-height CSS property
+ - Better tables support, especially layouts
+ - Support image align
+ - Handle :hover selector
+ - Selection by shift+arrows
+ - Better HTML tag parsing (optional closing tags)
+ - More styles support
+
+
+ Vision
+
+
+ - Most complete static HTML Renderer (no java script).
+ - Commercial web browser performance level.
+
+
+
+ 2015 - Nikita Tsukanov (Perspex Port)
+
+
+
+ https://perspex.github.io/
+
+
+
+ 2012 - Arthur Teplitzki
+
+
+ http://TheArtOfDev.com
+
+
+ 2009 - Jose Manuel Menendez Poo
+
+
+ www.menendezpoo.com
+
+
+
+
\ No newline at end of file
diff --git a/src/Perspex.HtmlRenderer/Adapters/BrushAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/BrushAdapter.cs
new file mode 100644
index 0000000000..4c8b06345e
--- /dev/null
+++ b/src/Perspex.HtmlRenderer/Adapters/BrushAdapter.cs
@@ -0,0 +1,47 @@
+// "Therefore those skilled at the unorthodox
+// are infinite as heaven and earth,
+// inexhaustible as the great rivers.
+// When they come to an end,
+// they begin again,
+// like the days and months;
+// they die and are reborn,
+// like the four seasons."
+//
+// - Sun Tsu,
+// "The Art of War"
+
+using Perspex.Media;
+using TheArtOfDev.HtmlRenderer.Adapters;
+
+namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
+{
+ ///
+ /// Adapter for Perspex brushes.
+ ///
+ internal sealed class BrushAdapter : RBrush
+ {
+ ///
+ /// The actual Perspex brush instance.
+ ///
+ private readonly Brush _brush;
+
+ ///
+ /// Init.
+ ///
+ public BrushAdapter(Brush brush)
+ {
+ _brush = brush;
+ }
+
+ ///
+ /// The actual Perspex brush instance.
+ ///
+ public Brush Brush
+ {
+ get { return _brush; }
+ }
+
+ public override void Dispose()
+ { }
+ }
+}
\ No newline at end of file
diff --git a/src/Perspex.HtmlRenderer/Adapters/ContextMenuAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/ContextMenuAdapter.cs
new file mode 100644
index 0000000000..abc64e7047
--- /dev/null
+++ b/src/Perspex.HtmlRenderer/Adapters/ContextMenuAdapter.cs
@@ -0,0 +1,51 @@
+// "Therefore those skilled at the unorthodox
+// are infinite as heaven and earth,
+// inexhaustible as the great rivers.
+// When they come to an end,
+// they begin again,
+// like the days and months;
+// they die and are reborn,
+// like the four seasons."
+//
+// - Sun Tsu,
+// "The Art of War"
+
+using System;
+using TheArtOfDev.HtmlRenderer.Adapters;
+using TheArtOfDev.HtmlRenderer.Adapters.Entities;
+
+namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
+{
+ ///
+ /// Adapter for Perspex context menu for core.
+ ///
+ internal sealed class NullContextMenuAdapter : RContextMenu
+ {
+ //TODO: actually implement context menu
+
+ private int _itemCount;
+ public override int ItemsCount => _itemCount;
+ public override void AddDivider()
+ {
+
+ }
+
+ public override void AddItem(string text, bool enabled, EventHandler onClick)
+ {
+ _itemCount++;
+ }
+
+ public override void RemoveLastDivider()
+ {
+ _itemCount++;
+ }
+
+ public override void Show(RControl parent, RPoint location)
+ {
+ }
+
+ public override void Dispose()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs
new file mode 100644
index 0000000000..f0f84cb64f
--- /dev/null
+++ b/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs
@@ -0,0 +1,108 @@
+// "Therefore those skilled at the unorthodox
+// are infinite as heaven and earth,
+// inexhaustible as the great rivers.
+// When they come to an end,
+// they begin again,
+// like the days and months;
+// they die and are reborn,
+// like the four seasons."
+//
+// - Sun Tsu,
+// "The Art of War"
+
+using Perspex.Controls;
+using Perspex.Controls.Html;
+using Perspex.Input;
+using TheArtOfDev.HtmlRenderer.Adapters;
+using TheArtOfDev.HtmlRenderer.Adapters.Entities;
+using TheArtOfDev.HtmlRenderer.Core.Utils;
+using TheArtOfDev.HtmlRenderer.Perspex.Utilities;
+// ReSharper disable ConvertPropertyToExpressionBody
+
+namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
+{
+ ///
+ /// Adapter for Perspex Control for core.
+ ///
+ internal sealed class ControlAdapter : RControl
+ {
+ ///
+ /// the underline Perspex control.
+ ///
+ private readonly Control _control;
+
+ ///
+ /// Init.
+ ///
+ public ControlAdapter(Control control)
+ : base(PerspexAdapter.Instance)
+ {
+ ArgChecker.AssertArgNotNull(control, "control");
+
+ _control = control;
+ }
+
+ ///
+ /// Get the underline Perspex control
+ ///
+ public Control Control
+ {
+ get { return _control; }
+ }
+
+ public override RPoint MouseLocation
+ {
+ get
+ {
+ return Util.Convert(MouseDevice.Instance.GetPosition(_control));
+ }
+ }
+
+ private bool _leftMouseButton;
+ public override bool LeftMouseButton => (_control as HtmlControl)?.LeftMouseButton ?? false;
+
+ public override bool RightMouseButton
+ {
+ get
+ {
+ return false;
+ //TODO: Implement right mouse click
+ //return Mouse.RightButton == MouseButtonState.Pressed;
+ }
+ }
+
+ public override void SetCursorDefault()
+ {
+ _control.Cursor = new Cursor(StandardCursorType.Arrow);
+ }
+
+ public override void SetCursorHand()
+ {
+ _control.Cursor = new Cursor(StandardCursorType.Hand);
+ }
+
+ public override void SetCursorIBeam()
+ {
+ _control.Cursor = new Cursor(StandardCursorType.Ibeam);
+ }
+
+ public override void DoDragDropCopy(object dragDropData)
+ {
+ //TODO: Implement DragDropCopy
+ //DragDrop.DoDragDrop(_control, dragDropData, DragDropEffects.Copy);
+ }
+
+ public override void MeasureString(string str, RFont font, double maxWidth, out int charFit, out double charFitWidth)
+ {
+ using (var g = new GraphicsAdapter())
+ {
+ g.MeasureString(str, font, maxWidth, out charFit, out charFitWidth);
+ }
+ }
+
+ public override void Invalidate()
+ {
+ _control.InvalidateVisual();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Perspex.HtmlRenderer/Adapters/FontAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/FontAdapter.cs
new file mode 100644
index 0000000000..1e645cd96c
--- /dev/null
+++ b/src/Perspex.HtmlRenderer/Adapters/FontAdapter.cs
@@ -0,0 +1,106 @@
+// "Therefore those skilled at the unorthodox
+// are infinite as heaven and earth,
+// inexhaustible as the great rivers.
+// When they come to an end,
+// they begin again,
+// like the days and months;
+// they die and are reborn,
+// like the four seasons."
+//
+// - Sun Tsu,
+// "The Art of War"
+
+using Perspex.Media;
+using TheArtOfDev.HtmlRenderer.Adapters;
+using TheArtOfDev.HtmlRenderer.Adapters.Entities;
+
+namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
+{
+ ///
+ /// Adapter for Perspex Font.
+ ///
+ internal sealed class FontAdapter : RFont
+ {
+ public RFontStyle Style { get; }
+
+ #region Fields and Consts
+
+
+ ///
+ /// the size of the font
+ ///
+ private readonly double _size;
+
+ ///
+ /// the vertical offset of the font underline location from the top of the font.
+ ///
+ private readonly double _underlineOffset = -1;
+
+ ///
+ /// Cached font height.
+ ///
+ private readonly double _height = -1;
+
+ ///
+ /// Cached font whitespace width.
+ ///
+ private double _whitespaceWidth = -1;
+
+
+ #endregion
+
+
+ ///
+ /// Init.
+ ///
+ public FontAdapter(string fontFamily, double size, RFontStyle style)
+ {
+ Style = style;
+ Name = fontFamily;
+ _size = size;
+ //TODO: Somehow get proper line spacing and underlinePosition
+ var lineSpacing = 1;
+ var underlinePosition = 0;
+
+ _height = 96d / 72d * _size * lineSpacing;
+ _underlineOffset = 96d / 72d * _size * (lineSpacing + underlinePosition);
+
+ }
+
+ public string Name { get; set; }
+
+
+ public override double Size
+ {
+ get { return _size; }
+ }
+
+ public override double UnderlineOffset
+ {
+ get { return _underlineOffset; }
+ }
+
+ public override double Height
+ {
+ get { return _height; }
+ }
+
+ public override double LeftPadding
+ {
+ get { return _height / 6f; }
+ }
+
+ public override double GetWhitespaceWidth(RGraphics graphics)
+ {
+ if (_whitespaceWidth < 0)
+ {
+ _whitespaceWidth = graphics.MeasureString(" ", this).Width;
+ }
+ return _whitespaceWidth;
+ }
+
+ public FontStyle FontStyle => Style.HasFlag(RFontStyle.Italic) ? FontStyle.Italic : FontStyle.Normal;
+
+ public FontWeight Weight => Style.HasFlag(RFontStyle.Bold) ? FontWeight.Bold : FontWeight.Normal;
+ }
+}
\ No newline at end of file
diff --git a/src/Perspex.HtmlRenderer/Adapters/FontFamilyAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/FontFamilyAdapter.cs
new file mode 100644
index 0000000000..eefc632dcf
--- /dev/null
+++ b/src/Perspex.HtmlRenderer/Adapters/FontFamilyAdapter.cs
@@ -0,0 +1,29 @@
+// "Therefore those skilled at the unorthodox
+// are infinite as heaven and earth,
+// inexhaustible as the great rivers.
+// When they come to an end,
+// they begin again,
+// like the days and months;
+// they die and are reborn,
+// like the four seasons."
+//
+// - Sun Tsu,
+// "The Art of War"
+
+using TheArtOfDev.HtmlRenderer.Adapters;
+
+namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
+{
+ ///
+ /// Adapter for Perspex Font family object for core.
+ ///
+ internal sealed class FontFamilyAdapter : RFontFamily
+ {
+ public FontFamilyAdapter(string fontFamily)
+ {
+ Name = fontFamily;
+ }
+
+ public override string Name { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs
new file mode 100644
index 0000000000..415363a5c0
--- /dev/null
+++ b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs
@@ -0,0 +1,295 @@
+// "Therefore those skilled at the unorthodox
+// are infinite as heaven and earth,
+// inexhaustible as the great rivers.
+// When they come to an end,
+// they begin again,
+// like the days and months;
+// they die and are reborn,
+// like the four seasons."
+//
+// - Sun Tsu,
+// "The Art of War"
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using Perspex;
+using Perspex.Media;
+using TheArtOfDev.HtmlRenderer.Adapters;
+using TheArtOfDev.HtmlRenderer.Adapters.Entities;
+using TheArtOfDev.HtmlRenderer.Core.Utils;
+using TheArtOfDev.HtmlRenderer.Perspex.Utilities;
+
+namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
+{
+ ///
+ /// Adapter for Perspex Graphics.
+ ///
+ internal sealed class GraphicsAdapter : RGraphics
+ {
+ #region Fields and Consts
+
+ ///
+ /// The wrapped Perspex graphics object
+ ///
+ private readonly IDrawingContext _g;
+
+ ///
+ /// if to release the graphics object on dispose
+ ///
+ private readonly bool _releaseGraphics;
+
+ #endregion
+
+
+ private Stack _clipStack = new Stack();
+
+
+ ///
+ /// Init.
+ ///
+ /// the Perspex graphics object to use
+ /// the initial clip of the graphics
+ /// optional: if to release the graphics object on dispose (default - false)
+ public GraphicsAdapter(IDrawingContext g, RRect initialClip, bool releaseGraphics = false)
+ : base(PerspexAdapter.Instance, initialClip)
+ {
+ ArgChecker.AssertArgNotNull(g, "g");
+
+ _g = g;
+ _releaseGraphics = releaseGraphics;
+ }
+
+ ///
+ /// Init.
+ ///
+ public GraphicsAdapter()
+ : base(PerspexAdapter.Instance, RRect.Empty)
+ {
+ _g = null;
+ _releaseGraphics = false;
+ }
+
+
+
+ public override void PopClip()
+ {
+ _clipStack.Pop()?.Dispose();
+ }
+
+ public override void PushClip(RRect rect)
+ {
+ _clipStack.Push(_g.PushClip(Util.Convert(rect)));
+ //_clipStack.Push(rect);
+ //_g.PushClip(new RectangleGeometry(Utils.Convert(rect)));
+ }
+
+ public override void PushClipExclude(RRect rect)
+ {
+ _clipStack.Push(null);
+
+ //TODO: Implement exclude rect, see #128
+ //var geometry = new CombinedGeometry();
+ //geometry.Geometry1 = new RectangleGeometry(Utils.Convert(_clipStack.Peek()));
+ //geometry.Geometry2 = new RectangleGeometry(Utils.Convert(rect));
+ //geometry.GeometryCombineMode = GeometryCombineMode.Exclude;
+
+ //_clipStack.Push(_clipStack.Peek());
+ //_g.PushClip(geometry);
+ }
+
+ public override Object SetAntiAliasSmoothingMode()
+ {
+ return null;
+ }
+
+ public override void ReturnPreviousSmoothingMode(Object prevMode)
+ { }
+
+ public override RSize MeasureString(string str, RFont font)
+ {
+ var text = GetText(str, font);
+ var measure = text.Measure();
+ return new RSize(measure.Width, measure.Height);
+
+ }
+
+ FormattedText GetText(string str, RFont font)
+ {
+ var f = ((FontAdapter)font);
+ return new FormattedText(str, f.Name, font.Size, f.FontStyle, TextAlignment.Left, f.Weight);
+ }
+
+ public override void MeasureString(string str, RFont font, double maxWidth, out int charFit, out double charFitWidth)
+ {
+ var text = GetText(str, font);
+ var fullLength = text.Measure().Width;
+ if (fullLength < maxWidth)
+ {
+ charFitWidth = fullLength;
+ charFit = str.Length;
+ return;
+ }
+
+ int lastLen = 0;
+ double lastMeasure = 0;
+ BinarySearch(len =>
+ {
+ text = GetText(str.Substring(0, len), font);
+ var size = text.Measure().Width;
+ lastMeasure = size;
+ lastLen = len;
+ if (size <= maxWidth)
+ return -1;
+ return 1;
+
+ }, 0, str.Length);
+ if (lastMeasure > maxWidth)
+ {
+ lastLen--;
+ lastMeasure = GetText(str.Substring(0, lastLen), font).Measure().Width;
+ }
+ charFit = lastLen;
+ charFitWidth = lastMeasure;
+
+ }
+
+ private static int BinarySearch(Func condition, int start, int end)
+ {
+ do
+ {
+ int ind = start + (end - start)/2;
+ int res = condition(ind);
+ if (res == 0)
+ return ind;
+ else if (res > 0)
+ {
+ if (start != ind)
+ start = ind;
+ else
+ start = ind + 1;
+ }
+ else
+ end = ind;
+
+ } while (end > start);
+ return -1;
+ }
+
+ public override void DrawString(string str, RFont font, RColor color, RPoint point, RSize size, bool rtl)
+ {
+ var text = GetText(str, font);
+ text.Constraint = Util.Convert(size);
+ _g.DrawText(new SolidColorBrush(Util.Convert(color)), Util.Convert(point), text);
+ }
+
+ public override RBrush GetTextureBrush(RImage image, RRect dstRect, RPoint translateTransformLocation)
+ {
+ //TODO: Implement texture brush
+ return PerspexAdapter.Instance.GetSolidBrush(Util.Convert(Colors.Magenta));
+
+ //var brush = new ImageBrush(((ImageAdapter)image).Image);
+ //brush.Stretch = Stretch.None;
+ //brush.TileMode = TileMode.Tile;
+ //brush.Viewport = Utils.Convert(dstRect);
+ //brush.ViewportUnits = BrushMappingMode.Absolute;
+ //brush.Transform = new TranslateTransform(translateTransformLocation.X, translateTransformLocation.Y);
+ //brush.Freeze();
+ //return new BrushAdapter(brush);
+ }
+
+ public override RGraphicsPath GetGraphicsPath()
+ {
+ return new GraphicsPathAdapter();
+ }
+
+ public override void Dispose()
+ {
+ while (_clipStack.Count != 0)
+ PopClip();
+ if (_releaseGraphics)
+ _g.Dispose();
+ }
+
+
+ #region Delegate graphics methods
+
+ public override void DrawLine(RPen pen, double x1, double y1, double x2, double y2)
+ {
+ x1 = (int)x1;
+ x2 = (int)x2;
+ y1 = (int)y1;
+ y2 = (int)y2;
+
+ var adj = pen.Width;
+ if (Math.Abs(x1 - x2) < .1 && Math.Abs(adj % 2 - 1) < .1)
+ {
+ x1 += .5;
+ x2 += .5;
+ }
+ if (Math.Abs(y1 - y2) < .1 && Math.Abs(adj % 2 - 1) < .1)
+ {
+ y1 += .5;
+ y2 += .5;
+ }
+
+ _g.DrawLine(((PenAdapter)pen).CreatePen(), new Point(x1, y1), new Point(x2, y2));
+ }
+
+ public override void DrawRectangle(RPen pen, double x, double y, double width, double height)
+ {
+ var adj = pen.Width;
+ if (Math.Abs(adj % 2 - 1) < .1)
+ {
+ x += .5;
+ y += .5;
+ }
+ _g.DrawRectange(((PenAdapter) pen).CreatePen(), new Rect(x, y, width, height));
+ }
+
+ public override void DrawRectangle(RBrush brush, double x, double y, double width, double height)
+ {
+ _g.FillRectange(((BrushAdapter) brush).Brush, new Rect(x, y, width, height));
+ }
+
+ public override void DrawImage(RImage image, RRect destRect, RRect srcRect)
+ {
+ _g.DrawImage(((ImageAdapter) image).Image, 1, Util.Convert(srcRect), Util.Convert(destRect));
+ }
+
+ public override void DrawImage(RImage image, RRect destRect)
+ {
+ _g.DrawImage(((ImageAdapter) image).Image, 1, new Rect(0, 0, image.Width, image.Height),
+ Util.Convert(destRect));
+ }
+
+ public override void DrawPath(RPen pen, RGraphicsPath path)
+ {
+ _g.DrawGeometry(null, ((PenAdapter)pen).CreatePen(), ((GraphicsPathAdapter)path).GetClosedGeometry());
+ }
+
+ public override void DrawPath(RBrush brush, RGraphicsPath path)
+ {
+ _g.DrawGeometry(((BrushAdapter)brush).Brush, null, ((GraphicsPathAdapter)path).GetClosedGeometry());
+ }
+
+ public override void DrawPolygon(RBrush brush, RPoint[] points)
+ {
+ if (points != null && points.Length > 0)
+ {
+ var g = new StreamGeometry();
+ using (var context = g.Open())
+ {
+ context.BeginFigure(Util.Convert(points[0]), true);
+ for (int i = 1; i < points.Length; i++)
+ context.LineTo(Util.Convert(points[i]));
+ context.EndFigure(false);
+ }
+
+ _g.DrawGeometry(((BrushAdapter)brush).Brush, null, g);
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Perspex.HtmlRenderer/Adapters/GraphicsPathAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/GraphicsPathAdapter.cs
new file mode 100644
index 0000000000..5ccfa7140c
--- /dev/null
+++ b/src/Perspex.HtmlRenderer/Adapters/GraphicsPathAdapter.cs
@@ -0,0 +1,67 @@
+// "Therefore those skilled at the unorthodox
+// are infinite as heaven and earth,
+// inexhaustible as the great rivers.
+// When they come to an end,
+// they begin again,
+// like the days and months;
+// they die and are reborn,
+// like the four seasons."
+//
+// - Sun Tsu,
+// "The Art of War"
+
+using Perspex;
+using Perspex.Media;
+using TheArtOfDev.HtmlRenderer.Adapters;
+
+namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
+{
+ ///
+ /// Adapter for Perspex graphics path object for core.
+ ///
+ internal sealed class GraphicsPathAdapter : RGraphicsPath
+ {
+ ///
+ /// The actual Perspex graphics geometry instance.
+ ///
+ private readonly StreamGeometry _geometry = new StreamGeometry();
+
+ ///
+ /// The context used in Perspex geometry to render path
+ ///
+ private readonly StreamGeometryContext _geometryContext;
+
+ public GraphicsPathAdapter()
+ {
+ _geometryContext = _geometry.Open();
+ }
+
+ public override void Start(double x, double y)
+ {
+ _geometryContext.BeginFigure(new Point(x, y), true);
+ }
+
+ public override void LineTo(double x, double y)
+ {
+ _geometryContext.LineTo(new Point(x, y));
+ }
+
+ public override void ArcTo(double x, double y, double size, Corner corner)
+ {
+ _geometryContext.ArcTo(new Point(x, y), new Size(size, size), 0, false, SweepDirection.Clockwise);
+ }
+
+ ///
+ /// Close the geometry to so no more path adding is allowed and return the instance so it can be rendered.
+ ///
+ public StreamGeometry GetClosedGeometry()
+ {
+ _geometryContext.EndFigure(true);
+ _geometryContext.Dispose();
+ return _geometry;
+ }
+
+ public override void Dispose()
+ { }
+ }
+}
\ No newline at end of file
diff --git a/src/Perspex.HtmlRenderer/Adapters/ImageAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/ImageAdapter.cs
new file mode 100644
index 0000000000..ed6a41d20b
--- /dev/null
+++ b/src/Perspex.HtmlRenderer/Adapters/ImageAdapter.cs
@@ -0,0 +1,52 @@
+// "Therefore those skilled at the unorthodox
+// are infinite as heaven and earth,
+// inexhaustible as the great rivers.
+// When they come to an end,
+// they begin again,
+// like the days and months;
+// they die and are reborn,
+// like the four seasons."
+//
+// - Sun Tsu,
+// "The Art of War"
+
+using Perspex.Media.Imaging;
+using TheArtOfDev.HtmlRenderer.Adapters;
+
+namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
+{
+ ///
+ /// Adapter for Perspex Image object for core.
+ ///
+ internal sealed class ImageAdapter : RImage
+ {
+ ///
+ /// the underline Perspex image.
+ ///
+ private readonly Bitmap _image;
+
+ ///
+ /// Init.
+ ///
+ public ImageAdapter(Bitmap image)
+ {
+ _image = image;
+ }
+
+ ///
+ /// the underline Perspex image.
+ ///
+ public Bitmap Image => _image;
+
+ public override double Width => _image.PixelWidth;
+
+ public override double Height => _image.PixelHeight;
+
+ public override void Dispose()
+ {
+ //TODO: Implement image disposal
+ /*if (_image.StreamSource != null)
+ _image.StreamSource.Dispose();*/
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Perspex.HtmlRenderer/Adapters/PenAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/PenAdapter.cs
new file mode 100644
index 0000000000..fa0960cf3f
--- /dev/null
+++ b/src/Perspex.HtmlRenderer/Adapters/PenAdapter.cs
@@ -0,0 +1,79 @@
+// "Therefore those skilled at the unorthodox
+// are infinite as heaven and earth,
+// inexhaustible as the great rivers.
+// When they come to an end,
+// they begin again,
+// like the days and months;
+// they die and are reborn,
+// like the four seasons."
+//
+// - Sun Tsu,
+// "The Art of War"
+
+using System.Collections.Generic;
+using Perspex.Media;
+using TheArtOfDev.HtmlRenderer.Adapters;
+using TheArtOfDev.HtmlRenderer.Adapters.Entities;
+
+namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
+{
+ ///
+ /// Adapter for Perspex pens objects for core.
+ ///
+ internal sealed class PenAdapter : RPen
+ {
+ ///
+ /// The actual Perspex brush instance.
+ ///
+ private readonly Brush _brush;
+
+ ///
+ /// the width of the pen
+ ///
+ private double _width;
+
+ private DashStyle _dashStyle;
+
+ ///
+ /// the dash style of the pen
+ ///
+ //private DashStyle _dashStyle = DashStyles.Solid;
+
+ ///
+ /// Init.
+ ///
+ public PenAdapter(Brush brush)
+ {
+ _brush = brush;
+ }
+
+ public override double Width
+ {
+ get { return _width; }
+ set { _width = value; }
+ }
+
+ public override RDashStyle DashStyle
+ {
+ set { DashStyles.TryGetValue(value, out _dashStyle); }
+ }
+
+ private static readonly Dictionary DashStyles = new Dictionary
+ {
+ {RDashStyle.Solid,null },
+ {RDashStyle.Dash, global::Perspex.Media.DashStyle.Dash },
+ {RDashStyle.DashDot, global::Perspex.Media.DashStyle.DashDot },
+ {RDashStyle.DashDotDot, global::Perspex.Media.DashStyle.DashDotDot },
+ {RDashStyle.Dot, global::Perspex.Media.DashStyle.Dot }
+ };
+
+ ///
+ /// Create the actual Perspex pen instance.
+ ///
+ public Pen CreatePen()
+ {
+ var pen = new Pen(_brush, _width, _dashStyle);
+ return pen;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Perspex.HtmlRenderer/Adapters/PerspexAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/PerspexAdapter.cs
new file mode 100644
index 0000000000..67d3c812c6
--- /dev/null
+++ b/src/Perspex.HtmlRenderer/Adapters/PerspexAdapter.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using Perspex;
+using Perspex.Media;
+using Perspex.Media.Imaging;
+using TheArtOfDev.HtmlRenderer.Adapters;
+using TheArtOfDev.HtmlRenderer.Adapters.Entities;
+using TheArtOfDev.HtmlRenderer.Perspex.Utilities;
+
+namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
+{
+ class PerspexAdapter : RAdapter
+ {
+ public static PerspexAdapter Instance { get; } = new PerspexAdapter();
+
+ ///
+ /// List of valid predefined color names in lower-case
+ ///
+ private static readonly Dictionary ColorNameDic = new Dictionary();
+
+
+ static PerspexAdapter()
+ {
+ foreach (var colorProp in typeof(Colors).GetRuntimeProperties()
+ .Where(p=>p.PropertyType == typeof(Color)))
+ {
+ ColorNameDic[colorProp.Name.ToLower()] = (Color)colorProp.GetValue(null);
+ }
+ }
+
+ protected override RColor GetColorInt(string colorName)
+ {
+ Color c;
+ if(!ColorNameDic.TryGetValue(colorName.ToLower(), out c))
+ return RColor.Empty;
+ return Util.Convert(c);
+ }
+
+ protected override RPen CreatePen(RColor color)
+ {
+ return new PenAdapter(GetSolidColorBrush(color));
+ }
+
+ ///
+ /// Get solid color brush for the given color.
+ ///
+ private static Brush GetSolidColorBrush(RColor color)
+ {
+ Brush solidBrush;
+ if (color == RColor.White)
+ solidBrush = Brushes.White;
+ else if (color == RColor.Black)
+ solidBrush = Brushes.Black;
+ else if (color.A < 1)
+ solidBrush = Brushes.Transparent;
+ else
+ solidBrush = new SolidColorBrush(Util.Convert(color));
+ return solidBrush;
+ }
+
+ protected override RBrush CreateSolidBrush(RColor color)
+ {
+ return new BrushAdapter(GetSolidColorBrush(color));
+ }
+
+ protected override RBrush CreateLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle)
+ {
+ var startColor = angle <= 180 ? Util.Convert(color1) : Util.Convert(color2);
+ var endColor = angle <= 180 ? Util.Convert(color2) : Util.Convert(color1);
+ angle = angle <= 180 ? angle : angle - 180;
+ double x = angle < 135 ? Math.Max((angle - 45) / 90, 0) : 1;
+ double y = angle <= 45 ? Math.Max(0.5 - angle / 90, 0) : angle > 135 ? Math.Abs(1.5 - angle / 90) : 0;
+ return new BrushAdapter(new LinearGradientBrush
+ {
+ StartPoint = new Point(x, y),
+ EndPoint = new Point(1 - x, 1 - y),
+ GradientStops =
+ {
+ new GradientStop(startColor, 0),
+ new GradientStop(endColor, 1)
+ }
+ });
+
+ }
+
+ protected override RImage ConvertImageInt(object image)
+ {
+ return image != null ? new ImageAdapter((Bitmap)image) : null;
+ }
+
+ protected override RImage ImageFromStreamInt(Stream memoryStream)
+ {
+ //TODO: Implement bitmap loader
+ return null;
+ }
+
+ protected override RFont CreateFontInt(string family, double size, RFontStyle style)
+ {
+ return new FontAdapter(family, size, style);
+ }
+
+ protected override RFont CreateFontInt(RFontFamily family, double size, RFontStyle style)
+ {
+ return new FontAdapter(family.Name, size, style);
+ }
+ }
+}
diff --git a/src/Perspex.HtmlRenderer/Compat/Attributes.cs b/src/Perspex.HtmlRenderer/Compat/Attributes.cs
new file mode 100644
index 0000000000..87ca0819a1
--- /dev/null
+++ b/src/Perspex.HtmlRenderer/Compat/Attributes.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+internal class CategoryAttribute : Attribute
+{
+ public CategoryAttribute(string s)
+ {
+
+ }
+}
+internal class DescriptionAttribute : Attribute
+{
+ public DescriptionAttribute(string s)
+ {
+
+ }
+}
+
+internal class BrowsableAttribute : Attribute
+{
+ public BrowsableAttribute(bool b)
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/Perspex.HtmlRenderer/Compat/ThreadPool.cs b/src/Perspex.HtmlRenderer/Compat/ThreadPool.cs
new file mode 100644
index 0000000000..140625d189
--- /dev/null
+++ b/src/Perspex.HtmlRenderer/Compat/ThreadPool.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TheArtOfDev.HtmlRenderer.Core.Handlers;
+
+namespace System.Threading
+{
+ class ThreadPool
+ {
+ public static void QueueUserWorkItem(Action