Skip to content

Commit d89cc4a

Browse files
jhaygood86eXpl0it3r
authored andcommitted
CSS engine: sync @layer and the user-preference media features
From PeachPDF v0.9.6 (upstream cc1a521, 607bcce). - LayerRule / LayerStatementRule / ILayerRule: both @layer forms - the block form that groups rules and the statement form that only declares an order - Seven new media features: prefers-color-scheme, prefers-reduced-motion, prefers-contrast, prefers-reduced-transparency, update, any-hover, any-pointer - MediaFeature gains a Comparison property (derived from the min-/max- prefix or the range-syntax operator) plus AsLength/AsResolution/AsRatio accessors, which is what a media-query matcher needs to actually evaluate a feature Layer *ordering* is not implemented - see the separate CssData change that indexes a layer's contents as unlayered rules.
1 parent 50181ec commit d89cc4a

12 files changed

Lines changed: 241 additions & 0 deletions

‎Source/HtmlRenderer/Core/CssEngine/Factories/MediaFeatureFactory.cs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ internal sealed class MediaFeatureFactory
6363
{FeatureNames.Hover, () => new HoverMediaFeature()},
6464
{FeatureNames.InlineSize, () => new SizeMediaFeature(FeatureNames.InlineSize)},
6565
{FeatureNames.BlockSize, () => new SizeMediaFeature(FeatureNames.BlockSize)},
66+
{FeatureNames.PrefersColorScheme, () => new PrefersColorSchemeMediaFeature()},
67+
{FeatureNames.PrefersReducedMotion, () => new PrefersReducedMotionMediaFeature()},
68+
{FeatureNames.PrefersContrast, () => new PrefersContrastMediaFeature()},
69+
{FeatureNames.PrefersReducedTransparency, () => new PrefersReducedTransparencyMediaFeature()},
70+
{FeatureNames.Update, () => new UpdateMediaFeature()},
71+
{FeatureNames.AnyHover, () => new AnyHoverMediaFeature()},
72+
{FeatureNames.AnyPointer, () => new AnyPointerMediaFeature()},
6673
};
6774

