diff --git a/Source/Demo/Common/DemoUtils.cs b/Source/Demo/Common/DemoUtils.cs
index 682f325f5..a14b33b3f 100644
--- a/Source/Demo/Common/DemoUtils.cs
+++ b/Source/Demo/Common/DemoUtils.cs
@@ -74,8 +74,8 @@ public static string GetStylesheet(string src)
a:link { text-decoration: none; }
a:hover { text-decoration: underline; }
.gray { color:gray; }
- .example { background-color:#efefef; corner-radius:5px; padding:0.5em; }
- .whitehole { background-color:white; corner-radius:10px; padding:15px; }
+ .example { background-color:#efefef; border-radius:5px; padding:0.5em; }
+ .whitehole { background-color:white; border-radius:10px; padding:15px; }
.caption { font-size: 1.1em }
.comment { color: green; margin-bottom: 5px; margin-left: 3px; }
.comment2 { color: green; }";
diff --git a/Source/Demo/Common/Samples/00.Intro.htm b/Source/Demo/Common/Samples/00.Intro.htm
index d310818b4..7f9f9f565 100644
--- a/Source/Demo/Common/Samples/00.Intro.htm
+++ b/Source/Demo/Common/Samples/00.Intro.htm
@@ -3,7 +3,7 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ornare mollis elit. Integer_sagittis_Fusce_elementum_commodo_felis_Vivamus_lacinia_eleifend_libero_Donec
lacus.
diff --git a/Source/Demo/Common/TestSamples/16.Borders.htm b/Source/Demo/Common/TestSamples/16.Borders.htm
index 6fae0757c..117dfbcae 100644
--- a/Source/Demo/Common/TestSamples/16.Borders.htm
+++ b/Source/Demo/Common/TestSamples/16.Borders.htm
@@ -12,13 +12,13 @@
- border 1px with corner-radius 5px
+
+ border 1px with border-radius 5px
-
- border 2px with corner-radius 10px
+
+ border 2px with border-radius 10px
@@ -37,8 +37,8 @@
-
- dashed border 2px with corner-radius 10px
+
+ dashed border 2px with border-radius 10px
diff --git a/Source/HtmlRenderer.PdfSharp/Adapters/GradientBrushAdapter.cs b/Source/HtmlRenderer.PdfSharp/Adapters/GradientBrushAdapter.cs
new file mode 100644
index 000000000..b92bf73d3
--- /dev/null
+++ b/Source/HtmlRenderer.PdfSharp/Adapters/GradientBrushAdapter.cs
@@ -0,0 +1,46 @@
+// "Therefore those skilled at the unorthodox
+// are infinite as heaven and earth,
+// inexhaustible as the great rivers.
+// When they come to an end,
+// they begin again,
+// like the days and months;
+// they die and are reborn,
+// like the four seasons."
+//
+// - Sun Tsu,
+// "The Art of War"
+
+using TheArtOfDev.HtmlRenderer.Adapters;
+using TheArtOfDev.HtmlRenderer.Adapters.Entities;
+
+namespace TheArtOfDev.HtmlRenderer.PdfSharp.Adapters
+{
+ ///
+ /// A multi-stop linear gradient "brush" for the PdfSharp backend. Unlike ,
+ /// this does not wrap a real PdfSharp.Drawing.XBrush - XLinearGradientBrush in the
+ /// PDFsharp 1.50 package this project depends on only supports 2 colors, no stop list. Instead this
+ /// just carries the gradient line and stops; 's DrawPath/
+ /// DrawRectangle special-case this type and paint it as a series of adjacent 2-color
+ /// XLinearGradientBrush bands, one per consecutive stop pair - each band is a real 2-color
+ /// linear gradient by definition, so the composite is an exact piecewise-linear rendering, not an
+ /// approximation.
+ ///
+ internal sealed class GradientBrushAdapter : RBrush
+ {
+ public GradientBrushAdapter(RPoint p1, RPoint p2, (RColor Color, double Position)[] stops)
+ {
+ P1 = p1;
+ P2 = p2;
+ Stops = stops;
+ }
+
+ public RPoint P1 { get; }
+
+ public RPoint P2 { get; }
+
+ public (RColor Color, double Position)[] Stops { get; }
+
+ public override void Dispose()
+ { }
+ }
+}
diff --git a/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsAdapter.cs b/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsAdapter.cs
index c6d406c58..76ede5ea1 100644
--- a/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsAdapter.cs
+++ b/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsAdapter.cs
@@ -155,6 +155,14 @@ public override void DrawRectangle(RPen pen, double x, double y, double width, d
public override void DrawRectangle(RBrush brush, double x, double y, double width, double height)
{
+ if (brush is GradientBrushAdapter gradient)
+ {
+ var rectPath = new XGraphicsPath();
+ rectPath.AddRectangle(x, y, width, height);
+ FillGradient(gradient, rectPath);
+ return;
+ }
+
var xBrush = ((BrushAdapter)brush).Brush;
var xTextureBrush = xBrush as XTextureBrush;
if (xTextureBrush != null)
@@ -188,6 +196,12 @@ public override void DrawPath(RPen pen, RGraphicsPath path)
public override void DrawPath(RBrush brush, RGraphicsPath path)
{
+ if (brush is GradientBrushAdapter gradient)
+ {
+ FillGradient(gradient, ((GraphicsPathAdapter)path).GraphicsPath);
+ return;
+ }
+
_g.DrawPath((XBrush)((BrushAdapter)brush).Brush, ((GraphicsPathAdapter)path).GraphicsPath);
}
@@ -199,6 +213,64 @@ public override void DrawPolygon(RBrush brush, RPoint[] points)
}
}
+ ///
+ /// Paints a multi-stop linear gradient by clipping to and drawing
+ /// one real 2-color band per consecutive stop pair, each
+ /// spanning the full perpendicular extent needed to cover the target - see
+ /// for why this backend needs banding instead of a single brush.
+ ///
+ private void FillGradient(GradientBrushAdapter gradient, XGraphicsPath targetPath)
+ {
+ var stops = gradient.Stops;
+ if (stops.Length == 0)
+ return;
+
+ _g.Save();
+ _g.IntersectClip(targetPath);
+
+ double dx = gradient.P2.X - gradient.P1.X;
+ double dy = gradient.P2.Y - gradient.P1.Y;
+ double len = Math.Sqrt(dx * dx + dy * dy);
+
+ if (stops.Length == 1 || len < 1e-6)
+ {
+ // Degenerate gradient line (single stop, or a zero-size box) - just flat-fill with the
+ // last color, matching what a real linear gradient converges to in that case.
+ var flatBrush = new XSolidBrush(Utils.Convert(stops[stops.Length - 1].Color));
+ _g.DrawRectangle(flatBrush, -1e5, -1e5, 2e5, 2e5);
+ _g.Restore();
+ _g.DrawRectangle(XBrushes.White, 0, 0, 0.1, 0.1);
+ return;
+ }
+
+ double ux = dx / len, uy = dy / len;
+ double perpX = -uy, perpY = ux;
+ double perpHalf = Math.Max(len, 1.0) * 4.0;
+
+ for (int i = 0; i < stops.Length - 1; i++)
+ {
+ double t1 = stops[i].Position, t2 = stops[i + 1].Position;
+ var bp1 = new XPoint(gradient.P1.X + ux * len * t1, gradient.P1.Y + uy * len * t1);
+ var bp2 = new XPoint(gradient.P1.X + ux * len * t2, gradient.P1.Y + uy * len * t2);
+
+ var band = new[]
+ {
+ new XPoint(bp1.X - perpX * perpHalf, bp1.Y - perpY * perpHalf),
+ new XPoint(bp1.X + perpX * perpHalf, bp1.Y + perpY * perpHalf),
+ new XPoint(bp2.X + perpX * perpHalf, bp2.Y + perpY * perpHalf),
+ new XPoint(bp2.X - perpX * perpHalf, bp2.Y - perpY * perpHalf),
+ };
+
+ var bandBrush = new XLinearGradientBrush(bp1, bp2, Utils.Convert(stops[i].Color), Utils.Convert(stops[i + 1].Color));
+ _g.DrawPolygon(bandBrush, band, XFillMode.Winding);
+ }
+
+ _g.Restore();
+
+ // handle bug in PdfSharp that keeps the brush color for next string draw
+ _g.DrawRectangle(XBrushes.White, 0, 0, 0.1, 0.1);
+ }
+
#endregion
}
}
\ No newline at end of file
diff --git a/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsPathAdapter.cs b/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsPathAdapter.cs
index 48290543c..9e057d8e9 100644
--- a/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsPathAdapter.cs
+++ b/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsPathAdapter.cs
@@ -51,11 +51,11 @@ public override void LineTo(double x, double y)
_lastPoint = new RPoint(x, y);
}
- public override void ArcTo(double x, double y, double size, Corner corner)
+ public override void ArcTo(double x, double y, double radiusX, double radiusY, Corner corner)
{
- float left = (float)(Math.Min(x, _lastPoint.X) - (corner == Corner.TopRight || corner == Corner.BottomRight ? size : 0));
- float top = (float)(Math.Min(y, _lastPoint.Y) - (corner == Corner.BottomLeft || corner == Corner.BottomRight ? size : 0));
- _graphicsPath.AddArc(left, top, (float)size * 2, (float)size * 2, GetStartAngle(corner), 90);
+ float left = (float)(Math.Min(x, _lastPoint.X) - (corner == Corner.TopRight || corner == Corner.BottomRight ? radiusX : 0));
+ float top = (float)(Math.Min(y, _lastPoint.Y) - (corner == Corner.BottomLeft || corner == Corner.BottomRight ? radiusY : 0));
+ _graphicsPath.AddArc(left, top, (float)radiusX * 2, (float)radiusY * 2, GetStartAngle(corner), 90);
_lastPoint = new RPoint(x, y);
}
diff --git a/Source/HtmlRenderer.PdfSharp/Adapters/PdfSharpAdapter.cs b/Source/HtmlRenderer.PdfSharp/Adapters/PdfSharpAdapter.cs
index e5fa309c0..f286da15b 100644
--- a/Source/HtmlRenderer.PdfSharp/Adapters/PdfSharpAdapter.cs
+++ b/Source/HtmlRenderer.PdfSharp/Adapters/PdfSharpAdapter.cs
@@ -93,18 +93,9 @@ protected override RBrush CreateSolidBrush(RColor color)
return new BrushAdapter(solidBrush);
}
- protected override RBrush CreateLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle)
+ protected override RBrush CreateLinearGradientBrush(RPoint p1, RPoint p2, (RColor Color, double Position)[] stops)
{
- XLinearGradientMode mode;
- if (angle < 45)
- mode = XLinearGradientMode.ForwardDiagonal;
- else if (angle < 90)
- mode = XLinearGradientMode.Vertical;
- else if (angle < 135)
- mode = XLinearGradientMode.BackwardDiagonal;
- else
- mode = XLinearGradientMode.Horizontal;
- return new BrushAdapter(new XLinearGradientBrush(Utils.Convert(rect), Utils.Convert(color1), Utils.Convert(color2), mode));
+ return new GradientBrushAdapter(p1, p2, stops);
}
protected override RImage ConvertImageInt(object image)
diff --git a/Source/HtmlRenderer.WPF/Adapters/GraphicsPathAdapter.cs b/Source/HtmlRenderer.WPF/Adapters/GraphicsPathAdapter.cs
index a6ffb66bb..af19357b4 100644
--- a/Source/HtmlRenderer.WPF/Adapters/GraphicsPathAdapter.cs
+++ b/Source/HtmlRenderer.WPF/Adapters/GraphicsPathAdapter.cs
@@ -46,9 +46,9 @@ public override void LineTo(double x, double y)
_geometryContext.LineTo(new Point(x, y), true, true);
}
- public override void ArcTo(double x, double y, double size, Corner corner)
+ public override void ArcTo(double x, double y, double radiusX, double radiusY, Corner corner)
{
- _geometryContext.ArcTo(new Point(x, y), new Size(size, size), 0, false, SweepDirection.Clockwise, true, true);
+ _geometryContext.ArcTo(new Point(x, y), new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise, true, true);
}
///
diff --git a/Source/HtmlRenderer.WPF/Adapters/WpfAdapter.cs b/Source/HtmlRenderer.WPF/Adapters/WpfAdapter.cs
index 3ea88d9c4..4de4c8777 100644
--- a/Source/HtmlRenderer.WPF/Adapters/WpfAdapter.cs
+++ b/Source/HtmlRenderer.WPF/Adapters/WpfAdapter.cs
@@ -102,14 +102,19 @@ protected override RBrush CreateSolidBrush(RColor color)
return new BrushAdapter(solidBrush);
}
- protected override RBrush CreateLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle)
- {
- var startColor = angle <= 180 ? Utils.Convert(color1) : Utils.Convert(color2);
- var endColor = angle <= 180 ? Utils.Convert(color2) : Utils.Convert(color1);
- angle = angle <= 180 ? angle : angle - 180;
- double x = angle < 135 ? Math.Max((angle - 45) / 90, 0) : 1;
- double y = angle <= 45 ? Math.Max(0.5 - angle / 90, 0) : angle > 135 ? Math.Abs(1.5 - angle / 90) : 0;
- return new BrushAdapter(new LinearGradientBrush(startColor, endColor, new Point(x, y), new Point(1 - x, 1 - y)));
+ protected override RBrush CreateLinearGradientBrush(RPoint p1, RPoint p2, (RColor Color, double Position)[] stops)
+ {
+ var gradientStops = new GradientStopCollection(stops.Length);
+ foreach (var stop in stops)
+ gradientStops.Add(new GradientStop(Utils.Convert(stop.Color), stop.Position));
+
+ var brush = new LinearGradientBrush(gradientStops, 0)
+ {
+ MappingMode = BrushMappingMode.Absolute,
+ StartPoint = Utils.Convert(p1),
+ EndPoint = Utils.Convert(p2)
+ };
+ return new BrushAdapter(brush);
}
protected override RImage ConvertImageInt(object image)
diff --git a/Source/HtmlRenderer.WinForms/Adapters/GraphicsPathAdapter.cs b/Source/HtmlRenderer.WinForms/Adapters/GraphicsPathAdapter.cs
index 6c4bbd061..d3a7b3f2c 100644
--- a/Source/HtmlRenderer.WinForms/Adapters/GraphicsPathAdapter.cs
+++ b/Source/HtmlRenderer.WinForms/Adapters/GraphicsPathAdapter.cs
@@ -51,11 +51,11 @@ public override void LineTo(double x, double y)
_lastPoint = new RPoint(x, y);
}
- public override void ArcTo(double x, double y, double size, Corner corner)
+ public override void ArcTo(double x, double y, double radiusX, double radiusY, Corner corner)
{
- float left = (float)(Math.Min(x, _lastPoint.X) - (corner == Corner.TopRight || corner == Corner.BottomRight ? size : 0));
- float top = (float)(Math.Min(y, _lastPoint.Y) - (corner == Corner.BottomLeft || corner == Corner.BottomRight ? size : 0));
- _graphicsPath.AddArc(left, top, (float)size * 2, (float)size * 2, GetStartAngle(corner), 90);
+ float left = (float)(Math.Min(x, _lastPoint.X) - (corner == Corner.TopRight || corner == Corner.BottomRight ? radiusX : 0));
+ float top = (float)(Math.Min(y, _lastPoint.Y) - (corner == Corner.BottomLeft || corner == Corner.BottomRight ? radiusY : 0));
+ _graphicsPath.AddArc(left, top, (float)radiusX * 2, (float)radiusY * 2, GetStartAngle(corner), 90);
_lastPoint = new RPoint(x, y);
}
diff --git a/Source/HtmlRenderer.WinForms/Adapters/WinFormsAdapter.cs b/Source/HtmlRenderer.WinForms/Adapters/WinFormsAdapter.cs
index 16bc83db8..10da9891a 100644
--- a/Source/HtmlRenderer.WinForms/Adapters/WinFormsAdapter.cs
+++ b/Source/HtmlRenderer.WinForms/Adapters/WinFormsAdapter.cs
@@ -10,6 +10,7 @@
// - Sun Tsu,
// "The Art of War"
+using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
@@ -83,9 +84,32 @@ protected override RBrush CreateSolidBrush(RColor color)
return new BrushAdapter(solidBrush, false);
}
- protected override RBrush CreateLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle)
+ protected override RBrush CreateLinearGradientBrush(RPoint p1, RPoint p2, (RColor Color, double Position)[] stops)
{
- return new BrushAdapter(new LinearGradientBrush(Utils.Convert(rect), Utils.Convert(color1), Utils.Convert(color2), (float)angle), true);
+ var brush = new LinearGradientBrush(Utils.Convert(p1), Utils.Convert(p2), Color.Black, Color.Black);
+
+ var colors = new Color[stops.Length];
+ var positions = new float[stops.Length];
+ for (int i = 0; i < stops.Length; i++)
+ {
+ colors[i] = Utils.Convert(stops[i].Color);
+ var pos = (float)Math.Min(Math.Max(stops[i].Position, 0.0), 1.0);
+ // GDI+ requires strictly increasing positions - nudge any duplicate up by an epsilon.
+ positions[i] = i > 0 && pos <= positions[i - 1] ? positions[i - 1] + 0.0001f : pos;
+ }
+ // GDI+ requires the first/last position to be exactly 0/1 - forcing them here (rather than
+ // requiring the caller to pre-normalize) also matches spec behavior for a gradient whose
+ // outermost stops aren't at the very ends: the outermost color simply extends flat to the edge.
+ positions[0] = 0f;
+ positions[positions.Length - 1] = 1f;
+
+ brush.InterpolationColors = new ColorBlend
+ {
+ Colors = colors,
+ Positions = positions
+ };
+
+ return new BrushAdapter(brush, true);
}
protected override RImage ConvertImageInt(object image)
diff --git a/Source/HtmlRenderer/Adapters/RAdapter.cs b/Source/HtmlRenderer/Adapters/RAdapter.cs
index 6b13459c1..1a19f7a57 100644
--- a/Source/HtmlRenderer/Adapters/RAdapter.cs
+++ b/Source/HtmlRenderer/Adapters/RAdapter.cs
@@ -85,7 +85,16 @@ protected RAdapter()
///
public CssData DefaultCssData
{
- get { return _defaultCssData ?? (_defaultCssData = CssData.Parse(this, CssDefaults.DefaultStyleSheet, false)); }
+ get
+ {
+ if (_defaultCssData == null)
+ {
+ _defaultCssData = CssData.Parse(this, CssDefaults.DefaultStyleSheet, false);
+ foreach (var stylesheet in _defaultCssData.Stylesheets)
+ stylesheet.IsUserAgent = true;
+ }
+ return _defaultCssData;
+ }
}
///
@@ -130,16 +139,15 @@ public RBrush GetSolidBrush(RColor color)
}
///
- /// Get linear gradient color brush from to .
+ /// Get a multi-stop linear gradient brush along the line from to .
///
- /// the rectangle to get the brush for
- /// the start color of the gradient
- /// the end color of the gradient
- /// the angle to move the gradient from start color to end color in the rectangle
+ /// the gradient line's start point
+ /// the gradient line's end point
+ /// color stops, each with a position in [0,1] along the gradient line
/// linear gradient color brush instance
- public RBrush GetLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle)
+ public RBrush GetLinearGradientBrush(RPoint p1, RPoint p2, (RColor Color, double Position)[] stops)
{
- return CreateLinearGradientBrush(rect, color1, color2, angle);
+ return CreateLinearGradientBrush(p1, p2, stops);
}
///
@@ -351,14 +359,13 @@ internal RFont CreateFont(RFontFamily family, double size, RFontStyle style)
protected abstract RBrush CreateSolidBrush(RColor color);
///
- /// Get linear gradient color brush from to .
+ /// Get a multi-stop linear gradient brush along the line from to .
///
- /// the rectangle to get the brush for
- /// the start color of the gradient
- /// the end color of the gradient
- /// the angle to move the gradient from start color to end color in the rectangle
+ /// the gradient line's start point
+ /// the gradient line's end point
+ /// color stops, each with a position in [0,1] along the gradient line
/// linear gradient color brush instance
- protected abstract RBrush CreateLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle);
+ protected abstract RBrush CreateLinearGradientBrush(RPoint p1, RPoint p2, (RColor Color, double Position)[] stops);
///
/// Convert image object returned from to .
diff --git a/Source/HtmlRenderer/Adapters/RGraphics.cs b/Source/HtmlRenderer/Adapters/RGraphics.cs
index af54f2ae4..22ba0df77 100644
--- a/Source/HtmlRenderer/Adapters/RGraphics.cs
+++ b/Source/HtmlRenderer/Adapters/RGraphics.cs
@@ -76,16 +76,15 @@ public RBrush GetSolidBrush(RColor color)
}
///
- /// Get linear gradient color brush from to .
+ /// Get a multi-stop linear gradient brush along the line from to .
///
- /// the rectangle to get the brush for
- /// the start color of the gradient
- /// the end color of the gradient
- /// the angle to move the gradient from start color to end color in the rectangle
+ /// the gradient line's start point
+ /// the gradient line's end point
+ /// color stops, each with a position in [0,1] along the gradient line
/// linear gradient color brush instance
- public RBrush GetLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle)
+ public RBrush GetLinearGradientBrush(RPoint p1, RPoint p2, (RColor Color, double Position)[] stops)
{
- return _adapter.GetLinearGradientBrush(rect, color1, color2, angle);
+ return _adapter.GetLinearGradientBrush(p1, p2, stops);
}
///
diff --git a/Source/HtmlRenderer/Adapters/RGraphicsPath.cs b/Source/HtmlRenderer/Adapters/RGraphicsPath.cs
index 21c86bc1a..474781a5e 100644
--- a/Source/HtmlRenderer/Adapters/RGraphicsPath.cs
+++ b/Source/HtmlRenderer/Adapters/RGraphicsPath.cs
@@ -30,10 +30,19 @@ public abstract class RGraphicsPath : IDisposable
public abstract void LineTo(double x, double y);
///
- /// Add circular arc of the given size to the given point from the last point.
+ /// Add an elliptical arc with independent horizontal (X) and vertical (Y) radii to the given
+ /// point from the last point - supports elliptical border-radius corners.
///
- public abstract void ArcTo(double x, double y, double size, Corner corner);
-
+ public abstract void ArcTo(double x, double y, double radiusX, double radiusY, Corner corner);
+
+ ///
+ /// Add a circular arc of the given size to the given point from the last point.
+ ///
+ public void ArcTo(double x, double y, double size, Corner corner)
+ {
+ ArcTo(x, y, size, size, corner);
+ }
+
///
/// Release path resources.
///
diff --git a/Source/HtmlRenderer/Core/CssData.cs b/Source/HtmlRenderer/Core/CssData.cs
index f0e4a8106..179956178 100644
--- a/Source/HtmlRenderer/Core/CssData.cs
+++ b/Source/HtmlRenderer/Core/CssData.cs
@@ -6,54 +6,51 @@
// 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.Linq;
using TheArtOfDev.HtmlRenderer.Adapters;
-using TheArtOfDev.HtmlRenderer.Core.Entities;
+using TheArtOfDev.HtmlRenderer.Core.CssEngine;
+using TheArtOfDev.HtmlRenderer.Core.Dom;
using TheArtOfDev.HtmlRenderer.Core.Parse;
using TheArtOfDev.HtmlRenderer.Core.Utils;
namespace TheArtOfDev.HtmlRenderer.Core
{
///
- /// Holds parsed stylesheet css blocks arranged by media and classes.
- ///
+ /// Holds parsed stylesheets (UA default + author + inline), and provides origin-aware,
+ /// specificity-ordered selector matching against the box tree.
///
///
- /// To learn more about CSS blocks visit CSS spec: http://www.w3.org/TR/CSS21/syndata.html#block
+ /// Ported from PeachPDF's Html/Core/CssData.cs (itself a fork of Tyler Brinks' ExCSS), adapted to
+ /// HTML-Renderer's CssBox/HtmlTag shape. One deliberate omission from the source: the
+ /// ::marker pseudo-element box-synthesis path is removed - ::before/::after
+ /// synthesis is kept, but this codebase has no CssBox.IsMarkerPseudoElement concept and no
+ /// consumer for it (list markers are painted procedurally, not via a synthesized box).
///
public sealed class CssData
{
- #region Fields and Consts
-
///
- /// used to return empty array
+ /// The parsed stylesheets that make up this CssData, in the order they were added (UA default
+ /// first, then author/`<link>`/`<style>` stylesheets in document order). Each carries its own
+ /// flag so origin-aware cascade phases can be resolved.
///
- private static readonly List _emptyArray = new List();
-
- ///
- /// dictionary of media type to dictionary of css class name to the cssBlocks collection with all the data.
- ///
- private readonly Dictionary>> _mediaBlocks = new Dictionary>>(StringComparer.InvariantCultureIgnoreCase);
-
- #endregion
-
+ internal List Stylesheets { get; } = new List();
///
/// Init.
///
internal CssData()
{
- _mediaBlocks.Add("all", new Dictionary>(StringComparer.InvariantCultureIgnoreCase));
}
///
/// Parse the given stylesheet to object.
- /// If is true the parsed css blocks are added to the
+ /// If is true the parsed css blocks are added to the
/// default css data (as defined by W3), merged if class name already exists. If false only the data in the given stylesheet is returned.
///
///
@@ -63,152 +60,872 @@ internal CssData()
/// the parsed css data
public static CssData Parse(RAdapter adapter, string stylesheet, bool combineWithDefault = true)
{
- CssParser parser = new CssParser(adapter);
+ var parser = new CssParser(adapter);
return parser.ParseStyleSheet(stylesheet, combineWithDefault);
}
///
- /// dictionary of media type to dictionary of css class name to the cssBlocks collection with all the data
+ /// Combine this CSS data's stylesheets with 's.
+ ///
+ /// the CSS data to combine with
+ public void Combine(CssData other)
+ {
+ ArgChecker.AssertArgNotNull(other, "other");
+ Stylesheets.AddRange(other.Stylesheets);
+ }
+
+ ///
+ /// Create a shallow copy of this css data - the returned instance has its own
+ /// list (so appending a new stylesheet to the clone doesn't affect the original), but the
+ /// instances themselves are shared (they're never mutated after parsing).
///
- internal IDictionary>> MediaBlocks
+ /// cloned object
+ public CssData Clone()
+ {
+ var clone = new CssData();
+ clone.Stylesheets.AddRange(Stylesheets);
+ return clone;
+ }
+
+ // --- Rule index -----------------------------------------------------------------------
+ //
+ // Matching every stylesheet rule against every CssBox in the document (the naive approach)
+ // is O(rules x boxes) and dominates cascade cost on large documents. Real browser engines
+ // avoid this by bucketing rules by the "subject" simple selector (the one that must match the
+ // box itself, e.g. the tag/class/id) so a box only needs to test the handful of rules that
+ // could plausibly match it, instead of the whole stylesheet. DoesSelectorMatch remains the
+ // source of truth for whether a rule actually matches - the index only narrows the candidates.
+ //
+ // Built lazily, once, the first time this CssData's rules are queried. Safe because by the
+ // time CascadeApplyStyles starts querying rules for the box tree, DomParser has already
+ // finished building/cloning CssData from ");
}
diff --git a/Source/HtmlRenderer/Core/Utils/RenderUtils.cs b/Source/HtmlRenderer/Core/Utils/RenderUtils.cs
index 2726b5fb2..091c75191 100644
--- a/Source/HtmlRenderer/Core/Utils/RenderUtils.cs
+++ b/Source/HtmlRenderer/Core/Utils/RenderUtils.cs
@@ -95,44 +95,42 @@ public static void DrawImageErrorIcon(RGraphics g, HtmlContainerInt htmlContaine
}
///
- /// Creates a rounded rectangle using the specified corner radius
- /// NW-----NE
+ /// Creates a rounded rectangle path. Each corner has independent horizontal (X) and vertical (Y)
+ /// radii, supporting elliptical corners per the CSS border-radius spec.
+ /// TL-----TR
/// | |
/// | |
- /// SW-----SE
+ /// BL-----BR
///
/// the device to draw into
/// Rectangle to round
- /// Radius of the north east corner
- /// Radius of the north west corner
- /// Radius of the south east corner
- /// Radius of the south west corner
/// GraphicsPath with the lines of the rounded rectangle ready to be painted
- public static RGraphicsPath GetRoundRect(RGraphics g, RRect rect, double nwRadius, double neRadius, double seRadius, double swRadius)
+ public static RGraphicsPath GetRoundRect(RGraphics g, RRect rect,
+ double tlX, double tlY, double trX, double trY,
+ double brX, double brY, double blX, double blY)
{
var path = g.GetGraphicsPath();
- path.Start(rect.Left + nwRadius, rect.Top);
-
- path.LineTo(rect.Right - neRadius, rect.Y);
-
- if (neRadius > 0f)
- path.ArcTo(rect.Right, rect.Top + neRadius, neRadius, RGraphicsPath.Corner.TopRight);
-
- path.LineTo(rect.Right, rect.Bottom - seRadius);
-
- if (seRadius > 0f)
- path.ArcTo(rect.Right - seRadius, rect.Bottom, seRadius, RGraphicsPath.Corner.BottomRight);
-
- path.LineTo(rect.Left + swRadius, rect.Bottom);
-
- if (swRadius > 0f)
- path.ArcTo(rect.Left, rect.Bottom - swRadius, swRadius, RGraphicsPath.Corner.BottomLeft);
-
- path.LineTo(rect.Left, rect.Top + nwRadius);
-
- if (nwRadius > 0f)
- path.ArcTo(rect.Left + nwRadius, rect.Top, nwRadius, RGraphicsPath.Corner.TopLeft);
+ // Top edge: start after TL corner, end before TR corner.
+ path.Start(rect.Left + tlX, rect.Top);
+ path.LineTo(rect.Right - trX, rect.Top);
+ if (trX > 0 || trY > 0)
+ path.ArcTo(rect.Right, rect.Top + trY, trX, trY, RGraphicsPath.Corner.TopRight);
+
+ // Right edge.
+ path.LineTo(rect.Right, rect.Bottom - brY);
+ if (brX > 0 || brY > 0)
+ path.ArcTo(rect.Right - brX, rect.Bottom, brX, brY, RGraphicsPath.Corner.BottomRight);
+
+ // Bottom edge.
+ path.LineTo(rect.Left + blX, rect.Bottom);
+ if (blX > 0 || blY > 0)
+ path.ArcTo(rect.Left, rect.Bottom - blY, blX, blY, RGraphicsPath.Corner.BottomLeft);
+
+ // Left edge.
+ path.LineTo(rect.Left, rect.Top + tlY);
+ if (tlX > 0 || tlY > 0)
+ path.ArcTo(rect.Left + tlX, rect.Top, tlX, tlY, RGraphicsPath.Corner.TopLeft);
return path;
}