X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=OSX%2Fjwxyz.m;h=720569573fd5385dff404fad7ae5287438c8f882;hb=7edd66e6bd3209013ee059819747b10b5835635b;hp=de78750a2080ab87ba1abbde4fe1eb9ca3f6ad67;hpb=b81f521c5ad7022ac12db18ca8fcdd9fb063831e;p=xscreensaver diff --git a/OSX/jwxyz.m b/OSX/jwxyz.m index de78750a..72056957 100644 --- a/OSX/jwxyz.m +++ b/OSX/jwxyz.m @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 1991-2012 Jamie Zawinski +/* xscreensaver, Copyright (c) 1991-2014 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,12 +18,12 @@ #import #import +#import #ifdef USE_IPHONE # import # import # import -# import # define NSView UIView # define NSRect CGRect # define NSPoint CGPoint @@ -36,17 +36,20 @@ # define NSWindow UIWindow # define NSMakeSize CGSizeMake # define NSBezierPath UIBezierPath +# define colorWithDeviceRed colorWithRed #else # import #endif +#import +#import + #import "jwxyz.h" #import "jwxyz-timers.h" #import "yarandom.h" +#import "utf8wc.h" -#ifdef USE_IPHONE # define USE_BACKBUFFER /* must be in sync with XScreenSaverView.h */ -#endif #undef Assert #define Assert(C,S) do { if (!(C)) jwxyz_abort ("%s",(S)); } while(0) @@ -78,6 +81,7 @@ struct jwxyz_Drawable { struct jwxyz_Display { Window main_window; Screen *screen; + int screen_count; struct jwxyz_sources_data *timers_data; # ifndef USE_IPHONE @@ -94,6 +98,7 @@ struct jwxyz_Display { struct jwxyz_Screen { Display *dpy; Visual *visual; + int screen_number; }; struct jwxyz_GC { @@ -112,6 +117,10 @@ struct jwxyz_Font { XFontStruct metrics; }; +struct jwxyz_XFontSet { + Font font; +}; + /* Instead of calling abort(), throw a real exception, so that XScreenSaverView can catch it and display a dialog. @@ -138,6 +147,26 @@ jwxyz_abort (const char *fmt, ...) } +/* 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, void *cgc_arg) { @@ -150,6 +179,24 @@ jwxyz_make_display (void *nsview_arg, void *cgc_arg) 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 + Visual *v = (Visual *) calloc (1, sizeof(Visual)); v->class = TrueColor; v->red_mask = 0x00FF0000; @@ -185,11 +232,26 @@ jwxyz_make_display (void *nsview_arg, void *cgc_arg) 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); } @@ -245,11 +307,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 -#if 0 +# ifndef USE_BACKBUFFER + // Funny thing: As of OS X 10.9, if USE_BACKBUFFER is turned off, + // then this one's faster. + { // Figure out this screen's colorspace, and use that for every CGImage. // @@ -259,12 +329,12 @@ 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); } @@ -281,6 +351,12 @@ jwxyz_mouse_moved (Display *dpy, Window w, int x, int y) #endif // USE_IPHONE +void +jwxyz_flush_context (Display *dpy) +{ + // This is only used when USE_BACKBUFFER is off. + CGContextFlush(dpy->main_window->cgc); // CGContextSynchronize is another possibility. +} jwxyz_sources_data * display_sources_data (Display *dpy) @@ -322,7 +398,13 @@ XDisplayNumberOfScreen (Screen *s) int XScreenNumberOfScreen (Screen *s) { - return 0; + return s->screen_number; +} + +int +jwxyz_ScreenCount (Display *dpy) +{ + return dpy->screen_count; } int @@ -510,36 +592,36 @@ XDrawPoints (Display *dpy, Drawable d, GC gc, Assert (data, "no bitmap data in Drawable"); - unsigned int argb = gc->gcv.foreground; + unsigned long argb = gc->gcv.foreground; validate_pixel (argb, gc->depth, gc->gcv.alpha_allowed_p); if (gc->depth == 1) argb = (gc->gcv.foreground ? WhitePixel(0,0) : BlackPixel(0,0)); CGFloat x0 = wr.origin.x; - CGFloat y0 = wr.origin.y + wr.size.height; + 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; + y += points->y; - if (0 <= x && x < w && 0 <= y && y < h) { + if (x >= 0 && x < w && y >= 0 && y < h) { unsigned int *p = (unsigned int *) ((char *) data + (size_t) y * bpr + (size_t) x * 4); - *p = argb; + *p = (unsigned int) argb; } } } else { for (i = 0; i < count; i++, points++) { CGFloat x = x0 + points->x; - CGFloat y = y0 - points->y; + CGFloat y = y0 + points->y; - if (0 <= x && x < w && 0 <= y && y < h) { + if (x >= 0 && x < w && y >= 0 && y < h) { unsigned int *p = (unsigned int *) ((char *) data + (size_t) y * bpr + (size_t) x * 4); - *p = argb; + *p = (unsigned int) argb; } } } @@ -635,6 +717,45 @@ static void draw_rect (Display *, Drawable, GC, int x, int y, unsigned int width, unsigned int height, BOOL foreground_p, BOOL fill_p); +static Bool +bitmap_context_p (Drawable d) +{ +# ifdef USE_BACKBUFFER + return True; +# else + // Because of the backbuffer, all iPhone Windows work like Pixmaps. + return d->type == PIXMAP; +# endif +} + +static void +fill_rect_memset (void *dst, size_t dst_pitch, uint32_t fill_data, + size_t fill_width, size_t fill_height) +{ + Assert(sizeof(wchar_t) == 4, "somebody changed the ABI"); + while (fill_height) { + // Would be nice if Apple used SSE/NEON in wmemset. Maybe someday. + wmemset (dst, fill_data, fill_width); + --fill_height; + dst = (char *) dst + dst_pitch; + } +} + +static void * +seek_xy (void *dst, size_t dst_pitch, unsigned x, unsigned y) +{ + return (char *)dst + dst_pitch * y + x * 4; +} + +static unsigned int +drawable_depth (Drawable d) +{ + return (d->type == WINDOW + ? visual_depth (NULL, NULL) + : d->pixmap.depth); +} + + int XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc, int src_x, int src_y, @@ -693,8 +814,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; \ @@ -715,12 +834,31 @@ 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 (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; @@ -728,11 +866,116 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc, BOOL free_cgi_p = NO; -#ifndef USE_BACKBUFFER - // Because of the backbuffer, all iPhone Windows work like Pixmaps. - if (src->type == PIXMAP) -# endif - { + /* If we're copying from a bitmap to a bitmap, and there's nothing funny + going on with clipping masks or depths or anything, optimize it by + just doing a memcpy instead of going through a CGI. + */ + if (bitmap_context_p (src)) { + + if (bitmap_context_p (dst) && + gc->gcv.function == GXcopy && + !gc->gcv.clip_mask && + drawable_depth (src) == drawable_depth (dst)) { + + Assert(!(int)src_frame.origin.x && + !(int)src_frame.origin.y && + !(int)dst_frame.origin.x && + !(int)dst_frame.origin.y, + "unexpected non-zero origin"); + + char *src_data = CGBitmapContextGetData(src->cgc); + char *dst_data = CGBitmapContextGetData(dst->cgc); + size_t src_pitch = CGBitmapContextGetBytesPerRow(src->cgc); + size_t dst_pitch = CGBitmapContextGetBytesPerRow(dst->cgc); + + // Int to float and back again. It's not very safe, but it seems to work. + int src_x0 = src_rect.origin.x; + int dst_x0 = dst_rect.origin.x; + + // Flip the Y-axis a second time. + int src_y0 = (src_frame.origin.y + src_frame.size.height - + src_rect.size.height - src_rect.origin.y); + int dst_y0 = (dst_frame.origin.y + dst_frame.size.height - + dst_rect.size.height - dst_rect.origin.y); + + unsigned width0 = (int) src_rect.size.width; + unsigned height0 = (int) src_rect.size.height; + + Assert((int)src_rect.size.width == (int)dst_rect.size.width || + (int)src_rect.size.height == (int)dst_rect.size.height, + "size mismatch"); + { + char *src_data0 = seek_xy(src_data, src_pitch, src_x0, src_y0); + char *dst_data0 = seek_xy(dst_data, dst_pitch, dst_x0, dst_y0); + size_t src_pitch0 = src_pitch; + size_t dst_pitch0 = dst_pitch; + size_t bytes = width0 * 4; + + if (src == dst && dst_y0 > src_y0) { + // Copy upwards if the areas might overlap. + src_data0 += src_pitch0 * (height0 - 1); + dst_data0 += dst_pitch0 * (height0 - 1); + src_pitch0 = -src_pitch0; + dst_pitch0 = -dst_pitch0; + } + + size_t lines0 = height0; + while (lines0) { + // memcpy is an alias for memmove on OS X. + memmove(dst_data0, src_data0, bytes); + src_data0 += src_pitch0; + dst_data0 += dst_pitch0; + --lines0; + } + } + + if (clipped) { + int orig_dst_x = orig_dst_rect.origin.x; + int orig_dst_y = (dst_frame.origin.y + dst_frame.size.height - + orig_dst_rect.origin.y - orig_dst_rect.size.height); + int orig_width = orig_dst_rect.size.width; + int orig_height = orig_dst_rect.size.height; + + Assert (orig_dst_x >= 0 && + orig_dst_x + orig_width <= (int) dst_frame.size.width && + orig_dst_y >= 0 && + orig_dst_y + orig_height <= (int) dst_frame.size.height, + "wrong dimensions"); + + if (orig_dst_y < dst_y0) { + fill_rect_memset (seek_xy (dst_data, dst_pitch, + orig_dst_x, orig_dst_y), dst_pitch, + (uint32_t) gc->gcv.background, orig_width, + dst_y0 - orig_dst_y); + } + + if (orig_dst_y + orig_height > dst_y0 + height0) { + fill_rect_memset (seek_xy (dst_data, dst_pitch, orig_dst_x, + dst_y0 + height0), + dst_pitch, + (uint32_t) gc->gcv.background, orig_width, + orig_dst_y + orig_height - dst_y0 - height0); + } + + if (orig_dst_x < dst_x0) { + fill_rect_memset (seek_xy (dst_data, dst_pitch, orig_dst_x, dst_y0), + dst_pitch, (uint32_t) gc->gcv.background, + dst_x0 - orig_dst_x, height0); + } + + if (dst_x0 + width0 < orig_dst_x + orig_width) { + fill_rect_memset (seek_xy (dst_data, dst_pitch, dst_x0 + width0, + dst_y0), + dst_pitch, (uint32_t) gc->gcv.background, + orig_dst_x + orig_width - dst_x0 - width0, + height0); + } + } + + invalidate_drawable_cache (dst); + return 0; + } + // If we are copying from a Pixmap to a Pixmap or Window, we must first // copy the bits to an intermediary CGImage object, then copy that to the @@ -1564,7 +1807,7 @@ 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; @@ -1677,7 +1920,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++]; } @@ -1827,7 +2070,8 @@ 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; + enum { RGBA, ARGB, BGRA } src_format; // As bytes. # ifndef USE_BACKBUFFER NSBitmapImageRep *bm = 0; # endif @@ -1847,9 +2091,8 @@ XGetImage (Display *dpy, Drawable d, int x, int y, depth = (d->type == PIXMAP ? d->pixmap.depth : 32); - // If it's a pixmap, we created it with kCGImageAlphaNoneSkipFirst. - // If it's an iPhone window, it's the other way around. - alpha_first_p = (d->type == PIXMAP); + // We create pixmaps and iPhone backbuffers with kCGImageAlphaNoneSkipFirst. + src_format = BGRA; // #### Should this be ARGB on PPC? ibpp = CGBitmapContextGetBitsPerPixel (cgc); ibpl = CGBitmapContextGetBytesPerRow (cgc); data = CGBitmapContextGetData (cgc); @@ -1861,12 +2104,13 @@ XGetImage (Display *dpy, Drawable d, int x, int y, // get the bits (desired sub-rectangle) out of the NSView 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 = [[NSBitmapImageRep alloc] initWithFocusedViewRect:nsfrom]; depth = 32; - alpha_first_p = ([bm bitmapFormat] & NSAlphaFirstBitmapFormat); + src_format = ([bm bitmapFormat] & NSAlphaFirstBitmapFormat) ? ARGB : RGBA; ibpp = [bm bitsPerPixel]; ibpl = [bm bytesPerRow]; data = [bm bitmapData]; @@ -1878,8 +2122,8 @@ XGetImage (Display *dpy, Drawable d, int x, int y, 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; @@ -1918,7 +2162,8 @@ XGetImage (Display *dpy, Drawable d, int x, int y, const unsigned char *iline2 = iline; unsigned char *oline2 = oline; - if (alpha_first_p) // ARGB + switch (src_format) { + case ARGB: for (xx = 0; xx < width; xx++) { unsigned char a = (ibpp == 32 ? (*iline2++) : 0xFF); unsigned char r = *iline2++; @@ -1931,7 +2176,8 @@ XGetImage (Display *dpy, Drawable d, int x, int y, *((uint32_t *) oline2) = pixel; oline2 += 4; } - else // RGBA + break; + case RGBA: for (xx = 0; xx < width; xx++) { unsigned char r = *iline2++; unsigned char g = *iline2++; @@ -1944,6 +2190,25 @@ XGetImage (Display *dpy, Drawable d, int x, int y, *((uint32_t *) oline2) = pixel; oline2 += 4; } + break; + case BGRA: + for (xx = 0; xx < width; xx++) { + unsigned char b = *iline2++; + unsigned char g = *iline2++; + unsigned char r = *iline2++; + unsigned char a = (ibpp == 32 ? (*iline2++) : 0xFF); + uint32_t pixel = ((a << 24) | + (r << 16) | + (g << 8) | + (b << 0)); + *((uint32_t *) oline2) = pixel; + oline2 += 4; + } + break; + default: + abort(); + break; + } oline += obpl; iline += ibpl; @@ -2157,7 +2422,8 @@ XCreatePixmap (Display *dpy, Drawable d, width * 4, /* bpl */ dpy->colorspace, // Without this, it returns 0... - kCGImageAlphaNoneSkipFirst + (kCGImageAlphaNoneSkipFirst | + kCGBitmapByteOrder32Host) ); Assert (p->cgc, "could not create CGBitmapContext"); return p; @@ -2199,7 +2465,8 @@ copy_pixmap (Display *dpy, Pixmap p) width * 4, /* bpl */ dpy->colorspace, // Without this, it returns 0... - kCGImageAlphaNoneSkipFirst + (kCGImageAlphaNoneSkipFirst | + kCGBitmapByteOrder32Host) ); Assert (p2->cgc, "could not create CGBitmapContext"); @@ -2215,7 +2482,8 @@ copy_pixmap (Display *dpy, 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. @@ -2228,6 +2496,46 @@ copy_pixmap (Display *dpy, Pixmap p) */ +static void +glyph_metrics (CTFontRef ctfont, CGGlyph cgglyph, XCharStruct *cs) +{ + CGRect bbox = CTFontGetBoundingRectsForGlyphs (ctfont, + kCTFontDefaultOrientation, + &cgglyph, NULL, 1); + CGSize advancement; + CTFontGetAdvancesForGlyphs (ctfont, kCTFontDefaultOrientation, + &cgglyph, &advancement, 1); + +# 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 + +//#define CEIL(F) ((F) < 0 ? floor(F) : ceil(F)) +//#define FLOOR(F) ((F) < 0 ? ceil(F) : floor(F)) +#define CEIL(F) ceil(F) +#define FLOOR(F) floor(F) + + /* Now that we know the advancement and bounding box, we can compute + the lbearing and rbearing. + */ +//cs->ascent = CEIL (bbox.origin.y) + CEIL (bbox.size.height); + cs->ascent = CEIL (bbox.origin.y + bbox.size.height); + cs->descent = CEIL(-bbox.origin.y); + cs->lbearing = FLOOR (bbox.origin.x); +//cs->rbearing = CEIL (bbox.origin.x) + CEIL (bbox.size.width); + cs->rbearing = CEIL (bbox.origin.x + bbox.size.width); + cs->width = FLOOR (advancement.width + 0.5); + + // Assert (cs->rbearing - cs->lbearing == CEIL(bbox.size.width), + // "bbox w wrong"); + // Assert (cs->ascent + cs->descent == CEIL(bbox.size.height), + // "bbox h wrong"); +} + + // This is XQueryFont, but for the XFontStruct embedded in 'Font' // static void @@ -2249,14 +2557,12 @@ 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->descent = -FLOOR ([fid->nsfont descender]); min->width = 255; // set to smaller values in the loop min->ascent = 255; @@ -2265,74 +2571,16 @@ query_font (Font fid) min->rbearing = 255; f->per_char = (XCharStruct *) calloc (last-first+2, sizeof (XCharStruct)); - int i; + UniChar i; -# ifndef USE_IPHONE - NSBezierPath *bpath = [NSBezierPath bezierPath]; -# else // USE_IPHONE CTFontRef ctfont = CTFontCreateWithName ((CFStringRef) [fid->nsfont fontName], [fid->nsfont pointSize], NULL); Assert (ctfont, @"no CTFontRef for UIFont"); -# endif // USE_IPHONE for (i = first; i <= last; i++) { - unsigned char str[2]; - str[0] = i; - str[1] = 0; - - NSString *nsstr = [NSString stringWithCString:(char *) str - encoding:NSISOLatin1StringEncoding]; - NSPoint advancement = { 0, }; - NSRect bbox = {{ 0, }, }; - -# ifndef USE_IPHONE - - /* I can't believe we have to go through this bullshit just to - convert a 'char' to an NSGlyph!! - - You might think that we could do - NSGlyph glyph = [fid->nsfont glyphWithName:nsstr]; - but that doesn't work; my guess is that glyphWithName expects - full Unicrud names like "LATIN CAPITAL LETTER A WITH ACUTE". - */ - NSGlyph glyph; - { - NSTextStorage *ts = [[NSTextStorage alloc] initWithString:nsstr]; - [ts setFont:fid->nsfont]; - NSLayoutManager *lm = [[NSLayoutManager alloc] init]; - - /* Without this, the layout manager ends up on a queue somewhere and is - referenced again after we return to the command loop. Since we don't - use this layout manager again, by that time it may have been garbage - collected, and we crash. Setting this seems to cause `lm' to no - longer be referenced once we exit this block. */ - [lm setBackgroundLayoutEnabled:NO]; - - NSTextContainer *tc = [[NSTextContainer alloc] init]; - [lm addTextContainer:tc]; - [tc release]; // lm retains tc - [ts addLayoutManager:lm]; - [lm release]; // ts retains lm - glyph = [lm glyphAtIndex:0]; - [ts release]; - } - - /* Compute the bounding box and advancement by converting the glyph - to a bezier path. There appears to be *no other way* to find out - the bounding box of a character: [NSFont boundingRectForGlyph] and - [NSString sizeWithAttributes] both return an advancement-sized - rectangle, not a rectangle completely enclosing the glyph's ink. - */ - advancement.x = advancement.y = 0; - [bpath removeAllPoints]; - [bpath moveToPoint:advancement]; - [bpath appendBezierPathWithGlyph:glyph inFont:fid->nsfont]; - advancement = [bpath currentPoint]; - bbox = [bpath bounds]; - -# else // USE_IPHONE + XCharStruct *cs = &f->per_char[i-first]; /* There is no way to get "lbearing", "rbearing" or "descent" out of NSFont. 'sizeWithFont' gives us "width" and "height" only. @@ -2343,43 +2591,28 @@ query_font (Font fid) the CoreText library, but there's no non-CoreText way to turn a unichar into a CGGlyph. */ - UniChar uchar = [nsstr characterAtIndex: 0]; CGGlyph cgglyph = 0; - if (CTFontGetGlyphsForCharacters (ctfont, &uchar, &cgglyph, 1)) - { - bbox = CTFontGetBoundingRectsForGlyphs (ctfont, - kCTFontDefaultOrientation, - &cgglyph, NULL, 1); - CGSize adv = { 0, }; - CTFontGetAdvancesForGlyphs (ctfont, kCTFontDefaultOrientation, - &cgglyph, &adv, 1); - advancement.x = adv.width; - advancement.y = adv.height; - - // Seems to be clipping by a pixel or two. Add a margin to be safe. - bbox.origin.x -= 2; - bbox.origin.y -= 2; - bbox.size.width += 4; - bbox.size.height += 4; - } -# endif // USE_IPHONE - - /* Now that we know the advancement and bounding box, we can compute - the lbearing and rbearing. - */ - 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"); + if (CTFontGetGlyphsForCharacters (ctfont, &i, &cgglyph, 1)) + glyph_metrics (ctfont, cgglyph, cs); + else + // This is normal, since Latin1 does not encode 0-31 or 127-159. + memset (cs, 0, sizeof(*cs)); + +# if 0 + if (i == 224) { // Latin1 == "agrave", MacRoman == "daggerdouble". + NSString *glyph_name = (NSString *) + CGFontCopyGlyphNameForGlyph (CTFontCopyGraphicsFont (ctfont, 0), + cgglyph); + Assert ([glyph_name isEqualToString:@"agrave"], @"wrong encoding"); + } + if (i == 250) { // Latin1 == "uacute", MacRoman == "dotaccent". + NSString *glyph_name = (NSString *) + CGFontCopyGlyphNameForGlyph (CTFontCopyGraphicsFont (ctfont, 0), + cgglyph); + Assert ([glyph_name isEqualToString:@"uacute"], @"wrong encoding"); + } +# endif // 0 max->width = MAX (max->width, cs->width); max->ascent = MAX (max->ascent, cs->ascent); @@ -2394,21 +2627,20 @@ query_font (Font fid) min->rbearing = MIN (min->rbearing, cs->rbearing); # undef CEIL +# undef FLOOR -#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 } -# ifdef USE_IPHONE CFRelease (ctfont); -# endif } @@ -2422,7 +2654,7 @@ XQueryFont (Display *dpy, Font fid) *f = fid->metrics; // 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)); @@ -2657,7 +2889,7 @@ try_xlfd_font (const char *name, float scale, while (*s2 && (*s2 != '*' && *s2 != '-')) s2++; - int L = s2-s; + unsigned long L = s2-s; if (s == s2) ; # define CMP(STR) (L == strlen(STR) && !strncasecmp (s, (STR), L)) @@ -2735,8 +2967,15 @@ XLoadFont (Display *dpy, const char *name) float scale = 1; # ifdef USE_IPHONE - // Scale up fonts on Retina displays. - scale = dpy->main_window->window.view.contentScaleFactor; + /* 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->nsfont = try_native_font (name, scale, &fid->ps_name, &fid->size); @@ -2836,14 +3075,57 @@ XSetFont (Display *dpy, GC gc, Font fid) 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; + Font f = XLoadFont (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) +{ + XUnloadFont (dpy, set->font); + free (set); +} + + +void +XFreeStringList (char **list) +{ + int i; + if (!list) return; + for (i = 0; list[i]; i++) + XFree (list[i]); + XFree (list); +} + + 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++) { + for (int 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; @@ -2874,64 +3156,121 @@ XTextWidth (XFontStruct *f, const char *s, int length) } -static void -set_font (Display *dpy, 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 (dpy, 0); - gc->gcv.font = font; - [gc->gcv.font->nsfont retain]; - CFRetain (gc->gcv.font->nsfont); // needed for garbage collection? + Font fid = f->fid; + CTFontRef ctfont = + CTFontCreateWithName ((CFStringRef) [fid->nsfont fontName], + [fid->nsfont pointSize], + NULL); + Assert (ctfont, @"no CTFontRef for UIFont"); + + int utf8_len = 0; + char *utf8 = XChar2b_to_utf8 (s, &utf8_len); + NSString *nsstr = [NSString stringWithCString:utf8 + encoding:NSUTF8StringEncoding]; + NSUInteger L = [nsstr length]; + + for (int i = 0; i < L; i++) { + unichar c = [nsstr characterAtIndex:i]; + XCharStruct cc; + CGGlyph cgglyph = 0; + + if (CTFontGetGlyphsForCharacters (ctfont, &c, &cgglyph, 1)) + glyph_metrics (ctfont, cgglyph, &cc); + else + // This is normal, since Latin1 does not encode 0-31 or 127-159. + memset (&cc, 0, sizeof(cc)); + + 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; + } } - CGContextSelectFont (cgc, font->ps_name, font->size, kCGEncodingMacRoman); - CGContextSetTextMatrix (cgc, CGAffineTransformIdentity); + *dir_ret = 0; + *ascent_ret = f->ascent; + *descent_ret = f->descent; + + free (utf8); + CFRelease (ctfont); + + 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) +int +Xutf8TextExtents (XFontSet set, const char *str, int num_bytes, + XRectangle *overall_ink_return, + XRectangle *overall_logical_return) { - if (clear_background_p) { - int ascent, descent, dir; + Font fid = set->font; + CTFontRef ctfont = + CTFontCreateWithName ((CFStringRef) [fid->nsfont fontName], + [fid->nsfont pointSize], + NULL); + Assert (ctfont, @"no CTFontRef for UIFont"); + + NSString *nsstr = [NSString stringWithCString:str + encoding:NSUTF8StringEncoding]; + NSUInteger L = [nsstr length]; + + XRectangle ink = { 0, }; + XRectangle logical = { 0, }; + + logical.height = fid->metrics.ascent; + + for (int i = 0; i < L; i++) { + unichar c = [nsstr characterAtIndex:i]; 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); - } + CGGlyph cgglyph = 0; - CGRect wr = d->frame; + if (CTFontGetGlyphsForCharacters (ctfont, &c, &cgglyph, 1)) + glyph_metrics (ctfont, cgglyph, &cs); + else + // This is normal, since Latin1 does not encode 0-31 or 127-159. + memset (&cs, 0, sizeof(cs)); -# 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)! - */ + logical.width += cs.width; - CGContextRef cgc = d->cgc; - push_fg_gc (d, gc, YES); - set_font (dpy, cgc, gc); + ink.height = MAX(ink.height, cs.ascent); + ink.y = MIN(ink.y, cs.descent); - CGContextSetTextDrawingMode (cgc, kCGTextFill); - if (gc->gcv.antialias_p) - CGContextSetShouldAntialias (cgc, YES); - CGContextShowTextAtPoint (cgc, - wr.origin.x + x, - wr.origin.y + wr.size.height - y, - str, len); - pop_gc (d, gc); + if (i == 0) + ink.x = cs.lbearing; -# else /* !0 */ + if (i == L-1) { + ink.width += cs.rbearing; + } else { + ink.width += cs.width; + } + } + + CFRelease (ctfont); - /* The Cocoa way... - */ + if (overall_ink_return) + *overall_ink_return = ink; + if (overall_logical_return) + *overall_logical_return = logical; + return 0; +} + + +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)); @@ -2940,23 +3279,34 @@ draw_string (Display *dpy, Drawable d, GC gc, int x, int y, float g = ((argb >> 8) & 0xFF) / 255.0; float b = ((argb ) & 0xFF) / 255.0; NSColor *fg = [NSColor colorWithDeviceRed:r green:g blue:b alpha:a]; + + if (!gc->gcv.font) { + Assert (0, "no font"); + return 1; + } + NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys: gc->gcv.font->nsfont, NSFontAttributeName, fg, NSForegroundColorAttributeName, nil]; - char *s2 = (char *) malloc (len + 1); - strncpy (s2, str, len); - s2[len] = 0; - NSString *nsstr = [NSString stringWithCString:s2 - encoding:NSISOLatin1StringEncoding]; - free (s2); - NSPoint pos; - pos.x = wr.origin.x + x; - pos.y = wr.origin.y + wr.size.height - y - gc->gcv.font->metrics.descent; - [nsstr drawAtPoint:pos withAttributes:attr]; -# endif /* 0 */ + // Don't understand why we have to do both set_color and + // NSForegroundColorAttributeName, but we do. + // + set_color (cgc, argb, 32, NO, YES); + + NSAttributedString *astr = [[NSAttributedString alloc] + initWithString:nsstr + attributes:attr]; + CTLineRef dl = CTLineCreateWithAttributedString ( + (__bridge CFAttributedStringRef) astr); + 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; @@ -2967,14 +3317,69 @@ int XDrawString (Display *dpy, Drawable d, GC gc, int x, int y, const char *str, int len) { - return draw_string (dpy, d, gc, x, y, str, len, NO); + char *s2 = (char *) malloc (len + 1); + strncpy (s2, str, len); + s2[len] = 0; + NSString *nsstr = [NSString stringWithCString:s2 + encoding:NSISOLatin1StringEncoding]; + int ret = draw_string (dpy, d, gc, x, y, nsstr); + free (s2); + return ret; } + +int +XDrawString16 (Display *dpy, Drawable d, GC gc, int x, int y, + const XChar2b *str, int len) +{ + char *s2 = XChar2b_to_utf8 (str, 0); + NSString *nsstr = [NSString stringWithCString:s2 + encoding:NSUTF8StringEncoding]; + if (! nsstr) + /* If the C string has invalid UTF8 bytes in it, the result is + "undefined", which turns out to mean "return a null string" + instead of just omitting the bogus characters. Greaaat. + So try it again as Latin1, I guess. */ + nsstr = [NSString stringWithCString:s2 encoding:NSISOLatin1StringEncoding]; + int ret = draw_string (dpy, d, gc, x, y, nsstr); + free (s2); + return ret; +} + + +void +Xutf8DrawString (Display *dpy, Drawable d, XFontSet set, 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:NSUTF8StringEncoding]; + if (! nsstr) + nsstr = [NSString stringWithCString:s2 encoding:NSISOLatin1StringEncoding]; + 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), + NO, YES); + return XDrawString (dpy, d, gc, x, y, str, len); } @@ -3111,13 +3516,8 @@ XQueryPointer (Display *dpy, Window w, Window *root_ret, Window *child_ret, NSScreen *screen = (screens && [screens count] > 0 ? [screens objectAtIndex:0] : [NSScreen mainScreen]); -#ifdef USE_IPHONE - double s = w->window.view.contentScaleFactor; -#else - int s = 1; -#endif NSRect srect = [screen frame]; - vpos.y = (s * srect.size.height) - vpos.y; + vpos.y = srect.size.height - vpos.y; // get the mouse position on window, from bottom left NSEvent *e = [NSApp currentEvent]; @@ -3181,13 +3581,8 @@ XTranslateCoordinates (Display *dpy, Window w, Window dest_w, NSScreen *screen = (screens && [screens count] > 0 ? [screens objectAtIndex:0] : [NSScreen mainScreen]); -# ifdef USE_IPHONE - double s = w->window.view.contentScaleFactor; -# else - int s = 1; -# endif NSRect srect = [screen frame]; - vpos.y = (s * srect.size.height) - vpos.y; + vpos.y = srect.size.height - vpos.y; // point starts out relative to top left of view NSPoint p; @@ -3279,6 +3674,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)