6875
#endregion
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace TheArtOfDev.HtmlRenderer.Core.CssEngine
2+
{
3+
/// <summary>
4+
/// The <c>any-hover</c> media feature (Media Queries 4). Matched against the renderer's fixed
5+
/// <c>none</c> stance (a static PDF has no hover-capable input) in <c>MediaQueryMatcher</c>.
6+
/// </summary>
7+
internal sealed class AnyHoverMediaFeature : MediaFeature
8+
{
9+
public AnyHoverMediaFeature() : base(FeatureNames.AnyHover)
10+
{
11+
}
12+
13+
internal override IValueConverter Converter => Converters.Any;
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace TheArtOfDev.HtmlRenderer.Core.CssEngine
2+
{
3+
/// <summary>
4+
/// The <c>any-pointer</c> media feature (Media Queries 4). Matched against the renderer's fixed
5+
/// <c>none</c> stance (a static PDF has no pointing device) in <c>MediaQueryMatcher</c>.
6+
/// </summary>
7+
internal sealed class AnyPointerMediaFeature : MediaFeature
8+
{
9+
public AnyPointerMediaFeature() : base(FeatureNames.AnyPointer)
10+
{
11+
}
12+
13+
internal override IValueConverter Converter => Converters.Any;
14+
}
15+
}

‎Source/HtmlRenderer/Core/CssEngine/MediaFeatures/MediaFeature.cs‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
11
using System.IO;
2+
using System.Linq;
23

34
namespace TheArtOfDev.HtmlRenderer.Core.CssEngine
45
{
6+
/// <summary>
7+
/// How a media feature's stored value constrains the device value, unifying the <c>min-</c>/<c>max-</c>
8+
/// name-prefix forms with the Media Queries 4 range operators (<c>&gt;=</c>/<c>&lt;=</c>/<c>&gt;</c>/<c>&lt;</c>).
9+
/// </summary>
10+
internal enum MediaFeatureComparison
11+
{
12+
/// <summary>Exact match (the <c>feature: value</c> form, or a boolean feature).</summary>
13+
Exact,
14+
/// <summary>Device value must be ≥ the feature value (<c>min-*</c> or <c>&gt;=</c>).</summary>
15+
Minimum,
16+
/// <summary>Device value must be ≤ the feature value (<c>max-*</c> or <c>&lt;=</c>).</summary>
17+
Maximum,
18+
/// <summary>Device value must be strictly &gt; the feature value.</summary>
19+
GreaterThan,
20+
/// <summary>Device value must be strictly &lt; the feature value.</summary>
21+
LessThan
22+
}
23+
524
internal abstract class MediaFeature : StylesheetNode, IMediaFeature
625
{
726
private TokenValue _tokenValue;
@@ -26,6 +45,49 @@ internal MediaFeature(string name)
2645

2746
public bool HasValue => _tokenValue != null && _tokenValue.Count > 0;
2847

48+
/// <summary>
49+
/// The comparison the stored value imposes on the device value, derived from the <c>min-</c>/
50+
/// <c>max-</c> name prefix or, for the range syntax (<c>width &gt;= 48rem</c>), the parsed operator.
51+
/// </summary>
52+
internal MediaFeatureComparison Comparison
53+
{
54+
get
55+
{
56+
if (IsMinimum) return MediaFeatureComparison.Minimum;
57+
if (IsMaximum) return MediaFeatureComparison.Maximum;
58+
59+
switch (_constraintDelimiter)
60+
{
61+
case TokenType.GreaterThanOrEqual: return MediaFeatureComparison.Minimum;
62+
case TokenType.GreaterThan: return MediaFeatureComparison.GreaterThan;
63+
case TokenType.LessThanOrEqual: return MediaFeatureComparison.Maximum;
64+
case TokenType.LessThan: return MediaFeatureComparison.LessThan;
65+
default: return MediaFeatureComparison.Exact;
66+
}
67+
}
68+
}
69+
70+
/// <summary>The stored value parsed as a <see cref="Length"/>, or <c>null</c> if absent/not a length.</summary>
71+
internal Length? AsLength() => HasValue ? _tokenValue.ToLength() : null;
72+
73+
/// <summary>The stored value parsed as a <see cref="Resolution"/>, or <c>null</c> if absent/not a resolution.</summary>
74+
internal Resolution? AsResolution() => HasValue ? _tokenValue.ToResolution() : null;
75+
76+
/// <summary>
77+
/// The stored value parsed as a numeric ratio (<c>&lt;number&gt; [ / &lt;number&gt; ]?</c>), e.g.
78+
/// <c>16/9</c>, or <c>null</c> if absent/unparseable.
79+
/// </summary>
80+
internal double? AsRatio()
81+
{
82+
if (!HasValue) return null;
83+
84+
var numbers = _tokenValue.OfType<NumberToken>().Select(token => (double)token.Value).ToList();
85+
86+
if (numbers.Count >= 2) return numbers[1] != 0 ? numbers[0] / numbers[1] : (double?)null;
87+
if (numbers.Count == 1) return numbers[0] > 0 ? numbers[0] : (double?)null;
88+
return null;
89+
}
90+
2991
public override void ToCss(TextWriter writer, IStyleFormatter formatter)
3092
{
3193
var constraintDelimiter = GetConstraintDelimiter();
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace TheArtOfDev.HtmlRenderer.Core.CssEngine
2+
{
3+
/// <summary>
4+
/// The <c>prefers-color-scheme</c> media feature (Media Queries 5). The value is validated as any
5+
/// identifier here; the actual <c>light</c>/<c>dark</c> match against the configured scheme happens
6+
/// in <c>MediaQueryMatcher</c>.
7+
/// </summary>
8+
internal sealed class PrefersColorSchemeMediaFeature : MediaFeature
9+
{
10+
public PrefersColorSchemeMediaFeature() : base(FeatureNames.PrefersColorScheme)
11+
{
12+
}
13+
14+
internal override IValueConverter Converter => Converters.Any;
15+
}
16+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace TheArtOfDev.HtmlRenderer.Core.CssEngine
2+
{
3+
/// <summary>
4+
/// The <c>prefers-contrast</c> media feature (Media Queries 5). Matched against the renderer's fixed
5+
/// <c>no-preference</c> stance in <c>MediaQueryMatcher</c>.
6+
/// </summary>
7+
internal sealed class PrefersContrastMediaFeature : MediaFeature
8+
{
9+
public PrefersContrastMediaFeature() : base(FeatureNames.PrefersContrast)
10+
{
11+
}
12+
13+
internal override IValueConverter Converter => Converters.Any;
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace TheArtOfDev.HtmlRenderer.Core.CssEngine
2+
{
3+
/// <summary>
4+
/// The <c>prefers-reduced-motion</c> media feature (Media Queries 5). Matched against the renderer's
5+
/// fixed <c>reduce</c> stance (a static PDF has no motion) in <c>MediaQueryMatcher</c>.
6+
/// </summary>
7+
internal sealed class PrefersReducedMotionMediaFeature : MediaFeature
8+
{
9+
public PrefersReducedMotionMediaFeature() : base(FeatureNames.PrefersReducedMotion)
10+
{
11+
}
12+
13+
internal override IValueConverter Converter => Converters.Any;
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace TheArtOfDev.HtmlRenderer.Core.CssEngine
2+
{
3+
/// <summary>
4+
/// The <c>prefers-reduced-transparency</c> media feature (Media Queries 5). Matched against the
5+
/// renderer's fixed <c>no-preference</c> stance in <c>MediaQueryMatcher</c>.
6+
/// </summary>
7+
internal sealed class PrefersReducedTransparencyMediaFeature : MediaFeature
8+
{
9+
public PrefersReducedTransparencyMediaFeature() : base(FeatureNames.PrefersReducedTransparency)
10+
{
11+
}
12+
13+
internal override IValueConverter Converter => Converters.Any;
14+
}
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace TheArtOfDev.HtmlRenderer.Core.CssEngine
2+
{
3+
/// <summary>
4+
/// The <c>update</c> media feature (Media Queries 4): how frequently the output device can modify the
5+
/// appearance of content. A printed/exported PDF cannot update once produced, so it is matched against
6+
/// <c>none</c> in <c>MediaQueryMatcher</c>.
7+
/// </summary>
8+
internal sealed class UpdateMediaFeature : MediaFeature
9+
{
10+
public UpdateMediaFeature() : base(FeatureNames.Update)
11+
{
12+
}
13+
14+
internal override IValueConverter Converter => Converters.Any;
15+
}
16+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace TheArtOfDev.HtmlRenderer.Core.CssEngine
2+
{
3+
/// <summary>
4+
/// The block form of the <c>@layer</c> cascade-layer at-rule (<c>@layer name { … }</c>, or an
5+
/// anonymous <c>@layer { … }</c>). Groups the style rules that belong to a named cascade layer,
6+
/// per <see href="https://www.w3.org/TR/css-cascade-5/#layering">CSS Cascade 5</see>.
7+
/// </summary>
8+
internal interface ILayerRule : IGroupingRule
9+
{
10+
/// <summary>The layer's (optionally dotted) name; empty for an anonymous layer.</summary>
11+
string Name { get; }
12+
}
13+
}

0 commit comments

Comments
 (0)