From a34c48ae5a2715a152e3eb19e6292afea3f70e5b Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Sat, 1 Aug 2026 10:39:24 +0200 Subject: [PATCH 1/6] Removed badges --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 4ecccc76..dcc5e464 100644 --- a/README.md +++ b/README.md @@ -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. From d74ba8068d3ede285ad7a373ad194c7cace19b38 Mon Sep 17 00:00:00 2001 From: Jason Finch Date: Mon, 3 Aug 2026 09:00:09 +1000 Subject: [PATCH 2/6] Avoid rebuilding shorthands when setting a declaration CssStyleDeclaration.CreateProperty seeded every newly created property with the raw value of an existing declaration of the same name before handing it back: var newProperty = _context.CreateProperty(propertyName); var existing = GetProperty(propertyName); if (existing is not null) newProperty.RawValue = existing.RawValue; That seed can never be observed. CreateProperty had a single caller, SetProperty(name, value, priority), whose next statement is `property.Value = propertyValue` - and both branches of the CssProperty.Value setter assign _value unconditionally. The lookup itself is not cheap. On a miss GetProperty falls through to GetPropertyShorthand, i.e. TryCreateShorthand(force: true), which for a shorthand such as background or border reconstructs the whole shorthand from its longhands - allocating an ICssValue[], recursing per longhand and calling CreateShorthand - only for the result to be overwritten on the next line. Parsing a sheet paid for that on every declaration. Dropping the seed leaves GetProperty and the rest of the public behaviour untouched. Measured on the sample sheets in AngleSharp.Performance.Css (net10.0): cdnjs.cloudflare 1.963 ms -> 1.499 ms 1379.8 KB -> 1226.6 KB csszengarden 1.743 ms -> 1.446 ms 1283.2 KB -> 1178.1 KB florian-rappl 4.137 ms -> 3.683 ms 2268.8 KB -> 2077.5 KB maxcdn.bootstrapcdn 7.811 ms -> 6.522 ms 4978.0 KB -> 4419.8 KB s.yimg 1.935 ms -> 1.807 ms 1032.2 KB -> 1000.5 KB static.licdn 1.904 ms -> 1.588 ms 1095.9 KB -> 972.1 KB style.aliunicorn 1.507 ms -> 1.434 ms 897.8 KB -> 876.1 KB Inline declaration parsing improves by a similar margin (968.4 us -> 724.8 us); the cascade benchmarks are unchanged, as they merge declarations instead of going through this setter. SetPropertyOverwriteTests covers 28 re-declaration cases - longhand over shorthand and back, !important in either order, an invalid value after a valid one, an empty value, custom properties, grid-area/grid-row, font, flex and border-radius. The expectations are a baseline captured from the previous implementation, which produced identical output for all of them. --- .../Declarations/SetPropertyOverwriteTests.cs | 81 +++++++++++++++++++ .../Dom/Internal/CssStyleDeclaration.cs | 20 ++--- 2 files changed, 87 insertions(+), 14 deletions(-) create mode 100644 src/AngleSharp.Css.Tests/Declarations/SetPropertyOverwriteTests.cs diff --git a/src/AngleSharp.Css.Tests/Declarations/SetPropertyOverwriteTests.cs b/src/AngleSharp.Css.Tests/Declarations/SetPropertyOverwriteTests.cs new file mode 100644 index 00000000..9846940e --- /dev/null +++ b/src/AngleSharp.Css.Tests/Declarations/SetPropertyOverwriteTests.cs @@ -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; + + /// + /// 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. + /// + [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(); + + 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); + } + } +} diff --git a/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs b/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs index b483b2d3..1163a282 100644 --- a/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs +++ b/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs @@ -284,7 +284,12 @@ public void SetProperty(String propertyName, String propertyValue, String priori { if (priority is null || priority.Isi(CssKeywords.Important)) { - var property = CreateProperty(propertyName); + // Deliberately not seeded from any existing declaration of the same + // name: assigning Value below always replaces the raw value, so + // reading the old one only costs a lookup. For a shorthand that + // lookup is not even a lookup - it rebuilds the shorthand from its + // longhands just to throw the result away. + var property = _context.CreateProperty(propertyName); if (property is not null) { @@ -372,19 +377,6 @@ private ICssProperty FindUnserializedLonghand(String name, IEnumerable s return null; } - private ICssProperty CreateProperty(String propertyName) - { - var newProperty = _context.CreateProperty(propertyName); - var existing = GetProperty(propertyName); - - if (existing is not null) - { - newProperty.RawValue = existing.RawValue; - } - - return newProperty; - } - private void SetProperty(ICssProperty property) { if (property.IsShorthand) From 31ffe0e9fd64a5eea533483bf9906410603793f9 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 8 Aug 2026 15:30:34 +0300 Subject: [PATCH 3/6] Move the build orchestrator to the Fallout stable channel (10.4.0) Fallout v10.4.0 is the first stable-channel release; the previously pinned 11.0.18 belongs to the edge channel. The stable CLI ships as Fallout.GlobalTool (the fallout command is unchanged), and Fallout.Common 10.4.0 resolves the patched System.Security.Cryptography.Xml 10.0.10 on its own, so the manual transitive pin is no longer needed (Fallout-build/Fallout#618). Co-Authored-By: Claude Fable 5 --- .config/dotnet-tools.json | 4 ++-- build/_build.csproj | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 6a8c8a1d..a8483d33 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -2,8 +2,8 @@ "version": 1, "isRoot": true, "tools": { - "fallout.cli": { - "version": "11.0.18", + "fallout.globaltool": { + "version": "10.4.0", "commands": [ "fallout" ] diff --git a/build/_build.csproj b/build/_build.csproj index 647ac264..11c89b02 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -11,9 +11,7 @@ - - - + From b61702058d3acf459b30fe8e334d628cbc8b485e Mon Sep 17 00:00:00 2001 From: Jason Finch Date: Thu, 13 Aug 2026 10:07:45 +1000 Subject: [PATCH 4/6] Preserve @font-face descriptors outside the hardcoded set CssFontFaceRule kept only the seven descriptors named in its private ContainedProperties set; CssDeclarationRule discarded everything else silently and without consulting CssParserOptions, so IsIncludingUnknownDeclarations had no effect inside @font-face even though it is what keeps unrecognized declarations alive in style rules. Standard CSS Fonts Level 4 descriptors (font-display, size-adjust, ascent-override, font-feature-settings, ...) and vendor descriptors (mso-*) were both lost, and ToCss emitted a well-formed looking rule so a caller round-tripping a stylesheet had no way to notice. Non-descriptor declarations now fall through to the same IsAllowingUnknownDeclarations gate that style rules use, which also covers @counter-style, @font-feature-values and @viewport - the sibling rules on the same base class. Register the standard descriptors that had no declaration at all (size-adjust, ascent-override, descent-override, line-gap-override, font-feature-settings) with real value grammars, add a percentage converter, and extend ContainedProperties so they are kept, typed, by default rather than only under the opt-in. Along the way: - font-variation-settings accepted only `normal`, so `"wght" 400` was rejected in style rules and would have been rejected in the newly preserved @font-face. It now implements normal | [ ]#. - SetValue added properties without checking they parsed, so an invalid descriptor serialized as a malformed `size-adjust: ;`. Invalid values are now ignored and leave an existing valid declaration standing, matching CssStyleDeclaration. - ICssFontFaceRule.Features was a String.Empty/no-op stub despite its featureSettings DOM name; it now maps to font-feature-settings. Fixes the silent loss reported downstream in mganss/HtmlSanitizer#541. --- .../Declarations/CssFontDescriptorProperty.cs | 91 ++++++++++ .../Rules/FontFaceDescriptors.cs | 162 ++++++++++++++++++ .../BrowsingContextExtensions.cs | 2 +- src/AngleSharp.Css/Constants/CssKeywords.cs | 10 ++ src/AngleSharp.Css/Constants/InitialValues.cs | 5 + src/AngleSharp.Css/Constants/PropertyNames.cs | 20 +++ .../Declarations/AscentOverrideDeclaration.cs | 17 ++ .../DescentOverrideDeclaration.cs | 17 ++ .../FontFeatureSettingsDeclaration.cs | 17 ++ .../LineGapOverrideDeclaration.cs | 17 ++ .../Declarations/SizeAdjustDeclaration.cs | 17 ++ .../Dom/Internal/Rules/CssDeclarationRule.cs | 36 ++-- .../Dom/Internal/Rules/CssFontFaceRule.cs | 12 +- .../Factories/DefaultDeclarationFactory.cs | 35 ++++ src/AngleSharp.Css/ValueConverters.cs | 57 +++++- 15 files changed, 500 insertions(+), 15 deletions(-) create mode 100644 src/AngleSharp.Css.Tests/Declarations/CssFontDescriptorProperty.cs create mode 100644 src/AngleSharp.Css.Tests/Rules/FontFaceDescriptors.cs create mode 100644 src/AngleSharp.Css/Declarations/AscentOverrideDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/DescentOverrideDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/FontFeatureSettingsDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/LineGapOverrideDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/SizeAdjustDeclaration.cs diff --git a/src/AngleSharp.Css.Tests/Declarations/CssFontDescriptorProperty.cs b/src/AngleSharp.Css.Tests/Declarations/CssFontDescriptorProperty.cs new file mode 100644 index 00000000..7f945917 --- /dev/null +++ b/src/AngleSharp.Css.Tests/Declarations/CssFontDescriptorProperty.cs @@ -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); + } + } +} diff --git a/src/AngleSharp.Css.Tests/Rules/FontFaceDescriptors.cs b/src/AngleSharp.Css.Tests/Rules/FontFaceDescriptors.cs new file mode 100644 index 00000000..f324c1e9 --- /dev/null +++ b/src/AngleSharp.Css.Tests/Rules/FontFaceDescriptors.cs @@ -0,0 +1,162 @@ +namespace AngleSharp.Css.Tests.Rules +{ + using AngleSharp.Css.Dom; + using AngleSharp.Css.Parser; + using NUnit.Framework; + using System.Linq; + using static CssConstructionFunctions; + + /// + /// Descriptors inside @font-face used to be limited to a hardcoded set of seven, + /// with everything else dropped silently and without regard to the parser options. + /// These cases pin down that the standard CSS Fonts Level 4 descriptors survive by + /// default and that anything else survives when unknown declarations are included. + /// + [TestFixture] + public class FontFaceDescriptorTests + { + private static readonly CssParserOptions IncludingUnknown = new() { IsIncludingUnknownDeclarations = true }; + + [Test] + public void FontFaceKeepsFontDisplay() + { + var sheet = ParseStyleSheet("@font-face { font-display: swap }"); + var fontface = (ICssFontFaceRule)sheet.Rules[0]; + Assert.AreEqual("swap", fontface.GetPropertyValue(PropertyNames.FontDisplay)); + } + + [Test] + public void FontFaceKeepsSizeAdjust() + { + var sheet = ParseStyleSheet("@font-face { size-adjust: 90% }"); + var fontface = (ICssFontFaceRule)sheet.Rules[0]; + Assert.AreEqual("90%", fontface.GetPropertyValue(PropertyNames.SizeAdjust)); + } + + [TestCase("ascent-override", "90%")] + [TestCase("descent-override", "20%")] + [TestCase("line-gap-override", "0%")] + [TestCase("ascent-override", "normal")] + public void FontFaceKeepsMetricOverrides(string name, string value) + { + var sheet = ParseStyleSheet($"@font-face {{ {name}: {value} }}"); + var fontface = (ICssFontFaceRule)sheet.Rules[0]; + Assert.AreEqual(value, fontface.GetPropertyValue(name)); + } + + [Test] + public void FontFaceKeepsFontFeatureSettings() + { + var sheet = ParseStyleSheet("@font-face { font-feature-settings: \"liga\" 1, \"kern\" on, \"smcp\" }"); + var fontface = (ICssFontFaceRule)sheet.Rules[0]; + Assert.AreEqual("\"liga\" 1, \"kern\" on, \"smcp\"", fontface.GetPropertyValue(PropertyNames.FontFeatureSettings)); + } + + [Test] + public void FontFaceKeepsFontVariationSettings() + { + var sheet = ParseStyleSheet("@font-face { font-variation-settings: \"wght\" 400, \"slnt\" -10 }"); + var fontface = (ICssFontFaceRule)sheet.Rules[0]; + Assert.AreEqual("\"wght\" 400, \"slnt\" -10", fontface.GetPropertyValue(PropertyNames.FontVariationSettings)); + } + + [Test] + public void FontFaceFeaturesMapToFontFeatureSettings() + { + var sheet = ParseStyleSheet("@font-face { font-feature-settings: \"liga\" 1 }"); + var fontface = (ICssFontFaceRule)sheet.Rules[0]; + Assert.AreEqual("\"liga\" 1", fontface.Features); + + fontface.Features = "\"kern\" off"; + Assert.AreEqual("\"kern\" off", fontface.GetPropertyValue(PropertyNames.FontFeatureSettings)); + } + + [Test] + public void FontFaceStandardDescriptorsRoundtripViaToCss() + { + var src = "@font-face { font-family: \"FontName\"; src: url(\"https://example.com/font.woff\") format(\"woff\"); font-display: swap; size-adjust: 100% }"; + var sheet = ParseStyleSheet(src); + var css = sheet.ToCss(); + Assert.That(css, Does.Contain("font-display: swap")); + Assert.That(css, Does.Contain("size-adjust: 100%")); + } + + [Test] + public void FontFaceDropsVendorDescriptorByDefault() + { + var sheet = ParseStyleSheet("@font-face { mso-generic-font-family: auto }"); + var fontface = (ICssFontFaceRule)sheet.Rules[0]; + Assert.AreEqual(0, fontface.Length); + } + + [Test] + public void FontFaceKeepsVendorDescriptorWhenIncludingUnknownDeclarations() + { + var sheet = ParseStyleSheet("@font-face { mso-generic-font-family: auto }", IncludingUnknown); + var fontface = (ICssFontFaceRule)sheet.Rules[0]; + Assert.AreEqual("auto", fontface.GetPropertyValue("mso-generic-font-family")); + Assert.That(sheet.ToCss(), Does.Contain("mso-generic-font-family: auto")); + } + + [Test] + public void FontFaceKeepsCustomPropertyWhenIncludingUnknownDeclarations() + { + var sheet = ParseStyleSheet("@font-face { --custom-thing: 12px }", IncludingUnknown); + var fontface = (ICssFontFaceRule)sheet.Rules[0]; + Assert.AreEqual("12px", fontface.GetPropertyValue("--custom-thing")); + } + + [Test] + public void FontFaceIgnoresInvalidDescriptorValue() + { + var sheet = ParseStyleSheet("@font-face { size-adjust: 10px; ascent-override: bogus }"); + var fontface = (ICssFontFaceRule)sheet.Rules[0]; + Assert.AreEqual(0, fontface.Length); + Assert.That(sheet.ToCss(), Does.Not.Contain("size-adjust")); + } + + [Test] + public void FontFaceInvalidValueDoesNotOverwriteValidOne() + { + var sheet = ParseStyleSheet("@font-face { font-weight: 400; font-weight: bogus }"); + var fontface = (ICssFontFaceRule)sheet.Rules[0]; + Assert.AreEqual("400", fontface.Weight); + } + + [Test] + public void FontFaceValidValueOverwritesEarlierOne() + { + var sheet = ParseStyleSheet("@font-face { font-weight: 400; font-weight: 700 }"); + var fontface = (ICssFontFaceRule)sheet.Rules[0]; + Assert.AreEqual("700", fontface.Weight); + Assert.AreEqual(1, fontface.Length); + } + + [Test] + public void CounterStyleKeepsDescriptorsWhenIncludingUnknownDeclarations() + { + var sheet = ParseStyleSheet("@counter-style thumbs { system: cyclic; symbols: \"X\" }", IncludingUnknown); + var rule = (ICssProperties)sheet.Rules[0]; + Assert.AreEqual("cyclic", rule.GetPropertyValue("system")); + Assert.AreEqual("\"X\"", rule.GetPropertyValue("symbols")); + } + + [Test] + public void ViewportKeepsUnknownDescriptorWhenIncludingUnknownDeclarations() + { + var sheet = ParseStyleSheet("@viewport { width: 100px; foo: bar }", IncludingUnknown); + var rule = (ICssProperties)sheet.Rules[0]; + Assert.AreEqual("bar", rule.GetPropertyValue("foo")); + Assert.AreEqual(2, rule.Length); + } + + [Test] + public void FontFaceDescriptorsAreEnumerable() + { + var sheet = ParseStyleSheet("@font-face { font-family: \"X\"; font-display: swap; size-adjust: 50% }"); + var fontface = (ICssFontFaceRule)sheet.Rules[0]; + var names = fontface.Select(m => m.Name).ToArray(); + CollectionAssert.AreEquivalent(new[] { "font-family", "font-display", "size-adjust" }, names); + } + } +} diff --git a/src/AngleSharp.Css/BrowsingContextExtensions.cs b/src/AngleSharp.Css/BrowsingContextExtensions.cs index 9b6249c3..5cecbb1c 100644 --- a/src/AngleSharp.Css/BrowsingContextExtensions.cs +++ b/src/AngleSharp.Css/BrowsingContextExtensions.cs @@ -95,7 +95,7 @@ internal static CssProperty CreateProperty(this IBrowsingContext context, String private static Boolean AllowsDeclaration(this IBrowsingContext context, DeclarationInfo info) => info.Flags != PropertyFlags.Unknown || context.IsAllowingUnknownDeclarations(); - private static Boolean IsAllowingUnknownDeclarations(this IBrowsingContext context) + internal static Boolean IsAllowingUnknownDeclarations(this IBrowsingContext context) { var parser = context.GetProvider(); return parser?.Options.IsIncludingUnknownDeclarations ?? true; diff --git a/src/AngleSharp.Css/Constants/CssKeywords.cs b/src/AngleSharp.Css/Constants/CssKeywords.cs index bacdb6aa..4d36e61b 100644 --- a/src/AngleSharp.Css/Constants/CssKeywords.cs +++ b/src/AngleSharp.Css/Constants/CssKeywords.cs @@ -307,6 +307,16 @@ public static class CssKeywords /// public static readonly String Optional = "optional"; + /// + /// The on keyword (font-feature-settings). + /// + public static readonly String On = "on"; + + /// + /// The off keyword (font-feature-settings). + /// + public static readonly String Off = "off"; + /// /// The avoid keyword. /// diff --git a/src/AngleSharp.Css/Constants/InitialValues.cs b/src/AngleSharp.Css/Constants/InitialValues.cs index 166fb563..eb808595 100644 --- a/src/AngleSharp.Css/Constants/InitialValues.cs +++ b/src/AngleSharp.Css/Constants/InitialValues.cs @@ -47,6 +47,11 @@ static class InitialValues public static readonly ICssValue FontSynthesisStyleDecl = new CssConstantValue(CssKeywords.Auto, null); public static readonly ICssValue FontSynthesisSmallCapsDecl = new CssConstantValue(CssKeywords.Auto, null); public static readonly ICssValue FontVariationSettingsDecl = new CssConstantValue(CssKeywords.Normal, null); + public static readonly ICssValue FontFeatureSettingsDecl = new CssConstantValue(CssKeywords.Normal, null); + public static readonly ICssValue SizeAdjustDecl = new CssPercentageValue(100.0); + public static readonly ICssValue AscentOverrideDecl = new CssConstantValue(CssKeywords.Normal, null); + public static readonly ICssValue DescentOverrideDecl = new CssConstantValue(CssKeywords.Normal, null); + public static readonly ICssValue LineGapOverrideDecl = new CssConstantValue(CssKeywords.Normal, null); public static readonly ICssValue AccentColorDecl = new CssConstantValue(CssKeywords.Auto, null); public static readonly ICssValue AppearanceDecl = new CssIdentifierValue(CssKeywords.Auto); public static readonly ICssValue CaretColorDecl = new CssConstantValue(CssKeywords.Auto, null); diff --git a/src/AngleSharp.Css/Constants/PropertyNames.cs b/src/AngleSharp.Css/Constants/PropertyNames.cs index 822509b4..340bcf27 100644 --- a/src/AngleSharp.Css/Constants/PropertyNames.cs +++ b/src/AngleSharp.Css/Constants/PropertyNames.cs @@ -907,6 +907,26 @@ public static class PropertyNames /// public static readonly String FontKerning = "font-kerning"; + /// + /// The size-adjust declaration (@font-face descriptor). + /// + public static readonly String SizeAdjust = "size-adjust"; + + /// + /// The ascent-override declaration (@font-face descriptor). + /// + public static readonly String AscentOverride = "ascent-override"; + + /// + /// The descent-override declaration (@font-face descriptor). + /// + public static readonly String DescentOverride = "descent-override"; + + /// + /// The line-gap-override declaration (@font-face descriptor). + /// + public static readonly String LineGapOverride = "line-gap-override"; + /// /// The font-language-override declaration. /// diff --git a/src/AngleSharp.Css/Declarations/AscentOverrideDeclaration.cs b/src/AngleSharp.Css/Declarations/AscentOverrideDeclaration.cs new file mode 100644 index 00000000..b9612271 --- /dev/null +++ b/src/AngleSharp.Css/Declarations/AscentOverrideDeclaration.cs @@ -0,0 +1,17 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Dom; + using System; + using static ValueConverters; + + static class AscentOverrideDeclaration + { + public static String Name = PropertyNames.AscentOverride; + + public static IValueConverter Converter = FontMetricOverrideConverter; + + public static ICssValue InitialValue = InitialValues.AscentOverrideDecl; + + public static PropertyFlags Flags = PropertyFlags.None; + } +} diff --git a/src/AngleSharp.Css/Declarations/DescentOverrideDeclaration.cs b/src/AngleSharp.Css/Declarations/DescentOverrideDeclaration.cs new file mode 100644 index 00000000..b5830286 --- /dev/null +++ b/src/AngleSharp.Css/Declarations/DescentOverrideDeclaration.cs @@ -0,0 +1,17 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Dom; + using System; + using static ValueConverters; + + static class DescentOverrideDeclaration + { + public static String Name = PropertyNames.DescentOverride; + + public static IValueConverter Converter = FontMetricOverrideConverter; + + public static ICssValue InitialValue = InitialValues.DescentOverrideDecl; + + public static PropertyFlags Flags = PropertyFlags.None; + } +} diff --git a/src/AngleSharp.Css/Declarations/FontFeatureSettingsDeclaration.cs b/src/AngleSharp.Css/Declarations/FontFeatureSettingsDeclaration.cs new file mode 100644 index 00000000..45cb76ed --- /dev/null +++ b/src/AngleSharp.Css/Declarations/FontFeatureSettingsDeclaration.cs @@ -0,0 +1,17 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Dom; + using System; + using static ValueConverters; + + static class FontFeatureSettingsDeclaration + { + public static String Name = PropertyNames.FontFeatureSettings; + + public static IValueConverter Converter = FontFeatureSettingsConverter; + + public static ICssValue InitialValue = InitialValues.FontFeatureSettingsDecl; + + public static PropertyFlags Flags = PropertyFlags.Inherited; + } +} diff --git a/src/AngleSharp.Css/Declarations/LineGapOverrideDeclaration.cs b/src/AngleSharp.Css/Declarations/LineGapOverrideDeclaration.cs new file mode 100644 index 00000000..2e956e8a --- /dev/null +++ b/src/AngleSharp.Css/Declarations/LineGapOverrideDeclaration.cs @@ -0,0 +1,17 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Dom; + using System; + using static ValueConverters; + + static class LineGapOverrideDeclaration + { + public static String Name = PropertyNames.LineGapOverride; + + public static IValueConverter Converter = FontMetricOverrideConverter; + + public static ICssValue InitialValue = InitialValues.LineGapOverrideDecl; + + public static PropertyFlags Flags = PropertyFlags.None; + } +} diff --git a/src/AngleSharp.Css/Declarations/SizeAdjustDeclaration.cs b/src/AngleSharp.Css/Declarations/SizeAdjustDeclaration.cs new file mode 100644 index 00000000..2e652eff --- /dev/null +++ b/src/AngleSharp.Css/Declarations/SizeAdjustDeclaration.cs @@ -0,0 +1,17 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Dom; + using System; + using static ValueConverters; + + static class SizeAdjustDeclaration + { + public static String Name = PropertyNames.SizeAdjust; + + public static IValueConverter Converter = SizeAdjustConverter; + + public static ICssValue InitialValue = InitialValues.SizeAdjustDecl; + + public static PropertyFlags Flags = PropertyFlags.None; + } +} diff --git a/src/AngleSharp.Css/Dom/Internal/Rules/CssDeclarationRule.cs b/src/AngleSharp.Css/Dom/Internal/Rules/CssDeclarationRule.cs index 53d1a40f..9d252016 100644 --- a/src/AngleSharp.Css/Dom/Internal/Rules/CssDeclarationRule.cs +++ b/src/AngleSharp.Css/Dom/Internal/Rules/CssDeclarationRule.cs @@ -85,7 +85,11 @@ public override void ToCss(TextWriter writer, IStyleFormatter formatter) private ICssProperty CreateNewProperty(String propertyName) { - if (_contained.Contains(propertyName)) + // Descriptors of the rule itself are always created. Anything else is + // kept only when unknown declarations are included - the same switch + // that preserves them in ordinary style rules. Without it the + // declaration would be dropped silently. + if (_contained.Contains(propertyName) || Owner.Context.IsAllowingUnknownDeclarations()) { return Owner.Context.CreateProperty(propertyName); } @@ -110,22 +114,32 @@ protected void SetValue(String propertyName, String valueText) { if (!String.IsNullOrEmpty(valueText)) { - foreach (var declaration in _declarations) + var property = CreateNewProperty(propertyName); + + if (property is null) { - if (declaration.Name.Is(propertyName)) - { - declaration.Value = valueText; - return; - } + return; } - var property = CreateNewProperty(propertyName); + property.Value = valueText; - if (property != null) + if (property.RawValue is null) { - property.Value = valueText; - _declarations.Add(property); + // The value is not valid for this declaration; ignore it instead + // of storing an empty declaration that would serialize as "name: ". + return; } + + for (var i = 0; i < _declarations.Count; i++) + { + if (_declarations[i].Name.Is(propertyName)) + { + _declarations[i] = property; + return; + } + } + + _declarations.Add(property); } else { diff --git a/src/AngleSharp.Css/Dom/Internal/Rules/CssFontFaceRule.cs b/src/AngleSharp.Css/Dom/Internal/Rules/CssFontFaceRule.cs index 809644ff..d5eef949 100644 --- a/src/AngleSharp.Css/Dom/Internal/Rules/CssFontFaceRule.cs +++ b/src/AngleSharp.Css/Dom/Internal/Rules/CssFontFaceRule.cs @@ -22,6 +22,13 @@ sealed class CssFontFaceRule : CssDeclarationRule, ICssFontFaceRule PropertyNames.FontStretch, PropertyNames.UnicodeRange, PropertyNames.FontVariant, + PropertyNames.FontDisplay, + PropertyNames.FontFeatureSettings, + PropertyNames.FontVariationSettings, + PropertyNames.SizeAdjust, + PropertyNames.AscentOverride, + PropertyNames.DescentOverride, + PropertyNames.LineGapOverride, }; #endregion @@ -83,8 +90,8 @@ String ICssFontFaceRule.Variant String ICssFontFaceRule.Features { - get => String.Empty; - set { } + get => GetValue(PropertyNames.FontFeatureSettings); + set => SetValue(PropertyNames.FontFeatureSettings, value); } #endregion @@ -101,6 +108,7 @@ protected override void ReplaceWith(ICssRule rule) SetValue(PropertyNames.FontStretch, newRule.Stretch); SetValue(PropertyNames.UnicodeRange, newRule.Range); SetValue(PropertyNames.FontVariant, newRule.Variant); + SetValue(PropertyNames.FontFeatureSettings, newRule.Features); } #endregion diff --git a/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs b/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs index abde6601..7a168ad6 100644 --- a/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs +++ b/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs @@ -974,6 +974,41 @@ public class DefaultDeclarationFactory : IDeclarationFactory initialValue: FontDisplayDeclaration.InitialValue, flags: FontDisplayDeclaration.Flags) }, + { + FontFeatureSettingsDeclaration.Name, new DeclarationInfo( + name: FontFeatureSettingsDeclaration.Name, + converter: FontFeatureSettingsDeclaration.Converter, + initialValue: FontFeatureSettingsDeclaration.InitialValue, + flags: FontFeatureSettingsDeclaration.Flags) + }, + { + SizeAdjustDeclaration.Name, new DeclarationInfo( + name: SizeAdjustDeclaration.Name, + converter: SizeAdjustDeclaration.Converter, + initialValue: SizeAdjustDeclaration.InitialValue, + flags: SizeAdjustDeclaration.Flags) + }, + { + AscentOverrideDeclaration.Name, new DeclarationInfo( + name: AscentOverrideDeclaration.Name, + converter: AscentOverrideDeclaration.Converter, + initialValue: AscentOverrideDeclaration.InitialValue, + flags: AscentOverrideDeclaration.Flags) + }, + { + DescentOverrideDeclaration.Name, new DeclarationInfo( + name: DescentOverrideDeclaration.Name, + converter: DescentOverrideDeclaration.Converter, + initialValue: DescentOverrideDeclaration.InitialValue, + flags: DescentOverrideDeclaration.Flags) + }, + { + LineGapOverrideDeclaration.Name, new DeclarationInfo( + name: LineGapOverrideDeclaration.Name, + converter: LineGapOverrideDeclaration.Converter, + initialValue: LineGapOverrideDeclaration.InitialValue, + flags: LineGapOverrideDeclaration.Flags) + }, { FontKerningDeclaration.Name, new DeclarationInfo( name: FontKerningDeclaration.Name, diff --git a/src/AngleSharp.Css/ValueConverters.cs b/src/AngleSharp.Css/ValueConverters.cs index f7fe0121..8fb2a465 100644 --- a/src/AngleSharp.Css/ValueConverters.cs +++ b/src/AngleSharp.Css/ValueConverters.cs @@ -120,6 +120,11 @@ static class ValueConverters /// public static readonly IValueConverter OnlyLengthOrPercentConverter = new StructValueConverter(UnitParser.ParseDistance); + /// + /// Represents a percentage object, i.e. a number followed by a percent sign. + /// + public static readonly IValueConverter OnlyPercentConverter = new StructValueConverter(ParseOnlyPercent); + /// /// Represents a string object. /// @@ -216,6 +221,11 @@ static class ValueConverters /// public static readonly IValueConverter LengthOrPercentConverter = Or(OnlyLengthOrPercentConverter, CalcConverter); + /// + /// Represents a (calculated) percentage object. + /// + public static readonly IValueConverter PercentConverter = Or(OnlyPercentConverter, CalcConverter); + /// /// Represents an number object that is zero or greater. /// @@ -1088,6 +1098,32 @@ static class ValueConverters Assign(CssKeywords.Fallback, CssKeywords.Fallback), Assign(CssKeywords.Optional, CssKeywords.Optional)); + /// + /// Represents a converter for the size-adjust descriptor. + /// + public static readonly IValueConverter SizeAdjustConverter = PercentConverter; + + /// + /// Represents a converter for the ascent-override, descent-override and + /// line-gap-override descriptors. + /// + public static readonly IValueConverter FontMetricOverrideConverter = Or( + Assign(CssKeywords.Normal, CssKeywords.Normal), + PercentConverter); + + /// + /// Represents a converter for the font-feature-settings property. + /// + public static readonly IValueConverter FontFeatureSettingsConverter = Or( + Assign(CssKeywords.Normal, CssKeywords.Normal), + WithOrder( + StringConverter, + Or( + NaturalIntegerConverter, + Assign(CssKeywords.On, CssKeywords.On), + Assign(CssKeywords.Off, CssKeywords.Off))) + .FromList()); + /// /// Represents a converter for the font-kerning property. /// @@ -1152,7 +1188,12 @@ static class ValueConverters /// /// Represents a converter for the font-variation-settings property. /// - public static readonly IValueConverter FontVariationSettingsConverter = Assign(CssKeywords.Normal, CssKeywords.Normal); + public static readonly IValueConverter FontVariationSettingsConverter = Or( + Assign(CssKeywords.Normal, CssKeywords.Normal), + WithOrder( + StringConverter, + NumberConverter) + .FromList()); /// /// Represents a converter for the ResizeMode enumeration. @@ -1703,6 +1744,20 @@ private static IValueConverter FromParser(Func converter) private static IValueConverter FromParser(Func converter) where T : class, ICssValue => new ClassValueConverter(converter); + private static CssPercentageValue? ParseOnlyPercent(this StringSource source) + { + var pos = source.Index; + var result = source.ParsePercentOrNumber(); + + if (result?.Type == CssPercentageValue.Unit.Percent) + { + return result; + } + + source.BackTo(pos); + return null; + } + private static Func FromString(Func converter) => source => { var result = converter.Invoke(source); From 5023f7ff36095be3595fdf98c3145ca71dc32ca7 Mon Sep 17 00:00:00 2001 From: "Casteran, Sebastien" <23136293+scasteran-jw@users.noreply.github.com> Date: Wed, 19 Aug 2026 13:38:44 -0400 Subject: [PATCH 5/6] Fix oklab/oklch lightness misparsed as percentage when given as a number --- .../Functions/CssColorFunction.cs | 44 +++++++++++++++++++ .../Parser/Micro/ColorParser.cs | 8 ++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/AngleSharp.Css.Tests/Functions/CssColorFunction.cs b/src/AngleSharp.Css.Tests/Functions/CssColorFunction.cs index cf36e11b..e74d060d 100644 --- a/src/AngleSharp.Css.Tests/Functions/CssColorFunction.cs +++ b/src/AngleSharp.Css.Tests/Functions/CssColorFunction.cs @@ -75,6 +75,28 @@ public void ParseOklabToRgb_Second() Assert.AreEqual("rgba(198, 93, 7, 1)", color); } + [Test] + public void ParseOklabNumberToRgb_First() + { + var html = @"

