/// <summary> /// Load stylesheet data from the given source.<br/> /// The source can be local file or web URI.<br/> /// First raise <see cref="HtmlStylesheetLoadEventArgs"/> event to allow the client to overwrite the stylesheet loading.<br/> /// If the stylesheet is downloaded from URI we will try to correct local URIs to absolute.<br/> /// </summary> /// <param name="htmlContainer">the container of the html to handle load stylesheet for</param> /// <param name="src">the source of the element to load the stylesheet by</param> /// <param name="attributes">the attributes of the link element</param> /// <param name="stylesheet">return the stylesheet string that has been loaded (null if failed or <paramref name="stylesheetData"/> is given)</param> /// <param name="stylesheetData">return stylesheet data object that was provided by overwrite (null if failed or <paramref name="stylesheet"/> is given)</param> public static void LoadStylesheet(HtmlContainerInt htmlContainer, string src, Dictionary<string, string> attributes, out string stylesheet, out CssData stylesheetData) { ArgChecker.AssertArgNotNull(htmlContainer, "htmlContainer"); stylesheet = null; stylesheetData = null; try { var args = new HtmlStylesheetLoadEventArgs(src, attributes); htmlContainer.RaiseHtmlStylesheetLoadEvent(args); if (!string.IsNullOrEmpty(args.SetStyleSheet)) { stylesheet = args.SetStyleSheet; } else if (args.SetStyleSheetData != null) { stylesheetData = args.SetStyleSheetData; } else if (args.SetSrc != null) { stylesheet = LoadStylesheet(htmlContainer, args.SetSrc); } else { stylesheet = LoadStylesheet(htmlContainer, src); } } catch (Exception ex) { htmlContainer.ReportError(HtmlRenderErrorType.CssParsing, "Exception in handling stylesheet source", ex); } }
/// <summary> /// Generate css tree by parsing the given html and applying the given css style data on it. /// </summary> /// <param name="html">the html to parse</param> /// <param name="htmlContainer">the html container to use for reference resolve</param> /// <param name="cssData">the css data to use</param> /// <returns>the root of the generated tree</returns> public CssBox GenerateCssTree(string html, HtmlContainerInt htmlContainer, ref CssData cssData) { var root = HtmlParser.ParseDocument(html); if (root != null) { root.HtmlContainer = htmlContainer; bool cssDataChanged = false; CascadeParseStyles(root, htmlContainer, ref cssData, ref cssDataChanged); CascadeApplyStyles(root, cssData); SetTextSelectionStyle(htmlContainer, cssData); CorrectTextBoxes(root); CorrectImgBoxes(root); bool followingBlock = true; CorrectLineBreaksBlocks(root, ref followingBlock); CorrectInlineBoxesParent(root); CorrectBlockInsideInline(root); CorrectInlineBoxesParent(root); } return root; }
/// <summary> /// Measure the size of the html by performing layout under the given restrictions. /// </summary> /// <param name="g">the graphics to use</param> /// <param name="htmlContainer">the html to calculate the layout for</param> /// <param name="minSize">the minimal size of the rendered html (zero - not limit the width/height)</param> /// <param name="maxSize">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)</param> /// <returns>return: the size of the html to be rendered within the min/max limits</returns> public static RSize MeasureHtmlByRestrictions(RGraphics g, HtmlContainerInt htmlContainer, RSize minSize, RSize maxSize) { // first layout without size restriction to know html actual size htmlContainer.PerformLayout(g); if (maxSize.Width > 0 && maxSize.Width < htmlContainer.ActualSize.Width) { // to allow the actual size be smaller than max we need to set max size only if it is really larger htmlContainer.MaxSize = new RSize(maxSize.Width, 0); htmlContainer.PerformLayout(g); } // restrict the final size by min/max var finalWidth = Math.Max(maxSize.Width > 0 ? Math.Min(maxSize.Width, (int)htmlContainer.ActualSize.Width) : (int)htmlContainer.ActualSize.Width, minSize.Width); // if the final width is larger than the actual we need to re-layout so the html can take the full given width. if (finalWidth > htmlContainer.ActualSize.Width) { htmlContainer.MaxSize = new RSize(finalWidth, 0); htmlContainer.PerformLayout(g); } var finalHeight = Math.Max(maxSize.Height > 0 ? Math.Min(maxSize.Height, (int)htmlContainer.ActualSize.Height) : (int)htmlContainer.ActualSize.Height, minSize.Height); return new RSize(finalWidth, finalHeight); }
/// <summary> /// Load stylesheet string from given source (file path or uri). /// </summary> /// <param name="htmlContainer">the container of the html to handle load stylesheet for</param> /// <param name="src">the file path or uri to load the stylesheet from</param> /// <returns>the stylesheet string</returns> private static string LoadStylesheet(HtmlContainerInt htmlContainer, string src) { var uri = CommonUtils.TryGetUri(src); if (uri == null || uri.Scheme == "file") { return LoadStylesheetFromFile(htmlContainer, uri != null ? uri.AbsolutePath : src); } else { return LoadStylesheetFromUri(htmlContainer, uri); } }
/// <summary> /// Perform the layout of the html container by given size restrictions returning the final size.<br/> /// The layout can be effected by the HTML content in the <paramref name="htmlContainer"/> if <paramref name="autoSize"/> or /// <paramref name="autoSizeHeightOnly"/> is set to true.<br/> /// Handle minimum and maximum size restrictions.<br/> /// Handle auto size and auto size for height only. if <paramref name="autoSize"/> is true <paramref name="autoSizeHeightOnly"/> /// is ignored.<br/> /// </summary> /// <param name="g">the graphics used for layout</param> /// <param name="htmlContainer">the html container to layout</param> /// <param name="size">the current size</param> /// <param name="minSize">the min size restriction - can be empty for no restriction</param> /// <param name="maxSize">the max size restriction - can be empty for no restriction</param> /// <param name="autoSize">if to modify the size (width and height) by html content layout</param> /// <param name="autoSizeHeightOnly">if to modify the height by html content layout</param> public static RSize Layout(RGraphics g, HtmlContainerInt htmlContainer, RSize size, RSize minSize, RSize maxSize, bool autoSize, bool autoSizeHeightOnly) { if (autoSize) htmlContainer.MaxSize = new RSize(0, 0); else if (autoSizeHeightOnly) htmlContainer.MaxSize = new RSize(size.Width, 0); else htmlContainer.MaxSize = size; htmlContainer.PerformLayout(g); RSize newSize = size; if (autoSize || autoSizeHeightOnly) { if (autoSize) { if (maxSize.Width > 0 && maxSize.Width < htmlContainer.ActualSize.Width) { // to allow the actual size be smaller than max we need to set max size only if it is really larger htmlContainer.MaxSize = maxSize; htmlContainer.PerformLayout(g); } else if (minSize.Width > 0 && minSize.Width > htmlContainer.ActualSize.Width) { // if min size is larger than the actual we need to re-layout so all 100% layouts will be correct htmlContainer.MaxSize = new RSize(minSize.Width, 0); htmlContainer.PerformLayout(g); } newSize = htmlContainer.ActualSize; } else if (Math.Abs(size.Height - htmlContainer.ActualSize.Height) > 0.01) { var prevWidth = size.Width; // make sure the height is not lower than min if given newSize.Height = minSize.Height > 0 && minSize.Height > htmlContainer.ActualSize.Height ? minSize.Height : htmlContainer.ActualSize.Height; // handle if changing the height of the label affects the desired width and those require re-layout if (Math.Abs(prevWidth - size.Width) > 0.01) return Layout(g, htmlContainer, size, minSize, maxSize, false, true); } } return newSize; }
/// <summary> /// Read styles defined inside the dom structure in links and style elements.<br/> /// If the html tag is "style" tag parse it content and add to the css data for all future tags parsing.<br/> /// If the html tag is "link" that point to style data parse it content and add to the css data for all future tags parsing.<br/> /// </summary> /// <param name="box">the box to parse style data in</param> /// <param name="htmlContainer">the html container to use for reference resolve</param> /// <param name="cssData">the style data to fill with found styles</param> /// <param name="cssDataChanged">check if the css data has been modified by the handled html not to change the base css data</param> private void CascadeParseStyles(CssBox box, HtmlContainerInt htmlContainer, ref CssData cssData, ref bool cssDataChanged) { if (box.HtmlTag != null) { // Check for the <link rel=stylesheet> tag if (box.HtmlTag.Name.Equals("link", StringComparison.CurrentCultureIgnoreCase) && box.GetAttribute("rel", string.Empty).Equals("stylesheet", StringComparison.CurrentCultureIgnoreCase)) { CloneCssData(ref cssData, ref cssDataChanged); string stylesheet; CssData stylesheetData; StylesheetLoadHandler.LoadStylesheet(htmlContainer, box.GetAttribute("href", string.Empty), box.HtmlTag.Attributes, out stylesheet, out stylesheetData); if (stylesheet != null) _cssParser.ParseStyleSheet(cssData, stylesheet); else if (stylesheetData != null) cssData.Combine(stylesheetData); } // Check for the <style> tag if (box.HtmlTag.Name.Equals("style", StringComparison.CurrentCultureIgnoreCase) && box.Boxes.Count > 0) { CloneCssData(ref cssData, ref cssDataChanged); foreach (var child in box.Boxes) _cssParser.ParseStyleSheet(cssData, child.Text.CutSubstring()); } } foreach (var childBox in box.Boxes) { CascadeParseStyles(childBox, htmlContainer, ref cssData, ref cssDataChanged); } }
/// <summary> /// Set the selected text style (selection text color and background color). /// </summary> /// <param name="htmlContainer"> </param> /// <param name="cssData">the style data</param> private void SetTextSelectionStyle(HtmlContainerInt htmlContainer, CssData cssData) { htmlContainer.SelectionForeColor = RColor.Empty; htmlContainer.SelectionBackColor = RColor.Empty; if (cssData.ContainsCssBlock("::selection")) { var blocks = cssData.GetCssBlock("::selection"); foreach (var block in blocks) { if (block.Properties.ContainsKey("color")) htmlContainer.SelectionForeColor = _cssParser.ParseColor(block.Properties["color"]); if (block.Properties.ContainsKey("background-color")) htmlContainer.SelectionBackColor = _cssParser.ParseColor(block.Properties["background-color"]); } } }
/// <summary> /// Draw image failed to load icon. /// </summary> /// <param name="g">the device to draw into</param> /// <param name="htmlContainer"></param> /// <param name="r">the rectangle to draw icon in</param> public static void DrawImageErrorIcon(RGraphics g, HtmlContainerInt htmlContainer, RRect r) { g.DrawRectangle(g.GetPen(RColor.LightGray), r.Left + 2, r.Top + 2, 15, 15); var image = htmlContainer.Adapter.GetLoadingFailedImage(); g.DrawImage(image, new RRect(r.Left + 3, r.Top + 3, image.Width, image.Height)); }
/// <summary> /// Load the stylesheet from local file by given path. /// </summary> /// <param name="htmlContainer">the container of the html to handle load stylesheet for</param> /// <param name="path">the stylesheet file to load</param> /// <returns>the loaded stylesheet string</returns> private static string LoadStylesheetFromFile(HtmlContainerInt htmlContainer, string path) { var fileInfo = CommonUtils.TryGetFileInfo(path); if (fileInfo != null) { if (fileInfo.Exists) { using (var sr = new StreamReader(fileInfo.FullName)) { return sr.ReadToEnd(); } } else { htmlContainer.ReportError(HtmlRenderErrorType.CssParsing, "No stylesheet found by path: " + path); } } else { htmlContainer.ReportError(HtmlRenderErrorType.CssParsing, "Failed load image, invalid source: " + path); } return string.Empty; }
/// <summary> /// Load the stylesheet from uri by downloading the string. /// </summary> /// <param name="htmlContainer">the container of the html to handle load stylesheet for</param> /// <param name="uri">the uri to download from</param> /// <returns>the loaded stylesheet string</returns> private static string LoadStylesheetFromUri(HtmlContainerInt htmlContainer, Uri uri) { using (var client = new WebClient()) { var stylesheet = client.DownloadString(uri); try { stylesheet = CorrectRelativeUrls(stylesheet, uri); } catch (Exception ex) { htmlContainer.ReportError(HtmlRenderErrorType.CssParsing, "Error in correcting relative URL in loaded stylesheet", ex); } return stylesheet; } }
/// <summary> /// Init. /// </summary> /// <param name="htmlContainer">the container of the html to handle load image for</param> /// <param name="loadCompleteCallback">callback raised when image load process is complete with image or without</param> public ImageLoadHandler(HtmlContainerInt htmlContainer, ActionInt<RImage, RRect, bool> loadCompleteCallback) { ArgChecker.AssertArgNotNull(htmlContainer, "htmlContainer"); ArgChecker.AssertArgNotNull(loadCompleteCallback, "loadCompleteCallback"); _htmlContainer = htmlContainer; _loadCompleteCallback = loadCompleteCallback; }
/// <summary> /// Init. /// </summary> /// <param name="selectionHandler">the selection handler linked to the context menu handler</param> /// <param name="htmlContainer">the html container the handler is on</param> public ContextMenuHandler(SelectionHandler selectionHandler, HtmlContainerInt htmlContainer) { ArgChecker.AssertArgNotNull(selectionHandler, "selectionHandler"); ArgChecker.AssertArgNotNull(htmlContainer, "htmlContainer"); _selectionHandler = selectionHandler; _htmlContainer = htmlContainer; }