diff --git a/Headers/opal/OpalSurface.h b/Headers/opal/OpalSurface.h index 4e574255..b6c129b9 100644 --- a/Headers/opal/OpalSurface.h +++ b/Headers/opal/OpalSurface.h @@ -42,6 +42,7 @@ - (CGContextRef) backingCGContext; - (CGContextRef) x11CGContext; +- (void) ensureX11Context; - (void) handleExposeRect: (NSRect)rect; - (BOOL) isDrawingToScreen; @end diff --git a/Source/opal/OpalContext.m b/Source/opal/OpalContext.m index 30a6b31b..02bc52b3 100644 --- a/Source/opal/OpalContext.m +++ b/Source/opal/OpalContext.m @@ -45,6 +45,7 @@ @implementation OpalContext + (void) initializeBackend { + NSLog(@"OpalContext: initializeBackend called"); [NSGraphicsContext setDefaultContextClass: self]; [GSFontEnumerator setDefaultClass: [OpalFontEnumerator class]]; @@ -63,16 +64,8 @@ - (BOOL) supportsDrawGState - (BOOL) isDrawingToScreen { -#warning isDrawingToScreen returning NO to fix DPSimage - return NO; - - // NOTE: This was returning NO because it was not looking at the - // return value of GSCurrentSurface. Now it returns YES, which - // seems to have broken image drawing (yellow rectangles are drawn instead) OpalSurface *surface; - [OGSTATE GSCurrentSurface: &surface : NULL : NULL]; - return [surface isDrawingToScreen]; } @@ -150,17 +143,15 @@ - (BOOL) isCompatibleBitmap: (NSBitmapImageRep*)bitmap return NO; } - // FIXME: Allow more image types as soon as the Opal backend handles them correctly colorSpaceName = [bitmap colorSpaceName]; - if (![colorSpaceName isEqualToString: NSDeviceRGBColorSpace] && - ![colorSpaceName isEqualToString: NSCalibratedRGBColorSpace]) - { - return NO; - } - else + if ([colorSpaceName isEqualToString: NSDeviceRGBColorSpace] || + [colorSpaceName isEqualToString: NSCalibratedRGBColorSpace] || + [colorSpaceName isEqualToString: NSDeviceWhiteColorSpace] || + [colorSpaceName isEqualToString: NSCalibratedWhiteColorSpace]) { return YES; } + return NO; } - (void) GSCurrentDevice: (void **)device : (int *)x : (int *)y diff --git a/Source/opal/OpalFontInfo.m b/Source/opal/OpalFontInfo.m index 0b904bf5..4971c765 100644 --- a/Source/opal/OpalFontInfo.m +++ b/Source/opal/OpalFontInfo.m @@ -118,16 +118,40 @@ - (BOOL) setupAttributes return NO; } - // We must not leave the hinting settings as their defaults, - // because if we did, that would mean using the surface defaults - // which might or might not use hinting (xlib does by default.) - // - // Since we make measurements outside of the context of a surface - // (-advancementForGlyph:), we need to ensure that the same - // hinting settings are used there as when we draw. For now, - // just force hinting to be off. - cairo_font_options_set_hint_metrics(options, CAIRO_HINT_METRICS_ON); - cairo_font_options_set_hint_style(options, CAIRO_HINT_STYLE_NONE); + { + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + cairo_hint_metrics_t metrics = CAIRO_HINT_METRICS_ON; + cairo_hint_style_t style = CAIRO_HINT_STYLE_NONE; + int hinting = [ud integerForKey: @"GSFontHinting"]; + cairo_antialias_t antialias = CAIRO_ANTIALIAS_DEFAULT; + if (hinting == 0) + { + float scaleFactor = [ud floatForKey: @"GSScaleFactor"]; + if (scaleFactor != 0.0 && scaleFactor != 1.0) + hinting = 33; + else + hinting = 17; + } + switch (hinting >> 4) + { + case 0: metrics = CAIRO_HINT_METRICS_DEFAULT; break; + case 1: metrics = CAIRO_HINT_METRICS_ON; break; + case 2: metrics = CAIRO_HINT_METRICS_OFF; break; + } + switch (hinting & 0x0f) + { + case 0: style = CAIRO_HINT_STYLE_DEFAULT; break; + case 1: style = CAIRO_HINT_STYLE_NONE; break; + case 2: style = CAIRO_HINT_STYLE_SLIGHT; break; + case 3: style = CAIRO_HINT_STYLE_MEDIUM; break; + case 4: style = CAIRO_HINT_STYLE_FULL; break; + } + cairo_font_options_set_hint_metrics(options, metrics); + cairo_font_options_set_hint_style(options, style); + if ([ud objectForKey: @"back_art_subpixel_text"]) + antialias = CAIRO_ANTIALIAS_SUBPIXEL; + cairo_font_options_set_antialias(options, antialias); + } _scaled = cairo_scaled_font_create(face, &font_matrix, &ctm, options); cairo_font_options_destroy(options); @@ -302,35 +326,39 @@ - (NSGlyph) glyphWithName: (NSString *) glyphName - (NSRect) boundingRectForGlyph: (NSGlyph)glyph { -#if 0 - cairo_text_extents_t ctext; - - if (_cairo_extents_for_NSGlyph(_scaled, glyph, &ctext)) + // Use glyph advance as approximation for bounding rect + CGGlyph cgGlyph = (CGGlyph)glyph; + int advance = 0; + if (_faceInfo && [_faceInfo fontFace]) { - return NSMakeRect(ctext.x_bearing, ctext.y_bearing, - ctext.width, ctext.height); + CGFontGetGlyphAdvances([_faceInfo fontFace], &cgGlyph, 1, &advance); + int unitsPerEm = CGFontGetUnitsPerEm([_faceInfo fontFace]); + if (unitsPerEm > 0) + { + CGFloat scale = matrix[0] / (CGFloat)unitsPerEm; + CGFloat w = advance * scale; + return NSMakeRect(0, descender, w, ascender - descender); + } } -#endif - return NSMakeRect(0,0,10,10); + return NSMakeRect(0, descender, matrix[0] * 0.6, ascender - descender); } - (CGFloat) widthOfString: (NSString *)string { -#if 0 - cairo_text_extents_t ctext; + if (!string || [string length] == 0) + return 0.0; - if (!string) + // Sum glyph advances for the string + CGFloat totalWidth = 0; + NSUInteger len = [string length]; + for (NSUInteger i = 0; i < len; i++) { - return 0.0; + unichar ch = [string characterAtIndex: i]; + NSGlyph g = [self glyphForCharacter: ch]; + NSSize adv = [self advancementForGlyph: g]; + totalWidth += adv.width; } - - cairo_scaled_font_text_extents(_scaled, [string UTF8String], &ctext); - if (cairo_scaled_font_status(_scaled) == CAIRO_STATUS_SUCCESS) - { - return ctext.width; - } -#endif - return 100.0; + return totalWidth; } - (void) appendBezierPathWithGlyphs: (NSGlyph *)glyphs diff --git a/Source/opal/OpalGState.m b/Source/opal/OpalGState.m index 31333d0b..7842199a 100644 --- a/Source/opal/OpalGState.m +++ b/Source/opal/OpalGState.m @@ -29,6 +29,10 @@ #import // NS*ColorSpace #import #import +#import +#import +#import +#import #import "opal/OpalGState.h" #import "opal/OpalSurface.h" #import "opal/OpalFontInfo.h" @@ -53,6 +57,32 @@ static inline NSPoint _NSPointFromCGPoint(CGPoint cgpoint) return NSMakePoint(cgpoint.x, cgpoint.y); } + +#import + +// Map NSCompositingOperation to CGBlendMode +static inline CGBlendMode +_opalBlendModeForOp(NSCompositingOperation op) +{ + switch (op) + { + case NSCompositeClear: return kCGBlendModeClear; + case NSCompositeCopy: return kCGBlendModeCopy; + case NSCompositeSourceOver: return kCGBlendModeNormal; + case NSCompositeSourceIn: return kCGBlendModeSourceIn; + case NSCompositeSourceOut: return kCGBlendModeSourceOut; + case NSCompositeSourceAtop: return kCGBlendModeSourceAtop; + case NSCompositeDestinationOver: return kCGBlendModeDestinationOver; + case NSCompositeDestinationIn: return kCGBlendModeDestinationIn; + case NSCompositeDestinationOut: return kCGBlendModeDestinationOut; + case NSCompositeDestinationAtop: return kCGBlendModeDestinationAtop; + case NSCompositeXOR: return kCGBlendModeXOR; + case NSCompositePlusDarker: return kCGBlendModePlusDarker; + case NSCompositePlusLighter: return kCGBlendModePlusLighter; + default: return kCGBlendModeNormal; + } +} + @implementation OpalGState - (void) dealloc @@ -104,13 +134,16 @@ - (void) setColor: (device_color_t *)color state: (color_state_t)cState if (color->space == gray_colorspace) { + // Use RGB path for gray colors to ensure consistent text rendering + CGFloat gray = color->field[0]; + CGFloat alpha = color->field[AINDEX]; if (cState & COLOR_STROKE) { - CGContextSetGrayStrokeColor(cgctx, color->field[0], color->field[AINDEX]); + CGContextSetRGBStrokeColor(cgctx, gray, gray, gray, alpha); } if (cState & COLOR_FILL) { - CGContextSetGrayFillColor(cgctx, color->field[0], color->field[AINDEX]); + CGContextSetRGBFillColor(cgctx, gray, gray, gray, alpha); } } else if (color->space == rgb_colorspace) @@ -145,6 +178,7 @@ - (void) setColor: (device_color_t *)color state: (color_state_t)cState @end + @implementation OpalGState (Ops) - (void) DPSshow: (const char *)s @@ -166,13 +200,12 @@ - (void) GSShowText: (const char *)s : (size_t) length NSDebugLLog(@"OpalGState", @"%p (%@): %s", self, [self class], __PRETTY_FUNCTION__); CGContextRef cgctx = CGCTX; - if (cgctx) + if (cgctx && s && length > 0) { - CGContextSaveGState(cgctx); - CGContextSetRGBFillColor(cgctx, 0, 1, 0, 1); - CGContextFillRect(cgctx, CGRectMake(0, 0, length * 12, 12)); - CGContextRestoreGState(cgctx); - // TODO: implement! + CGPoint pt = CGContextGetPathCurrentPoint(cgctx); + pt.y += [self->font defaultLineHeightForFont] * 0.3; + CGContextSetTextPosition(cgctx, pt.x, pt.y); + CGContextShowText(cgctx, s, length); } } @@ -215,8 +248,8 @@ - (void) GSShowGlyphsWithAdvances: (const NSGlyph *)glyphs : (const NSSize *)adv } CGPoint pt = CGContextGetPathCurrentPoint(cgctx); - // FIXME: why? - pt.y += [self->font defaultLineHeightForFont] * 0.5; + // Offset Y to account for flipped coordinate system + pt.y += [self->font defaultLineHeightForFont] * 0.3; CGContextSetTextPosition(cgctx, pt.x, pt.y); CGContextShowGlyphsWithAdvances(cgctx, cgglyphs, (const CGSize *)advances, length); @@ -734,8 +767,79 @@ - (void) GSSendBezierPath: (NSBezierPath *)newpath - (NSDictionary *) GSReadRect: (NSRect)r { - NSDebugLLog(@"OpalGState", @"%p (%@): %s", self, [self class], __PRETTY_FUNCTION__); - return nil; + NSDebugLLog(@"OpalGState", @"%p (%@): %s - %@", self, [self class], __PRETTY_FUNCTION__, NSStringFromRect(r)); + + CGContextRef ctx = CGCTX; + if (!ctx) return nil; + + int x = (int)r.origin.x; + int y = (int)r.origin.y; + int w = (int)r.size.width; + int h = (int)r.size.height; + if (w <= 0 || h <= 0) return nil; + + // Create a temporary bitmap context to read pixels into + CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); + CGContextRef tmpCtx = CGBitmapContextCreate(NULL, w, h, 8, w * 4, cs, + kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); + CGColorSpaceRelease(cs); + if (!tmpCtx) return nil; + + // Get image from the backing context + CGImageRef img = CGBitmapContextCreateImage(ctx); + if (!img) { CGContextRelease(tmpCtx); return nil; } + + // Flip Y coordinate for the source rect (CGImage is top-down) + CGFloat ctxHeight = [_opalSurface size].height; + CGRect srcRect = CGRectMake(x, ctxHeight - y - h, w, h); + CGImageRef subImg = CGImageCreateWithImageInRect(img, srcRect); + CGImageRelease(img); + if (!subImg) { CGContextRelease(tmpCtx); return nil; } + + // Draw into temp context + CGContextDrawImage(tmpCtx, CGRectMake(0, 0, w, h), subImg); + CGImageRelease(subImg); + + // Extract pixel data + unsigned char *srcData = (unsigned char *)CGBitmapContextGetData(tmpCtx); + if (!srcData) { CGContextRelease(tmpCtx); return nil; } + + // Convert from BGRA premultiplied to RGBA non-premultiplied + NSMutableData *data = [NSMutableData dataWithLength: w * h * 4]; + unsigned char *dst = [data mutableBytes]; + for (int row = 0; row < h; row++) + { + unsigned char *s = srcData + row * w * 4; + unsigned char *d = dst + row * w * 4; + for (int col = 0; col < w; col++) + { + // BGRA premul -> RGBA + unsigned char b = s[col*4+0]; + unsigned char g = s[col*4+1]; + unsigned char r = s[col*4+2]; + unsigned char a = s[col*4+3]; + d[col*4+0] = r; + d[col*4+1] = g; + d[col*4+2] = b; + d[col*4+3] = a; + } + } + + CGContextRelease(tmpCtx); + + NSAffineTransform *matrix = [NSAffineTransform transform]; + NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: + data, @"Data", + [NSNumber numberWithInt: 8], @"BitsPerSample", + [NSNumber numberWithInt: 4], @"SamplesPerPixel", + [NSNumber numberWithBool: YES], @"HasAlpha", + [NSValue valueWithSize: NSMakeSize(w, h)], @"Size", + NSCalibratedRGBColorSpace, @"ColorSpace", + matrix, @"Matrix", + [NSNumber numberWithInt: 0], @"BitmapFormat", + nil]; + + return dict; } - (void) DPSimage: (NSAffineTransform *)matrix @@ -836,15 +940,19 @@ - (void) compositeGState: (OpalGState *)source NSDebugLLog(@"OpalGState", @"Source cgctx: %p, self: %p - from %@ to %@ with ctm %@", [source CGContext], self, _CGRectRepr(srcCGRect), _CGRectRepr(destCGRect), [self GSCurrentCTM]); // FIXME: this presumes that the backing CGContext of 'source' is // an OpalSurface with a backing CGBitmapContext - CGImageRef backingImage = CGBitmapContextCreateImage([source CGContext]); + CGContextRef srcCtx = [source CGContext]; + if (!srcCtx) return; + CGImageRef backingImage = CGBitmapContextCreateImage(srcCtx); + if (!backingImage) return; CGImageRef subImage = CGImageCreateWithImageInRect(backingImage, srcCGRect); CGContextSaveGState(destCGContext); OPContextSetIdentityCTM(destCGContext); OPContextSetCairoDeviceOffset(destCGContext, 0, 0); - // TODO: this ignores op - // TODO: this ignores delta + // Apply compositing operation and alpha + CGContextSetBlendMode(destCGContext, _opalBlendModeForOp(op)); + CGContextSetAlpha(destCGContext, delta); CGContextDrawImage(destCGContext, destCGRect, subImage); OPContextSetCairoDeviceOffset(CGCTX, -offset.x, @@ -907,11 +1015,17 @@ - (void) drawGState: (OpalGState *)source srcRect.size.width, srcRect.size.height); CGRect destCGRect = CGRectMake(destPoint.x, destPoint.y, srcRect.size.width, srcRect.size.height); - CGImageRef backingImage = CGBitmapContextCreateImage([source CGContext]); + CGContextRef srcCtx = [source CGContext]; + if (!srcCtx) return; + CGImageRef backingImage = CGBitmapContextCreateImage(srcCtx); + if (!backingImage) return; CGImageRef subImage = CGImageCreateWithImageInRect(backingImage, srcCGRect); - // TODO: this ignores op - // TODO: this ignores delta + // Apply compositing operation and alpha + CGContextSaveGState(destCGContext); + CGContextSetBlendMode(destCGContext, _opalBlendModeForOp(op)); + CGContextSetAlpha(destCGContext, delta); CGContextDrawImage(destCGContext, destCGRect, subImage); + CGContextRestoreGState(destCGContext); CGImageRelease(subImage); CGImageRelease(backingImage); } @@ -949,7 +1063,7 @@ - (void) compositerect: (NSRect)aRect { CGContextSaveGState(cgctx); OPContextSetIdentityCTM(cgctx); - // FIXME: Set operator + CGContextSetBlendMode(cgctx, _opalBlendModeForOp(op)); CGContextFillRect(cgctx, CGRectMake(aRect.origin.x, [_opalSurface size].height - aRect.origin.y, @@ -963,6 +1077,7 @@ - (void) compositerect: (NSRect)aRect // MARK: Initialization methods // MARK: - + @implementation OpalGState (InitializationMethods) - (void) DPSinitgraphics @@ -1024,6 +1139,7 @@ - (void) GSCurrentSurface: (OpalSurface **)surface // MARK: Accessors // MARK: - + @implementation OpalGState (Accessors) - (CGContextRef) CGContext @@ -1060,6 +1176,7 @@ - (void) setOPGState: (OPGStateRef)opGState // MARK: Non-required methods // MARK: - + @implementation OpalGState (NonrequiredMethods) - (void) DPSgsave @@ -1082,6 +1199,7 @@ - (void) DPSgrestore @end + @implementation OpalGState (PatternColor) - (void *) saveClip @@ -1104,4 +1222,86 @@ - (void) restoreClip: (void *)savedClip free(savedClip); } + +// Gradient rendering using CoreGraphics CGGradient API +static CGGradientRef OpalCreateGradientFromNSGradient(NSGradient *gradient) +{ + NSInteger stops = [gradient numberOfColorStops]; + if (stops == 0) return NULL; + + CGFloat *components = malloc(sizeof(CGFloat) * stops * 4); + CGFloat *locations = malloc(sizeof(CGFloat) * stops); + if (!components || !locations) { free(components); free(locations); return NULL; } + + for (int i = 0; i < stops; i++) + { + NSColor *color; + CGFloat location; + [gradient getColor: &color location: &location atIndex: i]; + NSColor *rgb = [color colorUsingColorSpaceName: NSCalibratedRGBColorSpace]; + if (rgb == nil) + rgb = [NSColor blackColor]; + components[i*4+0] = [rgb redComponent]; + components[i*4+1] = [rgb greenComponent]; + components[i*4+2] = [rgb blueComponent]; + components[i*4+3] = [rgb alphaComponent]; + locations[i] = location; + } + + CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); + CGGradientRef grad = CGGradientCreateWithColorComponents(cs, components, locations, stops); + CGColorSpaceRelease(cs); + free(components); + free(locations); + + return grad; +} + +- (void) drawGradient: (NSGradient*)gradient + fromPoint: (NSPoint)startPoint + toPoint: (NSPoint)endPoint + options: (NSUInteger)options +{ + CGContextRef ctx = CGCTX; + if (!ctx || !gradient) return; + + CGGradientRef grad = OpalCreateGradientFromNSGradient(gradient); + if (grad) + { + // CGContext already applies CTM to gradient coordinates, + // so pass them in user space without manual transform. + CGContextSaveGState(ctx); + CGContextDrawLinearGradient(ctx, grad, + CGPointMake(startPoint.x, startPoint.y), + CGPointMake(endPoint.x, endPoint.y), + (CGGradientDrawingOptions)options); + CGContextRestoreGState(ctx); + CGGradientRelease(grad); + } +} + +- (void) drawGradient: (NSGradient*)gradient + fromCenter: (NSPoint)startCenter + radius: (CGFloat)startRadius + toCenter: (NSPoint)endCenter + radius: (CGFloat)endRadius + options: (NSUInteger)options +{ + CGContextRef ctx = CGCTX; + if (!ctx || !gradient) return; + + CGGradientRef grad = OpalCreateGradientFromNSGradient(gradient); + if (grad) + { + // CGContext already applies CTM to gradient coordinates + CGContextSaveGState(ctx); + CGContextDrawRadialGradient(ctx, grad, + CGPointMake(startCenter.x, startCenter.y), startRadius, + CGPointMake(endCenter.x, endCenter.y), endRadius, + (CGGradientDrawingOptions)options); + CGContextRestoreGState(ctx); + CGGradientRelease(grad); + } +} + @end diff --git a/Source/opal/OpalSurface.m b/Source/opal/OpalSurface.m index 1dcd833e..9d460b6c 100644 --- a/Source/opal/OpalSurface.m +++ b/Source/opal/OpalSurface.m @@ -80,20 +80,38 @@ - (void) createCGContextsWithSuppliedBackingContext: (CGContextRef)ctx if (_x11CGContext || _backingCGContext) { NSLog(@"FIXME: Replacement of OpalSurface %p's CGContexts (x11=%p,backing=%p) without transfer of gstate", self, _x11CGContext, _backingCGContext); + // Resize path: drop the old X11 CGContext so the next expose + // recreates one at the new window size. The backing bitmap + // below is reallocated at the new size, too. + if (_x11CGContext) + { + CGContextRelease(_x11CGContext); + _x11CGContext = NULL; + } + if (_backingCGContext) + { + CGContextRelease(_backingCGContext); + _backingCGContext = NULL; + } } if (ctx) { + // Client supplied a ready-made CGContext. Treat it as the x11 + // destination (pre-existing behaviour) and derive the size from it. _x11CGContext = ctx; pixelsWide = CGBitmapContextGetWidth(ctx); pixelsHigh = CGBitmapContextGetHeight(ctx); } else { - Display * display = _gsWindowDevice->display; - Window window = _gsWindowDevice->ident; - - _x11CGContext = OPX11ContextCreate(display, window); + // Lazy path: defer creation of the X11 CGContext until the X window + // is actually mapped. -[NSWindow _startBackendWindow] invokes + // GSSetDevice (which ends up here) before the window is mapped, so + // OPX11ContextCreate() either fails or returns a context that does + // not draw. We instead create the X11 context on first use, which + // is triggered by the initial Expose event (or by any GState path + // that touches _x11CGContext). pixelsWide = _gsWindowDevice->buffer_width; pixelsHigh = _gsWindowDevice->buffer_height; @@ -119,11 +137,47 @@ - (void) createCGContextsWithSuppliedBackingContext: (CGContextRef)ctx _backingCGContext = createCGBitmapContext(pixelsWide, pixelsHigh); } - NSDebugLLog(@"OpalSurface", @"Created CGContexts: X11=%p, backing=%p, width=%d height=%d", + NSLog(@"OpalSurface Created CGContexts: X11=%p (deferred unless non-nil), backing=%p, width=%d height=%d", _x11CGContext, _backingCGContext, pixelsWide, pixelsHigh); } +/** + * Lazily create the X11-backed CGContext. Called from every code path + * that actually draws to or otherwise touches _x11CGContext. Safe to + * call repeatedly; only has side effects the first time (per surface + * instance) or after an explicit invalidation. + * + * We do nothing unless the associated gswindow_device_t has a valid + * X Window id (ident != 0), because OPX11ContextCreate on an unmapped + * or zero drawable returns a context that cannot draw. + */ +- (void) ensureX11Context +{ + if (_x11CGContext != NULL) + return; + if (_gsWindowDevice == NULL) + return; + if (_gsWindowDevice->ident == 0) + return; + + Display *display = _gsWindowDevice->display; + Window window = _gsWindowDevice->ident; + + _x11CGContext = OPX11ContextCreate(display, window); + if (_x11CGContext == NULL) + { + NSDebugLLog(@"OpalSurface", + @"OpalSurface %p: OPX11ContextCreate(display=%p, window=%lu) returned NULL; will retry on next use", + self, display, (unsigned long)window); + return; + } + + NSDebugLLog(@"OpalSurface", + @"OpalSurface %p: lazily created X11 CGContext=%p for window=%lu", + self, _x11CGContext, (unsigned long)window); +} + // FIXME: *VERY* bad things will happen if a non-bitmap // context is passed here. - (id) initWithDevice: (void *)device context: (CGContextRef)ctx @@ -158,18 +212,29 @@ - (CGContextRef) backingCGContext - (CGContextRef) x11CGContext { + // All external readers of the X11 context must see a valid context + // if one can be created right now, so route through the lazy path. + [self ensureX11Context]; return _x11CGContext; } - (void) handleExposeRect: (NSRect)rect { + // Expose events only fire on mapped windows, so this is the first + // safe moment at which we know the X Window is real. Create the + // X11 CGContext on demand if we haven't already. + [self ensureX11Context]; + + NSLog(@"OpalSurface handleExposeRect: %@ backing=%p x11=%p", NSStringFromRect(rect), _backingCGContext, _x11CGContext); NSDebugLLog(@"OpalSurface", @"handleExposeRect %@", NSStringFromRect(rect)); - if (!_backingCGContext) + if (!_backingCGContext || !_x11CGContext) { return; } + // Flush backing context to ensure all drawing is committed + CGContextFlush(_backingCGContext); CGImageRef backingImage = CGBitmapContextCreateImage(_backingCGContext); if (!backingImage) // FIXME: writing a nil image fails with Opal return; @@ -191,6 +256,7 @@ - (void) handleExposeRect: (NSRect)rect CGContextDrawImage(_x11CGContext, cgRect, subImage); + CGContextFlush(_x11CGContext); #if 0 #warning Saving debug images