X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=OSX%2Fjwxyz.m;h=8d181279e84b990311f0d48a2d4b7c9df95db311;hb=88cfe534a698a0562e81345957a50714af1453bc;hp=37290ecf8c9ed9454b50a081de010d907aa915ba;hpb=f0261d8acab611f3433160e4f07367b870439739;p=xscreensaver diff --git a/OSX/jwxyz.m b/OSX/jwxyz.m index 37290ecf..8d181279 100644 --- a/OSX/jwxyz.m +++ b/OSX/jwxyz.m @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 1991-2009 Jamie Zawinski +/* xscreensaver, Copyright (c) 1991-2015 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 @@ -18,16 +18,50 @@ #import #import -#import +#import + +#ifdef USE_IPHONE +# 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 +# define colorWithDeviceRed colorWithRed + +# define NSFontTraitMask UIFontDescriptorSymbolicTraits +// The values for the flags for NSFontTraitMask and +// UIFontDescriptorSymbolicTraits match up, not that it really matters here. +# define NSBoldFontMask UIFontDescriptorTraitBold +# define NSFixedPitchFontMask UIFontDescriptorTraitMonoSpace +# define NSItalicFontMask UIFontDescriptorTraitItalic +#else +# import +#endif + +#import +#import +#import + #import "jwxyz.h" #import "jwxyz-timers.h" +#import "yarandom.h" +#import "utf8wc.h" +#import "xft.h" + +# define USE_BACKBUFFER /* must be in sync with XScreenSaverView.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 @@ -38,14 +72,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; }; }; @@ -53,11 +90,14 @@ struct jwxyz_Drawable { struct jwxyz_Display { Window main_window; Screen *screen; + int screen_count; 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 @@ -66,7 +106,10 @@ struct jwxyz_Display { struct jwxyz_Screen { Display *dpy; + CGBitmapInfo bitmap_info; + unsigned long black, white; Visual *visual; + int screen_number; }; struct jwxyz_GC { @@ -76,31 +119,379 @@ struct jwxyz_GC { }; struct jwxyz_Font { + Display *dpy; char *ps_name; NSFont *nsfont; float size; // points + char *xa_font; // 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; }; +struct jwxyz_XFontSet { + XFontStruct *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 +} + +// 24/32bpp -> 32bpp image conversion. +// Any of RGBA, BGRA, ABGR, or ARGB can be represented by a rotate of 0/8/16/24 +// bits and an optional byte order swap. + +// This type encodes such a conversion. +typedef unsigned convert_mode_t; + +// It's rotate, then swap. +// A rotation here shifts bytes forward in memory. On x86/ARM, that's a left +// rotate, and on PowerPC, a rightward rotation. +static const convert_mode_t CONVERT_MODE_ROTATE_MASK = 0x3; +static const convert_mode_t CONVERT_MODE_SWAP = 0x4; + + +// Converts an array of pixels ('src') from one format to another, placing the +// result in 'dest', according to the pixel conversion mode 'mode'. +static void +convert_row (uint32_t *dest, const void *src, size_t count, + convert_mode_t mode, size_t src_bpp) +{ + Assert (src_bpp == 24 || src_bpp == 32, "weird bpp"); + + // This works OK iff src == dest or src and dest do not overlap. + + if (!mode) { + if (src != dest) + memcpy (dest, src, count * 4); + return; + } + + // This is correct, but not fast. + convert_mode_t rot = (mode & CONVERT_MODE_ROTATE_MASK) * 8; + convert_mode_t flip = mode & CONVERT_MODE_SWAP; + + src_bpp /= 8; + + uint32_t *dest_end = dest + count; + while (dest != dest_end) { + uint32_t x; + + if (src_bpp == 4) + x = *(const uint32_t *)src; + else { // src_bpp == 3 + const uint8_t *src8 = (const uint8_t *)src; + // __LITTLE/BIG_ENDIAN__ are defined by the compiler. +# if defined __LITTLE_ENDIAN__ + x = src8[0] | (src8[1] << 8) | (src8[2] << 16) | 0xff000000; +# elif defined __BIG_ENDIAN__ + x = (src8[0] << 24) | (src8[1] << 16) | (src8[2] << 8) | 0xff; +# else +# error "Can't determine system endianness." +# endif + } + + src = (const uint8_t *)src + src_bpp; + + /* The naive (i.e. ubiquitous) portable implementation of bitwise rotation, + for 32-bit integers, is: + + (x << rot) | (x >> (32 - rot)) + + This works nearly everywhere. Compilers on x86 wil generally recognize + the idiom and convert it to a ROL instruction. But there's a problem + here: according to the C specification, bit shifts greater than or equal + to the length of the integer are undefined. And if rot = 0: + 1. (x << 0) | (x >> (32 - 0)) + 2. (x << 0) | (x >> 32) + 3. (x << 0) | (Undefined!) + + Still, when the compiler converts this to a ROL on x86, everything works + as intended. But, there are two additional problems when Clang does + compile-time constant expression evaluation with the (x >> 32) + expression: + 1. Instead of evaluating it to something reasonable (either 0, like a + human would intuitively expect, or x, like x86 would with SHR), Clang + seems to pull a value out of nowhere, like -1, or some other random + number. + 2. Clang's warning for this, -Wshift-count-overflow, only works when the + shift count is a literal constant, as opposed to an arbitrary + expression that is optimized down to a constant. + Put together, this means that the assertions in jwxyz_make_display with + convert_px break with the above naive rotation, but only for a release + build. + + http://blog.regehr.org/archives/1063 + http://llvm.org/bugs/show_bug.cgi?id=17332 + As described in those links, there is a solution here: Masking the + undefined shift with '& 31' as below makes the experesion well-defined + again. And LLVM is set to pick up on this safe version of the idiom and + use a rotation instruction on architectures (like x86) that support it, + just like it does with the unsafe version. + + Too bad LLVM doesn't want to pick up on that particular optimization + here. Oh well. At least this code usually isn't critical w.r.t. + performance. + */ + +# if defined __LITTLE_ENDIAN__ + x = (x << rot) | (x >> ((32 - rot) & 31)); +# elif defined __BIG_ENDIAN__ + x = (x >> rot) | (x << ((32 - rot) & 31)); +# endif + + if (flip) + x = __builtin_bswap32(x); // LLVM/GCC built-in function. + + *dest = x; + ++dest; + } +} + + +// Converts a single pixel. +static uint32_t +convert_px (uint32_t px, convert_mode_t mode) +{ + convert_row (&px, &px, 1, mode, 32); + return px; +} + + +// This returns the inverse conversion mode, such that: +// pixel +// == convert_px(convert_px(pixel, mode), convert_mode_invert(mode)) +// == convert_px(convert_px(pixel, convert_mode_invert(mode)), mode) +static convert_mode_t +convert_mode_invert (convert_mode_t mode) +{ + // swap(0); rot(n) == rot(n); swap(0) + // swap(1); rot(n) == rot(-n); swap(1) + return mode & CONVERT_MODE_SWAP ? mode : CONVERT_MODE_ROTATE_MASK & -mode; +} + + +// This combines two conversions into one, such that: +// convert_px(convert_px(pixel, mode0), mode1) +// == convert_px(pixel, convert_mode_merge(mode0, mode1)) +static convert_mode_t +convert_mode_merge (convert_mode_t m0, convert_mode_t m1) +{ + // rot(r0); swap(s0); rot(r1); swap(s1) + // rot(r0); rot(s0 ? -r1 : r1); swap(s0); swap(s1) + // rot(r0 + (s0 ? -r1 : r1)); swap(s0 + s1) + return + ((m0 + (m0 & CONVERT_MODE_SWAP ? -m1 : m1)) & CONVERT_MODE_ROTATE_MASK) | + ((m0 ^ m1) & CONVERT_MODE_SWAP); +} + + +// This returns a conversion mode that converts an arbitrary 32-bit format +// specified by bitmap_info to RGBA. +static convert_mode_t +convert_mode_to_rgba (CGBitmapInfo bitmap_info) +{ + // Former default: kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little + // i.e. BGRA + // red = 0x00FF0000; + // green = 0x0000FF00; + // blue = 0x000000FF; + + // RGBA: kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big + + CGImageAlphaInfo alpha_info = + (CGImageAlphaInfo)(bitmap_info & kCGBitmapAlphaInfoMask); + + Assert (! (bitmap_info & kCGBitmapFloatComponents), + "kCGBitmapFloatComponents unsupported"); + Assert (alpha_info != kCGImageAlphaOnly, "kCGImageAlphaOnly not supported"); + + convert_mode_t rot = alpha_info == kCGImageAlphaFirst || + alpha_info == kCGImageAlphaPremultipliedFirst || + alpha_info == kCGImageAlphaNoneSkipFirst ? + 3 : 0; + + CGBitmapInfo byte_order = bitmap_info & kCGBitmapByteOrderMask; + + Assert (byte_order == kCGBitmapByteOrder32Little || + byte_order == kCGBitmapByteOrder32Big, + "byte order not supported"); + + convert_mode_t swap = byte_order == kCGBitmapByteOrder32Little ? + CONVERT_MODE_SWAP : 0; + if (swap) + rot = CONVERT_MODE_ROTATE_MASK & -rot; + return swap | rot; +} + + +union color_bytes +{ + uint32_t pixel; + uint8_t bytes[4]; +}; + + +static uint32_t +alloc_color (Display *dpy, uint16_t r, uint16_t g, uint16_t b, uint16_t a) +{ + union color_bytes color; + + /* Instead of (int)(c / 256.0), another possibility is + (int)(c * 255.0 / 65535.0 + 0.5). This can be calculated using only + uint8_t integer_math(uint16_t c) { + unsigned c0 = c + 128; + return (c0 - (c0 >> 8)) >> 8; + } + */ + + color.bytes[0] = r >> 8; + color.bytes[1] = g >> 8; + color.bytes[2] = b >> 8; + color.bytes[3] = a >> 8; + + return + convert_px (color.pixel, + convert_mode_invert (convert_mode_to_rgba (dpy->screen->bitmap_info))); +} + + +static void +query_color (Display *dpy, unsigned long pixel, uint8_t *rgba) +{ + union color_bytes color; + color.pixel = convert_px ((uint32_t)pixel, + convert_mode_to_rgba (dpy->screen->bitmap_info)); + for (unsigned i = 0; i != 4; ++i) + rgba[i] = color.bytes[i]; +} + + +static void +query_color_float (Display *dpy, unsigned long pixel, float *rgba) +{ + uint8_t rgba8[4]; + query_color (dpy, pixel, rgba8); + for (unsigned i = 0; i != 4; ++i) + rgba[i] = rgba8[i] * (1.0f / 255.0f); +} + + +/* We keep a list of all of the Displays that have been created and not + yet freed so that they can have sensible display numbers. If three + displays are created (0, 1, 2) and then #1 is closed, then the fourth + display will be given the now-unused display number 1. (Everything in + here assumes a 1:1 Display/Screen mapping.) + + The size of this array is the most number of live displays at one time. + So if it's 20, then we'll blow up if the system has 19 monitors and also + has System Preferences open (the small preview window). + + Note that xlockmore-style savers tend to allocate big structures, so + setting this to 1000 will waste a few megabytes. Also some of them assume + that the number of screens never changes, so dynamically expanding this + array won't work. + */ +# ifndef USE_IPHONE +static Display *jwxyz_live_displays[20] = { 0, }; +# endif + 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)); d->screen->dpy = d; + d->screen_count = 1; + d->screen->screen_number = 0; +# ifndef USE_IPHONE + { + // Find the first empty slot in live_displays and plug us in. + int size = sizeof(jwxyz_live_displays) / sizeof(*jwxyz_live_displays); + int i; + for (i = 0; i < size; i++) { + if (! jwxyz_live_displays[i]) + break; + } + if (i >= size) abort(); + jwxyz_live_displays[i] = d; + d->screen_count = size; + d->screen->screen_number = i; + } +# endif // !USE_IPHONE + +# ifdef USE_BACKBUFFER + d->screen->bitmap_info = CGBitmapContextGetBitmapInfo (cgc); +# else + d->screen->bitmap_info = (kCGImageAlphaNoneSkipFirst | + kCGBitmapByteOrder32Little); +# endif + d->screen->black = alloc_color (d, 0x0000, 0x0000, 0x0000, 0xFFFF); + d->screen->white = alloc_color (d, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF); + +# if 0 + // Tests for the image conversion modes. + { + const uint32_t key = 0x04030201; +# ifdef __LITTLE_ENDIAN__ + assert (convert_px (key, 0) == key); + assert (convert_px (key, 1) == 0x03020104); + assert (convert_px (key, 3) == 0x01040302); + assert (convert_px (key, 4) == 0x01020304); + assert (convert_px (key, 5) == 0x04010203); +# endif + for (unsigned i = 0; i != 8; ++i) { + assert (convert_px(convert_px(key, i), convert_mode_invert(i)) == key); + assert (convert_mode_invert(convert_mode_invert(i)) == i); + } + + for (unsigned i = 0; i != 8; ++i) { + for (unsigned j = 0; j != 8; ++j) + assert (convert_px(convert_px(key, i), j) == + convert_px(key, convert_mode_merge(i, j))); + } + } +# endif + Visual *v = (Visual *) calloc (1, sizeof(Visual)); v->class = TrueColor; - v->red_mask = 0x00FF0000; - v->green_mask = 0x0000FF00; - v->blue_mask = 0x000000FF; + v->red_mask = alloc_color (d, 0xFFFF, 0x0000, 0x0000, 0x0000); + v->green_mask = alloc_color (d, 0x0000, 0xFFFF, 0x0000, 0x0000); + v->blue_mask = alloc_color (d, 0x0000, 0x0000, 0xFFFF, 0x0000); + CGBitmapInfo byte_order = d->screen->bitmap_info & kCGBitmapByteOrderMask; + Assert ( ! (d->screen->bitmap_info & kCGBitmapFloatComponents) && + (byte_order == kCGBitmapByteOrder32Little || + byte_order == kCGBitmapByteOrder32Big), + "invalid bits per channel"); v->bits_per_rgb = 8; d->screen->visual = v; @@ -109,31 +500,48 @@ 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; - w->window.background = BlackPixel(0,0); + CFRetain (w->window.view); // needed for garbage collection? + w->window.background = BlackPixel(d,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; } void jwxyz_free_display (Display *dpy) { - jwxyz_XtRemoveInput_all (dpy); - // #### jwxyz_XtRemoveTimeOut_all (); + jwxyz_sources_free (dpy->timers_data); +# ifndef USE_IPHONE + { + // Find us in live_displays and clear that slot. + int size = ScreenCount(dpy); + int i; + for (i = 0; i < size; i++) { + if (dpy == jwxyz_live_displays[i]) { + jwxyz_live_displays[i] = 0; + break; + } + } + if (i >= size) abort(); + } +# endif // !USE_IPHONE + free (dpy->screen->visual); free (dpy->screen); + CFRelease (dpy->main_window->window.view); free (dpy->main_window); free (dpy); } @@ -142,22 +550,43 @@ jwxyz_free_display (Display *dpy) void * jwxyz_window_view (Window w) { - Assert (w->type == WINDOW, "not a window"); + 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) +jwxyz_window_resized (Display *dpy, Window w, + int new_x, int new_y, int new_width, int new_height, + void *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; + 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; @@ -168,10 +597,19 @@ jwxyz_window_resized (Display *dpy, Window w) 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. -#if 0 { // Figure out this screen's colorspace, and use that for every CGImage. // @@ -181,15 +619,36 @@ jwxyz_window_resized (Display *dpy, Window w) dpy->colorspace = CGColorSpaceCreateWithPlatformColorSpace (profile); Assert (dpy->colorspace, "unable to find colorspace"); } -# else +# else // USE_BACKBUFFER // WTF? It's faster if we *do not* use the screen's colorspace! // dpy->colorspace = CGColorSpaceCreateDeviceRGB(); -# endif +# 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. + // CGContextSynchronize is another possibility. + CGContextFlush(dpy->main_window->cgc); +} + jwxyz_sources_data * display_sources_data (Display *dpy) { @@ -230,7 +689,13 @@ XDisplayNumberOfScreen (Screen *s) int XScreenNumberOfScreen (Screen *s) { - return 0; + return s->screen_number; +} + +int +jwxyz_ScreenCount (Display *dpy) +{ + return dpy->screen_count; } int @@ -245,37 +710,54 @@ XDisplayHeight (Display *dpy, int screen) return (int) dpy->main_window->frame.size.height; } +unsigned long +XBlackPixelOfScreen(Screen *screen) +{ + return screen->black; +} + +unsigned long +XWhitePixelOfScreen(Screen *screen) +{ + return screen->white; +} + +unsigned long +XCellsOfScreen(Screen *screen) +{ + Visual *v = screen->visual; + return v->red_mask | v->green_mask | v->blue_mask; +} static void -validate_pixel (unsigned long pixel, unsigned int depth, BOOL alpha_allowed_p) +validate_pixel (Display *dpy, 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)), + Assert (((pixel & BlackPixel(dpy,0)) == BlackPixel(dpy,0)), "bogus color pixel"); } static void -set_color (CGContextRef cgc, unsigned long argb, unsigned int depth, - BOOL alpha_allowed_p, BOOL fill_p) +set_color (Display *dpy, CGContextRef cgc, unsigned long argb, + unsigned int depth, BOOL alpha_allowed_p, BOOL fill_p) { - validate_pixel (argb, depth, alpha_allowed_p); + validate_pixel (dpy, 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; + float rgba[4]; + query_color_float (dpy, argb, rgba); if (fill_p) - CGContextSetRGBFillColor (cgc, r, g, b, a); + CGContextSetRGBFillColor (cgc, rgba[0], rgba[1], rgba[2], rgba[3]); else - CGContextSetRGBStrokeColor (cgc, r, g, b, a); + CGContextSetRGBStrokeColor (cgc, rgba[0], rgba[1], rgba[2], rgba[3]); } } @@ -330,33 +812,32 @@ 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. */ static void -push_color_gc (Drawable d, GC gc, unsigned long color, +push_color_gc (Display *dpy, 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; + case GXset: color = (depth == 1 ? 1 : WhitePixel(dpy,0)); break; + case GXclear: color = (depth == 1 ? 0 : BlackPixel(dpy,0)); break; } CGContextRef cgc = d->cgc; - - set_color (cgc, color, depth, gc->gcv.alpha_allowed_p, fill_p); + set_color (dpy, cgc, color, depth, gc->gcv.alpha_allowed_p, fill_p); CGContextSetShouldAntialias (cgc, antialias_p); } @@ -364,17 +845,28 @@ push_color_gc (Drawable d, GC gc, unsigned long color, /* 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_fg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p) { - push_color_gc (d, gc, gc->gcv.foreground, gc->gcv.antialias_p, fill_p); + push_color_gc (dpy, 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_bg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p) +{ + push_color_gc (dpy, d, gc, gc->gcv.background, gc->gcv.antialias_p, fill_p); +} + +static Bool +bitmap_context_p (Drawable d) { - push_color_gc (d, gc, gc->gcv.background, gc->gcv.antialias_p, fill_p); +# ifdef USE_BACKBUFFER + return True; +# else + // Because of the backbuffer, all iPhone Windows work like Pixmaps. + return d->type == PIXMAP; +# endif } @@ -383,12 +875,18 @@ 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... */ #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) @@ -396,72 +894,125 @@ XDrawPoints (Display *dpy, Drawable d, GC gc, int i; CGRect wr = d->frame; - push_fg_gc (d, gc, YES); +# ifdef XDRAWPOINTS_CGDATA -# ifdef XDRAWPOINTS_IMAGES + if (bitmap_context_p (d)) + { + CGContextRef cgc = d->cgc; + void *data = CGBitmapContextGetData (cgc); + size_t bpr = CGBitmapContextGetBytesPerRow (cgc); + size_t w = CGBitmapContextGetWidth (cgc); + size_t h = CGBitmapContextGetHeight (cgc); - 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); + Assert (data, "no bitmap data in Drawable"); - 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; + unsigned long argb = gc->gcv.foreground; + validate_pixel (dpy, argb, gc->depth, gc->gcv.alpha_allowed_p); + if (gc->depth == 1) + argb = (gc->gcv.foreground ? WhitePixel(dpy,0) : BlackPixel(dpy,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 = (unsigned int) argb; + } + } } else { - rect.origin.x = wr.origin.x + points->x; - rect.origin.y = wr.origin.y + wr.size.height - points->y - 1; + 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 = (unsigned int) argb; + } + } } - //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace"); - CGContextDrawImage (cgc, rect, cgi); - points++; - } + } else /* d->type == WINDOW */ + +# endif /* XDRAWPOINTS_CGDATA */ + { + push_fg_gc (dpy, d, gc, YES); + +# ifdef XDRAWPOINTS_IMAGES + + unsigned int argb = gc->gcv.foreground; + validate_pixel (dpy, argb, gc->depth, gc->gcv.alpha_allowed_p); + if (gc->depth == 1) + argb = (gc->gcv.foreground ? WhitePixel(dpy,0) : BlackPixel(dpy,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. */ + dpy->screen->bitmap_info, + 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); + CGImageRelease (cgi); # else /* ! XDRAWPOINTS_IMAGES */ - CGRect *rects = (CGRect *) malloc (count * sizeof(CGRect)); - CGRect *r = rects; + 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; + 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++; } - points++; - r++; - } - CGContextFillRects (d->cgc, rects, count); - free (rects); + CGContextFillRects (d->cgc, rects, count); + free (rects); # endif /* ! XDRAWPOINTS_IMAGES */ - pop_gc (d, gc); + pop_gc (d, gc); + } + + invalidate_drawable_cache (d); return 0; } @@ -477,9 +1028,28 @@ XDrawPoint (Display *dpy, Drawable d, GC gc, int x, int y) } +static void draw_rects (Display *dpy, Drawable d, GC gc, + const XRectangle *rectangles, unsigned nrectangles, + unsigned long pixel, BOOL fill_p); + static void draw_rect (Display *, Drawable, GC, int x, int y, unsigned int width, unsigned int height, - BOOL foreground_p, BOOL fill_p); + unsigned long pixel, BOOL fill_p); + +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, @@ -487,6 +1057,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"); @@ -497,15 +1068,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))), - gc->depth, gc->gcv.alpha_allowed_p, YES); - draw_rect (dpy, dst, 0, dst_x, dst_y, width, height, YES, YES); + draw_rect (dpy, dst, 0, dst_x, dst_y, width, height, + (gc->gcv.function == GXset + ? (gc->depth == 1 ? 1 : WhitePixel(dpy,0)) + : (gc->depth == 1 ? 0 : BlackPixel(dpy,0))), YES); return 0; } @@ -537,8 +1107,6 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc, // Clip rects to frames... // -// CGRect orig_src_rect = src_rect; - CGRect orig_dst_rect = dst_rect; # define CLIP(THIS,THAT,VAL,SIZE) do { \ float off = THIS##_rect.origin.VAL; \ @@ -559,162 +1127,319 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc, 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 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) + 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) { + + Assert((int)src_rect.size.width == (int)dst_rect.size.width || + (int)src_rect.size.height == (int)dst_rect.size.height, + "size mismatch"); + + src_rect.size.width = 0; + src_rect.size.height = 0; + dst_rect.size.width = 0; + dst_rect.size.height = 0; + } - NSObject *releaseme = 0; - CGImageRef cgi; - BOOL mask_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); - - // 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. - CGImageRef cgi2 = CGImageCreateWithImageInRect (cgi, src_rect); - CGImageRelease (cgi); - cgi = cgi2; + BOOL mask_p = src->type == PIXMAP && src->pixmap.depth == 1; + + + /* 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) && + 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; + } } +# ifndef USE_BACKBUFFER + } else if (src->type == WINDOW && src == dst && !mask_p) { - if (src->pixmap.depth == 1) - mask_p = YES; + // 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) + // + + push_gc (dst, gc); - } else { /* (src->type == WINDOW) */ - NSRect nsfrom; - nsfrom.origin.x = src_rect.origin.x; + 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); -#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 + pop_gc (dst, gc); -#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 +# endif + } else { -#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); + NSObject *releaseme = 0; + CGImageRef cgi; + BOOL free_cgi_p = NO; + + if (bitmap_context_p (src)) { + + // 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; + } -#endif +# 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 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 + } - // 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); - } + CGContextRef cgc = dst->cgc; - if (mask_p) { // src depth == 1 + if (mask_p) { // src depth == 1 - push_bg_gc (dst, gc, YES); + push_bg_gc (dpy, dst, gc, YES); - // fill the destination rectangle with solid background... - CGContextFillRect (dst->cgc, orig_dst_rect); + // fill the destination rectangle with solid background... + CGContextFillRect (cgc, dst_rect); - // 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, - gc->gcv.alpha_allowed_p, YES); - CGContextClipToMask (dst->cgc, dst_rect, cgi); - CGContextFillRect (dst->cgc, dst_rect); + Assert (cgc, "no CGC with 1-bit XCopyArea"); - pop_gc (dst, gc); + // 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 (dpy, cgc, gc->gcv.foreground, gc->depth, + gc->gcv.alpha_allowed_p, YES); + CGContextClipToMask (cgc, dst_rect, cgi); + CGContextFillRect (cgc, dst_rect); - } else { // src depth > 1 + pop_gc (dst, gc); - push_gc (dst, gc); + } else { // src depth > 1 - // 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 (dst->cgc, gc->gcv.background, gc->depth, - gc->gcv.alpha_allowed_p, YES); - CGContextFillRect (dst->cgc, orig_dst_rect); + push_gc (dst, gc); + + // copy the CGImage onto the destination CGContext + //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace, "bad colorspace"); + CGContextDrawImage (cgc, dst_rect, cgi); + + pop_gc (dst, gc); } - // copy the CGImage onto the destination CGContext - //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace"); - CGContextDrawImage (dst->cgc, dst_rect, cgi); + if (free_cgi_p) CGImageRelease (cgi); - pop_gc (dst, gc); + if (releaseme) [releaseme release]; } - - CGImageRelease (cgi); - if (releaseme) [releaseme release]; + + // 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 clear to the background, so that clipped bits end + // up in the bg color instead of simply not being copied. + // + // This has to happen after the copy, because if it happens before, the + // cleared area will get grabbed if it overlaps with the source rectangle. + // + if (clipped && dst->type == WINDOW) { + // Int to float and back again. It's not very safe, but it seems to work. + int dst_x0 = dst_rect.origin.x; + + // Flip the Y-axis a second time. + 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; + + 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"); + + XRectangle rects[4]; + XRectangle *rects_end = rects; + + if (orig_dst_y < dst_y0) { + rects_end->x = orig_dst_x; + rects_end->y = orig_dst_y; + rects_end->width = orig_width; + rects_end->height = dst_y0 - orig_dst_y; + ++rects_end; + } + + if (orig_dst_y + orig_height > dst_y0 + height0) { + rects_end->x = orig_dst_x; + rects_end->y = dst_y0 + height0; + rects_end->width = orig_width; + rects_end->height = orig_dst_y + orig_height - dst_y0 - height0; + ++rects_end; + } + + if (orig_dst_x < dst_x0) { + rects_end->x = orig_dst_x; + rects_end->y = dst_y0; + rects_end->width = dst_x0 - orig_dst_x; + rects_end->height = height0; + ++rects_end; + } + + if (dst_x0 + width0 < orig_dst_x + orig_width) { + rects_end->x = dst_x0 + width0; + rects_end->y = dst_y0; + rects_end->width = orig_dst_x + orig_width - dst_x0 - width0; + rects_end->height = height0; + ++rects_end; + } + + unsigned long old_function = gc->gcv.function; + gc->gcv.function = GXcopy; + draw_rects (dpy, dst, gc, rects, rects_end - rects, + dst->window.background, + YES); + gc->gcv.function = old_function; + } + + invalidate_drawable_cache (dst); return 0; } @@ -734,6 +1459,52 @@ XCopyPlane (Display *dpy, Drawable src, Drawable dest, GC gc, } +static CGPoint +map_point (Drawable d, int x, int y) +{ + const CGRect *wr = &d->frame; + CGPoint p; + p.x = wr->origin.x + x; + p.y = wr->origin.y + wr->size.height - y; + return p; +} + + +static void +adjust_point_for_line (GC gc, CGPoint *p) +{ + // Here's the authoritative discussion on how X draws lines: + // http://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:CreateGC:line-width + if (gc->gcv.line_width <= 1) { + /* Thin lines are "drawn using an unspecified, device-dependent + algorithm", but seriously though, Bresenham's algorithm. Bresenham's + algorithm runs to and from pixel centers. + + There's a few screenhacks (Maze, at the very least) that set line_width + to 1 when it probably should be set to 0, so it's line_width <= 1 + instead of < 1. + */ + p->x += 0.5; + p->y -= 0.5; + } else { + /* Thick lines OTOH run from the upper-left corners of pixels. This means + that a horizontal thick line of width 1 straddles two scan lines. + Aliasing requires one of these scan lines be chosen; the following + nudges the point so that the right choice is made. */ + p->y -= 1e-3; + } +} + + +static CGPoint +point_for_line (Drawable d, GC gc, int x, int y) +{ + CGPoint result = map_point (d, x, y); + adjust_point_for_line (gc, &result); + return result; +} + + int XDrawLine (Display *dpy, Drawable d, GC gc, int x1, int y1, int x2, int y2) { @@ -744,25 +1515,26 @@ XDrawLine (Display *dpy, Drawable d, GC gc, int x1, int y1, int x2, int y2) 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 + else { + if (!w) + w = 1; // Actually show zero-length lines. 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); - - set_line_mode (d->cgc, &gc->gcv); - CGContextBeginPath (d->cgc); - CGContextMoveToPoint (d->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); + CGPoint p = point_for_line (d, gc, x1, y1); + + push_fg_gc (dpy, d, gc, NO); + + CGContextRef cgc = d->cgc; + set_line_mode (cgc, &gc->gcv); + CGContextBeginPath (cgc); + CGContextMoveToPoint (cgc, p.x, p.y); + p = point_for_line(d, gc, x2, y2); + CGContextAddLineToPoint (cgc, p.x, p.y); + CGContextStrokePath (cgc); pop_gc (d, gc); + invalidate_drawable_cache (d); return 0; } @@ -771,10 +1543,12 @@ 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); - set_line_mode (d->cgc, &gc->gcv); + CGPoint p; + push_fg_gc (dpy, 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. @@ -782,25 +1556,24 @@ XDrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count, 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; + p = point_for_line(d, gc, points->x, 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; p.y -= points->y; } else { - p.x = wr.origin.x + points->x; - p.y = wr.origin.y + wr.size.height - points->y; + p = point_for_line(d, gc, points->x, 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; } @@ -809,22 +1582,22 @@ int XDrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count) { int i; - CGRect wr = d->frame; - push_fg_gc (d, gc, NO); - set_line_mode (d->cgc, &gc->gcv); - CGContextBeginPath (d->cgc); + CGContextRef cgc = d->cgc; + + push_fg_gc (dpy, d, gc, NO); + set_line_mode (cgc, &gc->gcv); + CGContextBeginPath (cgc); for (i = 0; i < count; i++) { - CGContextMoveToPoint (d->cgc, - wr.origin.x + segments->x1, - wr.origin.y + wr.size.height - segments->y1); - CGContextAddLineToPoint (d->cgc, - wr.origin.x + segments->x2, - wr.origin.y + wr.size.height - segments->y2); + CGPoint p = point_for_line (d, gc, segments->x1, segments->y1); + CGContextMoveToPoint (cgc, p.x, p.y); + p = point_for_line (d, gc, segments->x2, segments->y2); + CGContextAddLineToPoint (cgc, p.x, p.y); segments++; } - CGContextStrokePath (d->cgc); + CGContextStrokePath (cgc); pop_gc (d, gc); + invalidate_drawable_cache (d); return 0; } @@ -832,7 +1605,7 @@ XDrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count) int XClearWindow (Display *dpy, Window win) { - Assert (win->type == WINDOW, "not a window"); + 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); } @@ -840,49 +1613,122 @@ XClearWindow (Display *dpy, Window win) int XSetWindowBackground (Display *dpy, Window w, unsigned long pixel) { - Assert (w->type == WINDOW, "not a window"); - validate_pixel (pixel, 32, NO); + Assert (w && w->type == WINDOW, "not a window"); + validate_pixel (dpy, 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) +draw_rects (Display *dpy, Drawable d, GC gc, + const XRectangle *rectangles, unsigned nrectangles, + unsigned long pixel, 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); + Assert (!gc || gc->depth == drawable_depth (d), "depth mismatch"); + + CGContextRef cgc = d->cgc; + + Bool fast_fill_p = + fill_p && + bitmap_context_p (d) && + (!gc || (gc->gcv.function == GXcopy && + !gc->gcv.alpha_allowed_p && + !gc->gcv.clip_mask)); + + if (!fast_fill_p) { + if (gc) { + push_color_gc (dpy, d, gc, pixel, gc->gcv.antialias_p, fill_p); + if (!fill_p) + set_line_mode (cgc, &gc->gcv); + } else { + set_color (dpy, d->cgc, pixel, drawable_depth (d), NO, fill_p); + } } - if (fill_p) - CGContextFillRect (d->cgc, r); - else { - if (gc) - set_line_mode (d->cgc, &gc->gcv); - CGContextStrokeRect (d->cgc, r); + for (unsigned i = 0; i != nrectangles; ++i) { + + int x = rectangles[i].x; + int y = rectangles[i].y; + int width = rectangles[i].width; + int height = rectangles[i].height; + + if (fast_fill_p) { + int + dw = CGBitmapContextGetWidth (cgc), + dh = CGBitmapContextGetHeight (cgc); + + if (x >= dw || y >= dh) + continue; + + if (x < 0) { + width += x; + x = 0; + } + + if (y < 0) { + height += y; + y = 0; + } + + if (width <= 0 || height <= 0) + continue; + + int max_width = dw - x; + if (width > max_width) + width = max_width; + int max_height = dh - y; + if (height > max_height) + height = max_height; + + if (drawable_depth (d) == 1) + pixel = pixel ? WhitePixel(dpy, 0) : BlackPixel(dpy, 0); + + size_t dst_bytes_per_row = CGBitmapContextGetBytesPerRow (d->cgc); + void *dst = seek_xy (CGBitmapContextGetData (d->cgc), + dst_bytes_per_row, x, y); + + Assert(sizeof(wchar_t) == 4, "somebody changed the ABI"); + while (height) { + // Would be nice if Apple used SSE/NEON in wmemset. Maybe someday. + wmemset (dst, pixel, width); + --height; + dst = (char *) dst + dst_bytes_per_row; + } + + } else { + CGRect r; + r.origin = map_point (d, x, y); + r.origin.y -= height; + r.size.width = width; + r.size.height = height; + if (fill_p) + CGContextFillRect (cgc, r); + else { + adjust_point_for_line (gc, &r.origin); + CGContextStrokeRect (cgc, r); + } + } } - if (gc) + if (!fast_fill_p && gc) pop_gc (d, gc); + invalidate_drawable_cache (d); } +static void +draw_rect (Display *dpy, Drawable d, GC gc, + int x, int y, unsigned int width, unsigned int height, + unsigned long pixel, BOOL fill_p) +{ + XRectangle r = {x, y, width, height}; + draw_rects (dpy, d, gc, &r, 1, pixel, fill_p); +} 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); + draw_rect (dpy, d, gc, x, y, width, height, gc->gcv.foreground, YES); return 0; } @@ -890,26 +1736,14 @@ 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); + draw_rect (dpy, d, gc, x, y, width, height, gc->gcv.foreground, NO); return 0; } int XFillRectangles (Display *dpy, Drawable d, GC gc, XRectangle *rects, int n) { - CGRect wr = d->frame; - int i; - 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 (d->cgc, r); - rects++; - } - pop_gc (d, gc); + draw_rects (dpy, d, gc, rects, n, gc->gcv.foreground, YES); return 0; } @@ -917,9 +1751,8 @@ XFillRectangles (Display *dpy, Drawable d, GC gc, XRectangle *rects, int n) 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); - draw_rect (dpy, win, 0, x, y, w, h, NO, YES); + Assert (win && win->type == WINDOW, "not a window"); + draw_rect (dpy, win, 0, x, y, w, h, win->window.background, YES); return 0; } @@ -930,10 +1763,11 @@ XFillPolygon (Display *dpy, Drawable d, GC gc, { CGRect wr = d->frame; int i; - push_fg_gc (d, gc, YES); - CGContextBeginPath (d->cgc); + push_fg_gc (dpy, d, gc, YES); + 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; @@ -943,16 +1777,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; } @@ -980,30 +1815,32 @@ draw_arc (Display *dpy, Drawable d, GC gc, int x, int y, BOOL clockwise = angle2 < 0; BOOL closed_p = (angle2 >= 360*64 || angle2 <= -360*64); - push_fg_gc (d, gc, fill_p); + push_fg_gc (dpy, 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; } @@ -1049,12 +1886,12 @@ XFillArcs (Display *dpy, Drawable d, GC gc, XArc *arcs, int narcs) static void -gcv_defaults (XGCValues *gcv, int depth) +gcv_defaults (Display *dpy, 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->foreground = (depth == 1 ? 1 : WhitePixel(dpy,0)); + gcv->background = (depth == 1 ? 0 : BlackPixel(dpy,0)); gcv->line_width = 1; gcv->cap_style = CapNotLast; gcv->join_style = JoinMiter; @@ -1065,8 +1902,12 @@ gcv_defaults (XGCValues *gcv, int depth) } static void -set_gcv (GC gc, XGCValues *from, unsigned long mask) +set_gcv (Display *dpy, 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; @@ -1081,22 +1922,23 @@ set_gcv (GC gc, XGCValues *from, unsigned long mask) 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, + if (mask & GCForeground) validate_pixel (dpy, from->foreground, gc->depth, gc->gcv.alpha_allowed_p); - if (mask & GCBackground) validate_pixel (from->background, gc->depth, + if (mask & GCBackground) validate_pixel (dpy, 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"); } @@ -1104,21 +1946,17 @@ 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; - } + gc->depth = drawable_depth (d); - gcv_defaults (&gc->gcv, gc->depth); - set_gcv (gc, xgcv, mask); + gcv_defaults (dpy, &gc->gcv, gc->depth); + set_gcv (dpy, gc, xgcv, mask); return gc; } int XChangeGC (Display *dpy, GC gc, unsigned long mask, XGCValues *gcv) { - set_gcv (gc, gcv, mask); + set_gcv (dpy, gc, gcv, mask); return 0; } @@ -1143,7 +1981,7 @@ XFreeGC (Display *dpy, GC gc) Status XGetWindowAttributes (Display *dpy, Window w, XWindowAttributes *xgwa) { - Assert (w->type == WINDOW, "not a window"); + 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; @@ -1165,7 +2003,7 @@ XGetGeometry (Display *dpy, Drawable d, Window *root_ret, *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); + *d_ret = drawable_depth (d); *root_ret = RootWindow (dpy, 0); *bw_ret = 0; return True; @@ -1175,13 +2013,8 @@ XGetGeometry (Display *dpy, Drawable d, Window *root_ret, 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) )); + color->pixel = alloc_color (dpy, + color->red, color->green, color->blue, 0xFFFF); return 1; } @@ -1232,7 +2065,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")) { @@ -1271,13 +2104,12 @@ XAllocNamedColor (Display *dpy, Colormap cmap, char *name, 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; + validate_pixel (dpy, color->pixel, 32, NO); + uint8_t rgba[4]; + query_color(dpy, color->pixel, rgba); + color->red = (rgba[0] << 8) | rgba[0]; + color->green = (rgba[1] << 8) | rgba[1]; + color->blue = (rgba[2] << 8) | rgba[2]; color->flags = DoRed|DoGreen|DoBlue; return 0; } @@ -1343,7 +2175,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; } @@ -1361,13 +2193,13 @@ XCreateImage (Display *dpy, Visual *visual, unsigned int depth, ximage->format = format; ximage->data = data; ximage->bitmap_unit = 8; - ximage->byte_order = MSBFirst; + ximage->byte_order = LSBFirst; 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->red_mask = (depth == 1 ? 0 : dpy->screen->visual->red_mask); + ximage->green_mask = (depth == 1 ? 0 : dpy->screen->visual->green_mask); + ximage->blue_mask = (depth == 1 ? 0 : dpy->screen->visual->blue_mask); ximage->bits_per_pixel = (depth == 1 ? 1 : 32); ximage->bytes_per_line = bytes_per_line; @@ -1378,8 +2210,13 @@ XCreateImage (Display *dpy, Visual *visual, unsigned int depth, 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); + XImage *to = (XImage *) malloc (sizeof(*to)); + memcpy (to, from, sizeof(*from)); + to->width = w; + to->height = h; + to->bytes_per_line = 0; + XInitImage (to); + to->data = (char *) malloc (h * to->bytes_per_line); if (x >= from->width) @@ -1474,7 +2311,7 @@ flipbits (unsigned const char *in, unsigned char *out, int length) 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF }; - while (--length > 0) + while (length-- > 0) *out++ = table[*in++]; } @@ -1486,6 +2323,7 @@ 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"); @@ -1523,15 +2361,16 @@ 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 - ? (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); + draw_rect (dpy, d, 0, dest_x, dest_y, w, h, + (gc->gcv.function == GXset + ? (gc->depth == 1 ? 1 : WhitePixel(dpy,0)) + : (gc->depth == 1 ? 0 : BlackPixel(dpy,0))), YES); return 0; } @@ -1558,17 +2397,14 @@ XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, 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), + dpy->screen->bitmap_info, prov, NULL, /* decode[] */ NO, /* interpolate */ kCGRenderingIntentDefault); CGDataProviderRelease (prov); //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace"); - CGContextDrawImage (d->cgc, r, cgi); + CGContextDrawImage (cgc, r, cgi); CGImageRelease (cgi); } else { // (bpp == 1) @@ -1596,12 +2432,12 @@ XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, prov, NULL, /* decode[] */ NO); /* interpolate */ - push_fg_gc (d, gc, YES); + push_fg_gc (dpy, 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 (dpy, cgc, gc->gcv.background, gc->depth, NO, YES); + CGContextFillRect (cgc, r); // background color pop_gc (d, gc); free (flipped); @@ -1609,6 +2445,8 @@ XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, CGImageRelease (mask); } + invalidate_drawable_cache (d); + return 0; } @@ -1619,43 +2457,57 @@ XGetImage (Display *dpy, Drawable d, int x, int y, unsigned long plane_mask, int format) { const unsigned char *data = 0; - int depth, ibpp, ibpl, alpha_first_p; + size_t depth, ibpp, ibpl; + convert_mode_t mode; +# 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"); - if (d->type == PIXMAP) { - depth = d->pixmap.depth; - ibpp = CGBitmapContextGetBitsPerPixel (d->cgc); - ibpl = CGBitmapContextGetBytesPerRow (d->cgc); - data = CGBitmapContextGetData (d->cgc); + CGContextRef cgc = d->cgc; + +#ifndef USE_BACKBUFFER + // Because of the backbuffer, all iPhone Windows work like Pixmaps. + if (d->type == PIXMAP) +# endif + { + depth = drawable_depth (d); + mode = convert_mode_to_rgba (dpy->screen->bitmap_info); + ibpp = CGBitmapContextGetBitsPerPixel (cgc); + ibpl = CGBitmapContextGetBytesPerRow (cgc); + data = CGBitmapContextGetData (cgc); Assert (data, "CGBitmapContextGetData failed"); - } else { + +# ifndef USE_BACKBUFFER + } 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.origin.y = y; + nsfrom.origin.y = d->frame.size.height - height - 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); + mode = ([bm bitmapFormat] & NSAlphaFirstBitmapFormat) ? 3 : 0; 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); + XImage *image = XCreateImage (dpy, 0, (unsigned int) depth, + format, 0, 0, width, height, 0, 0); image->data = (char *) malloc (height * image->bytes_per_line); int obpl = image->bytes_per_line; @@ -1686,52 +2538,31 @@ XGetImage (Display *dpy, Drawable d, int x, int y, iline += ibpl; } } else { - Assert (ibpp == 24 || ibpp == 32, "weird obpp"); const unsigned char *iline = data; unsigned char *oline = (unsigned char *) image->data; + + mode = convert_mode_merge (mode, + convert_mode_invert ( + convert_mode_to_rgba (dpy->screen->bitmap_info))); + for (yy = 0; yy < height; yy++) { - const unsigned char *iline2 = iline; - unsigned char *oline2 = oline; - - 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; - } + convert_row ((uint32_t *)oline, iline, width, mode, ibpp); 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. */ @@ -1785,22 +2616,43 @@ exif_rotate (int rot, CGSize rect) void -jwxyz_draw_NSImage (Display *dpy, Drawable d, void *nsimg_arg, - XRectangle *geom_ret, int exif_rotation) +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); + } - // 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]; Bool rot_p = (exif_rotation >= 5); if (rot_p) @@ -1830,24 +2682,28 @@ jwxyz_draw_NSImage (Display *dpy, Drawable d, void *nsimg_arg, if (d->type == WINDOW) XClearWindow (dpy, d); else { - set_color (d->cgc, BlackPixel(dpy,0), 32, NO, YES); - draw_rect (dpy, d, 0, 0, 0, winr.size.width, winr.size.height, NO, YES); + draw_rect (dpy, d, 0, 0, 0, winr.size.width, winr.size.height, + drawable_depth (d) == 1 ? 0 : BlackPixel(dpy,0), YES); } CGAffineTransform trans = exif_rotate (exif_rotation, rot_p ? dst2.size : dst.size); - CGContextSaveGState (d->cgc); - CGContextConcatCTM (d->cgc, + CGContextSaveGState (cgc); + CGContextConcatCTM (cgc, CGAffineTransformMakeTranslation (dst.origin.x, dst.origin.y)); - CGContextConcatCTM (d->cgc, trans); + CGContextConcatCTM (cgc, trans); //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace"); - CGContextDrawImage (d->cgc, dst2, cgi); - CGContextRestoreGState (d->cgc); + 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; @@ -1855,9 +2711,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, @@ -1891,18 +2750,17 @@ 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, 8, /* bits per component */ width * 4, /* bpl */ dpy->colorspace, - // Without this, it returns 0... - kCGImageAlphaNoneSkipFirst - ); + dpy->screen->bitmap_info); Assert (p->cgc, "could not create CGBitmapContext"); return p; } @@ -1911,25 +2769,57 @@ XCreatePixmap (Display *dpy, Drawable d, int XFreePixmap (Display *d, Pixmap p) { - Assert (p->type == PIXMAP, "not a pixmap"); + 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 (Pixmap p) +copy_pixmap (Display *dpy, Pixmap p) { if (!p) return 0; Assert (p->type == PIXMAP, "not a pixmap"); - Pixmap p2 = (Pixmap) malloc (sizeof (*p2)); - *p2 = *p; - CGContextRetain (p2->cgc); // #### is this ok? need to copy it instead? + + Pixmap p2 = 0; + + Window root; + int x, y; + unsigned int width, height, border_width, depth; + if (XGetGeometry (dpy, p, &root, + &x, &y, &width, &height, &border_width, &depth)) { + XGCValues gcv; + gcv.function = GXcopy; + GC gc = XCreateGC (dpy, p, GCFunction, &gcv); + if (gc) { + p2 = XCreatePixmap (dpy, p, width, height, depth); + if (p2) + XCopyArea (dpy, p, p2, gc, 0, 0, width, height, 0, 0); + XFreeGC (dpy, gc); + } + } + + Assert (p2, "could not copy pixmap"); + return p2; } +char * +XGetAtomName (Display *dpy, Atom atom) +{ + if (atom == XA_FONT) + return strdup ("FONT"); + + // Note that atoms (that aren't predefined) are just char *. + return strdup ((char *) atom); +} + + /* Font metric terminology, as used by X11: "lbearing" is the distance from the logical origin to the leftmost pixel. @@ -1938,7 +2828,8 @@ copy_pixmap (Pixmap p) "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. + For characters with descenders, it is positive. For superscripts, it + is negative. "ascent" is the distance from the logical origin to the topmost pixel. It is the number of pixels above the baseline. @@ -1949,6 +2840,81 @@ copy_pixmap (Pixmap p) If "rbearing" is greater than "width", then this character overlaps the following character. If smaller, then there is trailing blank space. */ +static void +utf8_metrics (Font fid, NSString *nsstr, XCharStruct *cs) +{ + // Returns the metrics of the multi-character, single-line UTF8 string. + + NSFont *nsfont = fid->nsfont; + Drawable d = XRootWindow (fid->dpy, 0); + + CGContextRef cgc = d->cgc; + NSDictionary *attr = + [NSDictionary dictionaryWithObjectsAndKeys: + nsfont, NSFontAttributeName, + nil]; + NSAttributedString *astr = [[NSAttributedString alloc] + initWithString:nsstr + attributes:attr]; + CTLineRef ctline = CTLineCreateWithAttributedString ( + (__bridge CFAttributedStringRef) astr); + CGContextSetTextPosition (cgc, 0, 0); + CGContextSetShouldAntialias (cgc, True); // #### Guess? + + memset (cs, 0, sizeof(*cs)); + + // "CTRun represents set of consecutive glyphs sharing the same + // attributes and direction". + // + // We also get multiple runs any time font subsitution happens: + // E.g., if the current font is Verdana-Bold, a ← character + // in the NSString will actually be rendered in LucidaGrande-Bold. + // + int count = 0; + for (id runid in (NSArray *)CTLineGetGlyphRuns(ctline)) { + CTRunRef run = (CTRunRef) runid; + CFRange r = { 0, }; + CGRect bbox = CTRunGetImageBounds (run, cgc, r); + CGFloat ascent, descent, leading; + CGFloat advancement = + CTRunGetTypographicBounds (run, r, &ascent, &descent, &leading); + +# ifndef USE_IPHONE + // Only necessary for when LCD smoothing is enabled, which iOS doesn't do. + bbox.origin.x -= 2.0/3.0; + bbox.size.width += 4.0/3.0; + bbox.size.height += 1.0/2.0; +# endif + + // Create the metrics for this run: + XCharStruct cc; + cc.ascent = ceil (bbox.origin.y + bbox.size.height); + cc.descent = ceil (-bbox.origin.y); + cc.lbearing = floor (bbox.origin.x); + cc.rbearing = ceil (bbox.origin.x + bbox.size.width); + cc.width = floor (advancement + 0.5); + + // Add those metrics into the cumulative metrics: + if (count == 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 = MAX (cs->width, cs->width + cc.width); + } + + // Why no y? What about vertical text? + // XCharStruct doesn't encapsulate that but XGlyphInfo does. + + count++; + } + + CFRelease (ctline); +} + // This is XQueryFont, but for the XFontStruct embedded in 'Font' @@ -1957,12 +2923,12 @@ static void query_font (Font fid) { if (!fid || !fid->nsfont) { - NSLog(@"no NSFont in fid"); - abort(); + Assert (0, "no NSFont in fid"); + return; } if (![fid->nsfont fontName]) { - NSLog(@"broken NSFont in fid"); - abort(); + Assert(0, @"broken NSFont in fid"); + return; } int first = 32; @@ -1972,86 +2938,30 @@ query_font (Font fid) 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]); + f->ascent = ceil ([fid->nsfont ascender]); + f->descent = -floor ([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; + min->width = 32767; // set to smaller values in the loop + min->ascent = 32767; + min->descent = 32767; + min->lbearing = 32767; + min->rbearing = 32767; f->per_char = (XCharStruct *) calloc (last-first+2, sizeof (XCharStruct)); - int i; - - NSBezierPath *bpath = [NSBezierPath bezierPath]; - - for (i = first; i <= last; i++) { - unsigned char str[2]; - str[0] = i; - str[1] = 0; - NSString *nsstr = [NSString stringWithCString:(char *) str - encoding:NSISOLatin1StringEncoding]; - - /* 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]; - 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. - */ - NSPoint advancement; - NSRect bbox; - advancement.x = advancement.y = 0; - [bpath removeAllPoints]; - [bpath moveToPoint:advancement]; - [bpath appendBezierPathWithGlyph:glyph inFont:fid->nsfont]; - advancement = [bpath currentPoint]; - bbox = [bpath bounds]; - - /* Now that we know the advancement and bounding box, we can compute - the lbearing and rbearing. - */ + for (int i = first; i <= last; i++) { 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 = CEIL (bbox.origin.x); - cs->rbearing = CEIL (bbox.origin.x) + CEIL (bbox.size.width); - 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"); + char s2[2]; + s2[0] = i; + s2[1] = 0; + NSString *nsstr = [NSString stringWithCString:s2 + encoding:NSISOLatin1StringEncoding]; + utf8_metrics (fid, nsstr, cs); max->width = MAX (max->width, cs->width); max->ascent = MAX (max->ascent, cs->ascent); @@ -2065,19 +2975,16 @@ query_font (Font fid) min->lbearing = MIN (min->lbearing, cs->lbearing); min->rbearing = MIN (min->rbearing, cs->rbearing); -# undef CEIL - -#if 0 +# if 0 fprintf(stderr, " %3d %c: w=%3d lb=%3d rb=%3d as=%3d ds=%3d " - " bb=%3d x %3d @ %3d %3d adv=%3d %3d\n", + " 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, - (int) bbox.size.width, (int) bbox.size.height, - (int) bbox.origin.x, (int) bbox.origin.y, - (int) advancement.x, (int) advancement.y); -#endif + bbox.size.width, bbox.size.height, + bbox.origin.x, bbox.origin.y, + advancement.width, advancement.height); +# endif // 0 } - } @@ -2090,8 +2997,17 @@ XQueryFont (Display *dpy, Font fid) XFontStruct *f = (XFontStruct *) calloc (1, sizeof(*f)); *f = fid->metrics; + // build XFontProps + f->n_properties = 1; + f->properties = malloc (sizeof(*f->properties) * f->n_properties); + f->properties[0].name = XA_FONT; + Assert (sizeof (f->properties[0].card32) >= sizeof (char *), + "atoms probably needs a real implementation"); + // If XInternAtom is ever implemented, use it here. + f->properties[0].card32 = (char *)fid->xa_font; + // copy XCharStruct array - int size = f->max_char_or_byte2 - f->min_char_or_byte2; + int size = (f->max_char_or_byte2 - f->min_char_or_byte2) + 1; f->per_char = (XCharStruct *) calloc (size + 2, sizeof (XCharStruct)); memcpy (f->per_char, fid->metrics.per_char, size * sizeof (XCharStruct)); @@ -2116,6 +3032,7 @@ copy_font (Font fid) // copy the other pointers fid2->ps_name = strdup (fid->ps_name); + fid2->xa_font = strdup (fid->xa_font); // [fid2->nsfont retain]; fid2->metrics.fid = fid2; @@ -2123,117 +3040,222 @@ copy_font (Font fid) } +static NSArray * +font_family_members (NSString *family_name) +{ +# ifndef USE_IPHONE + return [[NSFontManager sharedFontManager] + availableMembersOfFontFamily:family_name]; +# else + return [UIFont fontNamesForFamilyName:family_name]; +# endif +} + + +static NSString * +default_font_family (NSFontTraitMask require) +{ + return require & NSFixedPitchFontMask ? @"Courier" : @"Verdana"; +} + + static NSFont * -try_font (BOOL fixed, BOOL bold, BOOL ital, BOOL serif, float size, +try_font (NSFontTraitMask traits, NSFontTraitMask mask, + NSString *family_name, 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; + NSArray *family_members = font_family_members (family_name); + if (!family_members.count) + family_members = font_family_members (default_font_family (traits)); + +# ifndef USE_IPHONE + for (unsigned k = 0; k != family_members.count; ++k) { + + NSArray *member = [family_members objectAtIndex:k]; + NSFontTraitMask font_mask = + [(NSNumber *)[member objectAtIndex:3] unsignedIntValue]; + + if ((font_mask & mask) == traits) { + + NSString *name = [member objectAtIndex:0]; + NSFont *f = [NSFont fontWithName:name size:size]; + if (!f) + break; + + /* 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); + break; + } + // NSLog(@"using \"%@\": %d", name, enc); + + // *name_ret = strdup ([name cStringUsingEncoding:NSUTF8StringEncoding]); + *name_ret = strdup (name.UTF8String); + return f; + } + } +# else // USE_IPHONE + + for (NSString *fn in family_members) { +# define MATCH(X) \ + ([fn rangeOfString:X options:NSCaseInsensitiveSearch].location \ + != NSNotFound) + + // The magic invocation for getting font names is + // [[UIFontDescriptor + // fontDescriptorWithFontAttributes:@{UIFontDescriptorNameAttribute: name}] + // symbolicTraits] + // ...but this only works on iOS 7 and later. + NSFontTraitMask font_mask = 0; + if (MATCH(@"Bold")) + font_mask |= NSBoldFontMask; + if (MATCH(@"Italic") || MATCH(@"Oblique")) + font_mask |= NSItalicFontMask; + + if ((font_mask & mask) == traits) { + + /* 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 + ) + break; + + *name_ret = strdup (fn.UTF8String); + return [UIFont fontWithName:fn size:size]; + } +# undef MATCH + } + +# endif + + return NULL; } + +/* On Cocoa and iOS, fonts may be specified as "Georgia Bold 24" instead + of XLFD strings; also they can be comma-separated strings with multiple + font names. First one that exists wins. + */ 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, char **xa_font) { if (!name) return 0; const char *spc = strrchr (name, ' '); if (!spc) return 0; - int size = 0; - if (1 != sscanf (spc, " %d ", &size)) return 0; - if (size <= 4) return 0; - 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; + NSFont *f = 0; + char *token = strdup (name); + char *name2; + + while ((name2 = strtok (token, ","))) { + token = 0; + + while (*name2 == ' ' || *name2 == '\t' || *name2 == '\n') + name2++; + + spc = strrchr (name2, ' '); + if (!spc) continue; + + int dsize = 0; + if (1 != sscanf (spc, " %d ", &dsize)) + continue; + float size = dsize; + + if (size <= 4) continue; + + size *= scale; + + name2[strlen(name2) - strlen(spc)] = 0; + + NSString *nsname = [NSString stringWithCString:name2 + encoding:NSUTF8StringEncoding]; + f = [NSFont fontWithName:nsname size:size]; + if (f) { + *name_ret = name2; + *size_ret = size; + *xa_font = strdup (name); // Maybe this should be an XLFD? + break; + } else { + NSLog(@"No native font: \"%@\" %.0f", nsname, size); + } } + + free (token); + return f; } /* Returns a random font in the given size and face. */ static NSFont * -random_font (BOOL bold, BOOL ital, float size, char **name_ret) +random_font (NSFontTraitMask traits, NSFontTraitMask mask, + float size, NSString **family_ret, char **name_ret) { - NSFontTraitMask mask = ((bold ? NSBoldFontMask : NSUnboldFontMask) | - (ital ? NSItalicFontMask : NSUnitalicFontMask)); - NSArray *fonts = [[NSFontManager sharedFontManager] - availableFontNamesWithTraits:mask]; - if (!fonts) return 0; - int n = [fonts count]; +# ifndef USE_IPHONE + // Providing Unbold or Unitalic in the mask for availableFontNamesWithTraits + // returns an empty list, at least on a system with default fonts only. + NSArray *families = [[NSFontManager sharedFontManager] + availableFontFamilies]; + if (!families) return 0; +# else + NSArray *families = [UIFont familyNames]; + + // There are many dups in the families array -- uniquify it. + { + NSArray *sorted_families = + [families sortedArrayUsingSelector:@selector(compare:)]; + NSMutableArray *new_families = + [NSMutableArray arrayWithCapacity:sorted_families.count]; + + NSString *prev_family = nil; + for (NSString *family in sorted_families) { + if ([family compare:prev_family]) + [new_families addObject:family]; + } + + families = new_families; + } +# endif // USE_IPHONE + + long n = [families 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; + NSString *family_name = [families objectAtIndex:i]; + + NSFont *result = try_font (traits, mask, family_name, size, name_ret); + if (result) { + [*family_ret release]; + *family_ret = family_name; + [*family_ret retain]; + return result; } - // NSLog(@"using \"%@\": %d", name, enc); - - *name_ret = strdup ([name cStringUsingEncoding:NSUTF8StringEncoding]); - return f; } // None of the fonts support ASCII? @@ -2241,87 +3263,169 @@ random_font (BOOL bold, BOOL ital, float size, char **name_ret) } +// Fonts need this. XDisplayHeightMM and friends should probably be consistent +// with this as well if they're ever implemented. +static const unsigned dpi = 75; + + +static const char * +xlfd_field_end (const char *s) +{ + const char *s2 = strchr(s, '-'); + if (!s2) + s2 = s + strlen(s); + return s2; +} + + +static size_t +xlfd_next (const char **s, const char **s2) +{ + if (!**s2) { + *s = *s2; + } else { + Assert (**s2 == '-', "xlfd parse error"); + *s = *s2 + 1; + *s2 = xlfd_field_end (*s); + } + + return *s2 - *s; +} + + 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, char **xa_font) { NSFont *nsfont = 0; - BOOL bold = NO; - BOOL ital = NO; - BOOL fixed = NO; - BOOL serif = NO; + NSString *family_name = nil; + NSFontTraitMask require = 0, forbid = 0; 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) - ; + + size_t L = strlen (s); # 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; +# define UNSPEC (L == 0 || L == 1 && *s == '*') + if (CMP ("6x10")) size = 8, require |= NSFixedPitchFontMask; + else if (CMP ("6x10bold")) size = 8, require |= NSFixedPitchFontMask | NSBoldFontMask; + else if (CMP ("fixed")) size = 12, require |= NSFixedPitchFontMask; + else if (CMP ("9x15")) size = 12, require |= NSFixedPitchFontMask; + else if (CMP ("9x15bold")) size = 12, require |= NSFixedPitchFontMask | NSBoldFontMask; + else if (CMP ("vga")) size = 12, require |= NSFixedPitchFontMask; + else if (CMP ("console")) size = 12, require |= NSFixedPitchFontMask; + else if (CMP ("gallant")) size = 12, require |= NSFixedPitchFontMask; + else { + + // Incorrect fields are ignored. + + if (*s == '-') + ++s; + const char *s2 = xlfd_field_end(s); + + // Foundry (ignore) + + L = xlfd_next (&s, &s2); // Family name + // This used to substitute Georgia for Times. Now it doesn't. + if (CMP ("random")) { + rand = YES; + } else if (CMP ("fixed")) { + require |= NSFixedPitchFontMask; + family_name = @"Courier"; + } else if (!UNSPEC) { + family_name = [[[NSString alloc] initWithBytes:s + length:L + encoding:NSUTF8StringEncoding] + autorelease]; } - s = s2; + L = xlfd_next (&s, &s2); // Weight name + if (CMP ("bold") || CMP ("demibold")) + require |= NSBoldFontMask; + else if (CMP ("medium") || CMP ("regular")) + forbid |= NSBoldFontMask; + + L = xlfd_next (&s, &s2); // Slant + if (CMP ("i") || CMP ("o")) + require |= NSItalicFontMask; + else if (CMP ("r")) + forbid |= NSItalicFontMask; + + xlfd_next (&s, &s2); // Set width name (ignore) + xlfd_next (&s, &s2); // Add style name (ignore) + + xlfd_next (&s, &s2); // Pixel size (ignore) + + xlfd_next (&s, &s2); // Point size + char *s3; + uintmax_t n = strtoumax(s, &s3, 10); + if (s2 == s3) + size = n / 10.0; + + xlfd_next (&s, &s2); // Resolution X (ignore) + xlfd_next (&s, &s2); // Resolution Y (ignore) + + xlfd_next (&s, &s2); // Spacing + if (CMP ("p")) + forbid |= NSFixedPitchFontMask; + else if (CMP ("m") || CMP ("c")) + require |= NSFixedPitchFontMask; + + // Don't care about average_width or charset registry. } +# undef CMP +# undef UNSPEC + + if (!family_name && !rand) + family_name = default_font_family (require); if (size < 6 || size > 1000) size = 12; - if (rand) - nsfont = random_font (bold, ital, size, &ps_name); + size *= scale; + + NSFontTraitMask mask = require | forbid; + + if (rand) { + nsfont = random_font (require, mask, size, &family_name, &ps_name); + [family_name autorelease]; + } if (!nsfont) - nsfont = try_font (fixed, bold, ital, serif, size, &ps_name); + nsfont = try_font (require, mask, family_name, 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 && (mask & NSItalicFontMask)) { + require &= ~NSItalicFontMask; + mask &= ~NSItalicFontMask; + nsfont = try_font (require, mask, family_name, size, &ps_name); } - if (!nsfont && ital) { - ital = NO; - nsfont = try_font (fixed, bold, ital, serif, size, &ps_name); + if (!nsfont && (mask & NSBoldFontMask)) { + require &= ~NSBoldFontMask; + mask &= ~NSBoldFontMask; + nsfont = try_font (require, mask, family_name, 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 && (mask & NSFixedPitchFontMask)) { + require &= ~NSFixedPitchFontMask; + mask &= ~NSFixedPitchFontMask; + nsfont = try_font (require, mask, family_name, size, &ps_name); } if (nsfont) { *name_ret = ps_name; *size_ret = size; + float actual_size = size / scale; + asprintf(xa_font, "-*-%s-%s-%c-*-*-%u-%u-%u-%u-%c-0-iso10646-1", + family_name.UTF8String, + (require & NSBoldFontMask) ? "bold" : "medium", + (require & NSItalicFontMask) ? 'o' : 'r', + (unsigned)(dpi * actual_size / 72.27 + 0.5), + (unsigned)(actual_size * 10 + 0.5), dpi, dpi, + (require & NSFixedPitchFontMask) ? 'm' : 'p'); return nsfont; } else { return 0; @@ -2334,13 +3438,44 @@ XLoadFont (Display *dpy, const char *name) { Font fid = (Font) calloc (1, sizeof(*fid)); - fid->nsfont = try_native_font (name, &fid->ps_name, &fid->size); + float scale = 1; + +# ifdef USE_IPHONE + /* Since iOS screens are physically smaller than desktop screens, scale up + the fonts to make them more readable. + + Note that X11 apps on iOS also have the backbuffer sized in points + instead of pixels, resulting in an effective X11 screen size of 768x1024 + or so, even if the display has significantly higher resolution. That is + unrelated to this hack, which is really about DPI. + */ + scale = 2; +# endif + + fid->dpy = dpy; + fid->nsfont = try_native_font (name, scale, &fid->ps_name, &fid->size, + &fid->xa_font); + + 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, &fid->ps_name, &fid->size); + fid->nsfont = try_xlfd_font (name, scale, &fid->ps_name, &fid->size, + &fid->xa_font); + + // We should never return NULL for XLFD fonts. if (!fid->nsfont) { - NSLog(@"no NSFont for \"%s\"", name); - abort(); + 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); @@ -2354,14 +3489,17 @@ 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); + 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 @@ -2369,6 +3507,7 @@ XUnloadFont (Display *dpy, Font fid) // They're probably not very big... // // [fid->nsfont release]; + // CFRelease (fid->nsfont); free (fid); return 0; @@ -2385,8 +3524,10 @@ XFreeFontInfo (char **names, XFontStruct *info, int n) } if (info) { for (i = 0; i < n; i++) - if (info[i].per_char) + if (info[i].per_char) { free (info[i].per_char); + free (info[i].properties); + } free (info); } return 0; @@ -2409,31 +3550,136 @@ XSetFont (Display *dpy, GC gc, Font fid) 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; } + +XFontSet +XCreateFontSet (Display *dpy, char *name, + char ***missing_charset_list_return, + int *missing_charset_count_return, + char **def_string_return) +{ + char *name2 = strdup (name); + char *s = strchr (name, ","); + if (s) *s = 0; + XFontSet set = 0; + XFontStruct *f = XLoadQueryFont (dpy, name2); + if (f) + { + set = (XFontSet) calloc (1, sizeof(*set)); + set->font = f; + } + free (name2); + if (missing_charset_list_return) *missing_charset_list_return = 0; + if (missing_charset_count_return) *missing_charset_count_return = 0; + if (def_string_return) *def_string_return = 0; + return set; +} + + +void +XFreeFontSet (Display *dpy, XFontSet set) +{ + XFreeFont (dpy, set->font); + free (set); +} + + +const char * +jwxyz_nativeFontName (Font f, float *size) +{ + if (size) *size = f->size; + return f->ps_name; +} + + +void +XFreeStringList (char **list) +{ + int i; + if (!list) return; + for (i = 0; list[i]; i++) + XFree (list[i]); + XFree (list); +} + + +// Returns the verbose Unicode name of this character, like "agrave" or +// "daggerdouble". Used by fontglide debugMetrics. +// +char * +jwxyz_unicode_character_name (Font fid, unsigned long uc) +{ + char *ret = 0; + CTFontRef ctfont = + CTFontCreateWithName ((CFStringRef) [fid->nsfont fontName], + [fid->nsfont pointSize], + NULL); + Assert (ctfont, @"no CTFontRef for UIFont"); + + CGGlyph cgglyph; + if (CTFontGetGlyphsForCharacters (ctfont, (UniChar *) &uc, &cgglyph, 1)) { + NSString *name = (NSString *) + CGFontCopyGlyphNameForGlyph (CTFontCopyGraphicsFont (ctfont, 0), + cgglyph); + ret = (name ? strdup ([name UTF8String]) : 0); + } + + CFRelease (ctfont); + return ret; +} + + +// Given a UTF8 string, return an NSString. Bogus UTF8 characters are ignored. +// We have to do this because stringWithCString returns NULL if there are +// any invalid characters at all. +// +static NSString * +sanitize_utf8 (const char *in, int in_len, Bool *latin1_pP) +{ + int out_len = in_len * 4; // length of string might increase + char *s2 = (char *) malloc (out_len); + char *out = s2; + const char *in_end = in + in_len; + const char *out_end = out + out_len; + Bool latin1_p = True; + + while (in < in_end) + { + unsigned long uc; + long L1 = utf8_decode ((const unsigned char *) in, in_end - in, &uc); + long L2 = utf8_encode (uc, out, out_end - out); + in += L1; + out += L2; + if (uc > 255) latin1_p = False; + } + *out = 0; + NSString *nsstr = + [NSString stringWithCString:s2 encoding:NSUTF8StringEncoding]; + free (s2); + if (latin1_pP) *latin1_pP = latin1_p; + return (nsstr ? nsstr : @""); +} + + 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; - } - } + // Unfortunately, adding XCharStructs together to get the extents for a + // string doesn't work: Cocoa uses non-integral character advancements, but + // XCharStruct.width is an integer. Plus that doesn't take into account + // kerning pairs, alternate glyphs, and fun stuff like the word "Zapfino" in + // Zapfino. + + NSString *nsstr = [[[NSString alloc] initWithBytes:s + length:length + encoding:NSISOLatin1StringEncoding] + autorelease]; + utf8_metrics (f->fid, nsstr, cs); *dir_ret = 0; *ascent_ret = f->ascent; *descent_ret = f->descent; @@ -2450,111 +3696,222 @@ XTextWidth (XFontStruct *f, const char *s, int length) } -static void -set_font (CGContextRef cgc, GC gc) +int +XTextExtents16 (XFontStruct *f, const XChar2b *s, int length, + int *dir_ret, int *ascent_ret, int *descent_ret, + XCharStruct *cs) { - Font font = gc->gcv.font; - if (! font) { - font = XLoadFont (0, 0); - gc->gcv.font = font; - [gc->gcv.font->nsfont retain]; + Bool latin1_p = True; + int i, utf8_len = 0; + char *utf8 = XChar2b_to_utf8 (s, &utf8_len); // already sanitized + + for (i = 0; i < length; i++) + if (s[i].byte1 > 0) { + latin1_p = False; + break; + } + + { + NSString *nsstr = [NSString stringWithCString:utf8 + encoding:NSUTF8StringEncoding]; + utf8_metrics (f->fid, nsstr, cs); } - CGContextSelectFont (cgc, font->ps_name, font->size, kCGEncodingMacRoman); - CGContextSetTextMatrix (cgc, CGAffineTransformIdentity); + + *dir_ret = 0; + *ascent_ret = f->ascent; + *descent_ret = f->descent; + free (utf8); + return 0; } -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); - } +/* "Returns the distance in pixels in the primary draw direction from + the drawing origin to the origin of the next character to be drawn." - CGRect wr = d->frame; + "overall_ink_return is set to the bbox of the string's character ink." -# 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)! - */ + "The overall_ink_return for a nondescending, horizontally drawn Latin + character is conventionally entirely above the baseline; that is, + overall_ink_return.height <= -overall_ink_return.y." - push_fg_gc (d, gc, YES); - set_font (d->cgc, gc); + [So this means that y is the top of the ink, and height grows down: + For above-the-baseline characters, y is negative.] - CGContextSetTextDrawingMode (d->cgc, kCGTextFill); - if (gc->gcv.antialias_p) - CGContextSetShouldAntialias (d->cgc, YES); - CGContextShowTextAtPoint (d->cgc, - wr.origin.x + x, - wr.origin.y + wr.size.height - y, - str, len); - pop_gc (d, gc); + "The overall_ink_return for a nonkerned character is entirely at, and to + the right of, the origin; that is, overall_ink_return.x >= 0." + + [So this means that x is the left of the ink, and width grows right. + For left-of-the-origin characters, x is negative.] + + "A character consisting of a single pixel at the origin would set + overall_ink_return fields y = 0, x = 0, width = 1, and height = 1." + */ +int +Xutf8TextExtents (XFontSet set, const char *str, int len, + XRectangle *overall_ink_return, + XRectangle *overall_logical_return) +{ + Bool latin1_p; + NSString *nsstr = sanitize_utf8 (str, len, &latin1_p); + XCharStruct cs; + + utf8_metrics (set->font->fid, nsstr, &cs); -#else /* !0 */ + /* "The overall_logical_return is the bounding box that provides minimum + spacing to other graphical features for the string. Other graphical + features, for example, a border surrounding the text, should not + intersect this rectangle." - /* The Cocoa way... + So I think that means they're the same? Or maybe "ink" is the bounding + box, and "logical" is the advancement? But then why is the return value + the advancement? */ + if (overall_ink_return) + XCharStruct_to_XmbRectangle (cs, *overall_ink_return); + if (overall_logical_return) + XCharStruct_to_XmbRectangle (cs, *overall_logical_return); + + return cs.width; +} + + +static int +draw_string (Display *dpy, Drawable d, GC gc, int x, int y, + NSString *nsstr) +{ + if (! nsstr) return 1; + + CGRect wr = d->frame; + CGContextRef cgc = d->cgc; 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]; + float rgba[4]; + query_color_float (dpy, argb, rgba); + NSColor *fg = [NSColor colorWithDeviceRed:rgba[0] + green:rgba[1] + blue:rgba[2] + alpha:rgba[3]]; + + if (!gc->gcv.font) { + Assert (0, "no font"); + return 1; + } + + /* This crashes on iOS 5.1 because NSForegroundColorAttributeName, + NSFontAttributeName, and NSAttributedString are only present on iOS 6 + and later. We could resurrect the Quartz code from v5.29 and do a + runtime conditional on that, but that would be a pain in the ass. + Probably time to just make iOS 6 a requirement. + */ + NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys: gc->gcv.font->nsfont, NSFontAttributeName, fg, NSForegroundColorAttributeName, nil]; + + // Don't understand why we have to do both set_color and + // NSForegroundColorAttributeName, but we do. + // + set_color (dpy, cgc, argb, 32, NO, YES); + + NSAttributedString *astr = [[NSAttributedString alloc] + initWithString:nsstr + attributes:attr]; + CTLineRef dl = CTLineCreateWithAttributedString ( + (__bridge CFAttributedStringRef) astr); + + // Not sure why this is necessary, but xoff is positive when the first + // character on the line has a negative lbearing. Without this, the + // string is rendered with the first ink at 0 instead of at lbearing. + // I have not seen xoff be negative, so I'm not sure if that can happen. + // + // Test case: "Combining Double Tilde" U+0360 (\315\240) followed by + // a letter. + // + CGFloat xoff = CTLineGetOffsetForStringIndex (dl, 0, NULL); + Assert (xoff >= 0, "unexpected CTLineOffset"); + x -= xoff; + + CGContextSetTextPosition (cgc, + wr.origin.x + x, + wr.origin.y + wr.size.height - y); + CGContextSetShouldAntialias (cgc, gc->gcv.antialias_p); + + CTLineDraw (dl, cgc); + CFRelease (dl); + + invalidate_drawable_cache (d); + return 0; +} + + +int +XDrawString (Display *dpy, Drawable d, GC gc, int x, int y, + const char *str, int len) +{ char *s2 = (char *) malloc (len + 1); strncpy (s2, str, len); s2[len] = 0; NSString *nsstr = [NSString stringWithCString:s2 - encoding:NSISOLatin1StringEncoding]; + encoding:NSISOLatin1StringEncoding]; + int ret = draw_string (dpy, d, gc, x, y, nsstr); 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]; + return ret; +} -#endif /* 0 */ - return 0; +int +XDrawString16 (Display *dpy, Drawable d, GC gc, int x, int y, + const XChar2b *str, int len) +{ + char *s2 = XChar2b_to_utf8 (str, 0); // already sanitized + NSString *nsstr = + [NSString stringWithCString:s2 encoding:NSUTF8StringEncoding]; + int ret = draw_string (dpy, d, gc, x, y, nsstr); + free (s2); + return ret; } -int -XDrawString (Display *dpy, Drawable d, GC gc, int x, int y, - const char *str, int len) +void +Xutf8DrawString (Display *dpy, Drawable d, XFontSet set, GC gc, + int x, int y, const char *str, int len) { - return draw_string (dpy, d, gc, x, y, str, len, NO); + char *s2 = (char *) malloc (len + 1); + strncpy (s2, str, len); + s2[len] = 0; + NSString *nsstr = sanitize_utf8 (str, len, 0); + draw_string (dpy, d, gc, x, y, nsstr); + free (s2); } + 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 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), + gc->gcv.background, YES); + return XDrawString (dpy, d, gc, x, y, str, len); } int XSetForeground (Display *dpy, GC gc, unsigned long fg) { - validate_pixel (fg, gc->depth, gc->gcv.alpha_allowed_p); + validate_pixel (dpy, fg, gc->depth, gc->gcv.alpha_allowed_p); gc->gcv.foreground = fg; return 0; } @@ -2563,7 +3920,7 @@ XSetForeground (Display *dpy, GC gc, unsigned long fg) int XSetBackground (Display *dpy, GC gc, unsigned long bg) { - validate_pixel (bg, gc->depth, gc->gcv.alpha_allowed_p); + validate_pixel (dpy, bg, gc->depth, gc->gcv.alpha_allowed_p); gc->gcv.background = bg; return 0; } @@ -2625,9 +3982,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; @@ -2643,29 +4001,37 @@ XSetClipOrigin (Display *dpy, GC gc, int x, int y) } -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) +static void +get_pos (Window w, NSPoint *vpos, NSPoint *p) { - Assert (w->type == WINDOW, "not a window"); +# ifdef USE_IPHONE + + vpos->x = 0; + vpos->y = 0; + + if (p) { + p->x = w->window.last_mouse_x; + p->y = w->window.last_mouse_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]]; + 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; + vpos->x += wpos.x; + vpos->y += wpos.y; // get top left of view on screen, from bottom left - vpos.y += w->frame.size.height; + vpos->y += w->frame.size.height; // get top left of view on screen, from top left NSArray *screens = [NSScreen screens]; @@ -2673,25 +4039,39 @@ XQueryPointer (Display *dpy, Window w, Window *root_ret, Window *child_ret, ? [screens objectAtIndex:0] : [NSScreen mainScreen]); NSRect srect = [screen frame]; - vpos.y = 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; + vpos->y = srect.size.height - vpos->y; + + if (p) { + // get the mouse position on window, from bottom left + NSEvent *e = [NSApp currentEvent]; + *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; + } + +# endif // !USE_IPHONE +} + +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"); + + NSPoint vpos, p; + get_pos (w, &vpos, &p); 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); - - 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; @@ -2703,38 +4083,15 @@ XTranslateCoordinates (Display *dpy, Window w, Window dest_w, int *dest_x_ret, int *dest_y_ret, Window *child_ret) { - Assert (w->type == WINDOW, "not a window"); - 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]]; + Assert (w && w->type == WINDOW, "not a window"); + + NSPoint vpos, p; + get_pos (w, &vpos, NULL); - // 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]); - NSRect srect = [screen frame]; - vpos.y = 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; @@ -2758,11 +4115,25 @@ XLookupString (XKeyEvent *e, char *buf, int size, KeySym *k_ret, XComposeStatus *xc) { KeySym ks = XKeycodeToKeysym (0, e->keycode, 0); - char c = (char) ks; // could be smarter about modifiers here... + 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 0; + return (size > 0 ? 1 : 0); } @@ -2796,7 +4167,7 @@ visual_depth (Screen *s, Visual *v) int visual_cells (Screen *s, Visual *v) { - return 0xFFFFFF; + return (int)(v->red_mask | v->green_mask | v->blue_mask); } int @@ -2805,6 +4176,25 @@ visual_class (Screen *s, Visual *v) return TrueColor; } +int +get_bits_per_pixel (Display *dpy, int depth) +{ + Assert (depth == 32 || depth == 1, "unexpected depth"); + return depth; +} + +int +screen_number (Screen *screen) +{ + Display *dpy = DisplayOfScreen (screen); + int i; + for (i = 0; i < ScreenCount (dpy); i++) + if (ScreenOfDisplay (dpy, i) == screen) + return i; + abort (); + return 0; +} + // declared in utils/grabclient.h Bool use_subwindow_mode_p (Screen *screen, Window window)