Implement spec-oriented paint phases for normal flow, floats, and
positioned descendants, including atomic stacking groups and applicable
z-index handling for flex and grid items.
Share cached ordering between drawing and hit testing, propagate
invalidation across stacking scopes, and preserve ancestor transforms
and clipping for promoted descendants.
Add regression coverage for paint order, hit testing, reparenting,
clipping, flex ordering, and the old Reddit layout. Document the
supported stacking model and remove the completed implementation plan.
Introduce contiguous PropertyId and ShorthandId enums backed by name-to-ID maps, replacing hash-valued property identifiers throughout the style system.
Replace PropertyIdSet's unordered set with a fixed-size bitset, eliminating heap allocations during style state changes while providing deterministic ascending iteration.
Keep name hashes for stylesheet storage and unknown or custom properties, while using dense typed IDs for registered-property lookup and dispatch.
Preserve property aliases and the public Checked compatibility identifier.
Add transaction-end reconciliation for interdependent image sizing properties, deferring percentage-based calculations until their containing block is resolved.
Keep formatting-context-owned sizes intact for table cells and inline content.
Add validation and tests for dense built-in registration, custom IDs, aliases, bitset operations, allocation-free storage, unknown properties, property application order, and image sizing regressions.
Snap dotted and dashed border geometry to physical pixels so centered
patterns cannot begin on ambiguous half-pixel coordinates. This makes
rectangular dot placement deterministic across llvmpipe and macOS GPU
drivers.
Update the old Reddit golden image to use the snapped dot phase.
Optimize compile-time header dependencies
- Extract lightweight singleton declarations.
- Remove unnecessary core.hpp, stream, pack, and container includes.
- Add explicit direct dependencies across system, network, graphics, and syntax headers.
- Forward-declare stream and pack types where possible.
- Normalize selected network headers to LF.
- implement per-side CSS border styles and patterned rendering
- preserve rectangular rasterization for thin dotted borders
- separate CSS and native-widget border defaults
- correct inline atomic-box and vertical-align baseline layout
- collapse adjacent block margins in rich-text flow
- preserve forced line breaks and formatting margins
- size HTML text inputs from font ascent and descent
- expose replaced-control text baselines to inline layout
- enforce single-owner painting for inline backgrounds and borders
- enable and update the old Reddit visual regression golden
- add generic layout, border, and form-control regression tests
- document spec-first HTML work and project build conventions
The float-aware rich-text layout was resetting curX for every block formatting context, including inline-blocks. That made consecutive action buttons overlap. The reset is now limited to block-level BFCs;
inline-blocks retain normal cursor advancement.
Added:
- An always-on deferred CSS regression covering inline anchors, inline-block siblings, and float exclusions.
- Strict action-row and title/domain ordering checks in the opt-in Reddit visual smoke test.
- A small CSS fixture reproducing Reddit’s layout rules.
Track percentage-based margins in UIHTMLWidget and re-resolve them when
the containing parent acquires its final size. This prevents margins
computed against an initial zero-width container from remaining stale
until the viewport is resized.
Add regressions covering percentage margin recomputation and the SS64
footer layout after a deferred stylesheet is loaded.
Reserve stylesheet source positions when link elements are encountered
instead of assigning them when asynchronous loading completes.
Track the stylesheet and rule ordinals explicitly so deferred external
styles retain their document order relative to later inline styles.
Preserve this ordering when extracting marked stylesheet rules.
Add an SS64 regression test covering heading and button styles after a
deferred stylesheet finishes loading.
Resolve percentage dimensions for HTML-backed widgets after their containing
block has a definite size. Apply the resolution during rich-text, flex, and
grid layout without triggering broad layout invalidation.
Rasterize SVGs using their resolved dimensions when intrinsic width or height
attributes are missing or percentage-based.
Add regression coverage for percentage-sized SVGs and the SS64 navigation
buttons and search icon.
Treat UIHTMLInput as the stable HTML/CSS host while keeping its native
control as an anonymous implementation detail.
Prevent author CSS from matching internal control widgets, avoiding
duplicated backgrounds and other styles. Keep valid input types such as
email intact for attribute selectors while falling back to compatible
native implementations.
Synchronize the internal control with the host content box after layout,
including flex sizing, and preserve intrinsic measurement for size and
font changes. Preserve input state across dynamic type changes and make
hidden transitions reversible.
Also fix border-radius calculations and add regressions for CSS ownership,
anonymous widget isolation, dynamic type changes, input sizing, and the
Causality newsletter form.
- parse slash-separated background position and size without requiring whitespace
- recognize custom-property background images following an explicit size
- resolve root body and html minimum heights against the layout viewport
- avoid expanding semantic document boxes to the scrollable content extent
- handle auto margins and percentage offsets correctly for absolute positioning
- add DuckDuckGo layout and background shorthand regression tests
- document the Xvfb limitation with multisampled OpenGL contexts
Resolve CSS variable values at the points where properties are applied,
inherited, propagated, and used to seed transitions. This prevents raw
var() values from leaking into widget state or transition start/end
values when style attributes, inherited properties, or state-dependent
rules are involved.
Also refresh style definitions when widgets load so selector-dependent
rules are evaluated with the final widget context, and add regression
coverage for inline style vars, inherited vars, parent-state child rules,
tab close opacity, and foreground tint transitions.
Preserve shrink-to-fit widths for floated HTML boxes whose CSS width is
auto, instead of letting their own block layout expand them back to the
containing block width. This matches the CSS float model and fixes floated
list-item menus where hover boxes were much wider than their content.
Also separate RichText visual font metrics from CSS line-box metrics. Text
runs now keep native font height for drawing while line layout still uses
CSS line-height, allowing the expected half-leading offset for vertically
centered text in block anchors.
Core problem
Three interconnected bugs caused incorrect rendering of display: inline-block elements:
1. Inline-block elements with explicit width/height (e.g. share buttons with width: 20px; height: 20px) had zero dimensions — their internal children were never laid out.
2. Text-based inline-blocks (e.g. <a href="...">Download</a> styled as display: inline-block) were affected by an incorrect font shorthand that failed to reset line-height to normal, allowing an inherited line-height: 10 on <body> to inflate element heights via the line box strut.
3. Inline-block elements with long text did not wrap atomically to the next line when they exceeded the container width.
Changes by file
include/eepp/graphics/richtext.hpp / src/eepp/graphics/richtext.cpp
- Added lineHeight (Float) and isAtomic (bool) fields to SpanBlock. The lineHeight field provides per-span line-height overriding the global mLineHeight — this is used by inline-block spans to set their line height to the font's em height rather than inheriting the container's line-height. The isAtomic flag marks a span as an atomic inline-level box: during RichText Pass 1 (both the non-float and float-aware paths), atomic spans check whether the full text width plus margins fits on the current line; if not, they push to the next line before any text wrapping occurs. This matches the CSS behavior where an inline-block drops to a new line as an opaque unit rather than having its text wrap within the parent line.
- Updated addSpan() to accept lineHeight and isAtomic parameters (with defaults of 0 and false to preserve existing call sites).
- Added per-span line-height priority (3-tier cascade): a span's own lineHeight takes precedence over the global mLineHeight, which takes precedence over the font's intrinsic line spacing. This ensures inline-block spans use font em height (set by UIRichText::rebuildRichText), pure inline text uses the container's line-height, and all fall back to font metrics.
- Added line box strut in Pass 2 (both paths): line.height = std::max(line.height, mLineHeight) ensures every line box respects the containing block's line-height, per CSS 2.1 §10.8.1. Previously a line box's height was determined solely by the tallest inline element on that line; now the container's line-height acts as a minimum.
- Propagated pText->lineHeight and pText->isAtomic through to the SpanBlock construction in RenderSpan creation in both the non-float and float-aware paths, so the per-span values persist from addSpan() through to layout.
include/eepp/ui/uitextspan.hpp / src/eepp/ui/uitextspan.cpp
- isMergeable() now returns true for both CSSDisplay::Inline and CSSDisplay::InlineBlock. Previously only Inline was mergeable. This allows text-based inline-block content to participate in the parent container's RichText flow (baseline-aligned), while the widget still draws its own border and background independently.
- Added isInlineBlock() helper method that returns mDisplay == CSSDisplay::InlineBlock, used by callers throughout the layouter and RichText code to distinguish inline-block treatment from pure inline.
- Fixed drawBorder(): inline-block widgets now draw their border at the widget bounds (mScreenPos, mSize) without expanding by padding, because padding has already been incorporated into the widget's size via the RichText bounds expansion. Pure inline spans continue to draw the border expanded by padding (the old behavior).
src/eepp/ui/uirichtext.cpp
- In rebuildRichText(), inline-block widgets are now handled via two distinct code paths:
- Text-based inline-block (has its own text content): treated as mergeable — text flows into the parent container's RichText as a SpanBlock with isAtomic = true for atomic line-wrapping, and the span's lineHeight set to the font's em height. Padding and margins are propagated through the SpanBlock. The widget's size and hit-boxes are computed from the parent RichText's layout (via BlockLayouter::positionRichTextChildren bounds expansion).
- Inline-block without text (has only child widgets, e.g. share buttons containing nested <span> elements): diverted to the CustomBlock path — the widget runs its own BlockLayouter::updateLayout() to process internal children and determine its intrinsic size, and is added to the parent's RichText as a CustomBlock (atomic inline-level box). This ensures inner children are properly laid out rather than left at zero dimensions.
- Whitespace collapse logic around inline-block spans now accounts for whether the adjacent nodes are inline, preventing incorrect stripping of leading/trailing spaces.
src/eepp/ui/blocklayouter.cpp
- BlockLayouter::updateLayout(): the early return for mergeable widgets now makes an exception for inline-block widgets that have no text of their own — these still need their internal children laid out, so the layouter proceeds rather than returning early.
- BlockLayouter::positionRichTextChildren(): inline-block widgets are processed in two ways depending on whether they have text:
- Text-based: bounds computed from the parent RichText's span positions, expanded by padding. The widget's position and size are set from these bounds.
- No-text (CustomBlock): positioned via getNextCustomSpan() from the parent RichText's CustomBlock spans, with padding expansion for bounds.
Both paths expand the widget's pixel bounds by padding for inline-block styling. A bool handled flag replaces the previous goto-based control flow.
- Added bounds and hit-box padding expansion for inline-block widgets in the CustomBlock positioning path (previously only the mergeable path had this).
src/eepp/ui/css/stylesheetspecification.cpp
- Fixed font shorthand registration: changed the component list from { font-style, font-size, line-spacing, font-family } to { font-style, font-weight, font-size, line-height, font-family }. The line-spacing property does not participate in the font shorthand per the CSS Fonts specification (§3.1); line-height does.
- Fixed font shorthand parser:
- font-weight keywords (bold, bolder, lighter) are now emitted as a separate font-weight property instead of being incorrectly merged into the font-style property string.
- When the font shorthand does not include an explicit /line-height value, the parser now always emits line-height: normal to reset any previously inherited line-height. Previously it emitted nothing, allowing an inherited line-height (e.g. line-height: 10 on <body>) to leak into child elements that used the font shorthand.
src/tests/unit_tests/uihtml_tests.cpp
- Added UIRichText.anchorPadding test: verifies that an inline-block <a> tag with padding and border renders correctly at the expected size (81×28 px for "Download" text in 11px Noto Sans with padding: 6px 14px and border: 1px solid), using both pixel-diff comparison and explicit size assertions. The HTML uses line-height: 1.5 on <body> and font: 11px on the inline-block anchor.
- Added UIRichText.anchorPaddingLineHeight test: same layout but with line-height: 10 on <body>, verifying that the per-span line-height prevents the inherited line-height: 10 from inflating the inline-block anchor's dimensions.
- Added UIBackground.InlineBlockImageSpans test: verifies that inline-block <a> elements with fixed width/height (20×20 px share buttons) and nested child <span> elements (with display: block and text-indent: -9999px) have non-zero dimensions and render correctly with pixel-diff comparison. This covers the CustomBlock path for inline-blocks without text of their own.
Fixes in selector rule parser.
Stub implementation for attributes filter in CSS.
Fixes in `background-position` and `background` shorthand parsers.
Added a few HTML elements tags.
Added body default left and right margin.