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 @@ Intro - +

HTML Renderer Project - $$Platform$$
diff --git a/Source/Demo/Common/Samples/07.Additional features.htm b/Source/Demo/Common/Samples/07.Additional features.htm index 764e9511c..bd07880e9 100644 --- a/Source/Demo/Common/Samples/07.Additional features.htm +++ b/Source/Demo/Common/Samples/07.Additional features.htm @@ -5,20 +5,18 @@ @@ -54,7 +54,7 @@

Transparent text

elementum dolor ac mauris. Ut tristique. In varius volutpat metus. Integer leo dolor, tristique a, dignissim ac, iaculis eget, elit. Donec arcu.

-

+

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; }