summaryrefslogtreecommitdiffstats
path: root/jwxyz/jwxyz-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'jwxyz/jwxyz-common.c')
-rw-r--r--jwxyz/jwxyz-common.c1828
1 files changed, 1828 insertions, 0 deletions
diff --git a/jwxyz/jwxyz-common.c b/jwxyz/jwxyz-common.c
new file mode 100644
index 0000000..1a31ae3
--- /dev/null
+++ b/jwxyz/jwxyz-common.c
@@ -0,0 +1,1828 @@
+/* xscreensaver, Copyright (c) 1991-2018 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* JWXYZ Is Not Xlib.
+
+ But it's a bunch of function definitions that bear some resemblance to
+ Xlib and that do Cocoa-ish or OpenGL-ish things that bear some resemblance
+ to the things that Xlib might have done.
+
+ This is the version of jwxyz for Android. The version used by MacOS
+ and iOS is in jwxyz.m.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef HAVE_JWXYZ /* whole file */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "jwxyzI.h"
+#include "pow2.h"
+#include "utf8wc.h"
+#include "xft.h"
+
+/* There's only one Window for a given jwxyz_Display. */
+#define assert_window(dpy, w) \
+ Assert (w == RootWindow (dpy, 0), "not a window")
+
+#define VTBL JWXYZ_VTBL(dpy)
+
+struct jwxyz_Font {
+ Display *dpy;
+ void *native_font;
+ int refcount; // for deciding when to release the native font
+ int ascent, descent;
+ char *xa_font;
+
+ // In X11, "Font" is just an ID, and "XFontStruct" contains the metrics.
+ // But we need the metrics on both of them, so they go here.
+ XFontStruct metrics;
+};
+
+struct jwxyz_XFontSet {
+ XFontStruct *font;
+};
+
+
+void
+Log (const char *fmt, ...)
+{
+ va_list args;
+ va_start (args, fmt);
+ Logv (fmt, args);
+ va_end (args);
+}
+
+
+int
+XDisplayWidth (Display *dpy, int screen)
+{
+ return jwxyz_frame (XRootWindow (dpy, 0))->width;
+}
+
+int
+XDisplayHeight (Display *dpy, int screen)
+{
+ return jwxyz_frame (XRootWindow (dpy, 0))->height;
+}
+
+
+/* XLFDs use dots per inch, but Xlib uses millimeters. Go figure. */
+static int
+size_mm (Display *dpy, unsigned size)
+{
+ /* ((mm / inch) / (points / inch)) * dots / (dots / points) */
+ return (25.4 / 72) * size / jwxyz_scale (XRootWindow (dpy,0)) + 0.5;
+}
+
+int
+XDisplayWidthMM (Display *dpy, int screen)
+{
+ return size_mm (dpy, XDisplayWidth (dpy, screen));
+}
+
+int
+XDisplayHeightMM (Display *dpy, int screen)
+{
+ return size_mm (dpy, XDisplayHeight (dpy, screen));
+}
+
+unsigned long
+XBlackPixelOfScreen(Screen *screen)
+{
+ return DefaultVisualOfScreen (screen)->alpha_mask;
+}
+
+unsigned long
+XWhitePixelOfScreen(Screen *screen)
+{
+ Visual *v = DefaultVisualOfScreen (screen);
+ return (v->red_mask | v->green_mask |v->blue_mask | v->alpha_mask);
+}
+
+unsigned long
+XCellsOfScreen(Screen *screen)
+{
+ Visual *v = DefaultVisualOfScreen (screen);
+ return (v->red_mask | v->green_mask |v->blue_mask);
+}
+
+void
+jwxyz_validate_pixel (Display *dpy, unsigned long pixel, unsigned int depth,
+ Bool alpha_allowed_p)
+{
+ Assert (depth == 1 || depth == visual_depth(NULL, NULL),
+ "invalid depth: %d", depth);
+
+ if (depth == 1)
+ Assert ((pixel == 0 || pixel == 1), "bogus mono pixel: 0x%08X", pixel);
+ else if (!alpha_allowed_p)
+ Assert (((pixel & BlackPixel(dpy,0)) == BlackPixel(dpy,0)),
+ "bogus color pixel: 0x%08X", pixel);
+}
+
+
+int
+XDrawPoint (Display *dpy, Drawable d, GC gc, int x, int y)
+{
+ XPoint p;
+ p.x = x;
+ p.y = y;
+ return XDrawPoints (dpy, d, gc, &p, 1, CoordModeOrigin);
+}
+
+
+Bool
+jwxyz_dumb_drawing_mode(Display *dpy, Drawable d, GC gc,
+ int x, int y, unsigned width, unsigned height)
+{
+ XGCValues *gcv = VTBL->gc_gcv (gc);
+
+ if (gcv->function == GXset || gcv->function == GXclear) {
+ // "set" and "clear" are dumb drawing modes that ignore the source
+ // bits and just draw solid rectangles.
+ unsigned depth = VTBL->gc_depth (gc);
+ jwxyz_fill_rect (dpy, d, 0, x, y, width, height,
+ (gcv->function == GXset
+ ? (depth == 1 ? 1 : WhitePixel(dpy,0))
+ : (depth == 1 ? 0 : BlackPixel(dpy,0))));
+ return True;
+ }
+
+ return False;
+}
+
+
+int
+XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc,
+ int src_x, int src_y,
+ unsigned int width, unsigned int height,
+ int dst_x, int dst_y)
+{
+ Assert (gc, "no GC");
+ Assert ((width < 65535), "improbably large width");
+ Assert ((height < 65535), "improbably large height");
+ Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x");
+ Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y");
+ Assert ((dst_x < 65535 && dst_x > -65535), "improbably large dst_x");
+ Assert ((dst_y < 65535 && dst_y > -65535), "improbably large dst_y");
+
+ if (width == 0 || height == 0)
+ return 0;
+
+ if (jwxyz_dumb_drawing_mode (dpy, dst, gc, dst_x, dst_y, width, height))
+ return 0;
+
+ XRectangle src_frame, dst_frame; // Sizes and origins of the two drawables
+ Bool clipped = False; // Whether we did any clipping of the rects.
+
+ src_frame = *jwxyz_frame (src);
+ dst_frame = *jwxyz_frame (dst);
+
+ // Initialize src_rect...
+ //
+ src_x += src_frame.x;
+ src_y += src_frame.y;
+ if (src_y < -65535) Assert(0, "src.origin.y went nuts");
+
+ // Initialize dst_rect...
+ //
+ dst_x += dst_frame.x;
+ dst_y += dst_frame.y;
+ if (dst_y < -65535) Assert(0, "dst.origin.y went nuts");
+
+ // Use signed width and height for this...
+ int width0 = width, height0 = height;
+
+ // Clip rects to frames...
+ //
+
+# define CLIP(THIS,THAT,VAL,SIZE) do { \
+ int off = THIS##_##VAL; \
+ if (off < 0) { \
+ clipped = True; \
+ SIZE##0 += off; \
+ THIS##_##VAL -= off; \
+ THAT##_##VAL -= off; \
+ } \
+ off = (( THIS##_##VAL + SIZE##0) - \
+ (THIS##_frame.VAL + THIS##_frame.SIZE)); \
+ if (off > 0) { \
+ clipped = True; \
+ SIZE##0 -= off; \
+ }} while(0)
+
+ CLIP (dst, src, x, width);
+ CLIP (dst, src, y, height);
+
+ // Not actually the original dst_rect, just the one before it's clipped to
+ // the src_frame.
+ int orig_dst_x = dst_x;
+ int orig_dst_y = dst_y;
+ int orig_width = width0;
+ int orig_height = height0;
+
+ if (width0 <= 0 || height0 <= 0)
+ return 0;
+
+ CLIP (src, dst, x, width);
+ CLIP (src, dst, y, height);
+# undef CLIP
+
+ // Sort-of-special case where no pixels can be grabbed from the source,
+ // and the whole destination is filled with the background color.
+ if (width0 <= 0 || height0 <= 0) {
+ width0 = 0;
+ height0 = 0;
+ } else {
+ VTBL->copy_area (dpy, src, dst, gc,
+ src_x, src_y, width0, height0, dst_x, dst_y);
+ }
+
+ // If either the src or dst rects did not lie within their drawables, then
+ // we have adjusted both the src and dst rects to account for the clipping;
+ // that means we need to clear to the background, so that clipped bits end
+ // up in the bg color instead of simply not being copied.
+ //
+ // This has to happen after the copy, because if it happens before, the
+ // cleared area will get grabbed if it overlaps with the source rectangle.
+ //
+ if (clipped && dst == XRootWindow (dpy,0)) {
+ int dst_x0 = dst_x;
+ int dst_y0 = dst_y;
+
+ Assert (orig_dst_x >= 0 &&
+ orig_dst_x + orig_width <= dst_frame.width &&
+ orig_dst_y >= 0 &&
+ orig_dst_y + orig_height <= dst_frame.height,
+ "wrong dimensions");
+
+ XRectangle rects[4];
+ XRectangle *rects_end = rects;
+
+ if (orig_dst_y < dst_y0) {
+ rects_end->x = orig_dst_x;
+ rects_end->y = orig_dst_y;
+ rects_end->width = orig_width;
+ rects_end->height = dst_y0 - orig_dst_y;
+ ++rects_end;
+ }
+
+ if (orig_dst_y + orig_height > dst_y0 + height0) {
+ rects_end->x = orig_dst_x;
+ rects_end->y = dst_y0 + height0;
+ rects_end->width = orig_width;
+ rects_end->height = orig_dst_y + orig_height - dst_y0 - height0;
+ ++rects_end;
+ }
+
+ if (orig_dst_x < dst_x0) {
+ rects_end->x = orig_dst_x;
+ rects_end->y = dst_y0;
+ rects_end->width = dst_x0 - orig_dst_x;
+ rects_end->height = height0;
+ ++rects_end;
+ }
+
+ if (dst_x0 + width0 < orig_dst_x + orig_width) {
+ rects_end->x = dst_x0 + width0;
+ rects_end->y = dst_y0;
+ rects_end->width = orig_dst_x + orig_width - dst_x0 - width0;
+ rects_end->height = height0;
+ ++rects_end;
+ }
+
+ XGCValues *gcv = VTBL->gc_gcv (gc);
+ int old_function = gcv->function;
+ gcv->function = GXcopy;
+ VTBL->fill_rects (dpy, dst, gc, rects, rects_end - rects,
+ *VTBL->window_background (dpy));
+ gcv->function = old_function;
+ }
+
+ return 0;
+}
+
+
+void
+jwxyz_blit (const void *src_data, ptrdiff_t src_pitch,
+ unsigned src_x, unsigned src_y,
+ void *dst_data, ptrdiff_t dst_pitch,
+ unsigned dst_x, unsigned dst_y,
+ unsigned width, unsigned height)
+{
+ Bool same = src_data == dst_data;
+ src_data = SEEK_XY (src_data, src_pitch, src_x, src_y);
+ dst_data = SEEK_XY (dst_data, dst_pitch, dst_x, dst_y);
+
+ size_t bytes = width * 4;
+
+ if (same && dst_y > src_y) {
+ // Copy upwards if the areas might overlap.
+ src_data += src_pitch * (height - 1);
+ dst_data += dst_pitch * (height - 1);
+ src_pitch = -src_pitch;
+ dst_pitch = -dst_pitch;
+ }
+
+ while (height) {
+ // memcpy is an alias for memmove on macOS.
+ memmove (dst_data, src_data, bytes);
+ src_data += src_pitch;
+ dst_data += dst_pitch;
+ --height;
+ }
+}
+
+
+int
+XCopyPlane (Display *dpy, Drawable src, Drawable dest, GC gc,
+ int src_x, int src_y,
+ unsigned width, int height,
+ int dest_x, int dest_y, unsigned long plane)
+{
+ Assert ((VTBL->gc_depth (gc) == 1 || plane == 1), "hairy plane mask!");
+
+ // This isn't right: XCopyPlane() is supposed to map 1/0 to fg/bg,
+ // not to white/black.
+ return XCopyArea (dpy, src, dest, gc,
+ src_x, src_y, width, height, dest_x, dest_y);
+}
+
+
+int
+XDrawLine (Display *dpy, Drawable d, GC gc, int x1, int y1, int x2, int y2)
+{
+ XSegment segment;
+ segment.x1 = x1;
+ segment.y1 = y1;
+ segment.x2 = x2;
+ segment.y2 = y2;
+ XDrawSegments (dpy, d, gc, &segment, 1);
+ return 0;
+}
+
+
+int
+XSetWindowBackground (Display *dpy, Window w, unsigned long pixel)
+{
+ Assert (w == XRootWindow (dpy,0), "not a window");
+ jwxyz_validate_pixel (dpy, pixel, visual_depth (NULL, NULL), False);
+ *VTBL->window_background (dpy) = pixel;
+ return 0;
+}
+
+void
+jwxyz_fill_rect (Display *dpy, Drawable d, GC gc,
+ int x, int y, unsigned int width, unsigned int height,
+ unsigned long pixel)
+{
+ XRectangle r = {x, y, width, height};
+ VTBL->fill_rects (dpy, d, gc, &r, 1, pixel);
+}
+
+int
+XFillRectangle (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height)
+{
+ jwxyz_fill_rect (dpy, d, gc, x, y, width, height,
+ VTBL->gc_gcv (gc)->foreground);
+ return 0;
+}
+
+int
+XDrawRectangle (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height)
+{
+ XPoint points[5] = {
+ {x, y},
+ {x, y + height},
+ {x + width, y + height},
+ {x + width, y},
+ {x, y}
+ };
+
+ XDrawLines(dpy, d, gc, points, 5, CoordModeOrigin);
+ return 0;
+}
+
+int
+XFillRectangles (Display *dpy, Drawable d, GC gc, XRectangle *rects, int n)
+{
+ VTBL->fill_rects (dpy, d, gc, rects, n, VTBL->gc_gcv (gc)->foreground);
+ return 0;
+}
+
+
+int
+XClearArea (Display *dpy, Window win, int x, int y, int w, int h, Bool exp)
+{
+ Assert(win == XRootWindow(dpy,0), "XClearArea: not a window");
+ Assert(!exp, "XClearArea: exposures unsupported");
+ jwxyz_fill_rect (dpy, win, 0, x, y, w, h, *VTBL->window_background (dpy));
+ return 0;
+}
+
+
+int
+XDrawArc (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height, int angle1, int angle2)
+{
+ return VTBL->draw_arc (dpy, d, gc, x, y, width, height, angle1, angle2,
+ False);
+}
+
+int
+XFillArc (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height, int angle1, int angle2)
+{
+ return VTBL->draw_arc (dpy, d, gc, x, y, width, height, angle1, angle2,
+ True);
+}
+
+int
+XDrawArcs (Display *dpy, Drawable d, GC gc, XArc *arcs, int narcs)
+{
+ int i;
+ for (i = 0; i < narcs; i++)
+ VTBL->draw_arc (dpy, d, gc,
+ arcs[i].x, arcs[i].y,
+ arcs[i].width, arcs[i].height,
+ arcs[i].angle1, arcs[i].angle2,
+ False);
+ return 0;
+}
+
+int
+XFillArcs (Display *dpy, Drawable d, GC gc, XArc *arcs, int narcs)
+{
+ int i;
+ for (i = 0; i < narcs; i++)
+ VTBL->draw_arc (dpy, d, gc,
+ arcs[i].x, arcs[i].y,
+ arcs[i].width, arcs[i].height,
+ arcs[i].angle1, arcs[i].angle2,
+ True);
+ return 0;
+}
+
+void
+jwxyz_gcv_defaults (Display *dpy, XGCValues *gcv, int depth)
+{
+ memset (gcv, 0, sizeof(*gcv));
+ gcv->function = GXcopy;
+ gcv->foreground = (depth == 1 ? 1 : WhitePixel(dpy,0));
+ gcv->background = (depth == 1 ? 0 : BlackPixel(dpy,0));
+ gcv->line_width = 1;
+ gcv->cap_style = CapButt;
+ gcv->join_style = JoinMiter;
+ gcv->fill_rule = EvenOddRule;
+
+ gcv->alpha_allowed_p = False;
+ gcv->antialias_p = True;
+}
+
+
+int
+XChangeGC (Display *dpy, GC gc, unsigned long mask, XGCValues *from)
+{
+ if (! mask) return 0;
+ Assert (gc && from, "no gc");
+ if (!gc || !from) return 0;
+
+ XGCValues *to = VTBL->gc_gcv (gc);
+ unsigned depth = VTBL->gc_depth (gc);
+
+ if (mask & GCFunction) to->function = from->function;
+ if (mask & GCForeground) to->foreground = from->foreground;
+ if (mask & GCBackground) to->background = from->background;
+ if (mask & GCLineWidth) to->line_width = from->line_width;
+ if (mask & GCCapStyle) to->cap_style = from->cap_style;
+ if (mask & GCJoinStyle) to->join_style = from->join_style;
+ if (mask & GCFillRule) to->fill_rule = from->fill_rule;
+ if (mask & GCClipXOrigin) to->clip_x_origin = from->clip_x_origin;
+ if (mask & GCClipYOrigin) to->clip_y_origin = from->clip_y_origin;
+ if (mask & GCSubwindowMode) to->subwindow_mode = from->subwindow_mode;
+
+ if (mask & GCClipMask) XSetClipMask (dpy, gc, from->clip_mask);
+ if (mask & GCFont) XSetFont (dpy, gc, from->font);
+
+ if (mask & GCForeground)
+ jwxyz_validate_pixel (dpy, from->foreground, depth, to->alpha_allowed_p);
+ if (mask & GCBackground)
+ jwxyz_validate_pixel (dpy, from->background, depth, to->alpha_allowed_p);
+
+ Assert ((! (mask & (GCLineStyle |
+ GCPlaneMask |
+ GCFillStyle |
+ GCTile |
+ GCStipple |
+ GCTileStipXOrigin |
+ GCTileStipYOrigin |
+ GCGraphicsExposures |
+ GCDashOffset |
+ GCDashList |
+ GCArcMode))),
+ "unimplemented gcvalues mask");
+
+ return 0;
+}
+
+
+Status
+XGetWindowAttributes (Display *dpy, Window w, XWindowAttributes *xgwa)
+{
+ assert_window(dpy, w);
+ memset (xgwa, 0, sizeof(*xgwa));
+ const XRectangle *frame = jwxyz_frame (w);
+ xgwa->x = frame->x;
+ xgwa->y = frame->y;
+ xgwa->width = frame->width;
+ xgwa->height = frame->height;
+ xgwa->depth = visual_depth (NULL, NULL);
+ xgwa->screen = DefaultScreenOfDisplay (dpy);
+ xgwa->visual = XDefaultVisualOfScreen (xgwa->screen);
+ return 0;
+}
+
+Status
+XGetGeometry (Display *dpy, Drawable d, Window *root_ret,
+ int *x_ret, int *y_ret,
+ unsigned int *w_ret, unsigned int *h_ret,
+ unsigned int *bw_ret, unsigned int *d_ret)
+{
+ const XRectangle *frame = jwxyz_frame (d);
+ *x_ret = frame->x;
+ *y_ret = frame->y;
+ *w_ret = frame->width;
+ *h_ret = frame->height;
+ *d_ret = jwxyz_drawable_depth (d);
+ *root_ret = RootWindow (dpy, 0);
+ *bw_ret = 0;
+ return True;
+}
+
+
+Status
+XAllocColor (Display *dpy, Colormap cmap, XColor *color)
+{
+ Visual *v = DefaultVisualOfScreen (DefaultScreenOfDisplay (dpy));
+ color->pixel =
+ (((color->red << 16) >> (31 - i_log2(v->red_mask))) & v->red_mask) |
+ (((color->green << 16) >> (31 - i_log2(v->green_mask))) & v->green_mask) |
+ (((color->blue << 16) >> (31 - i_log2(v->blue_mask))) & v->blue_mask) |
+ v->alpha_mask;
+ return 1;
+}
+
+Status
+XAllocColorCells (Display *dpy, Colormap cmap, Bool contig,
+ unsigned long *pmret, unsigned int npl,
+ unsigned long *pxret, unsigned int npx)
+{
+ return 0;
+}
+
+int
+XStoreColors (Display *dpy, Colormap cmap, XColor *colors, int n)
+{
+ Assert(0, "XStoreColors called");
+ return 0;
+}
+
+int
+XStoreColor (Display *dpy, Colormap cmap, XColor *c)
+{
+ Assert(0, "XStoreColor called");
+ return 0;
+}
+
+int
+XFreeColors (Display *dpy, Colormap cmap, unsigned long *px, int npixels,
+ unsigned long planes)
+{
+ return 0;
+}
+
+Status
+XParseColor (Display *dpy, Colormap cmap, const char *spec, XColor *ret)
+{
+ unsigned char r=0, g=0, b=0;
+ if (*spec == '#' && strlen(spec) == 7) {
+ static unsigned const char hex[] = { // yeah yeah, shoot me.
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,
+ 0,10,11,12,13,14,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,10,11,12,13,14,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+ const unsigned char *uspec = (const unsigned char *)spec;
+ r = (hex[uspec[1]] << 4) | hex[uspec[2]];
+ g = (hex[uspec[3]] << 4) | hex[uspec[4]];
+ b = (hex[uspec[5]] << 4) | hex[uspec[6]];
+ } else if (!strcasecmp(spec,"black")) {
+ // r = g = b = 0;
+ } else if (!strcasecmp(spec,"white")) {
+ r = g = b = 255;
+ } else if (!strcasecmp(spec,"red")) {
+ r = 255;
+ } else if (!strcasecmp(spec,"green")) {
+ g = 255;
+ } else if (!strcasecmp(spec,"blue")) {
+ b = 255;
+ } else if (!strcasecmp(spec,"cyan")) {
+ g = b = 255;
+ } else if (!strcasecmp(spec,"magenta")) {
+ r = b = 255;
+ } else if (!strcasecmp(spec,"yellow")) {
+ r = g = 255;
+ } else {
+ return 0;
+ }
+
+ ret->red = (r << 8) | r;
+ ret->green = (g << 8) | g;
+ ret->blue = (b << 8) | b;
+ ret->flags = DoRed|DoGreen|DoBlue;
+ return 1;
+}
+
+Status
+XAllocNamedColor (Display *dpy, Colormap cmap, char *name,
+ XColor *screen_ret, XColor *exact_ret)
+{
+ if (! XParseColor (dpy, cmap, name, screen_ret))
+ return False;
+ *exact_ret = *screen_ret;
+ return XAllocColor (dpy, cmap, screen_ret);
+}
+
+int
+XQueryColor (Display *dpy, Colormap cmap, XColor *color)
+{
+ jwxyz_validate_pixel (dpy, color->pixel, visual_depth (NULL, NULL), False);
+ uint16_t rgba[4];
+ JWXYZ_QUERY_COLOR (dpy, color->pixel, 0xffffull, rgba);
+ color->red = rgba[0];
+ color->green = rgba[1];
+ color->blue = rgba[2];
+ color->flags = DoRed|DoGreen|DoBlue;
+ return 0;
+}
+
+int
+XQueryColors (Display *dpy, Colormap cmap, XColor *c, int n)
+{
+ int i;
+ for (i = 0; i < n; i++)
+ XQueryColor (dpy, cmap, &c[i]);
+ return 0;
+}
+
+
+static unsigned long
+ximage_getpixel_1 (XImage *ximage, int x, int y)
+{
+ return ((ximage->data [y * ximage->bytes_per_line + (x>>3)] >> (x & 7)) & 1);
+}
+
+static int
+ximage_putpixel_1 (XImage *ximage, int x, int y, unsigned long pixel)
+{
+ if (pixel)
+ ximage->data [y * ximage->bytes_per_line + (x>>3)] |= (1 << (x & 7));
+ else
+ ximage->data [y * ximage->bytes_per_line + (x>>3)] &= ~(1 << (x & 7));
+
+ return 0;
+}
+
+static unsigned long
+ximage_getpixel_8 (XImage *ximage, int x, int y)
+{
+ return ((unsigned long)
+ *((uint8_t *) ximage->data +
+ (y * ximage->bytes_per_line) +
+ x));
+}
+
+static int
+ximage_putpixel_8 (XImage *ximage, int x, int y, unsigned long pixel)
+{
+ *((uint8_t *) ximage->data +
+ (y * ximage->bytes_per_line) +
+ x) = (uint8_t) pixel;
+ return 0;
+}
+
+
+static unsigned long
+ximage_getpixel_32 (XImage *ximage, int x, int y)
+{
+ return ((unsigned long)
+ *((uint32_t *) ximage->data +
+ (y * (ximage->bytes_per_line >> 2)) +
+ x));
+}
+
+static int
+ximage_putpixel_32 (XImage *ximage, int x, int y, unsigned long pixel)
+{
+ *((uint32_t *) ximage->data +
+ (y * (ximage->bytes_per_line >> 2)) +
+ x) = (uint32_t) pixel;
+ return 0;
+}
+
+
+Status
+XInitImage (XImage *ximage)
+{
+ if (!ximage->bytes_per_line)
+ ximage->bytes_per_line = (ximage->depth == 1 ? (ximage->width + 7) / 8 :
+ ximage->depth == 8 ? ximage->width :
+ ximage->width * 4);
+
+ if (ximage->depth == 1) {
+ ximage->f.put_pixel = ximage_putpixel_1;
+ ximage->f.get_pixel = ximage_getpixel_1;
+ } else if (ximage->depth == 32 || ximage->depth == 24) {
+ ximage->f.put_pixel = ximage_putpixel_32;
+ ximage->f.get_pixel = ximage_getpixel_32;
+ } else if (ximage->depth == 8) {
+ ximage->f.put_pixel = ximage_putpixel_8;
+ ximage->f.get_pixel = ximage_getpixel_8;
+ } else {
+ Assert (0, "unknown depth");
+ }
+ return 1;
+}
+
+
+XImage *
+XCreateImage (Display *dpy, Visual *visual, unsigned int depth,
+ int format, int offset, char *data,
+ unsigned int width, unsigned int height,
+ int bitmap_pad, int bytes_per_line)
+{
+ XImage *ximage = (XImage *) calloc (1, sizeof(*ximage));
+ ximage->width = width;
+ ximage->height = height;
+ ximage->format = format;
+ ximage->data = data;
+ ximage->bitmap_unit = 8;
+ ximage->byte_order = LSBFirst;
+ ximage->bitmap_bit_order = ximage->byte_order;
+ ximage->bitmap_pad = bitmap_pad;
+ ximage->depth = depth;
+ Visual *v = DefaultVisualOfScreen (DefaultScreenOfDisplay (dpy));
+ ximage->red_mask = (depth == 1 ? 0 : v->red_mask);
+ ximage->green_mask = (depth == 1 ? 0 : v->green_mask);
+ ximage->blue_mask = (depth == 1 ? 0 : v->blue_mask);
+ ximage->bits_per_pixel = (depth == 1 ? 1 : visual_depth (NULL, NULL));
+ ximage->bytes_per_line = bytes_per_line;
+
+ XInitImage (ximage);
+ return ximage;
+}
+
+XImage *
+XSubImage (XImage *from, int x, int y, unsigned int w, unsigned int h)
+{
+ XImage *to = (XImage *) malloc (sizeof(*to));
+ memcpy (to, from, sizeof(*from));
+ to->width = w;
+ to->height = h;
+ to->bytes_per_line = 0;
+ XInitImage (to);
+
+ to->data = (char *) malloc (h * to->bytes_per_line);
+
+ if (x >= from->width)
+ w = 0;
+ else if (x+w > from->width)
+ w = from->width - x;
+
+ if (y >= from->height)
+ h = 0;
+ else if (y+h > from->height)
+ h = from->height - y;
+
+ int tx, ty;
+ for (ty = 0; ty < h; ty++)
+ for (tx = 0; tx < w; tx++)
+ XPutPixel (to, tx, ty, XGetPixel (from, x+tx, y+ty));
+ return to;
+}
+
+
+XPixmapFormatValues *
+XListPixmapFormats (Display *dpy, int *n_ret)
+{
+ XPixmapFormatValues *ret = calloc (2, sizeof(*ret));
+ ret[0].depth = visual_depth (NULL, NULL);
+ ret[0].bits_per_pixel = 32;
+ ret[0].scanline_pad = 8;
+ ret[1].depth = 1;
+ ret[1].bits_per_pixel = 1;
+ ret[1].scanline_pad = 8;
+ *n_ret = 2;
+ return ret;
+}
+
+
+unsigned long
+XGetPixel (XImage *ximage, int x, int y)
+{
+ return ximage->f.get_pixel (ximage, x, y);
+}
+
+
+int
+XPutPixel (XImage *ximage, int x, int y, unsigned long pixel)
+{
+ return ximage->f.put_pixel (ximage, x, y, pixel);
+}
+
+int
+XDestroyImage (XImage *ximage)
+{
+ if (ximage->data) free (ximage->data);
+ free (ximage);
+ return 0;
+}
+
+
+XImage *
+XGetImage (Display *dpy, Drawable d, int x, int y,
+ unsigned int width, unsigned int height,
+ unsigned long plane_mask, int format)
+{
+ unsigned depth = jwxyz_drawable_depth (d);
+ XImage *image = XCreateImage (dpy, 0, depth, format, 0, 0, width, height,
+ 0, 0);
+ image->data = (char *) malloc (height * image->bytes_per_line);
+
+ return XGetSubImage (dpy, d, x, y, width, height, plane_mask, format,
+ image, 0, 0);
+}
+
+
+Pixmap
+XCreatePixmapFromBitmapData (Display *dpy, Drawable drawable,
+ const char *data,
+ unsigned int w, unsigned int h,
+ unsigned long fg, unsigned long bg,
+ unsigned int depth)
+{
+ Pixmap p = XCreatePixmap (dpy, drawable, w, h, depth);
+ XImage *image = XCreateImage (dpy, 0, 1, XYPixmap, 0,
+ (char *) data, w, h, 0, 0);
+ XGCValues gcv;
+ gcv.foreground = fg;
+ gcv.background = bg;
+ GC gc = XCreateGC (dpy, p, GCForeground|GCBackground, &gcv);
+ XPutImage (dpy, p, gc, image, 0, 0, 0, 0, w, h);
+ XFreeGC (dpy, gc);
+ image->data = 0;
+ XDestroyImage (image);
+ return p;
+}
+
+
+char *
+XGetAtomName (Display *dpy, Atom atom)
+{
+ if (atom == XA_FONT)
+ return strdup ("FONT");
+
+ // Note that atoms (that aren't predefined) are just char *.
+ return strdup ((char *) atom);
+}
+
+
+// This is XQueryFont, but for the XFontStruct embedded in 'Font'
+//
+static void
+query_font (Font fid)
+{
+ Assert (fid && fid->native_font, "no native font in fid");
+
+ int first = 32;
+ int last = 255;
+
+ Display *dpy = fid->dpy;
+ void *native_font = fid->native_font;
+
+ XFontStruct *f = &fid->metrics;
+ XCharStruct *min = &f->min_bounds;
+ XCharStruct *max = &f->max_bounds;
+
+ f->fid = fid;
+ f->min_char_or_byte2 = first;
+ f->max_char_or_byte2 = last;
+ f->default_char = 'M';
+ f->ascent = fid->ascent;
+ f->descent = fid->descent;
+
+ min->width = 32767; // set to smaller values in the loop
+ min->ascent = 32767;
+ min->descent = 32767;
+ min->lbearing = 32767;
+ min->rbearing = 32767;
+
+ f->per_char = (XCharStruct *) calloc (last-first+2, sizeof (XCharStruct));
+
+ for (int i = first; i <= last; i++) {
+ XCharStruct *cs = &f->per_char[i-first];
+ char s = (char) i;
+ jwxyz_render_text (dpy, native_font, &s, 1, False, False, cs, 0);
+
+ max->width = MAX (max->width, cs->width);
+ max->ascent = MAX (max->ascent, cs->ascent);
+ max->descent = MAX (max->descent, cs->descent);
+ max->lbearing = MAX (max->lbearing, cs->lbearing);
+ max->rbearing = MAX (max->rbearing, cs->rbearing);
+
+ min->width = MIN (min->width, cs->width);
+ min->ascent = MIN (min->ascent, cs->ascent);
+ min->descent = MIN (min->descent, cs->descent);
+ min->lbearing = MIN (min->lbearing, cs->lbearing);
+ min->rbearing = MIN (min->rbearing, cs->rbearing);
+/*
+ Log (" %3d %c: w=%3d lb=%3d rb=%3d as=%3d ds=%3d "
+ " bb=%5.1f x %5.1f @ %5.1f %5.1f adv=%5.1f %5.1f\n"
+ i, i, cs->width, cs->lbearing, cs->rbearing,
+ cs->ascent, cs->descent,
+ bbox.size.width, bbox.size.height,
+ bbox.origin.x, bbox.origin.y,
+ advancement.width, advancement.height);
+ */
+ }
+}
+
+
+// Since 'Font' includes the metrics, this just makes a copy of that.
+//
+XFontStruct *
+XQueryFont (Display *dpy, Font fid)
+{
+ // copy XFontStruct
+ XFontStruct *f = (XFontStruct *) calloc (1, sizeof(*f));
+ *f = fid->metrics;
+ f->fid = fid;
+
+ // build XFontProps
+ f->n_properties = 1;
+ f->properties = malloc (sizeof(*f->properties) * f->n_properties);
+ f->properties[0].name = XA_FONT;
+ Assert (sizeof (f->properties[0].card32) >= sizeof (char *),
+ "atoms probably needs a real implementation");
+ // If XInternAtom is ever implemented, use it here.
+ f->properties[0].card32 = (unsigned long)fid->xa_font;
+
+ // copy XCharStruct array
+ int size = (f->max_char_or_byte2 - f->min_char_or_byte2) + 1;
+ f->per_char = (XCharStruct *) calloc (size + 2, sizeof (XCharStruct));
+
+ memcpy (f->per_char, fid->metrics.per_char,
+ size * sizeof (XCharStruct));
+
+ return f;
+}
+
+
+static Font
+copy_font (Font fid)
+{
+ fid->refcount++;
+ return fid;
+}
+
+
+/* On Cocoa and iOS, fonts may be specified as "Georgia Bold 24" instead
+ of XLFD strings; also they can be comma-separated strings with multiple
+ font names. First one that exists wins.
+ */
+static void
+try_native_font (Display *dpy, const char *name, Font fid)
+{
+ if (!name) return;
+ const char *spc = strrchr (name, ' ');
+ if (!spc) return;
+
+ char *token = strdup (name);
+ char *otoken = token;
+ char *name2;
+ char *lasts;
+
+ while ((name2 = strtok_r (token, ",", &lasts))) {
+ token = 0;
+
+ while (*name2 == ' ' || *name2 == '\t' || *name2 == '\n')
+ name2++;
+
+ spc = strrchr (name2, ' ');
+ if (!spc) continue;
+
+ int dsize = 0;
+ if (1 != sscanf (spc, " %d ", &dsize))
+ continue;
+ float size = dsize;
+
+ if (size < 4) continue;
+
+ name2[strlen(name2) - strlen(spc)] = 0;
+
+ fid->native_font = jwxyz_load_native_font(XRootWindow(dpy,0), 0, 0, name2,
+ strlen(name2) - strlen(spc),
+ JWXYZ_FONT_FACE, size, NULL,
+ &fid->ascent, &fid->descent);
+ if (fid->native_font) {
+ fid->xa_font = strdup (name); // Maybe this should be an XLFD?
+ break;
+ } else {
+ // To list fonts:
+ // po [UIFont familyNames]
+ // po [UIFont fontNamesForFamilyName:@"Arial"]
+ Log("No native font: \"%s\" %.0f", name2, size);
+ }
+ }
+
+ free (otoken);
+}
+
+
+static const char *
+xlfd_field_end (const char *s)
+{
+ const char *s2 = strchr(s, '-');
+ if (!s2)
+ s2 = s + strlen(s);
+ return s2;
+}
+
+
+static size_t
+xlfd_next (const char **s, const char **s2)
+{
+ if (!**s2) {
+ *s = *s2;
+ } else {
+ Assert (**s2 == '-', "xlfd parse error");
+ *s = *s2 + 1;
+ *s2 = xlfd_field_end (*s);
+ }
+
+ return *s2 - *s;
+}
+
+
+static void
+try_xlfd_font (Display *dpy, const char *name, Font fid)
+{
+ const char *family_name = NULL; /* Not NULL-terminated. */
+ size_t family_name_size = 0;
+ int require = 0,
+ // Default mask is for the built-in X11 font aliases.
+ mask = JWXYZ_STYLE_MONOSPACE | JWXYZ_STYLE_BOLD | JWXYZ_STYLE_ITALIC;
+ Bool rand = False;
+ float size = 12; /* In points (1/72 in.) */
+
+ const char *s = (name ? name : "");
+
+ size_t L = strlen (s);
+# define CMP(STR) (L == strlen(STR) && !strncasecmp (s, (STR), L))
+# define UNSPEC (L == 0 || (L == 1 && *s == '*'))
+ if (CMP ("6x10")) size = 8, require |= JWXYZ_STYLE_MONOSPACE;
+ else if (CMP ("6x10bold")) size = 8, require |= JWXYZ_STYLE_MONOSPACE | JWXYZ_STYLE_BOLD;
+ else if (CMP ("fixed")) size = 12, require |= JWXYZ_STYLE_MONOSPACE;
+ else if (CMP ("9x15")) size = 12, require |= JWXYZ_STYLE_MONOSPACE;
+ else if (CMP ("9x15bold")) size = 12, require |= JWXYZ_STYLE_MONOSPACE | JWXYZ_STYLE_BOLD;
+ else if (CMP ("vga")) size = 12, require |= JWXYZ_STYLE_MONOSPACE;
+ else if (CMP ("console")) size = 12, require |= JWXYZ_STYLE_MONOSPACE;
+ else if (CMP ("gallant")) size = 12, require |= JWXYZ_STYLE_MONOSPACE;
+ else {
+
+ int forbid = 0;
+
+ // Incorrect fields are ignored.
+
+ if (*s == '-')
+ ++s;
+ const char *s2 = xlfd_field_end(s);
+
+ // Foundry (ignore)
+
+ L = xlfd_next (&s, &s2); // Family name
+ // This used to substitute Georgia for Times. Now it doesn't.
+ if (CMP ("random")) {
+ rand = True;
+ } else if (CMP ("fixed")) {
+ require |= JWXYZ_STYLE_MONOSPACE;
+ family_name = "Courier";
+ family_name_size = strlen(family_name);
+ } else if (!UNSPEC) {
+ family_name = s;
+ family_name_size = L;
+ }
+
+ L = xlfd_next (&s, &s2); // Weight name
+ if (CMP ("bold") || CMP ("demibold"))
+ require |= JWXYZ_STYLE_BOLD;
+ else if (CMP ("medium") || CMP ("regular"))
+ forbid |= JWXYZ_STYLE_BOLD;
+
+ L = xlfd_next (&s, &s2); // Slant
+ if (CMP ("i") || CMP ("o"))
+ require |= JWXYZ_STYLE_ITALIC;
+ else if (CMP ("r"))
+ forbid |= JWXYZ_STYLE_ITALIC;
+
+ xlfd_next (&s, &s2); // Set width name (ignore)
+ xlfd_next (&s, &s2); // Add style name (ignore)
+
+ L = xlfd_next (&s, &s2); // Pixel size
+ char *s3;
+ uintmax_t pxsize = strtoumax(s, &s3, 10);
+ if (UNSPEC || s2 != s3)
+ pxsize = UINTMAX_MAX; // i.e. it's invalid.
+
+ L = xlfd_next (&s, &s2); // Point size
+ uintmax_t ptsize = strtoumax(s, &s3, 10);
+ if (UNSPEC || s2 != s3)
+ ptsize = UINTMAX_MAX;
+
+ xlfd_next (&s, &s2); // Resolution X (ignore)
+ xlfd_next (&s, &s2); // Resolution Y (ignore)
+
+ L = xlfd_next (&s, &s2); // Spacing
+ if (CMP ("p"))
+ forbid |= JWXYZ_STYLE_MONOSPACE;
+ else if (CMP ("m") || CMP ("c"))
+ require |= JWXYZ_STYLE_MONOSPACE;
+
+ xlfd_next (&s, &s2); // Average width (ignore)
+
+ // -*-courier-bold-r-*-*-14-*-*-*-*-*-*-* 14 px
+ // -*-courier-bold-r-*-*-*-140-*-*-m-*-*-* 14 pt
+ // -*-courier-bold-r-*-*-140-* 14 pt, via wildcard
+ // -*-courier-bold-r-*-140-* 14 pt, not handled
+ // -*-courier-bold-r-*-*-14-180-*-*-*-*-*-* error
+
+ L = xlfd_next (&s, &s2); // Charset registry
+ if (ptsize != UINTMAX_MAX) {
+ // It was in the ptsize field, so that's definitely what it is.
+ size = ptsize / 10.0;
+ } else if (pxsize != UINTMAX_MAX) {
+ size = pxsize;
+ // If it's a fully qualified XLFD, then this really is the pxsize.
+ // Otherwise, this is probably point size with a multi-field wildcard.
+ if (L == 0)
+ size /= 10.0;
+ }
+
+ mask = require | forbid;
+ }
+# undef CMP
+# undef UNSPEC
+
+ if (!family_name && !rand) {
+ family_name = jwxyz_default_font_family (require);
+ family_name_size = strlen (family_name);
+ }
+
+ if (size < 6 || size > 1000)
+ size = 12;
+
+ char *family_name_ptr = NULL;
+ fid->native_font = jwxyz_load_native_font (XRootWindow(dpy,0),
+ require, mask,
+ family_name, family_name_size,
+ rand ? JWXYZ_FONT_RANDOM : JWXYZ_FONT_FAMILY,
+ size, &family_name_ptr,
+ &fid->ascent, &fid->descent);
+
+ if (fid->native_font) {
+ unsigned dpi_d = XDisplayHeightMM (dpy,0) * 10 / 2;
+ unsigned dpi = (254 * XDisplayHeight (dpy,0) + dpi_d) / (2 * dpi_d);
+ asprintf(&fid->xa_font, "-*-%s-%s-%c-*-*-%u-%u-%u-%u-%c-0-iso10646-1",
+ family_name_ptr,
+ (require & JWXYZ_STYLE_BOLD) ? "bold" : "medium",
+ (require & JWXYZ_STYLE_ITALIC) ? 'o' : 'r',
+ (unsigned)(dpi * size / 72.27 + 0.5),
+ (unsigned)(size * 10 + 0.5), dpi, dpi,
+ (require & JWXYZ_STYLE_MONOSPACE) ? 'm' : 'p');
+ }
+
+ free (family_name_ptr);
+}
+
+
+Font
+XLoadFont (Display *dpy, const char *name)
+{
+ Font fid = (Font) calloc (1, sizeof(*fid));
+
+ fid->refcount = 1;
+ fid->dpy = dpy;
+ try_native_font (dpy, name, fid);
+
+ if (!fid->native_font && name &&
+ strchr (name, ' ') &&
+ !strchr (name, '*')) {
+ // If name contains a space but no stars, it is a native font spec --
+ // return NULL so that we know it really didn't exist. Else, it is an
+ // XLFD font, so keep trying.
+ free (fid);
+ return 0;
+ }
+
+ if (! fid->native_font)
+ try_xlfd_font (dpy, name, fid);
+
+ if (!fid->native_font) {
+ free (fid);
+ return 0;
+ }
+
+ query_font (fid);
+
+ return fid;
+}
+
+
+XFontStruct *
+XLoadQueryFont (Display *dpy, const char *name)
+{
+ Font fid = XLoadFont (dpy, name);
+ if (!fid) return 0;
+ return XQueryFont (dpy, fid);
+}
+
+int
+XUnloadFont (Display *dpy, Font fid)
+{
+ if (--fid->refcount < 0) abort();
+ if (fid->refcount > 0) return 0;
+
+ if (fid->native_font)
+ jwxyz_release_native_font (fid->dpy, fid->native_font);
+
+ if (fid->metrics.per_char)
+ free (fid->metrics.per_char);
+
+ free (fid);
+ return 0;
+}
+
+int
+XFreeFontInfo (char **names, XFontStruct *info, int n)
+{
+ int i;
+ if (names) {
+ for (i = 0; i < n; i++)
+ if (names[i]) free (names[i]);
+ free (names);
+ }
+ if (info) {
+ for (i = 0; i < n; i++)
+ if (info[i].per_char) {
+ free (info[i].per_char);
+ free (info[i].properties);
+ }
+ free (info);
+ }
+ return 0;
+}
+
+int
+XFreeFont (Display *dpy, XFontStruct *f)
+{
+ Font fid = f->fid;
+ XFreeFontInfo (0, f, 1);
+ XUnloadFont (dpy, fid);
+ return 0;
+}
+
+
+int
+XSetFont (Display *dpy, GC gc, Font fid)
+{
+ XGCValues *gcv = VTBL->gc_gcv(gc);
+ Font font2 = copy_font (fid);
+ if (gcv->font)
+ XUnloadFont (dpy, gcv->font);
+ gcv->font = font2;
+ return 0;
+}
+
+
+XFontSet
+XCreateFontSet (Display *dpy, char *name,
+ char ***missing_charset_list_return,
+ int *missing_charset_count_return,
+ char **def_string_return)
+{
+ char *name2 = strdup (name);
+ char *s = strchr (name, ',');
+ if (s) *s = 0;
+ XFontSet set = 0;
+ XFontStruct *f = XLoadQueryFont (dpy, name2);
+ if (f)
+ {
+ set = (XFontSet) calloc (1, sizeof(*set));
+ set->font = f;
+ }
+ free (name2);
+ if (missing_charset_list_return) *missing_charset_list_return = 0;
+ if (missing_charset_count_return) *missing_charset_count_return = 0;
+ if (def_string_return) *def_string_return = 0;
+ return set;
+}
+
+
+void
+XFreeFontSet (Display *dpy, XFontSet set)
+{
+ XFreeFont (dpy, set->font);
+ free (set);
+}
+
+
+void
+XFreeStringList (char **list)
+{
+ int i;
+ if (!list) return;
+ for (i = 0; list[i]; i++)
+ XFree (list[i]);
+ XFree (list);
+}
+
+
+int
+XTextExtents (XFontStruct *f, const char *s, int length,
+ int *dir_ret, int *ascent_ret, int *descent_ret,
+ XCharStruct *cs)
+{
+ // Unfortunately, adding XCharStructs together to get the extents for a
+ // string doesn't work: Cocoa uses non-integral character advancements, but
+ // XCharStruct.width is an integer. Plus that doesn't take into account
+ // kerning pairs, alternate glyphs, and fun stuff like the word "Zapfino" in
+ // Zapfino.
+
+ Font ff = f->fid;
+ Display *dpy = ff->dpy;
+ jwxyz_render_text (dpy, ff->native_font, s, length, False, False, cs, 0);
+ *dir_ret = 0;
+ *ascent_ret = f->ascent;
+ *descent_ret = f->descent;
+ return 0;
+}
+
+int
+XTextWidth (XFontStruct *f, const char *s, int length)
+{
+ int ascent, descent, dir;
+ XCharStruct cs;
+ XTextExtents (f, s, length, &dir, &ascent, &descent, &cs);
+ return cs.width;
+}
+
+
+int
+XTextExtents16 (XFontStruct *f, const XChar2b *s, int length,
+ int *dir_ret, int *ascent_ret, int *descent_ret,
+ XCharStruct *cs)
+{
+ // Bool latin1_p = True;
+ int i, utf8_len = 0;
+ char *utf8 = XChar2b_to_utf8 (s, &utf8_len); // already sanitized
+
+ for (i = 0; i < length; i++)
+ if (s[i].byte1 > 0) {
+ // latin1_p = False;
+ break;
+ }
+
+ {
+ Font ff = f->fid;
+ Display *dpy = ff->dpy;
+ jwxyz_render_text (dpy, ff->native_font, utf8, strlen(utf8),
+ True, False, cs, 0);
+ }
+
+ *dir_ret = 0;
+ *ascent_ret = f->ascent;
+ *descent_ret = f->descent;
+ free (utf8);
+ return 0;
+}
+
+
+/* "Returns the distance in pixels in the primary draw direction from
+ the drawing origin to the origin of the next character to be drawn."
+
+ "overall_ink_return is set to the bbox of the string's character ink."
+
+ "The overall_ink_return for a nondescending, horizontally drawn Latin
+ character is conventionally entirely above the baseline; that is,
+ overall_ink_return.height <= -overall_ink_return.y."
+
+ [So this means that y is the top of the ink, and height grows down:
+ For above-the-baseline characters, y is negative.]
+
+ "The overall_ink_return for a nonkerned character is entirely at, and to
+ the right of, the origin; that is, overall_ink_return.x >= 0."
+
+ [So this means that x is the left of the ink, and width grows right.
+ For left-of-the-origin characters, x is negative.]
+
+ "A character consisting of a single pixel at the origin would set
+ overall_ink_return fields y = 0, x = 0, width = 1, and height = 1."
+ */
+int
+Xutf8TextExtents (XFontSet set, const char *str, int len,
+ XRectangle *overall_ink_return,
+ XRectangle *overall_logical_return)
+{
+ XCharStruct cs;
+ Font f = set->font->fid;
+
+ jwxyz_render_text (f->dpy, f->native_font, str, len, True, False, &cs,
+ NULL);
+
+ /* "The overall_logical_return is the bounding box that provides minimum
+ spacing to other graphical features for the string. Other graphical
+ features, for example, a border surrounding the text, should not
+ intersect this rectangle."
+
+ So I think that means they're the same? Or maybe "ink" is the bounding
+ box, and "logical" is the advancement? But then why is the return value
+ the advancement?
+ */
+ if (overall_ink_return)
+ XCharStruct_to_XmbRectangle (cs, *overall_ink_return);
+ if (overall_logical_return)
+ XCharStruct_to_XmbRectangle (cs, *overall_logical_return);
+
+ return cs.width;
+}
+
+
+int
+jwxyz_draw_string (Display *dpy, Drawable d, GC gc, int x, int y,
+ const char *str, size_t len, int utf8_p)
+{
+ const XGCValues *gcv = VTBL->gc_gcv (gc);
+ Font ff = gcv->font;
+ XCharStruct cs;
+
+ char *data = 0;
+ jwxyz_render_text (dpy, jwxyz_native_font (ff), str, len, utf8_p,
+ gcv->antialias_p, &cs, &data);
+ int w = cs.rbearing - cs.lbearing;
+ int h = cs.ascent + cs.descent;
+
+ if (w < 0 || h < 0) abort();
+ if (w == 0 || h == 0) {
+ if (data) free(data);
+ return 0;
+ }
+
+ XImage *img = XCreateImage (dpy, VTBL->visual (dpy), 32,
+ ZPixmap, 0, data, w, h, 0, 0);
+
+ /* The image of text is a 32-bit image, in white.
+ Take the green channel for intensity and use that as alpha.
+ replace RGB with the GC's foreground color.
+ This expects that XPutImage respects alpha and only writes
+ the bits that are not masked out.
+ */
+ {
+# define ROTL(x, rot) (((x) << ((rot) & 31)) | ((x) >> (32 - ((rot) & 31))))
+
+ Visual *v = DefaultVisualOfScreen (DefaultScreenOfDisplay(dpy));
+ unsigned shift = (i_log2 (v->alpha_mask) - i_log2 (v->green_mask)) & 31;
+ uint32_t mask = ROTL(v->green_mask, shift) & v->alpha_mask,
+ color = gcv->foreground & ~v->alpha_mask;
+ uint32_t *s = (uint32_t *)data;
+ uint32_t *end = s + (w * h);
+ while (s < end) {
+
+ *s = (ROTL(*s, shift) & mask) | color;
+ ++s;
+ }
+ }
+
+ {
+ Bool old_alpha = gcv->alpha_allowed_p;
+ jwxyz_XSetAlphaAllowed (dpy, gc, True);
+ XPutImage (dpy, d, gc, img, 0, 0,
+ x + cs.lbearing,
+ y - cs.ascent,
+ w, h);
+ jwxyz_XSetAlphaAllowed (dpy, gc, old_alpha);
+ XDestroyImage (img);
+ }
+
+ return 0;
+}
+
+
+int
+XDrawString (Display *dpy, Drawable d, GC gc, int x, int y,
+ const char *str, int len)
+{
+ return VTBL->draw_string (dpy, d, gc, x, y, str, len, False);
+}
+
+
+int
+XDrawString16 (Display *dpy, Drawable d, GC gc, int x, int y,
+ const XChar2b *str, int len)
+{
+ XChar2b *b2 = malloc ((len + 1) * sizeof(*b2));
+ char *s2;
+ int ret;
+ memcpy (b2, str, len * sizeof(*b2));
+ b2[len].byte1 = b2[len].byte2 = 0;
+ s2 = XChar2b_to_utf8 (b2, 0);
+ free (b2);
+ ret = VTBL->draw_string (dpy, d, gc, x, y, s2, strlen(s2), True);
+ free (s2);
+ return ret;
+}
+
+
+void
+Xutf8DrawString (Display *dpy, Drawable d, XFontSet set, GC gc,
+ int x, int y, const char *str, int len)
+{
+ VTBL->draw_string (dpy, d, gc, x, y, str, len, True);
+}
+
+
+int
+XDrawImageString (Display *dpy, Drawable d, GC gc, int x, int y,
+ const char *str, int len)
+{
+ int ascent, descent, dir;
+ XCharStruct cs;
+ XTextExtents (&VTBL->gc_gcv (gc)->font->metrics, str, len,
+ &dir, &ascent, &descent, &cs);
+ jwxyz_fill_rect (dpy, d, gc,
+ x + MIN (0, cs.lbearing),
+ y - MAX (0, ascent),
+
+ /* The +1 here is almost certainly wrong, but BSOD
+ requires it; and only BSOD, fluidballs, juggle
+ and grabclient call XDrawImageString... */
+ MAX (MAX (0, cs.rbearing) -
+ MIN (0, cs.lbearing),
+ cs.width) + 1,
+ MAX (0, ascent) + MAX (0, descent),
+ VTBL->gc_gcv(gc)->background);
+ return XDrawString (dpy, d, gc, x, y, str, len);
+}
+
+
+void *
+jwxyz_native_font (Font f)
+{
+ return f->native_font;
+}
+
+
+int
+XSetForeground (Display *dpy, GC gc, unsigned long fg)
+{
+ XGCValues *gcv = VTBL->gc_gcv (gc);
+ jwxyz_validate_pixel (dpy, fg, VTBL->gc_depth (gc), gcv->alpha_allowed_p);
+ gcv->foreground = fg;
+ return 0;
+}
+
+
+int
+XSetBackground (Display *dpy, GC gc, unsigned long bg)
+{
+ XGCValues *gcv = VTBL->gc_gcv (gc);
+ jwxyz_validate_pixel (dpy, bg, VTBL->gc_depth (gc), gcv->alpha_allowed_p);
+ gcv->background = bg;
+ return 0;
+}
+
+int
+jwxyz_XSetAlphaAllowed (Display *dpy, GC gc, Bool allowed)
+{
+ VTBL->gc_gcv (gc)->alpha_allowed_p = allowed;
+ return 0;
+}
+
+int
+jwxyz_XSetAntiAliasing (Display *dpy, GC gc, Bool antialias_p)
+{
+ VTBL->gc_gcv (gc)->antialias_p = antialias_p;
+ return 0;
+}
+
+
+int
+XSetLineAttributes (Display *dpy, GC gc, unsigned int line_width,
+ int line_style, int cap_style, int join_style)
+{
+ XGCValues *gcv = VTBL->gc_gcv (gc);
+ gcv->line_width = line_width;
+ Assert (line_style == LineSolid, "only LineSolid implemented");
+// gc->gcv.line_style = line_style;
+ gcv->cap_style = cap_style;
+ gcv->join_style = join_style;
+ return 0;
+}
+
+int
+XSetGraphicsExposures (Display *dpy, GC gc, Bool which)
+{
+ return 0;
+}
+
+int
+XSetFunction (Display *dpy, GC gc, int which)
+{
+ VTBL->gc_gcv (gc)->function = which;
+ return 0;
+}
+
+int
+XSetSubwindowMode (Display *dpy, GC gc, int which)
+{
+ VTBL->gc_gcv (gc)->subwindow_mode = which;
+ return 0;
+}
+
+
+Bool
+XQueryPointer (Display *dpy, Window w, Window *root_ret, Window *child_ret,
+ int *root_x_ret, int *root_y_ret,
+ int *win_x_ret, int *win_y_ret, unsigned int *mask_ret)
+{
+ assert_window (dpy, w);
+
+ XPoint vpos, p;
+ jwxyz_get_pos (w, &vpos, &p);
+
+ if (root_x_ret) *root_x_ret = p.x;
+ if (root_y_ret) *root_y_ret = p.y;
+ if (win_x_ret) *win_x_ret = p.x - vpos.x;
+ if (win_y_ret) *win_y_ret = p.y - vpos.y;
+ if (mask_ret) *mask_ret = 0; // #### poll the keyboard modifiers?
+ if (root_ret) *root_ret = 0;
+ if (child_ret) *child_ret = 0;
+ return True;
+}
+
+Bool
+XTranslateCoordinates (Display *dpy, Window w, Window dest_w,
+ int src_x, int src_y,
+ int *dest_x_ret, int *dest_y_ret,
+ Window *child_ret)
+{
+ assert_window (dpy, w);
+
+ XPoint vpos, p;
+ jwxyz_get_pos (w, &vpos, NULL);
+
+ // point starts out relative to top left of view
+ p.x = src_x;
+ p.y = src_y;
+
+ // get point relative to top left of screen
+ p.x += vpos.x;
+ p.y += vpos.y;
+
+ *dest_x_ret = p.x;
+ *dest_y_ret = p.y;
+ if (child_ret)
+ *child_ret = w;
+ return True;
+}
+
+
+KeySym
+XKeycodeToKeysym (Display *dpy, KeyCode code, int index)
+{
+ return code;
+}
+
+int
+XLookupString (XKeyEvent *e, char *buf, int size, KeySym *k_ret,
+ XComposeStatus *xc)
+{
+ KeySym ks = XKeycodeToKeysym (0, e->keycode, 0);
+ char c = 0;
+ // Do not put non-ASCII KeySyms like XK_Shift_L and XK_Page_Up in the string.
+ if ((unsigned int) ks <= 255)
+ c = (char) ks;
+
+ // Put control characters in the string. Not meta.
+ if (e->state & ControlMask) {
+ if (c >= 'a' && c <= 'z') // Upcase control.
+ c -= 'a'-'A';
+ if (c >= '@' && c <= '_') // Shift to control page.
+ c -= '@';
+ if (c == ' ') // C-SPC is NULL.
+ c = 0;
+ }
+
+ if (k_ret) *k_ret = ks;
+ if (size > 0) buf[0] = c;
+ if (size > 1) buf[1] = 0;
+ return (size > 0 ? 1 : 0);
+}
+
+
+int
+XFlush (Display *dpy)
+{
+ // Just let the event loop take care of this on its own schedule.
+ return 0;
+}
+
+int
+XSync (Display *dpy, Bool flush)
+{
+ return XFlush (dpy);
+}
+
+
+// declared in utils/visual.h
+int
+has_writable_cells (Screen *s, Visual *v)
+{
+ return 0;
+}
+
+int
+visual_depth (Screen *s, Visual *v)
+{
+ return 32;
+}
+
+int
+visual_cells (Screen *s, Visual *v)
+{
+ return (int)(v->red_mask | v->green_mask | v->blue_mask);
+}
+
+int
+visual_class (Screen *s, Visual *v)
+{
+ return TrueColor;
+}
+
+void
+visual_rgb_masks (Screen *s, Visual *v, unsigned long *red_mask,
+ unsigned long *green_mask, unsigned long *blue_mask)
+{
+ *red_mask = v->red_mask;
+ *green_mask = v->green_mask;
+ *blue_mask = v->blue_mask;
+}
+
+int
+visual_pixmap_depth (Screen *s, Visual *v)
+{
+ return 32;
+}
+
+int
+screen_number (Screen *screen)
+{
+ return 0;
+}
+
+// declared in utils/grabclient.h
+Bool
+use_subwindow_mode_p (Screen *screen, Window window)
+{
+ return False;
+}
+
+#endif /* HAVE_JWXYZ */