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..d44fd7546a 100644 --- a/samples/TestApplication/Program.cs +++ b/samples/TestApplication/Program.cs @@ -2,6 +2,7 @@ // 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; @@ -219,6 +220,7 @@ namespace TestApplication { ButtonsTab(), TextTab(), + HtmlTab(), ImagesTab(), ListsTab(), LayoutTab(), @@ -365,6 +367,37 @@ 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 = 500, + VerticalScrollBarVisibility = ScrollBarVisibility.Visible, + Content = + new Border + { + Height = 1500, + Child = + new HtmlLabel() + { + + Text = htmlText, + AutoSize = false, + MaxWidth = 500, + MaxHeight = 900 + + } + } + } + }; + } + 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..764e9511c6 --- /dev/null +++ b/samples/TestApplication/html.htm @@ -0,0 +1,170 @@ + + + Additional features + + + + +

+ Additional features +

+
+

+ There are some additional features that you may already discovered about the renderer + core engine.

+

+ Graphic features

+

+ I have always wanted the W3C to add this features to the CSS spec (and so far, not + there yet :)

+
    +
  • Gradients on backgrounds
  • +
  • Rounded corners
  • +
+

+ And I think many many web designers would agree. Is it so hard or what?.

+

+ Background Gradients

+

+ It is a simple two color linear gradient, achieved by the adding of two CSS properties:

