# import <UIKit/UIKit.h>
# import <UIKit/UIScreen.h>
# import <QuartzCore/QuartzCore.h>
+# import <CoreText/CTFont.h>
# define NSView UIView
# define NSRect CGRect
# define NSPoint CGPoint
#import "jwxyz.h"
#import "jwxyz-timers.h"
+#import "yarandom.h"
+
+#ifdef USE_IPHONE
+# define USE_BACKBUFFER /* must be in sync with XScreenSaverView.h */
+#endif
#undef Assert
-#define Assert(C,S) do { \
- if (!(C)) { \
- NSLog(@"%s",S); \
- abort(); \
- }} while(0)
+#define Assert(C,S) do { if (!(C)) jwxyz_abort ("%s",(S)); } while(0)
# undef MAX
# undef MIN
};
+/* Instead of calling abort(), throw a real exception, so that
+ XScreenSaverView can catch it and display a dialog.
+ */
+void
+jwxyz_abort (const char *fmt, ...)
+{
+ char s[10240];
+ if (!fmt || !*fmt)
+ strcpy (s, "abort");
+ else
+ {
+ va_list args;
+ va_start (args, fmt);
+ vsprintf (s, fmt, args);
+ va_end (args);
+ }
+ [[NSException exceptionWithName: NSInternalInconsistencyException
+ reason: [NSString stringWithCString: s
+ encoding:NSUTF8StringEncoding]
+ userInfo: nil]
+ raise];
+ abort(); // not reached
+}
+
+
Display *
jwxyz_make_display (void *nsview_arg, void *cgc_arg)
{
CGContextRef cgc = (CGContextRef) cgc_arg;
NSView *view = (NSView *) nsview_arg;
- if (!view) abort();
+ Assert (view, "no view");
+ if (!view) return 0;
Display *d = (Display *) calloc (1, sizeof(*d));
d->screen = (Screen *) calloc (1, sizeof(Screen));
void *
jwxyz_window_view (Window w)
{
- Assert (w->type == WINDOW, "not a window");
+ Assert (w && w->type == WINDOW, "not a window");
return w->window.view;
}
void *cgc_arg)
{
CGContextRef cgc = (CGContextRef) cgc_arg;
- Assert (w->type == WINDOW, "not a window");
+ Assert (w && w->type == WINDOW, "not a window");
w->frame.origin.x = new_x;
w->frame.origin.y = new_y;
w->frame.size.width = new_width;
void
jwxyz_mouse_moved (Display *dpy, Window w, int x, int y)
{
- Assert (w->type == WINDOW, "not a window");
+ Assert (w && w->type == WINDOW, "not a window");
w->window.last_mouse_x = x;
w->window.last_mouse_y = y;
}
case GXxor: CGContextSetBlendMode (cgc, kCGBlendModeDifference); break;
case GXor: CGContextSetBlendMode (cgc, kCGBlendModeLighten); break;
case GXand: CGContextSetBlendMode (cgc, kCGBlendModeDarken); break;
- default: abort(); break;
+ default: Assert(0, "unknown gcv function"); break;
}
if (gc->gcv.clip_mask)
*/
#define XDRAWPOINTS_IMAGES
+/* Update, 2012: Kurt Revis <krevis@snoize.com> points out that diddling
+ the bitmap data directly is faster. This only works on Pixmaps, though,
+ not Windows. (Fortunately, on iOS, the Window is really a Pixmap.)
+ */
+#define XDRAWPOINTS_CGDATA
+
int
XDrawPoints (Display *dpy, Drawable d, GC gc,
XPoint *points, int count, int mode)
push_fg_gc (d, gc, YES);
-# ifdef XDRAWPOINTS_IMAGES
+# ifdef XDRAWPOINTS_CGDATA
- unsigned int argb = gc->gcv.foreground;
- validate_pixel (argb, gc->depth, gc->gcv.alpha_allowed_p);
- if (gc->depth == 1)
- argb = (gc->gcv.foreground ? WhitePixel(0,0) : BlackPixel(0,0));
-
- CGDataProviderRef prov = CGDataProviderCreateWithData (NULL, &argb, 4, NULL);
- CGImageRef cgi = CGImageCreate (1, 1,
- 8, 32, 4,
- dpy->colorspace,
- /* Host-ordered, since we're using the
- address of an int as the color data. */
- (kCGImageAlphaNoneSkipFirst |
- kCGBitmapByteOrder32Host),
- prov,
- NULL, /* decode[] */
- NO, /* interpolate */
- kCGRenderingIntentDefault);
- CGDataProviderRelease (prov);
+# ifdef USE_BACKBUFFER
+ if (1) // Because of the backbuffer, all iPhone Windows work like Pixmaps.
+# else
+ if (d->type == PIXMAP)
+# endif
+ {
+ CGContextRef cgc = d->cgc;
+ void *data = CGBitmapContextGetData (cgc);
+ size_t bpr = CGBitmapContextGetBytesPerRow (cgc);
+ size_t w = CGBitmapContextGetWidth (cgc);
+ size_t h = CGBitmapContextGetHeight (cgc);
- CGContextRef cgc = d->cgc;
- CGRect rect;
- rect.size.width = rect.size.height = 1;
- for (i = 0; i < count; i++) {
- if (i > 0 && mode == CoordModePrevious) {
- rect.origin.x += points->x;
- rect.origin.x -= points->y;
+ Assert (data, "no bitmap data in Drawable");
+
+ unsigned int argb = gc->gcv.foreground;
+ validate_pixel (argb, gc->depth, gc->gcv.alpha_allowed_p);
+ if (gc->depth == 1)
+ argb = (gc->gcv.foreground ? WhitePixel(0,0) : BlackPixel(0,0));
+
+ CGFloat x0 = wr.origin.x;
+ CGFloat y0 = wr.origin.y + wr.size.height;
+
+ // It's uglier, but faster, to hoist the conditional out of the loop.
+ if (mode == CoordModePrevious) {
+ CGFloat x = x0, y = y0;
+ for (i = 0; i < count; i++, points++) {
+ x += points->x;
+ y -= points->y;
+
+ if (0 <= x && x < w && 0 <= y && y < h) {
+ unsigned int *p = (unsigned int *)
+ ((char *) data + (size_t) y * bpr + (size_t) x * 4);
+ *p = argb;
+ }
+ }
} else {
- rect.origin.x = wr.origin.x + points->x;
- rect.origin.y = wr.origin.y + wr.size.height - points->y - 1;
+ for (i = 0; i < count; i++, points++) {
+ CGFloat x = x0 + points->x;
+ CGFloat y = y0 - points->y;
+
+ if (0 <= x && x < w && 0 <= y && y < h) {
+ unsigned int *p = (unsigned int *)
+ ((char *) data + (size_t) y * bpr + (size_t) x * 4);
+ *p = argb;
+ }
+ }
}
- //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
- CGContextDrawImage (cgc, rect, cgi);
- points++;
- }
+ } else /* d->type == WINDOW */
+
+# endif /* XDRAWPOINTS_CGDATA */
+ {
+
+# ifdef XDRAWPOINTS_IMAGES
- CGImageRelease (cgi);
+ unsigned int argb = gc->gcv.foreground;
+ validate_pixel (argb, gc->depth, gc->gcv.alpha_allowed_p);
+ if (gc->depth == 1)
+ argb = (gc->gcv.foreground ? WhitePixel(0,0) : BlackPixel(0,0));
+
+ CGDataProviderRef prov = CGDataProviderCreateWithData (NULL, &argb, 4,
+ NULL);
+ CGImageRef cgi = CGImageCreate (1, 1,
+ 8, 32, 4,
+ dpy->colorspace,
+ /* Host-ordered, since we're using the
+ address of an int as the color data. */
+ (kCGImageAlphaNoneSkipFirst |
+ kCGBitmapByteOrder32Host),
+ prov,
+ NULL, /* decode[] */
+ NO, /* interpolate */
+ kCGRenderingIntentDefault);
+ CGDataProviderRelease (prov);
+
+ CGContextRef cgc = d->cgc;
+ CGRect rect;
+ rect.size.width = rect.size.height = 1;
+ for (i = 0; i < count; i++) {
+ if (i > 0 && mode == CoordModePrevious) {
+ rect.origin.x += points->x;
+ rect.origin.x -= points->y;
+ } else {
+ rect.origin.x = wr.origin.x + points->x;
+ rect.origin.y = wr.origin.y + wr.size.height - points->y - 1;
+ }
+
+ //Assert(CGImageGetColorSpace (cgi) == dpy->colorspace,"bad colorspace");
+ CGContextDrawImage (cgc, rect, cgi);
+ points++;
+ }
+
+ CGImageRelease (cgi);
# else /* ! XDRAWPOINTS_IMAGES */
- CGRect *rects = (CGRect *) malloc (count * sizeof(CGRect));
- CGRect *r = rects;
+ CGRect *rects = (CGRect *) malloc (count * sizeof(CGRect));
+ CGRect *r = rects;
- for (i = 0; i < count; i++) {
- r->size.width = r->size.height = 1;
- if (i > 0 && mode == CoordModePrevious) {
- r->origin.x = r[-1].origin.x + points->x;
- r->origin.y = r[-1].origin.x - points->y;
- } else {
- r->origin.x = wr.origin.x + points->x;
- r->origin.y = wr.origin.y + wr.size.height - points->y;
+ for (i = 0; i < count; i++) {
+ r->size.width = r->size.height = 1;
+ if (i > 0 && mode == CoordModePrevious) {
+ r->origin.x = r[-1].origin.x + points->x;
+ r->origin.y = r[-1].origin.x - points->y;
+ } else {
+ r->origin.x = wr.origin.x + points->x;
+ r->origin.y = wr.origin.y + wr.size.height - points->y;
+ }
+ points++;
+ r++;
}
- points++;
- r++;
- }
- CGContextFillRects (d->cgc, rects, count);
- free (rects);
+ CGContextFillRects (d->cgc, rects, count);
+ free (rects);
# endif /* ! XDRAWPOINTS_IMAGES */
+ }
pop_gc (d, gc);
invalidate_drawable_cache (d);
BOOL free_cgi_p = NO;
-#ifndef USE_IPHONE
+#ifndef USE_BACKBUFFER
// Because of the backbuffer, all iPhone Windows work like Pixmaps.
if (src->type == PIXMAP)
# endif
if (src->type == PIXMAP && src->pixmap.depth == 1)
mask_p = YES;
-# ifndef USE_IPHONE
+# ifndef USE_BACKBUFFER
} else { /* (src->type == WINDOW) */
NSRect nsfrom; // NSRect != CGRect on 10.4
CGDataProviderRelease (prov);
}
-# endif // !USE_IPHONE
+# endif // !USE_BACKBUFFER
}
CGContextRef cgc = dst->cgc;
} else {
// No cgi means src == dst, and both are Windows.
-# ifdef USE_IPHONE
- abort(); // No NSCopyBits on iOS, but shouldn't be reached anyway.
-# else // !USE_IPHONE
+# ifdef USE_BACKBUFFER
+ Assert (0, "NSCopyBits unimplemented"); // shouldn't be reached anyway
+ return 0;
+# else // !USE_BACKBUFFER
NSRect nsfrom;
nsfrom.origin.x = src_rect.origin.x; // NSRect != CGRect on 10.4
nsfrom.origin.y = src_rect.origin.y;
nsto.x = dst_rect.origin.x;
nsto.y = dst_rect.origin.y;
NSCopyBits (0, nsfrom, nsto);
-# endif // !USE_IPHONE
+# endif // !USE_BACKBUFFER
}
pop_gc (dst, gc);
int
XClearWindow (Display *dpy, Window win)
{
- Assert (win->type == WINDOW, "not a window");
+ Assert (win && win->type == WINDOW, "not a window");
CGRect wr = win->frame;
return XClearArea (dpy, win, 0, 0, wr.size.width, wr.size.height, 0);
}
int
XSetWindowBackground (Display *dpy, Window w, unsigned long pixel)
{
- Assert (w->type == WINDOW, "not a window");
+ Assert (w && w->type == WINDOW, "not a window");
validate_pixel (pixel, 32, NO);
w->window.background = pixel;
return 0;
int
XClearArea (Display *dpy, Window win, int x, int y, int w, int h, Bool exp)
{
- Assert (win->type == WINDOW, "not a window");
+ Assert (win && win->type == WINDOW, "not a window");
CGContextRef cgc = win->cgc;
set_color (cgc, win->window.background, 32, NO, YES);
draw_rect (dpy, win, 0, x, y, w, h, NO, YES);
set_gcv (GC gc, XGCValues *from, unsigned long mask)
{
if (! mask) return;
- if (! gc) abort();
- if (! from) abort();
+ Assert (gc && from, "no gc");
+ if (!gc || !from) return;
if (mask & GCFunction) gc->gcv.function = from->function;
if (mask & GCForeground) gc->gcv.foreground = from->foreground;
if (mask & GCBackground) validate_pixel (from->background, gc->depth,
gc->gcv.alpha_allowed_p);
- if (mask & GCLineStyle) abort();
- if (mask & GCPlaneMask) abort();
- if (mask & GCFillStyle) abort();
- if (mask & GCTile) abort();
- if (mask & GCStipple) abort();
- if (mask & GCTileStipXOrigin) abort();
- if (mask & GCTileStipYOrigin) abort();
- if (mask & GCGraphicsExposures) abort();
- if (mask & GCDashOffset) abort();
- if (mask & GCDashList) abort();
- if (mask & GCArcMode) abort();
+ Assert ((! (mask & (GCLineStyle |
+ GCPlaneMask |
+ GCFillStyle |
+ GCTile |
+ GCStipple |
+ GCTileStipXOrigin |
+ GCTileStipYOrigin |
+ GCGraphicsExposures |
+ GCDashOffset |
+ GCDashList |
+ GCArcMode))),
+ "unimplemented gcvalues mask");
}
Status
XGetWindowAttributes (Display *dpy, Window w, XWindowAttributes *xgwa)
{
- Assert (w->type == WINDOW, "not a window");
+ Assert (w && w->type == WINDOW, "not a window");
memset (xgwa, 0, sizeof(*xgwa));
xgwa->x = w->frame.origin.x;
xgwa->y = w->frame.origin.y;
ximage->f.put_pixel = ximage_putpixel_32;
ximage->f.get_pixel = ximage_getpixel_32;
} else {
- abort();
+ Assert (0, "unknown depth");
}
return 1;
}
{
const unsigned char *data = 0;
int depth, ibpp, ibpl, alpha_first_p;
-# ifndef USE_IPHONE
+# ifndef USE_BACKBUFFER
NSBitmapImageRep *bm = 0;
# endif
CGContextRef cgc = d->cgc;
-#ifndef USE_IPHONE
+#ifndef USE_BACKBUFFER
// Because of the backbuffer, all iPhone Windows work like Pixmaps.
if (d->type == PIXMAP)
# endif
data = CGBitmapContextGetData (cgc);
Assert (data, "CGBitmapContextGetData failed");
-# ifndef USE_IPHONE
+# ifndef USE_BACKBUFFER
} else { /* (d->type == WINDOW) */
// get the bits (desired sub-rectangle) out of the NSView
ibpl = [bm bytesPerRow];
data = [bm bitmapData];
Assert (data, "NSBitmapImageRep initWithFocusedViewRect failed");
-# endif // !USE_IPHONE
+# endif // !USE_BACKBUFFER
}
// data points at (x,y) with ibpl rowstride. ignore x,y from now on.
}
}
-# ifndef USE_IPHONE
+# ifndef USE_BACKBUFFER
if (bm) [bm release];
# endif
int
XFreePixmap (Display *d, Pixmap p)
{
- Assert (p->type == PIXMAP, "not a pixmap");
+ Assert (p && p->type == PIXMAP, "not a pixmap");
invalidate_drawable_cache (p);
CGContextRelease (p->cgc);
if (p->pixmap.cgc_buffer)
query_font (Font fid)
{
if (!fid || !fid->nsfont) {
- NSLog(@"no NSFont in fid");
- abort();
+ Assert (0, "no NSFont in fid");
+ return;
}
if (![fid->nsfont fontName]) {
- NSLog(@"broken NSFont in fid");
- abort();
+ Assert(0, @"broken NSFont in fid");
+ return;
}
int first = 32;
# ifndef USE_IPHONE
NSBezierPath *bpath = [NSBezierPath bezierPath];
-# endif
+# 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];
NSString *nsstr = [NSString stringWithCString:(char *) str
encoding:NSISOLatin1StringEncoding];
- NSPoint advancement;
- NSRect bbox;
+ NSPoint advancement = { 0, };
+ NSRect bbox = {{ 0, }, };
# ifndef USE_IPHONE
# else // USE_IPHONE
- UIFont *ff = fid->nsfont;
- CGSize size = [nsstr sizeWithFont:ff];
-
- /* sizeWithFont gives us a character's "width" and "height".
- There is no way to get a character's "lbearing", "rbearing",
- or "descent". We do have the font's overall "descent", though.
+ /* There is no way to get "lbearing", "rbearing" or "descent" out of
+ NSFont. 'sizeWithFont' gives us "width" and "height" only.
+ Likewise, 'drawAtPoint' (to an offscreen CGContext) gives us the
+ width of the character and the ascent of the font.
- drawAtPoint (to an offscreen CGContext) returns "width" and the
- "ascent" of the font (not of the glyph, I think) so that doesn't
- help.
-
- CGFontGetGlyphBBoxes might help (if it actually returns a bounding
- box and not just the ascent/width again, which I don't know) but
- we can't use it anyway because there is no way to map a unichar to
- a CGGlyph.
-
- Fuck you sideways, Apple.
+ Maybe we could use CGFontGetGlyphBBoxes() and avoid linking in
+ the CoreText library, but there's no non-CoreText way to turn a
+ unichar into a CGGlyph.
*/
-
- bbox.origin.x = 0;
- bbox.origin.y = [ff descender];
- bbox.size.width = size.width;
- bbox.size.height = size.height;
-
- advancement.x = size.width;
- advancement.y = 0;
-
+ 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
(int) advancement.x, (int) advancement.y);
#endif
}
+
+# ifdef USE_IPHONE
+ CFRelease (ctfont);
+# endif
}
// We should never return NULL for XLFD fonts.
if (!fid->nsfont) {
- abort();
+ Assert (0, "no font");
+ return 0;
}
CFRetain (fid->nsfont); // needed for garbage collection?
int *root_x_ret, int *root_y_ret,
int *win_x_ret, int *win_y_ret, unsigned int *mask_ret)
{
- Assert (w->type == WINDOW, "not a window");
+ Assert (w && w->type == WINDOW, "not a window");
# ifdef USE_IPHONE
int x = w->window.last_mouse_x;
int *dest_x_ret, int *dest_y_ret,
Window *child_ret)
{
- Assert (w->type == WINDOW, "not a window");
+ Assert (w && w->type == WINDOW, "not a window");
# ifdef USE_IPHONE