Click here to Skip to main content
15,883,772 members
Articles / Desktop Programming / Windows Forms

A Professional HTML Renderer You Will Use

Rate me:
Please Sign up or sign in to vote.
4.91/5 (205 votes)
29 Jan 2009BSD4 min read 736K   23.3K   531  
100% managed code that draws HTML on any device
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing.Drawing2D;

namespace System.Drawing.Html
{
    /// <summary>
    /// Provides some drawing functionallity
    /// </summary>
    internal static class CssDrawingHelper
    {
        /// <summary>
        /// Border specifiers
        /// </summary>
        internal enum Border
        {
            Top, Right, Bottom, Left
        }

        /// <summary>
        /// Rounds the specified point
        /// </summary>
        /// <param name="p"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private static PointF RoundP(PointF p, CssBox b)
        {
            //HACK: Don't round if in printing mode
            //return Point.Round(p);
            return p;
        }

        /// <summary>
        /// Rounds the specified rectangle
        /// </summary>
        /// <param name="p"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private static RectangleF RoundR(RectangleF r, CssBox b)
        {
            //HACK: Don't round if in printing mode
            return Rectangle.Round(r);
        }

        /// <summary>
        /// Makes a border path
        /// </summary>
        /// <param name="border">Desired border</param>
        /// <param name="b">Box wich the border corresponds</param>
        /// <param name="isLineStart">Specifies if the border is for a starting line (no bevel on left)</param>
        /// <param name="isLineEnd">Specifies if the border is for an ending line (no bevel on right)</param>
        /// <returns>Beveled border path</returns>
        public static GraphicsPath GetBorderPath(Border border, CssBox b, RectangleF r, bool isLineStart, bool isLineEnd)
        {
            PointF[] pts = new PointF[4];
            float bwidth = 0;
            GraphicsPath corner = null;

            switch (border)
            {
                case Border.Top:
                    bwidth = b.ActualBorderTopWidth;
                    pts[0] = RoundP(new PointF(r.Left + b.ActualCornerNW, r.Top), b);
                    pts[1] = RoundP(new PointF(r.Right - b.ActualCornerNE, r.Top), b);
                    pts[2] = RoundP(new PointF(r.Right - b.ActualCornerNE, r.Top + bwidth), b);
                    pts[3] = RoundP(new PointF(r.Left + b.ActualCornerNW, r.Top + bwidth), b);

                    if (isLineEnd && b.ActualCornerNE == 0f) pts[2].X -= b.ActualBorderRightWidth;
                    if (isLineStart && b.ActualCornerNW == 0f) pts[3].X += b.ActualBorderLeftWidth;

                    if (b.ActualCornerNW > 0f) corner = CreateCorner(b, r, 1);
                    
                    break;
                case Border.Right:
                    bwidth = b.ActualBorderRightWidth;
                    pts[0] = RoundP(new PointF(r.Right - bwidth, r.Top + b.ActualCornerNE), b);
                    pts[1] = RoundP(new PointF(r.Right, r.Top + b.ActualCornerNE), b);
                    pts[2] = RoundP(new PointF(r.Right, r.Bottom - b.ActualCornerSE), b);
                    pts[3] = RoundP(new PointF(r.Right - bwidth, r.Bottom - b.ActualCornerSE), b);
                    
                   
                    if (b.ActualCornerNE == 0f) pts[0].Y += b.ActualBorderTopWidth;
                    if (b.ActualCornerSE == 0f) pts[3].Y -= b.ActualBorderBottomWidth; 
                    if (b.ActualCornerNE > 0f) corner = CreateCorner(b, r, 2);
                    break;
                case Border.Bottom:
                    bwidth = b.ActualBorderBottomWidth;
                    pts[0] = RoundP(new PointF(r.Left + b.ActualCornerSW, r.Bottom - bwidth), b);
                    pts[1] = RoundP(new PointF(r.Right - b.ActualCornerSE, r.Bottom - bwidth), b);
                    pts[2] = RoundP(new PointF(r.Right - b.ActualCornerSE, r.Bottom), b);
                    pts[3] = RoundP(new PointF(r.Left + b.ActualCornerSW, r.Bottom), b);

                    if (isLineStart && b.ActualCornerSW == 0f) pts[0].X += b.ActualBorderLeftWidth;
                    if (isLineEnd && b.ActualCornerSE == 0f) pts[1].X -= b.ActualBorderRightWidth;

                    if (b.ActualCornerSE > 0f) corner = CreateCorner(b, r, 3);
                    break;
                case Border.Left:
                    bwidth = b.ActualBorderLeftWidth;
                    pts[0] = RoundP(new PointF(r.Left, r.Top + b.ActualCornerNW), b);
                    pts[1] = RoundP(new PointF(r.Left + bwidth, r.Top + b.ActualCornerNW), b);
                    pts[2] = RoundP(new PointF(r.Left + bwidth, r.Bottom - b.ActualCornerSW), b);
                    pts[3] = RoundP(new PointF(r.Left, r.Bottom - b.ActualCornerSW), b);

                    if (b.ActualCornerNW == 0f) pts[1].Y += b.ActualBorderTopWidth;
                    if (b.ActualCornerSW == 0f) pts[2].Y -= b.ActualBorderBottomWidth;

                    if (b.ActualCornerSW > 0f) corner = CreateCorner(b, r, 4);
                    break;
            }

            GraphicsPath path = new GraphicsPath(pts, new byte[] { (byte)PathPointType.Line, (byte)PathPointType.Line, (byte)PathPointType.Line, (byte)PathPointType.Line });

            if (corner != null)
            {
                path.AddPath(corner, true);
            }

            return path;
        }

