1 /* xscreensaver, Copyright (c) 1991-2016 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 OpenGL-ish things that bear some resemblance to the
16 things that Xlib might have done.
18 This is the version of jwxyz for Android. The version used by MacOS
19 and iOS is in jwxyz.m.
22 /* Be advised, this is all very much a work in progress.
24 TODO: The following should be implemented before OpenGL can be considered
26 - Above all, pick the smallest not-yet working hack that utilizes the
28 - Half-ass the drawing functions.
29 - [OK] What Interference needs
31 - Whatever clipping is used in XScreenSaver (shape and/or bitmap clipping)
32 - Delayed context creation to support anti-aliasing/multisampling
33 - Everything these hacks need:
34 - FuzzyFlakes (needs wide lines)
37 - [OK] Get DangerBall going.
39 - [Interference, so far...] And fast, too.
40 - And text really needs to work for the FPS display. */
42 /* Also, Take note that OS X can actually run with 256 colors. */
45 - malloc error checking
46 - Check max texture sizes for XGet/PutImage, XCopyArea.
47 - Optional 5:5:5 16-bit color
50 /* Techniques & notes:
51 - iOS: OpenGL ES 2.0 isn't always available. Use OpenGL ES 1.1.
52 - OS X: Drivers can go back to OpenGL 1.1 (GeForce 2 MX on 10.5.8).
53 - Use stencil buffers (OpenGL 1.0+) for bitmap clipping masks.
54 - glLogicOp is an actual thing that should work for GCs.
55 - Pixmaps can be any of the following, depending on GL implementation.
56 - This requires offscreen rendering. Fortunately, this is always
58 - OS X: Drawable objects, including: pixel buffers and offscreen
60 - Offscreen buffers w/ CGLSetOffScreen (10.0+)
61 - http://lists.apple.com/archives/mac-opengl/2002/Jun/msg00087.html
62 provides a very ugly solution for hardware-accelerated offscreen
63 rendering with CGLSetParameter(*, kCGLCPSurfaceTexture, *) on OS X
64 10.1+. Apple docs say it's actually for OS X 10.3+, instead.
65 - Pixel buffers w/ CGLSetPBuffer (10.3+, now deprecated)
66 - Requires APPLE_pixel_buffer.
67 - Available in software on x86 only.
68 - Always available on hardware.
69 - Need to blit? Use OpenGL pixel buffers. (GL_PIXEL_PACK_BUFFER)
70 - Framebuffer objects w/ GL_(ARB|EXT)_framebuffer_object
71 - TODO: Can EXT_framebuffers be different sizes?
73 - iOS: Use OES_framebuffer_object, it's always present.
76 /* OpenGL hacks call a number of X11 functions, including
77 * XCopyArea, XDrawString, XGetImage
78 * XCreatePixmap, XCreateGC, XCreateImage
80 * Check these, of course. */
82 #ifdef JWXYZ_GL /* entire file */
94 # import <QuartzCore/QuartzCore.h>
95 # include <OpenGLES/ES1/gl.h>
96 # include <OpenGLES/ES1/glext.h>
98 # define NSView UIView
99 # define NSRect CGRect
100 # define NSPoint CGPoint
101 # define NSSize CGSize
102 # define NSColor UIColor
103 # define NSImage UIImage
104 # define NSEvent UIEvent
105 # define NSFont UIFont
106 # define NSGlyph CGGlyph
107 # define NSWindow UIWindow
108 # define NSMakeSize CGSizeMake
109 # define NSBezierPath UIBezierPath
110 # define colorWithDeviceRed colorWithRed
112 # define NSFontTraitMask UIFontDescriptorSymbolicTraits
113 // The values for the flags for NSFontTraitMask and
114 // UIFontDescriptorSymbolicTraits match up, not that it really matters here.
115 # define NSBoldFontMask UIFontDescriptorTraitBold
116 # define NSFixedPitchFontMask UIFontDescriptorTraitMonoSpace
117 # define NSItalicFontMask UIFontDescriptorTraitItalic
119 # define NSOpenGLContext EAGLContext
122 # include <OpenGL/glu.h>
125 /* TODO: Does this work on iOS? */
126 # ifndef HAVE_JWZGLES
129 # include <GLES/gl.h>
130 # include <GLES/glext.h>
135 # include "jwzglesI.h"
139 # include <android/log.h>
143 #include "jwxyz-timers.h"
144 #include "yarandom.h"
148 #if defined HAVE_COCOA
149 # include <CoreGraphics/CGGeometry.h>
154 extern void Log(const char *fmt, ...);
157 Log (const char *fmt, ...)
160 va_start (args, fmt);
161 vfprintf (stderr, fmt, args);
170 typedef struct CGPoint CGPoint;
176 typedef struct CGSize CGSize;
182 typedef struct CGRect CGRect;
188 # define MAX(a,b) ((a)>(b)?(a):(b))
189 # define MIN(a,b) ((a)<(b)?(a):(b))
193 /* On 64-bit systems, high bits of the 32-bit pixel are available as scratch
194 space. I doubt if any screen savers need it, but just in case... */
199 struct jwxyz_Display {
203 struct jwxyz_sources_data *timers_data;
205 Bool gl_texture_npot_p;
206 /* Bool opengl_core_p */;
207 GLenum gl_texture_target;
209 // #if defined USE_IPHONE
210 GLuint rect_texture; // Also can work on the desktop.
213 unsigned long window_background;
216 struct jwxyz_Screen {
218 GLenum pixel_format, pixel_type;
219 unsigned long black, white;
227 // CGImageRef clip_mask; // CGImage copy of the Pixmap in gcv.clip_mask
234 int refcount; // for deciding when to release the native font
235 float size; // points
239 // In X11, "Font" is just an ID, and "XFontStruct" contains the metrics.
240 // But we need the metrics on both of them, so they go here.
244 struct jwxyz_XFontSet {
249 /* XGetImage in CoreGraphics JWXYZ has to deal with funky pixel formats
250 necessitating fast & flexible pixel conversion. OpenGL does image format
251 conversion itself, so alloc_color and query_color are mercifully simple.
254 jwxyz_alloc_color (Display *dpy,
255 uint16_t r, uint16_t g, uint16_t b, uint16_t a)
257 union color_bytes color;
259 /* Instead of (int)(c / 256.0), another possibility is
260 (int)(c * 255.0 / 65535.0 + 0.5). This can be calculated using only
261 uint8_t integer_math(uint16_t c) {
262 unsigned c0 = c + 128;
263 return (c0 - (c0 >> 8)) >> 8;
267 color.bytes[0] = r >> 8;
268 color.bytes[1] = g >> 8;
269 color.bytes[2] = b >> 8;
270 color.bytes[3] = a >> 8;
272 if (dpy->screen->pixel_format == GL_BGRA_EXT) {
273 color.pixel = color.bytes[2] |
274 (color.bytes[1] << 8) |
275 (color.bytes[0] << 16) |
276 (color.bytes[3] << 24);
278 Assert(dpy->screen->pixel_format == GL_RGBA,
279 "jwxyz_alloc_color: Unknown pixel_format");
282 return (uint32_t)color.pixel;
285 // Converts an array of pixels ('src') from one format to another, placing the
286 // result in 'dest', according to the pixel conversion mode 'mode'.
288 jwxyz_query_color (Display *dpy, unsigned long pixel, uint8_t *rgba)
290 union color_bytes color;
292 if(dpy->screen->pixel_format == GL_RGBA)
295 for (unsigned i = 0; i != 4; ++i)
296 rgba[i] = color.bytes[i];
300 Assert (dpy->screen->pixel_format == GL_BGRA_EXT,
301 "jwxyz_query_color: Unknown pixel format");
302 /* TODO: Cross-check with XAllocColor. */
303 rgba[0] = (pixel >> 16) & 0xFF;
304 rgba[1] = (pixel >> 8) & 0xFF;
305 rgba[2] = (pixel >> 0) & 0xFF;
306 rgba[3] = (pixel >> 24) & 0xFF;
311 jwxyz_assert_display(Display *dpy)
316 jwxyz_assert_drawable (dpy->main_window, dpy->main_window);
321 jwxyz_set_matrices (Display *dpy, unsigned width, unsigned height,
324 /* TODO: Check registration pattern from Interference with rectangles instead of points. */
326 // The projection matrix is always set as follows. The modelview matrix is
327 // usually identity, but for points and thin lines, it's translated by 0.5.
328 glMatrixMode(GL_PROJECTION);
331 # if defined(USE_IPHONE) || defined(HAVE_ANDROID)
333 if (window_p && ignore_rotation_p(dpy)) {
334 int o = (int) current_device_rotation();
335 glRotatef (-o, 0, 0, 1);
338 // glPointSize(1); // This is the default.
341 glOrthof /* TODO: Is glOrthox worth it? Signs point to no. */
345 (0, width, height, 0, -1, 1);
347 glMatrixMode(GL_MODELVIEW);
348 # endif // HAVE_MOBILE
355 // iOS always uses OpenGL ES 1.1.
361 gl_check_ver (const struct gl_version *caps,
365 return caps->major > gl_major ||
366 (caps->major == gl_major && caps->minor >= gl_minor);
370 static GLboolean gl_check_ext(const struct gl_caps *caps,
373 const char *extension)
376 gl_check_ver(caps, gl_major, gl_minor) ||
377 gluCheckExtension(extension, caps->extensions);
384 // NSOpenGLContext *jwxyz_debug_context;
386 /* We keep a list of all of the Displays that have been created and not
387 yet freed so that they can have sensible display numbers. If three
388 displays are created (0, 1, 2) and then #1 is closed, then the fourth
389 display will be given the now-unused display number 1. (Everything in
390 here assumes a 1:1 Display/Screen mapping.)
392 The size of this array is the most number of live displays at one time.
393 So if it's 20, then we'll blow up if the system has 19 monitors and also
394 has System Preferences open (the small preview window).
396 Note that xlockmore-style savers tend to allocate big structures, so
397 setting this to 1000 will waste a few megabytes. Also some of them assume
398 that the number of screens never changes, so dynamically expanding this
402 static Display *jwxyz_live_displays[20] = { 0, };
407 jwxyz_make_display (Window w)
409 Display *d = (Display *) calloc (1, sizeof(*d));
410 d->screen = (Screen *) calloc (1, sizeof(Screen));
414 d->screen->screen_number = 0;
417 // Find the first empty slot in live_displays and plug us in.
418 int size = sizeof(jwxyz_live_displays) / sizeof(*jwxyz_live_displays);
420 for (i = 0; i < size; i++) {
421 if (! jwxyz_live_displays[i])
424 if (i >= size) abort();
425 jwxyz_live_displays[i] = d;
426 d->screen_count = size;
427 d->screen->screen_number = i;
429 # endif // !USE_IPHONE
431 # ifndef HAVE_JWZGLES
432 struct gl_version version;
435 const GLubyte *version_str = glGetString (GL_VERSION);
437 /* iPhone is always OpenGL ES 1.1. */
438 if (sscanf ((const char *) version_str, "%u.%u",
439 &version.major, &version.minor) < 2)
445 # endif // !HAVE_JWZGLES
447 const GLubyte *extensions = glGetString (GL_EXTENSIONS);
450 - Apple TN2080: Understanding and Detecting OpenGL Functionality.
451 - OpenGL Programming Guide for the Mac - Best Practices for Working with
452 Texture Data - Optimal Data Formats and Types
455 // If a video adapter suports BGRA textures, then that's probably as fast as
456 // you're gonna get for getting a texture onto the screen.
458 /* TODO: Make BGRA work on iOS. As it is, it breaks XPutImage. (glTexImage2D, AFAIK) */
459 d->screen->pixel_format = GL_RGBA; /*
460 gluCheckExtension ((const GLubyte *) "GL_APPLE_texture_format_BGRA8888",
461 extensions) ? GL_BGRA_EXT : GL_RGBA; */
462 d->screen->pixel_type = GL_UNSIGNED_BYTE;
463 // See also OES_read_format.
464 # else // !HAVE_JWZGLES
465 if (gl_check_ver (&version, 1, 2) ||
466 (gluCheckExtension ((const GLubyte *) "GL_EXT_bgra", extensions) &&
467 gluCheckExtension ((const GLubyte *) "GL_APPLE_packed_pixels",
469 d->screen->pixel_format = GL_BGRA_EXT;
470 // Both Intel and PowerPC-era docs say to use GL_UNSIGNED_INT_8_8_8_8_REV.
471 d->screen->pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
473 d->screen->pixel_format = GL_RGBA;
474 d->screen->pixel_type = GL_UNSIGNED_BYTE;
476 // GL_ABGR_EXT/GL_UNSIGNED_BYTE is another possibilty that may have made more
478 # endif // !HAVE_JWZGLES
480 // On really old systems, it would make sense to split the texture
482 # ifndef HAVE_JWZGLES
483 d->gl_texture_npot_p = gluCheckExtension ((const GLubyte *)
484 "GL_ARB_texture_rectangle",
486 d->gl_texture_target = d->gl_texture_npot_p ?
487 GL_TEXTURE_RECTANGLE_EXT :
490 d->gl_texture_npot_p = jwzgles_gluCheckExtension
491 ((const GLubyte *) "GL_APPLE_texture_2D_limited_npot", extensions) ||
492 jwzgles_gluCheckExtension
493 ((const GLubyte *) "GL_OES_texture_npot", extensions) ||
494 jwzgles_gluCheckExtension // From PixelFlinger 1.4
495 ((const GLubyte *) "GL_ARB_texture_non_power_of_two", extensions);
497 d->gl_texture_target = GL_TEXTURE_2D;
500 d->screen->black = jwxyz_alloc_color (d, 0x0000, 0x0000, 0x0000, 0xFFFF);
501 d->screen->white = jwxyz_alloc_color (d, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF);
503 Visual *v = (Visual *) calloc (1, sizeof(Visual));
504 v->class = TrueColor;
505 v->red_mask = jwxyz_alloc_color (d, 0xFFFF, 0x0000, 0x0000, 0x0000);
506 v->green_mask = jwxyz_alloc_color (d, 0x0000, 0xFFFF, 0x0000, 0x0000);
507 v->blue_mask = jwxyz_alloc_color (d, 0x0000, 0x0000, 0xFFFF, 0x0000);
509 d->screen->visual = v;
511 d->timers_data = jwxyz_sources_init (XtDisplayToApplicationContext (d));
513 d->window_background = BlackPixel(d,0);
517 fputs((char *)glGetString(GL_VENDOR), stderr);
519 fputs((char *)glGetString(GL_RENDERER), stderr);
521 fputs((char *)glGetString(GL_VERSION), stderr);
523 // puts(caps.extensions);
524 GLint max_texture_size;
525 glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max_texture_size);
526 printf ("GL_MAX_TEXTURE_SIZE: %d\n", max_texture_size);
529 // In case a GL hack wants to use X11 to draw offscreen, the rect_texture is available.
530 Assert (d->main_window == w, "Uh-oh.");
531 glGenTextures (1, &d->rect_texture);
532 // TODO: Check for (ARB|EXT|NV)_texture_rectangle. (All three are alike.)
533 // Rectangle textures should be present on OS X with the following exceptions:
534 // - Generic renderer on PowerPC OS X 10.4 and earlier
536 glBindTexture (d->gl_texture_target, d->rect_texture);
537 // TODO: This is all somewhere else. Refactor.
538 glTexParameteri (d->gl_texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
539 glTexParameteri (d->gl_texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
541 // This might be redundant for rectangular textures.
542 # ifndef HAVE_JWZGLES
543 const GLint wrap = GL_CLAMP;
544 # else // HAVE_JWZGLES
545 const GLint wrap = GL_CLAMP_TO_EDGE;
546 # endif // HAVE_JWZGLES
548 // In OpenGL, CLAMP_TO_EDGE is OpenGL 1.2 or GL_SGIS_texture_edge_clamp.
549 // This is always present with OpenGL ES.
550 glTexParameteri (d->gl_texture_target, GL_TEXTURE_WRAP_S, wrap);
551 glTexParameteri (d->gl_texture_target, GL_TEXTURE_WRAP_T, wrap);
553 jwxyz_assert_display(d);
558 jwxyz_free_display (Display *dpy)
560 /* TODO: Go over everything. */
562 jwxyz_sources_free (dpy->timers_data);
566 // Find us in live_displays and clear that slot.
567 int size = ScreenCount(dpy);
569 for (i = 0; i < size; i++) {
570 if (dpy == jwxyz_live_displays[i]) {
571 jwxyz_live_displays[i] = 0;
575 if (i >= size) abort();
577 # endif // !USE_IPHONE
579 free (dpy->screen->visual);
585 /* Call this after any modification to the bits on a Pixmap or Window.
586 Most Pixmaps are used frequently as sources and infrequently as
587 destinations, so it pays to cache the data as a CGImage as needed.
590 invalidate_drawable_cache (Drawable d)
592 /* TODO: Kill this outright. jwxyz_bind_drawable handles any potential
599 CGImageRelease (d->cgi);
606 /* Call this when the View changes size or position.
609 jwxyz_window_resized (Display *dpy)
611 const XRectangle *new_frame = jwxyz_frame (dpy->main_window);
612 unsigned new_width = new_frame->width;
613 unsigned new_height = new_frame->height;
615 Assert (new_width, "jwxyz_window_resized: No width.");
616 Assert (new_height, "jwxyz_window_resized: No height.");
618 /*if (cgc) w->cgc = cgc;
619 Assert (w->cgc, "no CGContext"); */
621 Log("resize: %d, %d\n", new_width, new_height);
623 jwxyz_bind_drawable (dpy, dpy->main_window, dpy->main_window);
625 // TODO: What does the iPhone need?
627 // iOS only: If the main_window is not the current_drawable, then set_matrices
628 // was already called in bind_drawable.
629 jwxyz_set_matrices (dpy, new_width, new_height, True);
633 glGetIntegerv (GL_DRAW_BUFFER, &draw_buffer);
635 glDrawBuffer (GL_FRONT);
636 glClearColor (1, 0, 1, 0);
637 glClear (GL_COLOR_BUFFER_BIT);
638 glDrawBuffer (GL_BACK);
639 glClearColor (0, 1, 1, 0);
640 glClear (GL_COLOR_BUFFER_BIT);
642 glDrawBuffer (draw_buffer); */
644 // Stylish and attractive purple!
645 // glClearColor (1, 0, 1, 0.5);
646 // glClear (GL_COLOR_BUFFER_BIT);
648 invalidate_drawable_cache (dpy->main_window);
653 display_sources_data (Display *dpy)
655 return dpy->timers_data;
660 XRootWindow (Display *dpy, int screen)
662 return dpy ? dpy->main_window : 0;
666 XDefaultScreenOfDisplay (Display *dpy)
668 return dpy ? dpy->screen : 0;
672 XDefaultVisualOfScreen (Screen *screen)
674 return screen ? screen->visual : 0;
678 XDisplayOfScreen (Screen *s)
680 return s ? s->dpy : 0;
684 XDisplayNumberOfScreen (Screen *s)
690 XScreenNumberOfScreen (Screen *s)
692 return s? s->screen_number : 0;
696 jwxyz_ScreenCount (Display *dpy)
698 return dpy ? dpy->screen_count : 0;
702 XBlackPixelOfScreen(Screen *screen)
704 return screen->black;
708 XWhitePixelOfScreen(Screen *screen)
710 return screen->white;
714 XCellsOfScreen(Screen *screen)
716 Visual *v = screen->visual;
717 return v->red_mask | v->green_mask | v->blue_mask;
721 /* GC attributes by usage and OpenGL implementation:
723 * All drawing functions:
724 * function | glLogicOp w/ GL_COLOR_LOGIC_OP
725 * clip_x_origin, clip_y_origin, clip_mask | Stencil mask
727 * Shape drawing functions:
728 * foreground, background | glColor*
730 * XDrawLines, XDrawRectangles, XDrawSegments:
731 * line_width, cap_style, join_style | Lotsa vertices
734 * fill_rule | Multiple GL_TRIANGLE_FANs
737 * font | Cocoa, then OpenGL display lists.
739 * alpha_allowed_p | TODO
747 set_clip_mask (GC gc)
749 Assert (!gc->gcv.clip_mask, "set_gc: TODO");
753 set_function (int function)
755 Assert (function == GXcopy, "set_gc: (TODO) Stubbed gcv function");
757 /* TODO: The GL_COLOR_LOGIC_OP extension is exactly what is needed here. (OpenGL 1.1)
758 Fun fact: The glLogicOp opcode constants are the same as the X11 GX* function constants | GL_CLEAR.
762 switch (gc->gcv.function) {
765 case GXcopy:/*CGContextSetBlendMode (cgc, kCGBlendModeNormal);*/ break;
766 case GXxor: CGContextSetBlendMode (cgc, kCGBlendModeDifference); break;
767 case GXor: CGContextSetBlendMode (cgc, kCGBlendModeLighten); break;
768 case GXand: CGContextSetBlendMode (cgc, kCGBlendModeDarken); break;
769 default: Assert(0, "unknown gcv function"); break;
776 set_color (Display *dpy, unsigned long pixel, unsigned int depth,
777 Bool alpha_allowed_p)
779 jwxyz_validate_pixel (dpy, pixel, depth, alpha_allowed_p);
783 glColor4f (f, f, f, 1);
785 /* TODO: alpha_allowed_p */
787 jwxyz_query_color (dpy, pixel, rgba);
789 glColor4f (rgba[0] / 255.0f, rgba[1] / 255.0f,
790 rgba[2] / 255.0f, rgba[3] / 255.0f);
797 /* Pushes a GC context; sets Function, ClipMask, and color.
800 set_color_gc (Display *dpy, GC gc, unsigned long color)
802 // GC is NULL for XClearArea and XClearWindow.
806 function = gc->gcv.function;
811 depth = visual_depth (NULL, NULL);
812 // TODO: Set null clip mask here.
815 set_function (function);
818 case GXset: color = (depth == 1 ? 1 : WhitePixel(dpy,0)); break;
819 case GXclear: color = (depth == 1 ? 0 : BlackPixel(dpy,0)); break;
822 set_color (dpy, color, depth, gc ? gc->gcv.alpha_allowed_p : False);
824 /* TODO: Antialiasing. */
825 /* CGContextSetShouldAntialias (cgc, antialias_p); */
828 /* Pushes a GC context; sets color to the foreground color.
831 set_fg_gc (Display *dpy, GC gc)
833 set_color_gc (dpy, gc, gc->gcv.foreground);
837 next_point(short *v, XPoint p, int mode)
840 case CoordModeOrigin:
844 case CoordModePrevious:
849 Assert (False, "next_point: bad mode");
855 XDrawPoints (Display *dpy, Drawable d, GC gc,
856 XPoint *points, int count, int mode)
858 jwxyz_bind_drawable (dpy, dpy->main_window, d);
864 for (unsigned i = 0; i < count; i++) {
865 next_point(v, points[i], mode);
866 glVertex2f(v[0] + 0.5f, v[1] + 0.5f);
873 // TODO: XPoints can be fed directly to OpenGL.
874 GLshort *gl_points = malloc (count * 2 * sizeof(GLshort)); // TODO: malloc returns NULL.
875 for (unsigned i = 0; i < count; i++) {
876 next_point (v, points[i], mode);
877 gl_points[2 * i] = v[0];
878 gl_points[2 * i + 1] = v[1];
881 glMatrixMode (GL_MODELVIEW);
882 glTranslatef (0.5, 0.5, 0);
884 glEnableClientState (GL_VERTEX_ARRAY);
885 glDisableClientState (GL_TEXTURE_COORD_ARRAY);
886 glVertexPointer (2, GL_SHORT, 0, gl_points);
887 glDrawArrays (GL_POINTS, 0, count);
898 texture_internalformat(Display *dpy)
901 return dpy->screen->pixel_format;
907 static GLenum gl_pixel_type(const Display *dpy)
909 return dpy->screen->pixel_type;
913 clear_texture (Display *dpy)
915 glTexImage2D (dpy->gl_texture_target, 0, texture_internalformat(dpy), 0, 0,
916 0, dpy->screen->pixel_format, gl_pixel_type (dpy), NULL);
919 static void set_white (void)
922 glColor4f (1, 1, 1, 1);
924 glColor3ub (0xff, 0xff, 0xff);
929 static GLsizei to_pow2 (size_t x);
933 jwxyz_gl_copy_area_copy_tex_image (Display *dpy, Drawable src, Drawable dst,
934 GC gc, int src_x, int src_y,
935 unsigned int width, unsigned int height,
936 int dst_x, int dst_y)
938 const XRectangle *src_frame = jwxyz_frame (src);
940 Assert(gc->gcv.function == GXcopy, "XCopyArea: Unknown function");
942 jwxyz_bind_drawable (dpy, dpy->main_window, src);
944 # if defined HAVE_COCOA && !defined USE_IPHONE
945 /* TODO: Does this help? */
949 unsigned tex_w = width, tex_h = height;
950 if (!dpy->gl_texture_npot_p) {
951 tex_w = to_pow2(tex_w);
952 tex_h = to_pow2(tex_h);
955 GLint internalformat = texture_internalformat(dpy);
957 glBindTexture (dpy->gl_texture_target, dpy->rect_texture);
959 if (tex_w == width && tex_h == height) {
960 glCopyTexImage2D (dpy->gl_texture_target, 0, internalformat,
961 src_x, src_frame->height - src_y - height,
964 glTexImage2D (dpy->gl_texture_target, 0, internalformat, tex_w, tex_h,
965 0, dpy->screen->pixel_format, gl_pixel_type(dpy), NULL);
966 glCopyTexSubImage2D (dpy->gl_texture_target, 0, 0, 0,
967 src_x, src_frame->height - src_y - height,
971 jwxyz_bind_drawable (dpy, dpy->main_window, dst);
973 glBindTexture (dpy->gl_texture_target, dpy->rect_texture);
974 glEnable (dpy->gl_texture_target);
976 glEnableClientState (GL_TEXTURE_COORD_ARRAY);
977 glEnableClientState (GL_VERTEX_ARRAY);
979 /* TODO: Copied from XPutImage. Refactor. */
980 /* TODO: EXT_draw_texture or whatever it's called. */
981 GLfloat vertices[4][2] =
984 {dst_x, dst_y + height},
985 {dst_x + width, dst_y + height},
986 {dst_x + width, dst_y}
990 static const GLshort tex_coords[4][2] = {{0, 1}, {0, 0}, {1, 0}, {1, 1}};
992 GLshort tex_coords[4][2] = {{0, height}, {0, 0}, {width, 0}, {width, height}};
995 glVertexPointer (2, GL_FLOAT, 0, vertices);
996 glTexCoordPointer (2, GL_SHORT, 0, tex_coords);
997 glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
1001 glDisable (dpy->gl_texture_target);
1005 jwxyz_gl_copy_area_read_pixels (Display *dpy, Drawable src, Drawable dst,
1006 GC gc, int src_x, int src_y,
1007 unsigned int width, unsigned int height,
1008 int dst_x, int dst_y)
1011 XImage *img = XGetImage (dpy, src, src_x, src_y, width, height, ~0, ZPixmap);
1012 XPutImage (dpy, dst, gc, img, 0, 0, dst_x, dst_y, width, height);
1013 XDestroyImage (img);
1017 /* Something may or may not be broken in here. (shrug) */
1018 bind_drawable(dpy, src);
1020 /* Error checking would be nice. */
1021 void *pixels = malloc (src_rect.size.width * 4 * src_rect.size.height);
1023 glPixelStorei (GL_PACK_ROW_LENGTH, 0);
1024 glPixelStorei (GL_PACK_ALIGNMENT, 4);
1026 glReadPixels (src_rect.origin.x, dst_frame.size.height - (src_rect.origin.y + src_rect.size.height),
1027 src_rect.size.width, src_rect.size.height,
1028 GL_RGBA, GL_UNSIGNED_BYTE, // TODO: Pick better formats.
1031 bind_drawable (dpy, dst);
1033 glPixelZoom (1.0f, 1.0f);
1035 glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
1036 glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
1038 glRasterPos2i (dst_rect.origin.x, dst_rect.origin.y + dst_rect.size.height);
1039 glDrawPixels (dst_rect.size.width, dst_rect.size.height,
1040 GL_RGBA, GL_UNSIGNED_BYTE, pixels);
1048 // TODO: Make sure offset works in super-sampled mode.
1050 adjust_point_for_line (GC gc, CGPoint *p)
1052 // Here's the authoritative discussion on how X draws lines:
1053 // http://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:CreateGC:line-width
1054 if (gc->gcv.line_width <= 1) {
1055 /* Thin lines are "drawn using an unspecified, device-dependent
1056 algorithm", but seriously though, Bresenham's algorithm. Bresenham's
1057 algorithm runs to and from pixel centers.
1059 There's a few screenhacks (Maze, at the very least) that set line_width
1060 to 1 when it probably should be set to 0, so it's line_width <= 1
1066 /* Thick lines OTOH run from the upper-left corners of pixels. This means
1067 that a horizontal thick line of width 1 straddles two scan lines.
1068 Aliasing requires one of these scan lines be chosen; the following
1069 nudges the point so that the right choice is made. */
1077 XDrawLine (Display *dpy, Drawable d, GC gc, int x1, int y1, int x2, int y2)
1079 // TODO: XDrawLine == XDrawSegments(nlines == 1), also in jwxyz.m
1085 XDrawSegments (dpy, d, gc, &segment, 1);
1087 // when drawing a zero-length line, obey line-width and cap-style.
1088 /* if (x1 == x2 && y1 == y2) {
1089 int w = gc->gcv.line_width;
1092 if (gc->gcv.line_width > 1 && gc->gcv.cap_style == CapRound)
1093 return XFillArc (dpy, d, gc, x1, y1, w, w, 0, 360*64);
1096 w = 1; // Actually show zero-length lines.
1097 return XFillRectangle (dpy, d, gc, x1, y1, w, w);
1101 CGPoint p = point_for_line (d, gc, x1, y1);
1103 push_fg_gc (dpy, d, gc, NO);
1105 CGContextRef cgc = d->cgc;
1106 set_line_mode (cgc, &gc->gcv);
1107 CGContextBeginPath (cgc);
1108 CGContextMoveToPoint (cgc, p.x, p.y);
1109 p = point_for_line(d, gc, x2, y2);
1110 CGContextAddLineToPoint (cgc, p.x, p.y);
1111 CGContextStrokePath (cgc);
1113 invalidate_drawable_cache (d); */
1118 XDrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count,
1121 jwxyz_bind_drawable (dpy, dpy->main_window, d);
1122 set_fg_gc (dpy, gc);
1124 /* TODO: Thick lines
1125 * Zero-length line segments
1126 * Paths with zero length total (Remember line width, cap style.)
1133 GLshort *vertices = malloc(2 * sizeof(GLshort) * count); // TODO malloc NULL sigh
1135 glMatrixMode (GL_MODELVIEW);
1136 glTranslatef (0.5f, 0.5f, 0);
1138 short p[2] = {0, 0};
1139 for (unsigned i = 0; i < count; i++) {
1140 next_point (p, points[i], mode);
1141 vertices[2 * i] = p[0];
1142 vertices[2 * i + 1] = p[1];
1145 glEnableClientState (GL_VERTEX_ARRAY);
1146 glDisableClientState (GL_TEXTURE_COORD_ARRAY);
1147 glVertexPointer (2, GL_SHORT, 0, vertices);
1148 glDrawArrays (GL_LINE_STRIP, 0, count);
1152 if (gc->gcv.cap_style != CapNotLast) {
1153 // TODO: How does this look with multisampling?
1154 // TODO: Disable me for closed loops.
1155 glVertexPointer (2, GL_SHORT, 0, p);
1156 glDrawArrays (GL_POINTS, 0, 1);
1166 XDrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count)
1168 jwxyz_bind_drawable (dpy, dpy->main_window, d);
1169 set_fg_gc (dpy, gc);
1171 /* TODO: Thick lines. */
1172 /* Thin lines <= 1px are offset by +0.5; thick lines are not. */
1174 glMatrixMode (GL_MODELVIEW);
1175 glTranslatef (0.5, 0.5, 0);
1177 glEnableClientState (GL_VERTEX_ARRAY);
1178 glDisableClientState (GL_TEXTURE_COORD_ARRAY);
1180 Assert (sizeof(XSegment) == sizeof(short) * 4, "XDrawSegments: Data alignment mix-up."); // TODO: Static assert here.
1181 Assert (sizeof(GLshort) == sizeof(short), "XDrawSegments: Data alignment mix-up."); // TODO: Static assert here.
1182 Assert (offsetof(XSegment, x1) == 0, "XDrawSegments: Data alignment mix-up.");
1183 Assert (offsetof(XSegment, x2) == 4, "XDrawSegments: Data alignment mix-up.");
1184 glVertexPointer (2, GL_SHORT, 0, segments);
1185 glDrawArrays (GL_LINES, 0, count * 2);
1187 if (gc->gcv.cap_style != CapNotLast) {
1188 glVertexPointer (2, GL_SHORT, sizeof(GLshort) * 4, (const GLshort *)segments + 2);
1189 glDrawArrays (GL_POINTS, 0, count);
1194 /* CGRect wr = d->frame;
1195 push_fg_gc (dpy, d, gc, NO);
1196 set_line_mode (cgc, &gc->gcv);
1197 CGContextBeginPath (cgc);
1198 for (i = 0; i < count; i++) {
1199 CGPoint p = point_for_line (d, gc, segments->x1, segments->y1);
1200 CGContextMoveToPoint (cgc, p.x, p.y);
1201 p = point_for_line (d, gc, segments->x2, segments->y2);
1202 CGContextAddLineToPoint (cgc, p.x, p.y);
1205 CGContextStrokePath (cgc);
1207 invalidate_drawable_cache (d); */
1213 XClearWindow (Display *dpy, Window win)
1215 Assert (win == dpy->main_window, "not a window");
1216 const XRectangle *wr = jwxyz_frame (win);
1217 /* TODO: Use glClear if there's no background pixmap. */
1218 return XClearArea (dpy, win, 0, 0, wr->width, wr->height, 0);
1222 jwxyz_window_background (Display *dpy)
1224 return dpy->window_background;
1228 XSetWindowBackground (Display *dpy, Window w, unsigned long pixel)
1230 Assert (w == dpy->main_window, "not a window");
1231 jwxyz_validate_pixel (dpy, pixel, visual_depth (NULL, NULL), False);
1232 dpy->window_background = pixel;
1237 jwxyz_fill_rects (Display *dpy, Drawable d, GC gc,
1238 const XRectangle *rectangles, unsigned long nrectangles,
1239 unsigned long pixel)
1241 jwxyz_bind_drawable (dpy, dpy->main_window, d);
1242 set_color_gc (dpy, gc, pixel);
1245 for (unsigned i = 0; i != nrectangles; ++i) {
1246 const XRectangle *r = &rectangles[i];
1247 glVertex2i(r->x, r->y);
1248 glVertex2i(r->x, r->y + r->height);
1249 glVertex2i(r->x + r->width, r->y + r->height);
1250 glVertex2i(r->x + r->width, r->y);
1254 glEnableClientState (GL_VERTEX_ARRAY);
1255 glDisableClientState (GL_TEXTURE_COORD_ARRAY);
1257 for (unsigned long i = 0; i != nrectangles; ++i)
1259 const XRectangle *r = &rectangles[i];
1261 GLfloat coords[4][2] =
1264 {r->x, r->y + r->height},
1265 {r->x + r->width, r->y + r->height},
1266 {r->x + r->width, r->y}
1269 // TODO: Several rects at once. Maybe even tune for XScreenSaver workloads.
1270 glVertexPointer (2, GL_FLOAT, 0, coords);
1272 glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
1279 XClearArea (Display *dpy, Window win, int x, int y, int w, int h, Bool exp)
1281 Assert(win == dpy->main_window, "XClearArea: not a window");
1282 Assert(!exp, "XClearArea: exposures unsupported");
1284 jwxyz_fill_rect (dpy, win, 0, x, y, w, h, dpy->window_background);
1290 XFillPolygon (Display *dpy, Drawable d, GC gc,
1291 XPoint *points, int npoints, int shape, int mode)
1293 jwxyz_bind_drawable (dpy, dpy->main_window, d);
1296 // TODO: Re-implement the GLU tesselation functions.
1298 /* Complex: Pedal, and for some reason Attraction, Mountain, Qix, SpeedMine, Starfish
1299 * Nonconvex: Goop, Pacman, Rocks, Speedmine
1302 Assert(shape == Convex, "XFillPolygon: (TODO) Unimplemented shape");
1304 // TODO: Feed vertices straight to OpenGL for CoordModeOrigin.
1305 GLshort *vertices = malloc(npoints * sizeof(GLshort) * 2); // TODO: Oh look, another unchecked malloc.
1306 short v[2] = {0, 0};
1308 for (unsigned i = 0; i < npoints; i++) {
1309 next_point(v, points[i], mode);
1310 vertices[2 * i] = v[0];
1311 vertices[2 * i + 1] = v[1];
1314 glEnableClientState (GL_VERTEX_ARRAY);
1315 glDisableClientState (GL_TEXTURE_COORD_ARRAY);
1317 glVertexPointer (2, GL_SHORT, 0, vertices);
1318 glDrawArrays (GL_TRIANGLE_FAN, 0, npoints);
1323 CGRect wr = d->frame;
1325 push_fg_gc (dpy, d, gc, YES);
1326 CGContextRef cgc = d->cgc;
1327 CGContextBeginPath (cgc);
1329 for (i = 0; i < npoints; i++) {
1330 if (i > 0 && mode == CoordModePrevious) {
1334 x = wr.origin.x + points[i].x;
1335 y = wr.origin.y + wr.size.height - points[i].y;
1339 CGContextMoveToPoint (cgc, x, y);
1341 CGContextAddLineToPoint (cgc, x, y);
1343 CGContextClosePath (cgc);
1344 if (gc->gcv.fill_rule == EvenOddRule)
1345 CGContextEOFillPath (cgc);
1347 CGContextFillPath (cgc);
1349 invalidate_drawable_cache (d);
1354 #define radians(DEG) ((DEG) * M_PI / 180.0)
1355 #define degrees(RAD) ((RAD) * 180.0 / M_PI)
1358 arc_xy(GLfloat *p, double cx, double cy, double w2, double h2, double theta)
1360 p[0] = cos(theta) * w2 + cx;
1361 p[1] = -sin(theta) * h2 + cy;
1365 mod_neg(int a, unsigned b)
1367 /* Normal modulus is implementation defined for negative numbers. This is
1368 * well-defined such that the repeating pattern for a >= 0 is maintained for
1370 return a < 0 ? (b - 1) - (-(a + 1) % b) : a % b;
1374 jwxyz_draw_arc (Display *dpy, Drawable d, GC gc, int x, int y,
1375 unsigned int width, unsigned int height,
1376 int angle1, int angle2, Bool fill_p)
1378 jwxyz_bind_drawable (dpy, dpy->main_window, d);
1381 /* Let's say the number of line segments needed to make a convincing circle is
1382 4*sqrt(radius). (But these arcs aren't necessarily circular arcs...) */
1384 double w2 = width * 0.5f, h2 = height * 0.5f;
1385 double a, b; /* Semi-major/minor axes. */
1394 const double two_pi = 2 * M_PI;
1396 double amb = a - b, apb = a + b;
1397 double h = (amb * amb) / (apb * apb);
1398 // TODO: Math cleanup.
1399 double C_approx = M_PI * apb * (1 + 3 * h / (10 + sqrtf(4 - 3 * h)));
1400 double segments_f = 4 * sqrtf(C_approx / (2 * M_PI));
1402 // TODO: Explain how drawing works what with the points of overlapping arcs
1406 unsigned segments_360 = segments_f;
1408 /* TODO: angle2 == 0. This is a tilted square with CapSquare. */
1409 /* TODO: color, thick lines, CapNotLast for thin lines */
1410 /* TODO: Transformations. */
1412 double segment_angle = two_pi / segments_360;
1414 const unsigned deg64 = 360 * 64;
1415 const double rad_from_deg64 = two_pi / deg64;
1422 angle1 = mod_neg(angle1, deg64); // TODO: Is this OK? Consider negative numbers.
1425 angle2 = deg64; // TODO: Handle circles special.
1428 angle1_f = angle1 * rad_from_deg64,
1429 angle2_f = angle2 * rad_from_deg64;
1431 if (angle2_f > two_pi) // TODO: Move this up.
1434 double segment1_angle_part = fmodf(angle1_f, segment_angle);
1436 unsigned segment1 = ((angle1_f - segment1_angle_part) / segment_angle) + 1.5;
1438 double angle_2r = angle2_f - segment1_angle_part;
1439 unsigned segments = angle_2r / segment_angle;
1441 GLfloat cx = x + w2, cy = y + h2;
1443 GLfloat *data = malloc((segments + 3) * sizeof(GLfloat) * 2); // TODO: Check result.
1445 GLfloat *data_ptr = data;
1452 arc_xy (data_ptr, cx, cy, w2, h2, angle1_f);
1455 for (unsigned s = 0; s != segments; ++s) {
1456 // TODO: Make sure values of theta for the following arc_xy call are between
1457 // angle1_f and angle1_f + angle2_f.
1458 arc_xy (data_ptr, cx, cy, w2, h2, (segment1 + s) * segment_angle);
1462 arc_xy (data_ptr, cx, cy, w2, h2, angle1_f + angle2_f);
1465 glDisableClientState (GL_TEXTURE_COORD_ARRAY);
1466 glEnableClientState (GL_VERTEX_ARRAY);
1468 glVertexPointer (2, GL_FLOAT, 0, data);
1469 glDrawArrays (fill_p ? GL_TRIANGLE_FAN : GL_LINE_STRIP,
1471 (GLsizei)((data_ptr - data) / 2));
1478 unsigned segments = segments_f * (fabs(angle2) / (360 * 64));
1480 glBegin (fill_p ? GL_TRIANGLE_FAN : GL_LINE_STRIP);
1482 if (fill_p /* && gc->gcv.arc_mode == ArcPieSlice */)
1483 glVertex2f (cx, cy);
1485 /* TODO: This should fix the middle points of the arc so that the starting and ending points are OK. */
1487 float to_radians = 2 * M_PI / (360 * 64);
1488 float theta = angle1 * to_radians, d_theta = angle2 * to_radians / segments;
1490 for (unsigned s = 0; s != segments + 1; ++s) /* TODO: This is the right number of segments, yes? */
1492 glVertex2f(cos(theta) * w2 + cx, -sin(theta) * h2 + cy);
1504 jwxyz_gc_gcv (GC gc)
1511 jwxyz_gc_depth (GC gc)
1518 XCreateGC (Display *dpy, Drawable d, unsigned long mask, XGCValues *xgcv)
1520 struct jwxyz_GC *gc = (struct jwxyz_GC *) calloc (1, sizeof(*gc));
1521 gc->depth = jwxyz_drawable_depth (d);
1523 jwxyz_gcv_defaults (dpy, &gc->gcv, gc->depth);
1524 XChangeGC (dpy, gc, mask, xgcv);
1530 XFreeGC (Display *dpy, GC gc)
1533 XUnloadFont (dpy, gc->gcv.font);
1537 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
1539 if (gc->gcv.clip_mask) {
1540 XFreePixmap (dpy, gc->gcv.clip_mask);
1541 CGImageRelease (gc->clip_mask);
1551 flipbits (unsigned const char *in, unsigned char *out, int length)
1553 static const unsigned char table[256] = {
1554 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0,
1555 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
1556 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8,
1557 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
1558 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4,
1559 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
1560 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC,
1561 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
1562 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2,
1563 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
1564 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA,
1565 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
1566 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6,
1567 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
1568 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE,
1569 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
1570 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1,
1571 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
1572 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9,
1573 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
1574 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5,
1575 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
1576 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED,
1577 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
1578 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3,
1579 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
1580 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB,
1581 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
1582 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7,
1583 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
1584 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF,
1585 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
1587 while (length-- > 0)
1588 *out++ = table[*in++];
1592 // Copied and pasted from OSX/XScreenSaverView.m
1599 size_t mask = (size_t)-1;
1600 unsigned bits = sizeof(x) * CHAR_BIT;
1601 unsigned log2 = bits;
1619 XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
1620 int src_x, int src_y, int dest_x, int dest_y,
1621 unsigned int w, unsigned int h)
1623 jwxyz_assert_display (dpy);
1625 const XRectangle *wr = jwxyz_frame (d);
1627 Assert (gc, "no GC");
1628 Assert ((w < 65535), "improbably large width");
1629 Assert ((h < 65535), "improbably large height");
1630 Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x");
1631 Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y");
1632 Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x");
1633 Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y");
1635 // Clip width and height to the bounds of the Drawable
1637 if (dest_x + w > wr->width) {
1638 if (dest_x > wr->width)
1640 w = wr->width - dest_x;
1642 if (dest_y + h > wr->height) {
1643 if (dest_y > wr->height)
1645 h = wr->height - dest_y;
1647 if (w <= 0 || h <= 0)
1650 // Clip width and height to the bounds of the XImage
1652 if (src_x + w > ximage->width) {
1653 if (src_x > ximage->width)
1655 w = ximage->width - src_x;
1657 if (src_y + h > ximage->height) {
1658 if (src_y > ximage->height)
1660 h = ximage->height - src_y;
1662 if (w <= 0 || h <= 0)
1665 /* Assert (d->win */
1667 if (jwxyz_dumb_drawing_mode(dpy, d, gc, dest_x, dest_y, w, h))
1670 jwxyz_bind_drawable (dpy, dpy->main_window, d);
1671 int bpl = ximage->bytes_per_line;
1672 int bpp = ximage->bits_per_pixel;
1673 /* int bsize = bpl * h; */
1674 char *data = ximage->data;
1678 r.origin.x = wr->x + dest_x;
1679 r.origin.y = wr->y + wr->height - dest_y - h;
1684 Assert (gc->gcv.function == GXcopy, "XPutImage: (TODO) GC function not supported");
1685 Assert (!gc->gcv.clip_mask, "XPutImage: (TODO) GC clip mask not supported");
1689 /* Take advantage of the fact that it's ok for (bpl != w * bpp)
1690 to create a CGImage from a sub-rectagle of the XImage.
1692 data += (src_y * bpl) + (src_x * 4);
1694 jwxyz_assert_display(dpy);
1696 /* There probably won't be any hacks that do this, but... */
1697 Assert (!(bpl % 4), "XPutImage: bytes_per_line not divisible by four.");
1699 unsigned src_w = bpl / 4;
1701 /* GL_UNPACK_ROW_LENGTH is not allowed to be negative. (sigh) */
1702 # ifndef HAVE_JWZGLES
1703 glPixelStorei (GL_UNPACK_ROW_LENGTH, src_w);
1707 glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
1709 # if 1 // defined HAVE_JWZGLES
1710 // Regular OpenGL uses GL_TEXTURE_RECTANGLE_EXT in place of GL_TEXTURE_2D.
1711 // TODO: Make use of OES_draw_texture.
1712 // TODO: Coords might be wrong; things might be upside-down or backwards
1715 unsigned tex_w = src_w, tex_h = h;
1716 if (!dpy->gl_texture_npot_p) {
1717 tex_w = to_pow2(tex_w);
1718 tex_h = to_pow2(tex_h);
1721 GLint internalformat = texture_internalformat(dpy);
1723 glBindTexture (dpy->gl_texture_target, dpy->rect_texture);
1725 if (tex_w == src_w && tex_h == h) {
1726 glTexImage2D (dpy->gl_texture_target, 0, internalformat, tex_w, tex_h,
1727 0, dpy->screen->pixel_format, gl_pixel_type(dpy), data);
1729 // TODO: Sampling the last row might be a problem if src_x != 0.
1730 glTexImage2D (dpy->gl_texture_target, 0, internalformat, tex_w, tex_h,
1731 0, dpy->screen->pixel_format, gl_pixel_type(dpy), NULL);
1732 glTexSubImage2D (dpy->gl_texture_target, 0, 0, 0, src_w, h,
1733 dpy->screen->pixel_format, gl_pixel_type(dpy), data);
1737 // glEnable (dpy->gl_texture_target);
1738 // glColor4f (0.5, 0, 1, 1);
1739 glEnable (dpy->gl_texture_target);
1740 glEnableClientState (GL_VERTEX_ARRAY);
1741 glEnableClientState (GL_TEXTURE_COORD_ARRAY);
1743 // TODO: Why are these ever turned on in the first place?
1744 glDisableClientState (GL_COLOR_ARRAY);
1745 glDisableClientState (GL_NORMAL_ARRAY);
1746 // glDisableClientState (GL_TEXTURE_COORD_ARRAY);
1748 GLfloat vertices[4][2] =
1751 {dest_x, dest_y + h},
1752 {dest_x + w, dest_y + h},
1753 {dest_x + w, dest_y}
1756 GLfloat texcoord_w, texcoord_h;
1757 # ifndef HAVE_JWZGLES
1758 if (dpy->gl_texture_target == GL_TEXTURE_RECTANGLE_EXT) {
1762 # endif /* HAVE_JWZGLES */
1764 texcoord_w = (double)w / tex_w;
1765 texcoord_h = (double)h / tex_h;
1768 GLfloat tex_coords[4][2];
1769 tex_coords[0][0] = 0;
1770 tex_coords[0][1] = 0;
1771 tex_coords[1][0] = 0;
1772 tex_coords[1][1] = texcoord_h;
1773 tex_coords[2][0] = texcoord_w;
1774 tex_coords[2][1] = texcoord_h;
1775 tex_coords[3][0] = texcoord_w;
1776 tex_coords[3][1] = 0;
1778 glVertexPointer (2, GL_FLOAT, 0, vertices);
1779 glTexCoordPointer (2, GL_FLOAT, 0, tex_coords);
1781 // Respect the alpha channel in the XImage
1782 glEnable (GL_BLEND);
1783 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1785 glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
1787 glDisable (GL_BLEND);
1790 glDisable (dpy->gl_texture_target);
1792 glRasterPos2i (dest_x, dest_y);
1793 glPixelZoom (1, -1);
1794 jwxyz_assert_display (dpy);
1795 glDrawPixels (w, h, dpy->screen->pixel_format, gl_pixel_type(dpy), data);
1797 } else { // (bpp == 1)
1799 // Assert(FALSE, "XPutImage: TODO");
1800 // Check out ximage_(get|put)pixel_1
1803 /* To draw a 1bpp image, we use it as a mask and fill two rectangles.
1805 #### However, the bit order within a byte in a 1bpp XImage is
1806 the wrong way around from what Quartz expects, so first we
1807 have to copy the data to reverse it. Shit! Maybe it
1808 would be worthwhile to go through the hacks and #ifdef
1809 each one that diddles 1bpp XImage->data directly...
1811 Assert ((src_x % 8) == 0,
1812 "XPutImage with non-byte-aligned 1bpp X offset not implemented");
1814 data += (src_y * bpl) + (src_x / 8); // move to x,y within the data
1815 unsigned char *flipped = (unsigned char *) malloc (bsize);
1817 flipbits ((unsigned char *) data, flipped, bsize);
1819 CGDataProviderRef prov =
1820 CGDataProviderCreateWithData (NULL, flipped, bsize, NULL);
1821 CGImageRef mask = CGImageMaskCreate (w, h,
1824 NULL, /* decode[] */
1825 GL_FALSE); /* interpolate */
1826 push_fg_gc (dpy, d, gc, GL_TRUE);
1828 CGContextFillRect (cgc, r); // foreground color
1829 CGContextClipToMask (cgc, r, mask);
1830 set_color (dpy, cgc, gc->gcv.background, gc->depth, GL_FALSE, GL_TRUE);
1831 CGContextFillRect (cgc, r); // background color
1835 CGDataProviderRelease (prov);
1836 CGImageRelease (mask);
1841 invalidate_drawable_cache (d);
1846 /* At the moment nothing actually uses XGetSubImage. */
1847 /* #### Actually lots of things call XGetImage, which calls XGetSubImage.
1848 E.g., Twang calls XGetImage on the window intending to get a
1849 buffer full of black. This is returning a buffer full of white
1850 instead of black for some reason. */
1852 XGetSubImage (Display *dpy, Drawable d, int x, int y,
1853 unsigned int width, unsigned int height,
1854 unsigned long plane_mask, int format,
1855 XImage *dest_image, int dest_x, int dest_y)
1857 Assert ((width < 65535), "improbably large width");
1858 Assert ((height < 65535), "improbably large height");
1859 Assert ((x < 65535 && x > -65535), "improbably large x");
1860 Assert ((y < 65535 && y > -65535), "improbably large y");
1862 jwxyz_bind_drawable (dpy, dpy->main_window, d);
1864 // TODO: What if this reads off the edge? What is supposed to happen?
1867 // In case the caller tries to write off the edge.
1869 max_width = dest_image->width - dest_x,
1870 max_height = dest_image->height - dest_y;
1872 if (width > max_width) {
1876 if (height > max_height) {
1877 height = max_height;
1881 Assert (jwxyz_drawable_depth (d) == dest_image->depth, "XGetSubImage: depth mismatch");
1883 if (dest_image->depth == visual_depth (NULL, NULL)) {
1884 Assert (!(dest_image->bytes_per_line % 4), "XGetSubImage: bytes_per_line not divisible by 4");
1885 unsigned pixels_per_line = dest_image->bytes_per_line / 4;
1887 Assert (pixels_per_line == width, "XGetSubImage: (TODO) pixels_per_line != width");
1889 glPixelStorei (GL_PACK_ROW_LENGTH, pixels_per_line);
1891 glPixelStorei (GL_PACK_ALIGNMENT, 4);
1893 uint32_t *dest_data = (uint32_t *)dest_image->data + pixels_per_line * dest_y + dest_x;
1895 glReadPixels (x, jwxyz_frame (d)->height - (y + height), width, height,
1896 dpy->screen->pixel_format, gl_pixel_type(dpy), dest_data);
1898 /* Flip this upside down. :( */
1899 uint32_t *top = dest_data;
1900 uint32_t *bottom = dest_data + pixels_per_line * (height - 1);
1901 for (unsigned y = height / 2; y; --y) {
1902 for (unsigned x = 0; x != width; ++x) {
1903 uint32_t px = top[x];
1907 top += pixels_per_line;
1908 bottom -= pixels_per_line;
1912 /* TODO: Actually get pixels. */
1914 Assert (!(dest_x % 8), "XGetSubImage: dest_x not byte-aligned");
1915 uint8_t *dest_data =
1916 (uint8_t *)dest_image->data + dest_image->bytes_per_line * dest_y
1918 for (unsigned y = height / 2; y; --y) {
1919 memset (dest_data, y & 1 ? 0x55 : 0xAA, width / 8);
1920 dest_data += dest_image->bytes_per_line;
1928 XGetImage (Display *dpy, Drawable d, int x, int y,
1929 unsigned int width, unsigned int height,
1930 unsigned long plane_mask, int format)
1932 unsigned depth = jwxyz_drawable_depth (d);
1933 XImage *image = XCreateImage (dpy, 0, depth, format, 0, 0, width, height,
1935 image->data = (char *) malloc (height * image->bytes_per_line);
1937 return XGetSubImage (dpy, d, x, y, width, height, plane_mask, format,
1941 /* Returns a transformation matrix to do rotation as per the provided
1942 EXIF "Orientation" value.
1945 static CGAffineTransform
1946 exif_rotate (int rot, CGSize rect)
1948 CGAffineTransform trans = CGAffineTransformIdentity;
1950 case 2: // flip horizontal
1951 trans = CGAffineTransformMakeTranslation (rect.width, 0);
1952 trans = CGAffineTransformScale (trans, -1, 1);
1955 case 3: // rotate 180
1956 trans = CGAffineTransformMakeTranslation (rect.width, rect.height);
1957 trans = CGAffineTransformRotate (trans, M_PI);
1960 case 4: // flip vertical
1961 trans = CGAffineTransformMakeTranslation (0, rect.height);
1962 trans = CGAffineTransformScale (trans, 1, -1);
1965 case 5: // transpose (UL-to-LR axis)
1966 trans = CGAffineTransformMakeTranslation (rect.height, rect.width);
1967 trans = CGAffineTransformScale (trans, -1, 1);
1968 trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
1971 case 6: // rotate 90
1972 trans = CGAffineTransformMakeTranslation (0, rect.width);
1973 trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
1976 case 7: // transverse (UR-to-LL axis)
1977 trans = CGAffineTransformMakeScale (-1, 1);
1978 trans = CGAffineTransformRotate (trans, M_PI / 2);
1981 case 8: // rotate 270
1982 trans = CGAffineTransformMakeTranslation (rect.height, 0);
1983 trans = CGAffineTransformRotate (trans, M_PI / 2);
1995 jwxyz_draw_NSImage_or_CGImage (Display *dpy, Drawable d,
1996 Bool nsimg_p, void *img_arg,
1997 XRectangle *geom_ret, int exif_rotation)
1999 Assert (False, "jwxyz_draw_NSImage_or_CGImage: TODO stub");
2003 CGImageSourceRef cgsrc;
2004 # endif // USE_IPHONE
2007 CGContextRef cgc = d->cgc;
2011 NSImage *nsimg = (NSImage *) img_arg;
2012 imgr = [nsimg size];
2015 // convert the NSImage to a CGImage via the toll-free-bridging
2016 // of NSData and CFData...
2018 NSData *nsdata = [NSBitmapImageRep
2019 TIFFRepresentationOfImageRepsInArray:
2020 [nsimg representations]];
2021 CFDataRef cfdata = (CFDataRef) nsdata;
2022 cgsrc = CGImageSourceCreateWithData (cfdata, NULL);
2023 cgi = CGImageSourceCreateImageAtIndex (cgsrc, 0, NULL);
2024 # else // USE_IPHONE
2025 cgi = nsimg.CGImage;
2026 # endif // USE_IPHONE
2029 cgi = (CGImageRef) img_arg;
2030 imgr.width = CGImageGetWidth (cgi);
2031 imgr.height = CGImageGetHeight (cgi);
2034 Bool rot_p = (exif_rotation >= 5);
2037 imgr = NSMakeSize (imgr.height, imgr.width);
2039 CGRect winr = d->frame;
2040 float rw = winr.size.width / imgr.width;
2041 float rh = winr.size.height / imgr.height;
2042 float r = (rw < rh ? rw : rh);
2045 dst.size.width = imgr.width * r;
2046 dst.size.height = imgr.height * r;
2047 dst.origin.x = (winr.size.width - dst.size.width) / 2;
2048 dst.origin.y = (winr.size.height - dst.size.height) / 2;
2050 dst2.origin.x = dst2.origin.y = 0;
2052 dst2.size.width = dst.size.height;
2053 dst2.size.height = dst.size.width;
2055 dst2.size = dst.size;
2058 // Clear the part not covered by the image to background or black.
2060 if (d->type == WINDOW)
2061 XClearWindow (dpy, d);
2063 jwxyz_fill_rect (dpy, d, 0, 0, 0, winr.size.width, winr.size.height,
2064 drawable_depth (d) == 1 ? 0 : BlackPixel(dpy,0));
2067 CGAffineTransform trans =
2068 exif_rotate (exif_rotation, rot_p ? dst2.size : dst.size);
2070 CGContextSaveGState (cgc);
2071 CGContextConcatCTM (cgc,
2072 CGAffineTransformMakeTranslation (dst.origin.x,
2074 CGContextConcatCTM (cgc, trans);
2075 //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
2076 CGContextDrawImage (cgc, dst2, cgi);
2077 CGContextRestoreGState (cgc);
2082 CGImageRelease (cgi);
2084 # endif // USE_IPHONE
2087 geom_ret->x = dst.origin.x;
2088 geom_ret->y = dst.origin.y;
2089 geom_ret->width = dst.size.width;
2090 geom_ret->height = dst.size.height;
2093 invalidate_drawable_cache (d);
2097 #ifndef HAVE_JWZGLES
2101 create_rectangle_texture (GLuint *texture)
2103 glGenTextures(1, texture);
2104 glBindTexture(GL_TEXTURE_RECTANGLE_EXT, *texture);
2105 glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2106 glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2115 copy_pixmap (Display *dpy, Pixmap p)
2118 Assert (p->type == PIXMAP, "not a pixmap");
2124 unsigned int width, height, border_width, depth;
2125 if (XGetGeometry (dpy, p, &root,
2126 &x, &y, &width, &height, &border_width, &depth)) {
2128 gcv.function = GXcopy;
2129 GC gc = XCreateGC (dpy, p, GCFunction, &gcv);
2131 p2 = XCreatePixmap (dpy, p, width, height, depth);
2133 XCopyArea (dpy, p, p2, gc, 0, 0, width, height, 0, 0);
2138 Assert (p2, "could not copy pixmap");
2145 // This is XQueryFont, but for the XFontStruct embedded in 'Font'
2148 query_font (Font fid)
2150 if (!fid || !fid->native_font) {
2151 Assert (0, "no native font in fid");
2158 Display *dpy = fid->dpy;
2159 void *native_font = fid->native_font;
2161 XFontStruct *f = &fid->metrics;
2162 XCharStruct *min = &f->min_bounds;
2163 XCharStruct *max = &f->max_bounds;
2166 f->min_char_or_byte2 = first;
2167 f->max_char_or_byte2 = last;
2168 f->default_char = 'M';
2169 f->ascent = fid->ascent;
2170 f->descent = fid->descent;
2172 min->width = 32767; // set to smaller values in the loop
2173 min->ascent = 32767;
2174 min->descent = 32767;
2175 min->lbearing = 32767;
2176 min->rbearing = 32767;
2178 f->per_char = (XCharStruct *) calloc (last-first+2, sizeof (XCharStruct));
2180 for (int i = first; i <= last; i++) {
2181 XCharStruct *cs = &f->per_char[i-first];
2183 jwxyz_render_text (dpy, native_font, &s, 1, GL_FALSE, cs, 0);
2185 max->width = MAX (max->width, cs->width);
2186 max->ascent = MAX (max->ascent, cs->ascent);
2187 max->descent = MAX (max->descent, cs->descent);
2188 max->lbearing = MAX (max->lbearing, cs->lbearing);
2189 max->rbearing = MAX (max->rbearing, cs->rbearing);
2191 min->width = MIN (min->width, cs->width);
2192 min->ascent = MIN (min->ascent, cs->ascent);
2193 min->descent = MIN (min->descent, cs->descent);
2194 min->lbearing = MIN (min->lbearing, cs->lbearing);
2195 min->rbearing = MIN (min->rbearing, cs->rbearing);
2197 Log (" %3d %c: w=%3d lb=%3d rb=%3d as=%3d ds=%3d\n",
2198 i, i, cs->width, cs->lbearing, cs->rbearing,
2199 cs->ascent, cs->descent);
2205 // Since 'Font' includes the metrics, this just makes a copy of that.
2208 XQueryFont (Display *dpy, Font fid)
2211 XFontStruct *f = (XFontStruct *) calloc (1, sizeof(*f));
2216 f->n_properties = 1;
2217 f->properties = malloc (sizeof(*f->properties) * f->n_properties);
2218 f->properties[0].name = XA_FONT;
2219 Assert (sizeof (f->properties[0].card32) >= sizeof (char *),
2220 "atoms probably needs a real implementation");
2221 // If XInternAtom is ever implemented, use it here.
2222 f->properties[0].card32 = (unsigned long)(char *)fid->xa_font;
2224 // copy XCharStruct array
2225 int size = (f->max_char_or_byte2 - f->min_char_or_byte2) + 1;
2226 f->per_char = (XCharStruct *) calloc (size + 2, sizeof (XCharStruct));
2228 memcpy (f->per_char, fid->metrics.per_char,
2229 size * sizeof (XCharStruct));
2236 copy_font (Font fid)
2247 font_family_members (NSString *family_name)
2250 return [[NSFontManager sharedFontManager]
2251 availableMembersOfFontFamily:family_name];
2253 return [UIFont fontNamesForFamilyName:family_name];
2259 default_font_family (NSFontTraitMask require)
2261 return require & NSFixedPitchFontMask ? @"Courier" : @"Verdana";
2266 try_font (NSFontTraitMask traits, NSFontTraitMask mask,
2267 NSString *family_name, float size,
2270 Assert (size > 0, "zero font size");
2272 NSArray *family_members = font_family_members (family_name);
2273 if (!family_members.count)
2274 family_members = font_family_members (default_font_family (traits));
2277 for (unsigned k = 0; k != family_members.count; ++k) {
2279 NSArray *member = [family_members objectAtIndex:k];
2280 NSFontTraitMask font_mask =
2281 [(NSNumber *)[member objectAtIndex:3] unsignedIntValue];
2283 if ((font_mask & mask) == traits) {
2285 NSString *name = [member objectAtIndex:0];
2286 NSFont *f = [NSFont fontWithName:name size:size];
2290 /* Don't use this font if it (probably) doesn't include ASCII characters.
2292 NSStringEncoding enc = [f mostCompatibleStringEncoding];
2293 if (! (enc == NSUTF8StringEncoding ||
2294 enc == NSISOLatin1StringEncoding ||
2295 enc == NSNonLossyASCIIStringEncoding ||
2296 enc == NSISOLatin2StringEncoding ||
2297 enc == NSUnicodeStringEncoding ||
2298 enc == NSWindowsCP1250StringEncoding ||
2299 enc == NSWindowsCP1252StringEncoding ||
2300 enc == NSMacOSRomanStringEncoding)) {
2301 // NSLog(@"skipping \"%@\": encoding = %d", name, enc);
2304 // NSLog(@"using \"%@\": %d", name, enc);
2306 // *name_ret = strdup ([name cStringUsingEncoding:NSUTF8StringEncoding]);
2307 *name_ret = strdup (name.UTF8String);
2311 # else // USE_IPHONE
2313 for (NSString *fn in family_members) {
2315 ([fn rangeOfString:X options:NSCaseInsensitiveSearch].location \
2318 // The magic invocation for getting font names is
2319 // [[UIFontDescriptor
2320 // fontDescriptorWithFontAttributes:@{UIFontDescriptorNameAttribute: name}]
2322 // ...but this only works on iOS 7 and later.
2323 NSFontTraitMask font_mask = 0;
2325 font_mask |= NSBoldFontMask;
2326 if (MATCH(@"Italic") || MATCH(@"Oblique"))
2327 font_mask |= NSItalicFontMask;
2329 if ((font_mask & mask) == traits) {
2331 /* Check if it can do ASCII. No good way to accomplish this!
2332 These are fonts present in iPhone Simulator as of June 2012
2333 that don't include ASCII.
2335 if (MATCH(@"AppleGothic") || // Korean
2336 MATCH(@"Dingbats") || // Dingbats
2337 MATCH(@"Emoji") || // Emoticons
2338 MATCH(@"Geeza") || // Arabic
2339 MATCH(@"Hebrew") || // Hebrew
2340 MATCH(@"HiraKaku") || // Japanese
2341 MATCH(@"HiraMin") || // Japanese
2342 MATCH(@"Kailasa") || // Tibetan
2343 MATCH(@"Ornaments") || // Dingbats
2344 MATCH(@"STHeiti") // Chinese
2348 *name_ret = strdup (fn.UTF8String);
2349 return [UIFont fontWithName:fn size:size];
2361 /* On Cocoa and iOS, fonts may be specified as "Georgia Bold 24" instead
2362 of XLFD strings; also they can be comma-separated strings with multiple
2363 font names. First one that exists wins.
2366 try_native_font (const char *name, float scale,
2367 char **name_ret, float *size_ret, char **xa_font)
2369 if (!name) return 0;
2370 const char *spc = strrchr (name, ' ');
2374 char *token = strdup (name);
2377 while ((name2 = strtok (token, ","))) {
2380 while (*name2 == ' ' || *name2 == '\t' || *name2 == '\n')
2383 spc = strrchr (name2, ' ');
2387 if (1 != sscanf (spc, " %d ", &dsize))
2391 if (size <= 4) continue;
2395 name2[strlen(name2) - strlen(spc)] = 0;
2397 NSString *nsname = [NSString stringWithCString:name2
2398 encoding:NSUTF8StringEncoding];
2399 f = [NSFont fontWithName:nsname size:size];
2403 *xa_font = strdup (name); // Maybe this should be an XLFD?
2406 NSLog(@"No native font: \"%@\" %.0f", nsname, size);
2415 /* Returns a random font in the given size and face.
2418 random_font (NSFontTraitMask traits, NSFontTraitMask mask,
2419 float size, NSString **family_ret, char **name_ret)
2423 // Providing Unbold or Unitalic in the mask for availableFontNamesWithTraits
2424 // returns an empty list, at least on a system with default fonts only.
2425 NSArray *families = [[NSFontManager sharedFontManager]
2426 availableFontFamilies];
2427 if (!families) return 0;
2429 NSArray *families = [UIFont familyNames];
2431 // There are many dups in the families array -- uniquify it.
2433 NSArray *sorted_families =
2434 [families sortedArrayUsingSelector:@selector(compare:)];
2435 NSMutableArray *new_families =
2436 [NSMutableArray arrayWithCapacity:sorted_families.count];
2438 NSString *prev_family = nil;
2439 for (NSString *family in sorted_families) {
2440 if ([family compare:prev_family])
2441 [new_families addObject:family];
2444 families = new_families;
2446 # endif // USE_IPHONE
2448 long n = [families count];
2449 if (n <= 0) return 0;
2452 for (j = 0; j < n; j++) {
2453 int i = random() % n;
2454 NSString *family_name = [families objectAtIndex:i];
2456 NSFont *result = try_font (traits, mask, family_name, size, name_ret);
2458 [*family_ret release];
2459 *family_ret = family_name;
2460 [*family_ret retain];
2465 // None of the fonts support ASCII?
2470 // Fonts need this. XDisplayHeightMM and friends should probably be consistent
2471 // with this as well if they're ever implemented.
2472 static const unsigned dpi = 75;
2476 xlfd_field_end (const char *s)
2478 const char *s2 = strchr(s, '-');
2486 xlfd_next (const char **s, const char **s2)
2491 Assert (**s2 == '-', "xlfd parse error");
2493 *s2 = xlfd_field_end (*s);
2500 try_xlfd_font (const char *name, float scale,
2501 char **name_ret, float *size_ret, char **xa_font)
2504 NSString *family_name = nil;
2505 NSFontTraitMask require = 0, forbid = 0;
2506 GLboolean rand = GL_FALSE;
2510 const char *s = (name ? name : "");
2512 size_t L = strlen (s);
2513 # define CMP(STR) (L == strlen(STR) && !strncasecmp (s, (STR), L))
2514 # define UNSPEC (L == 0 || L == 1 && *s == '*')
2515 if (CMP ("6x10")) size = 8, require |= NSFixedPitchFontMask;
2516 else if (CMP ("6x10bold")) size = 8, require |= NSFixedPitchFontMask | NSBoldFontMask;
2517 else if (CMP ("fixed")) size = 12, require |= NSFixedPitchFontMask;
2518 else if (CMP ("9x15")) size = 12, require |= NSFixedPitchFontMask;
2519 else if (CMP ("9x15bold")) size = 12, require |= NSFixedPitchFontMask | NSBoldFontMask;
2520 else if (CMP ("vga")) size = 12, require |= NSFixedPitchFontMask;
2521 else if (CMP ("console")) size = 12, require |= NSFixedPitchFontMask;
2522 else if (CMP ("gallant")) size = 12, require |= NSFixedPitchFontMask;
2525 // Incorrect fields are ignored.
2529 const char *s2 = xlfd_field_end(s);
2533 L = xlfd_next (&s, &s2); // Family name
2534 // This used to substitute Georgia for Times. Now it doesn't.
2535 if (CMP ("random")) {
2537 } else if (CMP ("fixed")) {
2538 require |= NSFixedPitchFontMask;
2539 family_name = @"Courier";
2540 } else if (!UNSPEC) {
2541 family_name = [[[NSString alloc] initWithBytes:s
2543 encoding:NSUTF8StringEncoding]
2547 L = xlfd_next (&s, &s2); // Weight name
2548 if (CMP ("bold") || CMP ("demibold"))
2549 require |= NSBoldFontMask;
2550 else if (CMP ("medium") || CMP ("regular"))
2551 forbid |= NSBoldFontMask;
2553 L = xlfd_next (&s, &s2); // Slant
2554 if (CMP ("i") || CMP ("o"))
2555 require |= NSItalicFontMask;
2557 forbid |= NSItalicFontMask;
2559 xlfd_next (&s, &s2); // Set width name (ignore)
2560 xlfd_next (&s, &s2); // Add style name (ignore)
2562 xlfd_next (&s, &s2); // Pixel size (ignore)
2564 xlfd_next (&s, &s2); // Point size
2566 uintmax_t n = strtoumax(s, &s3, 10);
2570 xlfd_next (&s, &s2); // Resolution X (ignore)
2571 xlfd_next (&s, &s2); // Resolution Y (ignore)
2573 xlfd_next (&s, &s2); // Spacing
2575 forbid |= NSFixedPitchFontMask;
2576 else if (CMP ("m") || CMP ("c"))
2577 require |= NSFixedPitchFontMask;
2579 // Don't care about average_width or charset registry.
2584 if (!family_name && !rand)
2585 family_name = default_font_family (require);
2587 if (size < 6 || size > 1000)
2592 NSFontTraitMask mask = require | forbid;
2595 nsfont = random_font (require, mask, size, &family_name, &ps_name);
2596 [family_name autorelease];
2600 nsfont = try_font (require, mask, family_name, size, &ps_name);
2602 // if that didn't work, turn off attibutes until it does
2603 // (e.g., there is no "Monaco-Bold".)
2605 if (!nsfont && (mask & NSItalicFontMask)) {
2606 require &= ~NSItalicFontMask;
2607 mask &= ~NSItalicFontMask;
2608 nsfont = try_font (require, mask, family_name, size, &ps_name);
2610 if (!nsfont && (mask & NSBoldFontMask)) {
2611 require &= ~NSBoldFontMask;
2612 mask &= ~NSBoldFontMask;
2613 nsfont = try_font (require, mask, family_name, size, &ps_name);
2615 if (!nsfont && (mask & NSFixedPitchFontMask)) {
2616 require &= ~NSFixedPitchFontMask;
2617 mask &= ~NSFixedPitchFontMask;
2618 nsfont = try_font (require, mask, family_name, size, &ps_name);
2622 *name_ret = ps_name;
2624 float actual_size = size / scale;
2625 asprintf(xa_font, "-*-%s-%s-%c-*-*-%u-%u-%u-%u-%c-0-iso10646-1",
2626 family_name.UTF8String,
2627 (require & NSBoldFontMask) ? "bold" : "medium",
2628 (require & NSItalicFontMask) ? 'o' : 'r',
2629 (unsigned)(dpi * actual_size / 72.27 + 0.5),
2630 (unsigned)(actual_size * 10 + 0.5), dpi, dpi,
2631 (require & NSFixedPitchFontMask) ? 'm' : 'p');
2641 XLoadFont (Display *dpy, const char *name)
2643 Font fid = (Font) calloc (1, sizeof(*fid));
2647 // (TODO) float scale = 1;
2650 /* Since iOS screens are physically smaller than desktop screens, scale up
2651 the fonts to make them more readable.
2653 Note that X11 apps on iOS also have the backbuffer sized in points
2654 instead of pixels, resulting in an effective X11 screen size of 768x1024
2655 or so, even if the display has significantly higher resolution. That is
2656 unrelated to this hack, which is really about DPI.
2662 fid->native_font = jwxyz_load_native_font (dpy, name,
2663 &fid->ps_name, &fid->size,
2664 &fid->ascent, &fid->descent);
2665 if (!fid->native_font) {
2676 XLoadQueryFont (Display *dpy, const char *name)
2678 Font fid = XLoadFont (dpy, name);
2680 return XQueryFont (dpy, fid);
2684 XUnloadFont (Display *dpy, Font fid)
2686 if (--fid->refcount < 0) abort();
2687 if (fid->refcount > 0) return 0;
2689 if (fid->native_font)
2690 jwxyz_release_native_font (fid->dpy, fid->native_font);
2693 free (fid->ps_name);
2694 if (fid->metrics.per_char)
2695 free (fid->metrics.per_char);
2697 // #### DAMMIT! I can't tell what's going wrong here, but I keep getting
2698 // crashes in [NSFont ascender] <- query_font, and it seems to go away
2699 // if I never release the nsfont. So, fuck it, we'll just leak fonts.
2700 // They're probably not very big...
2702 // [fid->nsfont release];
2703 // CFRelease (fid->nsfont);
2710 XFreeFontInfo (char **names, XFontStruct *info, int n)
2714 for (i = 0; i < n; i++)
2715 if (names[i]) free (names[i]);
2719 for (i = 0; i < n; i++)
2720 if (info[i].per_char) {
2721 free (info[i].per_char);
2722 free (info[i].properties);
2730 XFreeFont (Display *dpy, XFontStruct *f)
2733 XFreeFontInfo (0, f, 1);
2734 XUnloadFont (dpy, fid);
2740 XSetFont (Display *dpy, GC gc, Font fid)
2742 Font font2 = copy_font (fid);
2744 XUnloadFont (dpy, gc->gcv.font);
2745 gc->gcv.font = font2;
2751 XCreateFontSet (Display *dpy, char *name,
2752 char ***missing_charset_list_return,
2753 int *missing_charset_count_return,
2754 char **def_string_return)
2756 char *name2 = strdup (name);
2757 char *s = strchr (name, ',');
2760 XFontStruct *f = XLoadQueryFont (dpy, name2);
2763 set = (XFontSet) calloc (1, sizeof(*set));
2767 if (missing_charset_list_return) *missing_charset_list_return = 0;
2768 if (missing_charset_count_return) *missing_charset_count_return = 0;
2769 if (def_string_return) *def_string_return = 0;
2775 XFreeFontSet (Display *dpy, XFontSet set)
2777 XFreeFont (dpy, set->font);
2783 jwxyz_nativeFontName (Font f, float *size)
2785 if (size) *size = f->size;
2791 XFreeStringList (char **list)
2795 for (i = 0; list[i]; i++)
2801 // Returns the verbose Unicode name of this character, like "agrave" or
2802 // "daggerdouble". Used by fontglide debugMetrics.
2805 jwxyz_unicode_character_name (Font fid, unsigned long uc)
2810 CTFontCreateWithName ((CFStringRef) [fid->nsfont fontName],
2811 [fid->nsfont pointSize],
2813 Assert (ctfont, @"no CTFontRef for UIFont");
2816 if (CTFontGetGlyphsForCharacters (ctfont, (UniChar *) &uc, &cgglyph, 1)) {
2817 NSString *name = (NSString *)
2818 CGFontCopyGlyphNameForGlyph (CTFontCopyGraphicsFont (ctfont, 0),
2820 ret = (name ? strdup ([name UTF8String]) : 0);
2830 // Given a UTF8 string, return an NSString. Bogus UTF8 characters are ignored.
2831 // We have to do this because stringWithCString returns NULL if there are
2832 // any invalid characters at all.
2836 sanitize_utf8 (const char *in, int in_len, Bool *latin1_pP)
2838 int out_len = in_len * 4; // length of string might increase
2839 char *s2 = (char *) malloc (out_len);
2841 const char *in_end = in + in_len;
2842 const char *out_end = out + out_len;
2843 Bool latin1_p = True;
2848 long L1 = utf8_decode ((const unsigned char *) in, in_end - in, &uc);
2849 long L2 = utf8_encode (uc, out, out_end - out);
2852 if (uc > 255) latin1_p = False;
2856 [NSString stringWithCString:s2 encoding:NSUTF8StringEncoding];
2858 if (latin1_pP) *latin1_pP = latin1_p;
2859 return (nsstr ? nsstr : @"");
2864 XTextExtents (XFontStruct *f, const char *s, int length,
2865 int *dir_ret, int *ascent_ret, int *descent_ret,
2868 // Unfortunately, adding XCharStructs together to get the extents for a
2869 // string doesn't work: Cocoa uses non-integral character advancements, but
2870 // XCharStruct.width is an integer. Plus that doesn't take into account
2871 // kerning pairs, alternate glyphs, and fun stuff like the word "Zapfino" in
2875 Display *dpy = ff->dpy;
2876 jwxyz_render_text (dpy, ff->native_font, s, length, GL_FALSE, cs, 0);
2878 *ascent_ret = f->ascent;
2879 *descent_ret = f->descent;
2884 XTextWidth (XFontStruct *f, const char *s, int length)
2886 int ascent, descent, dir;
2888 XTextExtents (f, s, length, &dir, &ascent, &descent, &cs);
2894 XTextExtents16 (XFontStruct *f, const XChar2b *s, int length,
2895 int *dir_ret, int *ascent_ret, int *descent_ret,
2898 // Bool latin1_p = True;
2899 int i, utf8_len = 0;
2900 char *utf8 = XChar2b_to_utf8 (s, &utf8_len); // already sanitized
2902 for (i = 0; i < length; i++)
2903 if (s[i].byte1 > 0) {
2904 // latin1_p = False;
2910 Display *dpy = ff->dpy;
2911 jwxyz_render_text (dpy, ff->native_font, utf8, strlen(utf8),
2916 *ascent_ret = f->ascent;
2917 *descent_ret = f->descent;
2923 /* "Returns the distance in pixels in the primary draw direction from
2924 the drawing origin to the origin of the next character to be drawn."
2926 "overall_ink_return is set to the bbox of the string's character ink."
2928 "The overall_ink_return for a nondescending, horizontally drawn Latin
2929 character is conventionally entirely above the baseline; that is,
2930 overall_ink_return.height <= -overall_ink_return.y."
2932 [So this means that y is the top of the ink, and height grows down:
2933 For above-the-baseline characters, y is negative.]
2935 "The overall_ink_return for a nonkerned character is entirely at, and to
2936 the right of, the origin; that is, overall_ink_return.x >= 0."
2938 [So this means that x is the left of the ink, and width grows right.
2939 For left-of-the-origin characters, x is negative.]
2941 "A character consisting of a single pixel at the origin would set
2942 overall_ink_return fields y = 0, x = 0, width = 1, and height = 1."
2945 Xutf8TextExtents (XFontSet set, const char *str, int len,
2946 XRectangle *overall_ink_return,
2947 XRectangle *overall_logical_return)
2951 NSString *nsstr = sanitize_utf8 (str, len, &latin1_p);
2954 utf8_metrics (set->font->fid, nsstr, &cs);
2956 /* "The overall_logical_return is the bounding box that provides minimum
2957 spacing to other graphical features for the string. Other graphical
2958 features, for example, a border surrounding the text, should not
2959 intersect this rectangle."
2961 So I think that means they're the same? Or maybe "ink" is the bounding
2962 box, and "logical" is the advancement? But then why is the return value
2965 if (overall_ink_return)
2966 XCharStruct_to_XmbRectangle (cs, *overall_ink_return);
2967 if (overall_logical_return)
2968 XCharStruct_to_XmbRectangle (cs, *overall_logical_return);
2977 draw_string (Display *dpy, Drawable d, GC gc, int x, int y,
2978 const char *str, size_t len, GLboolean utf8)
2980 Font ff = gc->gcv.font;
2984 jwxyz_render_text (dpy, ff->native_font, str, len, utf8, &cs, &data);
2985 int w = cs.rbearing - cs.lbearing;
2986 int h = cs.ascent + cs.descent;
2988 if (w < 0 || h < 0) abort();
2989 if (w == 0 || h == 0) {
2990 if (data) free(data);
2994 XImage *img = XCreateImage (dpy, dpy->screen->visual, 32,
2995 ZPixmap, 0, data, w, h, 0, 0);
2997 /* The image of text is a 32-bit image, in white.
2998 Take the red channel for intensity and use that as alpha.
2999 replace RGB with the GC's foreground color.
3000 This expects that XPutImage respects alpha and only writes
3001 the bits that are not masked out.
3002 This also assumes that XPutImage expects ARGB.
3006 char *end = s + (w * h * 4);
3008 jwxyz_query_color (dpy, gc->gcv.foreground, rgba);
3019 XPutImage (dpy, d, gc, img, 0, 0,
3023 XDestroyImage (img);
3029 XDrawString (Display *dpy, Drawable d, GC gc, int x, int y,
3030 const char *str, int len)
3032 return draw_string (dpy, d, gc, x, y, str, len, GL_FALSE);
3037 XDrawString16 (Display *dpy, Drawable d, GC gc, int x, int y,
3038 const XChar2b *str, int len)
3040 XChar2b *b2 = malloc ((len + 1) * sizeof(*b2));
3043 memcpy (b2, str, len * sizeof(*b2));
3044 b2[len].byte1 = b2[len].byte2 = 0;
3045 s2 = XChar2b_to_utf8 (b2, 0);
3047 ret = draw_string (dpy, d, gc, x, y, s2, strlen(s2), GL_TRUE);
3054 Xutf8DrawString (Display *dpy, Drawable d, XFontSet set, GC gc,
3055 int x, int y, const char *str, int len)
3057 draw_string (dpy, d, gc, x, y, str, len, GL_TRUE);
3062 XDrawImageString (Display *dpy, Drawable d, GC gc, int x, int y,
3063 const char *str, int len)
3065 int ascent, descent, dir;
3067 XTextExtents (&gc->gcv.font->metrics, str, len,
3068 &dir, &ascent, &descent, &cs);
3069 jwxyz_fill_rect (dpy, d, gc,
3070 x + MIN (0, cs.lbearing),
3071 y - MAX (0, ascent),
3072 MAX (MAX (0, cs.rbearing) -
3073 MIN (0, cs.lbearing),
3075 MAX (0, ascent) + MAX (0, descent),
3076 gc->gcv.background);
3077 return XDrawString (dpy, d, gc, x, y, str, len);
3082 XSetClipMask (Display *dpy, GC gc, Pixmap m)
3088 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
3090 if (gc->gcv.clip_mask) {
3091 XFreePixmap (dpy, gc->gcv.clip_mask);
3092 CGImageRelease (gc->clip_mask);
3095 gc->gcv.clip_mask = copy_pixmap (dpy, m);
3096 if (gc->gcv.clip_mask)
3098 CGBitmapContextCreateImage (gc->gcv.clip_mask->cgc);
3107 XSetClipOrigin (Display *dpy, GC gc, int x, int y)
3109 gc->gcv.clip_x_origin = x;
3110 gc->gcv.clip_y_origin = y;
3114 #endif /* JWXYZ_GL -- entire file */