Text

"; + 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 = @"

Text

"; + 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() { @@ -108,6 +130,28 @@ public void ParseOklchToRgb_Second() Assert.AreEqual("rgba(198, 93, 7, 1)", color); } + [Test] + public void ParseOklchNumberToRgb_First() + { + var html = @"

Text

"; + 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 = @"

Text

"; + 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() { diff --git a/src/AngleSharp.Css/Parser/Micro/ColorParser.cs b/src/AngleSharp.Css/Parser/Micro/ColorParser.cs index 033accc3..bc5bd43a 100644 --- a/src/AngleSharp.Css/Parser/Micro/ColorParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/ColorParser.cs @@ -292,7 +292,7 @@ static class ColorParser private static CssColorValue? ParseOklab(StringSource source) { - var l = ParseLabComponent(source); + var l = ParseLabComponent(source, numberScale: 100.0); source.SkipSpacesAndComments(); var a = ParseLabComponent(source); source.SkipSpacesAndComments(); @@ -324,7 +324,7 @@ static class ColorParser private static CssColorValue? ParseOklch(StringSource source) { - var l = ParseLabComponent(source); + var l = ParseLabComponent(source, numberScale: 100.0); source.SkipSpacesAndComments(); var c = ParseLabComponent(source); source.SkipSpacesAndComments(); @@ -385,7 +385,7 @@ static class ColorParser return null; } - private static Double? ParseLabComponent(StringSource source) + private static Double? ParseLabComponent(StringSource source, Double numberScale = 1.0) { var pos = source.Index; var unit = source.ParseUnit(); @@ -404,7 +404,7 @@ static class ColorParser if ((unit.Dimension == String.Empty || unit.Dimension == "%") && Double.TryParse(unit.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) { - return value; + return unit.Dimension == String.Empty ? value * numberScale : value; } return null; From 7a5da5bdfdefe0faae00a9ddb62cceb39ee8d571 Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Wed, 19 Aug 2026 21:50:01 +0200 Subject: [PATCH 6/6] Changed version --- CHANGELOG.md | 6 ++++++ CONTRIBUTORS.md | 1 + src/AngleSharp.Css.Docs/package.json | 2 +- src/Directory.Build.props | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed966e3e..465f5936 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index f62e6cee..6c9b8b2d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -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: diff --git a/src/AngleSharp.Css.Docs/package.json b/src/AngleSharp.Css.Docs/package.json index 2ba7b5bf..df838e5a 100644 --- a/src/AngleSharp.Css.Docs/package.json +++ b/src/AngleSharp.Css.Docs/package.json @@ -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": [ diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 929f1545..37fcde26 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ Extends the CSSOM from the core AngleSharp library. AngleSharp.Css - 1.0.0 + 1.0.2 enable latest true