1 /* xscreensaver, Copyright (c) 1991-2018 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
14 But it's a bunch of function definitions that bear some resemblance to
15 Xlib and that do Cocoa-ish things that bear some resemblance to the
16 things that Xlib might have done.
18 This is the original version of jwxyz for MacOS and iOS.
19 The version used by Android is in jwxyz-gl.c and jwxyz-common.c.
20 Both versions depend on jwxyz-cocoa.m.
23 #ifdef JWXYZ_QUARTZ // entire file
30 # import <UIKit/UIKit.h>
31 # import <UIKit/UIScreen.h>
32 # import <QuartzCore/QuartzCore.h>
33 # define NSView UIView
34 # define NSRect CGRect
35 # define NSPoint CGPoint
36 # define NSSize CGSize
37 # define NSColor UIColor
38 # define NSImage UIImage
39 # define NSEvent UIEvent
40 # define NSFont UIFont
41 # define NSGlyph CGGlyph
42 # define NSWindow UIWindow
43 # define NSMakeSize CGSizeMake
44 # define NSBezierPath UIBezierPath
45 # define colorWithDeviceRed colorWithRed
47 # import <Cocoa/Cocoa.h>
50 #import <CoreText/CTFont.h>
51 #import <CoreText/CTLine.h>
54 #import "jwxyz-cocoa.h"
55 #import "jwxyz-timers.h"
61 struct jwxyz_Display {
62 const struct jwxyz_vtbl *vtbl; // Must come first.
65 CGBitmapInfo bitmap_info;
67 struct jwxyz_sources_data *timers_data;
70 CGDirectDisplayID cgdpy; /* ...of the one and only Window, main_window.
71 This can change if the window is dragged to
72 a different screen. */
75 CGColorSpaceRef colorspace; /* Color space of this screen. We tag all of
76 our images with this to avoid translation
79 unsigned long window_background;
85 CGImageRef clip_mask; // CGImage copy of the Pixmap in gcv.clip_mask
89 // 8/16/24/32bpp -> 32bpp image conversion.
90 // Any of RGBA, BGRA, ABGR, or ARGB can be represented by a rotate of 0/8/16/24
91 // bits and an optional byte order swap.
93 // This type encodes such a conversion.
94 typedef unsigned convert_mode_t;
96 // It's rotate, then swap.
97 // A rotation here shifts bytes forward in memory. On x86/ARM, that's a left
98 // rotate, and on PowerPC, a rightward rotation.
99 static const convert_mode_t CONVERT_MODE_ROTATE_MASK = 0x3;
100 static const convert_mode_t CONVERT_MODE_SWAP = 0x4;
103 #if defined __LITTLE_ENDIAN__
104 # define PAD(r, g, b, a) ((r) | ((g) << 8) | ((b) << 16) | ((a) << 24))
105 #elif defined __BIG_ENDIAN__
106 # define PAD(r, g, b, a) (((r) << 24) | ((g) << 16) | ((b) << 8) | (a))
108 # error "Can't determine system endianness."
112 // Converts an array of pixels ('src') from one format to another, placing the
113 // result in 'dest', according to the pixel conversion mode 'mode'.
115 convert_row (uint32_t *dest, const void *src, size_t count,
116 convert_mode_t mode, size_t src_bpp)
118 Assert (src_bpp == 8 || src_bpp == 24 || src_bpp == 16 || src_bpp == 32,
121 // This works OK iff src == dest or src and dest do not overlap.
123 if (!mode && src_bpp == 32) {
125 memcpy (dest, src, count * 4);
129 // This is correct, but not fast.
130 convert_mode_t rot = (mode & CONVERT_MODE_ROTATE_MASK) * 8;
131 convert_mode_t flip = mode & CONVERT_MODE_SWAP;
135 uint32_t *dest_end = dest + count;
136 while (dest != dest_end) {
139 const uint8_t *src8 = (const uint8_t *)src;
142 x = *(const uint32_t *)src;
145 x = PAD(src8[0], src8[1], src8[2], 0xff);
148 x = PAD(src8[0], src8[0], src8[0], src8[1]);
151 x = PAD(src8[0], src8[0], src8[0], 0xff);
155 src = (const uint8_t *)src + src_bpp;
157 /* The naive (i.e. ubiquitous) portable implementation of bitwise rotation,
158 for 32-bit integers, is:
160 (x << rot) | (x >> (32 - rot))
162 This works nearly everywhere. Compilers on x86 wil generally recognize
163 the idiom and convert it to a ROL instruction. But there's a problem
164 here: according to the C specification, bit shifts greater than or equal
165 to the length of the integer are undefined. And if rot = 0:
166 1. (x << 0) | (x >> (32 - 0))
167 2. (x << 0) | (x >> 32)
168 3. (x << 0) | (Undefined!)
170 Still, when the compiler converts this to a ROL on x86, everything works
171 as intended. But, there are two additional problems when Clang does
172 compile-time constant expression evaluation with the (x >> 32)
174 1. Instead of evaluating it to something reasonable (either 0, like a
175 human would intuitively expect, or x, like x86 would with SHR), Clang
176 seems to pull a value out of nowhere, like -1, or some other random
178 2. Clang's warning for this, -Wshift-count-overflow, only works when the
179 shift count is a literal constant, as opposed to an arbitrary
180 expression that is optimized down to a constant.
181 Put together, this means that the assertions in
182 jwxyz_quartz_make_display with convert_px break with the above naive
183 rotation, but only for a release build.
185 http://blog.regehr.org/archives/1063
186 http://llvm.org/bugs/show_bug.cgi?id=17332
187 As described in those links, there is a solution here: Masking the
188 undefined shift with '& 31' as below makes the experesion well-defined
189 again. And LLVM is set to pick up on this safe version of the idiom and
190 use a rotation instruction on architectures (like x86) that support it,
191 just like it does with the unsafe version.
193 Too bad LLVM doesn't want to pick up on that particular optimization
194 here. Oh well. At least this code usually isn't critical w.r.t.
198 # if defined __LITTLE_ENDIAN__
199 x = (x << rot) | (x >> ((32 - rot) & 31));
200 # elif defined __BIG_ENDIAN__
201 x = (x >> rot) | (x << ((32 - rot) & 31));
205 x = __builtin_bswap32(x); // LLVM/GCC built-in function.
213 // Converts a single pixel.
215 convert_px (uint32_t px, convert_mode_t mode)
217 convert_row (&px, &px, 1, mode, 32);
222 // This returns the inverse conversion mode, such that:
224 // == convert_px(convert_px(pixel, mode), convert_mode_invert(mode))
225 // == convert_px(convert_px(pixel, convert_mode_invert(mode)), mode)
226 static convert_mode_t
227 convert_mode_invert (convert_mode_t mode)
229 // swap(0); rot(n) == rot(n); swap(0)
230 // swap(1); rot(n) == rot(-n); swap(1)
231 return mode & CONVERT_MODE_SWAP ? mode : CONVERT_MODE_ROTATE_MASK & -mode;
235 // This combines two conversions into one, such that:
236 // convert_px(convert_px(pixel, mode0), mode1)
237 // == convert_px(pixel, convert_mode_merge(mode0, mode1))
238 static convert_mode_t
239 convert_mode_merge (convert_mode_t m0, convert_mode_t m1)
241 // rot(r0); swap(s0); rot(r1); swap(s1)
242 // rot(r0); rot(s0 ? -r1 : r1); swap(s0); swap(s1)
243 // rot(r0 + (s0 ? -r1 : r1)); swap(s0 + s1)
245 ((m0 + (m0 & CONVERT_MODE_SWAP ? -m1 : m1)) & CONVERT_MODE_ROTATE_MASK) |
246 ((m0 ^ m1) & CONVERT_MODE_SWAP);
250 // This returns a conversion mode that converts an arbitrary 32-bit format
251 // specified by bitmap_info to RGBA.
252 static convert_mode_t
253 convert_mode_to_rgba (CGBitmapInfo bitmap_info)
255 // Former default: kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little
258 // green = 0x0000FF00;
259 // blue = 0x000000FF;
261 // RGBA: kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big
263 CGImageAlphaInfo alpha_info =
264 (CGImageAlphaInfo)(bitmap_info & kCGBitmapAlphaInfoMask);
266 Assert (! (bitmap_info & kCGBitmapFloatComponents),
267 "kCGBitmapFloatComponents unsupported");
268 Assert (alpha_info != kCGImageAlphaOnly, "kCGImageAlphaOnly not supported");
270 convert_mode_t rot = alpha_info == kCGImageAlphaFirst ||
271 alpha_info == kCGImageAlphaPremultipliedFirst ||
272 alpha_info == kCGImageAlphaNoneSkipFirst ?
275 CGBitmapInfo byte_order = bitmap_info & kCGBitmapByteOrderMask;
277 Assert (byte_order == kCGBitmapByteOrder32Little ||
278 byte_order == kCGBitmapByteOrder32Big,
279 "byte order not supported");
281 convert_mode_t swap = byte_order == kCGBitmapByteOrder32Little ?
282 CONVERT_MODE_SWAP : 0;
284 rot = CONVERT_MODE_ROTATE_MASK & -rot;
297 query_color_float (Display *dpy, unsigned long pixel, CGFloat *rgba)
299 JWXYZ_QUERY_COLOR (dpy, pixel, (CGFloat)1, rgba);
303 extern const struct jwxyz_vtbl quartz_vtbl;
306 jwxyz_quartz_make_display (Window w)
308 CGContextRef cgc = w->cgc;
310 Display *d = (Display *) calloc (1, sizeof(*d));
311 d->vtbl = &quartz_vtbl;
313 d->bitmap_info = CGBitmapContextGetBitmapInfo (cgc);
316 // Tests for the image conversion modes.
318 const uint32_t key = 0x04030201;
319 # ifdef __LITTLE_ENDIAN__
320 assert (convert_px (key, 0) == key);
321 assert (convert_px (key, 1) == 0x03020104);
322 assert (convert_px (key, 3) == 0x01040302);
323 assert (convert_px (key, 4) == 0x01020304);
324 assert (convert_px (key, 5) == 0x04010203);
326 for (unsigned i = 0; i != 8; ++i) {
327 assert (convert_px(convert_px(key, i), convert_mode_invert(i)) == key);
328 assert (convert_mode_invert(convert_mode_invert(i)) == i);
331 for (unsigned i = 0; i != 8; ++i) {
332 for (unsigned j = 0; j != 8; ++j)
333 assert (convert_px(convert_px(key, i), j) ==
334 convert_px(key, convert_mode_merge(i, j)));
339 Visual *v = &d->visual;
340 v->class = TrueColor;
342 union color_bytes color;
343 convert_mode_t mode =
344 convert_mode_invert (convert_mode_to_rgba (d->bitmap_info));
345 unsigned long masks[4];
346 for (unsigned i = 0; i != 4; ++i) {
348 color.bytes[i] = 0xff;
349 masks[i] = convert_px (color.pixel, mode);
351 v->red_mask = masks[0];
352 v->green_mask = masks[1];
353 v->blue_mask = masks[2];
354 v->alpha_mask = masks[3];
356 CGBitmapInfo byte_order = d->bitmap_info & kCGBitmapByteOrderMask;
357 Assert ( ! (d->bitmap_info & kCGBitmapFloatComponents) &&
358 (byte_order == kCGBitmapByteOrder32Little ||
359 byte_order == kCGBitmapByteOrder32Big),
360 "invalid bits per channel");
362 d->timers_data = jwxyz_sources_init (XtDisplayToApplicationContext (d));
364 d->window_background = BlackPixel(d,0);
368 Assert (cgc, "no CGContext");
373 jwxyz_quartz_free_display (Display *dpy)
375 jwxyz_sources_free (dpy->timers_data);
381 /* Call this after any modification to the bits on a Pixmap or Window.
382 Most Pixmaps are used frequently as sources and infrequently as
383 destinations, so it pays to cache the data as a CGImage as needed.
386 invalidate_drawable_cache (Drawable d)
389 CGImageRelease (d->cgi);
395 /* Call this when the View changes size or position.
398 jwxyz_window_resized (Display *dpy)
400 Window w = dpy->main_window;
403 // Figure out which screen the window is currently on.
406 XTranslateCoordinates (dpy, w, NULL, 0, 0, &wx, &wy, NULL);
412 CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n);
416 CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n);
418 Assert (dpy->cgdpy, "unable to find CGDisplay");
420 # endif // USE_IPHONE
424 // Figure out this screen's colorspace, and use that for every CGImage.
426 CMProfileRef profile = 0;
427 CMGetProfileByAVID ((CMDisplayIDType) dpy->cgdpy, &profile);
428 Assert (profile, "unable to find colorspace profile");
429 dpy->colorspace = CGColorSpaceCreateWithPlatformColorSpace (profile);
430 Assert (dpy->colorspace, "unable to find colorspace");
434 // WTF? It's faster if we *do not* use the screen's colorspace!
436 dpy->colorspace = CGColorSpaceCreateDeviceRGB();
438 invalidate_drawable_cache (w);
443 jwxyz_flush_context (Display *dpy)
445 // CGContextSynchronize is another possibility.
446 CGContextFlush(dpy->main_window->cgc);
449 static jwxyz_sources_data *
450 display_sources_data (Display *dpy)
452 return dpy->timers_data;
459 return dpy->main_window;
463 visual (Display *dpy)
470 set_color (Display *dpy, CGContextRef cgc, unsigned long argb,
471 unsigned int depth, Bool alpha_allowed_p, Bool fill_p)
473 jwxyz_validate_pixel (dpy, argb, depth, alpha_allowed_p);
476 CGContextSetGrayFillColor (cgc, (argb ? 1.0 : 0.0), 1.0);
478 CGContextSetGrayStrokeColor (cgc, (argb ? 1.0 : 0.0), 1.0);
481 query_color_float (dpy, argb, rgba);
483 CGContextSetRGBFillColor (cgc, rgba[0], rgba[1], rgba[2], rgba[3]);
485 CGContextSetRGBStrokeColor (cgc, rgba[0], rgba[1], rgba[2], rgba[3]);
490 set_line_mode (CGContextRef cgc, XGCValues *gcv)
492 CGContextSetLineWidth (cgc, gcv->line_width ? gcv->line_width : 1);
493 CGContextSetLineJoin (cgc,
494 gcv->join_style == JoinMiter ? kCGLineJoinMiter :
495 gcv->join_style == JoinRound ? kCGLineJoinRound :
497 CGContextSetLineCap (cgc,
498 gcv->cap_style == CapNotLast ? kCGLineCapButt :
499 gcv->cap_style == CapButt ? kCGLineCapButt :
500 gcv->cap_style == CapRound ? kCGLineCapRound :
505 set_clip_mask (Drawable d, GC gc)
507 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
509 Pixmap p = gc->gcv.clip_mask;
511 Assert (p->type == PIXMAP, "not a pixmap");
513 XRectangle wr = d->frame;
515 to.origin.x = wr.x + gc->gcv.clip_x_origin;
516 to.origin.y = wr.y + wr.height - gc->gcv.clip_y_origin
518 to.size.width = p->frame.width;
519 to.size.height = p->frame.height;
521 CGContextClipToMask (d->cgc, to, gc->clip_mask);
525 /* Pushes a GC context; sets BlendMode and ClipMask.
528 push_gc (Drawable d, GC gc)
530 CGContextRef cgc = d->cgc;
531 CGContextSaveGState (cgc);
533 switch (gc->gcv.function) {
536 case GXcopy:/*CGContextSetBlendMode (cgc, kCGBlendModeNormal);*/ break;
537 case GXxor: CGContextSetBlendMode (cgc, kCGBlendModeDifference); break;
538 case GXor: CGContextSetBlendMode (cgc, kCGBlendModeLighten); break;
539 case GXand: CGContextSetBlendMode (cgc, kCGBlendModeDarken); break;
540 default: Assert(0, "unknown gcv function"); break;
543 if (gc->gcv.clip_mask)
544 set_clip_mask (d, gc);
548 /* Pushes a GC context; sets BlendMode, ClipMask, Fill, and Stroke colors.
551 push_color_gc (Display *dpy, Drawable d, GC gc, unsigned long color,
552 Bool antialias_p, Bool fill_p)
556 int depth = gc->depth;
557 switch (gc->gcv.function) {
558 case GXset: color = (depth == 1 ? 1 : WhitePixel(dpy,0)); break;
559 case GXclear: color = (depth == 1 ? 0 : BlackPixel(dpy,0)); break;
562 CGContextRef cgc = d->cgc;
563 set_color (dpy, cgc, color, depth, gc->gcv.alpha_allowed_p, fill_p);
564 CGContextSetShouldAntialias (cgc, antialias_p);
568 /* Pushes a GC context; sets Fill and Stroke colors to the foreground color.
571 push_fg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p)
573 push_color_gc (dpy, d, gc, gc->gcv.foreground, gc->gcv.antialias_p, fill_p);
577 bitmap_context_p (Drawable d)
584 /* You've got to be fucking kidding me!
586 It is *way* faster to draw points by creating and drawing a 1x1 CGImage
587 with repeated calls to CGContextDrawImage than it is to make a single
588 call to CGContextFillRects() with a list of 1x1 rectangles!
590 I still wouldn't call it *fast*, however...
592 #define XDRAWPOINTS_IMAGES
594 /* Update, 2012: Kurt Revis <krevis@snoize.com> points out that diddling
595 the bitmap data directly is faster. This only works on Pixmaps, though,
596 not Windows. (Fortunately, on iOS, the Window is really a Pixmap.)
598 #define XDRAWPOINTS_CGDATA
601 DrawPoints (Display *dpy, Drawable d, GC gc,
602 XPoint *points, int count, int mode)
605 XRectangle wr = d->frame;
607 # ifdef XDRAWPOINTS_CGDATA
609 if (bitmap_context_p (d))
611 CGContextRef cgc = d->cgc;
612 void *data = CGBitmapContextGetData (cgc);
613 size_t bpr = CGBitmapContextGetBytesPerRow (cgc);
614 size_t w = CGBitmapContextGetWidth (cgc);
615 size_t h = CGBitmapContextGetHeight (cgc);
617 Assert (data, "no bitmap data in Drawable");
619 unsigned long argb = gc->gcv.foreground;
620 jwxyz_validate_pixel (dpy, argb, gc->depth, gc->gcv.alpha_allowed_p);
622 argb = (gc->gcv.foreground ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
625 CGFloat y0 = wr.y; // Y axis is refreshingly not flipped.
627 // It's uglier, but faster, to hoist the conditional out of the loop.
628 if (mode == CoordModePrevious) {
629 CGFloat x = x0, y = y0;
630 for (i = 0; i < count; i++, points++) {
634 if (x >= 0 && x < w && y >= 0 && y < h) {
635 unsigned int *p = (unsigned int *)
636 ((char *) data + (size_t) y * bpr + (size_t) x * 4);
637 *p = (unsigned int) argb;
641 for (i = 0; i < count; i++, points++) {
642 CGFloat x = x0 + points->x;
643 CGFloat y = y0 + points->y;
645 if (x >= 0 && x < w && y >= 0 && y < h) {
646 unsigned int *p = (unsigned int *)
647 ((char *) data + (size_t) y * bpr + (size_t) x * 4);
648 *p = (unsigned int) argb;
653 } else /* d->type == WINDOW */
655 # endif /* XDRAWPOINTS_CGDATA */
657 push_fg_gc (dpy, d, gc, YES);
659 # ifdef XDRAWPOINTS_IMAGES
661 unsigned long argb = gc->gcv.foreground;
662 jwxyz_validate_pixel (dpy, argb, gc->depth, gc->gcv.alpha_allowed_p);
664 argb = (gc->gcv.foreground ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
666 CGDataProviderRef prov = CGDataProviderCreateWithData (NULL, &argb, 4,
668 CGImageRef cgi = CGImageCreate (1, 1,
671 /* Host-ordered, since we're using the
672 address of an int as the color data. */
676 NO, /* interpolate */
677 kCGRenderingIntentDefault);
678 CGDataProviderRelease (prov);
680 CGContextRef cgc = d->cgc;
682 rect.size.width = rect.size.height = 1;
683 for (i = 0; i < count; i++) {
684 if (i > 0 && mode == CoordModePrevious) {
685 rect.origin.x += points->x;
686 rect.origin.x -= points->y;
688 rect.origin.x = wr.x + points->x;
689 rect.origin.y = wr.y + wr.height - points->y - 1;
692 //Assert(CGImageGetColorSpace (cgi) == dpy->colorspace,"bad colorspace");
693 CGContextDrawImage (cgc, rect, cgi);
697 CGImageRelease (cgi);
699 # else /* ! XDRAWPOINTS_IMAGES */
701 CGRect *rects = (CGRect *) malloc (count * sizeof(CGRect));
704 for (i = 0; i < count; i++) {
705 r->size.width = r->size.height = 1;
706 if (i > 0 && mode == CoordModePrevious) {
707 r->origin.x = r[-1].origin.x + points->x;
708 r->origin.y = r[-1].origin.x - points->y;
710 r->origin.x = wr.origin.x + points->x;
711 r->origin.y = wr.origin.y + wr.size.height - points->y;
717 CGContextFillRects (d->cgc, rects, count);
720 # endif /* ! XDRAWPOINTS_IMAGES */
725 invalidate_drawable_cache (d);
732 map_point (Drawable d, int x, int y)
734 const XRectangle *wr = &d->frame;
737 p.y = wr->y + wr->height - y;
743 adjust_point_for_line (GC gc, CGPoint *p)
745 // Here's the authoritative discussion on how X draws lines:
746 // http://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:CreateGC:line-width
747 if (gc->gcv.line_width <= 1) {
748 /* Thin lines are "drawn using an unspecified, device-dependent
749 algorithm", but seriously though, Bresenham's algorithm. Bresenham's
750 algorithm runs to and from pixel centers.
752 There's a few screenhacks (Maze, at the very least) that set line_width
753 to 1 when it probably should be set to 0, so it's line_width <= 1
759 /* Thick lines OTOH run from the upper-left corners of pixels. This means
760 that a horizontal thick line of width 1 straddles two scan lines.
761 Aliasing requires one of these scan lines be chosen; the following
762 nudges the point so that the right choice is made. */
769 point_for_line (Drawable d, GC gc, int x, int y)
771 CGPoint result = map_point (d, x, y);
772 adjust_point_for_line (gc, &result);
778 DrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count,
783 push_fg_gc (dpy, d, gc, NO);
785 CGContextRef cgc = d->cgc;
787 set_line_mode (cgc, &gc->gcv);
789 // if the first and last points coincide, use closepath to get
790 // the proper line-joining.
791 BOOL closed_p = (points[0].x == points[count-1].x &&
792 points[0].y == points[count-1].y);
793 if (closed_p) count--;
795 p = point_for_line(d, gc, points->x, points->y);
797 CGContextBeginPath (cgc);
798 CGContextMoveToPoint (cgc, p.x, p.y);
799 for (i = 1; i < count; i++) {
800 if (mode == CoordModePrevious) {
804 p = point_for_line(d, gc, points->x, points->y);
806 CGContextAddLineToPoint (cgc, p.x, p.y);
809 if (closed_p) CGContextClosePath (cgc);
810 CGContextStrokePath (cgc);
812 invalidate_drawable_cache (d);
818 DrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count)
822 CGContextRef cgc = d->cgc;
824 push_fg_gc (dpy, d, gc, NO);
825 set_line_mode (cgc, &gc->gcv);
826 CGContextBeginPath (cgc);
827 for (i = 0; i < count; i++) {
828 // when drawing a zero-length line, obey line-width and cap-style.
829 if (segments->x1 == segments->x2 && segments->y1 == segments->y2) {
830 int w = gc->gcv.line_width;
831 int x1 = segments->x1 - w/2;
832 int y1 = segments->y1 - w/2;
833 if (gc->gcv.line_width > 1 && gc->gcv.cap_style == CapRound)
834 XFillArc (dpy, d, gc, x1, y1, w, w, 0, 360*64);
837 w = 1; // Actually show zero-length lines.
838 XFillRectangle (dpy, d, gc, x1, y1, w, w);
841 CGPoint p = point_for_line (d, gc, segments->x1, segments->y1);
842 CGContextMoveToPoint (cgc, p.x, p.y);
843 p = point_for_line (d, gc, segments->x2, segments->y2);
844 CGContextAddLineToPoint (cgc, p.x, p.y);
849 CGContextStrokePath (cgc);
851 invalidate_drawable_cache (d);
857 ClearWindow (Display *dpy, Window win)
859 Assert (win && win->type == WINDOW, "not a window");
860 XRectangle wr = win->frame;
861 return XClearArea (dpy, win, 0, 0, wr.width, wr.height, 0);
864 static unsigned long *
865 window_background (Display *dpy)
867 return &dpy->window_background;
871 fill_rects (Display *dpy, Drawable d, GC gc,
872 const XRectangle *rectangles, unsigned long nrectangles,
875 Assert (!gc || gc->depth == jwxyz_drawable_depth (d), "depth mismatch");
877 CGContextRef cgc = d->cgc;
880 bitmap_context_p (d) &&
881 (!gc || (gc->gcv.function == GXcopy &&
882 !gc->gcv.alpha_allowed_p &&
883 !gc->gcv.clip_mask));
887 push_color_gc (dpy, d, gc, pixel, gc->gcv.antialias_p, YES);
889 set_color (dpy, d->cgc, pixel, jwxyz_drawable_depth (d), NO, YES);
892 for (unsigned i = 0; i != nrectangles; ++i) {
894 int x = rectangles[i].x;
895 int y = rectangles[i].y;
896 unsigned long width = rectangles[i].width;
897 unsigned long height = rectangles[i].height;
900 long // negative_int > unsigned_int == 1
901 dw = CGBitmapContextGetWidth (cgc),
902 dh = CGBitmapContextGetHeight (cgc);
904 if (x >= dw || y >= dh)
917 if (width <= 0 || height <= 0)
920 unsigned long max_width = dw - x;
921 if (width > max_width)
923 unsigned long max_height = dh - y;
924 if (height > max_height)
927 if (jwxyz_drawable_depth (d) == 1)
928 pixel = pixel ? WhitePixel(dpy, 0) : BlackPixel(dpy, 0);
930 size_t dst_bytes_per_row = CGBitmapContextGetBytesPerRow (d->cgc);
931 void *dst = SEEK_XY (CGBitmapContextGetData (d->cgc),
932 dst_bytes_per_row, x, y);
934 Assert(sizeof(wchar_t) == 4, "somebody changed the ABI");
936 // Would be nice if Apple used SSE/NEON in wmemset. Maybe someday.
937 wmemset (dst, (wchar_t) pixel, width);
939 dst = (char *) dst + dst_bytes_per_row;
944 r.origin = map_point (d, x, y);
945 r.origin.y -= height;
946 r.size.width = width;
947 r.size.height = height;
948 CGContextFillRect (cgc, r);
952 if (!fast_fill_p && gc)
954 invalidate_drawable_cache (d);
959 FillPolygon (Display *dpy, Drawable d, GC gc,
960 XPoint *points, int npoints, int shape, int mode)
962 XRectangle wr = d->frame;
964 push_fg_gc (dpy, d, gc, YES);
965 CGContextRef cgc = d->cgc;
966 CGContextBeginPath (cgc);
968 for (i = 0; i < npoints; i++) {
969 if (i > 0 && mode == CoordModePrevious) {
973 x = wr.x + points[i].x;
974 y = wr.y + wr.height - points[i].y;
978 CGContextMoveToPoint (cgc, x, y);
980 CGContextAddLineToPoint (cgc, x, y);
982 CGContextClosePath (cgc);
983 if (gc->gcv.fill_rule == EvenOddRule)
984 CGContextEOFillPath (cgc);
986 CGContextFillPath (cgc);
988 invalidate_drawable_cache (d);
992 #define radians(DEG) ((DEG) * M_PI / 180.0)
993 #define degrees(RAD) ((RAD) * 180.0 / M_PI)
996 draw_arc (Display *dpy, Drawable d, GC gc, int x, int y,
997 unsigned int width, unsigned int height,
998 int angle1, int angle2, Bool fill_p)
1000 XRectangle wr = d->frame;
1002 bound.origin.x = wr.x + x;
1003 bound.origin.y = wr.y + wr.height - y - (int)height;
1004 bound.size.width = width;
1005 bound.size.height = height;
1008 ctr.x = bound.origin.x + bound.size.width /2;
1009 ctr.y = bound.origin.y + bound.size.height/2;
1011 float r1 = radians (angle1/64.0);
1012 float r2 = radians (angle2/64.0) + r1;
1013 BOOL clockwise = angle2 < 0;
1014 BOOL closed_p = (angle2 >= 360*64 || angle2 <= -360*64);
1016 push_fg_gc (dpy, d, gc, fill_p);
1018 CGContextRef cgc = d->cgc;
1019 CGContextBeginPath (cgc);
1021 CGContextSaveGState(cgc);
1022 CGContextTranslateCTM (cgc, ctr.x, ctr.y);
1023 CGContextScaleCTM (cgc, width/2.0, height/2.0);
1025 CGContextMoveToPoint (cgc, 0, 0);
1027 CGContextAddArc (cgc, 0.0, 0.0, 1, r1, r2, clockwise);
1028 CGContextRestoreGState (cgc); // restore before stroke, for line width
1031 CGContextClosePath (cgc); // for proper line joining
1034 CGContextFillPath (cgc);
1036 set_line_mode (cgc, &gc->gcv);
1037 CGContextStrokePath (cgc);
1041 invalidate_drawable_cache (d);
1061 CreateGC (Display *dpy, Drawable d, unsigned long mask, XGCValues *xgcv)
1063 struct jwxyz_GC *gc = (struct jwxyz_GC *) calloc (1, sizeof(*gc));
1064 gc->depth = jwxyz_drawable_depth (d);
1066 jwxyz_gcv_defaults (dpy, &gc->gcv, gc->depth);
1067 XChangeGC (dpy, gc, mask, xgcv);
1073 FreeGC (Display *dpy, GC gc)
1076 XUnloadFont (dpy, gc->gcv.font);
1078 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
1080 if (gc->gcv.clip_mask) {
1081 XFreePixmap (dpy, gc->gcv.clip_mask);
1082 CGImageRelease (gc->clip_mask);
1090 flipbits (unsigned const char *in, unsigned char *out, int length)
1092 static const unsigned char table[256] = {
1093 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0,
1094 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
1095 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8,
1096 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
1097 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4,
1098 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
1099 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC,
1100 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
1101 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2,
1102 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
1103 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA,
1104 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
1105 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6,
1106 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
1107 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE,
1108 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
1109 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1,
1110 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
1111 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9,
1112 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
1113 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5,
1114 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
1115 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED,
1116 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
1117 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3,
1118 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
1119 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB,
1120 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
1121 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7,
1122 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
1123 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF,
1124 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
1126 while (length-- > 0)
1127 *out++ = table[*in++];
1132 PutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
1133 int src_x, int src_y, int dest_x, int dest_y,
1134 unsigned int w, unsigned int h)
1136 XRectangle wr = d->frame;
1138 Assert (gc, "no GC");
1139 Assert ((w < 65535), "improbably large width");
1140 Assert ((h < 65535), "improbably large height");
1141 Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x");
1142 Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y");
1143 Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x");
1144 Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y");
1146 // Clip width and height to the bounds of the Drawable
1148 if (dest_x + (int)w > wr.width) {
1149 if (dest_x > wr.width)
1151 w = wr.width - dest_x;
1153 if (dest_y + (int)h > wr.height) {
1154 if (dest_y > wr.height)
1156 h = wr.height - dest_y;
1158 if (w <= 0 || h <= 0)
1161 // Clip width and height to the bounds of the XImage
1163 if (src_x + w > ximage->width) {
1164 if (src_x > ximage->width)
1166 w = ximage->width - src_x;
1168 if (src_y + h > ximage->height) {
1169 if (src_y > ximage->height)
1171 h = ximage->height - src_y;
1173 if (w <= 0 || h <= 0)
1176 CGContextRef cgc = d->cgc;
1178 if (jwxyz_dumb_drawing_mode(dpy, d, gc, dest_x, dest_y, w, h))
1181 int bpl = ximage->bytes_per_line;
1182 int bpp = ximage->bits_per_pixel;
1183 int bsize = bpl * h;
1184 char *data = ximage->data;
1187 r.origin.x = wr.x + dest_x;
1188 r.origin.y = wr.y + wr.height - dest_y - (int)h;
1194 /* Take advantage of the fact that it's ok for (bpl != w * bpp)
1195 to create a CGImage from a sub-rectagle of the XImage.
1197 data += (src_y * bpl) + (src_x * 4);
1198 CGDataProviderRef prov =
1199 CGDataProviderCreateWithData (NULL, data, bsize, NULL);
1201 CGImageRef cgi = CGImageCreate (w, h,
1206 NULL, /* decode[] */
1207 NO, /* interpolate */
1208 kCGRenderingIntentDefault);
1209 CGDataProviderRelease (prov);
1210 //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
1211 CGContextDrawImage (cgc, r, cgi);
1212 CGImageRelease (cgi);
1214 } else { // (bpp == 1)
1216 /* To draw a 1bpp image, we use it as a mask and fill two rectangles.
1218 #### However, the bit order within a byte in a 1bpp XImage is
1219 the wrong way around from what Quartz expects, so first we
1220 have to copy the data to reverse it. Shit! Maybe it
1221 would be worthwhile to go through the hacks and #ifdef
1222 each one that diddles 1bpp XImage->data directly...
1224 Assert ((src_x % 8) == 0,
1225 "XPutImage with non-byte-aligned 1bpp X offset not implemented");
1227 data += (src_y * bpl) + (src_x / 8); // move to x,y within the data
1228 unsigned char *flipped = (unsigned char *) malloc (bsize);
1230 flipbits ((unsigned char *) data, flipped, bsize);
1232 CGDataProviderRef prov =
1233 CGDataProviderCreateWithData (NULL, flipped, bsize, NULL);
1234 CGImageRef mask = CGImageMaskCreate (w, h,
1237 NULL, /* decode[] */
1238 NO); /* interpolate */
1239 push_fg_gc (dpy, d, gc, YES);
1241 CGContextFillRect (cgc, r); // foreground color
1242 CGContextClipToMask (cgc, r, mask);
1243 set_color (dpy, cgc, gc->gcv.background, gc->depth, NO, YES);
1244 CGContextFillRect (cgc, r); // background color
1248 CGDataProviderRelease (prov);
1249 CGImageRelease (mask);
1252 invalidate_drawable_cache (d);
1259 GetSubImage (Display *dpy, Drawable d, int x, int y,
1260 unsigned int width, unsigned int height,
1261 unsigned long plane_mask, int format,
1262 XImage *image, int dest_x, int dest_y)
1264 const unsigned char *data = 0;
1265 size_t depth, ibpp, ibpl;
1266 convert_mode_t mode;
1268 Assert ((width < 65535), "improbably large width");
1269 Assert ((height < 65535), "improbably large height");
1270 Assert ((x < 65535 && x > -65535), "improbably large x");
1271 Assert ((y < 65535 && y > -65535), "improbably large y");
1273 CGContextRef cgc = d->cgc;
1276 depth = jwxyz_drawable_depth (d);
1277 mode = convert_mode_to_rgba (dpy->bitmap_info);
1278 ibpp = CGBitmapContextGetBitsPerPixel (cgc);
1279 ibpl = CGBitmapContextGetBytesPerRow (cgc);
1280 data = CGBitmapContextGetData (cgc);
1281 Assert (data, "CGBitmapContextGetData failed");
1284 // data points at (x,y) with ibpl rowstride. ignore x,y from now on.
1285 data += (y * ibpl) + (x * (ibpp/8));
1287 format = (depth == 1 ? XYPixmap : ZPixmap);
1289 int obpl = image->bytes_per_line;
1291 /* both PPC and Intel use word-ordered ARGB frame buffers, which
1292 means that on Intel it is BGRA when viewed by bytes (And BGR
1293 when using 24bpp packing).
1295 BUT! Intel-64 stores alpha at the other end! 32bit=RGBA, 64bit=ARGB.
1296 The NSAlphaFirstBitmapFormat bit in bitmapFormat seems to be the
1297 indicator of this latest kink.
1301 const unsigned char *iline = data;
1302 for (yy = 0; yy < height; yy++) {
1304 const unsigned char *iline2 = iline;
1305 for (xx = 0; xx < width; xx++) {
1307 iline2++; // ignore R or A or A or B
1308 iline2++; // ignore G or B or R or G
1309 unsigned char r = *iline2++; // use B or G or G or R
1310 if (ibpp == 32) iline2++; // ignore A or R or B or A
1312 XPutPixel (image, xx + dest_x, yy + dest_y, (r ? 1 : 0));
1317 const unsigned char *iline = data;
1318 unsigned char *oline = (unsigned char *) image->data + dest_y * obpl +
1321 mode = convert_mode_merge (mode,
1322 convert_mode_invert (
1323 convert_mode_to_rgba (dpy->bitmap_info)));
1325 for (yy = 0; yy < height; yy++) {
1327 convert_row ((uint32_t *)oline, iline, width, mode, ibpp);
1339 /* Returns a transformation matrix to do rotation as per the provided
1340 EXIF "Orientation" value.
1342 static CGAffineTransform
1343 exif_rotate (int rot, CGSize rect)
1345 CGAffineTransform trans = CGAffineTransformIdentity;
1347 case 2: // flip horizontal
1348 trans = CGAffineTransformMakeTranslation (rect.width, 0);
1349 trans = CGAffineTransformScale (trans, -1, 1);
1352 case 3: // rotate 180
1353 trans = CGAffineTransformMakeTranslation (rect.width, rect.height);
1354 trans = CGAffineTransformRotate (trans, M_PI);
1357 case 4: // flip vertical
1358 trans = CGAffineTransformMakeTranslation (0, rect.height);
1359 trans = CGAffineTransformScale (trans, 1, -1);
1362 case 5: // transpose (UL-to-LR axis)
1363 trans = CGAffineTransformMakeTranslation (rect.height, rect.width);
1364 trans = CGAffineTransformScale (trans, -1, 1);
1365 trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
1368 case 6: // rotate 90
1369 trans = CGAffineTransformMakeTranslation (0, rect.width);
1370 trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
1373 case 7: // transverse (UR-to-LL axis)
1374 trans = CGAffineTransformMakeScale (-1, 1);
1375 trans = CGAffineTransformRotate (trans, M_PI / 2);
1378 case 8: // rotate 270
1379 trans = CGAffineTransformMakeTranslation (rect.height, 0);
1380 trans = CGAffineTransformRotate (trans, M_PI / 2);
1392 jwxyz_draw_NSImage_or_CGImage (Display *dpy, Drawable d,
1393 Bool nsimg_p, void *img_arg,
1394 XRectangle *geom_ret, int exif_rotation)
1398 CGImageSourceRef cgsrc;
1399 # endif // USE_IPHONE
1402 CGContextRef cgc = d->cgc;
1406 NSImage *nsimg = (NSImage *) img_arg;
1407 imgr = [nsimg size];
1410 // convert the NSImage to a CGImage via the toll-free-bridging
1411 // of NSData and CFData...
1413 NSData *nsdata = [NSBitmapImageRep
1414 TIFFRepresentationOfImageRepsInArray:
1415 [nsimg representations]];
1416 CFDataRef cfdata = (CFDataRef) nsdata;
1417 cgsrc = CGImageSourceCreateWithData (cfdata, NULL);
1418 cgi = CGImageSourceCreateImageAtIndex (cgsrc, 0, NULL);
1419 # else // USE_IPHONE
1420 cgi = nsimg.CGImage;
1421 # endif // USE_IPHONE
1424 cgi = (CGImageRef) img_arg;
1425 imgr.width = CGImageGetWidth (cgi);
1426 imgr.height = CGImageGetHeight (cgi);
1429 Bool rot_p = (exif_rotation >= 5);
1432 imgr = NSMakeSize (imgr.height, imgr.width);
1434 XRectangle winr = d->frame;
1435 float rw = winr.width / imgr.width;
1436 float rh = winr.height / imgr.height;
1437 float r = (rw < rh ? rw : rh);
1439 /* If the window is a goofy aspect ratio, take a middle slice of
1440 the image instead. */
1441 if (winr.width > winr.height * 5 ||
1442 winr.width > winr.width * 5) {
1443 r *= (winr.width > winr.height
1444 ? winr.width / (double) winr.height
1445 : winr.height / (double) winr.width);
1446 // NSLog (@"weird aspect: scaling by %.1f\n", r);
1450 dst.size.width = imgr.width * r;
1451 dst.size.height = imgr.height * r;
1452 dst.origin.x = (winr.width - dst.size.width) / 2;
1453 dst.origin.y = (winr.height - dst.size.height) / 2;
1455 dst2.origin.x = dst2.origin.y = 0;
1457 dst2.size.width = dst.size.height;
1458 dst2.size.height = dst.size.width;
1460 dst2.size = dst.size;
1463 // Clear the part not covered by the image to background or black.
1465 if (d->type == WINDOW)
1466 XClearWindow (dpy, d);
1468 jwxyz_fill_rect (dpy, d, 0, 0, 0, winr.width, winr.height,
1469 jwxyz_drawable_depth (d) == 1 ? 0 : BlackPixel(dpy,0));
1472 CGAffineTransform trans =
1473 exif_rotate (exif_rotation, rot_p ? dst2.size : dst.size);
1475 CGContextSaveGState (cgc);
1476 CGContextConcatCTM (cgc,
1477 CGAffineTransformMakeTranslation (dst.origin.x,
1479 CGContextConcatCTM (cgc, trans);
1480 //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
1481 CGContextDrawImage (cgc, dst2, cgi);
1482 CGContextRestoreGState (cgc);
1487 CGImageRelease (cgi);
1489 # endif // USE_IPHONE
1492 geom_ret->x = dst.origin.x;
1493 geom_ret->y = dst.origin.y;
1494 geom_ret->width = dst.size.width;
1495 geom_ret->height = dst.size.height;
1498 invalidate_drawable_cache (d);
1503 jwxyz_png_to_ximage (Display *dpy, Visual *visual,
1504 const unsigned char *png_data, unsigned long data_size)
1506 NSImage *img = [[NSImage alloc] initWithData:
1507 [NSData dataWithBytes:png_data
1509 if (! img) return 0;
1511 NSBitmapImageRep *bm = [NSBitmapImageRep
1514 TIFFRepresentationOfImageRepsInArray:
1515 [img representations]]];
1520 int width = [img size].width;
1521 int height = [img size].height;
1522 size_t ibpp = [bm bitsPerPixel];
1523 size_t ibpl = [bm bytesPerRow];
1524 const unsigned char *data = [bm bitmapData];
1525 convert_mode_t mode = (([bm bitmapFormat] & NSAlphaFirstBitmapFormat)
1526 ? CONVERT_MODE_ROTATE_MASK
1529 CGImageRef cgi = [img CGImage];
1534 int width = CGImageGetWidth (cgi);
1535 int height = CGImageGetHeight (cgi);
1537 size_t ibpl = ibpp/4 * width;
1538 unsigned char *data = (unsigned char *) calloc (ibpl, height);
1539 const CGBitmapInfo bitmap_info =
1540 kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
1542 CGBitmapContextCreate (data, width, height,
1543 8, /* bits per component */
1544 ibpl, dpy->colorspace,
1546 CGContextDrawImage (cgc, CGRectMake (0, 0, width, height), cgi);
1548 convert_mode_t mode = convert_mode_to_rgba (bitmap_info);
1550 #endif // USE_IPHONE
1552 XImage *image = XCreateImage (dpy, visual, 32, ZPixmap, 0, 0,
1553 width, height, 8, 0);
1554 image->data = (char *) malloc (image->height * image->bytes_per_line);
1556 // data points at (x,y) with ibpl rowstride.
1558 int obpl = image->bytes_per_line;
1559 const unsigned char *iline = data;
1560 unsigned char *oline = (unsigned char *) image->data;
1562 for (yy = 0; yy < height; yy++) {
1563 convert_row ((uint32_t *)oline, iline, width, mode, ibpp);
1573 CGContextRelease (cgc);
1582 XCreatePixmap (Display *dpy, Drawable d,
1583 unsigned int width, unsigned int height, unsigned int depth)
1586 char *data = (char *) malloc (width * height * 4);
1587 if (! data) return 0;
1589 Pixmap p = (Pixmap) calloc (1, sizeof(*p));
1591 p->frame.width = width;
1592 p->frame.height = height;
1593 p->pixmap.depth = depth;
1594 p->pixmap.cgc_buffer = data;
1596 /* Quartz doesn't have a 1bpp image type.
1597 Used to use 8bpp gray images instead of 1bpp, but some Mac video cards
1598 don't support that! So we always use 32bpp, regardless of depth. */
1600 p->cgc = CGBitmapContextCreate (data, width, height,
1601 8, /* bits per component */
1602 width * 4, /* bpl */
1605 Assert (p->cgc, "could not create CGBitmapContext");
1611 XFreePixmap (Display *d, Pixmap p)
1613 Assert (p && p->type == PIXMAP, "not a pixmap");
1614 invalidate_drawable_cache (p);
1615 CGContextRelease (p->cgc);
1616 if (p->pixmap.cgc_buffer)
1617 free (p->pixmap.cgc_buffer);
1624 copy_pixmap (Display *dpy, Pixmap p)
1627 Assert (p->type == PIXMAP, "not a pixmap");
1633 unsigned int width, height, border_width, depth;
1634 if (XGetGeometry (dpy, p, &root,
1635 &x, &y, &width, &height, &border_width, &depth)) {
1637 gcv.function = GXcopy;
1638 GC gc = XCreateGC (dpy, p, GCFunction, &gcv);
1640 p2 = XCreatePixmap (dpy, p, width, height, depth);
1642 XCopyArea (dpy, p, p2, gc, 0, 0, width, height, 0, 0);
1647 Assert (p2, "could not copy pixmap");
1653 // Returns the verbose Unicode name of this character, like "agrave" or
1654 // "daggerdouble". Used by fontglide debugMetrics.
1657 jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
1660 NSFont *nsfont = (NSFont *) jwxyz_native_font (fid);
1662 CTFontCreateWithName ((CFStringRef) [nsfont fontName],
1665 Assert (ctfont, "no CTFontRef for UIFont");
1668 if (CTFontGetGlyphsForCharacters (ctfont, (UniChar *) &uc, &cgglyph, 1)) {
1669 CGFontRef cgfont = CTFontCopyGraphicsFont (ctfont, 0);
1670 NSString *name = (NSString *) CGFontCopyGlyphNameForGlyph(cgfont, cgglyph);
1671 ret = (name ? strdup ([name UTF8String]) : 0);
1672 CGFontRelease (cgfont);
1682 draw_string (Display *dpy, Drawable d, GC gc, int x, int y,
1683 const char *str, size_t len, int utf8_p)
1685 NSString *nsstr = nsstring_from (str, len, utf8_p);
1687 if (! nsstr) return 1;
1689 XRectangle wr = d->frame;
1690 CGContextRef cgc = d->cgc;
1692 unsigned long argb = gc->gcv.foreground;
1693 if (gc->depth == 1) argb = (argb ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
1695 query_color_float (dpy, argb, rgba);
1696 NSColor *fg = [NSColor colorWithDeviceRed:rgba[0]
1701 if (!gc->gcv.font) {
1702 Assert (0, "no font");
1706 /* This crashes on iOS 5.1 because NSForegroundColorAttributeName,
1707 NSFontAttributeName, and NSAttributedString are only present on iOS 6
1708 and later. We could resurrect the Quartz code from v5.29 and do a
1709 runtime conditional on that, but that would be a pain in the ass.
1710 Probably time to just make iOS 6 a requirement.
1713 NSDictionary *attr =
1714 [NSDictionary dictionaryWithObjectsAndKeys:
1715 (NSFont *) jwxyz_native_font (gc->gcv.font), NSFontAttributeName,
1716 fg, NSForegroundColorAttributeName,
1719 // Don't understand why we have to do both set_color and
1720 // NSForegroundColorAttributeName, but we do.
1722 set_color (dpy, cgc, argb, 32, NO, YES);
1724 NSAttributedString *astr = [[NSAttributedString alloc]
1725 initWithString:nsstr
1727 CTLineRef dl = CTLineCreateWithAttributedString (
1728 (__bridge CFAttributedStringRef) astr);
1730 // Not sure why this is necessary, but xoff is positive when the first
1731 // character on the line has a negative lbearing. Without this, the
1732 // string is rendered with the first ink at 0 instead of at lbearing.
1733 // I have not seen xoff be negative, so I'm not sure if that can happen.
1735 // Test case: "Combining Double Tilde" U+0360 (\315\240) followed by
1738 CGFloat xoff = CTLineGetOffsetForStringIndex (dl, 0, NULL);
1739 Assert (xoff >= 0, "unexpected CTLineOffset");
1742 CGContextSetTextPosition (cgc,
1744 wr.y + wr.height - y);
1745 CGContextSetShouldAntialias (cgc, gc->gcv.antialias_p);
1747 CTLineDraw (dl, cgc);
1751 invalidate_drawable_cache (d);
1757 SetClipMask (Display *dpy, GC gc, Pixmap m)
1759 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
1761 if (gc->gcv.clip_mask) {
1762 XFreePixmap (dpy, gc->gcv.clip_mask);
1763 CGImageRelease (gc->clip_mask);
1766 gc->gcv.clip_mask = copy_pixmap (dpy, m);
1767 if (gc->gcv.clip_mask)
1769 CGBitmapContextCreateImage (gc->gcv.clip_mask->cgc);
1777 SetClipOrigin (Display *dpy, GC gc, int x, int y)
1779 gc->gcv.clip_x_origin = x;
1780 gc->gcv.clip_y_origin = y;
1785 const struct jwxyz_vtbl quartz_vtbl = {
1788 display_sources_data,
1797 jwxyz_quartz_copy_area,
1812 #endif // JWXYZ_QUARTZ -- entire file