1 /* xscreensaver, Copyright (c) 1991-2017 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 // 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 // Converts an array of pixels ('src') from one format to another, placing the
104 // result in 'dest', according to the pixel conversion mode 'mode'.
106 convert_row (uint32_t *dest, const void *src, size_t count,
107 convert_mode_t mode, size_t src_bpp)
109 Assert (src_bpp == 24 || src_bpp == 32, "weird bpp");
111 // This works OK iff src == dest or src and dest do not overlap.
115 memcpy (dest, src, count * 4);
119 // This is correct, but not fast.
120 convert_mode_t rot = (mode & CONVERT_MODE_ROTATE_MASK) * 8;
121 convert_mode_t flip = mode & CONVERT_MODE_SWAP;
125 uint32_t *dest_end = dest + count;
126 while (dest != dest_end) {
130 x = *(const uint32_t *)src;
131 else { // src_bpp == 3
132 const uint8_t *src8 = (const uint8_t *)src;
133 // __LITTLE/BIG_ENDIAN__ are defined by the compiler.
134 # if defined __LITTLE_ENDIAN__
135 x = src8[0] | (src8[1] << 8) | (src8[2] << 16) | 0xff000000;
136 # elif defined __BIG_ENDIAN__
137 x = (src8[0] << 24) | (src8[1] << 16) | (src8[2] << 8) | 0xff;
139 # error "Can't determine system endianness."
143 src = (const uint8_t *)src + src_bpp;
145 /* The naive (i.e. ubiquitous) portable implementation of bitwise rotation,
146 for 32-bit integers, is:
148 (x << rot) | (x >> (32 - rot))
150 This works nearly everywhere. Compilers on x86 wil generally recognize
151 the idiom and convert it to a ROL instruction. But there's a problem
152 here: according to the C specification, bit shifts greater than or equal
153 to the length of the integer are undefined. And if rot = 0:
154 1. (x << 0) | (x >> (32 - 0))
155 2. (x << 0) | (x >> 32)
156 3. (x << 0) | (Undefined!)
158 Still, when the compiler converts this to a ROL on x86, everything works
159 as intended. But, there are two additional problems when Clang does
160 compile-time constant expression evaluation with the (x >> 32)
162 1. Instead of evaluating it to something reasonable (either 0, like a
163 human would intuitively expect, or x, like x86 would with SHR), Clang
164 seems to pull a value out of nowhere, like -1, or some other random
166 2. Clang's warning for this, -Wshift-count-overflow, only works when the
167 shift count is a literal constant, as opposed to an arbitrary
168 expression that is optimized down to a constant.
169 Put together, this means that the assertions in
170 jwxyz_quartz_make_display with convert_px break with the above naive
171 rotation, but only for a release build.
173 http://blog.regehr.org/archives/1063
174 http://llvm.org/bugs/show_bug.cgi?id=17332
175 As described in those links, there is a solution here: Masking the
176 undefined shift with '& 31' as below makes the experesion well-defined
177 again. And LLVM is set to pick up on this safe version of the idiom and
178 use a rotation instruction on architectures (like x86) that support it,
179 just like it does with the unsafe version.
181 Too bad LLVM doesn't want to pick up on that particular optimization
182 here. Oh well. At least this code usually isn't critical w.r.t.
186 # if defined __LITTLE_ENDIAN__
187 x = (x << rot) | (x >> ((32 - rot) & 31));
188 # elif defined __BIG_ENDIAN__
189 x = (x >> rot) | (x << ((32 - rot) & 31));
193 x = __builtin_bswap32(x); // LLVM/GCC built-in function.
201 // Converts a single pixel.
203 convert_px (uint32_t px, convert_mode_t mode)
205 convert_row (&px, &px, 1, mode, 32);
210 // This returns the inverse conversion mode, such that:
212 // == convert_px(convert_px(pixel, mode), convert_mode_invert(mode))
213 // == convert_px(convert_px(pixel, convert_mode_invert(mode)), mode)
214 static convert_mode_t
215 convert_mode_invert (convert_mode_t mode)
217 // swap(0); rot(n) == rot(n); swap(0)
218 // swap(1); rot(n) == rot(-n); swap(1)
219 return mode & CONVERT_MODE_SWAP ? mode : CONVERT_MODE_ROTATE_MASK & -mode;
223 // This combines two conversions into one, such that:
224 // convert_px(convert_px(pixel, mode0), mode1)
225 // == convert_px(pixel, convert_mode_merge(mode0, mode1))
226 static convert_mode_t
227 convert_mode_merge (convert_mode_t m0, convert_mode_t m1)
229 // rot(r0); swap(s0); rot(r1); swap(s1)
230 // rot(r0); rot(s0 ? -r1 : r1); swap(s0); swap(s1)
231 // rot(r0 + (s0 ? -r1 : r1)); swap(s0 + s1)
233 ((m0 + (m0 & CONVERT_MODE_SWAP ? -m1 : m1)) & CONVERT_MODE_ROTATE_MASK) |
234 ((m0 ^ m1) & CONVERT_MODE_SWAP);
238 // This returns a conversion mode that converts an arbitrary 32-bit format
239 // specified by bitmap_info to RGBA.
240 static convert_mode_t
241 convert_mode_to_rgba (CGBitmapInfo bitmap_info)
243 // Former default: kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little
246 // green = 0x0000FF00;
247 // blue = 0x000000FF;
249 // RGBA: kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big
251 CGImageAlphaInfo alpha_info =
252 (CGImageAlphaInfo)(bitmap_info & kCGBitmapAlphaInfoMask);
254 Assert (! (bitmap_info & kCGBitmapFloatComponents),
255 "kCGBitmapFloatComponents unsupported");
256 Assert (alpha_info != kCGImageAlphaOnly, "kCGImageAlphaOnly not supported");
258 convert_mode_t rot = alpha_info == kCGImageAlphaFirst ||
259 alpha_info == kCGImageAlphaPremultipliedFirst ||
260 alpha_info == kCGImageAlphaNoneSkipFirst ?
263 CGBitmapInfo byte_order = bitmap_info & kCGBitmapByteOrderMask;
265 Assert (byte_order == kCGBitmapByteOrder32Little ||
266 byte_order == kCGBitmapByteOrder32Big,
267 "byte order not supported");
269 convert_mode_t swap = byte_order == kCGBitmapByteOrder32Little ?
270 CONVERT_MODE_SWAP : 0;
272 rot = CONVERT_MODE_ROTATE_MASK & -rot;
285 query_color_float (Display *dpy, unsigned long pixel, CGFloat *rgba)
287 JWXYZ_QUERY_COLOR (dpy, pixel, (CGFloat)1, rgba);
291 extern const struct jwxyz_vtbl quartz_vtbl;
294 jwxyz_quartz_make_display (Window w)
296 CGContextRef cgc = w->cgc;
298 Display *d = (Display *) calloc (1, sizeof(*d));
299 d->vtbl = &quartz_vtbl;
301 d->bitmap_info = CGBitmapContextGetBitmapInfo (cgc);
304 // Tests for the image conversion modes.
306 const uint32_t key = 0x04030201;
307 # ifdef __LITTLE_ENDIAN__
308 assert (convert_px (key, 0) == key);
309 assert (convert_px (key, 1) == 0x03020104);
310 assert (convert_px (key, 3) == 0x01040302);
311 assert (convert_px (key, 4) == 0x01020304);
312 assert (convert_px (key, 5) == 0x04010203);
314 for (unsigned i = 0; i != 8; ++i) {
315 assert (convert_px(convert_px(key, i), convert_mode_invert(i)) == key);
316 assert (convert_mode_invert(convert_mode_invert(i)) == i);
319 for (unsigned i = 0; i != 8; ++i) {
320 for (unsigned j = 0; j != 8; ++j)
321 assert (convert_px(convert_px(key, i), j) ==
322 convert_px(key, convert_mode_merge(i, j)));
327 Visual *v = &d->visual;
328 v->class = TrueColor;
330 union color_bytes color;
331 convert_mode_t mode =
332 convert_mode_invert (convert_mode_to_rgba (d->bitmap_info));
333 for (unsigned i = 0; i != 4; ++i) {
335 color.bytes[i] = 0xff;
336 v->rgba_masks[i] = convert_px (color.pixel, mode);
339 CGBitmapInfo byte_order = d->bitmap_info & kCGBitmapByteOrderMask;
340 Assert ( ! (d->bitmap_info & kCGBitmapFloatComponents) &&
341 (byte_order == kCGBitmapByteOrder32Little ||
342 byte_order == kCGBitmapByteOrder32Big),
343 "invalid bits per channel");
345 d->timers_data = jwxyz_sources_init (XtDisplayToApplicationContext (d));
347 d->window_background = BlackPixel(d,0);
351 Assert (cgc, "no CGContext");
356 jwxyz_quartz_free_display (Display *dpy)
358 jwxyz_sources_free (dpy->timers_data);
364 /* Call this after any modification to the bits on a Pixmap or Window.
365 Most Pixmaps are used frequently as sources and infrequently as
366 destinations, so it pays to cache the data as a CGImage as needed.
369 invalidate_drawable_cache (Drawable d)
372 CGImageRelease (d->cgi);
378 /* Call this when the View changes size or position.
381 jwxyz_window_resized (Display *dpy)
383 Window w = dpy->main_window;
386 // Figure out which screen the window is currently on.
389 XTranslateCoordinates (dpy, w, NULL, 0, 0, &wx, &wy, NULL);
395 CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n);
399 CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n);
401 Assert (dpy->cgdpy, "unable to find CGDisplay");
403 # endif // USE_IPHONE
407 // Figure out this screen's colorspace, and use that for every CGImage.
409 CMProfileRef profile = 0;
410 CMGetProfileByAVID ((CMDisplayIDType) dpy->cgdpy, &profile);
411 Assert (profile, "unable to find colorspace profile");
412 dpy->colorspace = CGColorSpaceCreateWithPlatformColorSpace (profile);
413 Assert (dpy->colorspace, "unable to find colorspace");
417 // WTF? It's faster if we *do not* use the screen's colorspace!
419 dpy->colorspace = CGColorSpaceCreateDeviceRGB();
421 invalidate_drawable_cache (w);
426 jwxyz_flush_context (Display *dpy)
428 // CGContextSynchronize is another possibility.
429 CGContextFlush(dpy->main_window->cgc);
432 static jwxyz_sources_data *
433 display_sources_data (Display *dpy)
435 return dpy->timers_data;
442 return dpy->main_window;
446 visual (Display *dpy)
453 set_color (Display *dpy, CGContextRef cgc, unsigned long argb,
454 unsigned int depth, Bool alpha_allowed_p, Bool fill_p)
456 jwxyz_validate_pixel (dpy, argb, depth, alpha_allowed_p);
459 CGContextSetGrayFillColor (cgc, (argb ? 1.0 : 0.0), 1.0);
461 CGContextSetGrayStrokeColor (cgc, (argb ? 1.0 : 0.0), 1.0);
464 query_color_float (dpy, argb, rgba);
466 CGContextSetRGBFillColor (cgc, rgba[0], rgba[1], rgba[2], rgba[3]);
468 CGContextSetRGBStrokeColor (cgc, rgba[0], rgba[1], rgba[2], rgba[3]);
473 set_line_mode (CGContextRef cgc, XGCValues *gcv)
475 CGContextSetLineWidth (cgc, gcv->line_width ? gcv->line_width : 1);
476 CGContextSetLineJoin (cgc,
477 gcv->join_style == JoinMiter ? kCGLineJoinMiter :
478 gcv->join_style == JoinRound ? kCGLineJoinRound :
480 CGContextSetLineCap (cgc,
481 gcv->cap_style == CapNotLast ? kCGLineCapButt :
482 gcv->cap_style == CapButt ? kCGLineCapButt :
483 gcv->cap_style == CapRound ? kCGLineCapRound :
488 set_clip_mask (Drawable d, GC gc)
490 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
492 Pixmap p = gc->gcv.clip_mask;
494 Assert (p->type == PIXMAP, "not a pixmap");
496 XRectangle wr = d->frame;
498 to.origin.x = wr.x + gc->gcv.clip_x_origin;
499 to.origin.y = wr.y + wr.height - gc->gcv.clip_y_origin
501 to.size.width = p->frame.width;
502 to.size.height = p->frame.height;
504 CGContextClipToMask (d->cgc, to, gc->clip_mask);
508 /* Pushes a GC context; sets BlendMode and ClipMask.
511 push_gc (Drawable d, GC gc)
513 CGContextRef cgc = d->cgc;
514 CGContextSaveGState (cgc);
516 switch (gc->gcv.function) {
519 case GXcopy:/*CGContextSetBlendMode (cgc, kCGBlendModeNormal);*/ break;
520 case GXxor: CGContextSetBlendMode (cgc, kCGBlendModeDifference); break;
521 case GXor: CGContextSetBlendMode (cgc, kCGBlendModeLighten); break;
522 case GXand: CGContextSetBlendMode (cgc, kCGBlendModeDarken); break;
523 default: Assert(0, "unknown gcv function"); break;
526 if (gc->gcv.clip_mask)
527 set_clip_mask (d, gc);
531 /* Pushes a GC context; sets BlendMode, ClipMask, Fill, and Stroke colors.
534 push_color_gc (Display *dpy, Drawable d, GC gc, unsigned long color,
535 Bool antialias_p, Bool fill_p)
539 int depth = gc->depth;
540 switch (gc->gcv.function) {
541 case GXset: color = (depth == 1 ? 1 : WhitePixel(dpy,0)); break;
542 case GXclear: color = (depth == 1 ? 0 : BlackPixel(dpy,0)); break;
545 CGContextRef cgc = d->cgc;
546 set_color (dpy, cgc, color, depth, gc->gcv.alpha_allowed_p, fill_p);
547 CGContextSetShouldAntialias (cgc, antialias_p);
551 /* Pushes a GC context; sets Fill and Stroke colors to the foreground color.
554 push_fg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p)
556 push_color_gc (dpy, d, gc, gc->gcv.foreground, gc->gcv.antialias_p, fill_p);
560 bitmap_context_p (Drawable d)
567 /* You've got to be fucking kidding me!
569 It is *way* faster to draw points by creating and drawing a 1x1 CGImage
570 with repeated calls to CGContextDrawImage than it is to make a single
571 call to CGContextFillRects() with a list of 1x1 rectangles!
573 I still wouldn't call it *fast*, however...
575 #define XDRAWPOINTS_IMAGES
577 /* Update, 2012: Kurt Revis <krevis@snoize.com> points out that diddling
578 the bitmap data directly is faster. This only works on Pixmaps, though,
579 not Windows. (Fortunately, on iOS, the Window is really a Pixmap.)
581 #define XDRAWPOINTS_CGDATA
584 DrawPoints (Display *dpy, Drawable d, GC gc,
585 XPoint *points, int count, int mode)
588 XRectangle wr = d->frame;
590 # ifdef XDRAWPOINTS_CGDATA
592 if (bitmap_context_p (d))
594 CGContextRef cgc = d->cgc;
595 void *data = CGBitmapContextGetData (cgc);
596 size_t bpr = CGBitmapContextGetBytesPerRow (cgc);
597 size_t w = CGBitmapContextGetWidth (cgc);
598 size_t h = CGBitmapContextGetHeight (cgc);
600 Assert (data, "no bitmap data in Drawable");
602 unsigned long argb = gc->gcv.foreground;
603 jwxyz_validate_pixel (dpy, argb, gc->depth, gc->gcv.alpha_allowed_p);
605 argb = (gc->gcv.foreground ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
608 CGFloat y0 = wr.y; // Y axis is refreshingly not flipped.
610 // It's uglier, but faster, to hoist the conditional out of the loop.
611 if (mode == CoordModePrevious) {
612 CGFloat x = x0, y = y0;
613 for (i = 0; i < count; i++, points++) {
617 if (x >= 0 && x < w && y >= 0 && y < h) {
618 unsigned int *p = (unsigned int *)
619 ((char *) data + (size_t) y * bpr + (size_t) x * 4);
620 *p = (unsigned int) argb;
624 for (i = 0; i < count; i++, points++) {
625 CGFloat x = x0 + points->x;
626 CGFloat y = y0 + points->y;
628 if (x >= 0 && x < w && y >= 0 && y < h) {
629 unsigned int *p = (unsigned int *)
630 ((char *) data + (size_t) y * bpr + (size_t) x * 4);
631 *p = (unsigned int) argb;
636 } else /* d->type == WINDOW */
638 # endif /* XDRAWPOINTS_CGDATA */
640 push_fg_gc (dpy, d, gc, YES);
642 # ifdef XDRAWPOINTS_IMAGES
644 unsigned long argb = gc->gcv.foreground;
645 jwxyz_validate_pixel (dpy, argb, gc->depth, gc->gcv.alpha_allowed_p);
647 argb = (gc->gcv.foreground ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
649 CGDataProviderRef prov = CGDataProviderCreateWithData (NULL, &argb, 4,
651 CGImageRef cgi = CGImageCreate (1, 1,
654 /* Host-ordered, since we're using the
655 address of an int as the color data. */
659 NO, /* interpolate */
660 kCGRenderingIntentDefault);
661 CGDataProviderRelease (prov);
663 CGContextRef cgc = d->cgc;
665 rect.size.width = rect.size.height = 1;
666 for (i = 0; i < count; i++) {
667 if (i > 0 && mode == CoordModePrevious) {
668 rect.origin.x += points->x;
669 rect.origin.x -= points->y;
671 rect.origin.x = wr.x + points->x;
672 rect.origin.y = wr.y + wr.height - points->y - 1;
675 //Assert(CGImageGetColorSpace (cgi) == dpy->colorspace,"bad colorspace");
676 CGContextDrawImage (cgc, rect, cgi);
680 CGImageRelease (cgi);
682 # else /* ! XDRAWPOINTS_IMAGES */
684 CGRect *rects = (CGRect *) malloc (count * sizeof(CGRect));
687 for (i = 0; i < count; i++) {
688 r->size.width = r->size.height = 1;
689 if (i > 0 && mode == CoordModePrevious) {
690 r->origin.x = r[-1].origin.x + points->x;
691 r->origin.y = r[-1].origin.x - points->y;
693 r->origin.x = wr.origin.x + points->x;
694 r->origin.y = wr.origin.y + wr.size.height - points->y;
700 CGContextFillRects (d->cgc, rects, count);
703 # endif /* ! XDRAWPOINTS_IMAGES */
708 invalidate_drawable_cache (d);
715 map_point (Drawable d, int x, int y)
717 const XRectangle *wr = &d->frame;
720 p.y = wr->y + wr->height - y;
726 adjust_point_for_line (GC gc, CGPoint *p)
728 // Here's the authoritative discussion on how X draws lines:
729 // http://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:CreateGC:line-width
730 if (gc->gcv.line_width <= 1) {
731 /* Thin lines are "drawn using an unspecified, device-dependent
732 algorithm", but seriously though, Bresenham's algorithm. Bresenham's
733 algorithm runs to and from pixel centers.
735 There's a few screenhacks (Maze, at the very least) that set line_width
736 to 1 when it probably should be set to 0, so it's line_width <= 1
742 /* Thick lines OTOH run from the upper-left corners of pixels. This means
743 that a horizontal thick line of width 1 straddles two scan lines.
744 Aliasing requires one of these scan lines be chosen; the following
745 nudges the point so that the right choice is made. */
752 point_for_line (Drawable d, GC gc, int x, int y)
754 CGPoint result = map_point (d, x, y);
755 adjust_point_for_line (gc, &result);
761 DrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count,
766 push_fg_gc (dpy, d, gc, NO);
768 CGContextRef cgc = d->cgc;
770 set_line_mode (cgc, &gc->gcv);
772 // if the first and last points coincide, use closepath to get
773 // the proper line-joining.
774 BOOL closed_p = (points[0].x == points[count-1].x &&
775 points[0].y == points[count-1].y);
776 if (closed_p) count--;
778 p = point_for_line(d, gc, points->x, points->y);
780 CGContextBeginPath (cgc);
781 CGContextMoveToPoint (cgc, p.x, p.y);
782 for (i = 1; i < count; i++) {
783 if (mode == CoordModePrevious) {
787 p = point_for_line(d, gc, points->x, points->y);
789 CGContextAddLineToPoint (cgc, p.x, p.y);
792 if (closed_p) CGContextClosePath (cgc);
793 CGContextStrokePath (cgc);
795 invalidate_drawable_cache (d);
801 DrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count)
805 CGContextRef cgc = d->cgc;
807 push_fg_gc (dpy, d, gc, NO);
808 set_line_mode (cgc, &gc->gcv);
809 CGContextBeginPath (cgc);
810 for (i = 0; i < count; i++) {
811 // when drawing a zero-length line, obey line-width and cap-style.
812 if (segments->x1 == segments->x2 && segments->y1 == segments->y2) {
813 int w = gc->gcv.line_width;
814 int x1 = segments->x1 - w/2;
815 int y1 = segments->y1 - w/2;
816 if (gc->gcv.line_width > 1 && gc->gcv.cap_style == CapRound)
817 XFillArc (dpy, d, gc, x1, y1, w, w, 0, 360*64);
820 w = 1; // Actually show zero-length lines.
821 XFillRectangle (dpy, d, gc, x1, y1, w, w);
824 CGPoint p = point_for_line (d, gc, segments->x1, segments->y1);
825 CGContextMoveToPoint (cgc, p.x, p.y);
826 p = point_for_line (d, gc, segments->x2, segments->y2);
827 CGContextAddLineToPoint (cgc, p.x, p.y);
832 CGContextStrokePath (cgc);
834 invalidate_drawable_cache (d);
840 ClearWindow (Display *dpy, Window win)
842 Assert (win && win->type == WINDOW, "not a window");
843 XRectangle wr = win->frame;
844 return XClearArea (dpy, win, 0, 0, wr.width, wr.height, 0);
847 static unsigned long *
848 window_background (Display *dpy)
850 return &dpy->window_background;
854 fill_rects (Display *dpy, Drawable d, GC gc,
855 const XRectangle *rectangles, unsigned long nrectangles,
858 Assert (!gc || gc->depth == jwxyz_drawable_depth (d), "depth mismatch");
860 CGContextRef cgc = d->cgc;
863 bitmap_context_p (d) &&
864 (!gc || (gc->gcv.function == GXcopy &&
865 !gc->gcv.alpha_allowed_p &&
866 !gc->gcv.clip_mask));
870 push_color_gc (dpy, d, gc, pixel, gc->gcv.antialias_p, YES);
872 set_color (dpy, d->cgc, pixel, jwxyz_drawable_depth (d), NO, YES);
875 for (unsigned i = 0; i != nrectangles; ++i) {
877 int x = rectangles[i].x;
878 int y = rectangles[i].y;
879 unsigned long width = rectangles[i].width;
880 unsigned long height = rectangles[i].height;
883 long // negative_int > unsigned_int == 1
884 dw = CGBitmapContextGetWidth (cgc),
885 dh = CGBitmapContextGetHeight (cgc);
887 if (x >= dw || y >= dh)
900 if (width <= 0 || height <= 0)
903 unsigned long max_width = dw - x;
904 if (width > max_width)
906 unsigned long max_height = dh - y;
907 if (height > max_height)
910 if (jwxyz_drawable_depth (d) == 1)
911 pixel = pixel ? WhitePixel(dpy, 0) : BlackPixel(dpy, 0);
913 size_t dst_bytes_per_row = CGBitmapContextGetBytesPerRow (d->cgc);
914 void *dst = SEEK_XY (CGBitmapContextGetData (d->cgc),
915 dst_bytes_per_row, x, y);
917 Assert(sizeof(wchar_t) == 4, "somebody changed the ABI");
919 // Would be nice if Apple used SSE/NEON in wmemset. Maybe someday.
920 wmemset (dst, (wchar_t) pixel, width);
922 dst = (char *) dst + dst_bytes_per_row;
927 r.origin = map_point (d, x, y);
928 r.origin.y -= height;
929 r.size.width = width;
930 r.size.height = height;
931 CGContextFillRect (cgc, r);
935 if (!fast_fill_p && gc)
937 invalidate_drawable_cache (d);
942 FillPolygon (Display *dpy, Drawable d, GC gc,
943 XPoint *points, int npoints, int shape, int mode)
945 XRectangle wr = d->frame;
947 push_fg_gc (dpy, d, gc, YES);
948 CGContextRef cgc = d->cgc;
949 CGContextBeginPath (cgc);
951 for (i = 0; i < npoints; i++) {
952 if (i > 0 && mode == CoordModePrevious) {
956 x = wr.x + points[i].x;
957 y = wr.y + wr.height - points[i].y;
961 CGContextMoveToPoint (cgc, x, y);
963 CGContextAddLineToPoint (cgc, x, y);
965 CGContextClosePath (cgc);
966 if (gc->gcv.fill_rule == EvenOddRule)
967 CGContextEOFillPath (cgc);
969 CGContextFillPath (cgc);
971 invalidate_drawable_cache (d);
975 #define radians(DEG) ((DEG) * M_PI / 180.0)
976 #define degrees(RAD) ((RAD) * 180.0 / M_PI)
979 draw_arc (Display *dpy, Drawable d, GC gc, int x, int y,
980 unsigned int width, unsigned int height,
981 int angle1, int angle2, Bool fill_p)
983 XRectangle wr = d->frame;
985 bound.origin.x = wr.x + x;
986 bound.origin.y = wr.y + wr.height - y - (int)height;
987 bound.size.width = width;
988 bound.size.height = height;
991 ctr.x = bound.origin.x + bound.size.width /2;
992 ctr.y = bound.origin.y + bound.size.height/2;
994 float r1 = radians (angle1/64.0);
995 float r2 = radians (angle2/64.0) + r1;
996 BOOL clockwise = angle2 < 0;
997 BOOL closed_p = (angle2 >= 360*64 || angle2 <= -360*64);
999 push_fg_gc (dpy, d, gc, fill_p);
1001 CGContextRef cgc = d->cgc;
1002 CGContextBeginPath (cgc);
1004 CGContextSaveGState(cgc);
1005 CGContextTranslateCTM (cgc, ctr.x, ctr.y);
1006 CGContextScaleCTM (cgc, width/2.0, height/2.0);
1008 CGContextMoveToPoint (cgc, 0, 0);
1010 CGContextAddArc (cgc, 0.0, 0.0, 1, r1, r2, clockwise);
1011 CGContextRestoreGState (cgc); // restore before stroke, for line width
1014 CGContextClosePath (cgc); // for proper line joining
1017 CGContextFillPath (cgc);
1019 set_line_mode (cgc, &gc->gcv);
1020 CGContextStrokePath (cgc);
1024 invalidate_drawable_cache (d);
1044 CreateGC (Display *dpy, Drawable d, unsigned long mask, XGCValues *xgcv)
1046 struct jwxyz_GC *gc = (struct jwxyz_GC *) calloc (1, sizeof(*gc));
1047 gc->depth = jwxyz_drawable_depth (d);
1049 jwxyz_gcv_defaults (dpy, &gc->gcv, gc->depth);
1050 XChangeGC (dpy, gc, mask, xgcv);
1056 FreeGC (Display *dpy, GC gc)
1059 XUnloadFont (dpy, gc->gcv.font);
1061 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
1063 if (gc->gcv.clip_mask) {
1064 XFreePixmap (dpy, gc->gcv.clip_mask);
1065 CGImageRelease (gc->clip_mask);
1073 flipbits (unsigned const char *in, unsigned char *out, int length)
1075 static const unsigned char table[256] = {
1076 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0,
1077 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
1078 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8,
1079 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
1080 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4,
1081 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
1082 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC,
1083 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
1084 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2,
1085 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
1086 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA,
1087 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
1088 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6,
1089 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
1090 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE,
1091 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
1092 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1,
1093 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
1094 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9,
1095 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
1096 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5,
1097 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
1098 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED,
1099 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
1100 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3,
1101 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
1102 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB,
1103 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
1104 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7,
1105 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
1106 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF,
1107 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
1109 while (length-- > 0)
1110 *out++ = table[*in++];
1115 PutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
1116 int src_x, int src_y, int dest_x, int dest_y,
1117 unsigned int w, unsigned int h)
1119 XRectangle wr = d->frame;
1121 Assert (gc, "no GC");
1122 Assert ((w < 65535), "improbably large width");
1123 Assert ((h < 65535), "improbably large height");
1124 Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x");
1125 Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y");
1126 Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x");
1127 Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y");
1129 // Clip width and height to the bounds of the Drawable
1131 if (dest_x + (int)w > wr.width) {
1132 if (dest_x > wr.width)
1134 w = wr.width - dest_x;
1136 if (dest_y + (int)h > wr.height) {
1137 if (dest_y > wr.height)
1139 h = wr.height - dest_y;
1141 if (w <= 0 || h <= 0)
1144 // Clip width and height to the bounds of the XImage
1146 if (src_x + w > ximage->width) {
1147 if (src_x > ximage->width)
1149 w = ximage->width - src_x;
1151 if (src_y + h > ximage->height) {
1152 if (src_y > ximage->height)
1154 h = ximage->height - src_y;
1156 if (w <= 0 || h <= 0)
1159 CGContextRef cgc = d->cgc;
1161 if (jwxyz_dumb_drawing_mode(dpy, d, gc, dest_x, dest_y, w, h))
1164 int bpl = ximage->bytes_per_line;
1165 int bpp = ximage->bits_per_pixel;
1166 int bsize = bpl * h;
1167 char *data = ximage->data;
1170 r.origin.x = wr.x + dest_x;
1171 r.origin.y = wr.y + wr.height - dest_y - (int)h;
1177 /* Take advantage of the fact that it's ok for (bpl != w * bpp)
1178 to create a CGImage from a sub-rectagle of the XImage.
1180 data += (src_y * bpl) + (src_x * 4);
1181 CGDataProviderRef prov =
1182 CGDataProviderCreateWithData (NULL, data, bsize, NULL);
1184 CGImageRef cgi = CGImageCreate (w, h,
1189 NULL, /* decode[] */
1190 NO, /* interpolate */
1191 kCGRenderingIntentDefault);
1192 CGDataProviderRelease (prov);
1193 //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
1194 CGContextDrawImage (cgc, r, cgi);
1195 CGImageRelease (cgi);
1197 } else { // (bpp == 1)
1199 /* To draw a 1bpp image, we use it as a mask and fill two rectangles.
1201 #### However, the bit order within a byte in a 1bpp XImage is
1202 the wrong way around from what Quartz expects, so first we
1203 have to copy the data to reverse it. Shit! Maybe it
1204 would be worthwhile to go through the hacks and #ifdef
1205 each one that diddles 1bpp XImage->data directly...
1207 Assert ((src_x % 8) == 0,
1208 "XPutImage with non-byte-aligned 1bpp X offset not implemented");
1210 data += (src_y * bpl) + (src_x / 8); // move to x,y within the data
1211 unsigned char *flipped = (unsigned char *) malloc (bsize);
1213 flipbits ((unsigned char *) data, flipped, bsize);
1215 CGDataProviderRef prov =
1216 CGDataProviderCreateWithData (NULL, flipped, bsize, NULL);
1217 CGImageRef mask = CGImageMaskCreate (w, h,
1220 NULL, /* decode[] */
1221 NO); /* interpolate */
1222 push_fg_gc (dpy, d, gc, YES);
1224 CGContextFillRect (cgc, r); // foreground color
1225 CGContextClipToMask (cgc, r, mask);
1226 set_color (dpy, cgc, gc->gcv.background, gc->depth, NO, YES);
1227 CGContextFillRect (cgc, r); // background color
1231 CGDataProviderRelease (prov);
1232 CGImageRelease (mask);
1235 invalidate_drawable_cache (d);
1242 GetSubImage (Display *dpy, Drawable d, int x, int y,
1243 unsigned int width, unsigned int height,
1244 unsigned long plane_mask, int format,
1245 XImage *image, int dest_x, int dest_y)
1247 const unsigned char *data = 0;
1248 size_t depth, ibpp, ibpl;
1249 convert_mode_t mode;
1251 Assert ((width < 65535), "improbably large width");
1252 Assert ((height < 65535), "improbably large height");
1253 Assert ((x < 65535 && x > -65535), "improbably large x");
1254 Assert ((y < 65535 && y > -65535), "improbably large y");
1256 CGContextRef cgc = d->cgc;
1259 depth = jwxyz_drawable_depth (d);
1260 mode = convert_mode_to_rgba (dpy->bitmap_info);
1261 ibpp = CGBitmapContextGetBitsPerPixel (cgc);
1262 ibpl = CGBitmapContextGetBytesPerRow (cgc);
1263 data = CGBitmapContextGetData (cgc);
1264 Assert (data, "CGBitmapContextGetData failed");
1267 // data points at (x,y) with ibpl rowstride. ignore x,y from now on.
1268 data += (y * ibpl) + (x * (ibpp/8));
1270 format = (depth == 1 ? XYPixmap : ZPixmap);
1272 int obpl = image->bytes_per_line;
1274 /* both PPC and Intel use word-ordered ARGB frame buffers, which
1275 means that on Intel it is BGRA when viewed by bytes (And BGR
1276 when using 24bpp packing).
1278 BUT! Intel-64 stores alpha at the other end! 32bit=RGBA, 64bit=ARGB.
1279 The NSAlphaFirstBitmapFormat bit in bitmapFormat seems to be the
1280 indicator of this latest kink.
1284 const unsigned char *iline = data;
1285 for (yy = 0; yy < height; yy++) {
1287 const unsigned char *iline2 = iline;
1288 for (xx = 0; xx < width; xx++) {
1290 iline2++; // ignore R or A or A or B
1291 iline2++; // ignore G or B or R or G
1292 unsigned char r = *iline2++; // use B or G or G or R
1293 if (ibpp == 32) iline2++; // ignore A or R or B or A
1295 XPutPixel (image, xx + dest_x, yy + dest_y, (r ? 1 : 0));
1300 const unsigned char *iline = data;
1301 unsigned char *oline = (unsigned char *) image->data + dest_y * obpl +
1304 mode = convert_mode_merge (mode,
1305 convert_mode_invert (
1306 convert_mode_to_rgba (dpy->bitmap_info)));
1308 for (yy = 0; yy < height; yy++) {
1310 convert_row ((uint32_t *)oline, iline, width, mode, ibpp);
1322 /* Returns a transformation matrix to do rotation as per the provided
1323 EXIF "Orientation" value.
1325 static CGAffineTransform
1326 exif_rotate (int rot, CGSize rect)
1328 CGAffineTransform trans = CGAffineTransformIdentity;
1330 case 2: // flip horizontal
1331 trans = CGAffineTransformMakeTranslation (rect.width, 0);
1332 trans = CGAffineTransformScale (trans, -1, 1);
1335 case 3: // rotate 180
1336 trans = CGAffineTransformMakeTranslation (rect.width, rect.height);
1337 trans = CGAffineTransformRotate (trans, M_PI);
1340 case 4: // flip vertical
1341 trans = CGAffineTransformMakeTranslation (0, rect.height);
1342 trans = CGAffineTransformScale (trans, 1, -1);
1345 case 5: // transpose (UL-to-LR axis)
1346 trans = CGAffineTransformMakeTranslation (rect.height, rect.width);
1347 trans = CGAffineTransformScale (trans, -1, 1);
1348 trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
1351 case 6: // rotate 90
1352 trans = CGAffineTransformMakeTranslation (0, rect.width);
1353 trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
1356 case 7: // transverse (UR-to-LL axis)
1357 trans = CGAffineTransformMakeScale (-1, 1);
1358 trans = CGAffineTransformRotate (trans, M_PI / 2);
1361 case 8: // rotate 270
1362 trans = CGAffineTransformMakeTranslation (rect.height, 0);
1363 trans = CGAffineTransformRotate (trans, M_PI / 2);
1375 jwxyz_draw_NSImage_or_CGImage (Display *dpy, Drawable d,
1376 Bool nsimg_p, void *img_arg,
1377 XRectangle *geom_ret, int exif_rotation)
1381 CGImageSourceRef cgsrc;
1382 # endif // USE_IPHONE
1385 CGContextRef cgc = d->cgc;
1389 NSImage *nsimg = (NSImage *) img_arg;
1390 imgr = [nsimg size];
1393 // convert the NSImage to a CGImage via the toll-free-bridging
1394 // of NSData and CFData...
1396 NSData *nsdata = [NSBitmapImageRep
1397 TIFFRepresentationOfImageRepsInArray:
1398 [nsimg representations]];
1399 CFDataRef cfdata = (CFDataRef) nsdata;
1400 cgsrc = CGImageSourceCreateWithData (cfdata, NULL);
1401 cgi = CGImageSourceCreateImageAtIndex (cgsrc, 0, NULL);
1402 # else // USE_IPHONE
1403 cgi = nsimg.CGImage;
1404 # endif // USE_IPHONE
1407 cgi = (CGImageRef) img_arg;
1408 imgr.width = CGImageGetWidth (cgi);
1409 imgr.height = CGImageGetHeight (cgi);
1412 Bool rot_p = (exif_rotation >= 5);
1415 imgr = NSMakeSize (imgr.height, imgr.width);
1417 XRectangle winr = d->frame;
1418 float rw = winr.width / imgr.width;
1419 float rh = winr.height / imgr.height;
1420 float r = (rw < rh ? rw : rh);
1422 /* If the window is a goofy aspect ratio, take a middle slice of
1423 the image instead. */
1424 if (winr.width > winr.height * 5 ||
1425 winr.width > winr.width * 5) {
1426 r *= (winr.width > winr.height
1427 ? winr.width / (double) winr.height
1428 : winr.height / (double) winr.width);
1429 // NSLog (@"weird aspect: scaling by %.1f\n", r);
1433 dst.size.width = imgr.width * r;
1434 dst.size.height = imgr.height * r;
1435 dst.origin.x = (winr.width - dst.size.width) / 2;
1436 dst.origin.y = (winr.height - dst.size.height) / 2;
1438 dst2.origin.x = dst2.origin.y = 0;
1440 dst2.size.width = dst.size.height;
1441 dst2.size.height = dst.size.width;
1443 dst2.size = dst.size;
1446 // Clear the part not covered by the image to background or black.
1448 if (d->type == WINDOW)
1449 XClearWindow (dpy, d);
1451 jwxyz_fill_rect (dpy, d, 0, 0, 0, winr.width, winr.height,
1452 jwxyz_drawable_depth (d) == 1 ? 0 : BlackPixel(dpy,0));
1455 CGAffineTransform trans =
1456 exif_rotate (exif_rotation, rot_p ? dst2.size : dst.size);
1458 CGContextSaveGState (cgc);
1459 CGContextConcatCTM (cgc,
1460 CGAffineTransformMakeTranslation (dst.origin.x,
1462 CGContextConcatCTM (cgc, trans);
1463 //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
1464 CGContextDrawImage (cgc, dst2, cgi);
1465 CGContextRestoreGState (cgc);
1470 CGImageRelease (cgi);
1472 # endif // USE_IPHONE
1475 geom_ret->x = dst.origin.x;
1476 geom_ret->y = dst.origin.y;
1477 geom_ret->width = dst.size.width;
1478 geom_ret->height = dst.size.height;
1481 invalidate_drawable_cache (d);
1487 XCreatePixmap (Display *dpy, Drawable d,
1488 unsigned int width, unsigned int height, unsigned int depth)
1491 char *data = (char *) malloc (width * height * 4);
1492 if (! data) return 0;
1494 Pixmap p = (Pixmap) calloc (1, sizeof(*p));
1496 p->frame.width = width;
1497 p->frame.height = height;
1498 p->pixmap.depth = depth;
1499 p->pixmap.cgc_buffer = data;
1501 /* Quartz doesn't have a 1bpp image type.
1502 Used to use 8bpp gray images instead of 1bpp, but some Mac video cards
1503 don't support that! So we always use 32bpp, regardless of depth. */
1505 p->cgc = CGBitmapContextCreate (data, width, height,
1506 8, /* bits per component */
1507 width * 4, /* bpl */
1510 Assert (p->cgc, "could not create CGBitmapContext");
1516 XFreePixmap (Display *d, Pixmap p)
1518 Assert (p && p->type == PIXMAP, "not a pixmap");
1519 invalidate_drawable_cache (p);
1520 CGContextRelease (p->cgc);
1521 if (p->pixmap.cgc_buffer)
1522 free (p->pixmap.cgc_buffer);
1529 copy_pixmap (Display *dpy, Pixmap p)
1532 Assert (p->type == PIXMAP, "not a pixmap");
1538 unsigned int width, height, border_width, depth;
1539 if (XGetGeometry (dpy, p, &root,
1540 &x, &y, &width, &height, &border_width, &depth)) {
1542 gcv.function = GXcopy;
1543 GC gc = XCreateGC (dpy, p, GCFunction, &gcv);
1545 p2 = XCreatePixmap (dpy, p, width, height, depth);
1547 XCopyArea (dpy, p, p2, gc, 0, 0, width, height, 0, 0);
1552 Assert (p2, "could not copy pixmap");
1558 // Returns the verbose Unicode name of this character, like "agrave" or
1559 // "daggerdouble". Used by fontglide debugMetrics.
1562 jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
1565 NSFont *nsfont = (NSFont *) jwxyz_native_font (fid);
1567 CTFontCreateWithName ((CFStringRef) [nsfont fontName],
1570 Assert (ctfont, "no CTFontRef for UIFont");
1573 if (CTFontGetGlyphsForCharacters (ctfont, (UniChar *) &uc, &cgglyph, 1)) {
1574 CGFontRef cgfont = CTFontCopyGraphicsFont (ctfont, 0);
1575 NSString *name = (NSString *) CGFontCopyGlyphNameForGlyph(cgfont, cgglyph);
1576 ret = (name ? strdup ([name UTF8String]) : 0);
1577 CGFontRelease (cgfont);
1587 draw_string (Display *dpy, Drawable d, GC gc, int x, int y,
1588 const char *str, size_t len, int utf8_p)
1590 NSString *nsstr = nsstring_from (str, len, utf8_p);
1592 if (! nsstr) return 1;
1594 XRectangle wr = d->frame;
1595 CGContextRef cgc = d->cgc;
1597 unsigned long argb = gc->gcv.foreground;
1598 if (gc->depth == 1) argb = (argb ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
1600 query_color_float (dpy, argb, rgba);
1601 NSColor *fg = [NSColor colorWithDeviceRed:rgba[0]
1606 if (!gc->gcv.font) {
1607 Assert (0, "no font");
1611 /* This crashes on iOS 5.1 because NSForegroundColorAttributeName,
1612 NSFontAttributeName, and NSAttributedString are only present on iOS 6
1613 and later. We could resurrect the Quartz code from v5.29 and do a
1614 runtime conditional on that, but that would be a pain in the ass.
1615 Probably time to just make iOS 6 a requirement.
1618 NSDictionary *attr =
1619 [NSDictionary dictionaryWithObjectsAndKeys:
1620 (NSFont *) jwxyz_native_font (gc->gcv.font), NSFontAttributeName,
1621 fg, NSForegroundColorAttributeName,
1624 // Don't understand why we have to do both set_color and
1625 // NSForegroundColorAttributeName, but we do.
1627 set_color (dpy, cgc, argb, 32, NO, YES);
1629 NSAttributedString *astr = [[NSAttributedString alloc]
1630 initWithString:nsstr
1632 CTLineRef dl = CTLineCreateWithAttributedString (
1633 (__bridge CFAttributedStringRef) astr);
1635 // Not sure why this is necessary, but xoff is positive when the first
1636 // character on the line has a negative lbearing. Without this, the
1637 // string is rendered with the first ink at 0 instead of at lbearing.
1638 // I have not seen xoff be negative, so I'm not sure if that can happen.
1640 // Test case: "Combining Double Tilde" U+0360 (\315\240) followed by
1643 CGFloat xoff = CTLineGetOffsetForStringIndex (dl, 0, NULL);
1644 Assert (xoff >= 0, "unexpected CTLineOffset");
1647 CGContextSetTextPosition (cgc,
1649 wr.y + wr.height - y);
1650 CGContextSetShouldAntialias (cgc, gc->gcv.antialias_p);
1652 CTLineDraw (dl, cgc);
1656 invalidate_drawable_cache (d);
1662 SetClipMask (Display *dpy, GC gc, Pixmap m)
1664 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
1666 if (gc->gcv.clip_mask) {
1667 XFreePixmap (dpy, gc->gcv.clip_mask);
1668 CGImageRelease (gc->clip_mask);
1671 gc->gcv.clip_mask = copy_pixmap (dpy, m);
1672 if (gc->gcv.clip_mask)
1674 CGBitmapContextCreateImage (gc->gcv.clip_mask->cgc);
1682 SetClipOrigin (Display *dpy, GC gc, int x, int y)
1684 gc->gcv.clip_x_origin = x;
1685 gc->gcv.clip_y_origin = y;
1690 const struct jwxyz_vtbl quartz_vtbl = {
1693 display_sources_data,
1702 jwxyz_quartz_copy_area,
1717 #endif // JWXYZ_QUARTZ -- entire file