X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=OSX%2Fjwxyz.m;h=b7e99525b6be63c925dd25c8cd03cf3f11e1dd0d;hb=6f5482d73adb0165c0130bb47d852644ab0c4869;hp=590b3ccad0d0c4995e242eaec8394a227c9abe7a;hpb=49f5b54f312fe4ac2e9bc47581a72451bd0e8439;p=xscreensaver diff --git a/OSX/jwxyz.m b/OSX/jwxyz.m index 590b3cca..b7e99525 100644 --- a/OSX/jwxyz.m +++ b/OSX/jwxyz.m @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 1991-2006 Jamie Zawinski +/* xscreensaver, Copyright (c) 1991-2012 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -17,16 +17,35 @@ */ #import -#import +#import + +#ifdef USE_IPHONE +# import +# import +# import +# import +# define NSView UIView +# define NSRect CGRect +# define NSPoint CGPoint +# define NSSize CGSize +# define NSColor UIColor +# define NSImage UIImage +# define NSEvent UIEvent +# define NSFont UIFont +# define NSGlyph CGGlyph +# define NSWindow UIWindow +# define NSMakeSize CGSizeMake +# define NSBezierPath UIBezierPath +#else +# import +#endif + #import "jwxyz.h" #import "jwxyz-timers.h" +#import "yarandom.h" #undef Assert -#define Assert(C,S) do { \ - if (!(C)) { \ - NSLog(@"%s",S); \ - abort(); \ - }} while(0) +#define Assert(C,S) do { if (!(C)) jwxyz_abort ("%s",(S)); } while(0) # undef MAX # undef MIN @@ -37,14 +56,17 @@ struct jwxyz_Drawable { enum { WINDOW, PIXMAP } type; CGContextRef cgc; + CGImageRef cgi; CGRect frame; union { struct { NSView *view; unsigned long background; + int last_mouse_x, last_mouse_y; } window; struct { int depth; + void *cgc_buffer; // the bits to which CGContextRef renders } pixmap; }; }; @@ -54,9 +76,11 @@ struct jwxyz_Display { Screen *screen; struct jwxyz_sources_data *timers_data; +# ifndef USE_IPHONE CGDirectDisplayID cgdpy; /* ...of the one and only Window, main_window. This can change if the window is dragged to a different screen. */ +# endif CGColorSpaceRef colorspace; /* Color space of this screen. We tag all of our images with this to avoid translation @@ -85,11 +109,38 @@ struct jwxyz_Font { }; +/* Instead of calling abort(), throw a real exception, so that + XScreenSaverView can catch it and display a dialog. + */ +void +jwxyz_abort (const char *fmt, ...) +{ + char s[10240]; + if (!fmt || !*fmt) + strcpy (s, "abort"); + else + { + va_list args; + va_start (args, fmt); + vsprintf (s, fmt, args); + va_end (args); + } + [[NSException exceptionWithName: NSInternalInconsistencyException + reason: [NSString stringWithCString: s + encoding:NSUTF8StringEncoding] + userInfo: nil] + raise]; + abort(); // not reached +} + + Display * -jwxyz_make_display (void *nsview_arg) +jwxyz_make_display (void *nsview_arg, void *cgc_arg) { + CGContextRef cgc = (CGContextRef) cgc_arg; NSView *view = (NSView *) nsview_arg; - if (!view) abort(); + Assert (view, "no view"); + if (!view) return 0; Display *d = (Display *) calloc (1, sizeof(*d)); d->screen = (Screen *) calloc (1, sizeof(Screen)); @@ -108,20 +159,22 @@ jwxyz_make_display (void *nsview_arg) Window w = (Window) calloc (1, sizeof(*w)); w->type = WINDOW; - // kludge! this needs to be set late, so we do it in XClearWindow! - // w->cgc = [[[view window] graphicsContext] graphicsPort]; - w->cgc = 0; w->window.view = view; + CFRetain (w->window.view); // needed for garbage collection? w->window.background = BlackPixel(0,0); d->main_window = w; - [view lockFocus]; - w->cgc = [[[view window] graphicsContext] graphicsPort]; - [view unlockFocus]; - - jwxyz_window_resized (d, w); +# ifndef USE_IPHONE + if (! cgc) { + [view lockFocus]; + cgc = [[[view window] graphicsContext] graphicsPort]; + [view unlockFocus]; + w->cgc = cgc; + } +# endif + Assert (cgc, "no CGContext"); return d; } @@ -145,18 +198,39 @@ jwxyz_window_view (Window w) return w->window.view; } + +/* Call this after any modification to the bits on a Pixmap or Window. + Most Pixmaps are used frequently as sources and infrequently as + destinations, so it pays to cache the data as a CGImage as needed. + */ +static void +invalidate_drawable_cache (Drawable d) +{ + if (d && d->cgi) { + CGImageRelease (d->cgi); + d->cgi = 0; + } +} + + /* Call this when the View changes size or position. */ void -jwxyz_window_resized (Display *dpy, Window w) +jwxyz_window_resized (Display *dpy, Window w, + int new_x, int new_y, int new_width, int new_height, + void *cgc_arg) { + CGContextRef cgc = (CGContextRef) cgc_arg; Assert (w->type == WINDOW, "not a window"); - NSRect r = [w->window.view frame]; - w->frame.origin.x = r.origin.x; // NSRect -> CGRect - w->frame.origin.y = r.origin.y; - w->frame.size.width = r.size.width; - w->frame.size.height = r.size.height; + w->frame.origin.x = new_x; + w->frame.origin.y = new_y; + w->frame.size.width = new_width; + w->frame.size.height = new_height; + + if (cgc) w->cgc = cgc; + Assert (w->cgc, "no CGContext"); +# ifndef USE_IPHONE // Figure out which screen the window is currently on. { int wx, wy; @@ -169,6 +243,7 @@ jwxyz_window_resized (Display *dpy, Window w) CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n); Assert (dpy->cgdpy, "unable to find CGDisplay"); } +# endif // USE_IPHONE #if 0 { @@ -186,9 +261,23 @@ jwxyz_window_resized (Display *dpy, Window w) // dpy->colorspace = CGColorSpaceCreateDeviceRGB(); # endif + + invalidate_drawable_cache (w); } +#ifdef USE_IPHONE +void +jwxyz_mouse_moved (Display *dpy, Window w, int x, int y) +{ + Assert (w->type == WINDOW, "not a window"); + w->window.last_mouse_x = x; + w->window.last_mouse_y = y; +} +#endif // USE_IPHONE + + + jwxyz_sources_data * display_sources_data (Display *dpy) { @@ -244,7 +333,6 @@ XDisplayHeight (Display *dpy, int screen) return (int) dpy->main_window->frame.size.height; } - static void validate_pixel (unsigned long pixel, unsigned int depth, BOOL alpha_allowed_p) { @@ -329,14 +417,14 @@ push_gc (Drawable d, GC gc) case GXxor: CGContextSetBlendMode (cgc, kCGBlendModeDifference); break; case GXor: CGContextSetBlendMode (cgc, kCGBlendModeLighten); break; case GXand: CGContextSetBlendMode (cgc, kCGBlendModeDarken); break; - default: abort(); break; + default: Assert(0, "unknown gcv function"); break; } if (gc->gcv.clip_mask) set_clip_mask (d, gc); } -#define pop_gc(d,gc) CGContextRestoreGState ((d)->cgc) +#define pop_gc(d,gc) CGContextRestoreGState (d->cgc) /* Pushes a GC context; sets BlendMode, ClipMask, Fill, and Stroke colors. @@ -354,7 +442,6 @@ push_color_gc (Drawable d, GC gc, unsigned long color, } CGContextRef cgc = d->cgc; - set_color (cgc, color, depth, gc->gcv.alpha_allowed_p, fill_p); CGContextSetShouldAntialias (cgc, antialias_p); } @@ -382,7 +469,7 @@ push_bg_gc (Drawable d, GC gc, Bool fill_p) It is *way* faster to draw points by creating and drawing a 1x1 CGImage with repeated calls to CGContextDrawImage than it is to make a single - call to CGContextFillRects()! + call to CGContextFillRects() with a list of 1x1 rectangles! I still wouldn't call it *fast*, however... */ @@ -395,6 +482,8 @@ XDrawPoints (Display *dpy, Drawable d, GC gc, int i; CGRect wr = d->frame; + push_fg_gc (d, gc, YES); + # ifdef XDRAWPOINTS_IMAGES unsigned int argb = gc->gcv.foreground; @@ -440,7 +529,6 @@ XDrawPoints (Display *dpy, Drawable d, GC gc, CGRect *rects = (CGRect *) malloc (count * sizeof(CGRect)); CGRect *r = rects; - push_fg_gc (d, gc, YES); for (i = 0; i < count; i++) { r->size.width = r->size.height = 1; if (i > 0 && mode == CoordModePrevious) { @@ -459,6 +547,9 @@ XDrawPoints (Display *dpy, Drawable d, GC gc, # endif /* ! XDRAWPOINTS_IMAGES */ + pop_gc (d, gc); + invalidate_drawable_cache (d); + return 0; } @@ -483,6 +574,7 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc, unsigned int width, unsigned int height, int dst_x, int dst_y) { + Assert (gc, "no GC"); Assert ((width < 65535), "improbably large width"); Assert ((height < 65535), "improbably large height"); Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x"); @@ -493,13 +585,14 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc, if (width == 0 || height == 0) return 0; - if (gc && (gc->gcv.function == GXset || - gc->gcv.function == GXclear)) { + if (gc->gcv.function == GXset || + gc->gcv.function == GXclear) { // "set" and "clear" are dumb drawing modes that ignore the source // bits and just draw solid rectangles. - set_color (dst->cgc, (gc->gcv.function == GXset - ? (gc->depth == 1 ? 1 : WhitePixel(0,0)) - : (gc->depth == 1 ? 0 : BlackPixel(0,0))), + set_color (dst->cgc, + (gc->gcv.function == GXset + ? (gc->depth == 1 ? 1 : WhitePixel(0,0)) + : (gc->depth == 1 ? 0 : BlackPixel(0,0))), gc->depth, gc->gcv.alpha_allowed_p, YES); draw_rect (dpy, dst, 0, dst_x, dst_y, width, height, YES, YES); return 0; @@ -559,29 +652,38 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc, CLIP (src, dst, y, height); # undef CLIP -#if 0 - Assert (src_rect.size.width == dst_rect.size.width, "width out of sync"); - Assert (src_rect.size.height == dst_rect.size.height, "height out of sync"); - Assert (src_rect.origin.x >= 0 && src_rect.origin.y >= 0, "clip failed src_x"); - Assert (dst_rect.origin.x >= 0 && dst_rect.origin.y >= 0, "clip failed dst_x"); - Assert (src_rect.origin.y >= 0 && src_rect.origin.y >= 0, "clip failed src_y"); - Assert (dst_rect.origin.y >= 0 && dst_rect.origin.y >= 0, "clip failed dst_y"); - Assert (src_rect.origin.x + src_rect.size.width <= - src_frame.origin.x + src_frame.size.width, "clip failed src_width"); -#endif - if (src_rect.size.width <= 0 || src_rect.size.height <= 0) return 0; NSObject *releaseme = 0; CGImageRef cgi; BOOL mask_p = NO; + BOOL free_cgi_p = NO; - if (src->type == PIXMAP) { - // get a CGImage out of the pixmap CGContext -- it's the whole pixmap, - // but it presumably shares the data pointer instead of copying it. - cgi = CGBitmapContextCreateImage (src->cgc); +#ifndef USE_IPHONE + // Because of the backbuffer, all iPhone Windows work like Pixmaps. + if (src->type == PIXMAP) +# endif + { + + // If we are copying from a Pixmap to a Pixmap or Window, we must first + // copy the bits to an intermediary CGImage object, then copy that to the + // destination drawable's CGContext. + // + // (It doesn't seem to be possible to use NSCopyBits() to optimize the + // case of copying from a Pixmap back to itself, but I don't think that + // happens very often anyway.) + // + // First we get a CGImage out of the pixmap CGContext -- it's the whole + // pixmap, but it presumably shares the data pointer instead of copying + // it. We then cache that CGImage it inside the Pixmap object. Note: + // invalidate_drawable_cache() must be called to discard this any time a + // modification is made to the pixmap, or we'll end up re-using old bits. + // + if (!src->cgi) + src->cgi = CGBitmapContextCreateImage (src->cgc); + cgi = src->cgi; // if doing a sub-rect, trim it down. if (src_rect.origin.x != src_frame.origin.x || @@ -592,97 +694,91 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc, src_rect.origin.y = (src_frame.size.height - src_rect.size.height - src_rect.origin.y); // This does not copy image data, so it should be fast. - CGImageRef cgi2 = CGImageCreateWithImageInRect (cgi, src_rect); - CGImageRelease (cgi); - cgi = cgi2; + cgi = CGImageCreateWithImageInRect (cgi, src_rect); + free_cgi_p = YES; } - if (src->pixmap.depth == 1) + if (src->type == PIXMAP && src->pixmap.depth == 1) mask_p = YES; +# ifndef USE_IPHONE } else { /* (src->type == WINDOW) */ - NSRect nsfrom; + NSRect nsfrom; // NSRect != CGRect on 10.4 nsfrom.origin.x = src_rect.origin.x; nsfrom.origin.y = src_rect.origin.y; nsfrom.size.width = src_rect.size.width; nsfrom.size.height = src_rect.size.height; -#if 1 - // get the bits (desired sub-rectangle) out of the NSView via Cocoa. - // - NSBitmapImageRep *bm = [NSBitmapImageRep alloc]; - [bm initWithFocusedViewRect:nsfrom]; - unsigned char *data = [bm bitmapData]; - int bps = [bm bitsPerSample]; - int bpp = [bm bitsPerPixel]; - int bpl = [bm bytesPerRow]; - releaseme = bm; -#endif + if (src == dst) { -#if 0 - // QuickDraw way (doesn't work, need NSQuickDrawView) - PixMapHandle pix = GetPortPixMap([src->window.view qdPort]); - char **data = GetPortPixMap (pix); - int bps = 8; - int bpp = 32; - int bpl = GetPixRowBytes (pix) & 0x3FFF; -#endif + // If we are copying from a window to itself, we can use NSCopyBits() + // without first copying the rectangle to an intermediary CGImage. + // This is ~28% faster (but I *expected* it to be twice as fast...) + // (kumppa, bsod, decayscreen, memscroller, slidescreen, slip, xjack) + // + cgi = 0; -#if 0 - // get the bits (desired sub-rectangle) out of the raw frame buffer. - // (This renders wrong, and appears to be even slower anyway.) - // - int window_x, window_y; - XTranslateCoordinates (dpy, src, NULL, 0, 0, &window_x, &window_y, NULL); - window_x += nsfrom.origin.x; - window_y += (dst->frame.size.height - - (nsfrom.origin.y + nsfrom.size.height)); - - unsigned char *data = (unsigned char *) - CGDisplayAddressForPosition (dpy->cgdpy, window_x, window_y); - int bps = CGDisplayBitsPerSample (dpy->cgdpy); - int bpp = CGDisplayBitsPerPixel (dpy->cgdpy); - int bpl = CGDisplayBytesPerRow (dpy->cgdpy); + } else { -#endif + // If we are copying from a Window to a Pixmap, we must first copy + // the bits to an intermediary CGImage object, then copy that to the + // Pixmap's CGContext. + // + NSBitmapImageRep *bm = [[NSBitmapImageRep alloc] + initWithFocusedViewRect:nsfrom]; + unsigned char *data = [bm bitmapData]; + int bps = [bm bitsPerSample]; + int bpp = [bm bitsPerPixel]; + int bpl = [bm bytesPerRow]; + releaseme = bm; + + // create a CGImage from those bits. + // (As of 10.5, we could just do cgi = [bm CGImage] (it is autoreleased) + // but that method didn't exist in 10.4.) + + CGDataProviderRef prov = + CGDataProviderCreateWithData (NULL, data, bpl * nsfrom.size.height, + NULL); + cgi = CGImageCreate (src_rect.size.width, src_rect.size.height, + bps, bpp, bpl, + dpy->colorspace, + /* Use whatever default bit ordering we got from + initWithFocusedViewRect. I would have assumed + that it was (kCGImageAlphaNoneSkipFirst | + kCGBitmapByteOrder32Host), but on Intel, + it's not! + */ + 0, + prov, + NULL, /* decode[] */ + NO, /* interpolate */ + kCGRenderingIntentDefault); + free_cgi_p = YES; + //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace,"bad colorspace"); + CGDataProviderRelease (prov); + } - // create a CGImage from those bits - - CGDataProviderRef prov = - CGDataProviderCreateWithData (NULL, data, bpl * nsfrom.size.height, - NULL); - cgi = CGImageCreate (src_rect.size.width, src_rect.size.height, - bps, bpp, bpl, - dpy->colorspace, - /* Use whatever default bit ordering we got from - initWithFocusedViewRect. I would have assumed - that it was (kCGImageAlphaNoneSkipFirst | - kCGBitmapByteOrder32Host), but on Intel, - it's not! - */ - 0, - prov, - NULL, /* decode[] */ - NO, /* interpolate */ - kCGRenderingIntentDefault); - //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace"); - CGDataProviderRelease (prov); +# endif // !USE_IPHONE } + CGContextRef cgc = dst->cgc; + if (mask_p) { // src depth == 1 push_bg_gc (dst, gc, YES); // fill the destination rectangle with solid background... - CGContextFillRect (dst->cgc, orig_dst_rect); + CGContextFillRect (cgc, orig_dst_rect); + + Assert (cgc, "no CGC with 1-bit XCopyArea"); // then fill in a solid rectangle of the fg color, using the image as an // alpha mask. (the image has only values of BlackPixel or WhitePixel.) - set_color (dst->cgc, gc->gcv.foreground, gc->depth, + set_color (cgc, gc->gcv.foreground, gc->depth, gc->gcv.alpha_allowed_p, YES); - CGContextClipToMask (dst->cgc, dst_rect, cgi); - CGContextFillRect (dst->cgc, dst_rect); + CGContextClipToMask (cgc, dst_rect, cgi); + CGContextFillRect (cgc, dst_rect); pop_gc (dst, gc); @@ -697,20 +793,41 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc, // being copied. // if (clipped) { - set_color (dst->cgc, gc->gcv.background, gc->depth, + set_color (cgc, gc->gcv.background, gc->depth, gc->gcv.alpha_allowed_p, YES); - CGContextFillRect (dst->cgc, orig_dst_rect); + CGContextFillRect (cgc, orig_dst_rect); } - // copy the CGImage onto the destination CGContext - //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace"); - CGContextDrawImage (dst->cgc, dst_rect, cgi); + if (cgi) { + // copy the CGImage onto the destination CGContext + //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace, "bad colorspace"); + CGContextDrawImage (cgc, dst_rect, cgi); + } else { + // No cgi means src == dst, and both are Windows. + +# ifdef USE_IPHONE + Assert (0, "NSCopyBits unimplemented"); // shouldn't be reached anyway + return 0; +# else // !USE_IPHONE + NSRect nsfrom; + nsfrom.origin.x = src_rect.origin.x; // NSRect != CGRect on 10.4 + nsfrom.origin.y = src_rect.origin.y; + nsfrom.size.width = src_rect.size.width; + nsfrom.size.height = src_rect.size.height; + NSPoint nsto; + nsto.x = dst_rect.origin.x; + nsto.y = dst_rect.origin.y; + NSCopyBits (0, nsfrom, nsto); +# endif // !USE_IPHONE + } pop_gc (dst, gc); } - - CGImageRelease (cgi); + + if (free_cgi_p) CGImageRelease (cgi); + if (releaseme) [releaseme release]; + invalidate_drawable_cache (dst); return 0; } @@ -751,14 +868,16 @@ XDrawLine (Display *dpy, Drawable d, GC gc, int x1, int y1, int x2, int y2) push_fg_gc (d, gc, NO); - set_line_mode (d->cgc, &gc->gcv); - CGContextBeginPath (d->cgc); - CGContextMoveToPoint (d->cgc, p.x, p.y); + CGContextRef cgc = d->cgc; + set_line_mode (cgc, &gc->gcv); + CGContextBeginPath (cgc); + CGContextMoveToPoint (cgc, p.x, p.y); p.x = wr.origin.x + x2; p.y = wr.origin.y + wr.size.height - y2; - CGContextAddLineToPoint (d->cgc, p.x, p.y); - CGContextStrokePath (d->cgc); + CGContextAddLineToPoint (cgc, p.x, p.y); + CGContextStrokePath (cgc); pop_gc (d, gc); + invalidate_drawable_cache (d); return 0; } @@ -770,7 +889,10 @@ XDrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count, NSPoint p; CGRect wr = d->frame; push_fg_gc (d, gc, NO); - set_line_mode (d->cgc, &gc->gcv); + + CGContextRef cgc = d->cgc; + + set_line_mode (cgc, &gc->gcv); // if the first and last points coincide, use closepath to get // the proper line-joining. @@ -781,8 +903,8 @@ XDrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count, p.x = wr.origin.x + points->x; p.y = wr.origin.y + wr.size.height - points->y; points++; - CGContextBeginPath (d->cgc); - CGContextMoveToPoint (d->cgc, p.x, p.y); + CGContextBeginPath (cgc); + CGContextMoveToPoint (cgc, p.x, p.y); for (i = 1; i < count; i++) { if (mode == CoordModePrevious) { p.x += points->x; @@ -791,12 +913,13 @@ XDrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count, p.x = wr.origin.x + points->x; p.y = wr.origin.y + wr.size.height - points->y; } - CGContextAddLineToPoint (d->cgc, p.x, p.y); + CGContextAddLineToPoint (cgc, p.x, p.y); points++; } - if (closed_p) CGContextClosePath (d->cgc); - CGContextStrokePath (d->cgc); + if (closed_p) CGContextClosePath (cgc); + CGContextStrokePath (cgc); pop_gc (d, gc); + invalidate_drawable_cache (d); return 0; } @@ -807,20 +930,23 @@ XDrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count) int i; CGRect wr = d->frame; + CGContextRef cgc = d->cgc; + push_fg_gc (d, gc, NO); - set_line_mode (d->cgc, &gc->gcv); - CGContextBeginPath (d->cgc); + set_line_mode (cgc, &gc->gcv); + CGContextBeginPath (cgc); for (i = 0; i < count; i++) { - CGContextMoveToPoint (d->cgc, + CGContextMoveToPoint (cgc, wr.origin.x + segments->x1, wr.origin.y + wr.size.height - segments->y1); - CGContextAddLineToPoint (d->cgc, + CGContextAddLineToPoint (cgc, wr.origin.x + segments->x2, wr.origin.y + wr.size.height - segments->y2); segments++; } - CGContextStrokePath (d->cgc); + CGContextStrokePath (cgc); pop_gc (d, gc); + invalidate_drawable_cache (d); return 0; } @@ -861,16 +987,18 @@ draw_rect (Display *dpy, Drawable d, GC gc, push_bg_gc (d, gc, fill_p); } + CGContextRef cgc = d->cgc; if (fill_p) - CGContextFillRect (d->cgc, r); + CGContextFillRect (cgc, r); else { if (gc) - set_line_mode (d->cgc, &gc->gcv); - CGContextStrokeRect (d->cgc, r); + set_line_mode (cgc, &gc->gcv); + CGContextStrokeRect (cgc, r); } if (gc) pop_gc (d, gc); + invalidate_drawable_cache (d); } @@ -895,6 +1023,7 @@ XFillRectangles (Display *dpy, Drawable d, GC gc, XRectangle *rects, int n) { CGRect wr = d->frame; int i; + CGContextRef cgc = d->cgc; push_fg_gc (d, gc, YES); for (i = 0; i < n; i++) { CGRect r; @@ -902,10 +1031,11 @@ XFillRectangles (Display *dpy, Drawable d, GC gc, XRectangle *rects, int n) r.origin.y = wr.origin.y + wr.size.height - rects->y - rects->height; r.size.width = rects->width; r.size.height = rects->height; - CGContextFillRect (d->cgc, r); + CGContextFillRect (cgc, r); rects++; } pop_gc (d, gc); + invalidate_drawable_cache (d); return 0; } @@ -914,7 +1044,8 @@ int XClearArea (Display *dpy, Window win, int x, int y, int w, int h, Bool exp) { Assert (win->type == WINDOW, "not a window"); - set_color (win->cgc, win->window.background, 32, NO, YES); + CGContextRef cgc = win->cgc; + set_color (cgc, win->window.background, 32, NO, YES); draw_rect (dpy, win, 0, x, y, w, h, NO, YES); return 0; } @@ -927,9 +1058,10 @@ XFillPolygon (Display *dpy, Drawable d, GC gc, CGRect wr = d->frame; int i; push_fg_gc (d, gc, YES); - CGContextBeginPath (d->cgc); + CGContextRef cgc = d->cgc; + CGContextBeginPath (cgc); + float x = 0, y = 0; for (i = 0; i < npoints; i++) { - float x, y; if (i > 0 && mode == CoordModePrevious) { x += points[i].x; y -= points[i].y; @@ -939,16 +1071,17 @@ XFillPolygon (Display *dpy, Drawable d, GC gc, } if (i == 0) - CGContextMoveToPoint (d->cgc, x, y); + CGContextMoveToPoint (cgc, x, y); else - CGContextAddLineToPoint (d->cgc, x, y); + CGContextAddLineToPoint (cgc, x, y); } - CGContextClosePath (d->cgc); + CGContextClosePath (cgc); if (gc->gcv.fill_rule == EvenOddRule) - CGContextEOFillPath (d->cgc); + CGContextEOFillPath (cgc); else - CGContextFillPath (d->cgc); + CGContextFillPath (cgc); pop_gc (d, gc); + invalidate_drawable_cache (d); return 0; } @@ -978,28 +1111,30 @@ draw_arc (Display *dpy, Drawable d, GC gc, int x, int y, push_fg_gc (d, gc, fill_p); - CGContextBeginPath (d->cgc); + CGContextRef cgc = d->cgc; + CGContextBeginPath (cgc); - CGContextSaveGState(d->cgc); - CGContextTranslateCTM (d->cgc, ctr.x, ctr.y); - CGContextScaleCTM (d->cgc, width/2.0, height/2.0); + CGContextSaveGState(cgc); + CGContextTranslateCTM (cgc, ctr.x, ctr.y); + CGContextScaleCTM (cgc, width/2.0, height/2.0); if (fill_p) - CGContextMoveToPoint (d->cgc, 0, 0); + CGContextMoveToPoint (cgc, 0, 0); - CGContextAddArc (d->cgc, 0.0, 0.0, 1, r1, r2, clockwise); - CGContextRestoreGState (d->cgc); // restore before stroke, for line width + CGContextAddArc (cgc, 0.0, 0.0, 1, r1, r2, clockwise); + CGContextRestoreGState (cgc); // restore before stroke, for line width if (closed_p) - CGContextClosePath (d->cgc); // for proper line joining + CGContextClosePath (cgc); // for proper line joining if (fill_p) { - CGContextFillPath (d->cgc); + CGContextFillPath (cgc); } else { - set_line_mode (d->cgc, &gc->gcv); - CGContextStrokePath (d->cgc); + set_line_mode (cgc, &gc->gcv); + CGContextStrokePath (cgc); } pop_gc (d, gc); + invalidate_drawable_cache (d); return 0; } @@ -1063,6 +1198,10 @@ gcv_defaults (XGCValues *gcv, int depth) static void set_gcv (GC gc, XGCValues *from, unsigned long mask) { + if (! mask) return; + Assert (gc && from, "no gc"); + if (!gc || !from) return; + if (mask & GCFunction) gc->gcv.function = from->function; if (mask & GCForeground) gc->gcv.foreground = from->foreground; if (mask & GCBackground) gc->gcv.background = from->background; @@ -1082,17 +1221,18 @@ set_gcv (GC gc, XGCValues *from, unsigned long mask) if (mask & GCBackground) validate_pixel (from->background, gc->depth, gc->gcv.alpha_allowed_p); - if (mask & GCLineStyle) abort(); - if (mask & GCPlaneMask) abort(); - if (mask & GCFillStyle) abort(); - if (mask & GCTile) abort(); - if (mask & GCStipple) abort(); - if (mask & GCTileStipXOrigin) abort(); - if (mask & GCTileStipYOrigin) abort(); - if (mask & GCGraphicsExposures) abort(); - if (mask & GCDashOffset) abort(); - if (mask & GCDashList) abort(); - if (mask & GCArcMode) abort(); + Assert ((! (mask & (GCLineStyle | + GCPlaneMask | + GCFillStyle | + GCTile | + GCStipple | + GCTileStipXOrigin | + GCTileStipYOrigin | + GCGraphicsExposures | + GCDashOffset | + GCDashList | + GCArcMode))), + "unimplemented gcvalues mask"); } @@ -1172,7 +1312,9 @@ Status XAllocColor (Display *dpy, Colormap cmap, XColor *color) { // store 32 bit ARGB in the pixel field. - color->pixel = (( 0xFF << 24) | + // (The uint32_t is so that 0xFF000000 doesn't become 0xFFFFFFFFFF000000) + color->pixel = (uint32_t) + (( 0xFF << 24) | (((color->red >> 8) & 0xFF) << 16) | (((color->green >> 8) & 0xFF) << 8) | (((color->blue >> 8) & 0xFF) )); @@ -1226,7 +1368,7 @@ XParseColor (Display *dpy, Colormap cmap, const char *spec, XColor *ret) g = (hex[spec[3]] << 4) | hex[spec[4]]; b = (hex[spec[5]] << 4) | hex[spec[6]]; } else if (!strcasecmp(spec,"black")) { - r = g = b = 0; +// r = g = b = 0; } else if (!strcasecmp(spec,"white")) { r = g = b = 255; } else if (!strcasecmp(spec,"red")) { @@ -1306,17 +1448,18 @@ ximage_putpixel_1 (XImage *ximage, int x, int y, unsigned long pixel) static unsigned long ximage_getpixel_32 (XImage *ximage, int x, int y) { - return *((unsigned long *) ximage->data + - (y * (ximage->bytes_per_line >> 2)) + - x); + return ((unsigned long) + *((uint32_t *) ximage->data + + (y * (ximage->bytes_per_line >> 2)) + + x)); } static int ximage_putpixel_32 (XImage *ximage, int x, int y, unsigned long pixel) { - *((unsigned long *) ximage->data + + *((uint32_t *) ximage->data + (y * (ximage->bytes_per_line >> 2)) + - x) = pixel; + x) = (uint32_t) pixel; return 0; } @@ -1336,7 +1479,7 @@ XInitImage (XImage *ximage) ximage->f.put_pixel = ximage_putpixel_32; ximage->f.get_pixel = ximage_getpixel_32; } else { - abort(); + Assert (0, "unknown depth"); } return 1; } @@ -1479,6 +1622,14 @@ XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, { CGRect wr = d->frame; + Assert (gc, "no GC"); + Assert ((w < 65535), "improbably large width"); + Assert ((h < 65535), "improbably large height"); + Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x"); + Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y"); + Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x"); + Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y"); + // Clip width and height to the bounds of the Drawable // if (dest_x + w > wr.size.width) { @@ -1509,11 +1660,13 @@ XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, if (w <= 0 || h <= 0) return 0; - if (gc && (gc->gcv.function == GXset || - gc->gcv.function == GXclear)) { + CGContextRef cgc = d->cgc; + + if (gc->gcv.function == GXset || + gc->gcv.function == GXclear) { // "set" and "clear" are dumb drawing modes that ignore the source // bits and just draw solid rectangles. - set_color (d->cgc, (gc->gcv.function == GXset + set_color (cgc, (gc->gcv.function == GXset ? (gc->depth == 1 ? 1 : WhitePixel(0,0)) : (gc->depth == 1 ? 0 : BlackPixel(0,0))), gc->depth, gc->gcv.alpha_allowed_p, YES); @@ -1554,7 +1707,7 @@ XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, kCGRenderingIntentDefault); CGDataProviderRelease (prov); //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace"); - CGContextDrawImage (d->cgc, r, cgi); + CGContextDrawImage (cgc, r, cgi); CGImageRelease (cgi); } else { // (bpp == 1) @@ -1584,10 +1737,10 @@ XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, NO); /* interpolate */ push_fg_gc (d, gc, YES); - CGContextFillRect (d->cgc, r); // foreground color - CGContextClipToMask (d->cgc, r, mask); - set_color (d->cgc, gc->gcv.background, gc->depth, NO, YES); - CGContextFillRect (d->cgc, r); // background color + CGContextFillRect (cgc, r); // foreground color + CGContextClipToMask (cgc, r, mask); + set_color (cgc, gc->gcv.background, gc->depth, NO, YES); + CGContextFillRect (cgc, r); // background color pop_gc (d, gc); free (flipped); @@ -1595,6 +1748,8 @@ XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, CGImageRelease (mask); } + invalidate_drawable_cache (d); + return 0; } @@ -1605,33 +1760,56 @@ XGetImage (Display *dpy, Drawable d, int x, int y, unsigned long plane_mask, int format) { const unsigned char *data = 0; - int depth, ibpp, ibpl; + int depth, ibpp, ibpl, alpha_first_p; +# ifndef USE_IPHONE NSBitmapImageRep *bm = 0; +# endif - if (d->type == PIXMAP) { - depth = d->pixmap.depth; - ibpp = CGBitmapContextGetBitsPerPixel (d->cgc); - ibpl = CGBitmapContextGetBytesPerRow (d->cgc); - data = CGBitmapContextGetData (d->cgc); + Assert ((width < 65535), "improbably large width"); + Assert ((height < 65535), "improbably large height"); + Assert ((x < 65535 && x > -65535), "improbably large x"); + Assert ((y < 65535 && y > -65535), "improbably large y"); + + CGContextRef cgc = d->cgc; + +#ifndef USE_IPHONE + // Because of the backbuffer, all iPhone Windows work like Pixmaps. + if (d->type == PIXMAP) +# endif + { + depth = (d->type == PIXMAP + ? d->pixmap.depth + : 32); + // If it's a pixmap, we created it with kCGImageAlphaNoneSkipFirst. + // If it's an iPhone window, it's the other way around. + alpha_first_p = (d->type == PIXMAP); + ibpp = CGBitmapContextGetBitsPerPixel (cgc); + ibpl = CGBitmapContextGetBytesPerRow (cgc); + data = CGBitmapContextGetData (cgc); Assert (data, "CGBitmapContextGetData failed"); - } else { + +# ifndef USE_IPHONE + } else { /* (d->type == WINDOW) */ + // get the bits (desired sub-rectangle) out of the NSView - bm = [NSBitmapImageRep alloc]; NSRect nsfrom; nsfrom.origin.x = x; nsfrom.origin.y = y; nsfrom.size.width = width; nsfrom.size.height = height; - [bm initWithFocusedViewRect:nsfrom]; + bm = [[NSBitmapImageRep alloc] initWithFocusedViewRect:nsfrom]; depth = 32; + alpha_first_p = ([bm bitmapFormat] & NSAlphaFirstBitmapFormat); ibpp = [bm bitsPerPixel]; ibpl = [bm bytesPerRow]; data = [bm bitmapData]; Assert (data, "NSBitmapImageRep initWithFocusedViewRect failed"); - - data += (y * ibpl) + (x * (ibpp/8)); +# endif // !USE_IPHONE } + // data points at (x,y) with ibpl rowstride. ignore x,y from now on. + data += (y * ibpl) + (x * (ibpp/8)); + format = (depth == 1 ? XYPixmap : ZPixmap); XImage *image = XCreateImage (dpy, 0, depth, format, 0, 0, width, height, 0, 0); @@ -1642,19 +1820,23 @@ XGetImage (Display *dpy, Drawable d, int x, int y, /* both PPC and Intel use word-ordered ARGB frame buffers, which means that on Intel it is BGRA when viewed by bytes (And BGR when using 24bpp packing). + + BUT! Intel-64 stores alpha at the other end! 32bit=RGBA, 64bit=ARGB. + The NSAlphaFirstBitmapFormat bit in bitmapFormat seems to be the + indicator of this latest kink. */ int xx, yy; if (depth == 1) { const unsigned char *iline = data; - for (yy = y; yy < y+height; yy++) { + for (yy = 0; yy < height; yy++) { const unsigned char *iline2 = iline; - for (xx = x; xx < x+width; xx++) { + for (xx = 0; xx < width; xx++) { - iline2++; // ignore b or a - iline2++; // ignore g or r - unsigned char r = *iline2++; // r or g - if (ibpp == 32) iline2++; // ignore a or b + iline2++; // ignore R or A or A or B + iline2++; // ignore G or B or R or G + unsigned char r = *iline2++; // use B or G or G or R + if (ibpp == 32) iline2++; // ignore A or R or B or A XPutPixel (image, xx, yy, (r ? 1 : 0)); } @@ -1664,76 +1846,193 @@ XGetImage (Display *dpy, Drawable d, int x, int y, Assert (ibpp == 24 || ibpp == 32, "weird obpp"); const unsigned char *iline = data; unsigned char *oline = (unsigned char *) image->data; - oline += (y * obpl); - for (yy = y; yy < y+height; yy++) { + for (yy = 0; yy < height; yy++) { const unsigned char *iline2 = iline; unsigned char *oline2 = oline; - for (xx = x; xx < x+width; xx++) { - - unsigned char a = (ibpp == 32 ? (*iline2++) : 0xFF); - unsigned char r = *iline2++; - unsigned char g = *iline2++; - unsigned char b = *iline2++; - unsigned long pixel = ((a << 24) | - (r << 16) | - (g << 8) | - (b << 0)); - *((unsigned int *) oline2) = pixel; - oline2 += 4; - } + + if (alpha_first_p) // ARGB + for (xx = 0; xx < width; xx++) { + unsigned char a = (ibpp == 32 ? (*iline2++) : 0xFF); + unsigned char r = *iline2++; + unsigned char g = *iline2++; + unsigned char b = *iline2++; + uint32_t pixel = ((a << 24) | + (r << 16) | + (g << 8) | + (b << 0)); + *((uint32_t *) oline2) = pixel; + oline2 += 4; + } + else // RGBA + for (xx = 0; xx < width; xx++) { + unsigned char r = *iline2++; + unsigned char g = *iline2++; + unsigned char b = *iline2++; + unsigned char a = (ibpp == 32 ? (*iline2++) : 0xFF); + uint32_t pixel = ((a << 24) | + (r << 16) | + (g << 8) | + (b << 0)); + *((uint32_t *) oline2) = pixel; + oline2 += 4; + } + oline += obpl; iline += ibpl; } } +# ifndef USE_IPHONE if (bm) [bm release]; +# endif return image; } + + +/* Returns a transformation matrix to do rotation as per the provided + EXIF "Orientation" value. + */ +static CGAffineTransform +exif_rotate (int rot, CGSize rect) +{ + CGAffineTransform trans = CGAffineTransformIdentity; + switch (rot) { + case 2: // flip horizontal + trans = CGAffineTransformMakeTranslation (rect.width, 0); + trans = CGAffineTransformScale (trans, -1, 1); + break; + + case 3: // rotate 180 + trans = CGAffineTransformMakeTranslation (rect.width, rect.height); + trans = CGAffineTransformRotate (trans, M_PI); + break; + + case 4: // flip vertical + trans = CGAffineTransformMakeTranslation (0, rect.height); + trans = CGAffineTransformScale (trans, 1, -1); + break; + + case 5: // transpose (UL-to-LR axis) + trans = CGAffineTransformMakeTranslation (rect.height, rect.width); + trans = CGAffineTransformScale (trans, -1, 1); + trans = CGAffineTransformRotate (trans, 3 * M_PI / 2); + break; + + case 6: // rotate 90 + trans = CGAffineTransformMakeTranslation (0, rect.width); + trans = CGAffineTransformRotate (trans, 3 * M_PI / 2); + break; + + case 7: // transverse (UR-to-LL axis) + trans = CGAffineTransformMakeScale (-1, 1); + trans = CGAffineTransformRotate (trans, M_PI / 2); + break; + + case 8: // rotate 270 + trans = CGAffineTransformMakeTranslation (rect.height, 0); + trans = CGAffineTransformRotate (trans, M_PI / 2); + break; + + default: + break; + } + + return trans; +} + + void -jwxyz_draw_NSImage (Display *dpy, Drawable d, void *nsimg_arg, - XRectangle *geom_ret) +jwxyz_draw_NSImage_or_CGImage (Display *dpy, Drawable d, + Bool nsimg_p, void *img_arg, + XRectangle *geom_ret, int exif_rotation) { - NSImage *nsimg = (NSImage *) nsimg_arg; + CGImageRef cgi; +# ifndef USE_IPHONE + CGImageSourceRef cgsrc; +# endif // USE_IPHONE + NSSize imgr; + + CGContextRef cgc = d->cgc; + + if (nsimg_p) { + + NSImage *nsimg = (NSImage *) img_arg; + imgr = [nsimg size]; + +# ifndef USE_IPHONE + // convert the NSImage to a CGImage via the toll-free-bridging + // of NSData and CFData... + // + NSData *nsdata = [NSBitmapImageRep + TIFFRepresentationOfImageRepsInArray: + [nsimg representations]]; + CFDataRef cfdata = (CFDataRef) nsdata; + cgsrc = CGImageSourceCreateWithData (cfdata, NULL); + cgi = CGImageSourceCreateImageAtIndex (cgsrc, 0, NULL); +# else // USE_IPHONE + cgi = nsimg.CGImage; +# endif // USE_IPHONE + + } else { + cgi = (CGImageRef) img_arg; + imgr.width = CGImageGetWidth (cgi); + imgr.height = CGImageGetHeight (cgi); + } + + Bool rot_p = (exif_rotation >= 5); + + if (rot_p) + imgr = NSMakeSize (imgr.height, imgr.width); - // convert the NSImage to a CGImage via the toll-free-bridging - // of NSData and CFData... - // - NSData *nsdata = [NSBitmapImageRep - TIFFRepresentationOfImageRepsInArray: - [nsimg representations]]; - CFDataRef cfdata = (CFDataRef) nsdata; - CGImageSourceRef cgsrc = CGImageSourceCreateWithData (cfdata, NULL); - CGImageRef cgi = CGImageSourceCreateImageAtIndex (cgsrc, 0, NULL); - - NSSize imgr = [nsimg size]; CGRect winr = d->frame; float rw = winr.size.width / imgr.width; float rh = winr.size.height / imgr.height; float r = (rw < rh ? rw : rh); - CGRect dst; + CGRect dst, dst2; dst.size.width = imgr.width * r; dst.size.height = imgr.height * r; dst.origin.x = (winr.size.width - dst.size.width) / 2; dst.origin.y = (winr.size.height - dst.size.height) / 2; + dst2.origin.x = dst2.origin.y = 0; + if (rot_p) { + dst2.size.width = dst.size.height; + dst2.size.height = dst.size.width; + } else { + dst2.size = dst.size; + } + // Clear the part not covered by the image to background or black. // if (d->type == WINDOW) XClearWindow (dpy, d); else { - set_color (d->cgc, BlackPixel(dpy,0), 32, NO, YES); + set_color (cgc, BlackPixel(dpy,0), 32, NO, YES); draw_rect (dpy, d, 0, 0, 0, winr.size.width, winr.size.height, NO, YES); } + CGAffineTransform trans = + exif_rotate (exif_rotation, rot_p ? dst2.size : dst.size); + + CGContextSaveGState (cgc); + CGContextConcatCTM (cgc, + CGAffineTransformMakeTranslation (dst.origin.x, + dst.origin.y)); + CGContextConcatCTM (cgc, trans); //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace"); - CGContextDrawImage (d->cgc, dst, cgi); + CGContextDrawImage (cgc, dst2, cgi); + CGContextRestoreGState (cgc); - CFRelease (cgsrc); - CGImageRelease (cgi); +# ifndef USE_IPHONE + if (nsimg_p) { + CFRelease (cgsrc); + CGImageRelease (cgi); + } +# endif // USE_IPHONE if (geom_ret) { geom_ret->x = dst.origin.x; @@ -1741,9 +2040,12 @@ jwxyz_draw_NSImage (Display *dpy, Drawable d, void *nsimg_arg, geom_ret->width = dst.size.width; geom_ret->height = dst.size.height; } + + invalidate_drawable_cache (d); } + Pixmap XCreatePixmapFromBitmapData (Display *dpy, Drawable drawable, const char *data, @@ -1777,9 +2079,10 @@ XCreatePixmap (Display *dpy, Drawable d, p->frame.size.width = width; p->frame.size.height = height; p->pixmap.depth = depth; + p->pixmap.cgc_buffer = data; /* Quartz doesn't have a 1bpp image type. - We used to use 8bpp gray images instead of 1bpp, but some Mac video + Used to use 8bpp gray images instead of 1bpp, but some Mac video cards don't support that! So we always use 32bpp, regardless of depth. */ p->cgc = CGBitmapContextCreate (data, width, height, @@ -1798,20 +2101,41 @@ int XFreePixmap (Display *d, Pixmap p) { Assert (p->type == PIXMAP, "not a pixmap"); + invalidate_drawable_cache (p); CGContextRelease (p->cgc); + if (p->pixmap.cgc_buffer) + free (p->pixmap.cgc_buffer); free (p); return 0; } static Pixmap -copy_pixmap (Pixmap p) +copy_pixmap (Display *dpy, Pixmap p) { if (!p) return 0; Assert (p->type == PIXMAP, "not a pixmap"); + + int width = p->frame.size.width; + int height = p->frame.size.height; + char *data = (char *) malloc (width * height * 4); + if (! data) return 0; + + memcpy (data, p->pixmap.cgc_buffer, width * height * 4); + Pixmap p2 = (Pixmap) malloc (sizeof (*p2)); *p2 = *p; - CGContextRetain (p2->cgc); // #### is this ok? need to copy it instead? + p2->cgi = 0; + p2->pixmap.cgc_buffer = data; + p2->cgc = CGBitmapContextCreate (data, width, height, + 8, /* bits per component */ + width * 4, /* bpl */ + dpy->colorspace, + // Without this, it returns 0... + kCGImageAlphaNoneSkipFirst + ); + Assert (p2->cgc, "could not create CGBitmapContext"); + return p2; } @@ -1842,6 +2166,15 @@ copy_pixmap (Pixmap p) static void query_font (Font fid) { + if (!fid || !fid->nsfont) { + Assert (0, "no NSFont in fid"); + return; + } + if (![fid->nsfont fontName]) { + Assert(0, @"broken NSFont in fid"); + return; + } + int first = 32; int last = 255; @@ -1867,7 +2200,15 @@ query_font (Font fid) f->per_char = (XCharStruct *) calloc (last-first+2, sizeof (XCharStruct)); int i; +# ifndef USE_IPHONE NSBezierPath *bpath = [NSBezierPath bezierPath]; +# else // USE_IPHONE + CTFontRef ctfont = + CTFontCreateWithName ((CFStringRef) [fid->nsfont fontName], + [fid->nsfont pointSize], + NULL); + Assert (ctfont, @"no CTFontRef for UIFont"); +# endif // USE_IPHONE for (i = first; i <= last; i++) { unsigned char str[2]; @@ -1876,6 +2217,10 @@ query_font (Font fid) NSString *nsstr = [NSString stringWithCString:(char *) str encoding:NSISOLatin1StringEncoding]; + NSPoint advancement = { 0, }; + NSRect bbox = {{ 0, }, }; + +# ifndef USE_IPHONE /* I can't believe we have to go through this bullshit just to convert a 'char' to an NSGlyph!! @@ -1890,6 +2235,14 @@ query_font (Font fid) NSTextStorage *ts = [[NSTextStorage alloc] initWithString:nsstr]; [ts setFont:fid->nsfont]; NSLayoutManager *lm = [[NSLayoutManager alloc] init]; + + /* Without this, the layout manager ends up on a queue somewhere and is + referenced again after we return to the command loop. Since we don't + use this layout manager again, by that time it may have been garbage + collected, and we crash. Setting this seems to cause `lm' to no + longer be referenced once we exit this block. */ + [lm setBackgroundLayoutEnabled:NO]; + NSTextContainer *tc = [[NSTextContainer alloc] init]; [lm addTextContainer:tc]; [tc release]; // lm retains tc @@ -1905,8 +2258,6 @@ query_font (Font fid) [NSString sizeWithAttributes] both return an advancement-sized rectangle, not a rectangle completely enclosing the glyph's ink. */ - NSPoint advancement; - NSRect bbox; advancement.x = advancement.y = 0; [bpath removeAllPoints]; [bpath moveToPoint:advancement]; @@ -1914,6 +2265,39 @@ query_font (Font fid) advancement = [bpath currentPoint]; bbox = [bpath bounds]; +# else // USE_IPHONE + + /* There is no way to get "lbearing", "rbearing" or "descent" out of + NSFont. 'sizeWithFont' gives us "width" and "height" only. + Likewise, 'drawAtPoint' (to an offscreen CGContext) gives us the + width of the character and the ascent of the font. + + Maybe we could use CGFontGetGlyphBBoxes() and avoid linking in + the CoreText library, but there's no non-CoreText way to turn a + unichar into a CGGlyph. + */ + UniChar uchar = [nsstr characterAtIndex: 0]; + CGGlyph cgglyph = 0; + + if (CTFontGetGlyphsForCharacters (ctfont, &uchar, &cgglyph, 1)) + { + bbox = CTFontGetBoundingRectsForGlyphs (ctfont, + kCTFontDefaultOrientation, + &cgglyph, NULL, 1); + CGSize adv = { 0, }; + CTFontGetAdvancesForGlyphs (ctfont, kCTFontDefaultOrientation, + &cgglyph, &adv, 1); + advancement.x = adv.width; + advancement.y = adv.height; + + // Seems to be clipping by a pixel or two. Add a margin to be safe. + bbox.origin.x -= 2; + bbox.origin.y -= 2; + bbox.size.width += 4; + bbox.size.height += 4; + } +# endif // USE_IPHONE + /* Now that we know the advancement and bounding box, we can compute the lbearing and rbearing. */ @@ -1955,6 +2339,9 @@ query_font (Font fid) #endif } +# ifdef USE_IPHONE + CFRelease (ctfont); +# endif } @@ -1993,7 +2380,7 @@ copy_font (Font fid) // copy the other pointers fid2->ps_name = strdup (fid->ps_name); - [fid2->nsfont retain]; +// [fid2->nsfont retain]; fid2->metrics.fid = fid2; return fid2; @@ -2004,36 +2391,63 @@ static NSFont * try_font (BOOL fixed, BOOL bold, BOOL ital, BOOL serif, float size, char **name_ret) { - const char *prefix = (fixed ? "Monaco" : (serif ? "Times" : "Helvetica")); - const char *suffix = (bold && ital - ? (serif ? "-BoldItalic" : "-BoldOblique") - : (bold ? "-Bold" : - ital ? (serif ? "-Italic" : "-Oblique") : "")); - char *name = (char *) malloc (strlen(prefix) + strlen(suffix) + 1); - strcpy (name, prefix); - strcat (name, suffix); + Assert (size > 0, "zero font size"); + const char *name; + + if (fixed) { + // + // "Monaco" only exists in plain. + // "LucidaSansTypewriterStd" gets an AGL bad value error. + // + if (bold && ital) name = "Courier-BoldOblique"; + else if (bold) name = "Courier-Bold"; + else if (ital) name = "Courier-Oblique"; + else name = "Courier"; + + } else if (serif) { + // + // "Georgia" looks better than "Times". + // + if (bold && ital) name = "Georgia-BoldItalic"; + else if (bold) name = "Georgia-Bold"; + else if (ital) name = "Georgia-Italic"; + else name = "Georgia"; + + } else { + // + // "Geneva" only exists in plain. + // "LucidaSansStd-BoldItalic" gets an AGL bad value error. + // "Verdana" renders smoother than "Helvetica" for some reason. + // + if (bold && ital) name = "Verdana-BoldItalic"; + else if (bold) name = "Verdana-Bold"; + else if (ital) name = "Verdana-Italic"; + else name = "Verdana"; + } NSString *nsname = [NSString stringWithCString:name encoding:NSUTF8StringEncoding]; NSFont *f = [NSFont fontWithName:nsname size:size]; - if (f) { - *name_ret = name; - } else { - free (name); - } + if (f) + *name_ret = strdup(name); return f; } static NSFont * -try_native_font (const char *name, char **name_ret, float *size_ret) +try_native_font (const char *name, float scale, + char **name_ret, float *size_ret) { if (!name) return 0; const char *spc = strrchr (name, ' '); if (!spc) return 0; - int size = 0; - if (1 != sscanf (spc, " %d ", &size)) return 0; + int dsize = 0; + if (1 != sscanf (spc, " %d ", &dsize)) return 0; + float size = dsize; + if (size <= 4) return 0; + size *= scale; + char *name2 = strdup (name); name2[strlen(name2) - strlen(spc)] = 0; NSString *nsname = [NSString stringWithCString:name2 @@ -2055,8 +2469,9 @@ try_native_font (const char *name, char **name_ret, float *size_ret) static NSFont * random_font (BOOL bold, BOOL ital, float size, char **name_ret) { - NSFontTraitMask mask = ((bold ? NSUnboldFontMask : NSBoldFontMask) | - (ital ? NSUnitalicFontMask : NSItalicFontMask)); +# ifndef USE_IPHONE + NSFontTraitMask mask = ((bold ? NSBoldFontMask : NSUnboldFontMask) | + (ital ? NSItalicFontMask : NSUnitalicFontMask)); NSArray *fonts = [[NSFontManager sharedFontManager] availableFontNamesWithTraits:mask]; if (!fonts) return 0; @@ -2093,11 +2508,70 @@ random_font (BOOL bold, BOOL ital, float size, char **name_ret) // None of the fonts support ASCII? return 0; + +# else // USE_IPHONE + + NSMutableArray *fonts = [NSMutableArray arrayWithCapacity:100]; + NSArray *families = [UIFont familyNames]; + NSMutableDictionary *famdict = [NSMutableDictionary + dictionaryWithCapacity:100]; + NSObject *y = [NSNumber numberWithBool:YES]; + for (NSString *name in families) { + // There are many dups in the families array -- uniquify it. + [famdict setValue:y forKey:name]; + } + + for (NSString *name in famdict) { + for (NSString *fn in [UIFont fontNamesForFamilyName:name]) { + +# define MATCH(X) \ + ([fn rangeOfString:X options:NSCaseInsensitiveSearch].location \ + != NSNotFound) + + BOOL bb = MATCH(@"Bold"); + BOOL ii = MATCH(@"Italic") || MATCH(@"Oblique"); + + if (!bold != !bb) continue; + if (!ital != !ii) continue; + + /* Check if it can do ASCII. No good way to accomplish this! + These are fonts present in iPhone Simulator as of June 2012 + that don't include ASCII. + */ + if (MATCH(@"AppleGothic") || // Korean + MATCH(@"Dingbats") || // Dingbats + MATCH(@"Emoji") || // Emoticons + MATCH(@"Geeza") || // Arabic + MATCH(@"Hebrew") || // Hebrew + MATCH(@"HiraKaku") || // Japanese + MATCH(@"HiraMin") || // Japanese + MATCH(@"Kailasa") || // Tibetan + MATCH(@"Ornaments") || // Dingbats + MATCH(@"STHeiti") // Chinese + ) + continue; + + [fonts addObject:fn]; +# undef MATCH + } + } + + if (! [fonts count]) return 0; // Nothing suitable? + + int i = random() % [fonts count]; + NSString *name = [fonts objectAtIndex:i]; + UIFont *ff = [UIFont fontWithName:name size:size]; + *name_ret = strdup ([name cStringUsingEncoding:NSUTF8StringEncoding]); + + return ff; + +# endif // USE_IPHONE } static NSFont * -try_xlfd_font (const char *name, char **name_ret, float *size_ret) +try_xlfd_font (const char *name, float scale, + char **name_ret, float *size_ret) { NSFont *nsfont = 0; BOOL bold = NO; @@ -2148,6 +2622,8 @@ try_xlfd_font (const char *name, char **name_ret, float *size_ret) if (size < 6 || size > 1000) size = 12; + size *= scale; + if (rand) nsfont = random_font (bold, ital, size, &ps_name); @@ -2189,53 +2665,40 @@ XLoadFont (Display *dpy, const char *name) { Font fid = (Font) calloc (1, sizeof(*fid)); - fid->nsfont = try_native_font (name, &fid->ps_name, &fid->size); - if (! fid->nsfont) - fid->nsfont = try_xlfd_font (name, &fid->ps_name, &fid->size); - if (!fid->nsfont) { - NSLog(@"no NSFont for \"%s\"", name); - abort(); - } + float scale = 1; - //NSLog(@"parsed \"%s\" to %s %.1f", name, fid->ps_name, fid->size); +# ifdef USE_IPHONE + // Scale up fonts on Retina displays. + scale = dpy->main_window->window.view.contentScaleFactor; +# endif - query_font (fid); + fid->nsfont = try_native_font (name, scale, &fid->ps_name, &fid->size); - return fid; -} + if (!fid->nsfont && name && + strchr (name, ' ') && + !strchr (name, '*')) { + // If name contains a space but no stars, it is a native font spec -- + // return NULL so that we know it really didn't exist. Else, it is an + // XLFD font, so keep trying. + XUnloadFont (dpy, fid); + return 0; + } + if (! fid->nsfont) + fid->nsfont = try_xlfd_font (name, scale, &fid->ps_name, &fid->size); -/* This translates the NSFont into the numbers that aglUseFont() wants. - */ -int -jwxyz_font_info (Font f, int *size_ret, int *face_ret) -{ - char *name = strdup (f->ps_name); - char *dash = strchr (name, '-'); - int flags = 0; - int size = f->size; - if (dash) { - // 0 = plain; 1=B; 2=I; 3=BI; 4=U; 5=UB; etc. - if (strcasestr (dash, "bold")) flags |= 1; - if (strcasestr (dash, "italic")) flags |= 2; - if (strcasestr (dash, "oblique")) flags |= 2; - *dash = 0; + // We should never return NULL for XLFD fonts. + if (!fid->nsfont) { + Assert (0, "no font"); + return 0; } - NSString *nname = [NSString stringWithCString:name - encoding:NSUTF8StringEncoding]; - ATSFontFamilyRef id = - ATSFontFamilyFindFromName ((CFStringRef) nname, kATSOptionFlagsDefault); - + CFRetain (fid->nsfont); // needed for garbage collection? - // WTF? aglUseFont gets a BadValue if size is small!! - if (size < 9) size = 9; + //NSLog(@"parsed \"%s\" to %s %.1f", name, fid->ps_name, fid->size); - //NSLog (@"font %s %.1f => %d %d %d\n", f->ps_name, f->size, id, flags, size); - Assert (id >= 0, "no ATS font family"); + query_font (fid); - *size_ret = size; - *face_ret = flags; - return id; + return fid; } @@ -2243,15 +2706,26 @@ XFontStruct * XLoadQueryFont (Display *dpy, const char *name) { Font fid = XLoadFont (dpy, name); + if (!fid) return 0; return XQueryFont (dpy, fid); } int XUnloadFont (Display *dpy, Font fid) { - free (fid->ps_name); - free (fid->metrics.per_char); - [fid->nsfont release]; + if (fid->ps_name) + free (fid->ps_name); + if (fid->metrics.per_char) + free (fid->metrics.per_char); + + // #### DAMMIT! I can't tell what's going wrong here, but I keep getting + // crashes in [NSFont ascender] <- query_font, and it seems to go away + // if I never release the nsfont. So, fuck it, we'll just leak fonts. + // They're probably not very big... + // + // [fid->nsfont release]; + // CFRelease (fid->nsfont); + free (fid); return 0; } @@ -2290,15 +2764,11 @@ XSetFont (Display *dpy, GC gc, Font fid) if (gc->gcv.font) XUnloadFont (dpy, gc->gcv.font); gc->gcv.font = copy_font (fid); + [gc->gcv.font->nsfont retain]; + CFRetain (gc->gcv.font->nsfont); // needed for garbage collection? return 0; } -Font // really GContext -XGContextFromGC (GC gc) // WTF is this actually supposed to do? -{ - return gc->gcv.font; -} - int XTextExtents (XFontStruct *f, const char *s, int length, int *dir_ret, int *ascent_ret, int *descent_ret, @@ -2338,11 +2808,15 @@ XTextWidth (XFontStruct *f, const char *s, int length) static void -set_font (CGContextRef cgc, GC gc) +set_font (Display *dpy, CGContextRef cgc, GC gc) { Font font = gc->gcv.font; - if (! font) - font = gc->gcv.font = XLoadFont (0, 0); + if (! font) { + font = XLoadFont (dpy, 0); + gc->gcv.font = font; + [gc->gcv.font->nsfont retain]; + CFRetain (gc->gcv.font->nsfont); // needed for garbage collection? + } CGContextSelectFont (cgc, font->ps_name, font->size, kCGEncodingMacRoman); CGContextSetTextMatrix (cgc, CGAffineTransformIdentity); } @@ -2374,19 +2848,20 @@ draw_string (Display *dpy, Drawable d, GC gc, int x, int y, But the Cocoa way only works on NSView, not on CGContextRef (pixmaps)! */ + CGContextRef cgc = d->cgc; push_fg_gc (d, gc, YES); - set_font (d->cgc, gc); + set_font (dpy, cgc, gc); - CGContextSetTextDrawingMode (d->cgc, kCGTextFill); - if (! gc->gcv.antialias_p) - CGContextSetShouldAntialias (d->cgc, YES); // always antialias text - CGContextShowTextAtPoint (d->cgc, + CGContextSetTextDrawingMode (cgc, kCGTextFill); + if (gc->gcv.antialias_p) + CGContextSetShouldAntialias (cgc, YES); + CGContextShowTextAtPoint (cgc, wr.origin.x + x, wr.origin.y + wr.size.height - y, str, len); pop_gc (d, gc); -#else /* !0 */ +# else /* !0 */ /* The Cocoa way... */ @@ -2414,8 +2889,9 @@ draw_string (Display *dpy, Drawable d, GC gc, int x, int y, pos.y = wr.origin.y + wr.size.height - y - gc->gcv.font->metrics.descent; [nsstr drawAtPoint:pos withAttributes:attr]; -#endif /* 0 */ +# endif /* 0 */ + invalidate_drawable_cache (d); return 0; } @@ -2509,9 +2985,10 @@ XSetClipMask (Display *dpy, GC gc, Pixmap m) CGImageRelease (gc->clip_mask); } - gc->gcv.clip_mask = copy_pixmap (m); + gc->gcv.clip_mask = copy_pixmap (dpy, m); if (gc->gcv.clip_mask) - gc->clip_mask = CGBitmapContextCreateImage (gc->gcv.clip_mask->cgc); + gc->clip_mask = + CGBitmapContextCreateImage (gc->gcv.clip_mask->cgc); else gc->clip_mask = 0; @@ -2533,6 +3010,17 @@ XQueryPointer (Display *dpy, Window w, Window *root_ret, Window *child_ret, int *win_x_ret, int *win_y_ret, unsigned int *mask_ret) { Assert (w->type == WINDOW, "not a window"); + +# ifdef USE_IPHONE + int x = w->window.last_mouse_x; + int y = w->window.last_mouse_y; + if (root_x_ret) *root_x_ret = x; + if (root_y_ret) *root_y_ret = y; + if (win_x_ret) *win_x_ret = x; + if (win_y_ret) *win_y_ret = y; + +# else // !USE_IPHONE + NSWindow *nsw = [w->window.view window]; NSPoint wpos; // get bottom left of window on screen, from bottom left @@ -2556,8 +3044,13 @@ XQueryPointer (Display *dpy, Window w, Window *root_ret, Window *child_ret, NSScreen *screen = (screens && [screens count] > 0 ? [screens objectAtIndex:0] : [NSScreen mainScreen]); +#ifdef USE_IPHONE + double s = w->window.view.contentScaleFactor; +#else + int s = 1; +#endif NSRect srect = [screen frame]; - vpos.y = srect.size.height - vpos.y; + vpos.y = (s * srect.size.height) - vpos.y; // get the mouse position on window, from bottom left NSEvent *e = [NSApp currentEvent]; @@ -2574,8 +3067,9 @@ XQueryPointer (Display *dpy, Window w, Window *root_ret, Window *child_ret, if (root_y_ret) *root_y_ret = (int) p.y; if (win_x_ret) *win_x_ret = (int) (p.x - vpos.x); if (win_y_ret) *win_y_ret = (int) (p.y - vpos.y); +# endif // !USE_IPHONE - if (mask_ret) *mask_ret = 0; // #### + if (mask_ret) *mask_ret = 0; // #### poll the keyboard modifiers? if (root_ret) *root_ret = 0; if (child_ret) *child_ret = 0; return True; @@ -2588,6 +3082,15 @@ XTranslateCoordinates (Display *dpy, Window w, Window dest_w, Window *child_ret) { Assert (w->type == WINDOW, "not a window"); + +# ifdef USE_IPHONE + + NSPoint p; + p.x = src_x; + p.y = src_y; + +# else // !USE_IPHONE + NSWindow *nsw = [w->window.view window]; NSPoint wpos; // get bottom left of window on screen, from bottom left @@ -2611,8 +3114,13 @@ XTranslateCoordinates (Display *dpy, Window w, Window dest_w, NSScreen *screen = (screens && [screens count] > 0 ? [screens objectAtIndex:0] : [NSScreen mainScreen]); +# ifdef USE_IPHONE + double s = w->window.view.contentScaleFactor; +# else + int s = 1; +# endif NSRect srect = [screen frame]; - vpos.y = srect.size.height - vpos.y; + vpos.y = (s * srect.size.height) - vpos.y; // point starts out relative to top left of view NSPoint p; @@ -2622,6 +3130,7 @@ XTranslateCoordinates (Display *dpy, Window w, Window dest_w, // get point relative to top left of screen p.x += vpos.x; p.y += vpos.y; +# endif // !USE_IPHONE *dest_x_ret = p.x; *dest_y_ret = p.y;