        /// <summary>
        /// Creates the corner to place with the borders
        /// </summary>
        /// <param name="outer"></param>
        /// <param name="inner"></param>
        /// <param name="startAngle"></param>
        /// <param name="sweepAngle"></param>
        /// <returns></returns>
        private static GraphicsPath CreateCorner(CssBox b, RectangleF r, int cornerIndex)
        {
            GraphicsPath corner = new GraphicsPath();

            RectangleF outer = RectangleF.Empty;
            RectangleF inner = RectangleF.Empty;
            float start1 = 0;
            float start2 = 0;

            switch (cornerIndex)
            {
                case 1:
                    outer = new RectangleF(r.Left, r.Top, b.ActualCornerNW, b.ActualCornerNW);
                    inner = RectangleF.FromLTRB(outer.Left + b.ActualBorderLeftWidth, outer.Top + b.ActualBorderTopWidth, outer.Right, outer.Bottom);
                    start1 = 180;
                    start2 = 270;
                    break;
                case 2:
                    outer = new RectangleF(r.Right - b.ActualCornerNE, r.Top, b.ActualCornerNE, b.ActualCornerNE);
                    inner = RectangleF.FromLTRB(outer.Left, outer.Top + b.ActualBorderTopWidth, outer.Right - b.ActualBorderRightWidth, outer.Bottom);
                    outer.X -= outer.Width;
                    inner.X -= inner.Width;
                    start1 = -90;
                    start2 = 0;
                    break;
                case 3:
                    outer = RectangleF.FromLTRB(r.Right - b.ActualCornerSE, r.Bottom - b.ActualCornerSE, r.Right, r.Bottom);
                    inner = new RectangleF(outer.Left, outer.Top, outer.Width - b.ActualBorderRightWidth, outer.Height - b.ActualBorderBottomWidth);
                    outer.X -= outer.Width;
                    outer.Y -= outer.Height;
                    inner.X -= inner.Width;
                    inner.Y -= inner.Height;
                    start1 = 0;
                    start2 = 90;
                    break;
                case 4:
                    outer = new RectangleF(r.Left, r.Bottom - b.ActualCornerSW, b.ActualCornerSW, b.ActualCornerSW);
                    inner = RectangleF.FromLTRB( r.Left + b.ActualBorderLeftWidth , outer.Top , outer.Right, outer.Bottom - b.ActualBorderBottomWidth);
                    start1 = 90;
                    start2 = 180;
                    outer.Y -= outer.Height;
                    inner.Y -= inner.Height;
                    break;
            }

            if (outer.Width <= 0f) outer.Width = 1f;
            if (outer.Height <= 0f) outer.Height = 1f;
            if (inner.Width <= 0f) inner.Width = 1f;
            if (inner.Height <= 0f) inner.Height = 1f;


            outer.Width *= 2; outer.Height *= 2;
            inner.Width *= 2; inner.Height *= 2;

            outer = RoundR(outer, b); inner = RoundR(inner, b);

            corner.AddArc(outer, start1, 90);
            corner.AddArc(inner, start2, -90);

            corner.CloseFigure();

            return corner;
        }

        /// <summary>
        /// Creates a rounded rectangle using the specified corner radius
        /// </summary>
        /// <param name="rect">Rectangle to round</param>
        /// <param name="nwRadius">Radius of the north east corner</param>
        /// <param name="neRadius">Radius of the north west corner</param>
        /// <param name="seRadius">Radius of the south east corner</param>
        /// <param name="swRadius">Radius of the south west corner</param>
        /// <returns>GraphicsPath with the lines of the rounded rectangle ready to be painted</returns>
        public static GraphicsPath GetRoundRect(RectangleF rect, float nwRadius, float neRadius, float seRadius, float swRadius)
        {
            ///  NW-----NE
            ///  |       |
            ///  |       |
            ///  SW-----SE

            GraphicsPath path = new GraphicsPath();

            nwRadius *= 2;
            neRadius *= 2;
            seRadius *= 2;
            swRadius *= 2;

            //NW ---- NE
            path.AddLine(rect.X + nwRadius, rect.Y, rect.Right - neRadius, rect.Y);
            
            //NE Arc
            if (neRadius > 0f)
            {
                path.AddArc(
                    RectangleF.FromLTRB(rect.Right - neRadius, rect.Top, rect.Right, rect.Top + neRadius),
                    -90, 90);
            }

            // NE
            //  |
            // SE
            path.AddLine(rect.Right, rect.Top + neRadius, rect.Right, rect.Bottom - seRadius);

            //SE Arc
            if (seRadius > 0f)
            {
                path.AddArc(
                    RectangleF.FromLTRB(rect.Right - seRadius, rect.Bottom - seRadius, rect.Right, rect.Bottom),
                    0, 90);
            }

            // SW --- SE
            path.AddLine(rect.Right - seRadius, rect.Bottom, rect.Left + swRadius, rect.Bottom);

            //SW Arc
            if (swRadius > 0f)
            {
                path.AddArc(
                    RectangleF.FromLTRB(rect.Left, rect.Bottom - swRadius, rect.Left + swRadius, rect.Bottom),
                    90, 90);
            }

            // NW
            // |
            // SW
            path.AddLine(rect.Left, rect.Bottom - swRadius, rect.Left, rect.Top + nwRadius);

            //NW Arc
            if (nwRadius > 0f)
            {
                path.AddArc(
                    RectangleF.FromLTRB(rect.Left, rect.Top, rect.Left + nwRadius, rect.Top + nwRadius),
                    180, 90);
            }

            path.CloseFigure();

            return path;
        }

        /// <summary>
        /// Makes the specified color darker
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        public static Color Darken(Color c)
        {
            return Color.FromArgb(c.R / 2, c.G / 2, c.B / 2);
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Product Manager
United States United States
- I've been programming Windows and Web apps since 1997.
- My greatest concern nowadays is product, user interface, and usability.
- TypeScript / React expert

@geeksplainer

Comments and Discussions