From 645d1d87928f3a2e8181aef6b38fd67cbdeb11fa Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 10 Sep 2015 03:40:21 +0300 Subject: [PATCH 01/10] WIP: Porting HTMLRenderer support from WPF --- .gitmodules | 4 + Perspex.sln | 6 + samples/TestApplication/Program.cs | 33 + .../TestApplication/TestApplication.csproj | 5 + samples/TestApplication/html.htm | 170 +++++ .../Adapters/BrushAdapter.cs | 47 ++ .../Adapters/ContextMenuAdapter.cs | 51 ++ .../Adapters/ControlAdapter.cs | 116 ++++ .../Adapters/FontAdapter.cs | 106 +++ .../Adapters/FontFamilyAdapter.cs | 29 + .../Adapters/GraphicsAdapter.cs | 277 ++++++++ .../Adapters/GraphicsPathAdapter.cs | 66 ++ .../Adapters/ImageAdapter.cs | 52 ++ .../Adapters/PenAdapter.cs | 93 +++ .../Adapters/PerspexAdapter.cs | 112 ++++ src/Perspex.HtmlRenderer/Compat/Attributes.cs | 28 + src/Perspex.HtmlRenderer/Compat/ThreadPool.cs | 22 + src/Perspex.HtmlRenderer/HtmlContainer.cs | 468 +++++++++++++ src/Perspex.HtmlRenderer/HtmlControl.cs | 620 ++++++++++++++++++ src/Perspex.HtmlRenderer/HtmlLabel.cs | 135 ++++ src/Perspex.HtmlRenderer/HtmlRender.cs | 424 ++++++++++++ .../Perspex.HtmlRenderer.csproj | 198 ++++++ .../Perspex.HtmlRenderer.csproj.DotSettings | 2 + .../Properties/AssemblyInfo.cs | 30 + src/Perspex.HtmlRenderer/PropertyHelper.cs | 24 + .../RoutedEventArgsWrapper.cs | 14 + src/Perspex.HtmlRenderer/Utilities/Util.cs | 123 ++++ src/Perspex.HtmlRenderer/external | 1 + src/Perspex.HtmlRenderer/packages.config | 7 + 29 files changed, 3263 insertions(+) create mode 100644 samples/TestApplication/html.htm create mode 100644 src/Perspex.HtmlRenderer/Adapters/BrushAdapter.cs create mode 100644 src/Perspex.HtmlRenderer/Adapters/ContextMenuAdapter.cs create mode 100644 src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs create mode 100644 src/Perspex.HtmlRenderer/Adapters/FontAdapter.cs create mode 100644 src/Perspex.HtmlRenderer/Adapters/FontFamilyAdapter.cs create mode 100644 src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs create mode 100644 src/Perspex.HtmlRenderer/Adapters/GraphicsPathAdapter.cs create mode 100644 src/Perspex.HtmlRenderer/Adapters/ImageAdapter.cs create mode 100644 src/Perspex.HtmlRenderer/Adapters/PenAdapter.cs create mode 100644 src/Perspex.HtmlRenderer/Adapters/PerspexAdapter.cs create mode 100644 src/Perspex.HtmlRenderer/Compat/Attributes.cs create mode 100644 src/Perspex.HtmlRenderer/Compat/ThreadPool.cs create mode 100644 src/Perspex.HtmlRenderer/HtmlContainer.cs create mode 100644 src/Perspex.HtmlRenderer/HtmlControl.cs create mode 100644 src/Perspex.HtmlRenderer/HtmlLabel.cs create mode 100644 src/Perspex.HtmlRenderer/HtmlRender.cs create mode 100644 src/Perspex.HtmlRenderer/Perspex.HtmlRenderer.csproj create mode 100644 src/Perspex.HtmlRenderer/Perspex.HtmlRenderer.csproj.DotSettings create mode 100644 src/Perspex.HtmlRenderer/Properties/AssemblyInfo.cs create mode 100644 src/Perspex.HtmlRenderer/PropertyHelper.cs create mode 100644 src/Perspex.HtmlRenderer/RoutedEventArgsWrapper.cs create mode 100644 src/Perspex.HtmlRenderer/Utilities/Util.cs create mode 160000 src/Perspex.HtmlRenderer/external create mode 100644 src/Perspex.HtmlRenderer/packages.config 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 From 34dff255f559617275f77580728529a0e5123363 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 10 Sep 2015 04:18:48 +0300 Subject: [PATCH 02/10] HTML: Seems to be rendering more like reference implementation --- samples/TestApplication/Program.cs | 8 +-- .../Adapters/FontAdapter.cs | 4 +- .../Adapters/GraphicsAdapter.cs | 56 ++++--------------- .../Adapters/GraphicsPathAdapter.cs | 1 + 4 files changed, 19 insertions(+), 50 deletions(-) diff --git a/samples/TestApplication/Program.cs b/samples/TestApplication/Program.cs index d44fd7546a..3fcdcd4e80 100644 --- a/samples/TestApplication/Program.cs +++ b/samples/TestApplication/Program.cs @@ -377,20 +377,20 @@ namespace TestApplication Header = "Html", Content = new ScrollViewer() { - Width = 500, + Width = 900, VerticalScrollBarVisibility = ScrollBarVisibility.Visible, Content = new Border { - Height = 1500, + Height = 2500, Child = new HtmlLabel() { Text = htmlText, AutoSize = false, - MaxWidth = 500, - MaxHeight = 900 + MaxWidth = 900, + MaxHeight = 2500 } } diff --git a/src/Perspex.HtmlRenderer/Adapters/FontAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/FontAdapter.cs index 43b726ec8c..1e645cd96c 100644 --- a/src/Perspex.HtmlRenderer/Adapters/FontAdapter.cs +++ b/src/Perspex.HtmlRenderer/Adapters/FontAdapter.cs @@ -59,8 +59,8 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters Name = fontFamily; _size = size; //TODO: Somehow get proper line spacing and underlinePosition - var lineSpacing = 2; - var underlinePosition = 1; + var lineSpacing = 1; + var underlinePosition = 0; _height = 96d / 72d * _size * lineSpacing; _underlineOffset = 96d / 72d * _size * (lineSpacing + underlinePosition); diff --git a/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs index ef0901692d..6b3aacd500 100644 --- a/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs +++ b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs @@ -11,6 +11,7 @@ // "The Art of War" using System; +using System.Collections.Generic; using System.Globalization; using Perspex; using Perspex.Media; @@ -41,6 +42,9 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters #endregion + private Stack _clipStack = new Stack(); + + /// /// Init. /// @@ -66,22 +70,25 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters _releaseGraphics = false; } + + public override void PopClip() { - /* - _g.Pop(); - _clipStack.Pop(); - */ + _clipStack.Pop()?.Dispose(); } public override void PushClip(RRect rect) { + _clipStack.Push(_g.PushClip(Util.Convert(rect))); //_clipStack.Push(rect); //_g.PushClip(new RectangleGeometry(Utils.Convert(rect))); } public override void PushClipExclude(RRect rect) { + _clipStack.Push(null); + + //TODO: Implement exclude rect, see #128 //var geometry = new CombinedGeometry(); //geometry.Geometry1 = new RectangleGeometry(Utils.Convert(_clipStack.Peek())); //geometry.Geometry2 = new RectangleGeometry(Utils.Convert(rect)); @@ -125,46 +132,6 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters 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) @@ -266,6 +233,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters context.BeginFigure(Util.Convert(points[0]), true); for (int i = 1; i < points.Length; i++) context.LineTo(Util.Convert(points[i])); + context.EndFigure(false); } _g.DrawGeometry(((BrushAdapter)brush).Brush, null, g); diff --git a/src/Perspex.HtmlRenderer/Adapters/GraphicsPathAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/GraphicsPathAdapter.cs index 80797429de..5ccfa7140c 100644 --- a/src/Perspex.HtmlRenderer/Adapters/GraphicsPathAdapter.cs +++ b/src/Perspex.HtmlRenderer/Adapters/GraphicsPathAdapter.cs @@ -57,6 +57,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters public StreamGeometry GetClosedGeometry() { _geometryContext.EndFigure(true); + _geometryContext.Dispose(); return _geometry; } From 104b84ba76735654ef9b06c9dacefe9f3296f6e2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 11 Sep 2015 22:02:19 +0300 Subject: [PATCH 03/10] Fixed HtmlLabel measure --- samples/TestApplication/Program.cs | 18 ++---- .../Adapters/GraphicsAdapter.cs | 55 ++++++++++++++++++- src/Perspex.HtmlRenderer/HtmlLabel.cs | 30 ++-------- 3 files changed, 61 insertions(+), 42 deletions(-) diff --git a/samples/TestApplication/Program.cs b/samples/TestApplication/Program.cs index 3fcdcd4e80..08adbc8a84 100644 --- a/samples/TestApplication/Program.cs +++ b/samples/TestApplication/Program.cs @@ -377,22 +377,14 @@ namespace TestApplication Header = "Html", Content = new ScrollViewer() { - Width = 900, + Width = 600, + HorizontalAlignment = HorizontalAlignment.Center, + CanScrollHorizontally = false, VerticalScrollBarVisibility = ScrollBarVisibility.Visible, Content = - new Border + new HtmlLabel() { - Height = 2500, - Child = - new HtmlLabel() - { - - Text = htmlText, - AutoSize = false, - MaxWidth = 900, - MaxHeight = 2500 - - } + Text = htmlText } } }; diff --git a/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs index 6b3aacd500..dedb4c9e48 100644 --- a/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs +++ b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs @@ -123,10 +123,59 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters 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; + var fullLength = text.Measure().Width; + if (fullLength < maxWidth) + { + charFitWidth = fullLength; + charFit = str.Length; + return; + } + + int lastLen = 0; + double lastMeasure = 0; + BinarySearch(len => + { + text = GetText(str.Substring(0, len), font); + var size = text.Measure().Width; + lastMeasure = size; + lastLen = len; + if (size <= maxWidth) + return -1; + return 1; + + }, 0, str.Length); + if (lastMeasure > maxWidth) + { + lastLen--; + lastMeasure = GetText(str.Substring(0, lastLen), font).Measure().Width; + } + charFit = lastLen; + charFitWidth = lastMeasure; + } - + + private static int BinarySearch(Func condition, int start, int end) + { + do + { + int ind = start + (end - start)/2; + int res = condition(ind); + if (res == 0) + return ind; + else if (res > 0) + { + if (start != ind) + start = ind; + else + start = ind + 1; + } + else + end = ind; + + } while (end > start); + return -1; + } + public override void DrawString(string str, RFont font, RColor color, RPoint point, RSize size, bool rtl) { var text = GetText(str, font); diff --git a/src/Perspex.HtmlRenderer/HtmlLabel.cs b/src/Perspex.HtmlRenderer/HtmlLabel.cs index 7d9b4c0489..f4c90cf05e 100644 --- a/src/Perspex.HtmlRenderer/HtmlLabel.cs +++ b/src/Perspex.HtmlRenderer/HtmlLabel.cs @@ -48,29 +48,6 @@ namespace Perspex.Controls //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 /// @@ -86,11 +63,12 @@ namespace Perspex.Controls 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 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); + _htmlContainer.HtmlContainerInt.MaxSize = maxSize; - var newSize = HtmlRendererUtils.Layout(ig, _htmlContainer.HtmlContainerInt, size, minSize, maxSize, AutoSize, AutoSizeHeightOnly); - + _htmlContainer.HtmlContainerInt.PerformLayout(ig); + var newSize = _htmlContainer.ActualSize; constraint = new Size(newSize.Width + horizontal, newSize.Height + vertical); } } From 18c0d3c65563e3cce6b0b526be9e31fd252b559c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 11 Sep 2015 22:50:32 +0300 Subject: [PATCH 04/10] Various html renderer improvements --- samples/TestApplication/Program.cs | 1 + .../Adapters/ControlAdapter.cs | 21 +- .../Adapters/GraphicsAdapter.cs | 5 +- .../Adapters/ImageAdapter.cs | 2 +- src/Perspex.HtmlRenderer/HtmlControl.cs | 33 +- src/Perspex.HtmlRenderer/HtmlLabel.cs | 7 +- src/Perspex.HtmlRenderer/HtmlRender.cs | 424 ------------------ .../Perspex.HtmlRenderer.csproj | 1 - 8 files changed, 33 insertions(+), 461 deletions(-) delete mode 100644 src/Perspex.HtmlRenderer/HtmlRender.cs diff --git a/samples/TestApplication/Program.cs b/samples/TestApplication/Program.cs index 08adbc8a84..7f1de7cbcd 100644 --- a/samples/TestApplication/Program.cs +++ b/samples/TestApplication/Program.cs @@ -378,6 +378,7 @@ namespace TestApplication Content = new ScrollViewer() { Width = 600, + MaxHeight = 600, HorizontalAlignment = HorizontalAlignment.Center, CanScrollHorizontally = false, VerticalScrollBarVisibility = ScrollBarVisibility.Visible, diff --git a/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs index b3908d12e8..be9be611e5 100644 --- a/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs +++ b/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs @@ -39,6 +39,8 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters ArgChecker.AssertArgNotNull(control, "control"); _control = control; + _control.PointerPressed += delegate { _leftMouseButton = true; }; + _control.PointerPressed += delegate { _leftMouseButton = false; }; } /// @@ -53,28 +55,19 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters { get { - //TODO: Implement - //return Utils.Convert(_control.PointFromScreen(Mouse.GetPosition(_control))); - return new RPoint(0, 0); + return Util.Convert(MouseDevice.Instance.GetPosition(_control)); } } - public override bool LeftMouseButton - { - get - { - return false; - //TODO: Implement - //return Mouse.LeftButton == MouseButtonState.Pressed; - } - } + private bool _leftMouseButton; + public override bool LeftMouseButton => _leftMouseButton; public override bool RightMouseButton { get { return false; - //TODO: Implement + //TODO: Implement right mouse click //return Mouse.RightButton == MouseButtonState.Pressed; } } @@ -96,7 +89,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters public override void DoDragDropCopy(object dragDropData) { - //TODO: Implement + //TODO: Implement DragDropCopy //DragDrop.DoDragDrop(_control, dragDropData, DragDropEffects.Copy); } diff --git a/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs index dedb4c9e48..e0da822676 100644 --- a/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs +++ b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs @@ -205,9 +205,8 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters public override void Dispose() { - //TODO: Do something about Dispose - //if (_releaseGraphics) - // _g.Close(); + if (_releaseGraphics) + _g.Dispose(); } diff --git a/src/Perspex.HtmlRenderer/Adapters/ImageAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/ImageAdapter.cs index c2ef084f4d..ed6a41d20b 100644 --- a/src/Perspex.HtmlRenderer/Adapters/ImageAdapter.cs +++ b/src/Perspex.HtmlRenderer/Adapters/ImageAdapter.cs @@ -44,7 +44,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters public override void Dispose() { - //TODO: Implement + //TODO: Implement image disposal /*if (_image.StreamSource != null) _image.StreamSource.Dispose();*/ } diff --git a/src/Perspex.HtmlRenderer/HtmlControl.cs b/src/Perspex.HtmlRenderer/HtmlControl.cs index 3ac0812a1f..a9f9dc9d9a 100644 --- a/src/Perspex.HtmlRenderer/HtmlControl.cs +++ b/src/Perspex.HtmlRenderer/HtmlControl.cs @@ -19,6 +19,7 @@ using Perspex.Media; using Perspex.Threading; using TheArtOfDev.HtmlRenderer.Core; using TheArtOfDev.HtmlRenderer.Core.Entities; +using TheArtOfDev.HtmlRenderer.Perspex.Adapters; namespace Perspex.Controls { @@ -91,11 +92,14 @@ namespace Perspex.Controls PropertyHelper.Register("Text", null, OnPerspexProperty_valueChanged); public static readonly PerspexProperty BackgroundProperty = - PerspexProperty.Register("Background", Colors.White); + PerspexProperty.Register("Background", Brushes.White); public static readonly PerspexProperty BorderThicknessProperty = PerspexProperty.Register("BorderThickness", new Thickness(0)); + public static readonly PerspexProperty BorderBrushProperty = + PerspexProperty.Register("BorderBrush"); + public static readonly PerspexProperty PaddingProperty = PerspexProperty.Register("Padding", new Thickness(0)); @@ -252,15 +256,21 @@ namespace Perspex.Controls set { SetValue(BorderThicknessProperty, value); } } + public Brush BorderBrush + { + get { return (Brush)GetValue(BorderBrushProperty); } + set { SetValue(BorderThicknessProperty, value); } + } + public Thickness Padding { get { return (Thickness)GetValue(PaddingProperty); } set { SetValue(PaddingProperty, value); } } - public Color Background + public Brush Background { - get { return (Color?) GetValue(BackgroundProperty) ?? Colors.White; } + get { return (Brush) GetValue(BackgroundProperty); } set { SetValue(BackgroundProperty, value);} } @@ -321,15 +331,10 @@ namespace Perspex.Controls 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)); - */ + context.FillRectange(Background, new Rect(RenderSize)); - if (BorderThickness != new Thickness(0)) + if (BorderThickness != new Thickness(0) && BorderBrush != null) { - //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)); @@ -346,7 +351,7 @@ namespace Perspex.Controls if (_htmlContainer != null && htmlWidth > 0 && htmlHeight > 0) { /* - //TODO: Rever antialiasing fixes + //TODO: Revert antialiasing fixes var windows = Window.GetWindow(this); if (windows != null) { @@ -514,8 +519,8 @@ namespace Perspex.Controls /// protected virtual void InvokeMouseMove() { - - _htmlContainer.HandleMouseMove(this, MouseDevice.Instance.GetPosition(this)); + + _htmlContainer.HandleMouseMove(this, MouseDevice.Instance?.GetPosition(this) ?? default(Point)); } /// @@ -541,7 +546,7 @@ namespace Perspex.Controls } else if (e.Property == BaseStylesheetProperty) { - var baseCssData = HtmlRender.ParseStyleSheet((string)e.NewValue); + var baseCssData = CssData.Parse(PerspexAdapter.Instance, (string)e.NewValue); control._baseCssData = baseCssData; htmlContainer.SetHtml(control.Text, baseCssData); } diff --git a/src/Perspex.HtmlRenderer/HtmlLabel.cs b/src/Perspex.HtmlRenderer/HtmlLabel.cs index f4c90cf05e..1ea40f3684 100644 --- a/src/Perspex.HtmlRenderer/HtmlLabel.cs +++ b/src/Perspex.HtmlRenderer/HtmlLabel.cs @@ -45,7 +45,7 @@ namespace Perspex.Controls /// static HtmlLabel() { - //BackgroundProperty.OverrideDefaultValue(Brushes.Transparent); + BackgroundProperty.OverrideDefaultValue(Brushes.Transparent); } #region Private methods @@ -62,9 +62,8 @@ namespace Perspex.Controls 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 size = new Size(Math.Min(MaxWidth, constraint.Width), Math.Min(MaxHeight, constraint.Height)); + var maxSize = new RSize(size.Width < Double.PositiveInfinity ? size.Width - horizontal : 0, size.Height < Double.PositiveInfinity ? size.Height - vertical : 0); _htmlContainer.HtmlContainerInt.MaxSize = maxSize; _htmlContainer.HtmlContainerInt.PerformLayout(ig); diff --git a/src/Perspex.HtmlRenderer/HtmlRender.cs b/src/Perspex.HtmlRenderer/HtmlRender.cs deleted file mode 100644 index 686116e6bb..0000000000 --- a/src/Perspex.HtmlRenderer/HtmlRender.cs +++ /dev/null @@ -1,424 +0,0 @@ -// "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 index 2a507821df..47e6491bf0 100644 --- a/src/Perspex.HtmlRenderer/Perspex.HtmlRenderer.csproj +++ b/src/Perspex.HtmlRenderer/Perspex.HtmlRenderer.csproj @@ -129,7 +129,6 @@ - From 676c2e20d47a283c2fcbec49cb5449bf278946b2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 11 Sep 2015 20:49:31 +0300 Subject: [PATCH 05/10] Html: Implemented event propagation --- src/Perspex.HtmlRenderer/HtmlControl.cs | 111 +++++++----------- ...pper.cs => HtmlRendererRoutedEventArgs.cs} | 2 +- .../Perspex.HtmlRenderer.csproj | 2 +- 3 files changed, 46 insertions(+), 69 deletions(-) rename src/Perspex.HtmlRenderer/{RoutedEventArgsWrapper.cs => HtmlRendererRoutedEventArgs.cs} (77%) diff --git a/src/Perspex.HtmlRenderer/HtmlControl.cs b/src/Perspex.HtmlRenderer/HtmlControl.cs index a9f9dc9d9a..87d06a1864 100644 --- a/src/Perspex.HtmlRenderer/HtmlControl.cs +++ b/src/Perspex.HtmlRenderer/HtmlControl.cs @@ -56,8 +56,6 @@ namespace Perspex.Controls public class HtmlControl : Control { - #region Fields and Consts - /// /// Underline html container instance. /// @@ -73,11 +71,6 @@ namespace Perspex.Controls ///
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 = @@ -106,19 +99,18 @@ namespace Perspex.Controls public static readonly RoutedEvent LoadCompleteEvent = RoutedEvent.Register("LoadComplete", RoutingStrategies.Bubble, typeof(HtmlControl)); public static readonly RoutedEvent LinkClickedEvent = - RoutedEvent.Register>("LinkClicked", RoutingStrategies.Bubble, typeof(HtmlControl)); + RoutedEvent.Register>("LinkClicked", RoutingStrategies.Bubble, typeof(HtmlControl)); public static readonly RoutedEvent RenderErrorEvent - = RoutedEvent.Register>("RenderError", RoutingStrategies.Bubble, typeof(HtmlControl)); + = RoutedEvent.Register>("RenderError", RoutingStrategies.Bubble, typeof(HtmlControl)); public static readonly RoutedEvent RefreshEvent - = RoutedEvent.Register< RoutedEventArgsWrapper>("Refresh", RoutingStrategies.Bubble, typeof(HtmlControl)); + = RoutedEvent.Register>("Refresh", RoutingStrategies.Bubble, typeof(HtmlControl)); public static readonly RoutedEvent StylesheetLoadEvent - = RoutedEvent.Register>("StylesheetLoad", RoutingStrategies.Bubble, typeof(HtmlControl)); + = RoutedEvent.Register>("StylesheetLoad", RoutingStrategies.Bubble, typeof(HtmlControl)); public static readonly RoutedEvent ImageLoadEvent - = RoutedEvent.Register>("ImageLoad", RoutingStrategies.Bubble, + = RoutedEvent.Register>("ImageLoad", RoutingStrategies.Bubble, typeof (HtmlControl)); - #endregion /// @@ -126,20 +118,20 @@ namespace Perspex.Controls /// protected HtmlControl() { - _htmlContainer = new HtmlContainer();/* - _htmlContainer.LoadComplete += OnLoadComplete; - _htmlContainer.LinkClicked += OnLinkClicked; - _htmlContainer.RenderError += OnRenderError; - _htmlContainer.Refresh += OnRefresh; - _htmlContainer.StylesheetLoad += OnStylesheetLoad; - _htmlContainer.ImageLoad += OnImageLoad;*/ + _htmlContainer = new HtmlContainer(); + _htmlContainer.LoadComplete += (_, e) => OnLoadComplete(e); + _htmlContainer.LinkClicked += (_, e) => OnLinkClicked(e); + _htmlContainer.RenderError += (_, e) => OnRenderError(e); + _htmlContainer.Refresh += (_, e) => OnRefresh(e); + _htmlContainer.StylesheetLoad += (_, e) => OnStylesheetLoad(e); + _htmlContainer.ImageLoad += (_, e) => OnImageLoad(e); } /// /// Raised when the set html document has been fully loaded.
/// Allows manipulation of the html dom, scroll position, etc. ///
- public event EventHandler LoadComplete + public event EventHandler> LoadComplete { add { AddHandler(LoadCompleteEvent, value); } remove { RemoveHandler(LoadCompleteEvent, value); } @@ -149,7 +141,7 @@ namespace Perspex.Controls /// Raised when the user clicks on a link in the html.
/// Allows canceling the execution of the link. /// - public event EventHandler LinkClicked + public event EventHandler> LinkClicked { add { AddHandler(LinkClickedEvent, value); } remove { RemoveHandler(LinkClickedEvent, value); } @@ -158,7 +150,7 @@ namespace Perspex.Controls /// /// Raised when an error occurred during html rendering.
///
- public event EventHandler RenderError + public event EventHandler> RenderError { add { AddHandler(RenderErrorEvent, value); } remove { RemoveHandler(RenderErrorEvent, value); } @@ -169,7 +161,7 @@ namespace Perspex.Controls /// 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 + public event EventHandler> StylesheetLoad { add { AddHandler(StylesheetLoadEvent, value); } remove { RemoveHandler(StylesheetLoadEvent, value); } @@ -179,7 +171,7 @@ namespace Perspex.Controls /// 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 + public event EventHandler> ImageLoad { add { AddHandler(ImageLoadEvent, value); } remove { RemoveHandler(ImageLoadEvent, value); } @@ -322,8 +314,7 @@ namespace Perspex.Controls _htmlContainer.ClearSelection(); } - - #region Private methods + //HACK: We don't have support for RenderSize for now private Size RenderSize => new Size(Bounds.Width, Bounds.Height); @@ -443,50 +434,42 @@ namespace Perspex.Controls _htmlContainer.HandleKeyDown(this, e); } + void RaiseRouted(RoutedEvent ev, T arg) + { + var e =new HtmlRendererRoutedEventArgs + { + Event = arg, + Source = this, + RoutedEvent = ev, + Route = ev.RoutingStrategies + }; + RaiseEvent(e); + } + /// /// Propagate the LoadComplete event from root container. /// - protected virtual void OnLoadComplete(EventArgs e) - { - //RoutedEventArgs newEventArgs = new RoutedEventArgs(LoadCompleteEvent, this, e); - //RaiseEvent(newEventArgs); - } + protected virtual void OnLoadComplete(EventArgs e) => RaiseRouted(LoadCompleteEvent, e); /// /// Propagate the LinkClicked event from root container. /// - protected virtual void OnLinkClicked(HtmlLinkClickedEventArgs e) - { - //RoutedEventArgs newEventArgs = new RoutedEvenArgs(LinkClickedEvent, this, e); - //RaiseEvent(newEventArgs); - } + protected virtual void OnLinkClicked(HtmlLinkClickedEventArgs e) => RaiseRouted(LinkClickedEvent, e); /// /// Propagate the Render Error event from root container. /// - protected virtual void OnRenderError(HtmlRenderErrorEventArgs e) - { - //RoutedEventArgs newEventArgs = new RoutedEvenArgs(RenderErrorEvent, this, e); - //RaiseEvent(newEventArgs); - } + protected virtual void OnRenderError(HtmlRenderErrorEventArgs e) => RaiseRouted(RenderErrorEvent, e); /// /// Propagate the stylesheet load event from root container. /// - protected virtual void OnStylesheetLoad(HtmlStylesheetLoadEventArgs e) - { - //RoutedEventArgs newEventArgs = new RoutedEvenArgs(StylesheetLoadEvent, this, e); - //RaiseEvent(newEventArgs); - } + protected virtual void OnStylesheetLoad(HtmlStylesheetLoadEventArgs e) => RaiseRouted(StylesheetLoadEvent, e); /// /// Propagate the image load event from root container. /// - protected virtual void OnImageLoad(HtmlImageLoadEventArgs e) - { - //RoutedEventArgs newEventArgs = new RoutedEvenArgs(ImageLoadEvent, this, e); - //RaiseEvent(newEventArgs); - } + protected virtual void OnImageLoad(HtmlImageLoadEventArgs e) => RaiseRouted(ImageLoadEvent, e); /// /// Handle html renderer invalidate and re-layout as requested. @@ -526,7 +509,8 @@ namespace Perspex.Controls /// /// Handle when dependency property value changes to update the underline HtmlContainer with the new value. /// - private static void OnPerspexProperty_valueChanged(PerspexObject PerspexObject, PerspexPropertyChangedEventArgs e) + private static void OnPerspexProperty_valueChanged(PerspexObject PerspexObject, + PerspexPropertyChangedEventArgs e) { var control = PerspexObject as HtmlControl; if (control != null) @@ -534,26 +518,26 @@ namespace Perspex.Controls var htmlContainer = control._htmlContainer; if (e.Property == AvoidImagesLateLoadingProperty) { - htmlContainer.AvoidImagesLateLoading = (bool)e.NewValue; + htmlContainer.AvoidImagesLateLoading = (bool) e.NewValue; } else if (e.Property == IsSelectionEnabledProperty) { - htmlContainer.IsSelectionEnabled = (bool)e.NewValue; + htmlContainer.IsSelectionEnabled = (bool) e.NewValue; } else if (e.Property == IsContextMenuEnabledProperty) { - htmlContainer.IsContextMenuEnabled = (bool)e.NewValue; + htmlContainer.IsContextMenuEnabled = (bool) e.NewValue; } else if (e.Property == BaseStylesheetProperty) { - var baseCssData = CssData.Parse(PerspexAdapter.Instance, (string)e.NewValue); + var baseCssData = CssData.Parse(PerspexAdapter.Instance, (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); + htmlContainer.SetHtml((string) e.NewValue, control._baseCssData); control.InvalidateMeasure(); control.InvalidateVisual(); control.InvokeMouseMove(); @@ -562,10 +546,8 @@ namespace Perspex.Controls } - #region Private event handlers - - - /* TODO: Implement events + //TODO: Implement CheckAccess calls + /* private void OnLoadComplete(object sender, EventArgs e) { @@ -616,10 +598,5 @@ namespace Perspex.Controls Dispatcher.Invoke(new Action(OnRefresh), e); } */ - - #endregion - - - #endregion } } \ No newline at end of file diff --git a/src/Perspex.HtmlRenderer/RoutedEventArgsWrapper.cs b/src/Perspex.HtmlRenderer/HtmlRendererRoutedEventArgs.cs similarity index 77% rename from src/Perspex.HtmlRenderer/RoutedEventArgsWrapper.cs rename to src/Perspex.HtmlRenderer/HtmlRendererRoutedEventArgs.cs index 625c296d00..61f2ccb1fa 100644 --- a/src/Perspex.HtmlRenderer/RoutedEventArgsWrapper.cs +++ b/src/Perspex.HtmlRenderer/HtmlRendererRoutedEventArgs.cs @@ -7,7 +7,7 @@ using Perspex.Interactivity; namespace Perspex.HtmlRenderer { - public class RoutedEventArgsWrapper : RoutedEventArgs + public class HtmlRendererRoutedEventArgs : RoutedEventArgs { public T Event { get; set; } } diff --git a/src/Perspex.HtmlRenderer/Perspex.HtmlRenderer.csproj b/src/Perspex.HtmlRenderer/Perspex.HtmlRenderer.csproj index 47e6491bf0..197ca7634d 100644 --- a/src/Perspex.HtmlRenderer/Perspex.HtmlRenderer.csproj +++ b/src/Perspex.HtmlRenderer/Perspex.HtmlRenderer.csproj @@ -131,7 +131,7 @@ - + From 59740cdec85f4c5564e8861447bd4121c1aa79e2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 11 Sep 2015 21:00:28 +0300 Subject: [PATCH 06/10] Html: added DashStyle support --- .../Adapters/PenAdapter.cs | 42 +++++++------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/src/Perspex.HtmlRenderer/Adapters/PenAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/PenAdapter.cs index 1531f058ce..fa0960cf3f 100644 --- a/src/Perspex.HtmlRenderer/Adapters/PenAdapter.cs +++ b/src/Perspex.HtmlRenderer/Adapters/PenAdapter.cs @@ -10,6 +10,7 @@ // - Sun Tsu, // "The Art of War" +using System.Collections.Generic; using Perspex.Media; using TheArtOfDev.HtmlRenderer.Adapters; using TheArtOfDev.HtmlRenderer.Adapters.Entities; @@ -31,6 +32,8 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters /// private double _width; + private DashStyle _dashStyle; + /// /// the dash style of the pen /// @@ -52,41 +55,24 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters 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; - }*/ - } + set { DashStyles.TryGetValue(value, out _dashStyle); } } + private static readonly Dictionary DashStyles = new Dictionary + { + {RDashStyle.Solid,null }, + {RDashStyle.Dash, global::Perspex.Media.DashStyle.Dash }, + {RDashStyle.DashDot, global::Perspex.Media.DashStyle.DashDot }, + {RDashStyle.DashDotDot, global::Perspex.Media.DashStyle.DashDotDot }, + {RDashStyle.Dot, global::Perspex.Media.DashStyle.Dot } + }; + /// /// Create the actual Perspex pen instance. /// public Pen CreatePen() { - var pen = new Pen(_brush, _width); - //pen.DashStyle = _dashStyle; + var pen = new Pen(_brush, _width, _dashStyle); return pen; } } From 6b5eb0c5de4cb8dd207ee3621847226e1094fd7b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 11 Sep 2015 21:28:44 +0300 Subject: [PATCH 07/10] Html: Fixed selection and changed demo --- samples/TestApplication/html.htm | 255 +++++++----------- .../Adapters/ControlAdapter.cs | 4 +- .../Adapters/GraphicsAdapter.cs | 2 + src/Perspex.HtmlRenderer/HtmlContainer.cs | 2 +- src/Perspex.HtmlRenderer/HtmlControl.cs | 5 + 5 files changed, 107 insertions(+), 161 deletions(-) diff --git a/samples/TestApplication/html.htm b/samples/TestApplication/html.htm index 764e9511c6..3b0ee9e8e3 100644 --- a/samples/TestApplication/html.htm +++ b/samples/TestApplication/html.htm @@ -1,170 +1,111 @@ - Additional features - - + Intro + - -

- Additional features + +

+ HTML Renderer Project - Perspex port +
+ Beta support

-
-

- 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 :)

+
+

+ + +
  • Lightweight (~300K).
  • +
  • High performance and low memory footprint.
  • +
  • Extendable and configurable.
  • -

    - 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 -
    + Limitations + +

      +
    • All HTML end tags marked as + optional should be there. No problem with tags marked as forbidden.
    • +
    +

    - 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:

    + On the roadmap + Of course it's not quite finished yet. Here are some of the important things to + do.
      -
    • 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
    • +
    • Better performance
    • +
    • Support of position CSS property
    • +
    • Support of height and min-height CSS property
    • +
    • Better tables support, especially layouts
    • +
    • Support image align
    • +
    • Handle :hover selector
    • +
    • Selection by shift+arrows
    • +
    • Better HTML tag parsing (optional closing tags)
    • +
    • More styles support
    - - 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; }
    +

    + Vision +

    +
      +
    • Most complete static HTML Renderer (no java script).
    • +
    • Commercial web browser performance level.
    • +
    +
    +

    + 2015 - Nikita Tsukanov (Perspex Port) + +

    +
    + https://perspex.github.io/ +
    + +

    + 2012 - Arthur Teplitzki +

    +
    + http://TheArtOfDev.com +
    +

    + 2009 - Jose Manuel Menendez Poo +

    +
    + www.menendezpoo.com +
    \ No newline at end of file diff --git a/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs index be9be611e5..3f76af92e1 100644 --- a/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs +++ b/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs @@ -39,8 +39,6 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters ArgChecker.AssertArgNotNull(control, "control"); _control = control; - _control.PointerPressed += delegate { _leftMouseButton = true; }; - _control.PointerPressed += delegate { _leftMouseButton = false; }; } /// @@ -60,7 +58,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters } private bool _leftMouseButton; - public override bool LeftMouseButton => _leftMouseButton; + public override bool LeftMouseButton => (_control as HtmlControl)?.LeftMouseButton ?? false; public override bool RightMouseButton { diff --git a/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs index e0da822676..415363a5c0 100644 --- a/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs +++ b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs @@ -205,6 +205,8 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters public override void Dispose() { + while (_clipStack.Count != 0) + PopClip(); if (_releaseGraphics) _g.Dispose(); } diff --git a/src/Perspex.HtmlRenderer/HtmlContainer.cs b/src/Perspex.HtmlRenderer/HtmlContainer.cs index 729613f5d1..a97987e90d 100644 --- a/src/Perspex.HtmlRenderer/HtmlContainer.cs +++ b/src/Perspex.HtmlRenderer/HtmlContainer.cs @@ -376,7 +376,7 @@ namespace Perspex.Controls { ArgChecker.AssertArgNotNull(parent, "parent"); ArgChecker.AssertArgNotNull(e, "e"); - + _htmlContainerInt.HandleMouseDown(new ControlAdapter(parent), Util.Convert(e.GetPosition(parent))); } diff --git a/src/Perspex.HtmlRenderer/HtmlControl.cs b/src/Perspex.HtmlRenderer/HtmlControl.cs index 87d06a1864..66c2289ebb 100644 --- a/src/Perspex.HtmlRenderer/HtmlControl.cs +++ b/src/Perspex.HtmlRenderer/HtmlControl.cs @@ -127,6 +127,9 @@ namespace Perspex.Controls _htmlContainer.ImageLoad += (_, e) => OnImageLoad(e); } + //Hack for adapter + internal bool LeftMouseButton { get; private set; } + /// /// Raised when the set html document has been fully loaded.
    /// Allows manipulation of the html dom, scroll position, etc. @@ -397,6 +400,7 @@ namespace Perspex.Controls protected override void OnPointerPressed(PointerPressEventArgs e) { base.OnPointerPressed(e); + LeftMouseButton = true; _htmlContainer?.HandleLeftMouseDown(this, e); } @@ -408,6 +412,7 @@ namespace Perspex.Controls protected override void OnPointerReleased(PointerEventArgs e) { base.OnPointerReleased(e); + LeftMouseButton = false; if (_htmlContainer != null) _htmlContainer.HandleLeftMouseUp(this, e); } From 43bff94d01a732be58246018829de087a1a531b5 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 11 Sep 2015 21:36:13 +0300 Subject: [PATCH 08/10] Html: added missing css to demo --- samples/TestApplication/html.htm | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/samples/TestApplication/html.htm b/samples/TestApplication/html.htm index 3b0ee9e8e3..85da0cdb87 100644 --- a/samples/TestApplication/html.htm +++ b/samples/TestApplication/html.htm @@ -1,10 +1,27 @@ Intro - + - -

    + +

    HTML Renderer Project - Perspex port
    Beta support From 516152a72824f4aa020ff14da0e7b5b811e5125c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 11 Sep 2015 22:15:23 +0300 Subject: [PATCH 09/10] Changed the namespace --- samples/TestApplication/Program.cs | 1 + src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs | 1 + src/Perspex.HtmlRenderer/HtmlContainer.cs | 5 ++++- src/Perspex.HtmlRenderer/HtmlControl.cs | 3 ++- src/Perspex.HtmlRenderer/HtmlLabel.cs | 2 +- src/Perspex.HtmlRenderer/HtmlRendererRoutedEventArgs.cs | 2 +- 6 files changed, 10 insertions(+), 4 deletions(-) diff --git a/samples/TestApplication/Program.cs b/samples/TestApplication/Program.cs index 7f1de7cbcd..54f7686e8c 100644 --- a/samples/TestApplication/Program.cs +++ b/samples/TestApplication/Program.cs @@ -8,6 +8,7 @@ using Perspex; using Perspex.Animation; using Perspex.Collections; using Perspex.Controls; +using Perspex.Controls.Html; using Perspex.Controls.Primitives; using Perspex.Controls.Shapes; using Perspex.Controls.Templates; diff --git a/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs index 3f76af92e1..f0f84cb64f 100644 --- a/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs +++ b/src/Perspex.HtmlRenderer/Adapters/ControlAdapter.cs @@ -11,6 +11,7 @@ // "The Art of War" using Perspex.Controls; +using Perspex.Controls.Html; using Perspex.Input; using TheArtOfDev.HtmlRenderer.Adapters; using TheArtOfDev.HtmlRenderer.Adapters.Entities; diff --git a/src/Perspex.HtmlRenderer/HtmlContainer.cs b/src/Perspex.HtmlRenderer/HtmlContainer.cs index a97987e90d..ead879b116 100644 --- a/src/Perspex.HtmlRenderer/HtmlContainer.cs +++ b/src/Perspex.HtmlRenderer/HtmlContainer.cs @@ -12,6 +12,9 @@ using System; using System.Collections.Generic; +using Perspex; +using Perspex.Controls; +using Perspex.Controls.Html; using Perspex.Input; using Perspex.Media; using TheArtOfDev.HtmlRenderer.Adapters.Entities; @@ -22,7 +25,7 @@ using TheArtOfDev.HtmlRenderer.Core.Utils; using TheArtOfDev.HtmlRenderer.Perspex.Adapters; using TheArtOfDev.HtmlRenderer.Perspex.Utilities; -namespace Perspex.Controls +namespace TheArtOfDev.HtmlRenderer.Perspex { /// /// Low level handling of Html Renderer logic, this class is used by , diff --git a/src/Perspex.HtmlRenderer/HtmlControl.cs b/src/Perspex.HtmlRenderer/HtmlControl.cs index 66c2289ebb..2b42e6960c 100644 --- a/src/Perspex.HtmlRenderer/HtmlControl.cs +++ b/src/Perspex.HtmlRenderer/HtmlControl.cs @@ -19,9 +19,10 @@ using Perspex.Media; using Perspex.Threading; using TheArtOfDev.HtmlRenderer.Core; using TheArtOfDev.HtmlRenderer.Core.Entities; +using TheArtOfDev.HtmlRenderer.Perspex; using TheArtOfDev.HtmlRenderer.Perspex.Adapters; -namespace Perspex.Controls +namespace Perspex.Controls.Html { /// /// Provides HTML rendering using the text property.
    diff --git a/src/Perspex.HtmlRenderer/HtmlLabel.cs b/src/Perspex.HtmlRenderer/HtmlLabel.cs index 1ea40f3684..8d96576937 100644 --- a/src/Perspex.HtmlRenderer/HtmlLabel.cs +++ b/src/Perspex.HtmlRenderer/HtmlLabel.cs @@ -17,7 +17,7 @@ using TheArtOfDev.HtmlRenderer.Adapters.Entities; using TheArtOfDev.HtmlRenderer.Core; using TheArtOfDev.HtmlRenderer.Perspex.Adapters; -namespace Perspex.Controls +namespace Perspex.Controls.Html { /// /// Provides HTML rendering using the text property.
    diff --git a/src/Perspex.HtmlRenderer/HtmlRendererRoutedEventArgs.cs b/src/Perspex.HtmlRenderer/HtmlRendererRoutedEventArgs.cs index 61f2ccb1fa..34db5ff548 100644 --- a/src/Perspex.HtmlRenderer/HtmlRendererRoutedEventArgs.cs +++ b/src/Perspex.HtmlRenderer/HtmlRendererRoutedEventArgs.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; using Perspex.Interactivity; -namespace Perspex.HtmlRenderer +namespace Perspex.Controls.Html { public class HtmlRendererRoutedEventArgs : RoutedEventArgs { From ed200cf0ecb8eab7cf85e6f6fe76d35d74003054 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 11 Sep 2015 22:15:39 +0300 Subject: [PATCH 10/10] Weird submodule stuff --- src/Perspex.HtmlRenderer/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perspex.HtmlRenderer/external b/src/Perspex.HtmlRenderer/external index 8ae3973d6b..9b58697e1c 160000 --- a/src/Perspex.HtmlRenderer/external +++ b/src/Perspex.HtmlRenderer/external @@ -1 +1 @@ -Subproject commit 8ae3973d6b766b2b286a8869c66470e6eba74ab1 +Subproject commit 9b58697e1cba93bb86a40eb33b7d8b8fc743e3c0