+
    +
  1. background-gradient: (#Color) - Second color of the gradient background, + the first one is given by background-color. Not inherited.
  2. +
  3. background-gradient-angle: (number) - Angle (in degrees, clockwise) of + the gradient. Not inherited. Initial value:90
  4. +
+ Some examples + + + + + + + + + + + + + + + + +
+ + + + +
+ 0 degrees + + 45 degrees + + 90 degrees + + 135 degrees + + 180 degrees +
+

+ Rounded corners

+

+ As you may already know, CSS is based on a + Box Model, where every box has it's own set of properties. Since we are + talking abound boxes, why not to make them with rounded corners, almost every + website you visit nowadays makes use of rounded corners, where a not very nice trick + with images and tables must be used.

+

+ In this renderer, the rounded corners are achieved by adding this CSS properties:

+
    +
  • corner-ne-radius: (length) Indicates the radius of the north-east corner. + Not ineritted
  • +
  • corner-se-radius: (length) Indicates the radius of the south-east corner. + Not ineritted
  • +
  • corner-sw-radius: (length) Indicates the radius of the south-west corner. + Not ineritted
  • +
  • corner-nw-radius: (length) Indicates the radius of the north-west corner. + Not ineritted
  • +
  • corner-radius: (length){1,4} Shorthand for the other corner properties. + Not ineritted
  • +
+ + Some examples + + + + + + + + + +
+

+

+

+

+
+ c1 + + c2 + + c3 + + c4 + + c5 +
+
.c1, .c2, .c3, .c4, .c5 { background-color:olive; border:0px; color:white; vertical-align:middle; }
+.c1  { corner-radius: 0px }
+.c2  { corner-radius: 10px }
+.c3  { corner-radius: 0px 10px 10px 0px }
+.c4  { corner-radius: 18px }
+.c5  { corner-radius: 10px; border: outset #bb0 2px; }
+
+ + \ 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..b3908d12e8 --- /dev/null +++ b/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs @@ -0,0 +1,116 @@ +// "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.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 + { + //TODO: Implement + //return Utils.Convert(_control.PointFromScreen(Mouse.GetPosition(_control))); + return new RPoint(0, 0); + } + } + + public override bool LeftMouseButton + { + get + { + return false; + //TODO: Implement + //return Mouse.LeftButton == MouseButtonState.Pressed; + } + } + + public override bool RightMouseButton + { + get + { + return false; + //TODO: Implement + //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 + //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..43b726ec8c --- /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 = 2; + var underlinePosition = 1; + + _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..ef0901692d --- /dev/null +++ b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs @@ -0,0 +1,277 @@ +// "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.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 + + + /// + /// 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() + { + /* + _g.Pop(); + _clipStack.Pop(); + */ + } + + public override void PushClip(RRect rect) + { + //_clipStack.Push(rect); + //_g.PushClip(new RectangleGeometry(Utils.Convert(rect))); + } + + public override void PushClipExclude(RRect rect) + { + //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); + charFit = str.Length; + charFitWidth = text.Measure().Width; + } + + 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); + + //var colorConv = ((BrushAdapter)_adapter.GetSolidBrush(color)).Brush; + + //bool glyphRendered = false; + //GlyphTypeface glyphTypeface = ((FontAdapter)font).GlyphTypeface; + //if (glyphTypeface != null) + //{ + // double width = 0; + // ushort[] glyphs = new ushort[str.Length]; + // double[] widths = new double[str.Length]; + + // int i = 0; + // for (; i < str.Length; i++) + // { + // ushort glyph; + // if (!glyphTypeface.CharacterToGlyphMap.TryGetValue(str[i], out glyph)) + // break; + + // glyphs[i] = glyph; + // width += glyphTypeface.AdvanceWidths[glyph]; + // widths[i] = 96d / 72d * font.Size * glyphTypeface.AdvanceWidths[glyph]; + // } + + // if (i >= str.Length) + // { + // point.Y += glyphTypeface.Baseline * font.Size * 96d / 72d; + // point.X += rtl ? 96d / 72d * font.Size * width : 0; + + // glyphRendered = true; + // var glyphRun = new GlyphRun(glyphTypeface, rtl ? 1 : 0, false, 96d / 72d * font.Size, glyphs, Utils.ConvertRound(point), widths, null, null, null, null, null, null); + // _g.DrawGlyphRun(colorConv, glyphRun); + // } + //} + + //if (!glyphRendered) + //{ + // var formattedText = new FormattedText(str, CultureInfo.CurrentCulture, rtl ? FlowDirection.RightToLeft : FlowDirection.LeftToRight, ((FontAdapter)font).Font, 96d / 72d * font.Size, colorConv); + // point.X += rtl ? formattedText.Width : 0; + // _g.DrawText(formattedText, Utils.ConvertRound(point)); + //} + } + + 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() + { + //TODO: Do something about Dispose + //if (_releaseGraphics) + // _g.Close(); + } + + + #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])); + } + + _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..80797429de --- /dev/null +++ b/src/Perspex.HtmlRenderer/Adapters/GraphicsPathAdapter.cs @@ -0,0 +1,66 @@ +// "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); + 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..c2ef084f4d --- /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 + /*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..1531f058ce --- /dev/null +++ b/src/Perspex.HtmlRenderer/Adapters/PenAdapter.cs @@ -0,0 +1,93 @@ +// "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 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; + + /// + /// 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 + { + //TODO: Implement DashStyles + /* + switch (value) + { + case RDashStyle.Solid: + _dashStyle = DashStyles.Solid; + break; + case RDashStyle.Dash: + _dashStyle = DashStyles.Dash; + break; + case RDashStyle.Dot: + _dashStyle = DashStyles.Dot; + break; + case RDashStyle.DashDot: + _dashStyle = DashStyles.DashDot; + break; + case RDashStyle.DashDotDot: + _dashStyle = DashStyles.DashDotDot; + break; + default: + _dashStyle = DashStyles.Solid; + break; + }*/ + } + } + + /// + /// Create the actual Perspex pen instance. + /// + public Pen CreatePen() + { + var pen = new Pen(_brush, _width); + //pen.DashStyle = _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 cb, object state) + { + Task.Factory.StartNew(() => cb(state)); + } + + public static void QueueUserWorkItem(Action cb) + { + Task.Factory.StartNew(() => cb(null)); + } + } +} diff --git a/src/Perspex.HtmlRenderer/HtmlContainer.cs b/src/Perspex.HtmlRenderer/HtmlContainer.cs new file mode 100644 index 0000000000..729613f5d1 --- /dev/null +++ b/src/Perspex.HtmlRenderer/HtmlContainer.cs @@ -0,0 +1,468 @@ +// "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 Perspex.Input; +using Perspex.Media; +using TheArtOfDev.HtmlRenderer.Adapters.Entities; +using TheArtOfDev.HtmlRenderer.Core; +using TheArtOfDev.HtmlRenderer.Core.Entities; +using TheArtOfDev.HtmlRenderer.Core.Parse; +using TheArtOfDev.HtmlRenderer.Core.Utils; +using TheArtOfDev.HtmlRenderer.Perspex.Adapters; +using TheArtOfDev.HtmlRenderer.Perspex.Utilities; + +namespace Perspex.Controls +{ + /// + /// Low level handling of Html Renderer logic, this class is used by , + /// , and .
+ ///
+ /// + public sealed class HtmlContainer : IDisposable + { + #region Fields and Consts + + /// + /// The internal core html container + /// + private readonly HtmlContainerInt _htmlContainerInt; + + #endregion + + + /// + /// Init. + /// + public HtmlContainer() + { + _htmlContainerInt = new HtmlContainerInt(PerspexAdapter.Instance); + } + + /// + /// Raised when the set html document has been fully loaded.
+ /// Allows manipulation of the html dom, scroll position, etc. + ///
+ public event EventHandler LoadComplete + { + add { _htmlContainerInt.LoadComplete += value; } + remove { _htmlContainerInt.LoadComplete -= value; } + } + + /// + /// Raised when the user clicks on a link in the html.
+ /// Allows canceling the execution of the link. + ///
+ public event EventHandler LinkClicked + { + add { _htmlContainerInt.LinkClicked += value; } + remove { _htmlContainerInt.LinkClicked -= value; } + } + + /// + /// Raised when html renderer requires refresh of the control hosting (invalidation and re-layout). + /// + /// + /// There is no guarantee that the event will be raised on the main thread, it can be raised on thread-pool thread. + /// + public event EventHandler Refresh + { + add { _htmlContainerInt.Refresh += value; } + remove { _htmlContainerInt.Refresh -= value; } + } + + /// + /// Raised when Html Renderer request scroll to specific location.
+ /// This can occur on document anchor click. + ///
+ public event EventHandler ScrollChange + { + add { _htmlContainerInt.ScrollChange += value; } + remove { _htmlContainerInt.ScrollChange -= value; } + } + + /// + /// Raised when an error occurred during html rendering.
+ ///
+ /// + /// There is no guarantee that the event will be raised on the main thread, it can be raised on thread-pool thread. + /// + public event EventHandler RenderError + { + add { _htmlContainerInt.RenderError += value; } + remove { _htmlContainerInt.RenderError -= value; } + } + + /// + /// Raised when a stylesheet is about to be loaded by file path or URI by link element.
+ /// This event allows to provide the stylesheet manually or provide new source (file or Uri) to load from.
+ /// If no alternative data is provided the original source will be used.
+ ///
+ public event EventHandler StylesheetLoad + { + add { _htmlContainerInt.StylesheetLoad += value; } + remove { _htmlContainerInt.StylesheetLoad -= value; } + } + + /// + /// Raised when an image is about to be loaded by file path or URI.
+ /// This event allows to provide the image manually, if not handled the image will be loaded from file or download from URI. + ///
+ public event EventHandler ImageLoad + { + add { _htmlContainerInt.ImageLoad += value; } + remove { _htmlContainerInt.ImageLoad -= value; } + } + + /// + /// The internal core html container + /// + internal HtmlContainerInt HtmlContainerInt + { + get { return _htmlContainerInt; } + } + + /// + /// the parsed stylesheet data used for handling the html + /// + public CssData CssData + { + get { return _htmlContainerInt.CssData; } + } + + /// + /// Gets or sets a value indicating if image asynchronous loading should be avoided (default - false).
+ /// True - images are loaded synchronously during html parsing.
+ /// False - images are loaded asynchronously to html parsing when downloaded from URL or loaded from disk.
+ ///
+ /// + /// Asynchronously image loading allows to unblock html rendering while image is downloaded or loaded from disk using IO + /// ports to achieve better performance.
+ /// Asynchronously image loading should be avoided when the full html content must be available during render, like render to image. + ///
+ public bool AvoidAsyncImagesLoading + { + get { return _htmlContainerInt.AvoidAsyncImagesLoading; } + set { _htmlContainerInt.AvoidAsyncImagesLoading = value; } + } + + /// + /// Gets or sets a value indicating if image loading only when visible should be avoided (default - false).
+ /// True - images are loaded as soon as the html is parsed.
+ /// False - images that are not visible because of scroll location are not loaded until they are scrolled to. + ///
+ /// + /// Images late loading improve performance if the page contains image outside the visible scroll area, especially if there is large + /// amount of images, as all image loading is delayed (downloading and loading into memory).
+ /// Late image loading may effect the layout and actual size as image without set size will not have actual size until they are loaded + /// resulting in layout change during user scroll.
+ /// Early image loading may also effect the layout if image without known size above the current scroll location are loaded as they + /// will push the html elements down. + ///
+ public bool AvoidImagesLateLoading + { + get { return _htmlContainerInt.AvoidImagesLateLoading; } + set { _htmlContainerInt.AvoidImagesLateLoading = value; } + } + + /// + /// Is content selection is enabled for the rendered html (default - true).
+ /// If set to 'false' the rendered html will be static only with ability to click on links. + ///
+ public bool IsSelectionEnabled + { + get { return _htmlContainerInt.IsSelectionEnabled; } + set { _htmlContainerInt.IsSelectionEnabled = value; } + } + + /// + /// Is the build-in context menu enabled and will be shown on mouse right click (default - true) + /// + public bool IsContextMenuEnabled + { + get { return _htmlContainerInt.IsContextMenuEnabled; } + set { _htmlContainerInt.IsContextMenuEnabled = value; } + } + + /// + /// The scroll offset of the html.
+ /// This will adjust the rendered html by the given offset so the content will be "scrolled".
+ ///
+ /// + /// Element that is rendered at location (50,100) with offset of (0,200) will not be rendered as it + /// will be at -100 therefore outside the client Rect. + /// + public Point ScrollOffset + { + get { return Util.Convert(_htmlContainerInt.ScrollOffset); } + set { _htmlContainerInt.ScrollOffset = Util.Convert(value); } + } + + /// + /// The top-left most location of the rendered html.
+ /// This will offset the top-left corner of the rendered html. + ///
+ public Point Location + { + get { return Util.Convert(_htmlContainerInt.Location); } + set { _htmlContainerInt.Location = Util.Convert(value); } + } + + /// + /// The max width and height of the rendered html.
+ /// The max width will effect the html layout wrapping lines, resize images and tables where possible.
+ /// The max height does NOT effect layout, but will not render outside it (clip).
+ /// can be exceed the max size by layout restrictions (unwrappable line, set image size, etc.).
+ /// Set zero for unlimited (width\height separately).
+ ///
+ public Size MaxSize + { + get { return Util.Convert(_htmlContainerInt.MaxSize); } + set { _htmlContainerInt.MaxSize = Util.Convert(value); } + } + + /// + /// The actual size of the rendered html (after layout) + /// + public Size ActualSize + { + get { return Util.Convert(_htmlContainerInt.ActualSize); } + internal set { _htmlContainerInt.ActualSize = Util.Convert(value); } + } + + /// + /// Get the currently selected text segment in the html. + /// + public string SelectedText + { + get { return _htmlContainerInt.SelectedText; } + } + + /// + /// Copy the currently selected html segment with style. + /// + public string SelectedHtml + { + get { return _htmlContainerInt.SelectedHtml; } + } + + /// + /// Clear the current selection. + /// + public void ClearSelection() + { + HtmlContainerInt.ClearSelection(); + } + + /// + /// Init with optional document and stylesheet. + /// + /// the html to init with, init empty if not given + /// optional: the stylesheet to init with, init default if not given + public void SetHtml(string htmlSource, CssData baseCssData = null) + { + _htmlContainerInt.SetHtml(htmlSource, baseCssData); + } + + /// + /// Clear the content of the HTML container releasing any resources used to render previously existing content. + /// + public void Clear() + { + _htmlContainerInt.Clear(); + } + + /// + /// Get html from the current DOM tree with style if requested. + /// + /// Optional: controls the way styles are generated when html is generated (default: ) + /// generated html + public string GetHtml(HtmlGenerationStyle styleGen = HtmlGenerationStyle.Inline) + { + return _htmlContainerInt.GetHtml(styleGen); + } + + /// + /// Get attribute value of element at the given x,y location by given key.
+ /// If more than one element exist with the attribute at the location the inner most is returned. + ///
+ /// the location to find the attribute at + /// the attribute key to get value by + /// found attribute value or null if not found + public string GetAttributeAt(Point location, string attribute) + { + return _htmlContainerInt.GetAttributeAt(Util.Convert(location), attribute); + } + + /// + /// Get all the links in the HTML with the element Rect and href data. + /// + /// collection of all the links in the HTML + public List> GetLinks() + { + var linkElements = new List>(); + foreach (var link in HtmlContainerInt.GetLinks()) + { + linkElements.Add(new LinkElementData(link.Id, link.Href, Util.Convert(link.Rectangle))); + } + return linkElements; + } + + /// + /// Get css link href at the given x,y location. + /// + /// the location to find the link at + /// css link href if exists or null + public string GetLinkAt(Point location) + { + return _htmlContainerInt.GetLinkAt(Util.Convert(location)); + } + + /// + /// Get the Rect of html element as calculated by html layout.
+ /// Element if found by id (id attribute on the html element).
+ /// Note: to get the screen Rect you need to adjust by the hosting control.
+ ///
+ /// the id of the element to get its Rect + /// the Rect of the element or null if not found + public Rect? GetElementRectangle(string elementId) + { + var r = _htmlContainerInt.GetElementRectangle(elementId); + return r.HasValue ? Util.Convert(r.Value) : (Rect?)null; + } + + /// + /// Measures the bounds of box and children, recursively. + /// + public void PerformLayout() + { + using (var ig = new GraphicsAdapter()) + { + _htmlContainerInt.PerformLayout(ig); + } + } + + /// + /// Render the html using the given device. + /// + /// the device to use to render + /// the clip rectangle of the html container + public void PerformPaint(IDrawingContext g, Rect clip) + { + ArgChecker.AssertArgNotNull(g, "g"); + + using (var ig = new GraphicsAdapter(g, Util.Convert(clip))) + { + _htmlContainerInt.PerformPaint(ig); + } + } + + /// + /// Handle mouse down to handle selection. + /// + /// the control hosting the html to invalidate + /// the mouse event args + public void HandleLeftMouseDown(Control parent, PointerEventArgs e) + { + ArgChecker.AssertArgNotNull(parent, "parent"); + ArgChecker.AssertArgNotNull(e, "e"); + + _htmlContainerInt.HandleMouseDown(new ControlAdapter(parent), Util.Convert(e.GetPosition(parent))); + } + + /// + /// Handle mouse up to handle selection and link click. + /// + /// the control hosting the html to invalidate + /// the mouse event args + public void HandleLeftMouseUp(Control parent, PointerEventArgs e) + { + ArgChecker.AssertArgNotNull(parent, "parent"); + ArgChecker.AssertArgNotNull(e, "e"); + + var mouseEvent = new RMouseEvent(true); + _htmlContainerInt.HandleMouseUp(new ControlAdapter(parent), Util.Convert(e.GetPosition(parent)), mouseEvent); + } + + /// + /// Handle mouse double click to select word under the mouse. + /// + /// the control hosting the html to set cursor and invalidate + /// mouse event args + public void HandleMouseDoubleClick(Control parent, PointerEventArgs e) + { + ArgChecker.AssertArgNotNull(parent, "parent"); + ArgChecker.AssertArgNotNull(e, "e"); + + _htmlContainerInt.HandleMouseDoubleClick(new ControlAdapter(parent), Util.Convert(e.GetPosition(parent))); + } + + /// + /// Handle mouse move to handle hover cursor and text selection. + /// + /// the control hosting the html to set cursor and invalidate + /// the mouse event args + public void HandleMouseMove(Control parent, Point mousePos) + { + ArgChecker.AssertArgNotNull(parent, "parent"); + + _htmlContainerInt.HandleMouseMove(new ControlAdapter(parent), Util.Convert(mousePos)); + } + + /// + /// Handle mouse leave to handle hover cursor. + /// + /// the control hosting the html to set cursor and invalidate + public void HandleMouseLeave(Control parent) + { + ArgChecker.AssertArgNotNull(parent, "parent"); + + _htmlContainerInt.HandleMouseLeave(new ControlAdapter(parent)); + } + + /// + /// Handle key down event for selection and copy. + /// + /// the control hosting the html to invalidate + /// the pressed key + public void HandleKeyDown(Control parent, KeyEventArgs e) + { + ArgChecker.AssertArgNotNull(parent, "parent"); + ArgChecker.AssertArgNotNull(e, "e"); + + _htmlContainerInt.HandleKeyDown(new ControlAdapter(parent), CreateKeyEevent(e)); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + _htmlContainerInt.Dispose(); + } + + + #region Private methods + + /// + /// Create HtmlRenderer key event from Perspex key event. + /// + private static RKeyEvent CreateKeyEevent(KeyEventArgs e) + { + var control = (e.Modifiers & ModifierKeys.Control) == ModifierKeys.Control; + return new RKeyEvent(control, e.Key == Key.A, e.Key == Key.C); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Perspex.HtmlRenderer/HtmlControl.cs b/src/Perspex.HtmlRenderer/HtmlControl.cs new file mode 100644 index 0000000000..3ac0812a1f --- /dev/null +++ b/src/Perspex.HtmlRenderer/HtmlControl.cs @@ -0,0 +1,620 @@ +// "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 Perspex.Controls.Primitives; +using Perspex.HtmlRenderer; +using Perspex.Input; +using Perspex.Interactivity; +using Perspex.Media; +using Perspex.Threading; +using TheArtOfDev.HtmlRenderer.Core; +using TheArtOfDev.HtmlRenderer.Core.Entities; + +namespace Perspex.Controls +{ + /// + /// Provides HTML rendering using the text property.
+ /// Perspex control that will render html content in it's client rectangle.
+ /// The control will handle mouse and keyboard events on it to support html text selection, copy-paste and mouse clicks.
+ /// + /// The major differential to use HtmlPanel or HtmlLabel is size and scrollbars.
+ /// If the size of the control depends on the html content the HtmlLabel should be used.
+ /// If the size is set by some kind of layout then HtmlPanel is more suitable, also shows scrollbars if the html contents is larger than the control client rectangle.
+ ///
+ /// + ///

LinkClicked event:

+ /// Raised when the user clicks on a link in the html.
+ /// Allows canceling the execution of the link. + ///
+ /// + ///

StylesheetLoad event:

+ /// Raised when a stylesheet is about to be loaded by file path or URI by link element.
+ /// This event allows to provide the stylesheet manually or provide new source (file or uri) to load from.
+ /// If no alternative data is provided the original source will be used.
+ ///
+ /// + ///

ImageLoad event:

+ /// Raised when an image is about to be loaded by file path or URI.
+ /// This event allows to provide the image manually, if not handled the image will be loaded from file or download from URI. + ///
+ /// + ///

RenderError event:

+ /// Raised when an error occurred during html rendering.
+ ///
+ ///
+ + public class HtmlControl : Control + { + #region Fields and Consts + + /// + /// Underline html container instance. + /// + protected readonly HtmlContainer _htmlContainer; + + /// + /// the base stylesheet data used in the control + /// + protected CssData _baseCssData; + + /// + /// The last position of the scrollbars to know if it has changed to update mouse + /// + protected Point _lastScrollOffset; + + #endregion + + + #region Dependency properties / routed events + + public static readonly PerspexProperty AvoidImagesLateLoadingProperty = + PropertyHelper.Register("AvoidImagesLateLoading", false, OnPerspexProperty_valueChanged); + public static readonly PerspexProperty IsSelectionEnabledProperty = + PropertyHelper.Register("IsSelectionEnabled", true, OnPerspexProperty_valueChanged); + public static readonly PerspexProperty IsContextMenuEnabledProperty = + PropertyHelper.Register("IsContextMenuEnabled", true, OnPerspexProperty_valueChanged); + + public static readonly PerspexProperty BaseStylesheetProperty = + PropertyHelper.Register("BaseStylesheet", null, OnPerspexProperty_valueChanged); + + public static readonly PerspexProperty TextProperty = + PropertyHelper.Register("Text", null, OnPerspexProperty_valueChanged); + + public static readonly PerspexProperty BackgroundProperty = + PerspexProperty.Register("Background", Colors.White); + + public static readonly PerspexProperty BorderThicknessProperty = + PerspexProperty.Register("BorderThickness", new Thickness(0)); + + public static readonly PerspexProperty PaddingProperty = + PerspexProperty.Register("Padding", new Thickness(0)); + + public static readonly RoutedEvent LoadCompleteEvent = + RoutedEvent.Register("LoadComplete", RoutingStrategies.Bubble, typeof(HtmlControl)); + public static readonly RoutedEvent LinkClickedEvent = + RoutedEvent.Register>("LinkClicked", RoutingStrategies.Bubble, typeof(HtmlControl)); + public static readonly RoutedEvent RenderErrorEvent + = RoutedEvent.Register>("RenderError", RoutingStrategies.Bubble, typeof(HtmlControl)); + public static readonly RoutedEvent RefreshEvent + = RoutedEvent.Register< RoutedEventArgsWrapper>("Refresh", RoutingStrategies.Bubble, typeof(HtmlControl)); + public static readonly RoutedEvent StylesheetLoadEvent + = RoutedEvent.Register>("StylesheetLoad", RoutingStrategies.Bubble, typeof(HtmlControl)); + + public static readonly RoutedEvent ImageLoadEvent + = RoutedEvent.Register>("ImageLoad", RoutingStrategies.Bubble, + typeof (HtmlControl)); + + #endregion + + + /// + /// Creates a new HtmlPanel and sets a basic css for it's styling. + /// + protected HtmlControl() + { + _htmlContainer = new HtmlContainer();/* + _htmlContainer.LoadComplete += OnLoadComplete; + _htmlContainer.LinkClicked += OnLinkClicked; + _htmlContainer.RenderError += OnRenderError; + _htmlContainer.Refresh += OnRefresh; + _htmlContainer.StylesheetLoad += OnStylesheetLoad; + _htmlContainer.ImageLoad += OnImageLoad;*/ + } + + /// + /// Raised when the set html document has been fully loaded.
+ /// Allows manipulation of the html dom, scroll position, etc. + ///
+ public event EventHandler LoadComplete + { + add { AddHandler(LoadCompleteEvent, value); } + remove { RemoveHandler(LoadCompleteEvent, value); } + } + + /// + /// Raised when the user clicks on a link in the html.
+ /// Allows canceling the execution of the link. + ///
+ public event EventHandler LinkClicked + { + add { AddHandler(LinkClickedEvent, value); } + remove { RemoveHandler(LinkClickedEvent, value); } + } + + /// + /// Raised when an error occurred during html rendering.
+ ///
+ public event EventHandler RenderError + { + add { AddHandler(RenderErrorEvent, value); } + remove { RemoveHandler(RenderErrorEvent, value); } + } + + /// + /// Raised when a stylesheet is about to be loaded by file path or URI by link element.
+ /// This event allows to provide the stylesheet manually or provide new source (file or uri) to load from.
+ /// If no alternative data is provided the original source will be used.
+ ///
+ public event EventHandler StylesheetLoad + { + add { AddHandler(StylesheetLoadEvent, value); } + remove { RemoveHandler(StylesheetLoadEvent, value); } + } + + /// + /// Raised when an image is about to be loaded by file path or URI.
+ /// This event allows to provide the image manually, if not handled the image will be loaded from file or download from URI. + ///
+ public event EventHandler ImageLoad + { + add { AddHandler(ImageLoadEvent, value); } + remove { RemoveHandler(ImageLoadEvent, value); } + } + + /// + /// Gets or sets a value indicating if image loading only when visible should be avoided (default - false).
+ /// True - images are loaded as soon as the html is parsed.
+ /// False - images that are not visible because of scroll location are not loaded until they are scrolled to. + ///
+ /// + /// Images late loading improve performance if the page contains image outside the visible scroll area, especially if there is large + /// amount of images, as all image loading is delayed (downloading and loading into memory).
+ /// Late image loading may effect the layout and actual size as image without set size will not have actual size until they are loaded + /// resulting in layout change during user scroll.
+ /// Early image loading may also effect the layout if image without known size above the current scroll location are loaded as they + /// will push the html elements down. + ///
+ [Category("Behavior")] + [Description("If image loading only when visible should be avoided")] + public bool AvoidImagesLateLoading + { + get { return (bool)GetValue(AvoidImagesLateLoadingProperty); } + set { SetValue(AvoidImagesLateLoadingProperty, value); } + } + + /// + /// Is content selection is enabled for the rendered html (default - true).
+ /// If set to 'false' the rendered html will be static only with ability to click on links. + ///
+ [Category("Behavior")] + [Description("Is content selection is enabled for the rendered html.")] + public bool IsSelectionEnabled + { + get { return (bool)GetValue(IsSelectionEnabledProperty); } + set { SetValue(IsSelectionEnabledProperty, value); } + } + + /// + /// Is the build-in context menu enabled and will be shown on mouse right click (default - true) + /// + [Category("Behavior")] + [Description("Is the build-in context menu enabled and will be shown on mouse right click.")] + public bool IsContextMenuEnabled + { + get { return (bool)GetValue(IsContextMenuEnabledProperty); } + set { SetValue(IsContextMenuEnabledProperty, value); } + } + + /// + /// Set base stylesheet to be used by html rendered in the panel. + /// + [Category("Appearance")] + [Description("Set base stylesheet to be used by html rendered in the control.")] + public string BaseStylesheet + { + get { return (string)GetValue(BaseStylesheetProperty); } + set { SetValue(BaseStylesheetProperty, value); } + } + + /// + /// Gets or sets the text of this panel + /// + [Description("Sets the html of this control.")] + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + public Thickness BorderThickness + { + get { return (Thickness) GetValue(BorderThicknessProperty); } + set { SetValue(BorderThicknessProperty, value); } + } + + public Thickness Padding + { + get { return (Thickness)GetValue(PaddingProperty); } + set { SetValue(PaddingProperty, value); } + } + + public Color Background + { + get { return (Color?) GetValue(BackgroundProperty) ?? Colors.White; } + set { SetValue(BackgroundProperty, value);} + } + + /// + /// Get the currently selected text segment in the html. + /// + [Browsable(false)] + public virtual string SelectedText + { + get { return _htmlContainer.SelectedText; } + } + + /// + /// Copy the currently selected html segment with style. + /// + [Browsable(false)] + public virtual string SelectedHtml + { + get { return _htmlContainer.SelectedHtml; } + } + + /// + /// Get html from the current DOM tree with inline style. + /// + /// generated html + public virtual string GetHtml() + { + return _htmlContainer != null ? _htmlContainer.GetHtml() : null; + } + + /// + /// Get the rectangle of html element as calculated by html layout.
+ /// Element if found by id (id attribute on the html element).
+ /// Note: to get the screen rectangle you need to adjust by the hosting control.
+ ///
+ /// the id of the element to get its rectangle + /// the rectangle of the element or null if not found + public virtual Rect? GetElementRectangle(string elementId) + { + return _htmlContainer != null ? _htmlContainer.GetElementRectangle(elementId) : null; + } + + /// + /// Clear the current selection. + /// + public void ClearSelection() + { + if (_htmlContainer != null) + _htmlContainer.ClearSelection(); + } + + + #region Private methods + + //HACK: We don't have support for RenderSize for now + private Size RenderSize => new Size(Bounds.Width, Bounds.Height); + + + public override void Render(IDrawingContext context) + { + //TODO: Use actual background + context.FillRectange(new SolidColorBrush(Colors.White), new Rect(RenderSize)); + /*if (Background.Opacity > 0) + context.DrawRectangle(Background, null, new Rect(RenderSize)); + */ + + if (BorderThickness != new Thickness(0)) + { + //TODO: Get default border brush from theme or something + var brush = new SolidColorBrush(Colors.Black); + if (BorderThickness.Top > 0) + context.FillRectange(brush, new Rect(0, 0, RenderSize.Width, BorderThickness.Top)); + if (BorderThickness.Bottom > 0) + context.FillRectange(brush, new Rect(0, RenderSize.Height - BorderThickness.Bottom, RenderSize.Width, BorderThickness.Bottom)); + if (BorderThickness.Left > 0) + context.FillRectange(brush, new Rect(0, 0, BorderThickness.Left, RenderSize.Height)); + if (BorderThickness.Right > 0) + context.FillRectange(brush, new Rect(RenderSize.Width - BorderThickness.Right, 0, BorderThickness.Right, RenderSize.Height)); + } + + var htmlWidth = HtmlWidth(RenderSize); + var htmlHeight = HtmlHeight(RenderSize); + if (_htmlContainer != null && htmlWidth > 0 && htmlHeight > 0) + { + /* + //TODO: Rever antialiasing fixes + var windows = Window.GetWindow(this); + if (windows != null) + { + // adjust render location to round point so we won't get anti-alias smugness + var wPoint = TranslatePoint(new Point(0, 0), windows); + wPoint.Offset(-(int)wPoint.X, -(int)wPoint.Y); + var xTrans = wPoint.X < .5 ? -wPoint.X : 1 - wPoint.X; + var yTrans = wPoint.Y < .5 ? -wPoint.Y : 1 - wPoint.Y; + context.PushTransform(new TranslateTransform(xTrans, yTrans)); + }*/ + + using (context.PushClip(new Rect(Padding.Left + BorderThickness.Left, Padding.Top + BorderThickness.Top, + htmlWidth, (int) htmlHeight))) + { + _htmlContainer.Location = new Point(Padding.Left + BorderThickness.Left, + Padding.Top + BorderThickness.Top); + _htmlContainer.PerformPaint(context, + new Rect(Padding.Left + BorderThickness.Left, Padding.Top + BorderThickness.Top, htmlWidth, + htmlHeight)); + } + + if (!_lastScrollOffset.Equals(_htmlContainer.ScrollOffset)) + { + _lastScrollOffset = _htmlContainer.ScrollOffset; + InvokeMouseMove(); + } + } + } + + /// + /// Handle mouse move to handle hover cursor and text selection. + /// + protected override void OnPointerMoved(PointerEventArgs e) + { + base.OnPointerMoved(e); + if (_htmlContainer != null) + _htmlContainer.HandleMouseMove(this, e.GetPosition(this)); + } + /// + /// Handle mouse leave to handle cursor change. + /// + protected override void OnPointerLeave(PointerEventArgs e) + { + base.OnPointerLeave(e); + if (_htmlContainer != null) + _htmlContainer.HandleMouseLeave(this); + } + + /// + /// Handle mouse down to handle selection. + /// + protected override void OnPointerPressed(PointerPressEventArgs e) + { + base.OnPointerPressed(e); + _htmlContainer?.HandleLeftMouseDown(this, e); + } + + + + /// + /// Handle mouse up to handle selection and link click. + /// + protected override void OnPointerReleased(PointerEventArgs e) + { + base.OnPointerReleased(e); + if (_htmlContainer != null) + _htmlContainer.HandleLeftMouseUp(this, e); + } + + //TODO: Implement double click + /* + /// + /// Handle mouse double click to select word under the mouse. + /// + protected override void OnMouseDoubleClick(MouseButtonEventArgs e) + { + base.OnMouseDoubleClick(e); + if (_htmlContainer != null) + _htmlContainer.HandleMouseDoubleClick(this, e); + } + */ + /// + /// Handle key down event for selection, copy and scrollbars handling. + /// + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + if (_htmlContainer != null) + _htmlContainer.HandleKeyDown(this, e); + } + + /// + /// Propagate the LoadComplete event from root container. + /// + protected virtual void OnLoadComplete(EventArgs e) + { + //RoutedEventArgs newEventArgs = new RoutedEventArgs(LoadCompleteEvent, this, e); + //RaiseEvent(newEventArgs); + } + + /// + /// Propagate the LinkClicked event from root container. + /// + protected virtual void OnLinkClicked(HtmlLinkClickedEventArgs e) + { + //RoutedEventArgs newEventArgs = new RoutedEvenArgs(LinkClickedEvent, this, e); + //RaiseEvent(newEventArgs); + } + + /// + /// Propagate the Render Error event from root container. + /// + protected virtual void OnRenderError(HtmlRenderErrorEventArgs e) + { + //RoutedEventArgs newEventArgs = new RoutedEvenArgs(RenderErrorEvent, this, e); + //RaiseEvent(newEventArgs); + } + + /// + /// Propagate the stylesheet load event from root container. + /// + protected virtual void OnStylesheetLoad(HtmlStylesheetLoadEventArgs e) + { + //RoutedEventArgs newEventArgs = new RoutedEvenArgs(StylesheetLoadEvent, this, e); + //RaiseEvent(newEventArgs); + } + + /// + /// Propagate the image load event from root container. + /// + protected virtual void OnImageLoad(HtmlImageLoadEventArgs e) + { + //RoutedEventArgs newEventArgs = new RoutedEvenArgs(ImageLoadEvent, this, e); + //RaiseEvent(newEventArgs); + } + + /// + /// Handle html renderer invalidate and re-layout as requested. + /// + protected virtual void OnRefresh(HtmlRefreshEventArgs e) + { + if (e.Layout) + InvalidateMeasure(); + InvalidateVisual(); + } + + /// + /// Get the width the HTML has to render in (not including vertical scroll iff it is visible) + /// + protected virtual double HtmlWidth(Size size) + { + return size.Width - Padding.Left - Padding.Right - BorderThickness.Left - BorderThickness.Right; + } + + /// + /// Get the width the HTML has to render in (not including vertical scroll iff it is visible) + /// + protected virtual double HtmlHeight(Size size) + { + return size.Height - Padding.Top - Padding.Bottom - BorderThickness.Top - BorderThickness.Bottom; + } + + /// + /// call mouse move to handle paint after scroll or html change affecting mouse cursor. + /// + protected virtual void InvokeMouseMove() + { + + _htmlContainer.HandleMouseMove(this, MouseDevice.Instance.GetPosition(this)); + } + + /// + /// Handle when dependency property value changes to update the underline HtmlContainer with the new value. + /// + private static void OnPerspexProperty_valueChanged(PerspexObject PerspexObject, PerspexPropertyChangedEventArgs e) + { + var control = PerspexObject as HtmlControl; + if (control != null) + { + var htmlContainer = control._htmlContainer; + if (e.Property == AvoidImagesLateLoadingProperty) + { + htmlContainer.AvoidImagesLateLoading = (bool)e.NewValue; + } + else if (e.Property == IsSelectionEnabledProperty) + { + htmlContainer.IsSelectionEnabled = (bool)e.NewValue; + } + else if (e.Property == IsContextMenuEnabledProperty) + { + htmlContainer.IsContextMenuEnabled = (bool)e.NewValue; + } + else if (e.Property == BaseStylesheetProperty) + { + var baseCssData = HtmlRender.ParseStyleSheet((string)e.NewValue); + control._baseCssData = baseCssData; + htmlContainer.SetHtml(control.Text, baseCssData); + } + else if (e.Property == TextProperty) + { + htmlContainer.ScrollOffset = new Point(0, 0); + htmlContainer.SetHtml((string)e.NewValue, control._baseCssData); + control.InvalidateMeasure(); + control.InvalidateVisual(); + control.InvokeMouseMove(); + } + } + } + + + #region Private event handlers + + + /* TODO: Implement events + private void OnLoadComplete(object sender, EventArgs e) + { + + if (CheckAccess()) + OnLoadComplete(e); + else + Dispatcher.Invoke(new Action(OnLinkClicked), e); + + } + + private void OnLinkClicked(object sender, HtmlLinkClickedEventArgs e) + { + if (CheckAccess()) + OnLinkClicked(e); + else + Dispatcher.Invoke(new Action(OnLinkClicked), e); + } + + private void OnRenderError(object sender, HtmlRenderErrorEventArgs e) + { + if (CheckAccess()) + OnRenderError(e); + else + Dispatcher.Invoke(new Action(OnRenderError), e); + } + + private void OnStylesheetLoad(object sender, HtmlStylesheetLoadEventArgs e) + { + if (CheckAccess()) + OnStylesheetLoad(e); + else + Dispatcher.Invoke(new Action(OnStylesheetLoad), e); + } + + private void OnImageLoad(object sender, HtmlImageLoadEventArgs e) + { + if (CheckAccess()) + OnImageLoad(e); + else + Dispatcher.Invoke(new Action(OnImageLoad), e); + } + + private void OnRefresh(object sender, HtmlRefreshEventArgs e) + { + if (CheckAccess()) + OnRefresh(e); + else + Dispatcher.Invoke(new Action(OnRefresh), e); + } + */ + + #endregion + + + #endregion + } +} \ No newline at end of file diff --git a/src/Perspex.HtmlRenderer/HtmlLabel.cs b/src/Perspex.HtmlRenderer/HtmlLabel.cs new file mode 100644 index 0000000000..7d9b4c0489 --- /dev/null +++ b/src/Perspex.HtmlRenderer/HtmlLabel.cs @@ -0,0 +1,135 @@ +// "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 Perspex.HtmlRenderer; +using Perspex.Media; +using TheArtOfDev.HtmlRenderer.Adapters.Entities; +using TheArtOfDev.HtmlRenderer.Core; +using TheArtOfDev.HtmlRenderer.Perspex.Adapters; + +namespace Perspex.Controls +{ + /// + /// Provides HTML rendering using the text property.
+ /// WPF control that will render html content in it's client rectangle.
+ /// Using and client can control how the html content effects the + /// size of the label. Either case scrollbars are never shown and html content outside of client bounds will be clipped. + /// MaxWidth/MaxHeight and MinWidth/MinHeight with AutoSize can limit the max/min size of the control
+ /// The control will handle mouse and keyboard events on it to support html text selection, copy-paste and mouse clicks.
+ ///
+ /// + /// See for more info. + /// + public class HtmlLabel : HtmlControl + { + #region Dependency properties + + public static readonly PerspexProperty AutoSizeProperty = PropertyHelper.Register("AutoSize", true, OnPerspexProperty_valueChanged); + public static readonly PerspexProperty AutoSizeHeightOnlyProperty = PropertyHelper.Register("AutoSizeHeightOnly", false, OnPerspexProperty_valueChanged); + + #endregion + + + /// + /// Init. + /// + static HtmlLabel() + { + //BackgroundProperty.OverrideDefaultValue(Brushes.Transparent); + } + + /// + /// Automatically sets the size of the label by content size + /// + [Category("Layout")] + [Description("Automatically sets the size of the label by content size.")] + public bool AutoSize + { + get { return (bool)GetValue(AutoSizeProperty); } + set { SetValue(AutoSizeProperty, value); } + } + + /// + /// Automatically sets the height of the label by content height (width is not effected). + /// + [Category("Layout")] + [Description("Automatically sets the height of the label by content height (width is not effected)")] + public virtual bool AutoSizeHeightOnly + { + get { return (bool)GetValue(AutoSizeHeightOnlyProperty); } + set { SetValue(AutoSizeHeightOnlyProperty, value); } + } + + + #region Private methods + + /// + /// Perform the layout of the html in the control. + /// + protected override Size MeasureOverride(Size constraint) + { + if (_htmlContainer != null) + { + using (var ig = new GraphicsAdapter()) + { + var horizontal = Padding.Left + Padding.Right + BorderThickness.Left + BorderThickness.Right; + var vertical = Padding.Top + Padding.Bottom + BorderThickness.Top + BorderThickness.Bottom; + + var size = new RSize(constraint.Width < Double.PositiveInfinity ? constraint.Width - horizontal : 0, constraint.Height < Double.PositiveInfinity ? constraint.Height - vertical : 0); + var minSize = new RSize(MinWidth < Double.PositiveInfinity ? MinWidth - horizontal : 0, MinHeight < Double.PositiveInfinity ? MinHeight - vertical : 0); + var maxSize = new RSize(MaxWidth < Double.PositiveInfinity ? MaxWidth - horizontal : 0, MaxHeight < Double.PositiveInfinity ? MaxHeight - vertical : 0); + + var newSize = HtmlRendererUtils.Layout(ig, _htmlContainer.HtmlContainerInt, size, minSize, maxSize, AutoSize, AutoSizeHeightOnly); + + constraint = new Size(newSize.Width + horizontal, newSize.Height + vertical); + } + } + + if (double.IsPositiveInfinity(constraint.Width) || double.IsPositiveInfinity(constraint.Height)) + constraint = Size.Empty; + + return constraint; + } + + /// + /// Handle when dependency property value changes to update the underline HtmlContainer with the new value. + /// + private static void OnPerspexProperty_valueChanged(PerspexObject PerspexObject, PerspexPropertyChangedEventArgs e) + { + var control = PerspexObject as HtmlLabel; + if (control != null) + { + if (e.Property == AutoSizeProperty) + { + if ((bool)e.NewValue) + { + PerspexObject.SetValue(AutoSizeHeightOnlyProperty, false); + control.InvalidateMeasure(); + control.InvalidateVisual(); + } + } + else if (e.Property == AutoSizeHeightOnlyProperty) + { + if ((bool)e.NewValue) + { + PerspexObject.SetValue(AutoSizeProperty, false); + control.InvalidateMeasure(); + control.InvalidateVisual(); + } + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Perspex.HtmlRenderer/HtmlRender.cs b/src/Perspex.HtmlRenderer/HtmlRender.cs new file mode 100644 index 0000000000..686116e6bb --- /dev/null +++ b/src/Perspex.HtmlRenderer/HtmlRender.cs @@ -0,0 +1,424 @@ +// "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 Perspex.Media; +using Perspex.Media.Imaging; +using TheArtOfDev.HtmlRenderer.Core; +using TheArtOfDev.HtmlRenderer.Core.Entities; +using TheArtOfDev.HtmlRenderer.Core.Utils; +using TheArtOfDev.HtmlRenderer.Perspex.Adapters; + +namespace Perspex.Controls +{ + /// + /// Standalone static class for simple and direct HTML rendering.
+ /// For Perspex UI prefer using HTML controls: or .
+ /// For low-level control and performance consider using .
+ ///
+ /// + /// + /// Rendering to image
+ /// // TODO:a update! + /// See https://htmlrenderer.codeplex.com/wikipage?title=Image%20generation
+ /// Because of GDI text rendering issue with alpha channel clear type text rendering rendering to image requires special handling.
+ /// Solid color background - generate an image where the background is filled with solid color and all the html is rendered on top + /// of the background color, GDI text rendering will be used. (RenderToImage method where the first argument is html string)
+ /// Image background - render html on top of existing image with whatever currently exist but it cannot have transparent pixels, + /// GDI text rendering will be used. (RenderToImage method where the first argument is Image object)
+ /// Transparent background - render html to empty image using GDI+ text rendering, the generated image can be transparent. + ///
+ /// + /// Overwrite stylesheet resolution
+ /// Exposed by optional "stylesheetLoad" delegate argument.
+ /// Invoked when a stylesheet is about to be loaded by file path or URL in 'link' element.
+ /// Allows to overwrite the loaded stylesheet by providing the stylesheet data manually, or different source (file or URL) to load from.
+ /// Example: The stylesheet 'href' can be non-valid URI string that is interpreted in the overwrite delegate by custom logic to pre-loaded stylesheet object
+ /// If no alternative data is provided the original source will be used.
+ ///
+ /// + /// Overwrite image resolution
+ /// Exposed by optional "imageLoad" delegate argument.
+ /// Invoked when an image is about to be loaded by file path, URL or inline data in 'img' element or background-image CSS style.
+ /// Allows to overwrite the loaded image by providing the image object manually, or different source (file or URL) to load from.
+ /// Example: image 'src' can be non-valid string that is interpreted in the overwrite delegate by custom logic to resource image object
+ /// Example: image 'src' in the html is relative - the overwrite intercepts the load and provide full source URL to load the image from
+ /// Example: image download requires authentication - the overwrite intercepts the load, downloads the image to disk using custom code and provide + /// file path to load the image from.
+ /// If no alternative data is provided the original source will be used.
+ /// Note: Cannot use asynchronous scheme overwrite scheme.
+ ///
+ ///
+ /// + /// + /// Simple rendering
+ /// HtmlRender.Render(g, "Hello World]]>");
+ /// HtmlRender.Render(g, "Hello World]]>", 10, 10, 500, CssData.Parse("body {font-size: 20px}")");
+ ///
+ /// + /// Image rendering
+ /// HtmlRender.RenderToImage("Hello World]]>", new Size(600,400));
+ /// HtmlRender.RenderToImage("Hello World]]>", 600);
+ /// HtmlRender.RenderToImage(existingImage, "Hello World]]>");
+ ///
+ ///
+ public static class HtmlRender + { + /// + /// Adds a font family to be used in html rendering.
+ /// The added font will be used by all rendering function including and all Perspex controls. + ///
+ /// + /// The given font family instance must be remain alive while the renderer is in use.
+ /// If loaded from file then the file must not be deleted. + ///
+ /// The font family to add. + public static void AddFontFamily(string fontFamily) + { + ArgChecker.AssertArgNotNull(fontFamily, "fontFamily"); + + PerspexAdapter.Instance.AddFontFamily(new FontFamilyAdapter(fontFamily)); + } + + /// + /// Adds a font mapping from to iff the is not found.
+ /// When the font is used in rendered html and is not found in existing + /// fonts (installed or added) it will be replaced by .
+ ///
+ /// + /// This fonts mapping can be used as a fallback in case the requested font is not installed in the client system. + /// + /// the font family to replace + /// the font family to replace with + public static void AddFontFamilyMapping(string fromFamily, string toFamily) + { + ArgChecker.AssertArgNotNullOrEmpty(fromFamily, "fromFamily"); + ArgChecker.AssertArgNotNullOrEmpty(toFamily, "toFamily"); + + PerspexAdapter.Instance.AddFontFamilyMapping(fromFamily, toFamily); + } + + /// + /// Parse the given stylesheet to object.
+ /// If is true the parsed css blocks are added to the + /// default css data (as defined by W3), merged if class name already exists. If false only the data in the given stylesheet is returned. + ///
+ /// + /// the stylesheet source to parse + /// true - combine the parsed css data with default css data, false - return only the parsed css data + /// the parsed css data + public static CssData ParseStyleSheet(string stylesheet, bool combineWithDefault = true) + { + return CssData.Parse(PerspexAdapter.Instance, stylesheet, combineWithDefault); + } + + /// + /// Measure the size (width and height) required to draw the given html under given max width restriction.
+ /// If no max width restriction is given the layout will use the maximum possible width required by the content, + /// it can be the longest text line or full image width.
+ ///
+ /// HTML source to render + /// optional: bound the width of the html to render in (default - 0, unlimited) + /// optional: the style to use for html rendering (default - use W3 default style) + /// optional: can be used to overwrite stylesheet resolution logic + /// optional: can be used to overwrite image resolution logic + /// the size required for the html + public static Size Measure(string html, double maxWidth = 0, CssData cssData = null, + EventHandler stylesheetLoad = null, EventHandler imageLoad = null) + { + Size actualSize = Size.Empty; + if (!string.IsNullOrEmpty(html)) + { + using (var container = new HtmlContainer()) + { + container.MaxSize = new Size(maxWidth, 0); + container.AvoidAsyncImagesLoading = true; + container.AvoidImagesLateLoading = true; + + if (stylesheetLoad != null) + container.StylesheetLoad += stylesheetLoad; + if (imageLoad != null) + container.ImageLoad += imageLoad; + + container.SetHtml(html, cssData); + container.PerformLayout(); + + actualSize = container.ActualSize; + } + } + return actualSize; + } + /* + /// + /// Renders the specified HTML source on the specified location and max width restriction.
+ /// If is zero the html will use all the required width, otherwise it will perform line + /// wrap as specified in the html
+ /// Returned is the actual width and height of the rendered html.
+ ///
+ /// Device to render with + /// HTML source to render + /// optional: the left most location to start render the html at (default - 0) + /// optional: the top most location to start render the html at (default - 0) + /// optional: bound the width of the html to render in (default - 0, unlimited) + /// optional: the style to use for html rendering (default - use W3 default style) + /// optional: can be used to overwrite stylesheet resolution logic + /// optional: can be used to overwrite image resolution logic + /// the actual size of the rendered html + public static Size Render(IDrawingContext g, string html, double left = 0, double top = 0, double maxWidth = 0, CssData cssData = null, + EventHandler stylesheetLoad = null, EventHandler imageLoad = null) + { + ArgChecker.AssertArgNotNull(g, "g"); + return RenderClip(g, html, new Point(left, top), new Size(maxWidth, 0), cssData, stylesheetLoad, imageLoad); + } + + /// + /// Renders the specified HTML source on the specified location and max size restriction.
+ /// If .Width is zero the html will use all the required width, otherwise it will perform line + /// wrap as specified in the html
+ /// If .Height is zero the html will use all the required height, otherwise it will clip at the + /// given max height not rendering the html below it.
+ /// Returned is the actual width and height of the rendered html.
+ ///
+ /// Device to render with + /// HTML source to render + /// the top-left most location to start render the html at + /// the max size of the rendered html (if height above zero it will be clipped) + /// optional: the style to use for html rendering (default - use W3 default style) + /// optional: can be used to overwrite stylesheet resolution logic + /// optional: can be used to overwrite image resolution logic + /// the actual size of the rendered html + public static Size Render(IDrawingContext g, string html, Point location, Size maxSize, CssData cssData = null, + EventHandler stylesheetLoad = null, EventHandler imageLoad = null) + { + ArgChecker.AssertArgNotNull(g, "g"); + return RenderClip(g, html, location, maxSize, cssData, stylesheetLoad, imageLoad); + } + + /// + /// Renders the specified HTML into a new image of the requested size.
+ /// The HTML will be layout by the given size but will be clipped if cannot fit.
+ ///
+ /// HTML source to render + /// The size of the image to render into, layout html by width and clipped by height + /// optional: the style to use for html rendering (default - use W3 default style) + /// optional: can be used to overwrite stylesheet resolution logic + /// optional: can be used to overwrite image resolution logic + /// the generated image of the html + public static BitmapFrame RenderToImage(string html, Size size, CssData cssData = null, + EventHandler stylesheetLoad = null, EventHandler imageLoad = null) + { + var renderTarget = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32); + + if (!string.IsNullOrEmpty(html)) + { + // render HTML into the visual + DrawingVisual drawingVisual = new DrawingVisual(); + using (IDrawingContext g = drawingVisual.RenderOpen()) + { + RenderHtml(g, html, new Point(), size, cssData, stylesheetLoad, imageLoad); + } + + // render visual into target bitmap + renderTarget.Render(drawingVisual); + } + + return BitmapFrame.Create(renderTarget); + } + + /// + /// Renders the specified HTML into a new image of unknown size that will be determined by max width/height and HTML layout.
+ /// If is zero the html will use all the required width, otherwise it will perform line + /// wrap as specified in the html
+ /// If is zero the html will use all the required height, otherwise it will clip at the + /// given max height not rendering the html below it.
+ ///

+ /// Limitation: The image cannot have transparent background, by default it will be white.
+ /// See "Rendering to image" remarks section on .
+ ///

+ ///
+ /// HTML source to render + /// optional: the max width of the rendered html, if not zero and html cannot be layout within the limit it will be clipped + /// optional: the max height of the rendered html, if not zero and html cannot be layout within the limit it will be clipped + /// optional: the color to fill the image with (default - white) + /// optional: the style to use for html rendering (default - use W3 default style) + /// optional: can be used to overwrite stylesheet resolution logic + /// optional: can be used to overwrite image resolution logic + /// the generated image of the html + public static BitmapFrame RenderToImage(string html, int maxWidth = 0, int maxHeight = 0, Color backgroundColor = new Color(), CssData cssData = null, + EventHandler stylesheetLoad = null, EventHandler imageLoad = null) + { + return RenderToImage(html, Size.Empty, new Size(maxWidth, maxHeight), backgroundColor, cssData, stylesheetLoad, imageLoad); + } + + /// + /// Renders the specified HTML into a new image of unknown size that will be determined by min/max width/height and HTML layout.
+ /// If is zero the html will use all the required width, otherwise it will perform line + /// wrap as specified in the html
+ /// If is zero the html will use all the required height, otherwise it will clip at the + /// given max height not rendering the html below it.
+ /// If (Width/Height) is above zero the rendered image will not be smaller than the given min size.
+ ///

+ /// Limitation: The image cannot have transparent background, by default it will be white.
+ /// See "Rendering to image" remarks section on .
+ ///

+ ///
+ /// HTML source to render + /// optional: the min size of the rendered html (zero - not limit the width/height) + /// optional: the max size of the rendered html, if not zero and html cannot be layout within the limit it will be clipped (zero - not limit the width/height) + /// optional: the color to fill the image with (default - white) + /// optional: the style to use for html rendering (default - use W3 default style) + /// optional: can be used to overwrite stylesheet resolution logic + /// optional: can be used to overwrite image resolution logic + /// the generated image of the html + public static BitmapFrame RenderToImage(string html, Size minSize, Size maxSize, Color backgroundColor = new Color(), CssData cssData = null, + EventHandler stylesheetLoad = null, EventHandler imageLoad = null) + { + RenderTargetBitmap renderTarget; + if (!string.IsNullOrEmpty(html)) + { + using (var container = new HtmlContainer()) + { + container.AvoidAsyncImagesLoading = true; + container.AvoidImagesLateLoading = true; + + if (stylesheetLoad != null) + container.StylesheetLoad += stylesheetLoad; + if (imageLoad != null) + container.ImageLoad += imageLoad; + container.SetHtml(html, cssData); + + var finalSize = MeasureHtmlByRestrictions(container, minSize, maxSize); + container.MaxSize = finalSize; + + renderTarget = new RenderTargetBitmap((int)finalSize.Width, (int)finalSize.Height, 96, 96, PixelFormats.Pbgra32); + + // render HTML into the visual + DrawingVisual drawingVisual = new DrawingVisual(); + using (IDrawingContext g = drawingVisual.RenderOpen()) + { + container.PerformPaint(g, new Rect(new Size(maxSize.Width > 0 ? maxSize.Width : double.MaxValue, maxSize.Height > 0 ? maxSize.Height : double.MaxValue))); + } + + // render visual into target bitmap + renderTarget.Render(drawingVisual); + } + } + else + { + renderTarget = new RenderTargetBitmap(0, 0, 96, 96, PixelFormats.Pbgra32); + } + + return BitmapFrame.Create(renderTarget); + } + + + #region Private methods + + /// + /// Measure the size of the html by performing layout under the given restrictions. + /// + /// the html to calculate the layout for + /// the minimal size of the rendered html (zero - not limit the width/height) + /// the maximum size of the rendered html, if not zero and html cannot be layout within the limit it will be clipped (zero - not limit the width/height) + /// return: the size of the html to be rendered within the min/max limits + private static Size MeasureHtmlByRestrictions(HtmlContainer htmlContainer, Size minSize, Size maxSize) + { + // use desktop created graphics to measure the HTML + using (var mg = new GraphicsAdapter()) + { + var sizeInt = HtmlRendererUtil.MeasureHtmlByRestrictions(mg, htmlContainer.HtmlContainerInt, Util.Convert(minSize), Util.Convert(maxSize)); + if (maxSize.Width < 1 && sizeInt.Width > 4096) + sizeInt.Width = 4096; + return Util.ConvertRound(sizeInt); + } + } + + /// + /// Renders the specified HTML source on the specified location and max size restriction.
+ /// If .Width is zero the html will use all the required width, otherwise it will perform line + /// wrap as specified in the html
+ /// If .Height is zero the html will use all the required height, otherwise it will clip at the + /// given max height not rendering the html below it.
+ /// Clip the graphics so the html will not be rendered outside the max height bound given.
+ /// Returned is the actual width and height of the rendered html.
+ ///
+ /// Device to render with + /// HTML source to render + /// the top-left most location to start render the html at + /// the max size of the rendered html (if height above zero it will be clipped) + /// optional: the style to use for html rendering (default - use W3 default style) + /// optional: can be used to overwrite stylesheet resolution logic + /// optional: can be used to overwrite image resolution logic + /// the actual size of the rendered html + private static Size RenderClip(IDrawingContext g, string html, Point location, Size maxSize, CssData cssData, EventHandler stylesheetLoad, EventHandler imageLoad) + { + if (maxSize.Height > 0) + g.PushClip(new RectangleGeometry(new Rect(location, maxSize))); + + var actualSize = RenderHtml(g, html, location, maxSize, cssData, stylesheetLoad, imageLoad); + + if (maxSize.Height > 0) + g.Pop(); + + return actualSize; + } + + /// + /// Renders the specified HTML source on the specified location and max size restriction.
+ /// If .Width is zero the html will use all the required width, otherwise it will perform line + /// wrap as specified in the html
+ /// If .Height is zero the html will use all the required height, otherwise it will clip at the + /// given max height not rendering the html below it.
+ /// Returned is the actual width and height of the rendered html.
+ ///
+ /// Device to render with + /// HTML source to render + /// the top-left most location to start render the html at + /// the max size of the rendered html (if height above zero it will be clipped) + /// optional: the style to use for html rendering (default - use W3 default style) + /// optional: can be used to overwrite stylesheet resolution logic + /// optional: can be used to overwrite image resolution logic + /// the actual size of the rendered html + private static Size RenderHtml(IDrawingContext g, string html, Point location, Size maxSize, CssData cssData, EventHandler stylesheetLoad, EventHandler imageLoad) + { + Size actualSize = Size.Empty; + + if (!string.IsNullOrEmpty(html)) + { + using (var container = new HtmlContainer()) + { + container.Location = location; + container.MaxSize = maxSize; + container.AvoidAsyncImagesLoading = true; + container.AvoidImagesLateLoading = true; + + if (stylesheetLoad != null) + container.StylesheetLoad += stylesheetLoad; + if (imageLoad != null) + container.ImageLoad += imageLoad; + + container.SetHtml(html, cssData); + container.PerformLayout(); + container.PerformPaint(g, new Rect(0, 0, double.MaxValue, double.MaxValue)); + + actualSize = container.ActualSize; + } + } + + return actualSize; + } + + #endregion + */ + } +} \ No newline at end of file diff --git a/src/Perspex.HtmlRenderer/Perspex.HtmlRenderer.csproj b/src/Perspex.HtmlRenderer/Perspex.HtmlRenderer.csproj new file mode 100644 index 0000000000..2a507821df --- /dev/null +++ b/src/Perspex.HtmlRenderer/Perspex.HtmlRenderer.csproj @@ -0,0 +1,198 @@ + + + + + 10.0 + Debug + AnyCPU + {5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D} + Library + Properties + Perspex.HtmlRenderer + Perspex.HtmlRenderer + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile7 + v4.5 + + + true + full + false + bin\Debug\ + TRACE;DEBUG;PCL + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Perspex.Animation + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {D2221C82-4A25-4583-9B43-D791E3F6820C} + Perspex.Controls + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Perspex.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Perspex.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Perspex.Layout + + + {EB582467-6ABB-43A1-B052-E981BA910E3A} + Perspex.SceneGraph + + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Perspex.Styling + + + + + + + + ..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll + True + + + ..\..\packages\Rx-Interfaces.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Interfaces.dll + True + + + + + + + + + + \ No newline at end of file diff --git a/src/Perspex.HtmlRenderer/Perspex.HtmlRenderer.csproj.DotSettings b/src/Perspex.HtmlRenderer/Perspex.HtmlRenderer.csproj.DotSettings new file mode 100644 index 0000000000..73e96563f9 --- /dev/null +++ b/src/Perspex.HtmlRenderer/Perspex.HtmlRenderer.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp60 \ No newline at end of file diff --git a/src/Perspex.HtmlRenderer/Properties/AssemblyInfo.cs b/src/Perspex.HtmlRenderer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..9ab512fab5 --- /dev/null +++ b/src/Perspex.HtmlRenderer/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.HtmlRenderer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.HtmlRenderer")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Perspex.HtmlRenderer/PropertyHelper.cs b/src/Perspex.HtmlRenderer/PropertyHelper.cs new file mode 100644 index 0000000000..bd322babf0 --- /dev/null +++ b/src/Perspex.HtmlRenderer/PropertyHelper.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex.HtmlRenderer +{ + static class PropertyHelper + { + + public static PerspexProperty Register(string name, T def, Action changed) where TOwner : PerspexObject + { + var pp = PerspexProperty.Register(name, def); + Action cb = args => + { + changed(args.Sender, args); + }; + + pp.Changed.Subscribe(cb); + return pp; + } + } +} diff --git a/src/Perspex.HtmlRenderer/RoutedEventArgsWrapper.cs b/src/Perspex.HtmlRenderer/RoutedEventArgsWrapper.cs new file mode 100644 index 0000000000..625c296d00 --- /dev/null +++ b/src/Perspex.HtmlRenderer/RoutedEventArgsWrapper.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Perspex.Interactivity; + +namespace Perspex.HtmlRenderer +{ + public class RoutedEventArgsWrapper : RoutedEventArgs + { + public T Event { get; set; } + } +} diff --git a/src/Perspex.HtmlRenderer/Utilities/Util.cs b/src/Perspex.HtmlRenderer/Utilities/Util.cs new file mode 100644 index 0000000000..10722785a6 --- /dev/null +++ b/src/Perspex.HtmlRenderer/Utilities/Util.cs @@ -0,0 +1,123 @@ +// "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.Entities; + +namespace TheArtOfDev.HtmlRenderer.Perspex.Utilities +{ + /// + /// Utilities for converting WPF entities to HtmlRenderer core entities. + /// + internal static class Util + { + /// + /// Convert from WPF point to core point. + /// + public static RPoint Convert(Point p) + { + return new RPoint(p.X, p.Y); + } + + /// + /// Convert from WPF point to core point. + /// + public static Point[] Convert(RPoint[] points) + { + Point[] myPoints = new Point[points.Length]; + for (int i = 0; i < points.Length; i++) + myPoints[i] = Convert(points[i]); + return myPoints; + } + + /// + /// Convert from core point to WPF point. + /// + public static Point Convert(RPoint p) + { + return new Point(p.X, p.Y); + } + + /// + /// Convert from core point to WPF point. + /// + public static Point ConvertRound(RPoint p) + { + return new Point((int)p.X, (int)p.Y); + } + + /// + /// Convert from WPF size to core size. + /// + public static RSize Convert(Size s) + { + return new RSize(s.Width, s.Height); + } + + /// + /// Convert from core size to WPF size. + /// + public static Size Convert(RSize s) + { + return new Size(s.Width, s.Height); + } + + /// + /// Convert from core point to WPF point. + /// + public static Size ConvertRound(RSize s) + { + return new Size((int)s.Width, (int)s.Height); + } + + /// + /// Convert from WPF rectangle to core rectangle. + /// + public static RRect Convert(Rect r) + { + return new RRect(r.X, r.Y, r.Width, r.Height); + } + + /// + /// Convert from core rectangle to WPF rectangle. + /// + public static Rect Convert(RRect r) + { + return new Rect(r.X, r.Y, r.Width, r.Height); + } + + /// + /// Convert from core rectangle to WPF rectangle. + /// + public static Rect ConvertRound(RRect r) + { + return new Rect((int)r.X, (int)r.Y, (int)r.Width, (int)r.Height); + } + + /// + /// Convert from WPF color to core color. + /// + public static RColor Convert(Color c) + { + return RColor.FromArgb(c.A, c.R, c.G, c.B); + } + + /// + /// Convert from core color to WPF color. + /// + public static Color Convert(RColor c) + { + return Color.FromArgb(c.A, c.R, c.G, c.B); + } + } +} \ No newline at end of file diff --git a/src/Perspex.HtmlRenderer/external b/src/Perspex.HtmlRenderer/external new file mode 160000 index 0000000000..8ae3973d6b --- /dev/null +++ b/src/Perspex.HtmlRenderer/external @@ -0,0 +1 @@ +Subproject commit 8ae3973d6b766b2b286a8869c66470e6eba74ab1 diff --git a/src/Perspex.HtmlRenderer/packages.config b/src/Perspex.HtmlRenderer/packages.config new file mode 100644 index 0000000000..f23358716a --- /dev/null +++ b/src/Perspex.HtmlRenderer/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file