Skip to content

Add a real, spec-compliant CSS3/4 engine (ported from ExCSS via PeachPDF)#244

Open
jhaygood86 wants to merge 4 commits into
ArthurHub:masterfrom
jhaygood86:feature/add-css-parser
Open

Add a real, spec-compliant CSS3/4 engine (ported from ExCSS via PeachPDF)#244
jhaygood86 wants to merge 4 commits into
ArthurHub:masterfrom
jhaygood86:feature/add-css-parser

Conversation

@jhaygood86

@jhaygood86 jhaygood86 commented Jul 17, 2026

Copy link
Copy Markdown

Summary

This replaces HTML-Renderer's ad-hoc CSS parsing/matching with a real CSS3/4 spec-compliant CSS engine — a proper cascade, a real selector engine (combinators, attribute selectors, structural pseudo-classes, :not()/:is()/:has(), ::before/::after), @media condition parsing, and calc() — in place of the old simple class-name-keyed block lookup.

The engine is derived from ExCSS, sourced from the more up-to-date fork maintained in PeachPDF, and ported into Source/HtmlRenderer/Core/CssEngine. Two of HTML-Renderer's own non-standard CSS extensions — corner-radius and background-gradient — depended on the old parser and have been replaced with their spec-compliant equivalents (border-radius, linear-gradient()) so the demo app keeps working against the new engine.

Details

  • CSS engine: Ported from ExCSS, but sourced from the more up-to-date version maintained by PeachPDF, rather than upstream ExCSS.
  • Minimal CSS fixes: Applied only what was needed to keep existing functionality working — e.g. replacing the corner-radius and background-gradient extensions with spec-compliant versions (see below).
  • Scope: This is a minimal port of the engine and the fixes needed to resolve the regressions it introduced. It is not a full CSS3/4 compliance pass — follow-up PRs will incrementally improve spec compliance on top of this engine.

What now works end-to-end

  • Real cascade: origin-aware (user-agent vs. author), specificity- and document-order-ranked, with a correct !important tier and the CSS-wide keywords initial / inherit / unset / revert / revert-layer.
  • Full selector engine: combinators (descendant, >, +, ~), comma-separated selector lists, attribute selectors ([attr], [attr=v], [attr~=v], [attr*=v], [attr^=v], [attr$=v], [attr|=v]), :not(), :is()/:matches(), :has() (descendant form), structural pseudo-classes :nth-child()/:nth-last-child()/:nth-of-type()/:nth-last-of-type() (including the CSS4 An+B of <selector> form) and :nth-column()/:nth-last-column(), :first-child/:last-child/:first-of-type/:last-of-type, :only-child/:only-of-type, :root, :link, and ::before/::after generated-content pseudo-elements.
  • @media conditions: parsed, indexed, and matched, including nesting and not/type conditions.
  • calc(): parsed and evaluated for property values.
  • content:: supports attr() and the open/close quote keywords.
  • Spec-compliant background-image: linear-gradient(...), replacing the old proprietary background-gradient extension.
  • Spec-compliant border-radius, with independent per-corner horizontal/vertical elliptical radii, replacing the old proprietary corner-radius extension.
  • Rule indexing: rules are bucketed by tag/class/id so cascade cost no longer scales as rules × boxes on large documents.

Known limitations (not yet wired up)

  • @media conditions are parsed and indexed, but this engine has no live "what device are we rendering for" concept, so cascading is always done against media all — non-all conditions (e.g. @media screen) never actually gate anything. This matches the old engine's pre-existing behavior; it's not a regression.
  • radial-gradient() parses but isn't painted yet — only linear-gradient() renders.
  • ::marker wasn't ported; list markers are still painted procedurally rather than as a generated box.
  • content: doesn't support counter() / counters() / string() / content().
  • :has() only supports the default descendant relative-selector form (no leading >, +, ~).

Related open issues

Confirmed by reading the relevant code paths (not just title-matching):

Not addressed by this PR (layout/painting gaps, not parsing/cascade): #203 (position: absolute), #34 (float), #42 (table border-conflict resolution), #104/#103 (<tr> display/background-color).

Port of CSS engine from PeachPDF
The Demo app depending on 2 HTML-Renderer extensions for background gradients and border radius.

This ports a minimal version of the CSS spec compliant versions from PeachPDF
Fixes <br>
@jhaygood86 jhaygood86 changed the title Feature/add css parser Add a real, spec-compliant CSS3/4 engine (ported from ExCSS via PeachPDF) Jul 17, 2026
@jhaygood86
jhaygood86 marked this pull request as ready for review July 17, 2026 01:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants