Skip to content

Update the CSS engine to ExCSS master - #287

Merged
eXpl0it3r merged 1 commit into
masterfrom
excss-sync
Nov 13, 2026
Merged

eXpl0it3r merged 1 commit into
masterfrom
excss-sync

Conversation

@eXpl0it3r

@eXpl0it3r eXpl0it3r commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Core/CssEngine was PeachPDF 0.9.6's copy of ExCSS. ExCSS itself has received 118 commits since 4.3.2 in the mean time, a good chunk of them upstreamed from PeachPDF, so this replaces the copy with ExCSS master (589afaf), kept internal and in our namespace. On top of it, only the hooks the renderer needs (Stylesheet.IsUserAgent, Length.ToPixels, the media feature helpers, CalcEvaluator, CodepointRange) and a few fixes ExCSS doesn't have stay local:

  • Escaped surrogates become U+FFFD
  • An alignment prefix like safe without a keyword no longer throws
  • transition: none is accepted
  • list-style-type: hebrew is accepted
  • text-shadow takes inset and a non-negative spread distance (Text Decoration 4)
  • perspective-origin: top 2px is rejected, only keyword pairs may be swapped
  • Math functions are accepted in grid track sizes and basic shapes
  • font-palette accepts palette-mix()

The last four are things the old copy got right and ExCSS master doesn't. They're good candidates to upstream to ExCSS, together with the first four.

ExCSS represents a few things differently, so the renderer is adapted:

  • The flow-relative properties (margins, padding, inset, borders, radii, float: inline-start) are properties of their own and get mapped to the physical sides, LTR as before
  • white-space and text-align are shorthands now, their longhands are combined back, and text-align-all and font-variant-caps map to the properties the box knows
  • :first-child and friends come in as plain pseudo-classes and are matched by the renderer, and the s flag of attribute selectors compares case-sensitively
  • A declaration with var() is applied after the cascade, and a later declaration only overrode it under the exact same name. With the flow-relative properties and the new shorthands that broke, e.g. margin-inline-start: var(--m) won over a later margin-left. A later declaration now wins for every longhand it sets, which also fixes margin: var(--m) overriding a later margin-left on master

ExCSS also accepts values that broke the rendering, because the old copy simply rejected them:

  • display: flow-root and display: contents collapsed the box to 0×0. flow-root lays out as block now, contents stays ignored as before.
  • rgb(calc(…)), relative colors and light-dark() ended up transparent. Colors the renderer's own parsers don't know are resolved through the engine, and light-dark() gives its light color, as long as color-scheme is ignored.
  • overflow: clip was treated as a scroll container and a new formatting context.

The spec manifests are updated accordingly. Over 100 gap tests pass now and lost their [Ignore], a few more were split into the part that passes and the part that doesn't, and the recorded gaps went from 1'558 to 1'445. The four PeachPDF-only grammars that only tests used are gone, their tests now check what the parser accepts and rejects.

How to test this PR?

CI should succeed. Additionally:

  • Every remaining gap test still fails without its [Ignore]
  • The spec compliance check (dotnet run --project Source/Tools/HtmlRenderer.SpecCompliance -c Release -- check) reports no problems
  • The image regression baselines are unchanged

- Vendor ExCSS 589afaf instead of PeachPDF 0.9.6's copy, keep only the
  renderer's hooks and the fixes ExCSS doesn't have yet: escaped
  surrogates, alignment prefixes without a keyword, transition: none and
  list-style-type: hebrew
- Fix the parsing ExCSS got wrong compared to the old copy: inset and
  spread in text-shadow, "top 2px" in perspective-origin, math functions
  in grid tracks and basic shapes, and palette-mix() in font-palette
- Map the flow-relative longhands, white-space-collapse, text-wrap-mode,
  text-align-all and font-variant-caps in the renderer, ExCSS keeps them
  as properties of their own
- Let a later declaration of the same longhand win over a deferred var()
  declaration, also through a shorthand or a flow-relative name
- Match :first-child and friends, which ExCSS creates as plain
  pseudo-classes, and honor the s flag of attribute selectors
- Resolve the colors the renderer's own parsers don't know through the
  engine, and light-dark() as its light color
- Lay out display: flow-root as block and keep ignoring display:
  contents, ExCSS accepts both now
- Don't treat overflow: clip as a scroll container or a formatting context
- Close the gaps ExCSS fixed and record the ones it introduced
@eXpl0it3r
eXpl0it3r merged commit e761e39 into master Nov 13, 2026
18 checks passed
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.

1 participant