Skip to content

Render context shadows in the raster backends#168

Open
DTW-Thalion wants to merge 4 commits into
gnustep:masterfrom
DTW-Thalion:fix/cairo-shadow
Open

Render context shadows in the raster backends#168
DTW-Thalion wants to merge 4 commits into
gnustep:masterfrom
DTW-Thalion:fix/cairo-shadow

Conversation

@DTW-Thalion

@DTW-Thalion DTW-Thalion commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

The backends ignored the shadow set through NSGraphicsContext, so [shadow set] followed by a fill or stroke drew nothing extra. This adds offset-shadow rendering to all four raster backends: cairo, art, xlib and winlib.

The shadow state is stored on the shared GSGState (-setShadow:/-shadow, released and copied with the state), and GSContext forwards -setShadow:/-shadow to it. Because the state lives on the graphics state, saving and restoring the graphics state carries the shadow with it, and no libs-gui change is needed (the gui already calls [ctxt setShadow:]).

Each backend paints the shadow before the real path: the current path, offset by the shadow offset, in the shadow colour. This covers the fill, even-odd fill and stroke operators. cairo draws the offset path directly on its context; the other backends render an offset copy of the path in the shadow colour, restoring the fill/stroke colour afterwards. Blur is not applied.

Tests/cairo/dropshadow.m draws a shape with a coloured shadow at a known offset through the AppKit offscreen path and checks the offset region for the shadow colour, that the shape sits on top of its own shadow, and that restoring the graphics state clears the shadow. It runs on cairo and art (8 assertions each), whose offscreen drawing reads back reliably in a headless run. The xlib and winlib backends render shadows the same way; xlib was verified locally against a live X server (the headless xlib offscreen path is not reliable enough to assert on in CI) and winlib was not built here.

Addresses gnustep/libs-gui#217

Store the current shadow on the shared GSGState, so that it is saved and
restored with the graphics state, and forward -setShadow: and -shadow
from GSContext to it.  The cairo backend paints the shadow by filling or
stroking the current path offset by the shadow offset, in the shadow
colour, before the path itself is drawn.  This covers the fill, even-odd
fill and stroke operators.  Blur is not applied.

Holding the shadow state on GSGState lets other backends render shadows
without further plumbing.

Addresses gnustep/libs-gui#217
@DTW-Thalion
DTW-Thalion requested a review from fredkiefer as a code owner July 21, 2026 00:02
Add the same offset shadow rendering to the remaining raster backends.
Each paints the current path offset by the shadow offset, in the shadow
colour, before the path itself, covering the fill, even-odd fill and
stroke operators.  The shadow parameters are read from the shared
GSGState, so saving and restoring the graphics state carries the shadow
with it.

Extend Tests/cairo/dropshadow.m to run on these backends as well.
@DTW-Thalion DTW-Thalion changed the title Render context shadows in the cairo backend Render context shadows in the raster backends Jul 21, 2026
The xlib offscreen path cannot be read back reliably in the headless CI
run and aborts there, so restrict dropshadow.m to the cairo and art
backends.  The xlib and winlib shadow rendering is unchanged.
The test skipped unless DISPLAY was set, so it did not exercise the shadow
path in a Wayland-only session even though the shadows render there through
the cairo graphics backend.  Accept WAYLAND_DISPLAY as well.
@DTW-Thalion

Copy link
Copy Markdown
Contributor Author

Confirmed this covers the Wayland server. The shadow is rendered in the cairo
graphics backend (CairoGState -_drawShadowForOperation:, which only touches the
cairo context), and the Wayland backend has no graphics gstate of its own, so a
wayland+cairo build draws shadows through the same code as x11+cairo.

Verified with the wayland+cairo backend loaded (GSCurrentServer is
WaylandServer) against a live compositor: dropshadow.m runs and all 8 checks
pass.

The last commit broadens the test's guard to accept WAYLAND_DISPLAY, since it
previously skipped whenever DISPLAY was unset and so did not run in a
Wayland-only session.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant