Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"version": 1,
"isRoot": true,
"tools": {
"fallout.cli": {
"version": "11.0.18",
"fallout.globaltool": {
"version": "10.4.0",
"commands": [
"fallout"
]
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 1.0.2

Released on Friday, August 21 2026.

- Fixed `oklab`/`oklch` usage with plain number as first argument (#225) @scasteran-jw

# 1.0.1

Released on Friday, July 31 2026.
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ AngleSharp.Css contains code written by (in order of first pull request / commit
* [Sebastian Stehle](https://github.com/SebastianStehle)
* [MaceWindu](https://github.com/MaceWindu)
* [Serhan Apaydın](https://github.com/monoblaine)
* [scasteran](https://github.com/scasteran-jw)

Without these awesome people AngleSharp.Css could not exist. Thanks to everyone for your contributions! :beers:

Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
[![GitHub Tag](https://img.shields.io/github/tag/AngleSharp/AngleSharp.Css.svg?style=flat-square)](https://github.com/AngleSharp/AngleSharp.Css/releases)
[![NuGet Count](https://img.shields.io/nuget/dt/AngleSharp.Css.svg?style=flat-square)](https://www.nuget.org/packages/AngleSharp.Css/)
[![Issues Open](https://img.shields.io/github/issues/AngleSharp/AngleSharp.Css.svg?style=flat-square)](https://github.com/AngleSharp/AngleSharp.Css/issues)
[![Gitter Chat](http://img.shields.io/badge/gitter-AngleSharp/AngleSharp-blue.svg?style=flat-square)](https://gitter.im/AngleSharp/AngleSharp)
[![StackOverflow Questions](https://img.shields.io/stackexchange/stackoverflow/t/anglesharp.svg?style=flat-square)](https://stackoverflow.com/tags/anglesharp)
[![CLA Assistant](https://cla-assistant.io/readme/badge/AngleSharp/AngleSharp.Css?style=flat-square)](https://cla-assistant.io/AngleSharp/AngleSharp.Css)

AngleSharp.Css extends the core AngleSharp library with some more powerful CSS capabilities. This repository is the home of the source for the AngleSharp.Css NuGet package.
Expand Down
4 changes: 1 addition & 3 deletions build/_build.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Fallout.Common" Version="11.0.18" />
<!-- Transitive pin: Fallout.Common 11.0.18 still resolves 10.0.6, which carries advisories. -->
<PackageReference Include="System.Security.Cryptography.Xml" Version="10.0.10" PrivateAssets="All" />
<PackageReference Include="Fallout.Common" Version="10.4.0" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion src/AngleSharp.Css.Docs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@anglesharp/css",
"version": "1.0.0",
"version": "1.0.2",
"preview": true,
"description": "The doclet for the AngleSharp.Css documentation.",
"keywords": [
Expand Down
91 changes: 91 additions & 0 deletions src/AngleSharp.Css.Tests/Declarations/CssFontDescriptorProperty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
namespace AngleSharp.Css.Tests.Declarations
{
using NUnit.Framework;
using static CssConstructionFunctions;

[TestFixture]
public class CssFontDescriptorPropertyTests
{
[TestCase("size-adjust: 100%", "100%")]
[TestCase("size-adjust: 90.5%", "90.5%")]
[TestCase("size-adjust: 0%", "0%")]
public void SizeAdjustLegalValues(string snippet, string expected)
{
var property = ParseDeclaration(snippet);
Assert.AreEqual("size-adjust", property.Name);
Assert.IsTrue(property.HasValue);
Assert.AreEqual(expected, property.Value);
}

[TestCase("size-adjust: 10px")]
[TestCase("size-adjust: 100")]
[TestCase("size-adjust: auto")]
public void SizeAdjustIllegalValues(string snippet)
{
var property = ParseDeclaration(snippet);
Assert.IsFalse(property.HasValue);
}

[TestCase("ascent-override: 90%", "90%")]
[TestCase("ascent-override: normal", "normal")]
[TestCase("descent-override: 20%", "20%")]
[TestCase("line-gap-override: 0%", "0%")]
public void MetricOverrideLegalValues(string snippet, string expected)
{
var property = ParseDeclaration(snippet);
Assert.IsTrue(property.HasValue);
Assert.AreEqual(expected, property.Value);
}

[TestCase("ascent-override: 10px")]
[TestCase("descent-override: auto")]
[TestCase("line-gap-override: none")]
public void MetricOverrideIllegalValues(string snippet)
{
var property = ParseDeclaration(snippet);
Assert.IsFalse(property.HasValue);
}

[TestCase("font-feature-settings: normal", "normal")]
[TestCase("font-feature-settings: \"liga\"", "\"liga\"")]
[TestCase("font-feature-settings: \"liga\" 1", "\"liga\" 1")]
[TestCase("font-feature-settings: \"kern\" on", "\"kern\" on")]
[TestCase("font-feature-settings: \"kern\" off", "\"kern\" off")]
[TestCase("font-feature-settings: \"liga\" 1, \"kern\" off", "\"liga\" 1, \"kern\" off")]
public void FontFeatureSettingsLegalValues(string snippet, string expected)
{
var property = ParseDeclaration(snippet);
Assert.AreEqual("font-feature-settings", property.Name);
Assert.IsTrue(property.HasValue);
Assert.AreEqual(expected, property.Value);
}

[TestCase("font-feature-settings: 12")]
[TestCase("font-feature-settings: liga")]
[TestCase("font-feature-settings: \"liga\" bogus")]
public void FontFeatureSettingsIllegalValues(string snippet)
{
var property = ParseDeclaration(snippet);
Assert.IsFalse(property.HasValue);
}

[TestCase("font-variation-settings: normal", "normal")]
[TestCase("font-variation-settings: \"wght\" 400", "\"wght\" 400")]
[TestCase("font-variation-settings: \"wght\" 400, \"slnt\" -10", "\"wght\" 400, \"slnt\" -10")]
public void FontVariationSettingsLegalValues(string snippet, string expected)
{
var property = ParseDeclaration(snippet);
Assert.AreEqual("font-variation-settings", property.Name);
Assert.IsTrue(property.HasValue);
Assert.AreEqual(expected, property.Value);
}

[TestCase("font-variation-settings: wght 400")]
[TestCase("font-variation-settings: 400")]
public void FontVariationSettingsIllegalValues(string snippet)
{
var property = ParseDeclaration(snippet);
Assert.IsFalse(property.HasValue);
}
}
}
81 changes: 81 additions & 0 deletions src/AngleSharp.Css.Tests/Declarations/SetPropertyOverwriteTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#nullable disable
namespace AngleSharp.Css.Tests.Declarations
{
using AngleSharp.Css.Dom;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using static CssConstructionFunctions;

/// <summary>
/// Setting a declaration builds a fresh property and assigns its value, which always
/// replaces the raw value it starts with. These cases pin down that re-declaring a
/// property - longhand over shorthand, shorthand over longhand, important over
/// normal, or an invalid value over a valid one - does not depend on the new property
/// being seeded from the old one first.
///
/// The expectations are a captured baseline of the behaviour, not a statement about
/// what the spec requires.
/// </summary>
[TestFixture]
public class SetPropertyOverwriteTests
{
private static readonly (String Input, String Expected)[] Cases =
{
("color:red;color:blue", "color: rgba(0, 0, 255, 1)"),
("color:blue;color:red", "color: rgba(255, 0, 0, 1)"),
("color:red;color:notacolor", "color: rgba(255, 0, 0, 1)"),
("color:red !important;color:blue", "color: rgba(255, 0, 0, 1) !important"),
("color:red;color:blue !important", "color: rgba(0, 0, 255, 1) !important"),
("color:red !important;color:blue !important", "color: rgba(0, 0, 255, 1) !important"),
("--x:1;--x:2", "--x: 2"),
("--x:1;color:red", "--x: 1; color: rgba(255, 0, 0, 1)"),
("margin:1px;margin-top:2px", "margin-bottom: 1px; margin-left: 1px; margin-right: 1px; margin-top: 2px"),
("margin-top:2px;margin:1px", "margin-bottom: 1px; margin-left: 1px; margin-right: 1px; margin-top: 1px"),
("margin:1px;margin:notalength", "margin-bottom: 1px; margin-left: 1px; margin-right: 1px; margin-top: 1px"),
("margin:1px !important;margin-top:2px", "margin-bottom: 1px !important; margin-left: 1px !important; margin-right: 1px !important; margin-top: 1px !important"),
("padding:1px 2px;padding-left:9px;padding:3px", "padding-bottom: 3px; padding-left: 3px; padding-right: 3px; padding-top: 3px"),
("border-radius:4px;border-top-left-radius:9px", "border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; border-top-left-radius: 9px; border-top-right-radius: 4px"),
("flex:1 1 auto;flex-grow:5", "flex-basis: auto; flex-grow: 5; flex-shrink: 1"),
("overflow:hidden;overflow-x:scroll", "overflow-x: scroll; overflow: hidden"),
("grid-area:a;grid-row:2", "grid-column-end: a; grid-column-start: a; grid-row-end: auto; grid-row-start: 2"),
("grid-row:2;grid-area:a", "grid-column-end: a; grid-column-start: a; grid-row-end: a; grid-row-start: a"),
("transition:all .3s;transition-duration:1s", "transition-delay: initial; transition-duration: 1s; transition-property: all; transition-timing-function: initial"),
("font:bold 14px/1.2 Arial;font-size:20px", "font-family: Arial; font-size: 20px; font-stretch: ; font-style: ; font-variant: ; font-weight: bold; line-height: 1.2"),
("font-size:20px;font:bold 14px/1.2 Arial", "font-family: Arial; font-size: 14px; font-stretch: ; font-style: ; font-variant: ; font-weight: bold; line-height: 1.2"),
("border:1px solid red;border-width:3px", "border-bottom-color: rgba(255, 0, 0, 1); border-bottom-style: solid; border-bottom-width: 3px; border-left-color: rgba(255, 0, 0, 1); border-left-style: solid; border-left-width: 3px; border-right-color: rgba(255, 0, 0, 1); border-right-style: solid; border-right-width: 3px; border-top-color: rgba(255, 0, 0, 1); border-top-style: solid; border-top-width: 3px"),
("border-width:3px;border:1px solid red", "border-bottom-color: rgba(255, 0, 0, 1); border-bottom-style: solid; border-bottom-width: 1px; border-left-color: rgba(255, 0, 0, 1); border-left-style: solid; border-left-width: 1px; border-right-color: rgba(255, 0, 0, 1); border-right-style: solid; border-right-width: 1px; border-top-color: rgba(255, 0, 0, 1); border-top-style: solid; border-top-width: 1px"),
("background:red;background:blue", "background-attachment: initial; background-clip: initial; background-color: rgba(0, 0, 255, 1); background-image: initial; background-origin: initial; background-position-x: initial; background-position-y: initial; background-repeat-x: initial; background-repeat-y: initial; background-size: initial"),
("background:red;background-color:blue", "background-attachment: initial; background-clip: initial; background-color: rgba(0, 0, 255, 1); background-image: initial; background-origin: initial; background-position-x: initial; background-position-y: initial; background-repeat-x: initial; background-repeat-y: initial; background-size: initial"),
("background-color:blue;background:red", "background-attachment: initial; background-clip: initial; background-color: rgba(255, 0, 0, 1); background-image: initial; background-origin: initial; background-position-x: initial; background-position-y: initial; background-repeat-x: initial; background-repeat-y: initial; background-size: initial"),
("background:red;background:", "background-attachment: initial; background-clip: initial; background-color: rgba(255, 0, 0, 1); background-image: initial; background-origin: initial; background-position-x: initial; background-position-y: initial; background-repeat-x: initial; background-repeat-y: initial; background-size: initial"),
("background:linear-gradient(red,blue);background:none", "background-attachment: initial; background-clip: initial; background-color: initial; background-image: none; background-origin: initial; background-position-x: initial; background-position-y: initial; background-repeat-x: initial; background-repeat-y: initial; background-size: initial"),
};

[Test]
public void RedeclaringAPropertyProducesTheExpectedBlock()
{
var failures = new List<String>();

foreach (var (input, expected) in Cases)
{
var actual = Describe(ParseDeclarations(input));

if (!String.Equals(actual, expected, StringComparison.Ordinal))
{
failures.Add($"{input}{Environment.NewLine} expected: {expected}{Environment.NewLine} actual: {actual}");
}
}

Assert.That(failures, Is.Empty, String.Join(Environment.NewLine, failures));
}

private static String Describe(ICssStyleDeclaration style)
{
var parts = style.Select(p => $"{p.Name}: {p.Value}{(p.IsImportant ? " !important" : String.Empty)}").ToList();
parts.Sort(StringComparer.Ordinal);
return String.Join("; ", parts);
}
}
}
44 changes: 44 additions & 0 deletions src/AngleSharp.Css.Tests/Functions/CssColorFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,28 @@ public void ParseOklabToRgb_Second()
Assert.AreEqual("rgba(198, 93, 7, 1)", color);
}

[Test]
public void ParseOklabNumberToRgb_First()
{
var html = @"<p style='color: oklab(0.401 0.1143 0.045)'>Text</p>";
var dom = ParseDocument(html);
var p = dom.QuerySelector("p");
var s = p.GetStyle();
var color = s.GetColor();
Assert.AreEqual("rgba(125, 35, 40, 1)", color);
}

[Test]
public void ParseOklabNumberToRgb_Second()
{
var html = @"<p style='color: oklab(0.5969 0.1007 0.1191);'>Text</p>";
var dom = ParseDocument(html);
var p = dom.QuerySelector("p");
var s = p.GetStyle();
var color = s.GetColor();
Assert.AreEqual("rgba(198, 93, 7, 1)", color);
}

[Test]
public void ParseOklabToRgb_Alpha()
{
Expand Down Expand Up @@ -108,6 +130,28 @@ public void ParseOklchToRgb_Second()
Assert.AreEqual("rgba(198, 93, 7, 1)", color);
}

[Test]
public void ParseOklchNumberToRgb_First()
{
var html = @"<p style='color: oklch(0.401 0.123 21.57)'>Text</p>";
var dom = ParseDocument(html);
var p = dom.QuerySelector("p");
var s = p.GetStyle();
var color = s.GetColor();
Assert.AreEqual("rgba(125, 35, 40, 1)", color);
}

[Test]
public void ParseOklchNumberToRgb_Second()
{
var html = @"<p style='color: oklch(0.5969 0.156 49.77)'>Text</p>";
var dom = ParseDocument(html);
var p = dom.QuerySelector("p");
var s = p.GetStyle();
var color = s.GetColor();
Assert.AreEqual("rgba(198, 93, 7, 1)", color);
}

[Test]
public void ParseOklchToRgb_Alpha()
{
Expand Down
Loading
Loading