X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=OSX%2Fjwxyz.m.orig;fp=OSX%2Fjwxyz.m.orig;h=080ce0ce8e961c53c0a04119fd13b3e636bf93a9;hp=0000000000000000000000000000000000000000;hb=019de959b265701cd0c3fccbb61f2b69f06bf9ee;hpb=2762a7d7cf8d83e68b8f635941f6609119d630ae diff --git a/OSX/jwxyz.m.orig b/OSX/jwxyz.m.orig new file mode 100644 index 00000000..080ce0ce --- /dev/null +++ b/OSX/jwxyz.m.orig @@ -0,0 +1,3499 @@ +/* xscreensaver, Copyright (c) 1991-2013 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 + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* JWXYZ Is Not Xlib. + + But it's a bunch of function definitions that bear some resemblance to + Xlib and that do Cocoa-ish things that bear some resemblance to the + things that Xlib might have done. + */ + +#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" + +# define USE_BACKBUFFER /* must be in sync with XScreenSaverView.h */ + +#undef Assert +#define Assert(C,S) do { if (!(C)) jwxyz_abort ("%s",(S)); } while(0) + +# undef MAX +# undef MIN +# define MAX(a,b) ((a)>(b)?(a):(b)) +# define MIN(a,b) ((a)<(b)?(a):(b)) + + +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; + }; +}; + +struct jwxyz_Display { + Window main_window; + 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 + when rendering. */ +}; + +struct jwxyz_Screen { + Display *dpy; + Visual *visual; +}; + +struct jwxyz_GC { + XGCValues gcv; + unsigned int depth; + CGImageRef clip_mask; // CGImage copy of the Pixmap in gcv.clip_mask +}; + +struct jwxyz_Font { + char *ps_name; + NSFont *nsfont; + float size; // points + + // In X11, "Font" is just an ID, and "XFontStruct" contains the metrics. + // But we need the metrics on both of them, so they go here. + XFontStruct metrics; +}; + + +/* 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, void *cgc_arg) +{ + CGContextRef cgc = (CGContextRef) cgc_arg; + NSView *view = (NSView *) nsview_arg; + Assert (view, "no view"); + if (!view) return 0; + + Display *d = (Display *) calloc (1, sizeof(*d)); + d->screen = (Screen *) calloc (1, sizeof(Screen)); + d->screen->dpy = d; + + Visual *v = (Visual *) calloc (1, sizeof(Visual)); + v->class = TrueColor; + v->red_mask = 0x00FF0000; + v->green_mask = 0x0000FF00; + v->blue_mask = 0x000000FF; + v->bits_per_rgb = 8; + d->screen->visual = v; + + d->timers_data = jwxyz_sources_init (XtDisplayToApplicationContext (d)); + + + Window w = (Window) calloc (1, sizeof(*w)); + w->type = WINDOW; + w->window.view = view; + CFRetain (w->window.view); // needed for garbage collection? + w->window.background = BlackPixel(0,0); + + d->main_window = 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; +} + +void +jwxyz_free_display (Display *dpy) +{ + jwxyz_XtRemoveInput_all (dpy); + // #### jwxyz_XtRemoveTimeOut_all (); + + free (dpy->screen->visual); + free (dpy->screen); + free (dpy->main_window); + free (dpy); +} + + +void * +jwxyz_window_view (Window w) +{ + Assert (w && w->type == WINDOW, "not a window"); + 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, + int new_x, int new_y, int new_width, int new_height, + void *cgc_arg) +{ + CGContextRef cgc = (CGContextRef) cgc_arg; + Assert (w && w->type == WINDOW, "not a window"); + 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; + XTranslateCoordinates (dpy, w, NULL, 0, 0, &wx, &wy, NULL); + CGPoint p; + p.x = wx; + p.y = wy; + CGDisplayCount n; + dpy->cgdpy = 0; + CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n); + // Auuugh! + if (! dpy->cgdpy) { + p.x = p.y = 0; + CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n); + } + Assert (dpy->cgdpy, "unable to find CGDisplay"); + } +# endif // USE_IPHONE + +# ifndef USE_BACKBUFFER + // Funny thing: As of OS X 10.9, if USE_BACKBUFFER is turned off, + // then this one's faster. + + { + // Figure out this screen's colorspace, and use that for every CGImage. + // + CMProfileRef profile = 0; + CMGetProfileByAVID ((CMDisplayIDType) dpy->cgdpy, &profile); + Assert (profile, "unable to find colorspace profile"); + dpy->colorspace = CGColorSpaceCreateWithPlatformColorSpace (profile); + Assert (dpy->colorspace, "unable to find colorspace"); + } +# else // USE_BACKBUFFER + + // WTF? It's faster if we *do not* use the screen's colorspace! + // + dpy->colorspace = CGColorSpaceCreateDeviceRGB(); +# endif // USE_BACKBUFFER + + invalidate_drawable_cache (w); +} + + +#ifdef USE_IPHONE +void +jwxyz_mouse_moved (Display *dpy, Window w, int x, int y) +{ + Assert (w && w->type == WINDOW, "not a window"); + w->window.last_mouse_x = x; + w->window.last_mouse_y = y; +} +#endif // USE_IPHONE + + +void +jwxyz_flush_context (Display *dpy) +{ + // This is only used when USE_BACKBUFFER is off. + CGContextFlush(dpy->main_window->cgc); // CGContextSynchronize is another possibility. +} + +jwxyz_sources_data * +display_sources_data (Display *dpy) +{ + return dpy->timers_data; +} + + +Window +XRootWindow (Display *dpy, int screen) +{ + return dpy->main_window; +} + +Screen * +XDefaultScreenOfDisplay (Display *dpy) +{ + return dpy->screen; +} + +Visual * +XDefaultVisualOfScreen (Screen *screen) +{ + return screen->visual; +} + +Display * +XDisplayOfScreen (Screen *s) +{ + return s->dpy; +} + +int +XDisplayNumberOfScreen (Screen *s) +{ + return 0; +} + +int +XScreenNumberOfScreen (Screen *s) +{ + return 0; +} + +int +XDisplayWidth (Display *dpy, int screen) +{ + return (int) dpy->main_window->frame.size.width; +} + +int +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) +{ + if (depth == 1) + Assert ((pixel == 0 || pixel == 1), "bogus mono pixel"); + else if (!alpha_allowed_p) + Assert (((pixel & BlackPixel(0,0)) == BlackPixel(0,0)), + "bogus color pixel"); +} + + +static void +set_color (CGContextRef cgc, unsigned long argb, unsigned int depth, + BOOL alpha_allowed_p, BOOL fill_p) +{ + validate_pixel (argb, depth, alpha_allowed_p); + if (depth == 1) { + if (fill_p) + CGContextSetGrayFillColor (cgc, (argb ? 1.0 : 0.0), 1.0); + else + CGContextSetGrayStrokeColor (cgc, (argb ? 1.0 : 0.0), 1.0); + } else { + float a = ((argb >> 24) & 0xFF) / 255.0; + float r = ((argb >> 16) & 0xFF) / 255.0; + float g = ((argb >> 8) & 0xFF) / 255.0; + float b = ((argb ) & 0xFF) / 255.0; + if (fill_p) + CGContextSetRGBFillColor (cgc, r, g, b, a); + else + CGContextSetRGBStrokeColor (cgc, r, g, b, a); + } +} + +static void +set_line_mode (CGContextRef cgc, XGCValues *gcv) +{ + CGContextSetLineWidth (cgc, gcv->line_width ? gcv->line_width : 1); + CGContextSetLineJoin (cgc, + gcv->join_style == JoinMiter ? kCGLineJoinMiter : + gcv->join_style == JoinRound ? kCGLineJoinRound : + kCGLineJoinBevel); + CGContextSetLineCap (cgc, + gcv->cap_style == CapNotLast ? kCGLineCapButt : + gcv->cap_style == CapButt ? kCGLineCapButt : + gcv->cap_style == CapRound ? kCGLineCapRound : + kCGLineCapSquare); +} + +static void +set_clip_mask (Drawable d, GC gc) +{ + Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup"); + + Pixmap p = gc->gcv.clip_mask; + if (!p) return; + Assert (p->type == PIXMAP, "not a pixmap"); + + CGRect wr = d->frame; + CGRect to; + to.origin.x = wr.origin.x + gc->gcv.clip_x_origin; + to.origin.y = wr.origin.y + wr.size.height - gc->gcv.clip_y_origin + - p->frame.size.height; + to.size.width = p->frame.size.width; + to.size.height = p->frame.size.height; + + CGContextClipToMask (d->cgc, to, gc->clip_mask); +} + + +/* Pushes a GC context; sets BlendMode and ClipMask. + */ +static void +push_gc (Drawable d, GC gc) +{ + CGContextRef cgc = d->cgc; + CGContextSaveGState (cgc); + + switch (gc->gcv.function) { + case GXset: + case GXclear: + case GXcopy:/*CGContextSetBlendMode (cgc, kCGBlendModeNormal);*/ break; + case GXxor: CGContextSetBlendMode (cgc, kCGBlendModeDifference); break; + case GXor: CGContextSetBlendMode (cgc, kCGBlendModeLighten); break; + case GXand: CGContextSetBlendMode (cgc, kCGBlendModeDarken); 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) + + +/* Pushes a GC context; sets BlendMode, ClipMask, Fill, and Stroke colors. + */ +static void +push_color_gc (Drawable d, GC gc, unsigned long color, + BOOL antialias_p, Bool fill_p) +{ + push_gc (d, gc); + + int depth = gc->depth; + switch (gc->gcv.function) { + case GXset: color = (depth == 1 ? 1 : WhitePixel(0,0)); break; + case GXclear: color = (depth == 1 ? 0 : BlackPixel(0,0)); break; + } + + CGContextRef cgc = d->cgc; + set_color (cgc, color, depth, gc->gcv.alpha_allowed_p, fill_p); + CGContextSetShouldAntialias (cgc, antialias_p); +} + + +/* Pushes a GC context; sets Fill and Stroke colors to the foreground color. + */ +static void +push_fg_gc (Drawable d, GC gc, Bool fill_p) +{ + push_color_gc (d, gc, gc->gcv.foreground, gc->gcv.antialias_p, fill_p); +} + +/* Pushes a GC context; sets Fill and Stroke colors to the background color. + */ +static void +push_bg_gc (Drawable d, GC gc, Bool fill_p) +{ + push_color_gc (d, gc, gc->gcv.background, gc->gcv.antialias_p, fill_p); +} + + + +/* You've got to be fucking kidding me! + + 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() with a list of 1x1 rectangles! + + I still wouldn't call it *fast*, however... + */ +#define XDRAWPOINTS_IMAGES + +/* Update, 2012: Kurt Revis points out that diddling + the bitmap data directly is faster. This only works on Pixmaps, though, + not Windows. (Fortunately, on iOS, the Window is really a Pixmap.) + */ +#define XDRAWPOINTS_CGDATA + +int +XDrawPoints (Display *dpy, Drawable d, GC gc, + XPoint *points, int count, int mode) +{ + int i; + CGRect wr = d->frame; + + push_fg_gc (d, gc, YES); + +# ifdef XDRAWPOINTS_CGDATA + +# ifdef USE_BACKBUFFER + if (1) // Because of the backbuffer, all iPhone Windows work like Pixmaps. +# else + if (d->type == PIXMAP) +# endif + { + CGContextRef cgc = d->cgc; + void *data = CGBitmapContextGetData (cgc); + size_t bpr = CGBitmapContextGetBytesPerRow (cgc); + size_t w = CGBitmapContextGetWidth (cgc); + size_t h = CGBitmapContextGetHeight (cgc); + + Assert (data, "no bitmap data in Drawable"); + + unsigned int argb = gc->gcv.foreground; + validate_pixel (argb, gc->depth, gc->gcv.alpha_allowed_p); + if (gc->depth == 1) + argb = (gc->gcv.foreground ? WhitePixel(0,0) : BlackPixel(0,0)); + + CGFloat x0 = wr.origin.x; + CGFloat y0 = wr.origin.y; // Y axis is refreshingly not flipped. + + // It's uglier, but faster, to hoist the conditional out of the loop. + if (mode == CoordModePrevious) { + CGFloat x = x0, y = y0; + for (i = 0; i < count; i++, points++) { + x += points->x; + y += points->y; + + if (x >= 0 && x < w && y >= 0 && y < h) { + unsigned int *p = (unsigned int *) + ((char *) data + (size_t) y * bpr + (size_t) x * 4); + *p = argb; + } + } + } else { + for (i = 0; i < count; i++, points++) { + CGFloat x = x0 + points->x; + CGFloat y = y0 + points->y; + + if (x >= 0 && x < w && y >= 0 && y < h) { + unsigned int *p = (unsigned int *) + ((char *) data + (size_t) y * bpr + (size_t) x * 4); + *p = argb; + } + } + } + + } else /* d->type == WINDOW */ + +# endif /* XDRAWPOINTS_CGDATA */ + { + +# ifdef XDRAWPOINTS_IMAGES + + unsigned int argb = gc->gcv.foreground; + validate_pixel (argb, gc->depth, gc->gcv.alpha_allowed_p); + if (gc->depth == 1) + argb = (gc->gcv.foreground ? WhitePixel(0,0) : BlackPixel(0,0)); + + CGDataProviderRef prov = CGDataProviderCreateWithData (NULL, &argb, 4, + NULL); + CGImageRef cgi = CGImageCreate (1, 1, + 8, 32, 4, + dpy->colorspace, + /* Host-ordered, since we're using the + address of an int as the color data. */ + (kCGImageAlphaNoneSkipFirst | + kCGBitmapByteOrder32Host), + prov, + NULL, /* decode[] */ + NO, /* interpolate */ + kCGRenderingIntentDefault); + CGDataProviderRelease (prov); + + CGContextRef cgc = d->cgc; + CGRect rect; + rect.size.width = rect.size.height = 1; + for (i = 0; i < count; i++) { + if (i > 0 && mode == CoordModePrevious) { + rect.origin.x += points->x; + rect.origin.x -= points->y; + } else { + rect.origin.x = wr.origin.x + points->x; + rect.origin.y = wr.origin.y + wr.size.height - points->y - 1; + } + + //Assert(CGImageGetColorSpace (cgi) == dpy->colorspace,"bad colorspace"); + CGContextDrawImage (cgc, rect, cgi); + points++; + } + + CGImageRelease (cgi); + +# else /* ! XDRAWPOINTS_IMAGES */ + + CGRect *rects = (CGRect *) malloc (count * sizeof(CGRect)); + CGRect *r = rects; + + for (i = 0; i < count; i++) { + r->size.width = r->size.height = 1; + if (i > 0 && mode == CoordModePrevious) { + r->origin.x = r[-1].origin.x + points->x; + r->origin.y = r[-1].origin.x - points->y; + } else { + r->origin.x = wr.origin.x + points->x; + r->origin.y = wr.origin.y + wr.size.height - points->y; + } + points++; + r++; + } + + CGContextFillRects (d->cgc, rects, count); + free (rects); + +# endif /* ! XDRAWPOINTS_IMAGES */ + } + + pop_gc (d, gc); + invalidate_drawable_cache (d); + + return 0; +} + + +int +XDrawPoint (Display *dpy, Drawable d, GC gc, int x, int y) +{ + XPoint p; + p.x = x; + p.y = y; + return XDrawPoints (dpy, d, gc, &p, 1, CoordModeOrigin); +} + + +static void draw_rect (Display *, Drawable, GC, + int x, int y, unsigned int width, unsigned int height, + BOOL foreground_p, BOOL fill_p); + +static Bool +bitmap_context_p (Drawable d) +{ +# ifdef USE_BACKBUFFER + return True; +# else + // Because of the backbuffer, all iPhone Windows work like Pixmaps. + return d->type == PIXMAP; +# endif +} + +static void +fill_rect_memset (void *dst, size_t dst_pitch, uint32_t fill_data, + size_t fill_width, size_t fill_height) +{ + Assert(sizeof(wchar_t) == 4, "somebody changed the ABI"); + while (fill_height) { + // Would be nice if Apple used SSE/NEON in wmemset. Maybe someday. + wmemset (dst, fill_data, fill_width); + --fill_height; + dst = (char *) dst + dst_pitch; + } +} + +static void * +seek_xy (void *dst, size_t dst_pitch, unsigned x, unsigned y) +{ + return (char *)dst + dst_pitch * y + x * 4; +} + +static unsigned int +drawable_depth (Drawable d) +{ + return (d->type == WINDOW + ? visual_depth (NULL, NULL) + : d->pixmap.depth); +} + + +int +XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc, + int src_x, int src_y, + 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"); + Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y"); + Assert ((dst_x < 65535 && dst_x > -65535), "improbably large dst_x"); + Assert ((dst_y < 65535 && dst_y > -65535), "improbably large dst_y"); + + if (width == 0 || height == 0) + return 0; + + 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))), + gc->depth, gc->gcv.alpha_allowed_p, YES); + draw_rect (dpy, dst, 0, dst_x, dst_y, width, height, YES, YES); + return 0; + } + + CGRect src_frame, dst_frame; // Sizes and origins of the two drawables + CGRect src_rect, dst_rect; // The two rects to draw, clipped to the + // bounds of their drawables. + BOOL clipped = NO; // Whether we did any clipping of the rects. + + src_frame = src->frame; + dst_frame = dst->frame; + + // Initialize src_rect... + // + src_rect.origin.x = src_frame.origin.x + src_x; + src_rect.origin.y = src_frame.origin.y + src_frame.size.height + - height - src_y; + if (src_rect.origin.y < -65535) Assert(0, "src.origin.y went nuts"); + src_rect.size.width = width; + src_rect.size.height = height; + + // Initialize dst_rect... + // + dst_rect.origin.x = dst_frame.origin.x + dst_x; + dst_rect.origin.y = dst_frame.origin.y + dst_frame.size.height + - height - dst_y; + if (dst_rect.origin.y < -65535) Assert(0, "dst.origin.y went nuts"); + dst_rect.size.width = width; + dst_rect.size.height = height; + + // Clip rects to frames... + // + +# define CLIP(THIS,THAT,VAL,SIZE) do { \ + float off = THIS##_rect.origin.VAL; \ + if (off < 0) { \ + clipped = YES; \ + THIS##_rect.size.SIZE += off; \ + THAT##_rect.size.SIZE += off; \ + THIS##_rect.origin.VAL -= off; \ + THAT##_rect.origin.VAL -= off; \ + } \ + off = (( THIS##_rect.origin.VAL + THIS##_rect.size.SIZE) - \ + (THIS##_frame.origin.VAL + THIS##_frame.size.SIZE)); \ + if (off > 0) { \ + clipped = YES; \ + THIS##_rect.size.SIZE -= off; \ + THAT##_rect.size.SIZE -= off; \ + }} while(0) + + CLIP (dst, src, x, width); + CLIP (dst, src, y, height); + + // Not actually the original dst_rect, just the one before it's clipped to + // the src_frame. + CGRect orig_dst_rect = dst_rect; + + CLIP (src, dst, x, width); + CLIP (src, dst, y, height); +# undef CLIP + + if (orig_dst_rect.size.width <= 0 || orig_dst_rect.size.height <= 0) + return 0; + + // Sort-of-special case where no pixels can be grabbed from the source, + // and the whole destination is filled with the background color. + if (src_rect.size.width < 0 || src_rect.size.height < 0) { + src_rect.size.width = 0; + src_rect.size.height = 0; + } + + NSObject *releaseme = 0; + CGImageRef cgi; + BOOL mask_p = NO; + BOOL free_cgi_p = NO; + + + /* If we're copying from a bitmap to a bitmap, and there's nothing funny + going on with clipping masks or depths or anything, optimize it by + just doing a memcpy instead of going through a CGI. + */ + if (bitmap_context_p (src)) { + + if (bitmap_context_p (dst) && + gc->gcv.function == GXcopy && + !gc->gcv.clip_mask && + drawable_depth (src) == drawable_depth (dst)) { + + Assert(!(int)src_frame.origin.x && + !(int)src_frame.origin.y && + !(int)dst_frame.origin.x && + !(int)dst_frame.origin.y, + "unexpected non-zero origin"); + + char *src_data = CGBitmapContextGetData(src->cgc); + char *dst_data = CGBitmapContextGetData(dst->cgc); + size_t src_pitch = CGBitmapContextGetBytesPerRow(src->cgc); + size_t dst_pitch = CGBitmapContextGetBytesPerRow(dst->cgc); + + // Int to float and back again. It's not very safe, but it seems to work. + int src_x0 = src_rect.origin.x; + int dst_x0 = dst_rect.origin.x; + + // Flip the Y-axis a second time. + int src_y0 = (src_frame.origin.y + src_frame.size.height - + src_rect.size.height - src_rect.origin.y); + int dst_y0 = (dst_frame.origin.y + dst_frame.size.height - + dst_rect.size.height - dst_rect.origin.y); + + unsigned width0 = (int) src_rect.size.width; + unsigned height0 = (int) src_rect.size.height; + + Assert((int)src_rect.size.width == (int)dst_rect.size.width || + (int)src_rect.size.height == (int)dst_rect.size.height, + "size mismatch"); + { + char *src_data0 = seek_xy(src_data, src_pitch, src_x0, src_y0); + char *dst_data0 = seek_xy(dst_data, dst_pitch, dst_x0, dst_y0); + size_t src_pitch0 = src_pitch; + size_t dst_pitch0 = dst_pitch; + size_t bytes = width0 * 4; + + if (src == dst && dst_y0 > src_y0) { + // Copy upwards if the areas might overlap. + src_data0 += src_pitch0 * (height0 - 1); + dst_data0 += dst_pitch0 * (height0 - 1); + src_pitch0 = -src_pitch0; + dst_pitch0 = -dst_pitch0; + } + + size_t lines0 = height0; + while (lines0) { + // memcpy is an alias for memmove on OS X. + memmove(dst_data0, src_data0, bytes); + src_data0 += src_pitch0; + dst_data0 += dst_pitch0; + --lines0; + } + } + + if (clipped) { + int orig_dst_x = orig_dst_rect.origin.x; + int orig_dst_y = (dst_frame.origin.y + dst_frame.size.height - + orig_dst_rect.origin.y - orig_dst_rect.size.height); + int orig_width = orig_dst_rect.size.width; + int orig_height = orig_dst_rect.size.height; + + Assert (orig_dst_x >= 0 && + orig_dst_x + orig_width <= (int) dst_frame.size.width && + orig_dst_y >= 0 && + orig_dst_y + orig_height <= (int) dst_frame.size.height, + "wrong dimensions"); + + if (orig_dst_y < dst_y0) { + fill_rect_memset (seek_xy (dst_data, dst_pitch, + orig_dst_x, orig_dst_y), dst_pitch, + gc->gcv.background, orig_width, + dst_y0 - orig_dst_y); + } + + if (orig_dst_y + orig_height > dst_y0 + height0) { + fill_rect_memset (seek_xy (dst_data, dst_pitch, orig_dst_x, + dst_y0 + height0), + dst_pitch, + gc->gcv.background, orig_width, + orig_dst_y + orig_height - dst_y0 - height0); + } + + if (orig_dst_x < dst_x0) { + fill_rect_memset (seek_xy (dst_data, dst_pitch, orig_dst_x, dst_y0), + dst_pitch, gc->gcv.background, + dst_x0 - orig_dst_x, height0); + } + + if (dst_x0 + width0 < orig_dst_x + orig_width) { + fill_rect_memset (seek_xy (dst_data, dst_pitch, dst_x0 + width0, + dst_y0), + dst_pitch, gc->gcv.background, + orig_dst_x + orig_width - dst_x0 - width0, + height0); + } + } + + invalidate_drawable_cache (dst); + return 0; + } + + + // 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 || + src_rect.origin.y != src_frame.origin.y || + src_rect.size.width != src_frame.size.width || + src_rect.size.height != src_frame.size.height) { + // #### I don't understand why this is needed... + 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. + cgi = CGImageCreateWithImageInRect (cgi, src_rect); + free_cgi_p = YES; + } + + if (src->type == PIXMAP && src->pixmap.depth == 1) + mask_p = YES; + +# ifndef USE_BACKBUFFER + } else { /* (src->type == WINDOW) */ + + 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 (src == dst) { + + // 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; + + } else { + + // 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); + } + +# endif // !USE_BACKBUFFER + } + + CGContextRef cgc = dst->cgc; + + if (mask_p) { // src depth == 1 + + push_bg_gc (dst, gc, YES); + + // fill the destination rectangle with solid background... + 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 (cgc, gc->gcv.foreground, gc->depth, + gc->gcv.alpha_allowed_p, YES); + CGContextClipToMask (cgc, dst_rect, cgi); + CGContextFillRect (cgc, dst_rect); + + pop_gc (dst, gc); + + } else { // src depth > 1 + + push_gc (dst, gc); + + // If either the src or dst rects did not lie within their drawables, + // then we have adjusted both the src and dst rects to account for + // the clipping; that means we need to first clear to the background, + // so that clipped bits end up in the bg color instead of simply not + // being copied. + // + if (clipped) { + set_color (cgc, gc->gcv.background, gc->depth, + gc->gcv.alpha_allowed_p, YES); + CGContextFillRect (cgc, orig_dst_rect); + } + + 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_BACKBUFFER + Assert (0, "NSCopyBits unimplemented"); // shouldn't be reached anyway + return 0; +# else // !USE_BACKBUFFER + 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_BACKBUFFER + } + + pop_gc (dst, gc); + } + + if (free_cgi_p) CGImageRelease (cgi); + + if (releaseme) [releaseme release]; + invalidate_drawable_cache (dst); + return 0; +} + + +int +XCopyPlane (Display *dpy, Drawable src, Drawable dest, GC gc, + int src_x, int src_y, + unsigned width, int height, + int dest_x, int dest_y, unsigned long plane) +{ + Assert ((gc->depth == 1 || plane == 1), "hairy plane mask!"); + + // This isn't right: XCopyPlane() is supposed to map 1/0 to fg/bg, + // not to white/black. + return XCopyArea (dpy, src, dest, gc, + src_x, src_y, width, height, dest_x, dest_y); +} + + +int +XDrawLine (Display *dpy, Drawable d, GC gc, int x1, int y1, int x2, int y2) +{ + // when drawing a zero-length line, obey line-width and cap-style. + if (x1 == x2 && y1 == y2) { + int w = gc->gcv.line_width; + x1 -= w/2; + y1 -= w/2; + if (gc->gcv.line_width > 1 && gc->gcv.cap_style == CapRound) + return XFillArc (dpy, d, gc, x1, y1, w, w, 0, 360*64); + else + return XFillRectangle (dpy, d, gc, x1, y1, w, w); + } + + CGRect wr = d->frame; + NSPoint p; + p.x = wr.origin.x + x1; + p.y = wr.origin.y + wr.size.height - y1; + + push_fg_gc (d, gc, NO); + + 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 (cgc, p.x, p.y); + CGContextStrokePath (cgc); + pop_gc (d, gc); + invalidate_drawable_cache (d); + return 0; +} + +int +XDrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count, + int mode) +{ + int i; + NSPoint p; + CGRect wr = d->frame; + push_fg_gc (d, gc, NO); + + 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. + BOOL closed_p = (points[0].x == points[count-1].x && + points[0].y == points[count-1].y); + if (closed_p) count--; + + p.x = wr.origin.x + points->x; + p.y = wr.origin.y + wr.size.height - points->y; + points++; + CGContextBeginPath (cgc); + CGContextMoveToPoint (cgc, p.x, p.y); + for (i = 1; i < count; i++) { + if (mode == CoordModePrevious) { + p.x += points->x; + p.y -= points->y; + } else { + p.x = wr.origin.x + points->x; + p.y = wr.origin.y + wr.size.height - points->y; + } + CGContextAddLineToPoint (cgc, p.x, p.y); + points++; + } + if (closed_p) CGContextClosePath (cgc); + CGContextStrokePath (cgc); + pop_gc (d, gc); + invalidate_drawable_cache (d); + return 0; +} + + +int +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 (cgc, &gc->gcv); + CGContextBeginPath (cgc); + for (i = 0; i < count; i++) { + CGContextMoveToPoint (cgc, + wr.origin.x + segments->x1, + wr.origin.y + wr.size.height - segments->y1); + CGContextAddLineToPoint (cgc, + wr.origin.x + segments->x2, + wr.origin.y + wr.size.height - segments->y2); + segments++; + } + CGContextStrokePath (cgc); + pop_gc (d, gc); + invalidate_drawable_cache (d); + return 0; +} + + +int +XClearWindow (Display *dpy, Window win) +{ + Assert (win && win->type == WINDOW, "not a window"); + CGRect wr = win->frame; + return XClearArea (dpy, win, 0, 0, wr.size.width, wr.size.height, 0); +} + +int +XSetWindowBackground (Display *dpy, Window w, unsigned long pixel) +{ + Assert (w && w->type == WINDOW, "not a window"); + validate_pixel (pixel, 32, NO); + w->window.background = pixel; + return 0; +} + +static void +draw_rect (Display *dpy, Drawable d, GC gc, + int x, int y, unsigned int width, unsigned int height, + BOOL foreground_p, BOOL fill_p) +{ + CGRect wr = d->frame; + CGRect r; + r.origin.x = wr.origin.x + x; + r.origin.y = wr.origin.y + wr.size.height - y - height; + r.size.width = width; + r.size.height = height; + + if (gc) { + if (foreground_p) + push_fg_gc (d, gc, fill_p); + else + push_bg_gc (d, gc, fill_p); + } + + CGContextRef cgc = d->cgc; + if (fill_p) + CGContextFillRect (cgc, r); + else { + if (gc) + set_line_mode (cgc, &gc->gcv); + CGContextStrokeRect (cgc, r); + } + + if (gc) + pop_gc (d, gc); + invalidate_drawable_cache (d); +} + + +int +XFillRectangle (Display *dpy, Drawable d, GC gc, int x, int y, + unsigned int width, unsigned int height) +{ + draw_rect (dpy, d, gc, x, y, width, height, YES, YES); + return 0; +} + +int +XDrawRectangle (Display *dpy, Drawable d, GC gc, int x, int y, + unsigned int width, unsigned int height) +{ + draw_rect (dpy, d, gc, x, y, width, height, YES, NO); + return 0; +} + +int +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; + r.origin.x = wr.origin.x + rects->x; + r.origin.y = wr.origin.y + wr.size.height - rects->y - rects->height; + r.size.width = rects->width; + r.size.height = rects->height; + CGContextFillRect (cgc, r); + rects++; + } + pop_gc (d, gc); + invalidate_drawable_cache (d); + return 0; +} + + +int +XClearArea (Display *dpy, Window win, int x, int y, int w, int h, Bool exp) +{ + Assert (win && win->type == WINDOW, "not a window"); + 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; +} + + +int +XFillPolygon (Display *dpy, Drawable d, GC gc, + XPoint *points, int npoints, int shape, int mode) +{ + CGRect wr = d->frame; + int i; + push_fg_gc (d, gc, YES); + CGContextRef cgc = d->cgc; + CGContextBeginPath (cgc); + float x = 0, y = 0; + for (i = 0; i < npoints; i++) { + if (i > 0 && mode == CoordModePrevious) { + x += points[i].x; + y -= points[i].y; + } else { + x = wr.origin.x + points[i].x; + y = wr.origin.y + wr.size.height - points[i].y; + } + + if (i == 0) + CGContextMoveToPoint (cgc, x, y); + else + CGContextAddLineToPoint (cgc, x, y); + } + CGContextClosePath (cgc); + if (gc->gcv.fill_rule == EvenOddRule) + CGContextEOFillPath (cgc); + else + CGContextFillPath (cgc); + pop_gc (d, gc); + invalidate_drawable_cache (d); + return 0; +} + +#define radians(DEG) ((DEG) * M_PI / 180.0) +#define degrees(RAD) ((RAD) * 180.0 / M_PI) + +static int +draw_arc (Display *dpy, Drawable d, GC gc, int x, int y, + unsigned int width, unsigned int height, int angle1, int angle2, + BOOL fill_p) +{ + CGRect wr = d->frame; + CGRect bound; + bound.origin.x = wr.origin.x + x; + bound.origin.y = wr.origin.y + wr.size.height - y - height; + bound.size.width = width; + bound.size.height = height; + + CGPoint ctr; + ctr.x = bound.origin.x + bound.size.width /2; + ctr.y = bound.origin.y + bound.size.height/2; + + float r1 = radians (angle1/64.0); + float r2 = radians (angle2/64.0) + r1; + BOOL clockwise = angle2 < 0; + BOOL closed_p = (angle2 >= 360*64 || angle2 <= -360*64); + + push_fg_gc (d, gc, fill_p); + + CGContextRef cgc = d->cgc; + CGContextBeginPath (cgc); + + CGContextSaveGState(cgc); + CGContextTranslateCTM (cgc, ctr.x, ctr.y); + CGContextScaleCTM (cgc, width/2.0, height/2.0); + if (fill_p) + CGContextMoveToPoint (cgc, 0, 0); + + CGContextAddArc (cgc, 0.0, 0.0, 1, r1, r2, clockwise); + CGContextRestoreGState (cgc); // restore before stroke, for line width + + if (closed_p) + CGContextClosePath (cgc); // for proper line joining + + if (fill_p) { + CGContextFillPath (cgc); + } else { + set_line_mode (cgc, &gc->gcv); + CGContextStrokePath (cgc); + } + + pop_gc (d, gc); + invalidate_drawable_cache (d); + return 0; +} + +int +XDrawArc (Display *dpy, Drawable d, GC gc, int x, int y, + unsigned int width, unsigned int height, int angle1, int angle2) +{ + return draw_arc (dpy, d, gc, x, y, width, height, angle1, angle2, NO); +} + +int +XFillArc (Display *dpy, Drawable d, GC gc, int x, int y, + unsigned int width, unsigned int height, int angle1, int angle2) +{ + return draw_arc (dpy, d, gc, x, y, width, height, angle1, angle2, YES); +} + +int +XDrawArcs (Display *dpy, Drawable d, GC gc, XArc *arcs, int narcs) +{ + int i; + for (i = 0; i < narcs; i++) + draw_arc (dpy, d, gc, + arcs[i].x, arcs[i].y, + arcs[i].width, arcs[i].height, + arcs[i].angle1, arcs[i].angle2, + NO); + return 0; +} + +int +XFillArcs (Display *dpy, Drawable d, GC gc, XArc *arcs, int narcs) +{ + int i; + for (i = 0; i < narcs; i++) + draw_arc (dpy, d, gc, + arcs[i].x, arcs[i].y, + arcs[i].width, arcs[i].height, + arcs[i].angle1, arcs[i].angle2, + YES); + return 0; +} + + +static void +gcv_defaults (XGCValues *gcv, int depth) +{ + memset (gcv, 0, sizeof(*gcv)); + gcv->function = GXcopy; + gcv->foreground = (depth == 1 ? 1 : WhitePixel(0,0)); + gcv->background = (depth == 1 ? 0 : BlackPixel(0,0)); + gcv->line_width = 1; + gcv->cap_style = CapNotLast; + gcv->join_style = JoinMiter; + gcv->fill_rule = EvenOddRule; + + gcv->alpha_allowed_p = NO; + gcv->antialias_p = YES; +} + +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; + if (mask & GCLineWidth) gc->gcv.line_width = from->line_width; + if (mask & GCCapStyle) gc->gcv.cap_style = from->cap_style; + if (mask & GCJoinStyle) gc->gcv.join_style = from->join_style; + if (mask & GCFillRule) gc->gcv.fill_rule = from->fill_rule; + if (mask & GCClipXOrigin) gc->gcv.clip_x_origin = from->clip_x_origin; + if (mask & GCClipYOrigin) gc->gcv.clip_y_origin = from->clip_y_origin; + if (mask & GCSubwindowMode) gc->gcv.subwindow_mode = from->subwindow_mode; + + if (mask & GCClipMask) XSetClipMask (0, gc, from->clip_mask); + if (mask & GCFont) XSetFont (0, gc, from->font); + + if (mask & GCForeground) validate_pixel (from->foreground, gc->depth, + gc->gcv.alpha_allowed_p); + if (mask & GCBackground) validate_pixel (from->background, gc->depth, + gc->gcv.alpha_allowed_p); + + Assert ((! (mask & (GCLineStyle | + GCPlaneMask | + GCFillStyle | + GCTile | + GCStipple | + GCTileStipXOrigin | + GCTileStipYOrigin | + GCGraphicsExposures | + GCDashOffset | + GCDashList | + GCArcMode))), + "unimplemented gcvalues mask"); +} + + +GC +XCreateGC (Display *dpy, Drawable d, unsigned long mask, XGCValues *xgcv) +{ + struct jwxyz_GC *gc = (struct jwxyz_GC *) calloc (1, sizeof(*gc)); + if (d->type == WINDOW) { + gc->depth = 32; + } else { /* (d->type == PIXMAP) */ + gc->depth = d->pixmap.depth; + } + + gcv_defaults (&gc->gcv, gc->depth); + set_gcv (gc, xgcv, mask); + return gc; +} + +int +XChangeGC (Display *dpy, GC gc, unsigned long mask, XGCValues *gcv) +{ + set_gcv (gc, gcv, mask); + return 0; +} + + +int +XFreeGC (Display *dpy, GC gc) +{ + if (gc->gcv.font) + XUnloadFont (dpy, gc->gcv.font); + + Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup"); + + if (gc->gcv.clip_mask) { + XFreePixmap (dpy, gc->gcv.clip_mask); + CGImageRelease (gc->clip_mask); + } + free (gc); + return 0; +} + + +Status +XGetWindowAttributes (Display *dpy, Window w, XWindowAttributes *xgwa) +{ + Assert (w && w->type == WINDOW, "not a window"); + memset (xgwa, 0, sizeof(*xgwa)); + xgwa->x = w->frame.origin.x; + xgwa->y = w->frame.origin.y; + xgwa->width = w->frame.size.width; + xgwa->height = w->frame.size.height; + xgwa->depth = 32; + xgwa->screen = dpy->screen; + xgwa->visual = dpy->screen->visual; + return 0; +} + +Status +XGetGeometry (Display *dpy, Drawable d, Window *root_ret, + int *x_ret, int *y_ret, + unsigned int *w_ret, unsigned int *h_ret, + unsigned int *bw_ret, unsigned int *d_ret) +{ + *x_ret = d->frame.origin.x; + *y_ret = d->frame.origin.y; + *w_ret = d->frame.size.width; + *h_ret = d->frame.size.height; + *d_ret = (d->type == WINDOW ? 32 : d->pixmap.depth); + *root_ret = RootWindow (dpy, 0); + *bw_ret = 0; + return True; +} + + +Status +XAllocColor (Display *dpy, Colormap cmap, XColor *color) +{ + // store 32 bit ARGB in the pixel field. + // (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) )); + return 1; +} + +Status +XAllocColorCells (Display *dpy, Colormap cmap, Bool contig, + unsigned long *pmret, unsigned int npl, + unsigned long *pxret, unsigned int npx) +{ + return 0; +} + +int +XStoreColors (Display *dpy, Colormap cmap, XColor *colors, int n) +{ + Assert(0, "XStoreColors called"); + return 0; +} + +int +XStoreColor (Display *dpy, Colormap cmap, XColor *c) +{ + Assert(0, "XStoreColor called"); + return 0; +} + +int +XFreeColors (Display *dpy, Colormap cmap, unsigned long *px, int npixels, + unsigned long planes) +{ + return 0; +} + +Status +XParseColor (Display *dpy, Colormap cmap, const char *spec, XColor *ret) +{ + unsigned char r=0, g=0, b=0; + if (*spec == '#' && strlen(spec) == 7) { + static unsigned const char hex[] = { // yeah yeah, shoot me. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, + 0,10,11,12,13,14,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,10,11,12,13,14,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + r = (hex[spec[1]] << 4) | hex[spec[2]]; + 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; + } else if (!strcasecmp(spec,"white")) { + r = g = b = 255; + } else if (!strcasecmp(spec,"red")) { + r = 255; + } else if (!strcasecmp(spec,"green")) { + g = 255; + } else if (!strcasecmp(spec,"blue")) { + b = 255; + } else if (!strcasecmp(spec,"cyan")) { + g = b = 255; + } else if (!strcasecmp(spec,"magenta")) { + r = b = 255; + } else if (!strcasecmp(spec,"yellow")) { + r = g = 255; + } else { + return 0; + } + + ret->red = (r << 8) | r; + ret->green = (g << 8) | g; + ret->blue = (b << 8) | b; + ret->flags = DoRed|DoGreen|DoBlue; + return 1; +} + +Status +XAllocNamedColor (Display *dpy, Colormap cmap, char *name, + XColor *screen_ret, XColor *exact_ret) +{ + if (! XParseColor (dpy, cmap, name, screen_ret)) + return False; + *exact_ret = *screen_ret; + return XAllocColor (dpy, cmap, screen_ret); +} + +int +XQueryColor (Display *dpy, Colormap cmap, XColor *color) +{ + validate_pixel (color->pixel, 32, NO); + unsigned char r = ((color->pixel >> 16) & 0xFF); + unsigned char g = ((color->pixel >> 8) & 0xFF); + unsigned char b = ((color->pixel ) & 0xFF); + color->red = (r << 8) | r; + color->green = (g << 8) | g; + color->blue = (b << 8) | b; + color->flags = DoRed|DoGreen|DoBlue; + return 0; +} + +int +XQueryColors (Display *dpy, Colormap cmap, XColor *c, int n) +{ + int i; + for (i = 0; i < n; i++) + XQueryColor (dpy, cmap, &c[i]); + return 0; +} + + +static unsigned long +ximage_getpixel_1 (XImage *ximage, int x, int y) +{ + return ((ximage->data [y * ximage->bytes_per_line + (x>>3)] >> (x & 7)) & 1); +} + +static int +ximage_putpixel_1 (XImage *ximage, int x, int y, unsigned long pixel) +{ + if (pixel) + ximage->data [y * ximage->bytes_per_line + (x>>3)] |= (1 << (x & 7)); + else + ximage->data [y * ximage->bytes_per_line + (x>>3)] &= ~(1 << (x & 7)); + + return 0; +} + +static unsigned long +ximage_getpixel_32 (XImage *ximage, int x, int y) +{ + 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) +{ + *((uint32_t *) ximage->data + + (y * (ximage->bytes_per_line >> 2)) + + x) = (uint32_t) pixel; + return 0; +} + + +Status +XInitImage (XImage *ximage) +{ + if (!ximage->bytes_per_line) + ximage->bytes_per_line = (ximage->depth == 1 + ? (ximage->width + 7) / 8 + : ximage->width * 4); + + if (ximage->depth == 1) { + ximage->f.put_pixel = ximage_putpixel_1; + ximage->f.get_pixel = ximage_getpixel_1; + } else if (ximage->depth == 32 || ximage->depth == 24) { + ximage->f.put_pixel = ximage_putpixel_32; + ximage->f.get_pixel = ximage_getpixel_32; + } else { + Assert (0, "unknown depth"); + } + return 1; +} + + +XImage * +XCreateImage (Display *dpy, Visual *visual, unsigned int depth, + int format, int offset, char *data, + unsigned int width, unsigned int height, + int bitmap_pad, int bytes_per_line) +{ + XImage *ximage = (XImage *) calloc (1, sizeof(*ximage)); + ximage->width = width; + ximage->height = height; + ximage->format = format; + ximage->data = data; + ximage->bitmap_unit = 8; + ximage->byte_order = MSBFirst; + ximage->bitmap_bit_order = ximage->byte_order; + ximage->bitmap_pad = bitmap_pad; + ximage->depth = depth; + ximage->red_mask = (depth == 1 ? 0 : 0x00FF0000); + ximage->green_mask = (depth == 1 ? 0 : 0x0000FF00); + ximage->blue_mask = (depth == 1 ? 0 : 0x000000FF); + ximage->bits_per_pixel = (depth == 1 ? 1 : 32); + ximage->bytes_per_line = bytes_per_line; + + XInitImage (ximage); + return ximage; +} + +XImage * +XSubImage (XImage *from, int x, int y, unsigned int w, unsigned int h) +{ + XImage *to = XCreateImage (0, 0, from->depth, from->format, 0, 0, + w, h, from->bitmap_pad, 0); + to->data = (char *) malloc (h * to->bytes_per_line); + + if (x >= from->width) + w = 0; + else if (x+w > from->width) + w = from->width - x; + + if (y >= from->height) + h = 0; + else if (y+h > from->height) + h = from->height - y; + + int tx, ty; + for (ty = 0; ty < h; ty++) + for (tx = 0; tx < w; tx++) + XPutPixel (to, tx, ty, XGetPixel (from, x+tx, y+ty)); + return to; +} + + +XPixmapFormatValues * +XListPixmapFormats (Display *dpy, int *n_ret) +{ + XPixmapFormatValues *ret = calloc (2, sizeof(*ret)); + ret[0].depth = 32; + ret[0].bits_per_pixel = 32; + ret[0].scanline_pad = 8; + ret[1].depth = 1; + ret[1].bits_per_pixel = 1; + ret[1].scanline_pad = 8; + *n_ret = 2; + return ret; +} + + +unsigned long +XGetPixel (XImage *ximage, int x, int y) +{ + return ximage->f.get_pixel (ximage, x, y); +} + + +int +XPutPixel (XImage *ximage, int x, int y, unsigned long pixel) +{ + return ximage->f.put_pixel (ximage, x, y, pixel); +} + +int +XDestroyImage (XImage *ximage) +{ + if (ximage->data) free (ximage->data); + free (ximage); + return 0; +} + + +static void +flipbits (unsigned const char *in, unsigned char *out, int length) +{ + static const unsigned char table[256] = { + 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, + 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, + 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, + 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, + 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, + 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, + 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, + 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, + 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, + 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, + 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, + 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, + 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, + 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, + 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, + 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, + 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, + 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, + 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, + 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, + 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, + 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, + 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, + 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, + 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, + 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, + 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, + 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, + 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, + 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, + 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF + }; + while (--length > 0) + *out++ = table[*in++]; +} + + +int +XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, + int src_x, int src_y, int dest_x, int dest_y, + unsigned int w, unsigned int h) +{ + 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) { + if (dest_x > wr.size.width) + return 0; + w = wr.size.width - dest_x; + } + if (dest_y + h > wr.size.height) { + if (dest_y > wr.size.height) + return 0; + h = wr.size.height - dest_y; + } + if (w <= 0 || h <= 0) + return 0; + + // Clip width and height to the bounds of the XImage + // + if (src_x + w > ximage->width) { + if (src_x > ximage->width) + return 0; + w = ximage->width - src_x; + } + if (src_y + h > ximage->height) { + if (src_y > ximage->height) + return 0; + h = ximage->height - src_y; + } + if (w <= 0 || h <= 0) + return 0; + + 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 (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, d, 0, dest_x, dest_y, w, h, YES, YES); + return 0; + } + + int bpl = ximage->bytes_per_line; + int bpp = ximage->bits_per_pixel; + int bsize = bpl * h; + char *data = ximage->data; + + CGRect r; + r.origin.x = wr.origin.x + dest_x; + r.origin.y = wr.origin.y + wr.size.height - dest_y - h; + r.size.width = w; + r.size.height = h; + + if (bpp == 32) { + + /* Take advantage of the fact that it's ok for (bpl != w * bpp) + to create a CGImage from a sub-rectagle of the XImage. + */ + data += (src_y * bpl) + (src_x * 4); + CGDataProviderRef prov = + CGDataProviderCreateWithData (NULL, data, bsize, NULL); + + CGImageRef cgi = CGImageCreate (w, h, + bpp/4, bpp, bpl, + dpy->colorspace, + /* Need this for XPMs to have the right + colors, e.g. the logo in "maze". */ + (kCGImageAlphaNoneSkipFirst | + kCGBitmapByteOrder32Host), + prov, + NULL, /* decode[] */ + NO, /* interpolate */ + kCGRenderingIntentDefault); + CGDataProviderRelease (prov); + //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace"); + CGContextDrawImage (cgc, r, cgi); + CGImageRelease (cgi); + + } else { // (bpp == 1) + + /* To draw a 1bpp image, we use it as a mask and fill two rectangles. + + #### However, the bit order within a byte in a 1bpp XImage is + the wrong way around from what Quartz expects, so first we + have to copy the data to reverse it. Shit! Maybe it + would be worthwhile to go through the hacks and #ifdef + each one that diddles 1bpp XImage->data directly... + */ + Assert ((src_x % 8) == 0, + "XPutImage with non-byte-aligned 1bpp X offset not implemented"); + + data += (src_y * bpl) + (src_x / 8); // move to x,y within the data + unsigned char *flipped = (unsigned char *) malloc (bsize); + + flipbits ((unsigned char *) data, flipped, bsize); + + CGDataProviderRef prov = + CGDataProviderCreateWithData (NULL, flipped, bsize, NULL); + CGImageRef mask = CGImageMaskCreate (w, h, + 1, bpp, bpl, + prov, + NULL, /* decode[] */ + NO); /* interpolate */ + push_fg_gc (d, gc, YES); + + 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); + CGDataProviderRelease (prov); + CGImageRelease (mask); + } + + invalidate_drawable_cache (d); + + return 0; +} + + +XImage * +XGetImage (Display *dpy, Drawable d, int x, int y, + unsigned int width, unsigned int height, + unsigned long plane_mask, int format) +{ + const unsigned char *data = 0; + int depth, ibpp, ibpl; + enum { RGBA, ARGB, BGRA } src_format; // As bytes. +# ifndef USE_BACKBUFFER + NSBitmapImageRep *bm = 0; +# endif + + 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_BACKBUFFER + // Because of the backbuffer, all iPhone Windows work like Pixmaps. + if (d->type == PIXMAP) +# endif + { + depth = (d->type == PIXMAP + ? d->pixmap.depth + : 32); + // We create pixmaps and iPhone backbuffers with kCGImageAlphaNoneSkipFirst. + src_format = BGRA; // #### Should this be ARGB on PPC? + ibpp = CGBitmapContextGetBitsPerPixel (cgc); + ibpl = CGBitmapContextGetBytesPerRow (cgc); + data = CGBitmapContextGetData (cgc); + Assert (data, "CGBitmapContextGetData failed"); + +# ifndef USE_BACKBUFFER + } else { /* (d->type == WINDOW) */ + + // get the bits (desired sub-rectangle) out of the NSView + NSRect nsfrom; + nsfrom.origin.x = x; +// nsfrom.origin.y = y; + nsfrom.origin.y = d->frame.size.height - height - y; + nsfrom.size.width = width; + nsfrom.size.height = height; + bm = [[NSBitmapImageRep alloc] initWithFocusedViewRect:nsfrom]; + depth = 32; + src_format = ([bm bitmapFormat] & NSAlphaFirstBitmapFormat) ? ARGB : RGBA; + ibpp = [bm bitsPerPixel]; + ibpl = [bm bytesPerRow]; + data = [bm bitmapData]; + Assert (data, "NSBitmapImageRep initWithFocusedViewRect failed"); +# endif // !USE_BACKBUFFER + } + + // 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); + image->data = (char *) malloc (height * image->bytes_per_line); + + int obpl = image->bytes_per_line; + + /* 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 = 0; yy < height; yy++) { + + const unsigned char *iline2 = iline; + for (xx = 0; xx < width; xx++) { + + 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)); + } + iline += ibpl; + } + } else { + Assert (ibpp == 24 || ibpp == 32, "weird obpp"); + const unsigned char *iline = data; + unsigned char *oline = (unsigned char *) image->data; + for (yy = 0; yy < height; yy++) { + + const unsigned char *iline2 = iline; + unsigned char *oline2 = oline; + + switch (src_format) { + case 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; + } + break; + case 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; + } + break; + case BGRA: + for (xx = 0; xx < width; xx++) { + unsigned char b = *iline2++; + unsigned char g = *iline2++; + unsigned char r = *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; + } + break; + default: + abort(); + break; + } + + oline += obpl; + iline += ibpl; + } + } + +# ifndef USE_BACKBUFFER + 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_or_CGImage (Display *dpy, Drawable d, + Bool nsimg_p, void *img_arg, + XRectangle *geom_ret, int exif_rotation) +{ + 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); + + 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, 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 (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 (cgc, dst2, cgi); + CGContextRestoreGState (cgc); + +# ifndef USE_IPHONE + if (nsimg_p) { + CFRelease (cgsrc); + CGImageRelease (cgi); + } +# endif // USE_IPHONE + + if (geom_ret) { + geom_ret->x = dst.origin.x; + geom_ret->y = dst.origin.y; + 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, + unsigned int w, unsigned int h, + unsigned long fg, unsigned int bg, + unsigned int depth) +{ + Pixmap p = XCreatePixmap (dpy, drawable, w, h, depth); + XImage *image = XCreateImage (dpy, 0, 1, XYPixmap, 0, + (char *) data, w, h, 0, 0); + XGCValues gcv; + gcv.foreground = fg; + gcv.background = bg; + GC gc = XCreateGC (dpy, p, GCForeground|GCBackground, &gcv); + XPutImage (dpy, p, gc, image, 0, 0, 0, 0, w, h); + XFreeGC (dpy, gc); + image->data = 0; + XDestroyImage (image); + return p; +} + +Pixmap +XCreatePixmap (Display *dpy, Drawable d, + unsigned int width, unsigned int height, unsigned int depth) +{ + char *data = (char *) malloc (width * height * 4); + if (! data) return 0; + + Pixmap p = (Pixmap) calloc (1, sizeof(*p)); + p->type = PIXMAP; + 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. + 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, + 8, /* bits per component */ + width * 4, /* bpl */ + dpy->colorspace, + // Without this, it returns 0... + (kCGImageAlphaNoneSkipFirst | + kCGBitmapByteOrder32Host) + ); + Assert (p->cgc, "could not create CGBitmapContext"); + return p; +} + + +int +XFreePixmap (Display *d, Pixmap p) +{ + Assert (p && 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 (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; + 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 | + kCGBitmapByteOrder32Host) + ); + Assert (p2->cgc, "could not create CGBitmapContext"); + + return p2; +} + + +/* Font metric terminology, as used by X11: + + "lbearing" is the distance from the logical origin to the leftmost pixel. + If a character's ink extends to the left of the origin, it is negative. + + "rbearing" is the distance from the logical origin to the rightmost pixel. + + "descent" is the distance from the logical origin to the bottommost pixel. + For characters with descenders, it is negative. + + "ascent" is the distance from the logical origin to the topmost pixel. + It is the number of pixels above the baseline. + + "width" is the distance from the logical origin to the position where + the logical origin of the next character should be placed. + + If "rbearing" is greater than "width", then this character overlaps the + following character. If smaller, then there is trailing blank space. + */ + + +// This is XQueryFont, but for the XFontStruct embedded in 'Font' +// +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; + + XFontStruct *f = &fid->metrics; + XCharStruct *min = &f->min_bounds; + XCharStruct *max = &f->max_bounds; + +#define CEIL(F) ((F) < 0 ? floor(F) : ceil(F)) + + f->fid = fid; + f->min_char_or_byte2 = first; + f->max_char_or_byte2 = last; + f->default_char = 'M'; + f->ascent = CEIL ([fid->nsfont ascender]); + f->descent = -CEIL ([fid->nsfont descender]); + + min->width = 255; // set to smaller values in the loop + min->ascent = 255; + min->descent = 255; + min->lbearing = 255; + min->rbearing = 255; + + 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]; + str[0] = i; + str[1] = 0; + + 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!! + + You might think that we could do + NSGlyph glyph = [fid->nsfont glyphWithName:nsstr]; + but that doesn't work; my guess is that glyphWithName expects + full Unicrud names like "LATIN CAPITAL LETTER A WITH ACUTE". + */ + NSGlyph glyph; + { + 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 + [ts addLayoutManager:lm]; + [lm release]; // ts retains lm + glyph = [lm glyphAtIndex:0]; + [ts release]; + } + + /* Compute the bounding box and advancement by converting the glyph + to a bezier path. There appears to be *no other way* to find out + the bounding box of a character: [NSFont boundingRectForGlyph] and + [NSString sizeWithAttributes] both return an advancement-sized + rectangle, not a rectangle completely enclosing the glyph's ink. + */ + advancement.x = advancement.y = 0; + [bpath removeAllPoints]; + [bpath moveToPoint:advancement]; + [bpath appendBezierPathWithGlyph:glyph inFont:fid->nsfont]; + 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; + + /* A bug that existed was that the GL FPS display was truncating + characters slightly: commas looked like periods. + + At one point, I believed the bounding box was being rounded + wrong and we needed to add padding to it here. + + I think what was actually going on was, I was computing rbearing + wrong. Also there was an off-by-one error in texfont.c, displaying + too little of the bitmap. + + Adding arbitrarily large padding to the bbox is fine in fontglide + and FPS display, but screws up BSOD. Increasing bbox width makes + inverted text print too wide; decreasing origin makes characters + clip. + + I think that all 3 states are correct now with the new lbearing + computation plus the texfont fix. + */ +# if 0 + double kludge = 2; + bbox.origin.x -= kludge; + bbox.origin.y -= kludge; + bbox.size.width += kludge; + bbox.size.height += kludge; +# endif + } +# endif // USE_IPHONE + + /* Now that we know the advancement and bounding box, we can compute + the lbearing and rbearing. + */ + XCharStruct *cs = &f->per_char[i-first]; + + cs->ascent = CEIL (bbox.origin.y) + CEIL (bbox.size.height); + cs->descent = CEIL(-bbox.origin.y); + cs->lbearing = floor (bbox.origin.x); +// cs->rbearing = CEIL (bbox.origin.x) + CEIL (bbox.size.width); + cs->rbearing = CEIL (bbox.origin.x + bbox.size.width) - cs->lbearing; + cs->width = CEIL (advancement.x); + +// Assert (cs->rbearing - cs->lbearing == CEIL(bbox.size.width), +// "bbox w wrong"); + Assert (cs->ascent + cs->descent == CEIL(bbox.size.height), + "bbox h wrong"); + + max->width = MAX (max->width, cs->width); + max->ascent = MAX (max->ascent, cs->ascent); + max->descent = MAX (max->descent, cs->descent); + max->lbearing = MAX (max->lbearing, cs->lbearing); + max->rbearing = MAX (max->rbearing, cs->rbearing); + + min->width = MIN (min->width, cs->width); + min->ascent = MIN (min->ascent, cs->ascent); + min->descent = MIN (min->descent, cs->descent); + min->lbearing = MIN (min->lbearing, cs->lbearing); + min->rbearing = MIN (min->rbearing, cs->rbearing); + +# undef CEIL + +#if 0 + fprintf(stderr, " %3d %c: w=%3d lb=%3d rb=%3d as=%3d ds=%3d " + " bb=%5.1f x %5.1f @ %5.1f %5.1f adv=%5.1f %5.1f\n", + i, i, cs->width, cs->lbearing, cs->rbearing, + cs->ascent, cs->descent, + bbox.size.width, bbox.size.height, + bbox.origin.x, bbox.origin.y, + advancement.x, advancement.y); +#endif + } + +# ifdef USE_IPHONE + CFRelease (ctfont); +# endif +} + + +// Since 'Font' includes the metrics, this just makes a copy of that. +// +XFontStruct * +XQueryFont (Display *dpy, Font fid) +{ + // copy XFontStruct + XFontStruct *f = (XFontStruct *) calloc (1, sizeof(*f)); + *f = fid->metrics; + + // copy XCharStruct array + int size = f->max_char_or_byte2 - f->min_char_or_byte2; + f->per_char = (XCharStruct *) calloc (size + 2, sizeof (XCharStruct)); + memcpy (f->per_char, fid->metrics.per_char, + size * sizeof (XCharStruct)); + + return f; +} + + +static Font +copy_font (Font fid) +{ + // copy 'Font' struct + Font fid2 = (Font) malloc (sizeof(*fid2)); + *fid2 = *fid; + + // copy XCharStruct array + int size = fid->metrics.max_char_or_byte2 - fid->metrics.min_char_or_byte2; + fid2->metrics.per_char = (XCharStruct *) + malloc ((size + 2) * sizeof (XCharStruct)); + memcpy (fid2->metrics.per_char, fid->metrics.per_char, + size * sizeof (XCharStruct)); + + // copy the other pointers + fid2->ps_name = strdup (fid->ps_name); +// [fid2->nsfont retain]; + fid2->metrics.fid = fid2; + + return fid2; +} + + +static NSFont * +try_font (BOOL fixed, BOOL bold, BOOL ital, BOOL serif, float size, + char **name_ret) +{ + 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 = strdup(name); + return f; +} + +static NSFont * +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 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 + encoding:NSUTF8StringEncoding]; + NSFont *f = [NSFont fontWithName:nsname size:size]; + if (f) { + *name_ret = name2; + *size_ret = size; + return f; + } else { + free (name2); + return 0; + } +} + + +/* Returns a random font in the given size and face. + */ +static NSFont * +random_font (BOOL bold, BOOL ital, float size, char **name_ret) +{ +# ifndef USE_IPHONE + NSFontTraitMask mask = ((bold ? NSBoldFontMask : NSUnboldFontMask) | + (ital ? NSItalicFontMask : NSUnitalicFontMask)); + NSArray *fonts = [[NSFontManager sharedFontManager] + availableFontNamesWithTraits:mask]; + if (!fonts) return 0; + + int n = [fonts count]; + if (n <= 0) return 0; + + int j; + for (j = 0; j < n; j++) { + int i = random() % n; + NSString *name = [fonts objectAtIndex:i]; + NSFont *f = [NSFont fontWithName:name size:size]; + if (!f) continue; + + /* Don't use this font if it (probably) doesn't include ASCII characters. + */ + NSStringEncoding enc = [f mostCompatibleStringEncoding]; + if (! (enc == NSUTF8StringEncoding || + enc == NSISOLatin1StringEncoding || + enc == NSNonLossyASCIIStringEncoding || + enc == NSISOLatin2StringEncoding || + enc == NSUnicodeStringEncoding || + enc == NSWindowsCP1250StringEncoding || + enc == NSWindowsCP1252StringEncoding || + enc == NSMacOSRomanStringEncoding)) { + // NSLog(@"skipping \"%@\": encoding = %d", name, enc); + continue; + } + // NSLog(@"using \"%@\": %d", name, enc); + + *name_ret = strdup ([name cStringUsingEncoding:NSUTF8StringEncoding]); + return f; + } + + // 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, float scale, + char **name_ret, float *size_ret) +{ + NSFont *nsfont = 0; + BOOL bold = NO; + BOOL ital = NO; + BOOL fixed = NO; + BOOL serif = NO; + BOOL rand = NO; + float size = 0; + char *ps_name = 0; + + const char *s = (name ? name : ""); + while (*s) { + while (*s && (*s == '*' || *s == '-')) + s++; + const char *s2 = s; + while (*s2 && (*s2 != '*' && *s2 != '-')) + s2++; + + int L = s2-s; + if (s == s2) + ; +# define CMP(STR) (L == strlen(STR) && !strncasecmp (s, (STR), L)) + else if (CMP ("random")) rand = YES; + else if (CMP ("bold")) bold = YES; + else if (CMP ("i")) ital = YES; + else if (CMP ("o")) ital = YES; + else if (CMP ("courier")) fixed = YES; + else if (CMP ("fixed")) fixed = YES; + else if (CMP ("m")) fixed = YES; + else if (CMP ("times")) serif = YES; + else if (CMP ("6x10")) fixed = YES, size = 8; + else if (CMP ("6x10bold")) fixed = YES, size = 8, bold = YES; + else if (CMP ("9x15")) fixed = YES, size = 12; + else if (CMP ("9x15bold")) fixed = YES, size = 12, bold = YES; + else if (CMP ("vga")) fixed = YES, size = 12; + else if (CMP ("console")) fixed = YES, size = 12; + else if (CMP ("gallant")) fixed = YES, size = 12; +# undef CMP + else if (size == 0) { + int n = 0; + if (1 == sscanf (s, " %d ", &n)) + size = n / 10.0; + } + + s = s2; + } + + if (size < 6 || size > 1000) + size = 12; + + size *= scale; + + if (rand) + nsfont = random_font (bold, ital, size, &ps_name); + + if (!nsfont) + nsfont = try_font (fixed, bold, ital, serif, size, &ps_name); + + // if that didn't work, turn off attibutes until it does + // (e.g., there is no "Monaco-Bold".) + // + if (!nsfont && serif) { + serif = NO; + nsfont = try_font (fixed, bold, ital, serif, size, &ps_name); + } + if (!nsfont && ital) { + ital = NO; + nsfont = try_font (fixed, bold, ital, serif, size, &ps_name); + } + if (!nsfont && bold) { + bold = NO; + nsfont = try_font (fixed, bold, ital, serif, size, &ps_name); + } + if (!nsfont && fixed) { + fixed = NO; + nsfont = try_font (fixed, bold, ital, serif, size, &ps_name); + } + + if (nsfont) { + *name_ret = ps_name; + *size_ret = size; + return nsfont; + } else { + return 0; + } +} + + +Font +XLoadFont (Display *dpy, const char *name) +{ + Font fid = (Font) calloc (1, sizeof(*fid)); + + float scale = 1; + +# ifdef USE_IPHONE + // Scale up fonts on Retina displays. + scale = dpy->main_window->window.view.contentScaleFactor; +# endif + + fid->nsfont = try_native_font (name, scale, &fid->ps_name, &fid->size); + + 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); + + // We should never return NULL for XLFD fonts. + if (!fid->nsfont) { + Assert (0, "no font"); + return 0; + } + CFRetain (fid->nsfont); // needed for garbage collection? + + //NSLog(@"parsed \"%s\" to %s %.1f", name, fid->ps_name, fid->size); + + query_font (fid); + + return fid; +} + + +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) +{ + 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; +} + +int +XFreeFontInfo (char **names, XFontStruct *info, int n) +{ + int i; + if (names) { + for (i = 0; i < n; i++) + if (names[i]) free (names[i]); + free (names); + } + if (info) { + for (i = 0; i < n; i++) + if (info[i].per_char) + free (info[i].per_char); + free (info); + } + return 0; +} + +int +XFreeFont (Display *dpy, XFontStruct *f) +{ + Font fid = f->fid; + XFreeFontInfo (0, f, 1); + XUnloadFont (dpy, fid); + return 0; +} + + +int +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; +} + +int +XTextExtents (XFontStruct *f, const char *s, int length, + int *dir_ret, int *ascent_ret, int *descent_ret, + XCharStruct *cs) +{ + memset (cs, 0, sizeof(*cs)); + int i; + for (i = 0; i < length; i++) { + unsigned char c = (unsigned char) s[i]; + if (c < f->min_char_or_byte2 || c > f->max_char_or_byte2) + c = f->default_char; + const XCharStruct *cc = &f->per_char[c - f->min_char_or_byte2]; + if (i == 0) { + *cs = *cc; + } else { + cs->ascent = MAX (cs->ascent, cc->ascent); + cs->descent = MAX (cs->descent, cc->descent); + cs->lbearing = MIN (cs->lbearing, cs->width + cc->lbearing); + cs->rbearing = MAX (cs->rbearing, cs->width + cc->rbearing); + cs->width += cc->width; + } + } + *dir_ret = 0; + *ascent_ret = f->ascent; + *descent_ret = f->descent; + return 0; +} + +int +XTextWidth (XFontStruct *f, const char *s, int length) +{ + int ascent, descent, dir; + XCharStruct cs; + XTextExtents (f, s, length, &dir, &ascent, &descent, &cs); + return cs.width; +} + + +static void +set_font (Display *dpy, CGContextRef cgc, GC gc) +{ + Font font = gc->gcv.font; + 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); +} + + +static int +draw_string (Display *dpy, Drawable d, GC gc, int x, int y, + const char *str, int len, BOOL clear_background_p) +{ + if (clear_background_p) { + int ascent, descent, dir; + XCharStruct cs; + XTextExtents (&gc->gcv.font->metrics, str, len, + &dir, &ascent, &descent, &cs); + draw_rect (dpy, d, gc, + x + MIN (0, cs.lbearing), + y - MAX (0, ascent), + MAX (MAX (0, cs.rbearing) - + MIN (0, cs.lbearing), + cs.width), + MAX (0, ascent) + MAX (0, descent), + NO, YES); + } + + CGRect wr = d->frame; + +# if 1 + /* The Quartz way is probably faster, but doesn't draw Latin1 properly. + But the Cocoa way only works on NSView, not on CGContextRef (pixmaps)! + */ + + CGContextRef cgc = d->cgc; + push_fg_gc (d, gc, YES); + set_font (dpy, cgc, gc); + + 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 */ + + /* The Cocoa way... + */ + + unsigned long argb = gc->gcv.foreground; + if (gc->depth == 1) argb = (argb ? WhitePixel(dpy,0) : BlackPixel(dpy,0)); + float a = ((argb >> 24) & 0xFF) / 255.0; + float r = ((argb >> 16) & 0xFF) / 255.0; + float g = ((argb >> 8) & 0xFF) / 255.0; + float b = ((argb ) & 0xFF) / 255.0; + NSColor *fg = [NSColor colorWithDeviceRed:r green:g blue:b alpha:a]; + NSDictionary *attr = + [NSDictionary dictionaryWithObjectsAndKeys: + gc->gcv.font->nsfont, NSFontAttributeName, + fg, NSForegroundColorAttributeName, + nil]; + char *s2 = (char *) malloc (len + 1); + strncpy (s2, str, len); + s2[len] = 0; + NSString *nsstr = [NSString stringWithCString:s2 + encoding:NSISOLatin1StringEncoding]; + free (s2); + NSPoint pos; + pos.x = wr.origin.x + x; + pos.y = wr.origin.y + wr.size.height - y - gc->gcv.font->metrics.descent; + [nsstr drawAtPoint:pos withAttributes:attr]; + +# endif /* 0 */ + + invalidate_drawable_cache (d); + return 0; +} + + +int +XDrawString (Display *dpy, Drawable d, GC gc, int x, int y, + const char *str, int len) +{ + return draw_string (dpy, d, gc, x, y, str, len, NO); +} + +int +XDrawImageString (Display *dpy, Drawable d, GC gc, int x, int y, + const char *str, int len) +{ + return draw_string (dpy, d, gc, x, y, str, len, YES); +} + + +int +XSetForeground (Display *dpy, GC gc, unsigned long fg) +{ + validate_pixel (fg, gc->depth, gc->gcv.alpha_allowed_p); + gc->gcv.foreground = fg; + return 0; +} + + +int +XSetBackground (Display *dpy, GC gc, unsigned long bg) +{ + validate_pixel (bg, gc->depth, gc->gcv.alpha_allowed_p); + gc->gcv.background = bg; + return 0; +} + +int +jwxyz_XSetAlphaAllowed (Display *dpy, GC gc, Bool allowed) +{ + gc->gcv.alpha_allowed_p = allowed; + return 0; +} + +int +jwxyz_XSetAntiAliasing (Display *dpy, GC gc, Bool antialias_p) +{ + gc->gcv.antialias_p = antialias_p; + return 0; +} + + +int +XSetLineAttributes (Display *dpy, GC gc, unsigned int line_width, + int line_style, int cap_style, int join_style) +{ + gc->gcv.line_width = line_width; + Assert (line_style == LineSolid, "only LineSolid implemented"); +// gc->gcv.line_style = line_style; + gc->gcv.cap_style = cap_style; + gc->gcv.join_style = join_style; + return 0; +} + +int +XSetGraphicsExposures (Display *dpy, GC gc, Bool which) +{ + return 0; +} + +int +XSetFunction (Display *dpy, GC gc, int which) +{ + gc->gcv.function = which; + return 0; +} + +int +XSetSubwindowMode (Display *dpy, GC gc, int which) +{ + gc->gcv.subwindow_mode = which; + return 0; +} + +int +XSetClipMask (Display *dpy, GC gc, Pixmap m) +{ + Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup"); + + if (gc->gcv.clip_mask) { + XFreePixmap (dpy, gc->gcv.clip_mask); + CGImageRelease (gc->clip_mask); + } + + gc->gcv.clip_mask = copy_pixmap (dpy, m); + if (gc->gcv.clip_mask) + gc->clip_mask = + CGBitmapContextCreateImage (gc->gcv.clip_mask->cgc); + else + gc->clip_mask = 0; + + return 0; +} + +int +XSetClipOrigin (Display *dpy, GC gc, int x, int y) +{ + gc->gcv.clip_x_origin = x; + gc->gcv.clip_y_origin = y; + return 0; +} + + +Bool +XQueryPointer (Display *dpy, Window w, Window *root_ret, Window *child_ret, + int *root_x_ret, int *root_y_ret, + int *win_x_ret, int *win_y_ret, unsigned int *mask_ret) +{ + Assert (w && 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 + wpos.x = wpos.y = 0; + wpos = [nsw convertBaseToScreen:wpos]; + + NSPoint vpos; + // get bottom left of view on window, from bottom left + vpos.x = vpos.y = 0; + vpos = [w->window.view convertPoint:vpos toView:[nsw contentView]]; + + // get bottom left of view on screen, from bottom left + vpos.x += wpos.x; + vpos.y += wpos.y; + + // get top left of view on screen, from bottom left + vpos.y += w->frame.size.height; + + // get top left of view on screen, from top left + NSArray *screens = [NSScreen screens]; + 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 = (s * srect.size.height) - vpos.y; + + // get the mouse position on window, from bottom left + NSEvent *e = [NSApp currentEvent]; + NSPoint p = [e locationInWindow]; + + // get mouse position on screen, from bottom left + p.x += wpos.x; + p.y += wpos.y; + + // get mouse position on screen, from top left + p.y = srect.size.height - p.y; + + if (root_x_ret) *root_x_ret = (int) p.x; + 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; // #### poll the keyboard modifiers? + if (root_ret) *root_ret = 0; + if (child_ret) *child_ret = 0; + return True; +} + +Bool +XTranslateCoordinates (Display *dpy, Window w, Window dest_w, + int src_x, int src_y, + int *dest_x_ret, int *dest_y_ret, + Window *child_ret) +{ + Assert (w && 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 + wpos.x = wpos.y = 0; + wpos = [nsw convertBaseToScreen:wpos]; + + NSPoint vpos; + // get bottom left of view on window, from bottom left + vpos.x = vpos.y = 0; + vpos = [w->window.view convertPoint:vpos toView:[nsw contentView]]; + + // get bottom left of view on screen, from bottom left + vpos.x += wpos.x; + vpos.y += wpos.y; + + // get top left of view on screen, from bottom left + vpos.y += w->frame.size.height; + + // get top left of view on screen, from top left + NSArray *screens = [NSScreen screens]; + 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 = (s * srect.size.height) - vpos.y; + + // point starts out relative to top left of view + NSPoint p; + p.x = src_x; + p.y = src_y; + + // 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; + if (child_ret) + *child_ret = w; + return True; +} + + +KeySym +XKeycodeToKeysym (Display *dpy, KeyCode code, int index) +{ + return code; +} + +int +XLookupString (XKeyEvent *e, char *buf, int size, KeySym *k_ret, + XComposeStatus *xc) +{ + KeySym ks = XKeycodeToKeysym (0, e->keycode, 0); + char c = 0; + // Do not put non-ASCII KeySyms like XK_Shift_L and XK_Page_Up in the string. + if ((unsigned int) ks <= 255) + c = (char) ks; + + // Put control characters in the string. Not meta. + if (e->state & ControlMask) { + if (c >= 'a' && c <= 'z') // Upcase control. + c -= 'a'-'A'; + if (c >= '@' && c <= '_') // Shift to control page. + c -= '@'; + if (c == ' ') // C-SPC is NULL. + c = 0; + } + + if (k_ret) *k_ret = ks; + if (size > 0) buf[0] = c; + if (size > 1) buf[1] = 0; + return (size > 0 ? 1 : 0); +} + + +int +XFlush (Display *dpy) +{ + // Just let the event loop take care of this on its own schedule. + return 0; +} + +int +XSync (Display *dpy, Bool flush) +{ + return XFlush (dpy); +} + + +// declared in utils/visual.h +int +has_writable_cells (Screen *s, Visual *v) +{ + return 0; +} + +int +visual_depth (Screen *s, Visual *v) +{ + return 32; +} + +int +visual_cells (Screen *s, Visual *v) +{ + return 0xFFFFFF; +} + +int +visual_class (Screen *s, Visual *v) +{ + return TrueColor; +} + +// declared in utils/grabclient.h +Bool +use_subwindow_mode_p (Screen *screen, Window window) +{ + return False; +}