/// <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> /// Asigns the given css style blocks to the given css box checking if matching. /// </summary> /// <param name="box">the css box to assign css to</param> /// <param name="cssData">the css data to use to get the matching css blocks</param> /// <param name="className">the class selector to search for css blocks</param> private static void AssignCssBlocks(CssBox box, CssData cssData, string className) { var blocks = cssData.GetCssBlock(className); foreach (var block in blocks) { if (IsBlockAssignableToBox(box, block)) { AssignCssBlock(box, block); } } }
/// <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="cssData">the css data to use</param> /// <param name="bridge">used to resolve external style references in html code</param> /// <returns>the root of the generated tree</returns> public static CssBox GenerateCssTree(string html, ref CssData cssData, object bridge) { var root = HtmlParser.ParseDocument(html); if (root != null) { bool cssDataChanged = false; CascadeStyles(root, bridge, ref cssData, ref cssDataChanged); CorrectLineBreaksBlocks(root); CorrectBlockInsideInline(root); CorrectInlineBoxesParent(root); } return root; }
public override void SetOn(CssData data) { data.AddAttribute(Selector, "font-variant", "small-caps"); }
/// <summary> /// Parse given stylesheet for media CSS blocks<br/> /// This blocks are added under the specific media block they are found. /// </summary> /// <param name="cssData">the CSS data to fill with parsed CSS objects</param> /// <param name="stylesheet">the stylesheet to parse</param> private void ParseMediaStyleBlocks(CssData cssData, string stylesheet) { int startIdx = 0; string atrule; while ((atrule = RegexParserUtils.GetCssAtRules(stylesheet, ref startIdx)) != null) { //Just process @media rules if (!atrule.StartsWith("@media", StringComparison.OrdinalIgnoreCase)) continue; //Extract specified media types MatchCollection types = RegexParserUtils.Match(RegexParserUtils.CssMediaTypes, atrule); if (types.Count == 1) { string line = types[0].Value; if (line.StartsWith("@media", StringComparison.OrdinalIgnoreCase) && line.EndsWith("{")) { //Get specified media types in the at-rule string[] media = line.Substring(6, line.Length - 7).Split(' '); //Scan media types foreach (string t in media) { if (!String.IsNullOrEmpty(t.Trim())) { //Get blocks inside the at-rule var insideBlocks = RegexParserUtils.Match(RegexParserUtils.CssBlocks, atrule); //Scan blocks and feed them to the style sheet foreach (Match insideBlock in insideBlocks) { FeedStyleBlock(cssData, insideBlock.Value, t.Trim()); } } } } } } }
/// <summary> /// Parse the given stylesheet source to CSS blocks dictionary.<br/> /// The CSS blocks are organized into two level buckets of media type and class name.<br/> /// Root media type are found under 'all' bucket.<br/> /// The parsed css blocks are added to the given css data, merged if class name already exists. /// </summary> /// <param name="cssData">the CSS data to fill with parsed CSS objects</param> /// <param name="stylesheet">raw css stylesheet to parse</param> public void ParseStyleSheet(CssData cssData, string stylesheet) { if (!String.IsNullOrEmpty(stylesheet)) { stylesheet = RemoveStylesheetComments(stylesheet); ParseStyleBlocks(cssData, stylesheet); ParseMediaStyleBlocks(cssData, stylesheet); } }
/// <summary> /// Parse the given stylesheet source to CSS blocks dictionary.<br/> /// The CSS blocks are organized into two level buckets of media type and class name.<br/> /// Root media type are found under 'all' bucket.<br/> /// The parsed css blocks are added to the given css data, merged if class name already exists. /// </summary> /// <param name="cssData">the CSS data to fill with parsed CSS objects</param> /// <param name="stylesheet">raw css stylesheet to parse</param> public static void ParseStyleSheet(CssData cssData, string stylesheet) { if (!String.IsNullOrEmpty(stylesheet)) { // Convert everything to lower-case so not to handle case in code stylesheet = stylesheet.ToLower(); stylesheet = RemoveStylesheetComments(stylesheet); ParseStyleBlocks(cssData, stylesheet); ParseMediaStyleBlocks(cssData, stylesheet); } }
static void Register(//PageFrameCompiler page, Type cssType) { CssData data; if (!styles.TryGetValue(cssType, out data)) { data = new CssData(); styles[cssType] = data; } }
public override void SetOff(CssData data) { }
/// <summary> /// Assigns the given css classes to the given css box checking if matching.<br/> /// Support multiple classes in single attribute separated by whitespace. /// </summary> /// <param name="box">the css box to assign css to</param> /// <param name="cssData">the css data to use to get the matching css blocks</param> private static void AssignClassCssBlocks(CssBox box, CssData cssData) { var classes = box.HtmlTag.TryGetAttribute("class"); var startIdx = 0; while (startIdx < classes.Length) { while (startIdx < classes.Length && classes[startIdx] == ' ') startIdx++; if (startIdx < classes.Length) { var endIdx = classes.IndexOf(' ', startIdx); if (endIdx < 0) endIdx = classes.Length; var cls = "." + classes.Substring(startIdx, endIdx - startIdx); AssignCssBlocks(box, cssData, cls); AssignCssBlocks(box, cssData, box.HtmlTag.Name + cls); startIdx = endIdx + 1; } } }
public override void SetOff(CssData data) { data.AddAttribute(Selector, "font-variant", "normal"); }
/// <summary> /// Create PDF pages from given HTML and appends them to the provided PDF document.<br/> /// </summary> /// <param name="document">PDF document to append pages to</param> /// <param name="html">HTML source to create PDF from</param> /// <param name="config">the configuration to use for the PDF generation (page size/page orientation/margins/etc.)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the generated image of the html</returns> public static void AddPdfPages(PdfDocument document, string html, PdfGenerateConfig config, CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { XSize orgPageSize; // get the size of each page to layout the HTML in if (config.PageSize != PageSize.Undefined) { orgPageSize = PageSizeConverter.ToSize(config.PageSize); } else { orgPageSize = config.ManualPageSize; } if (config.PageOrientation == PageOrientation.Landscape) { // invert pagesize for landscape orgPageSize = new XSize(orgPageSize.Height, orgPageSize.Width); } var pageSize = new XSize(orgPageSize.Width - config.MarginLeft - config.MarginRight, orgPageSize.Height - config.MarginTop - config.MarginBottom); if (!string.IsNullOrEmpty(html)) { using (var container = new HtmlContainer()) { if (stylesheetLoad != null) { container.StylesheetLoad += stylesheetLoad; } if (imageLoad != null) { container.ImageLoad += imageLoad; } container.Location = new XPoint(config.MarginLeft, config.MarginTop); container.MaxSize = new XSize(pageSize.Width, 0); container.SetHtml(html, cssData); container.PageSize = pageSize; container.MarginBottom = config.MarginBottom; container.MarginLeft = config.MarginLeft; container.MarginRight = config.MarginRight; container.MarginTop = config.MarginTop; // layout the HTML with the page width restriction to know how many pages are required using (var measure = XGraphics.CreateMeasureContext(pageSize, XGraphicsUnit.Point, XPageDirection.Downwards)) { container.PerformLayout(measure); } // while there is un-rendered HTML, create another PDF page and render with proper offset for the next page double scrollOffset = 0; while (scrollOffset > -container.ActualSize.Height) { var page = document.AddPage(); page.Height = orgPageSize.Height; page.Width = orgPageSize.Width; using (var g = XGraphics.FromPdfPage(page)) { //g.IntersectClip(new XRect(config.MarginLeft, config.MarginTop, pageSize.Width, pageSize.Height)); g.IntersectClip(new XRect(0, 0, page.Width, page.Height)); container.ScrollOffset = new XPoint(0, scrollOffset); container.PerformPaint(g); } scrollOffset -= pageSize.Height; } // add web links and anchors HandleLinks(document, container, orgPageSize, pageSize); } } }
/// <summary> /// Init with optional document and stylesheet. /// </summary> /// <param name="htmlSource">the html to init with, init empty if not given</param> /// <param name="baseCssData">optional: the stylesheet to init with, init default if not given</param> public void SetHtml(string htmlSource, CssData baseCssData = null) { _htmlContainerInt.SetHtml(htmlSource, baseCssData); }
/// <summary> /// Only used as a commodity during testing. /// </summary> public override void InsertCss(CssData cssData) { InsertCss(new CssPropertiesSet(), cssData); }
/// <summary> /// Create PDF pages from given HTML and appends them to the provided PDF document.<br/> /// </summary> /// <param name="document">PDF document to append pages to</param> /// <param name="html">HTML source to create PDF from</param> /// <param name="config">the configuration to use for the PDF generation (page size/page orientation/margins/etc.)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the generated image of the html</returns> public static void AddPdfPages(PdfDocument document, string html, PdfGenerateConfig config, CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { XSize orgPageSize; // get the size of each page to layout the HTML in if (config.PageSize != PageSize.Undefined) { orgPageSize = PageSizeConverter.ToSize(config.PageSize); } else { orgPageSize = config.ManualPageSize; } if (config.PageOrientation == PageOrientation.Landscape) { // invert pagesize for landscape orgPageSize = new XSize(orgPageSize.Height, orgPageSize.Width); } var pageSize = new XSize(orgPageSize.Width - config.MarginLeft - config.MarginRight, orgPageSize.Height - config.MarginTop - config.MarginBottom); if (!string.IsNullOrEmpty(html)) { using (var container = new HtmlContainer()) { if (stylesheetLoad != null) { container.StylesheetLoad += stylesheetLoad; } if (imageLoad != null) { container.ImageLoad += imageLoad; } container.Location = new XPoint(config.MarginLeft, config.MarginTop); container.MaxSize = new XSize(pageSize.Width, 0); container.SetHtml(html, cssData); container.PageSize = pageSize; container.MarginBottom = config.MarginBottom; container.MarginLeft = config.MarginLeft; container.MarginRight = config.MarginRight; container.MarginTop = config.MarginTop; // layout the HTML with the page width restriction to know how many pages are required var measure = XGraphics.CreateMeasureContext(pageSize, XGraphicsUnit.Point, XPageDirection.Downwards); container.PerformLayout(measure); // while there is un-rendered HTML, create another PDF page and render with proper offset for the next page double scrollOffset = 0; //SL: if there is more than one page, increase the bottom margin to allow space for the page number container.MarginBottom += scrollOffset > -container.ActualSize.Height ? 20 : 0; container.PerformLayout(measure); //SL: This still does not increase the margin for the first page of a multi page.. welp while (scrollOffset > -container.ActualSize.Height) { var page = document.AddPage(); page.Height = orgPageSize.Height; page.Width = orgPageSize.Width; using (var g = XGraphics.FromPdfPage(page)) { //g.IntersectClip(new XRect(config.MarginLeft, config.MarginTop, pageSize.Width, pageSize.Height)); g.IntersectClip(new XRect(0, 0, page.Width, page.Height)); container.ScrollOffset = new XPoint(0, scrollOffset); container.PerformPaint(g); } scrollOffset -= pageSize.Height; } if (config.AddPageCountFoooter && document.PageCount > 1) //Only add page numbers if more than one page { AddPageCountFoooter(document, pageSize); } // SL: Set config option to handle links or not as it crashes for // some valid html links. // TODO: Investigate reason for crashing. if (config.HandleLinks) { HandleLinks(document, container, orgPageSize, pageSize); } } } }
private static void AddGridCols(CssData expected, params int[] gridCols) { var val = string.Join(' ', gridCols.Select(x => Style.Properties.Utils.TwipsToCm(x) + "cm")); expected.AddAttribute(".demo", "grid-template-columns", val); }
public void InsertCss(CssPropertiesSet other, CssData cssData) { cssData.AddRange(_cssData); }
/// <summary> /// Renders the specified HTML into a new image of unknown size that will be determined by min/max width/height and HTML layout.<br/> /// If <paramref name="maxSize.Width"/> is zero the html will use all the required width, otherwise it will perform line /// wrap as specified in the html<br/> /// If <paramref name="maxSize.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.<br/> /// If <paramref name="minSize"/> (Width/Height) is above zero the rendered image will not be smaller than the given min size.<br/> /// The generated image have transparent background that the html is rendered on.<br/> /// GDI+ text rending can be controlled by providing <see cref="TextRenderingHint"/>.<br/> /// See "Rendering to image" remarks section on <see cref="HtmlRender"/>.<br/> /// </summary> /// <param name="html">HTML source to render</param> /// <param name="minSize">optional: the min size of the rendered html (zero - not limit the width/height)</param> /// <param name="maxSize">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)</param> /// <param name="textRenderingHint">optional: (default - SingleBitPerPixelGridFit)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the generated image of the html</returns> public static Image RenderToImageGdiPlus(string html, Size minSize, Size maxSize, TextRenderingHint textRenderingHint = TextRenderingHint.AntiAlias, CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { if (string.IsNullOrEmpty(html)) { return(new Bitmap(0, 0, PixelFormat.Format32bppArgb)); } using (var container = new HtmlContainer()) { container.AvoidAsyncImagesLoading = true; container.AvoidImagesLateLoading = true; container.UseGdiPlusTextRendering = 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; // create the final image to render into by measured size var image = new Bitmap(finalSize.Width, finalSize.Height, PixelFormat.Format32bppArgb); // render HTML into the image using (var g = Graphics.FromImage(image)) { g.TextRenderingHint = textRenderingHint; container.PerformPaint(g); } return(image); } }
/// <summary> /// Applies style to all boxes in the tree.<br/> /// If the html tag has style defined for each apply that style to the css box of the tag.<br/> /// If the html tag has "class" attribute and the class name has style defined apply that style on the tag css box.<br/> /// If the html tag has "style" attribute parse it and apply the parsed style on the tag css box.<br/> /// </summary> /// <param name="box">the box to apply the style to</param> /// <param name="cssData">the style data for the html</param> private void CascadeApplyStyles(CssBox box, CssData cssData) { box.InheritStyle(); if (box.HtmlTag != null) { // try assign style using all wildcard AssignCssBlocks(box, cssData, "*"); // try assign style using the html element tag AssignCssBlocks(box, cssData, box.HtmlTag.Name); // try assign style using the "class" attribute of the html element if (box.HtmlTag.HasAttribute("class")) { AssignClassCssBlocks(box, cssData); } // try assign style using the "id" attribute of the html element if (box.HtmlTag.HasAttribute("id")) { var id = box.HtmlTag.TryGetAttribute("id"); AssignCssBlocks(box, cssData, "#" + id); } TranslateAttributes(box.HtmlTag, box); // Check for the style="" attribute if (box.HtmlTag.HasAttribute("style")) { var block = _cssParser.ParseCssBlock(box.HtmlTag.Name, box.HtmlTag.TryGetAttribute("style")); if (block != null) AssignCssBlock(box, block); } } // cascade text decoration only to boxes that actually have text so it will be handled correctly. if (box.TextDecoration != String.Empty && box.Text == null) { foreach (var childBox in box.Boxes) childBox.TextDecoration = box.TextDecoration; box.TextDecoration = string.Empty; } foreach (var childBox in box.Boxes) { CascadeApplyStyles(childBox, cssData); } }
/// <summary> /// Renders the specified HTML source on the specified location and max size restriction.<br/> /// If <paramref name="maxSize"/>.Width is zero the html will use all the required width, otherwise it will perform line /// wrap as specified in the html<br/> /// If <paramref name="maxSize"/>.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.<br/> /// Returned is the actual width and height of the rendered html.<br/> /// </summary> /// <param name="g">Device to render with</param> /// <param name="html">HTML source to render</param> /// <param name="location">the top-left most location to start render the html at</param> /// <param name="maxSize">the max size of the rendered html (if height above zero it will be clipped)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="useGdiPlusTextRendering">true - use GDI+ text rendering, false - use GDI text rendering</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the actual size of the rendered html</returns> private static SizeF RenderHtml(Graphics g, string html, PointF location, SizeF maxSize, CssData cssData, bool useGdiPlusTextRendering, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad, EventHandler <HtmlImageLoadEventArgs> imageLoad) { SizeF actualSize = SizeF.Empty; if (!string.IsNullOrEmpty(html)) { using (var container = new HtmlContainer()) { container.Location = location; container.MaxSize = maxSize; container.AvoidAsyncImagesLoading = true; container.AvoidImagesLateLoading = true; container.UseGdiPlusTextRendering = useGdiPlusTextRendering; if (stylesheetLoad != null) { container.StylesheetLoad += stylesheetLoad; } if (imageLoad != null) { container.ImageLoad += imageLoad; } container.SetHtml(html, cssData); container.PerformLayout(g); container.PerformPaint(g); actualSize = container.ActualSize; } } return(actualSize); }
/// <summary> /// Create PDF document from given HTML.<br/> /// </summary> /// <param name="html">HTML source to create PDF from</param> /// <param name="pageSize">the page size to use for each page in the generated pdf </param> /// <param name="margin">the margin to use between the HTML and the edges of each page</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the generated image of the html</returns> public static PdfDocument GeneratePdf(string html, PageSize pageSize, int margin = 20, CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { var config = new PdfGenerateConfig(); config.PageSize = pageSize; config.SetMargins(margin); return(GeneratePdf(html, config, cssData, stylesheetLoad, imageLoad)); }
public override void SetOn(CssData data) { data.AddAttribute(Selector, "display", "none!important"); }
/// <summary> /// Renders the specified HTML into a new image of unknown size that will be determined by max width/height and HTML layout.<br/> /// If <paramref name="maxWidth"/> is zero the html will use all the required width, otherwise it will perform line /// wrap as specified in the html<br/> /// If <paramref name="maxHeight"/> 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.<br/> /// The generated image have transparent background that the html is rendered on.<br/> /// GDI+ text rending can be controlled by providing <see cref="TextRenderingHint"/>.<br/> /// See "Rendering to image" remarks section on <see cref="HtmlRender"/>.<br/> /// </summary> /// <param name="html">HTML source to render</param> /// <param name="maxWidth">optional: the max width of the rendered html, if not zero and html cannot be layout within the limit it will be clipped</param> /// <param name="maxHeight">optional: the max height of the rendered html, if not zero and html cannot be layout within the limit it will be clipped</param> /// <param name="textRenderingHint">optional: (default - SingleBitPerPixelGridFit)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the generated image of the html</returns> public static Image RenderToImageGdiPlus(string html, int maxWidth = 0, int maxHeight = 0, TextRenderingHint textRenderingHint = TextRenderingHint.AntiAlias, CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { return(RenderToImageGdiPlus(html, Size.Empty, new Size(maxWidth, maxHeight), textRenderingHint, cssData, stylesheetLoad, imageLoad)); }
/// <summary> /// Create PDF pages from given HTML and appends them to the provided PDF document.<br/> /// </summary> /// <param name="document">PDF document to append pages to</param> /// <param name="html">HTML source to create PDF from</param> /// <param name="pageSize">the page size to use for each page in the generated pdf </param> /// <param name="margin">the margin to use between the HTML and the edges of each page</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the generated image of the html</returns> public static void AddPdfPages(PdfDocument document, string html, PageSize pageSize, int margin = 20, CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { var config = new PdfGenerateConfig(); config.PageSize = pageSize; config.SetMargins(margin); AddPdfPages(document, html, config, cssData, stylesheetLoad, imageLoad); }
/// <summary> /// Renders the specified HTML source on the specified location and max size restriction.<br/> /// If <paramref name="maxSize"/>.Width is zero the html will use all the required width, otherwise it will perform line /// wrap as specified in the html<br/> /// If <paramref name="maxSize"/>.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.<br/> /// Clip the graphics so the html will not be rendered outside the max height bound given.<br/> /// Returned is the actual width and height of the rendered html.<br/> /// </summary> /// <param name="g">Device to render with</param> /// <param name="html">HTML source to render</param> /// <param name="location">the top-left most location to start render the html at</param> /// <param name="maxSize">the max size of the rendered html (if height above zero it will be clipped)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="useGdiPlusTextRendering">true - use GDI+ text rendering, false - use GDI text rendering</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the actual size of the rendered html</returns> private static SizeF RenderClip(Graphics g, string html, PointF location, SizeF maxSize, CssData cssData, bool useGdiPlusTextRendering, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad, EventHandler <HtmlImageLoadEventArgs> imageLoad) { Region prevClip = null; if (maxSize.Height > 0) { prevClip = g.Clip; g.SetClip(new RectangleF(location, maxSize)); } var actualSize = RenderHtml(g, html, location, maxSize, cssData, useGdiPlusTextRendering, stylesheetLoad, imageLoad); if (prevClip != null) { g.SetClip(prevClip, CombineMode.Replace); } return(actualSize); }
public static Metafile RenderToMetafile(string html, float left = 0, float top = 0, float maxWidth = 0, CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { Metafile image; IntPtr dib; var memoryHdc = Win32Utils.CreateMemoryHdc(IntPtr.Zero, 1, 1, out dib); try { image = new Metafile(memoryHdc, EmfType.EmfPlusDual, ".."); using (var g = Graphics.FromImage(image)) { Render(g, html, left, top, maxWidth, cssData, stylesheetLoad, imageLoad); } } finally { Win32Utils.ReleaseMemoryHdc(memoryHdc, dib); } return(image); }
/// <summary> /// LoadFromRaw 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> /// Parse the given stylesheet to <see cref="CssData"/> object.<br/> /// If <paramref name="combineWithDefault"/> 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. /// </summary> /// <seealso cref="http://www.w3.org/TR/CSS21/sample.html"/> /// <param name="stylesheet">the stylesheet source to parse</param> /// <param name="combineWithDefault">true - combine the parsed css data with default css data, false - return only the parsed css data</param> /// <returns>the parsed css data</returns> public static CssData ParseStyleSheet(string stylesheet, bool combineWithDefault = true) { return(CssData.Parse(WinFormsAdapter.Instance, stylesheet, combineWithDefault)); }
/// <summary> /// Applies style to all boxes in the tree.<br/> /// If the html tag has style defined for each apply that style to the css box of the tag.<br/> /// If the html tag has "class" attribute and the class name has style defined apply that style on the tag css box.<br/> /// If the html tag has "style" attribute parse it and apply the parsed style on the tag css box.<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"></param> /// <param name="htmlContainer">the html container to use for reference resolve</param> /// <param name="cssData"> </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 static void CascadeStyles(CssBox box, HtmlContainer htmlContainer, ref CssData cssData, ref bool cssDataChanged) { box.InheritStyle(); if (box.HtmlTag != null) { // try assign style using the html element tag AssignCssBlocks(box, cssData, box.HtmlTag.Name); // try assign style using the "class" attribute of the html element if (box.HtmlTag.HasAttribute("class")) { AssignClassCssBlocks(box, cssData); } // try assign style using the "id" attribute of the html element if (box.HtmlTag.HasAttribute("id")) { var id = box.HtmlTag.TryGetAttribute("id"); AssignCssBlocks(box, cssData, "#" + id); } TranslateAttributes(box.HtmlTag, box); // Check for the style="" attribute if (box.HtmlTag.HasAttribute("style")) { var block = CssParser.ParseCssBlock(box.HtmlTag.Name, box.HtmlTag.TryGetAttribute("style")); AssignCssBlock(box, block); } // Check for the <style> tag if (box.HtmlTag.Name.Equals("style", StringComparison.CurrentCultureIgnoreCase) && box.Boxes.Count == 1) { CloneCssData(ref cssData, ref cssDataChanged); CssParser.ParseStyleSheet(cssData, box.Boxes[0].Text.CutSubstring()); } // 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); var styleSheet = StylesheetLoadHelper.LoadStylesheet(htmlContainer, box.GetAttribute("href", string.Empty), box.HtmlTag.Attributes); CssParser.ParseStyleSheet(cssData, styleSheet); } } // cascade text decoration only to boxes that actually have text so it will be handled correctly. if (box.TextDecoration != String.Empty && box.Text == null) { foreach (var childBox in box.Boxes) childBox.TextDecoration = box.TextDecoration; box.TextDecoration = string.Empty; } foreach (var childBox in box.Boxes) { CascadeStyles(childBox, htmlContainer, ref cssData, ref cssDataChanged); } }
/// <summary> /// Measure the size (width and height) required to draw the given html under given max width restriction.<br/> /// 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.<br/> /// Use GDI+ text rending, use <see cref="Graphics.TextRenderingHint"/> to control text rendering. /// </summary> /// <param name="g">Device to use for measure</param> /// <param name="html">HTML source to render</param> /// <param name="maxWidth">optional: bound the width of the html to render in (default - 0, unlimited)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the size required for the html</returns> public static SizeF MeasureGdiPlus(Graphics g, string html, float maxWidth = 0, CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { ArgChecker.AssertArgNotNull(g, "g"); return(Measure(g, html, maxWidth, cssData, true, stylesheetLoad, imageLoad)); }
/// <summary> /// Write stylesheet data inline into the html. /// </summary> /// <param name="sb">the string builder to write stylesheet into</param> /// <param name="cssData">the css data to write to the head</param> /// <param name="indent">the indent to use for nice formating</param> private static void WriteStylesheet(StringBuilder sb, CssData cssData, int indent) { sb.Append(new string(' ', indent * 4)); sb.AppendLine("<style type=\"text/css\">"); foreach (var cssBlocks in cssData.MediaBlocks["all"]) { sb.Append(new string(' ', (indent + 1) * 4)); sb.Append(cssBlocks.Key); sb.Append(" { "); foreach (var cssBlock in cssBlocks.Value) { foreach (var property in cssBlock.Properties) { // atodo: handle selectors sb.AppendFormat("{0}: {1};", property.Key, property.Value); } } sb.Append(" }"); sb.AppendLine(); } sb.Append(new string(' ', indent * 4)); sb.AppendLine("</style>"); }
/// <summary> /// Renders the specified HTML source on the specified location and max size restriction.<br/> /// Use GDI+ text rending, use <see cref="Graphics.TextRenderingHint"/> to control text rendering.<br/> /// If <paramref name="maxWidth"/> is zero the html will use all the required width, otherwise it will perform line /// wrap as specified in the html<br/> /// Returned is the actual width and height of the rendered html.<br/> /// </summary> /// <param name="g">Device to render with</param> /// <param name="html">HTML source to render</param> /// <param name="left">optional: the left most location to start render the html at (default - 0)</param> /// <param name="top">optional: the top most location to start render the html at (default - 0)</param> /// <param name="maxWidth">optional: bound the width of the html to render in (default - 0, unlimited)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the actual size of the rendered html</returns> public static SizeF RenderGdiPlus(Graphics g, string html, float left = 0, float top = 0, float maxWidth = 0, CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { ArgChecker.AssertArgNotNull(g, "g"); return(RenderClip(g, html, new PointF(left, top), new SizeF(maxWidth, 0), cssData, true, stylesheetLoad, imageLoad)); }
/// <summary> /// Feeds the style with a block about the specific media.<br/> /// When no media is specified, "all" will be used. /// </summary> /// <param name="cssData"> </param> /// <param name="block">the CSS block to handle</param> /// <param name="media">optional: the media (default - all)</param> private void FeedStyleBlock(CssData cssData, string block, string media = "all") { int startIdx = block.IndexOf("{", StringComparison.Ordinal); int endIdx = startIdx > -1 ? block.IndexOf("}", startIdx) : -1; if (startIdx > -1 && endIdx > -1) { string blockSource = block.Substring(startIdx + 1, endIdx - startIdx - 1); var classes = block.Substring(0, startIdx).Split(','); foreach (string cls in classes) { string className = cls.Trim(_cssClassTrimChars); if (!String.IsNullOrEmpty(className)) { var newblock = ParseCssBlockImp(className, blockSource); if (newblock != null) { cssData.AddCssBlock(media, newblock); } } } } }
/// <summary> /// Renders the specified HTML source on the specified location and max size restriction.<br/> /// Use GDI+ text rending, use <see cref="Graphics.TextRenderingHint"/> to control text rendering.<br/> /// If <paramref name="maxSize"/>.Width is zero the html will use all the required width, otherwise it will perform line /// wrap as specified in the html<br/> /// If <paramref name="maxSize"/>.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.<br/> /// Returned is the actual width and height of the rendered html.<br/> /// </summary> /// <param name="g">Device to render with</param> /// <param name="html">HTML source to render</param> /// <param name="location">the top-left most location to start render the html at</param> /// <param name="maxSize">the max size of the rendered html (if height above zero it will be clipped)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the actual size of the rendered html</returns> public static SizeF RenderGdiPlus(Graphics g, string html, PointF location, SizeF maxSize, CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { ArgChecker.AssertArgNotNull(g, "g"); return(RenderClip(g, html, location, maxSize, cssData, true, stylesheetLoad, imageLoad)); }
/// <summary> /// Parse given stylesheet for CSS blocks<br/> /// This blocks are added under the "all" keyword. /// </summary> /// <param name="cssData">the CSS data to fill with parsed CSS objects</param> /// <param name="stylesheet">the stylesheet to parse</param> private void ParseStyleBlocks(CssData cssData, string stylesheet) { var startIdx = 0; int endIdx = 0; while (startIdx < stylesheet.Length && endIdx > -1) { endIdx = startIdx; while (endIdx + 1 < stylesheet.Length) { endIdx++; if (stylesheet[endIdx] == '}') startIdx = endIdx + 1; if (stylesheet[endIdx] == '{') break; } int midIdx = endIdx + 1; if (endIdx > -1) { endIdx++; while (endIdx < stylesheet.Length) { if (stylesheet[endIdx] == '{') startIdx = midIdx + 1; if (stylesheet[endIdx] == '}') break; endIdx++; } if (endIdx < stylesheet.Length) { while (Char.IsWhiteSpace(stylesheet[startIdx])) startIdx++; var substring = stylesheet.Substring(startIdx, endIdx - startIdx + 1); FeedStyleBlock(cssData, substring); } startIdx = endIdx + 1; } } }
/// <summary> /// Renders the specified HTML on top of the given image.<br/> /// <paramref name="image"/> will contain the rendered html in it on top of original content.<br/> /// <paramref name="image"/> must not contain transparent pixels as it will corrupt the rendered html text.<br/> /// The HTML will be layout by the given image size but may be clipped if cannot fit.<br/> /// See "Rendering to image" remarks section on <see cref="HtmlRender"/>.<br/> /// </summary> /// <param name="image">the image to render the html on</param> /// <param name="html">HTML source to render</param> /// <param name="location">optional: the top-left most location to start render the html at (default - 0,0)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> public static void RenderToImage(Image image, string html, PointF location = new PointF(), CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { ArgChecker.AssertArgNotNull(image, "image"); var maxSize = new SizeF(image.Size.Width - location.X, image.Size.Height - location.Y); RenderToImage(image, html, location, maxSize, cssData, stylesheetLoad, imageLoad); }
/// <summary> /// Applies style to all boxes in the tree.<br/> /// If the html tag has style defined for each apply that style to the css box of the tag.<br/> /// If the html tag has "class" attribute and the class name has style defined apply that style on the tag css box.<br/> /// If the html tag has "style" attribute parse it and apply the parsed style on the tag css box.<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"></param> /// <param name="bridge"> </param> /// <param name="cssData"> </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 static void CascadeStyles(CssBox box, object bridge, ref CssData cssData, ref bool cssDataChanged) { box.InheritStyle(); if (box.HtmlTag != null) { // try assign style using the html element tag AssignCssBlocks(box, cssData, box.HtmlTag.Name); // try assign style using the "class" attribute of the html element if (box.HtmlTag.HasAttribute("class")) { AssignCssBlocks(box, cssData, "." + box.HtmlTag.Attributes["class"]); AssignCssBlocks(box, cssData, box.HtmlTag.Name + "." + box.HtmlTag.Attributes["class"]); } // try assign style using the "id" attribute of the html element if (box.HtmlTag.HasAttribute("id")) { AssignCssBlocks(box, cssData, "#" + box.HtmlTag.Attributes["id"]); } HtmlParser.TranslateAttributes(box.HtmlTag, box); // Check for the style="" attribute if (box.HtmlTag.HasAttribute("style")) { var block = CssParser.ParseCssBlockImp(box.HtmlTag.Name, box.HtmlTag.Attributes["style"]); AssignCssBlock(box, block); } // Check for the <style> tag if (box.HtmlTag.Name.Equals("style", StringComparison.CurrentCultureIgnoreCase) && box.Boxes.Count == 1) { CloneCssData(ref cssData, ref cssDataChanged); CssParser.ParseStyleSheet(cssData, box.Boxes[0].Text); } // 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); var styleSheet = CssValueParser.GetStyleSheet(box.GetAttribute("href", string.Empty), bridge); CssParser.ParseStyleSheet(cssData, styleSheet); } } foreach (var childBox in box.Boxes) { CascadeStyles(childBox, bridge, ref cssData, ref cssDataChanged); } }
/// <summary> /// Renders the specified HTML on top of the given image.<br/> /// <paramref name="image"/> will contain the rendered html in it on top of original content.<br/> /// <paramref name="image"/> must not contain transparent pixels as it will corrupt the rendered html text.<br/> /// See "Rendering to image" remarks section on <see cref="HtmlRender"/>.<br/> /// </summary> /// <param name="image">the image to render the html on</param> /// <param name="html">HTML source to render</param> /// <param name="location">the top-left most location to start render the html at</param> /// <param name="maxSize">the max size of the rendered html (if height above zero it will be clipped)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> public static void RenderToImage(Image image, string html, PointF location, SizeF maxSize, CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { ArgChecker.AssertArgNotNull(image, "image"); if (!string.IsNullOrEmpty(html)) { // create memory buffer from desktop handle that supports alpha channel IntPtr dib; var memoryHdc = Win32Utils.CreateMemoryHdc(IntPtr.Zero, image.Width, image.Height, out dib); try { // create memory buffer graphics to use for HTML rendering using (var memoryGraphics = Graphics.FromHdc(memoryHdc)) { // draw the image to the memory buffer to be the background of the rendered html memoryGraphics.DrawImageUnscaled(image, 0, 0); // render HTML into the memory buffer RenderHtml(memoryGraphics, html, location, maxSize, cssData, false, stylesheetLoad, imageLoad); } // copy from memory buffer to image CopyBufferToImage(memoryHdc, image); } finally { Win32Utils.ReleaseMemoryHdc(memoryHdc, dib); } } }
/// <summary> /// Renders the specified HTML into a new image of the requested size.<br/> /// The HTML will be layout by the given size but will be clipped if cannot fit.<br/> /// <p> /// Limitation: The image cannot have transparent background, by default it will be white.<br/> /// See "Rendering to image" remarks section on <see cref="HtmlRender"/>.<br/> /// </p> /// </summary> /// <param name="html">HTML source to render</param> /// <param name="size">The size of the image to render into, layout html by width and clipped by height</param> /// <param name="backgroundColor">optional: the color to fill the image with (default - white)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the generated image of the html</returns> /// <exception cref="ArgumentOutOfRangeException">if <paramref name="backgroundColor"/> is <see cref="Color.Transparent"/></exception>. public static Image RenderToImage(string html, Size size, Color backgroundColor = new Color(), CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { if (backgroundColor == Color.Transparent) { throw new ArgumentOutOfRangeException("backgroundColor", "Transparent background in not supported"); } // create the final image to render into var image = new Bitmap(size.Width, size.Height, PixelFormat.Format32bppArgb); if (!string.IsNullOrEmpty(html)) { // create memory buffer from desktop handle that supports alpha channel IntPtr dib; var memoryHdc = Win32Utils.CreateMemoryHdc(IntPtr.Zero, image.Width, image.Height, out dib); try { // create memory buffer graphics to use for HTML rendering using (var memoryGraphics = Graphics.FromHdc(memoryHdc)) { memoryGraphics.Clear(backgroundColor != Color.Empty ? backgroundColor : Color.White); // render HTML into the memory buffer RenderHtml(memoryGraphics, html, PointF.Empty, size, cssData, true, stylesheetLoad, imageLoad); } // copy from memory buffer to image CopyBufferToImage(memoryHdc, image); } finally { Win32Utils.ReleaseMemoryHdc(memoryHdc, dib); } } return(image); }
/// <summary> /// Renders the specified HTML into a new image of unknown size that will be determined by max width/height and HTML layout.<br/> /// If <paramref name="maxWidth"/> is zero the html will use all the required width, otherwise it will perform line /// wrap as specified in the html<br/> /// If <paramref name="maxHeight"/> 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.<br/> /// <p> /// Limitation: The image cannot have transparent background, by default it will be white.<br/> /// See "Rendering to image" remarks section on <see cref="HtmlRender"/>.<br/> /// </p> /// </summary> /// <param name="html">HTML source to render</param> /// <param name="maxWidth">optional: the max width of the rendered html, if not zero and html cannot be layout within the limit it will be clipped</param> /// <param name="maxHeight">optional: the max height of the rendered html, if not zero and html cannot be layout within the limit it will be clipped</param> /// <param name="backgroundColor">optional: the color to fill the image with (default - white)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the generated image of the html</returns> /// <exception cref="ArgumentOutOfRangeException">if <paramref name="backgroundColor"/> is <see cref="Color.Transparent"/></exception>. public static Image RenderToImage(string html, int maxWidth = 0, int maxHeight = 0, Color backgroundColor = new Color(), CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { return(RenderToImage(html, Size.Empty, new Size(maxWidth, maxHeight), backgroundColor, cssData, stylesheetLoad, imageLoad)); }
/// <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> /// Renders the specified HTML into a new image of unknown size that will be determined by min/max width/height and HTML layout.<br/> /// If <paramref name="maxSize.Width"/> is zero the html will use all the required width, otherwise it will perform line /// wrap as specified in the html<br/> /// If <paramref name="maxSize.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.<br/> /// If <paramref name="minSize"/> (Width/Height) is above zero the rendered image will not be smaller than the given min size.<br/> /// <p> /// Limitation: The image cannot have transparent background, by default it will be white.<br/> /// See "Rendering to image" remarks section on <see cref="HtmlRender"/>.<br/> /// </p> /// </summary> /// <param name="html">HTML source to render</param> /// <param name="minSize">optional: the min size of the rendered html (zero - not limit the width/height)</param> /// <param name="maxSize">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)</param> /// <param name="backgroundColor">optional: the color to fill the image with (default - white)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the generated image of the html</returns> /// <exception cref="ArgumentOutOfRangeException">if <paramref name="backgroundColor"/> is <see cref="Color.Transparent"/></exception>. public static Image RenderToImage(string html, Size minSize, Size maxSize, Color backgroundColor = new Color(), CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { if (backgroundColor == Color.Transparent) { throw new ArgumentOutOfRangeException("backgroundColor", "Transparent background in not supported"); } if (string.IsNullOrEmpty(html)) { return(new Bitmap(0, 0, PixelFormat.Format32bppArgb)); } 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; // create the final image to render into by measured size var image = new Bitmap(finalSize.Width, finalSize.Height, PixelFormat.Format32bppArgb); // create memory buffer from desktop handle that supports alpha channel IntPtr dib; var memoryHdc = Win32Utils.CreateMemoryHdc(IntPtr.Zero, image.Width, image.Height, out dib); try { // render HTML into the memory buffer using (var memoryGraphics = Graphics.FromHdc(memoryHdc)) { memoryGraphics.Clear(backgroundColor != Color.Empty ? backgroundColor : Color.White); container.PerformPaint(memoryGraphics); } // copy from memory buffer to image CopyBufferToImage(memoryHdc, image); } finally { Win32Utils.ReleaseMemoryHdc(memoryHdc, dib); } return(image); } }
/// <summary> /// Clone css data if it has not already been cloned.<br/> /// Used to preserve the base css data used when changed by style inside html. /// </summary> private static void CloneCssData(ref CssData cssData, ref bool cssDataChanged) { if (!cssDataChanged) { cssDataChanged = true; cssData = cssData.Clone(); } }
/// <summary> /// Renders the specified HTML into a new image of the requested size.<br/> /// The HTML will be layout by the given size but will be clipped if cannot fit.<br/> /// The generated image have transparent background that the html is rendered on.<br/> /// GDI+ text rending can be controlled by providing <see cref="TextRenderingHint"/>.<br/> /// See "Rendering to image" remarks section on <see cref="HtmlRender"/>.<br/> /// </summary> /// <param name="html">HTML source to render</param> /// <param name="size">The size of the image to render into, layout html by width and clipped by height</param> /// <param name="textRenderingHint">optional: (default - SingleBitPerPixelGridFit)</param> /// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param> /// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param> /// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param> /// <returns>the generated image of the html</returns> public static Image RenderToImageGdiPlus(string html, Size size, TextRenderingHint textRenderingHint = TextRenderingHint.AntiAlias, CssData cssData = null, EventHandler <HtmlStylesheetLoadEventArgs> stylesheetLoad = null, EventHandler <HtmlImageLoadEventArgs> imageLoad = null) { var image = new Bitmap(size.Width, size.Height, PixelFormat.Format32bppArgb); using (var g = Graphics.FromImage(image)) { g.TextRenderingHint = textRenderingHint; RenderHtml(g, html, PointF.Empty, size, cssData, true, stylesheetLoad, imageLoad); } return(image); }
/// <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> /// 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 static CssBox GenerateCssTree(string html, HtmlContainer htmlContainer, ref CssData cssData) { var root = HtmlParser.ParseDocument(html); if (root != null) { bool cssDataChanged = false; CascadeStyles(root, htmlContainer, ref cssData, ref cssDataChanged); CorrectTextBoxes(root); CorrectLineBreaksBlocks(root); CorrectInlineBoxesParent(root); CorrectBlockInsideInline(root); CorrectInlineBoxesParent(root); } return(root); }