Skip to content

saveGraphicsState erases clipping path in Cairo #68

@sheffler

Description

@sheffler

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.

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions