This issue showed up as NSGradient not being draw correctly w.r.t. the clipping path. See
gnustep/libs-gui#228
Problem:
saveGraphicsState/restoreGraphicsState is not handling the clipping path correctly.
It seems that saveGraphicsState is actually clearing the clipping path
Inheritance Hierarchy (for reference)
CairoContext : GSContext : NSGraphicsContext
CairoGState : GSGState : NSObject
Why is saveGraphicsState clearing the clipping path? The call-chain is complex, but here is what is happening.
When saveGraphicsState is called, it works its way up the inheritance hierachy
CairoContext : GSContext : NSGraphicsContext
to NSGraphicsContext, since none of the intermediate classes have implemented it.
In NSGraphicsContext, the default implementation of saveGraphicsState trampolines to method DPSgsave, which is then defined as subclassResponsiblity.
The subclass method that handles it is [GSContext DPSgsave] which does this: the current state becomes a deep copy of the graphics context.
- (void) DPSgsave
{
ctxt_push(gstate, gstack);
AUTORELEASE(gstate);
gstate = [gstate copy];
}
So this is where the graphics state is actually pushed. And a "copy" of the current state becomes the current state. But the copy does not have the clipping path.
GSGState implements "deepen" which does a copyWithZone on a bunch of stuff, including the CairoGraphicsState.
The copyWithZone of CairoGState tries to copy the cairo context by making a new cairo
context and copying over its elements. However, it can't copy over the clipping path
because there is no clean way to access that from Cairo. So it is lost.
(Cairo really wants its context to be an opaque data structure, and
trying to copy one piecemeal doesn't seem to work.)
At this point, we have an "almost"-copy of the Cairo state, but not quite. The clipping
path is lost.
APPROACH to a SOLUTION:
Cairo implements a perfectly good save/restore mechanism on its own. And rather than
creating new cairo contexts and copying over the internals, we could use save/restore
directly. NSGraphicsContext requires the same stack ordering of save/restore, so the methods
should map directly.
In file CairoContext.m: add saveGraphicsState and restoreGraphicsState that use
the cairo methods. By not calling "super" in these methods the entire existing
copy-push/pop generic mechanism is avoided, as far as I can tell, and all of the
above description of the call-chain does not happen.
@implementation CairoContext
- (void) saveGraphicsState {
if (CGSTATE->_ct)
cairo_save(CGSTATE->_ct);
}
- (void) restoreGraphicsState {
if (CGSTATE->_ct)
cairo_restore(CGSTATE->_ct);
}
@end
This has been tested with Gworkspace, Terminal, TextEdit and the complex gradients of IconApp and seems to work. Do not know the assumptions made during the current implementation, however.

This issue showed up as NSGradient not being draw correctly w.r.t. the clipping path. See
gnustep/libs-gui#228
Problem:
saveGraphicsState/restoreGraphicsState is not handling the clipping path correctly.
It seems that saveGraphicsState is actually clearing the clipping path
Inheritance Hierarchy (for reference)
Why is saveGraphicsState clearing the clipping path? The call-chain is complex, but here is what is happening.
When saveGraphicsState is called, it works its way up the inheritance hierachy
to NSGraphicsContext, since none of the intermediate classes have implemented it.
In NSGraphicsContext, the default implementation of saveGraphicsState trampolines to method DPSgsave, which is then defined as subclassResponsiblity.
The subclass method that handles it is
[GSContext DPSgsave]which does this: the current state becomes a deep copy of the graphics context.So this is where the graphics state is actually pushed. And a "copy" of the current state becomes the current state. But the copy does not have the clipping path.
GSGState implements "deepen" which does a copyWithZone on a bunch of stuff, including the CairoGraphicsState.
The copyWithZone of CairoGState tries to copy the cairo context by making a new cairo
context and copying over its elements. However, it can't copy over the clipping path
because there is no clean way to access that from Cairo. So it is lost.
(Cairo really wants its context to be an opaque data structure, and
trying to copy one piecemeal doesn't seem to work.)
At this point, we have an "almost"-copy of the Cairo state, but not quite. The clipping
path is lost.
APPROACH to a SOLUTION:
Cairo implements a perfectly good save/restore mechanism on its own. And rather than
creating new cairo contexts and copying over the internals, we could use save/restore
directly. NSGraphicsContext requires the same stack ordering of save/restore, so the methods
should map directly.
In file CairoContext.m: add saveGraphicsState and restoreGraphicsState that use
the cairo methods. By not calling "super" in these methods the entire existing
copy-push/pop generic mechanism is avoided, as far as I can tell, and all of the
above description of the call-chain does not happen.
This has been tested with Gworkspace, Terminal, TextEdit and the complex gradients of IconApp and seems to work. Do not know the assumptions made during the current implementation, however.