// "Therefore those skilled at the unorthodox // are infinite as heaven and earth, // inexhaustible as the great rivers. // When they come to an end, // they begin again, // like the days and months; // they die and are reborn, // like the four seasons." // // - Sun Tsu, // "The Art of War" using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Text; using System.Windows.Forms; 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.WinForms.Adapters; using TheArtOfDev.HtmlRenderer.WinForms.Utilities; namespace TheArtOfDev.HtmlRenderer.WinForms { /// /// 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; /// /// Use GDI+ text rendering to measure/draw text. /// private bool _useGdiPlusTextRendering; #endregion /// /// Init. /// public HtmlContainer() { _htmlContainerInt = new HtmlContainerInt(WinFormsAdapter.Instance); } /// /// 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; } } /// /// returns page list /// public PageList PageList { get { return this._htmlContainerInt._pagelist; } } /// /// returns page list count /// public int PageListCount { get { return _htmlContainerInt._pagelist.Count; } } /// /// The internal core html container /// internal HtmlContainerInt HtmlContainerInt { get { return _htmlContainerInt; } } /// /// Use GDI+ text rendering to measure/draw text.
///
/// /// /// GDI+ text rendering is less smooth than GDI text rendering but it natively supports alpha channel /// thus allows creating transparent images. /// /// /// While using GDI+ text rendering you can control the text rendering using , note that /// using doesn't work well with transparent background. /// /// public bool UseGdiPlusTextRendering { get { return _useGdiPlusTextRendering; } set { if (_useGdiPlusTextRendering != value) { _useGdiPlusTextRendering = value; _htmlContainerInt.RequestRefresh(true); } } } /// /// the parsed stylesheet data used for handling the html /// public CssData CssData { get { return _htmlContainerInt.CssData; } } /// /// Gets or sets a value indicating if anti-aliasing should be avoided for geometry like backgrounds and borders (default - false). /// public bool AvoidGeometryAntialias { get { return _htmlContainerInt.AvoidGeometryAntialias; } set { _htmlContainerInt.AvoidGeometryAntialias = value; } } /// /// 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 rectangle. /// public Point ScrollOffset { get { return Utils.ConvertRound(_htmlContainerInt.ScrollOffset); } set { _htmlContainerInt.ScrollOffset = Utils.Convert(value); } } /// /// The top-left most location of the rendered html.
/// This will offset the top-left corner of the rendered html. ///
public PointF Location { get { return Utils.Convert(_htmlContainerInt.Location); } set { _htmlContainerInt.Location = Utils.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 SizeF MaxSize { get { return Utils.Convert(_htmlContainerInt.MaxSize); } set { _htmlContainerInt.MaxSize = Utils.Convert(value); } } /// /// The actual size of the rendered html (after layout) /// public SizeF ActualSize { get { return Utils.Convert(_htmlContainerInt.ActualSize); } internal set { _htmlContainerInt.ActualSize = Utils.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; } } /// /// 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); } /// /// 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(Utils.Convert(location), attribute); } /// /// Get all the links in the HTML with the element rectangle 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, Utils.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(Utils.Convert(location)); } /// /// 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 RectangleF? GetElementRectangle(string elementId) { var r = _htmlContainerInt.GetElementRectangle(elementId); return r.HasValue ? Utils.Convert(r.Value) : (RectangleF?)null; } /// /// Measures the bounds of box and children, recursively. /// /// Device context to draw public void PerformLayout(Graphics g) { ArgChecker.AssertArgNotNull(g, "g"); using (var ig = new GraphicsAdapter(g, _useGdiPlusTextRendering)) { _htmlContainerInt.PerformLayout(ig); _htmlContainerInt.GetPages(); } } /// /// Render the html using the given device. /// /// the device to use to render public void PerformPaint(Graphics g) { ArgChecker.AssertArgNotNull(g, "g"); using (var ig = new GraphicsAdapter(g, _useGdiPlusTextRendering)) { _htmlContainerInt.PerformPaint(ig); } } /// /// Render the html using the given printer device. /// /// the printer device to use to render /// public void PerformPrint(Graphics g, int iPage) { RPoint rpoint = this._htmlContainerInt.ScrollOffset; rpoint.Y = this._htmlContainerInt._pagelist.Page(iPage).YOffset; Point pt = new Point(Convert.ToInt32(rpoint.X), Convert.ToInt32(rpoint.Y)); //this._htmlContainerInt.ScrollOffset = rpoint; //this.ScrollOffset = pt; ArgChecker.AssertArgNotNull(g, "g"); using (var ig = new GraphicsAdapter(g, _useGdiPlusTextRendering)) { _htmlContainerInt.PerformPrint(ig, iPage); } } /// /// Handle mouse down to handle selection. /// /// the control hosting the html to invalidate /// the mouse event args public void HandleMouseDown(Control parent, MouseEventArgs e) { ArgChecker.AssertArgNotNull(parent, "parent"); ArgChecker.AssertArgNotNull(e, "e"); _htmlContainerInt.HandleMouseDown(new ControlAdapter(parent, _useGdiPlusTextRendering), Utils.Convert(e.Location)); } /// /// Handle mouse up to handle selection and link click. /// /// the control hosting the html to invalidate /// the mouse event args public void HandleMouseUp(Control parent, MouseEventArgs e) { ArgChecker.AssertArgNotNull(parent, "parent"); ArgChecker.AssertArgNotNull(e, "e"); _htmlContainerInt.HandleMouseUp(new ControlAdapter(parent, _useGdiPlusTextRendering), Utils.Convert(e.Location), CreateMouseEvent(e)); } /// /// 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, MouseEventArgs e) { ArgChecker.AssertArgNotNull(parent, "parent"); ArgChecker.AssertArgNotNull(e, "e"); _htmlContainerInt.HandleMouseDoubleClick(new ControlAdapter(parent, _useGdiPlusTextRendering), Utils.Convert(e.Location)); } /// /// 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, MouseEventArgs e) { ArgChecker.AssertArgNotNull(parent, "parent"); ArgChecker.AssertArgNotNull(e, "e"); _htmlContainerInt.HandleMouseMove(new ControlAdapter(parent, _useGdiPlusTextRendering), Utils.Convert(e.Location)); } /// /// 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, _useGdiPlusTextRendering)); } /// /// 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, _useGdiPlusTextRendering), CreateKeyEevent(e)); } /// /// /// public void Dispose() { _htmlContainerInt.Dispose(); } #region Private methods /// /// Create HtmlRenderer mouse event from win forms mouse event. /// private static RMouseEvent CreateMouseEvent(MouseEventArgs e) { return new RMouseEvent((e.Button & MouseButtons.Left) != 0); } /// /// Create HtmlRenderer key event from win forms key event. /// private static RKeyEvent CreateKeyEevent(KeyEventArgs e) { return new RKeyEvent(e.Control, e.KeyCode == Keys.A, e.KeyCode == Keys.C); } #endregion } }