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>
156 typedef struct CGPoint CGPoint;
162 typedef struct CGSize CGSize;
168 typedef struct CGRect CGRect;
174 # define MAX(a,b) ((a)>(b)?(a):(b))
175 # define MIN(a,b) ((a)<(b)?(a):(b))
179 /* On 64-bit systems, high bits of the 32-bit pixel are available as scratch
180 space. I doubt if any screen savers need it, but just in case... */
185 struct jwxyz_Display {
189 struct jwxyz_sources_data *timers_data;
191 Bool gl_texture_npot_p;
192 /* Bool opengl_core_p */;
193 GLenum gl_texture_target;
195 // #if defined USE_IPHONE
196 GLuint rect_texture; // Also can work on the desktop.
199 unsigned long window_background;
202 struct jwxyz_Screen {
204 GLenum pixel_format, pixel_type;
205 unsigned long black, white;
213 // CGImageRef clip_mask; // CGImage copy of the Pixmap in gcv.clip_mask
220 int refcount; // for deciding when to release the native font
221 float size; // points
225 // In X11, "Font" is just an ID, and "XFontStruct" contains the metrics.
226 // But we need the metrics on both of them, so they go here.
230 struct jwxyz_XFontSet {
235 /* XGetImage in CoreGraphics JWXYZ has to deal with funky pixel formats
236 necessitating fast & flexible pixel conversion. OpenGL does image format
237 conversion itself, so alloc_color and query_color are mercifully simple.
240 jwxyz_alloc_color (Display *dpy,
241 uint16_t r, uint16_t g, uint16_t b, uint16_t a)
243 union color_bytes color;
245 /* Instead of (int)(c / 256.0), another possibility is
246 (int)(c * 255.0 / 65535.0 + 0.5). This can be calculated using only
247 uint8_t integer_math(uint16_t c) {
248 unsigned c0 = c + 128;
249 return (c0 - (c0 >> 8)) >> 8;
253 color.bytes[0] = r >> 8;
254 color.bytes[1] = g >> 8;
255 color.bytes[2] = b >> 8;
256 color.bytes[3] = a >> 8;
258 if (dpy->screen->pixel_format == GL_BGRA_EXT) {
259 color.pixel = color.bytes[2] |
260 (color.bytes[1] << 8) |
261 (color.bytes[0] << 16) |
262 (color.bytes[3] << 24);
264 Assert(dpy->screen->pixel_format == GL_RGBA,
265 "jwxyz_alloc_color: Unknown pixel_format");
268 return (uint32_t)color.pixel;
271 // Converts an array of pixels ('src') from one format to another, placing the
272 // result in 'dest', according to the pixel conversion mode 'mode'.
274 jwxyz_query_color (Display *dpy, unsigned long pixel, uint8_t *rgba)
276 union color_bytes color;
278 if(dpy->screen->pixel_format == GL_RGBA)
281 for (unsigned i = 0; i != 4; ++i)
282 rgba[i] = color.bytes[i];
286 Assert (dpy->screen->pixel_format == GL_BGRA_EXT,
287 "jwxyz_query_color: Unknown pixel format");
288 /* TODO: Cross-check with XAllocColor. */
289 rgba[0] = (pixel >> 16) & 0xFF;
290 rgba[1] = (pixel >> 8) & 0xFF;
291 rgba[2] = (pixel >> 0) & 0xFF;
292 rgba[3] = (pixel >> 24) & 0xFF;
297 jwxyz_assert_display(Display *dpy)
302 jwxyz_assert_drawable (dpy->main_window, dpy->main_window);
307 jwxyz_set_matrices (Display *dpy, unsigned width, unsigned height,
310 /* TODO: Check registration pattern from Interference with rectangles instead of points. */
312 // The projection matrix is always set as follows. The modelview matrix is
313 // usually identity, but for points and thin lines, it's translated by 0.5.
314 glMatrixMode(GL_PROJECTION);
317 # if defined(USE_IPHONE) || defined(HAVE_ANDROID)
319 if (window_p && ignore_rotation_p(dpy)) {
320 int o = (int) current_device_rotation();
321 glRotatef (-o, 0, 0, 1);
324 // glPointSize(1); // This is the default.
327 glOrthof /* TODO: Is glOrthox worth it? Signs point to no. */
331 (0, width, height, 0, -1, 1);
333 glMatrixMode(GL_MODELVIEW);
334 # endif // HAVE_MOBILE
341 // iOS always uses OpenGL ES 1.1.
347 gl_check_ver (const struct gl_version *caps,
351 return caps->major > gl_major ||
352 (caps->major == gl_major && caps->minor >= gl_minor);
356 static GLboolean gl_check_ext(const struct gl_caps *caps,
359 const char *extension)
362 gl_check_ver(caps, gl_major, gl_minor) ||
363 gluCheckExtension(extension, caps->extensions);
370 // NSOpenGLContext *jwxyz_debug_context;
372 /* We keep a list of all of the Displays that have been created and not
373 yet freed so that they can have sensible display numbers. If three
374 displays are created (0, 1, 2) and then #1 is closed, then the fourth
375 display will be given the now-unused display number 1. (Everything in
376 here assumes a 1:1 Display/Screen mapping.)
378 The size of this array is the most number of live displays at one time.
379 So if it's 20, then we'll blow up if the system has 19 monitors and also
380 has System Preferences open (the small preview window).
382 Note that xlockmore-style savers tend to allocate big structures, so
383 setting this to 1000 will waste a few megabytes. Also some of them assume
384 that the number of screens never changes, so dynamically expanding this
388 static Display *jwxyz_live_displays[20] = { 0, };
393 jwxyz_make_display (Window w)
395 Display *d = (Display *) calloc (1, sizeof(*d));
396 d->screen = (Screen *) calloc (1, sizeof(Screen));
400 d->screen->screen_number = 0;
403 // Find the first empty slot in live_displays and plug us in.
404 int size = sizeof(jwxyz_live_displays) / sizeof(*jwxyz_live_displays);
406 for (i = 0; i < size; i++) {
407 if (! jwxyz_live_displays[i])
410 if (i >= size) abort();
411 jwxyz_live_displays[i] = d;
412 d->screen_count = size;
413 d->screen->screen_number = i;
415 # endif // !USE_IPHONE
417 # ifndef HAVE_JWZGLES
418 struct gl_version version;
421 const GLubyte *version_str = glGetString (GL_VERSION);
423 /* iPhone is always OpenGL ES 1.1. */
424 if (sscanf ((const char *) version_str, "%u.%u",
425 &version.major, &version.minor) < 2)
431 # endif // !HAVE_JWZGLES
433 const GLubyte *extensions = glGetString (GL_EXTENSIONS);
436 - Apple TN2080: Understanding and Detecting OpenGL Functionality.
437 - OpenGL Programming Guide for the Mac - Best Practices for Working with
438 Texture Data - Optimal Data Formats and Types
441 // If a video adapter suports BGRA textures, then that's probably as fast as
442 // you're gonna get for getting a texture onto the screen.
444 /* TODO: Make BGRA work on iOS. As it is, it breaks XPutImage. (glTexImage2D, AFAIK) */
445 d->screen->pixel_format = GL_RGBA; /*
446 gluCheckExtension ((const GLubyte *) "GL_APPLE_texture_format_BGRA8888",
447 extensions) ? GL_BGRA_EXT : GL_RGBA; */
448 d->screen->pixel_type = GL_UNSIGNED_BYTE;
449 // See also OES_read_format.
450 # else // !HAVE_JWZGLES
451 if (gl_check_ver (&version, 1, 2) ||
452 (gluCheckExtension ((const GLubyte *) "GL_EXT_bgra", extensions) &&
453 gluCheckExtension ((const GLubyte *) "GL_APPLE_packed_pixels",
455 d->screen->pixel_format = GL_BGRA_EXT;
456 // Both Intel and PowerPC-era docs say to use GL_UNSIGNED_INT_8_8_8_8_REV.
457 d->screen->pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
459 d->screen->pixel_format = GL_RGBA;
460 d->screen->pixel_type = GL_UNSIGNED_BYTE;
462 // GL_ABGR_EXT/GL_UNSIGNED_BYTE is another possibilty that may have made more
464 # endif // !HAVE_JWZGLES
466 // On really old systems, it would make sense to split the texture
468 # ifndef HAVE_JWZGLES
469 d->gl_texture_npot_p = gluCheckExtension ((const GLubyte *)
470 "GL_ARB_texture_rectangle",
472 d->gl_texture_target = d->gl_texture_npot_p ?
473 GL_TEXTURE_RECTANGLE_EXT :
476 d->gl_texture_npot_p = jwzgles_gluCheckExtension
477 ((const GLubyte *) "GL_APPLE_texture_2D_limited_npot", extensions) ||
478 jwzgles_gluCheckExtension
479 ((const GLubyte *) "GL_OES_texture_npot", extensions) ||
480 jwzgles_gluCheckExtension // From PixelFlinger 1.4
481 ((const GLubyte *) "GL_ARB_texture_non_power_of_two", extensions);
483 d->gl_texture_target = GL_TEXTURE_2D;
486 d->screen->black = jwxyz_alloc_color (d, 0x0000, 0x0000, 0x0000, 0xFFFF);
487 d->screen->white = jwxyz_alloc_color (d, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF);
489 Visual *v = (Visual *) calloc (1, sizeof(Visual));
490 v->class = TrueColor;
491 v->red_mask = jwxyz_alloc_color (d, 0xFFFF, 0x0000, 0x0000, 0x0000);
492 v->green_mask = jwxyz_alloc_color (d, 0x0000, 0xFFFF, 0x0000, 0x0000);
493 v->blue_mask = jwxyz_alloc_color (d, 0x0000, 0x0000, 0xFFFF, 0x0000);
495 d->screen->visual = v;
497 d->timers_data = jwxyz_sources_init (XtDisplayToApplicationContext (d));
499 d->window_background = BlackPixel(d,0);
503 fputs((char *)glGetString(GL_VENDOR), stderr);
505 fputs((char *)glGetString(GL_RENDERER), stderr);
507 fputs((char *)glGetString(GL_VERSION), stderr);
509 // puts(caps.extensions);
510 GLint max_texture_size;
511 glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max_texture_size);
512 printf ("GL_MAX_TEXTURE_SIZE: %d\n", max_texture_size);
515 // In case a GL hack wants to use X11 to draw offscreen, the rect_texture is available.
516 Assert (d->main_window == w, "Uh-oh.");
517 glGenTextures (1, &d->rect_texture);
518 // TODO: Check for (ARB|EXT|NV)_texture_rectangle. (All three are alike.)
519 // Rectangle textures should be present on OS X with the following exceptions:
520 // - Generic renderer on PowerPC OS X 10.4 and earlier
522 glBindTexture (d->gl_texture_target, d->rect_texture);
523 // TODO: This is all somewhere else. Refactor.
524 glTexParameteri (d->gl_texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
525 glTexParameteri (d->gl_texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
527 // This might be redundant for rectangular textures.
528 # ifndef HAVE_JWZGLES
529 const GLint wrap = GL_CLAMP;
530 # else // HAVE_JWZGLES
531 const GLint wrap = GL_CLAMP_TO_EDGE;
532 # endif // HAVE_JWZGLES
534 // In OpenGL, CLAMP_TO_EDGE is OpenGL 1.2 or GL_SGIS_texture_edge_clamp.
535 // This is always present with OpenGL ES.
536 glTexParameteri (d->gl_texture_target, GL_TEXTURE_WRAP_S, wrap);
537 glTexParameteri (d->gl_texture_target, GL_TEXTURE_WRAP_T, wrap);
539 jwxyz_assert_display(d);
544 jwxyz_free_display (Display *dpy)
546 /* TODO: Go over everything. */
548 jwxyz_sources_free (dpy->timers_data);
552 // Find us in live_displays and clear that slot.
553 int size = ScreenCount(dpy);
555 for (i = 0; i < size; i++) {
556 if (dpy == jwxyz_live_displays[i]) {
557 jwxyz_live_displays[i] = 0;
561 if (i >= size) abort();
563 # endif // !USE_IPHONE
565 free (dpy->screen->visual);
571 /* Call this after any modification to the bits on a Pixmap or Window.
572 Most Pixmaps are used frequently as sources and infrequently as
573 destinations, so it pays to cache the data as a CGImage as needed.
576 invalidate_drawable_cache (Drawable d)
578 /* TODO: Kill this outright. jwxyz_bind_drawable handles any potential
585 CGImageRelease (d->cgi);
592 /* Call this when the View changes size or position.
595 jwxyz_window_resized (Display *dpy)
597 const XRectangle *new_frame = jwxyz_frame (dpy->main_window);
598 unsigned new_width = new_frame->width;
599 unsigned new_height = new_frame->height;
601 Assert (new_width, "jwxyz_window_resized: No width.");
602 Assert (new_height, "jwxyz_window_resized: No height.");
604 /*if (cgc) w->cgc = cgc;
605 Assert (w->cgc, "no CGContext"); */
607 Log("resize: %d, %d\n", new_width, new_height);
609 jwxyz_bind_drawable (dpy, dpy->main_window, dpy->main_window);
611 // TODO: What does the iPhone need?
613 // iOS only: If the main_window is not the current_drawable, then set_matrices
614 // was already called in bind_drawable.
615 jwxyz_set_matrices (dpy, new_width, new_height, True);
619 glGetIntegerv (GL_DRAW_BUFFER, &draw_buffer);
621 glDrawBuffer (GL_FRONT);
622 glClearColor (1, 0, 1, 0);
623 glClear (GL_COLOR_BUFFER_BIT);
624 glDrawBuffer (GL_BACK);
625 glClearColor (0, 1, 1, 0);
626 glClear (GL_COLOR_BUFFER_BIT);
628 glDrawBuffer (draw_buffer); */
630 // Stylish and attractive purple!
631 // glClearColor (1, 0, 1, 0.5);
632 // glClear (GL_COLOR_BUFFER_BIT);
634 invalidate_drawable_cache (dpy->main_window);
639 display_sources_data (Display *dpy)
641 return dpy->timers_data;
646 XRootWindow (Display *dpy, int screen)
648 return dpy ? dpy->main_window : 0;
652 XDefaultScreenOfDisplay (Display *dpy)
654 return dpy ? dpy->screen : 0;
658 XDefaultVisualOfScreen (Screen *screen)
660 return screen ? screen->visual : 0;
664 XDisplayOfScreen (Screen *s)
666 return s ? s->dpy : 0;
670 XDisplayNumberOfScreen (Screen *s)
676 XScreenNumberOfScreen (Screen *s)
678 return s? s->screen_number : 0;
682 jwxyz_ScreenCount (Display *dpy)
684 return dpy ? dpy->screen_count : 0;
688 XBlackPixelOfScreen(Screen *screen)
690 return screen->black;
694 XWhitePixelOfScreen(Screen *screen)
696 return screen->white;
700 XCellsOfScreen(Screen *screen)
702 Visual *v = screen->visual;
703 return v->red_mask | v->green_mask | v->blue_mask;
707 /* GC attributes by usage and OpenGL implementation:
709 * All drawing functions:
710 * function | glLogicOp w/ GL_COLOR_LOGIC_OP
711 * clip_x_origin, clip_y_origin, clip_mask | Stencil mask
713 * Shape drawing functions:
714 * foreground, background | glColor*
716 * XDrawLines, XDrawRectangles, XDrawSegments:
717 * line_width, cap_style, join_style | Lotsa vertices
720 * fill_rule | Multiple GL_TRIANGLE_FANs
723 * font | Cocoa, then OpenGL display lists.
725 * alpha_allowed_p | TODO
733 set_clip_mask (GC gc)
735 Assert (!gc->gcv.clip_mask, "set_gc: TODO");
739 set_function (int function)
741 Assert (function == GXcopy, "set_gc: (TODO) Stubbed gcv function");
743 /* TODO: The GL_COLOR_LOGIC_OP extension is exactly what is needed here. (OpenGL 1.1)
744 Fun fact: The glLogicOp opcode constants are the same as the X11 GX* function constants | GL_CLEAR.
748 switch (gc->gcv.function) {
751 case GXcopy:/*CGContextSetBlendMode (cgc, kCGBlendModeNormal);*/ break;
752 case GXxor: CGContextSetBlendMode (cgc, kCGBlendModeDifference); break;
753 case GXor: CGContextSetBlendMode (cgc, kCGBlendModeLighten); break;
754 case GXand: CGContextSetBlendMode (cgc, kCGBlendModeDarken); break;
755 default: Assert(0, "unknown gcv function"); break;
762 set_color (Display *dpy, unsigned long pixel, unsigned int depth,
763 Bool alpha_allowed_p)
765 jwxyz_validate_pixel (dpy, pixel, depth, alpha_allowed_p);
769 glColor4f (f, f, f, 1);
771 /* TODO: alpha_allowed_p */
773 jwxyz_query_color (dpy, pixel, rgba);
775 glColor4f (rgba[0] / 255.0f, rgba[1] / 255.0f,
776 rgba[2] / 255.0f, rgba[3] / 255.0f);
783 /* Pushes a GC context; sets Function, ClipMask, and color.
786 set_color_gc (Display *dpy, GC gc, unsigned long color)
788 // GC is NULL for XClearArea and XClearWindow.
792 function = gc->gcv.function;
797 depth = visual_depth (NULL, NULL);
798 // TODO: Set null clip mask here.
801 set_function (function);
804 case GXset: color = (depth == 1 ? 1 : WhitePixel(dpy,0)); break;
805 case GXclear: color = (depth == 1 ? 0 : BlackPixel(dpy,0)); break;
808 set_color (dpy, color, depth, gc ? gc->gcv.alpha_allowed_p : False);
810 /* TODO: Antialiasing. */
811 /* CGContextSetShouldAntialias (cgc, antialias_p); */
814 /* Pushes a GC context; sets color to the foreground color.
817 set_fg_gc (Display *dpy, GC gc)
819 set_color_gc (dpy, gc, gc->gcv.foreground);
823 next_point(short *v, XPoint p, int mode)
826 case CoordModeOrigin:
830 case CoordModePrevious:
835 Assert (False, "next_point: bad mode");
841 XDrawPoints (Display *dpy, Drawable d, GC gc,
842 XPoint *points, int count, int mode)
844 jwxyz_bind_drawable (dpy, dpy->main_window, d);
850 for (unsigned i = 0; i < count; i++) {
851 next_point(v, points[i], mode);
852 glVertex2f(v[0] + 0.5f, v[1] + 0.5f);
859 // TODO: XPoints can be fed directly to OpenGL.
860 GLshort *gl_points = malloc (count * 2 * sizeof(GLshort)); // TODO: malloc returns NULL.
861 for (unsigned i = 0; i < count; i++) {
862 next_point (v, points[i], mode);
863 gl_points[2 * i] = v[0];
864 gl_points[2 * i + 1] = v[1];
867 glMatrixMode (GL_MODELVIEW);
868 glTranslatef (0.5, 0.5, 0);
870 glEnableClientState (GL_VERTEX_ARRAY);
871 glDisableClientState (GL_TEXTURE_COORD_ARRAY);
872 glVertexPointer (2, GL_SHORT, 0, gl_points);
873 glDrawArrays (GL_POINTS, 0, count);
884 texture_internalformat(Display *dpy)
887 return dpy->screen->pixel_format;
893 static GLenum gl_pixel_type(const Display *dpy)
895 return dpy->screen->pixel_type;
899 clear_texture (Display *dpy)
901 glTexImage2D (dpy->gl_texture_target, 0, texture_internalformat(dpy), 0, 0,
902 0, dpy->screen->pixel_format, gl_pixel_type (dpy), NULL);
905 static void set_white (void)
908 glColor4f (1, 1, 1, 1);
910 glColor3ub (0xff, 0xff, 0xff);
915 static GLsizei to_pow2 (size_t x);
919 jwxyz_gl_copy_area_copy_tex_image (Display *dpy, Drawable src, Drawable dst,
920 GC gc, int src_x, int src_y,
921 unsigned int width, unsigned int height,
922 int dst_x, int dst_y)
924 const XRectangle *src_frame = jwxyz_frame (src);
926 Assert(gc->gcv.function == GXcopy, "XCopyArea: Unknown function");
928 jwxyz_bind_drawable (dpy, dpy->main_window, src);
930 # if defined HAVE_COCOA && !defined USE_IPHONE
931 /* TODO: Does this help? */
935 unsigned tex_w = width, tex_h = height;
936 if (!dpy->gl_texture_npot_p) {
937 tex_w = to_pow2(tex_w);
938 tex_h = to_pow2(tex_h);
941 GLint internalformat = texture_internalformat(dpy);
943 glBindTexture (dpy->gl_texture_target, dpy->rect_texture);
945 if (tex_w == width && tex_h == height) {
946 glCopyTexImage2D (dpy->gl_texture_target, 0, internalformat,
947 src_x, src_frame->height - src_y - height,
950 glTexImage2D (dpy->gl_texture_target, 0, internalformat, tex_w, tex_h,
951 0, dpy->screen->pixel_format, gl_pixel_type(dpy), NULL);
952 glCopyTexSubImage2D (dpy->gl_texture_target, 0, 0, 0,
953 src_x, src_frame->height - src_y - height,
957 jwxyz_bind_drawable (dpy, dpy->main_window, dst);
959 glBindTexture (dpy->gl_texture_target, dpy->rect_texture);
960 glEnable (dpy->gl_texture_target);
962 glEnableClientState (GL_TEXTURE_COORD_ARRAY);
963 glEnableClientState (GL_VERTEX_ARRAY);
965 /* TODO: Copied from XPutImage. Refactor. */
966 /* TODO: EXT_draw_texture or whatever it's called. */
967 GLfloat vertices[4][2] =
970 {dst_x, dst_y + height},
971 {dst_x + width, dst_y + height},
972 {dst_x + width, dst_y}
976 static const GLshort tex_coords[4][2] = {{0, 1}, {0, 0}, {1, 0}, {1, 1}};
978 GLshort tex_coords[4][2] = {{0, height}, {0, 0}, {width, 0}, {width, height}};
981 glVertexPointer (2, GL_FLOAT, 0, vertices);
982 glTexCoordPointer (2, GL_SHORT, 0, tex_coords);
983 glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
987 glDisable (dpy->gl_texture_target);
991 jwxyz_gl_copy_area_read_pixels (Display *dpy, Drawable src, Drawable dst,
992 GC gc, int src_x, int src_y,
993 unsigned int width, unsigned int height,
994 int dst_x, int dst_y)
997 XImage *img = XGetImage (dpy, src, src_x, src_y, width, height, ~0, ZPixmap);
998 XPutImage (dpy, dst, gc, img, 0, 0, dst_x, dst_y, width, height);
1003 /* Something may or may not be broken in here. (shrug) */
1004 bind_drawable(dpy, src);
1006 /* Error checking would be nice. */
1007 void *pixels = malloc (src_rect.size.width * 4 * src_rect.size.height);
1009 glPixelStorei (GL_PACK_ROW_LENGTH, 0);
1010 glPixelStorei (GL_PACK_ALIGNMENT, 4);
1012 glReadPixels (src_rect.origin.x, dst_frame.size.height - (src_rect.origin.y + src_rect.size.height),
1013 src_rect.size.width, src_rect.size.height,
1014 GL_RGBA, GL_UNSIGNED_BYTE, // TODO: Pick better formats.
1017 bind_drawable (dpy, dst);
1019 glPixelZoom (1.0f, 1.0f);
1021 glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
1022 glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
1024 glRasterPos2i (dst_rect.origin.x, dst_rect.origin.y + dst_rect.size.height);
1025 glDrawPixels (dst_rect.size.width, dst_rect.size.height,
1026 GL_RGBA, GL_UNSIGNED_BYTE, pixels);
1034 // TODO: Make sure offset works in super-sampled mode.
1036 adjust_point_for_line (GC gc, CGPoint *p)
1038 // Here's the authoritative discussion on how X draws lines:
1039 // http://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:CreateGC:line-width
1040 if (gc->gcv.line_width <= 1) {
1041 /* Thin lines are "drawn using an unspecified, device-dependent
1042 algorithm", but seriously though, Bresenham's algorithm. Bresenham's
1043 algorithm runs to and from pixel centers.
1045 There's a few screenhacks (Maze, at the very least) that set line_width
1046 to 1 when it probably should be set to 0, so it's line_width <= 1
1052 /* Thick lines OTOH run from the upper-left corners of pixels. This means
1053 that a horizontal thick line of width 1 straddles two scan lines.
1054 Aliasing requires one of these scan lines be chosen; the following
1055 nudges the point so that the right choice is made. */
1063 XDrawLine (Display *dpy, Drawable d, GC gc, int x1, int y1, int x2, int y2)
1065 // TODO: XDrawLine == XDrawSegments(nlines == 1), also in jwxyz.m
1071 XDrawSegments (dpy, d, gc, &segment, 1);
1073 // when drawing a zero-length line, obey line-width and cap-style.
1074 /* if (x1 == x2 && y1 == y2) {
1075 int w = gc->gcv.line_width;
1078 if (gc->gcv.line_width > 1 && gc->gcv.cap_style == CapRound)
1079 return XFillArc (dpy, d, gc, x1, y1, w, w, 0, 360*64);
1082 w = 1; // Actually show zero-length lines.
1083 return XFillRectangle (dpy, d, gc, x1, y1, w, w);
1087 CGPoint p = point_for_line (d, gc, x1, y1);
1089 push_fg_gc (dpy, d, gc, NO);
1091 CGContextRef cgc = d->cgc;
1092 set_line_mode (cgc, &gc->gcv);
1093 CGContextBeginPath (cgc);
1094 CGContextMoveToPoint (cgc, p.x, p.y);
1095 p = point_for_line(d, gc, x2, y2);
1096 CGContextAddLineToPoint (cgc, p.x, p.y);
1097 CGContextStrokePath (cgc);
1099 invalidate_drawable_cache (d); */
1104 XDrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count,
1107 jwxyz_bind_drawable (dpy, dpy->main_window, d);
1108 set_fg_gc (dpy, gc);
1110 /* TODO: Thick lines
1111 * Zero-length line segments
1112 * Paths with zero length total (Remember line width, cap style.)
1119 GLshort *vertices = malloc(2 * sizeof(GLshort) * count); // TODO malloc NULL sigh
1121 glMatrixMode (GL_MODELVIEW);
1122 glTranslatef (0.5f, 0.5f, 0);
1124 short p[2] = {0, 0};
1125 for (unsigned i = 0; i < count; i++) {
1126 next_point (p, points[i], mode);
1127 vertices[2 * i] = p[0];
1128 vertices[2 * i + 1] = p[1];
1131 glEnableClientState (GL_VERTEX_ARRAY);
1132 glDisableClientState (GL_TEXTURE_COORD_ARRAY);
1133 glVertexPointer (2, GL_SHORT, 0, vertices);
1134 glDrawArrays (GL_LINE_STRIP, 0, count);
1138 if (gc->gcv.cap_style != CapNotLast) {
1139 // TODO: How does this look with multisampling?
1140 // TODO: Disable me for closed loops.
1141 glVertexPointer (2, GL_SHORT, 0, p);
1142 glDrawArrays (GL_POINTS, 0, 1);
1152 XDrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count)
1154 jwxyz_bind_drawable (dpy, dpy->main_window, d);
1155 set_fg_gc (dpy, gc);
1157 /* TODO: Thick lines. */
1158 /* Thin lines <= 1px are offset by +0.5; thick lines are not. */
1160 glMatrixMode (GL_MODELVIEW);
1161 glTranslatef (0.5, 0.5, 0);
1163 glEnableClientState (GL_VERTEX_ARRAY);
1164 glDisableClientState (GL_TEXTURE_COORD_ARRAY);
1166 Assert (sizeof(XSegment) == sizeof(short) * 4, "XDrawSegments: Data alignment mix-up."); // TODO: Static assert here.
1167 Assert (sizeof(GLshort) == sizeof(short), "XDrawSegments: Data alignment mix-up."); // TODO: Static assert here.
1168 Assert (offsetof(XSegment, x1) == 0, "XDrawSegments: Data alignment mix-up.");
1169 Assert (offsetof(XSegment, x2) == 4, "XDrawSegments: Data alignment mix-up.");
1170 glVertexPointer (2, GL_SHORT, 0, segments);
1171 glDrawArrays (GL_LINES, 0, count * 2);
1173 if (gc->gcv.cap_style != CapNotLast) {
1174 glVertexPointer (2, GL_SHORT, sizeof(GLshort) * 4, (const GLshort *)segments + 2);
1175 glDrawArrays (GL_POINTS, 0, count);
1180 /* CGRect wr = d->frame;
1181 push_fg_gc (dpy, d, gc, NO);
1182 set_line_mode (cgc, &gc->gcv);
1183 CGContextBeginPath (cgc);
1184 for (i = 0; i < count; i++) {
1185 CGPoint p = point_for_line (d, gc, segments->x1, segments->y1);
1186 CGContextMoveToPoint (cgc, p.x, p.y);
1187 p = point_for_line (d, gc, segments->x2, segments->y2);
1188 CGContextAddLineToPoint (cgc, p.x, p.y);
1191 CGContextStrokePath (cgc);
1193 invalidate_drawable_cache (d); */
1199 XClearWindow (Display *dpy, Window win)
1201 Assert (win == dpy->main_window, "not a window");
1202 const XRectangle *wr = jwxyz_frame (win);
1203 /* TODO: Use glClear if there's no background pixmap. */
1204 return XClearArea (dpy, win, 0, 0, wr->width, wr->height, 0);
1208 jwxyz_window_background (Display *dpy)
1210 return dpy->window_background;
1214 XSetWindowBackground (Display *dpy, Window w, unsigned long pixel)
1216 Assert (w == dpy->main_window, "not a window");
1217 jwxyz_validate_pixel (dpy, pixel, visual_depth (NULL, NULL), False);
1218 dpy->window_background = pixel;
1223 jwxyz_fill_rects (Display *dpy, Drawable d, GC gc,
1224 const XRectangle *rectangles, unsigned long nrectangles,
1225 unsigned long pixel)
1227 jwxyz_bind_drawable (dpy, dpy->main_window, d);
1228 set_color_gc (dpy, gc, pixel);
1231 for (unsigned i = 0; i != nrectangles; ++i) {
1232 const XRectangle *r = &rectangles[i];
1233 glVertex2i(r->x, r->y);
1234 glVertex2i(r->x, r->y + r->height);
1235 glVertex2i(r->x + r->width, r->y + r->height);
1236 glVertex2i(r->x + r->width, r->y);
1240 glEnableClientState (GL_VERTEX_ARRAY);
1241 glDisableClientState (GL_TEXTURE_COORD_ARRAY);
1243 for (unsigned long i = 0; i != nrectangles; ++i)
1245 const XRectangle *r = &rectangles[i];
1247 GLfloat coords[4][2] =
1250 {r->x, r->y + r->height},
1251 {r->x + r->width, r->y + r->height},
1252 {r->x + r->width, r->y}
1255 // TODO: Several rects at once. Maybe even tune for XScreenSaver workloads.
1256 glVertexPointer (2, GL_FLOAT, 0, coords);
1258 glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
1265 XClearArea (Display *dpy, Window win, int x, int y, int w, int h, Bool exp)
1267 Assert(win == dpy->main_window, "XClearArea: not a window");
1268 Assert(!exp, "XClearArea: exposures unsupported");
1270 jwxyz_fill_rect (dpy, win, 0, x, y, w, h, dpy->window_background);
1276 XFillPolygon (Display *dpy, Drawable d, GC gc,
1277 XPoint *points, int npoints, int shape, int mode)
1279 jwxyz_bind_drawable (dpy, dpy->main_window, d);
1282 // TODO: Re-implement the GLU tesselation functions.
1284 /* Complex: Pedal, and for some reason Attraction, Mountain, Qix, SpeedMine, Starfish
1285 * Nonconvex: Goop, Pacman, Rocks, Speedmine
1288 Assert(shape == Convex, "XFillPolygon: (TODO) Unimplemented shape");
1290 // TODO: Feed vertices straight to OpenGL for CoordModeOrigin.
1291 GLshort *vertices = malloc(npoints * sizeof(GLshort) * 2); // TODO: Oh look, another unchecked malloc.
1292 short v[2] = {0, 0};
1294 for (unsigned i = 0; i < npoints; i++) {
1295 next_point(v, points[i], mode);
1296 vertices[2 * i] = v[0];
1297 vertices[2 * i + 1] = v[1];
1300 glEnableClientState (GL_VERTEX_ARRAY);
1301 glDisableClientState (GL_TEXTURE_COORD_ARRAY);
1303 glVertexPointer (2, GL_SHORT, 0, vertices);
1304 glDrawArrays (GL_TRIANGLE_FAN, 0, npoints);
1309 CGRect wr = d->frame;
1311 push_fg_gc (dpy, d, gc, YES);
1312 CGContextRef cgc = d->cgc;
1313 CGContextBeginPath (cgc);
1315 for (i = 0; i < npoints; i++) {
1316 if (i > 0 && mode == CoordModePrevious) {
1320 x = wr.origin.x + points[i].x;
1321 y = wr.origin.y + wr.size.height - points[i].y;
1325 CGContextMoveToPoint (cgc, x, y);
1327 CGContextAddLineToPoint (cgc, x, y);
1329 CGContextClosePath (cgc);
1330 if (gc->gcv.fill_rule == EvenOddRule)
1331 CGContextEOFillPath (cgc);
1333 CGContextFillPath (cgc);
1335 invalidate_drawable_cache (d);
1340 #define radians(DEG) ((DEG) * M_PI / 180.0)
1341 #define degrees(RAD) ((RAD) * 180.0 / M_PI)
1344 arc_xy(GLfloat *p, double cx, double cy, double w2, double h2, double theta)
1346 p[0] = cos(theta) * w2 + cx;
1347 p[1] = -sin(theta) * h2 + cy;
1351 mod_neg(int a, unsigned b)
1353 /* Normal modulus is implementation defined for negative numbers. This is
1354 * well-defined such that the repeating pattern for a >= 0 is maintained for
1356 return a < 0 ? (b - 1) - (-(a + 1) % b) : a % b;
1360 jwxyz_draw_arc (Display *dpy, Drawable d, GC gc, int x, int y,
1361 unsigned int width, unsigned int height,
1362 int angle1, int angle2, Bool fill_p)
1364 jwxyz_bind_drawable (dpy, dpy->main_window, d);
1367 /* Let's say the number of line segments needed to make a convincing circle is
1368 4*sqrt(radius). (But these arcs aren't necessarily circular arcs...) */
1370 double w2 = width * 0.5f, h2 = height * 0.5f;
1371 double a, b; /* Semi-major/minor axes. */
1380 const double two_pi = 2 * M_PI;
1382 double amb = a - b, apb = a + b;
1383 double h = (amb * amb) / (apb * apb);
1384 // TODO: Math cleanup.
1385 double C_approx = M_PI * apb * (1 + 3 * h / (10 + sqrtf(4 - 3 * h)));
1386 double segments_f = 4 * sqrtf(C_approx / (2 * M_PI));
1388 // TODO: Explain how drawing works what with the points of overlapping arcs
1392 unsigned segments_360 = segments_f;
1394 /* TODO: angle2 == 0. This is a tilted square with CapSquare. */
1395 /* TODO: color, thick lines, CapNotLast for thin lines */
1396 /* TODO: Transformations. */
1398 double segment_angle = two_pi / segments_360;
1400 const unsigned deg64 = 360 * 64;
1401 const double rad_from_deg64 = two_pi / deg64;
1408 angle1 = mod_neg(angle1, deg64); // TODO: Is this OK? Consider negative numbers.
1411 angle2 = deg64; // TODO: Handle circles special.
1414 angle1_f = angle1 * rad_from_deg64,
1415 angle2_f = angle2 * rad_from_deg64;
1417 if (angle2_f > two_pi) // TODO: Move this up.
1420 double segment1_angle_part = fmodf(angle1_f, segment_angle);
1422 unsigned segment1 = ((angle1_f - segment1_angle_part) / segment_angle) + 1.5;
1424 double angle_2r = angle2_f - segment1_angle_part;
1425 unsigned segments = angle_2r / segment_angle;
1427 GLfloat cx = x + w2, cy = y + h2;
1429 GLfloat *data = malloc((segments + 3) * sizeof(GLfloat) * 2); // TODO: Check result.
1431 GLfloat *data_ptr = data;
1438 arc_xy (data_ptr, cx, cy, w2, h2, angle1_f);
1441 for (unsigned s = 0; s != segments; ++s) {
1442 // TODO: Make sure values of theta for the following arc_xy call are between
1443 // angle1_f and angle1_f + angle2_f.
1444 arc_xy (data_ptr, cx, cy, w2, h2, (segment1 + s) * segment_angle);
1448 arc_xy (data_ptr, cx, cy, w2, h2, angle1_f + angle2_f);
1451 glDisableClientState (GL_TEXTURE_COORD_ARRAY);
1452 glEnableClientState (GL_VERTEX_ARRAY);
1454 glVertexPointer (2, GL_FLOAT, 0, data);
1455 glDrawArrays (fill_p ? GL_TRIANGLE_FAN : GL_LINE_STRIP,
1457 (GLsizei)((data_ptr - data) / 2));
1464 unsigned segments = segments_f * (fabs(angle2) / (360 * 64));
1466 glBegin (fill_p ? GL_TRIANGLE_FAN : GL_LINE_STRIP);
1468 if (fill_p /* && gc->gcv.arc_mode == ArcPieSlice */)
1469 glVertex2f (cx, cy);
1471 /* TODO: This should fix the middle points of the arc so that the starting and ending points are OK. */
1473 float to_radians = 2 * M_PI / (360 * 64);
1474 float theta = angle1 * to_radians, d_theta = angle2 * to_radians / segments;
1476 for (unsigned s = 0; s != segments + 1; ++s) /* TODO: This is the right number of segments, yes? */
1478 glVertex2f(cos(theta) * w2 + cx, -sin(theta) * h2 + cy);
1490 jwxyz_gc_gcv (GC gc)
1497 jwxyz_gc_depth (GC gc)
1504 XCreateGC (Display *dpy, Drawable d, unsigned long mask, XGCValues *xgcv)
1506 struct jwxyz_GC *gc = (struct jwxyz_GC *) calloc (1, sizeof(*gc));
1507 gc->depth = jwxyz_drawable_depth (d);
1509 jwxyz_gcv_defaults (dpy, &gc->gcv, gc->depth);
1510 XChangeGC (dpy, gc, mask, xgcv);
1516 XFreeGC (Display *dpy, GC gc)
1519 XUnloadFont (dpy, gc->gcv.font);
1523 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
1525 if (gc->gcv.clip_mask) {
1526 XFreePixmap (dpy, gc->gcv.clip_mask);
1527 CGImageRelease (gc->clip_mask);
1537 flipbits (unsigned const char *in, unsigned char *out, int length)
1539 static const unsigned char table[256] = {
1540 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0,
1541 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
1542 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8,
1543 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
1544 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4,
1545 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
1546 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC,
1547 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
1548 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2,
1549 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
1550 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA,
1551 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
1552 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6,
1553 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
1554 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE,
1555 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
1556 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1,
1557 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
1558 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9,
1559 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
1560 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5,
1561 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
1562 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED,
1563 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
1564 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3,
1565 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
1566 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB,
1567 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
1568 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7,
1569 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
1570 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF,
1571 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
1573 while (length-- > 0)
1574 *out++ = table[*in++];
1578 // Copied and pasted from OSX/XScreenSaverView.m
1585 size_t mask = (size_t)-1;
1586 unsigned bits = sizeof(x) * CHAR_BIT;
1587 unsigned log2 = bits;
1605 XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
1606 int src_x, int src_y, int dest_x, int dest_y,
1607 unsigned int w, unsigned int h)
1609 jwxyz_assert_display (dpy);
1611 const XRectangle *wr = jwxyz_frame (d);
1613 Assert (gc, "no GC");
1614 Assert ((w < 65535), "improbably large width");
1615 Assert ((h < 65535), "improbably large height");
1616 Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x");
1617 Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y");
1618 Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x");
1619 Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y");
1621 // Clip width and height to the bounds of the Drawable
1623 if (dest_x + w > wr->width) {
1624 if (dest_x > wr->width)
1626 w = wr->width - dest_x;
1628 if (dest_y + h > wr->height) {
1629 if (dest_y > wr->height)
1631 h = wr->height - dest_y;
1633 if (w <= 0 || h <= 0)
1636 // Clip width and height to the bounds of the XImage
1638 if (src_x + w > ximage->width) {
1639 if (src_x > ximage->width)
1641 w = ximage->width - src_x;
1643 if (src_y + h > ximage->height) {
1644 if (src_y > ximage->height)
1646 h = ximage->height - src_y;
1648 if (w <= 0 || h <= 0)
1651 /* Assert (d->win */
1653 if (jwxyz_dumb_drawing_mode(dpy, d, gc, dest_x, dest_y, w, h))
1656 jwxyz_bind_drawable (dpy, dpy->main_window, d);
1657 int bpl = ximage->bytes_per_line;
1658 int bpp = ximage->bits_per_pixel;
1659 /* int bsize = bpl * h; */
1660 char *data = ximage->data;
1664 r.origin.x = wr->x + dest_x;
1665 r.origin.y = wr->y + wr->height - dest_y - h;
1670 Assert (gc->gcv.function == GXcopy, "XPutImage: (TODO) GC function not supported");
1671 Assert (!gc->gcv.clip_mask, "XPutImage: (TODO) GC clip mask not supported");
1675 /* Take advantage of the fact that it's ok for (bpl != w * bpp)
1676 to create a CGImage from a sub-rectagle of the XImage.
1678 data += (src_y * bpl) + (src_x * 4);
1680 jwxyz_assert_display(dpy);
1682 /* There probably won't be any hacks that do this, but... */
1683 Assert (!(bpl % 4), "XPutImage: bytes_per_line not divisible by four.");
1685 unsigned src_w = bpl / 4;
1687 /* GL_UNPACK_ROW_LENGTH is not allowed to be negative. (sigh) */
1688 # ifndef HAVE_JWZGLES
1689 glPixelStorei (GL_UNPACK_ROW_LENGTH, src_w);
1693 glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
1695 # if 1 // defined HAVE_JWZGLES
1696 // Regular OpenGL uses GL_TEXTURE_RECTANGLE_EXT in place of GL_TEXTURE_2D.
1697 // TODO: Make use of OES_draw_texture.
1698 // TODO: Coords might be wrong; things might be upside-down or backwards
1701 unsigned tex_w = src_w, tex_h = h;
1702 if (!dpy->gl_texture_npot_p) {
1703 tex_w = to_pow2(tex_w);
1704 tex_h = to_pow2(tex_h);
1707 GLint internalformat = texture_internalformat(dpy);
1709 glBindTexture (dpy->gl_texture_target, dpy->rect_texture);
1711 if (tex_w == src_w && tex_h == h) {
1712 glTexImage2D (dpy->gl_texture_target, 0, internalformat, tex_w, tex_h,
1713 0, dpy->screen->pixel_format, gl_pixel_type(dpy), data);
1715 // TODO: Sampling the last row might be a problem if src_x != 0.
1716 glTexImage2D (dpy->gl_texture_target, 0, internalformat, tex_w, tex_h,
1717 0, dpy->screen->pixel_format, gl_pixel_type(dpy), NULL);
1718 glTexSubImage2D (dpy->gl_texture_target, 0, 0, 0, src_w, h,
1719 dpy->screen->pixel_format, gl_pixel_type(dpy), data);
1723 // glEnable (dpy->gl_texture_target);
1724 // glColor4f (0.5, 0, 1, 1);
1725 glEnable (dpy->gl_texture_target);
1726 glEnableClientState (GL_VERTEX_ARRAY);
1727 glEnableClientState (GL_TEXTURE_COORD_ARRAY);
1729 // TODO: Why are these ever turned on in the first place?
1730 glDisableClientState (GL_COLOR_ARRAY);
1731 glDisableClientState (GL_NORMAL_ARRAY);
1732 // glDisableClientState (GL_TEXTURE_COORD_ARRAY);
1734 GLfloat vertices[4][2] =
1737 {dest_x, dest_y + h},
1738 {dest_x + w, dest_y + h},
1739 {dest_x + w, dest_y}
1742 GLfloat texcoord_w, texcoord_h;
1743 # ifndef HAVE_JWZGLES
1744 if (dpy->gl_texture_target == GL_TEXTURE_RECTANGLE_EXT) {
1748 # endif /* HAVE_JWZGLES */
1750 texcoord_w = (double)w / tex_w;
1751 texcoord_h = (double)h / tex_h;
1754 GLfloat tex_coords[4][2];
1755 tex_coords[0][0] = 0;
1756 tex_coords[0][1] = 0;
1757 tex_coords[1][0] = 0;
1758 tex_coords[1][1] = texcoord_h;
1759 tex_coords[2][0] = texcoord_w;
1760 tex_coords[2][1] = texcoord_h;
1761 tex_coords[3][0] = texcoord_w;
1762 tex_coords[3][1] = 0;
1764 glVertexPointer (2, GL_FLOAT, 0, vertices);
1765 glTexCoordPointer (2, GL_FLOAT, 0, tex_coords);
1767 // Respect the alpha channel in the XImage if we're using alpha.
1768 if (gc->gcv.alpha_allowed_p) {
1769 glEnable (GL_BLEND);
1770 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1773 glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
1775 if (gc->gcv.alpha_allowed_p)
1776 glDisable (GL_BLEND);
1779 glDisable (dpy->gl_texture_target);
1781 glRasterPos2i (dest_x, dest_y);
1782 glPixelZoom (1, -1);
1783 jwxyz_assert_display (dpy);
1784 glDrawPixels (w, h, dpy->screen->pixel_format, gl_pixel_type(dpy), data);
1786 } else { // (bpp == 1)
1788 // Assert(FALSE, "XPutImage: TODO");
1789 // Check out ximage_(get|put)pixel_1
1792 /* To draw a 1bpp image, we use it as a mask and fill two rectangles.
1794 #### However, the bit order within a byte in a 1bpp XImage is
1795 the wrong way around from what Quartz expects, so first we
1796 have to copy the data to reverse it. Shit! Maybe it
1797 would be worthwhile to go through the hacks and #ifdef
1798 each one that diddles 1bpp XImage->data directly...
1800 Assert ((src_x % 8) == 0,
1801 "XPutImage with non-byte-aligned 1bpp X offset not implemented");
1803 data += (src_y * bpl) + (src_x / 8); // move to x,y within the data
1804 unsigned char *flipped = (unsigned char *) malloc (bsize);
1806 flipbits ((unsigned char *) data, flipped, bsize);
1808 CGDataProviderRef prov =
1809 CGDataProviderCreateWithData (NULL, flipped, bsize, NULL);
1810 CGImageRef mask = CGImageMaskCreate (w, h,
1813 NULL, /* decode[] */
1814 GL_FALSE); /* interpolate */
1815 push_fg_gc (dpy, d, gc, GL_TRUE);
1817 CGContextFillRect (cgc, r); // foreground color
1818 CGContextClipToMask (cgc, r, mask);
1819 set_color (dpy, cgc, gc->gcv.background, gc->depth, GL_FALSE, GL_TRUE);
1820 CGContextFillRect (cgc, r); // background color
1824 CGDataProviderRelease (prov);
1825 CGImageRelease (mask);
1830 invalidate_drawable_cache (d);
1835 /* At the moment nothing actually uses XGetSubImage. */
1836 /* #### Actually lots of things call XGetImage, which calls XGetSubImage.
1837 E.g., Twang calls XGetImage on the window intending to get a
1838 buffer full of black. This is returning a buffer full of white
1839 instead of black for some reason. */
1841 XGetSubImage (Display *dpy, Drawable d, int x, int y,
1842 unsigned int width, unsigned int height,
1843 unsigned long plane_mask, int format,
1844 XImage *dest_image, int dest_x, int dest_y)
1846 Assert ((width < 65535), "improbably large width");
1847 Assert ((height < 65535), "improbably large height");
1848 Assert ((x < 65535 && x > -65535), "improbably large x");
1849 Assert ((y < 65535 && y > -65535), "improbably large y");
1851 jwxyz_bind_drawable (dpy, dpy->main_window, d);
1853 // TODO: What if this reads off the edge? What is supposed to happen?
1856 // In case the caller tries to write off the edge.
1858 max_width = dest_image->width - dest_x,
1859 max_height = dest_image->height - dest_y;
1861 if (width > max_width) {
1865 if (height > max_height) {
1866 height = max_height;
1870 Assert (jwxyz_drawable_depth (d) == dest_image->depth, "XGetSubImage: depth mismatch");
1872 if (dest_image->depth == visual_depth (NULL, NULL)) {
1873 Assert (!(dest_image->bytes_per_line % 4), "XGetSubImage: bytes_per_line not divisible by 4");
1874 unsigned pixels_per_line = dest_image->bytes_per_line / 4;
1876 Assert (pixels_per_line == width, "XGetSubImage: (TODO) pixels_per_line != width");
1878 glPixelStorei (GL_PACK_ROW_LENGTH, pixels_per_line);
1880 glPixelStorei (GL_PACK_ALIGNMENT, 4);
1882 uint32_t *dest_data = (uint32_t *)dest_image->data + pixels_per_line * dest_y + dest_x;
1884 glReadPixels (x, jwxyz_frame (d)->height - (y + height), width, height,
1885 dpy->screen->pixel_format, gl_pixel_type(dpy), dest_data);
1887 /* Flip this upside down. :( */
1888 uint32_t *top = dest_data;
1889 uint32_t *bottom = dest_data + pixels_per_line * (height - 1);
1890 for (unsigned y = height / 2; y; --y) {
1891 for (unsigned x = 0; x != width; ++x) {
1892 uint32_t px = top[x];
1896 top += pixels_per_line;
1897 bottom -= pixels_per_line;
1901 /* TODO: Actually get pixels. */
1903 Assert (!(dest_x % 8), "XGetSubImage: dest_x not byte-aligned");
1904 uint8_t *dest_data =
1905 (uint8_t *)dest_image->data + dest_image->bytes_per_line * dest_y
1907 for (unsigned y = height / 2; y; --y) {
1908 memset (dest_data, y & 1 ? 0x55 : 0xAA, width / 8);
1909 dest_data += dest_image->bytes_per_line;
1917 XGetImage (Display *dpy, Drawable d, int x, int y,
1918 unsigned int width, unsigned int height,
1919 unsigned long plane_mask, int format)
1921 unsigned depth = jwxyz_drawable_depth (d);
1922 XImage *image = XCreateImage (dpy, 0, depth, format, 0, 0, width, height,
1924 image->data = (char *) malloc (height * image->bytes_per_line);
1926 return XGetSubImage (dpy, d, x, y, width, height, plane_mask, format,
1930 /* Returns a transformation matrix to do rotation as per the provided
1931 EXIF "Orientation" value.
1934 static CGAffineTransform
1935 exif_rotate (int rot, CGSize rect)
1937 CGAffineTransform trans = CGAffineTransformIdentity;
1939 case 2: // flip horizontal
1940 trans = CGAffineTransformMakeTranslation (rect.width, 0);
1941 trans = CGAffineTransformScale (trans, -1, 1);
1944 case 3: // rotate 180
1945 trans = CGAffineTransformMakeTranslation (rect.width, rect.height);
1946 trans = CGAffineTransformRotate (trans, M_PI);
1949 case 4: // flip vertical
1950 trans = CGAffineTransformMakeTranslation (0, rect.height);
1951 trans = CGAffineTransformScale (trans, 1, -1);
1954 case 5: // transpose (UL-to-LR axis)
1955 trans = CGAffineTransformMakeTranslation (rect.height, rect.width);
1956 trans = CGAffineTransformScale (trans, -1, 1);
1957 trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
1960 case 6: // rotate 90
1961 trans = CGAffineTransformMakeTranslation (0, rect.width);
1962 trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
1965 case 7: // transverse (UR-to-LL axis)
1966 trans = CGAffineTransformMakeScale (-1, 1);
1967 trans = CGAffineTransformRotate (trans, M_PI / 2);
1970 case 8: // rotate 270
1971 trans = CGAffineTransformMakeTranslation (rect.height, 0);
1972 trans = CGAffineTransformRotate (trans, M_PI / 2);
1984 jwxyz_draw_NSImage_or_CGImage (Display *dpy, Drawable d,
1985 Bool nsimg_p, void *img_arg,
1986 XRectangle *geom_ret, int exif_rotation)
1988 Assert (False, "jwxyz_draw_NSImage_or_CGImage: TODO stub");
1992 CGImageSourceRef cgsrc;
1993 # endif // USE_IPHONE
1996 CGContextRef cgc = d->cgc;
2000 NSImage *nsimg = (NSImage *) img_arg;
2001 imgr = [nsimg size];
2004 // convert the NSImage to a CGImage via the toll-free-bridging
2005 // of NSData and CFData...
2007 NSData *nsdata = [NSBitmapImageRep
2008 TIFFRepresentationOfImageRepsInArray:
2009 [nsimg representations]];
2010 CFDataRef cfdata = (CFDataRef) nsdata;
2011 cgsrc = CGImageSourceCreateWithData (cfdata, NULL);
2012 cgi = CGImageSourceCreateImageAtIndex (cgsrc, 0, NULL);
2013 # else // USE_IPHONE
2014 cgi = nsimg.CGImage;
2015 # endif // USE_IPHONE
2018 cgi = (CGImageRef) img_arg;
2019 imgr.width = CGImageGetWidth (cgi);
2020 imgr.height = CGImageGetHeight (cgi);
2023 Bool rot_p = (exif_rotation >= 5);
2026 imgr = NSMakeSize (imgr.height, imgr.width);
2028 CGRect winr = d->frame;
2029 float rw = winr.size.width / imgr.width;
2030 float rh = winr.size.height / imgr.height;
2031 float r = (rw < rh ? rw : rh);
2034 dst.size.width = imgr.width * r;
2035 dst.size.height = imgr.height * r;
2036 dst.origin.x = (winr.size.width - dst.size.width) / 2;
2037 dst.origin.y = (winr.size.height - dst.size.height) / 2;
2039 dst2.origin.x = dst2.origin.y = 0;
2041 dst2.size.width = dst.size.height;
2042 dst2.size.height = dst.size.width;
2044 dst2.size = dst.size;
2047 // Clear the part not covered by the image to background or black.
2049 if (d->type == WINDOW)
2050 XClearWindow (dpy, d);
2052 jwxyz_fill_rect (dpy, d, 0, 0, 0, winr.size.width, winr.size.height,
2053 drawable_depth (d) == 1 ? 0 : BlackPixel(dpy,0));
2056 CGAffineTransform trans =
2057 exif_rotate (exif_rotation, rot_p ? dst2.size : dst.size);
2059 CGContextSaveGState (cgc);
2060 CGContextConcatCTM (cgc,
2061 CGAffineTransformMakeTranslation (dst.origin.x,
2063 CGContextConcatCTM (cgc, trans);
2064 //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
2065 CGContextDrawImage (cgc, dst2, cgi);
2066 CGContextRestoreGState (cgc);
2071 CGImageRelease (cgi);
2073 # endif // USE_IPHONE
2076 geom_ret->x = dst.origin.x;
2077 geom_ret->y = dst.origin.y;
2078 geom_ret->width = dst.size.width;
2079 geom_ret->height = dst.size.height;
2082 invalidate_drawable_cache (d);
2086 #ifndef HAVE_JWZGLES
2090 create_rectangle_texture (GLuint *texture)
2092 glGenTextures(1, texture);
2093 glBindTexture(GL_TEXTURE_RECTANGLE_EXT, *texture);
2094 glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2095 glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2104 copy_pixmap (Display *dpy, Pixmap p)
2107 Assert (p->type == PIXMAP, "not a pixmap");
2113 unsigned int width, height, border_width, depth;
2114 if (XGetGeometry (dpy, p, &root,
2115 &x, &y, &width, &height, &border_width, &depth)) {
2117 gcv.function = GXcopy;
2118 GC gc = XCreateGC (dpy, p, GCFunction, &gcv);
2120 p2 = XCreatePixmap (dpy, p, width, height, depth);
2122 XCopyArea (dpy, p, p2, gc, 0, 0, width, height, 0, 0);
2127 Assert (p2, "could not copy pixmap");
2134 // This is XQueryFont, but for the XFontStruct embedded in 'Font'
2137 query_font (Font fid)
2139 if (!fid || !fid->native_font) {
2140 Assert (0, "no native font in fid");
2147 Display *dpy = fid->dpy;
2148 void *native_font = fid->native_font;
2150 XFontStruct *f = &fid->metrics;
2151 XCharStruct *min = &f->min_bounds;
2152 XCharStruct *max = &f->max_bounds;
2155 f->min_char_or_byte2 = first;
2156 f->max_char_or_byte2 = last;
2157 f->default_char = 'M';
2158 f->ascent = fid->ascent;
2159 f->descent = fid->descent;
2161 min->width = 32767; // set to smaller values in the loop
2162 min->ascent = 32767;
2163 min->descent = 32767;
2164 min->lbearing = 32767;
2165 min->rbearing = 32767;
2167 f->per_char = (XCharStruct *) calloc (last-first+2, sizeof (XCharStruct));
2169 for (int i = first; i <= last; i++) {
2170 XCharStruct *cs = &f->per_char[i-first];
2172 jwxyz_render_text (dpy, native_font, &s, 1, GL_FALSE, cs, 0);
2174 max->width = MAX (max->width, cs->width);
2175 max->ascent = MAX (max->ascent, cs->ascent);
2176 max->descent = MAX (max->descent, cs->descent);
2177 max->lbearing = MAX (max->lbearing, cs->lbearing);
2178 max->rbearing = MAX (max->rbearing, cs->rbearing);
2180 min->width = MIN (min->width, cs->width);
2181 min->ascent = MIN (min->ascent, cs->ascent);
2182 min->descent = MIN (min->descent, cs->descent);
2183 min->lbearing = MIN (min->lbearing, cs->lbearing);
2184 min->rbearing = MIN (min->rbearing, cs->rbearing);
2186 Log (" %3d %c: w=%3d lb=%3d rb=%3d as=%3d ds=%3d\n",
2187 i, i, cs->width, cs->lbearing, cs->rbearing,
2188 cs->ascent, cs->descent);
2194 // Since 'Font' includes the metrics, this just makes a copy of that.
2197 XQueryFont (Display *dpy, Font fid)
2200 XFontStruct *f = (XFontStruct *) calloc (1, sizeof(*f));
2205 f->n_properties = 1;
2206 f->properties = malloc (sizeof(*f->properties) * f->n_properties);
2207 f->properties[0].name = XA_FONT;
2208 Assert (sizeof (f->properties[0].card32) >= sizeof (char *),
2209 "atoms probably needs a real implementation");
2210 // If XInternAtom is ever implemented, use it here.
2211 f->properties[0].card32 = (unsigned long)(char *)fid->xa_font;
2213 // copy XCharStruct array
2214 int size = (f->max_char_or_byte2 - f->min_char_or_byte2) + 1;
2215 f->per_char = (XCharStruct *) calloc (size + 2, sizeof (XCharStruct));
2217 memcpy (f->per_char, fid->metrics.per_char,
2218 size * sizeof (XCharStruct));
2225 copy_font (Font fid)
2236 font_family_members (NSString *family_name)
2239 return [[NSFontManager sharedFontManager]
2240 availableMembersOfFontFamily:family_name];
2242 return [UIFont fontNamesForFamilyName:family_name];
2248 default_font_family (NSFontTraitMask require)
2250 return require & NSFixedPitchFontMask ? @"Courier" : @"Verdana";
2255 try_font (NSFontTraitMask traits, NSFontTraitMask mask,
2256 NSString *family_name, float size,
2259 Assert (size > 0, "zero font size");
2261 NSArray *family_members = font_family_members (family_name);
2262 if (!family_members.count)
2263 family_members = font_family_members (default_font_family (traits));
2266 for (unsigned k = 0; k != family_members.count; ++k) {
2268 NSArray *member = [family_members objectAtIndex:k];
2269 NSFontTraitMask font_mask =
2270 [(NSNumber *)[member objectAtIndex:3] unsignedIntValue];
2272 if ((font_mask & mask) == traits) {
2274 NSString *name = [member objectAtIndex:0];
2275 NSFont *f = [NSFont fontWithName:name size:size];
2279 /* Don't use this font if it (probably) doesn't include ASCII characters.
2281 NSStringEncoding enc = [f mostCompatibleStringEncoding];
2282 if (! (enc == NSUTF8StringEncoding ||
2283 enc == NSISOLatin1StringEncoding ||
2284 enc == NSNonLossyASCIIStringEncoding ||
2285 enc == NSISOLatin2StringEncoding ||
2286 enc == NSUnicodeStringEncoding ||
2287 enc == NSWindowsCP1250StringEncoding ||
2288 enc == NSWindowsCP1252StringEncoding ||
2289 enc == NSMacOSRomanStringEncoding)) {
2290 // NSLog(@"skipping \"%@\": encoding = %d", name, enc);
2293 // NSLog(@"using \"%@\": %d", name, enc);
2295 // *name_ret = strdup ([name cStringUsingEncoding:NSUTF8StringEncoding]);
2296 *name_ret = strdup (name.UTF8String);
2300 # else // USE_IPHONE
2302 for (NSString *fn in family_members) {
2304 ([fn rangeOfString:X options:NSCaseInsensitiveSearch].location \
2307 // The magic invocation for getting font names is
2308 // [[UIFontDescriptor
2309 // fontDescriptorWithFontAttributes:@{UIFontDescriptorNameAttribute: name}]
2311 // ...but this only works on iOS 7 and later.
2312 NSFontTraitMask font_mask = 0;
2314 font_mask |= NSBoldFontMask;
2315 if (MATCH(@"Italic") || MATCH(@"Oblique"))
2316 font_mask |= NSItalicFontMask;
2318 if ((font_mask & mask) == traits) {
2320 /* Check if it can do ASCII. No good way to accomplish this!
2321 These are fonts present in iPhone Simulator as of June 2012
2322 that don't include ASCII.
2324 if (MATCH(@"AppleGothic") || // Korean
2325 MATCH(@"Dingbats") || // Dingbats
2326 MATCH(@"Emoji") || // Emoticons
2327 MATCH(@"Geeza") || // Arabic
2328 MATCH(@"Hebrew") || // Hebrew
2329 MATCH(@"HiraKaku") || // Japanese
2330 MATCH(@"HiraMin") || // Japanese
2331 MATCH(@"Kailasa") || // Tibetan
2332 MATCH(@"Ornaments") || // Dingbats
2333 MATCH(@"STHeiti") // Chinese
2337 *name_ret = strdup (fn.UTF8String);
2338 return [UIFont fontWithName:fn size:size];
2350 /* On Cocoa and iOS, fonts may be specified as "Georgia Bold 24" instead
2351 of XLFD strings; also they can be comma-separated strings with multiple
2352 font names. First one that exists wins.
2355 try_native_font (const char *name, float scale,
2356 char **name_ret, float *size_ret, char **xa_font)
2358 if (!name) return 0;
2359 const char *spc = strrchr (name, ' ');
2363 char *token = strdup (name);
2366 while ((name2 = strtok (token, ","))) {
2369 while (*name2 == ' ' || *name2 == '\t' || *name2 == '\n')
2372 spc = strrchr (name2, ' ');
2376 if (1 != sscanf (spc, " %d ", &dsize))
2380 if (size <= 4) continue;
2384 name2[strlen(name2) - strlen(spc)] = 0;
2386 NSString *nsname = [NSString stringWithCString:name2
2387 encoding:NSUTF8StringEncoding];
2388 f = [NSFont fontWithName:nsname size:size];
2392 *xa_font = strdup (name); // Maybe this should be an XLFD?
2395 NSLog(@"No native font: \"%@\" %.0f", nsname, size);
2404 /* Returns a random font in the given size and face.
2407 random_font (NSFontTraitMask traits, NSFontTraitMask mask,
2408 float size, NSString **family_ret, char **name_ret)
2412 // Providing Unbold or Unitalic in the mask for availableFontNamesWithTraits
2413 // returns an empty list, at least on a system with default fonts only.
2414 NSArray *families = [[NSFontManager sharedFontManager]
2415 availableFontFamilies];
2416 if (!families) return 0;
2418 NSArray *families = [UIFont familyNames];
2420 // There are many dups in the families array -- uniquify it.
2422 NSArray *sorted_families =
2423 [families sortedArrayUsingSelector:@selector(compare:)];
2424 NSMutableArray *new_families =
2425 [NSMutableArray arrayWithCapacity:sorted_families.count];
2427 NSString *prev_family = nil;
2428 for (NSString *family in sorted_families) {
2429 if ([family compare:prev_family])
2430 [new_families addObject:family];
2433 families = new_families;
2435 # endif // USE_IPHONE
2437 long n = [families count];
2438 if (n <= 0) return 0;
2441 for (j = 0; j < n; j++) {
2442 int i = random() % n;
2443 NSString *family_name = [families objectAtIndex:i];
2445 NSFont *result = try_font (traits, mask, family_name, size, name_ret);
2447 [*family_ret release];
2448 *family_ret = family_name;
2449 [*family_ret retain];
2454 // None of the fonts support ASCII?
2459 // Fonts need this. XDisplayHeightMM and friends should probably be consistent
2460 // with this as well if they're ever implemented.
2461 static const unsigned dpi = 75;
2465 xlfd_field_end (const char *s)
2467 const char *s2 = strchr(s, '-');
2475 xlfd_next (const char **s, const char **s2)
2480 Assert (**s2 == '-', "xlfd parse error");
2482 *s2 = xlfd_field_end (*s);
2489 try_xlfd_font (const char *name, float scale,
2490 char **name_ret, float *size_ret, char **xa_font)
2493 NSString *family_name = nil;
2494 NSFontTraitMask require = 0, forbid = 0;
2495 GLboolean rand = GL_FALSE;
2499 const char *s = (name ? name : "");
2501 size_t L = strlen (s);
2502 # define CMP(STR) (L == strlen(STR) && !strncasecmp (s, (STR), L))
2503 # define UNSPEC (L == 0 || L == 1 && *s == '*')
2504 if (CMP ("6x10")) size = 8, require |= NSFixedPitchFontMask;
2505 else if (CMP ("6x10bold")) size = 8, require |= NSFixedPitchFontMask | NSBoldFontMask;
2506 else if (CMP ("fixed")) size = 12, require |= NSFixedPitchFontMask;
2507 else if (CMP ("9x15")) size = 12, require |= NSFixedPitchFontMask;
2508 else if (CMP ("9x15bold")) size = 12, require |= NSFixedPitchFontMask | NSBoldFontMask;
2509 else if (CMP ("vga")) size = 12, require |= NSFixedPitchFontMask;
2510 else if (CMP ("console")) size = 12, require |= NSFixedPitchFontMask;
2511 else if (CMP ("gallant")) size = 12, require |= NSFixedPitchFontMask;
2514 // Incorrect fields are ignored.
2518 const char *s2 = xlfd_field_end(s);
2522 L = xlfd_next (&s, &s2); // Family name
2523 // This used to substitute Georgia for Times. Now it doesn't.
2524 if (CMP ("random")) {
2526 } else if (CMP ("fixed")) {
2527 require |= NSFixedPitchFontMask;
2528 family_name = @"Courier";
2529 } else if (!UNSPEC) {
2530 family_name = [[[NSString alloc] initWithBytes:s
2532 encoding:NSUTF8StringEncoding]
2536 L = xlfd_next (&s, &s2); // Weight name
2537 if (CMP ("bold") || CMP ("demibold"))
2538 require |= NSBoldFontMask;
2539 else if (CMP ("medium") || CMP ("regular"))
2540 forbid |= NSBoldFontMask;
2542 L = xlfd_next (&s, &s2); // Slant
2543 if (CMP ("i") || CMP ("o"))
2544 require |= NSItalicFontMask;
2546 forbid |= NSItalicFontMask;
2548 xlfd_next (&s, &s2); // Set width name (ignore)
2549 xlfd_next (&s, &s2); // Add style name (ignore)
2551 xlfd_next (&s, &s2); // Pixel size (ignore)
2553 xlfd_next (&s, &s2); // Point size
2555 uintmax_t n = strtoumax(s, &s3, 10);
2559 xlfd_next (&s, &s2); // Resolution X (ignore)
2560 xlfd_next (&s, &s2); // Resolution Y (ignore)
2562 xlfd_next (&s, &s2); // Spacing
2564 forbid |= NSFixedPitchFontMask;
2565 else if (CMP ("m") || CMP ("c"))
2566 require |= NSFixedPitchFontMask;
2568 // Don't care about average_width or charset registry.
2573 if (!family_name && !rand)
2574 family_name = default_font_family (require);
2576 if (size < 6 || size > 1000)
2581 NSFontTraitMask mask = require | forbid;
2584 nsfont = random_font (require, mask, size, &family_name, &ps_name);
2585 [family_name autorelease];
2589 nsfont = try_font (require, mask, family_name, size, &ps_name);
2591 // if that didn't work, turn off attibutes until it does
2592 // (e.g., there is no "Monaco-Bold".)
2594 if (!nsfont && (mask & NSItalicFontMask)) {
2595 require &= ~NSItalicFontMask;
2596 mask &= ~NSItalicFontMask;
2597 nsfont = try_font (require, mask, family_name, size, &ps_name);
2599 if (!nsfont && (mask & NSBoldFontMask)) {
2600 require &= ~NSBoldFontMask;
2601 mask &= ~NSBoldFontMask;
2602 nsfont = try_font (require, mask, family_name, size, &ps_name);
2604 if (!nsfont && (mask & NSFixedPitchFontMask)) {
2605 require &= ~NSFixedPitchFontMask;
2606 mask &= ~NSFixedPitchFontMask;
2607 nsfont = try_font (require, mask, family_name, size, &ps_name);
2611 *name_ret = ps_name;
2613 float actual_size = size / scale;
2614 asprintf(xa_font, "-*-%s-%s-%c-*-*-%u-%u-%u-%u-%c-0-iso10646-1",
2615 family_name.UTF8String,
2616 (require & NSBoldFontMask) ? "bold" : "medium",
2617 (require & NSItalicFontMask) ? 'o' : 'r',
2618 (unsigned)(dpi * actual_size / 72.27 + 0.5),
2619 (unsigned)(actual_size * 10 + 0.5), dpi, dpi,
2620 (require & NSFixedPitchFontMask) ? 'm' : 'p');
2630 XLoadFont (Display *dpy, const char *name)
2632 Font fid = (Font) calloc (1, sizeof(*fid));
2636 // (TODO) float scale = 1;
2639 /* Since iOS screens are physically smaller than desktop screens, scale up
2640 the fonts to make them more readable.
2642 Note that X11 apps on iOS also have the backbuffer sized in points
2643 instead of pixels, resulting in an effective X11 screen size of 768x1024
2644 or so, even if the display has significantly higher resolution. That is
2645 unrelated to this hack, which is really about DPI.
2651 fid->native_font = jwxyz_load_native_font (dpy, name,
2652 &fid->ps_name, &fid->size,
2653 &fid->ascent, &fid->descent);
2654 if (!fid->native_font) {
2665 XLoadQueryFont (Display *dpy, const char *name)
2667 Font fid = XLoadFont (dpy, name);
2669 return XQueryFont (dpy, fid);
2673 XUnloadFont (Display *dpy, Font fid)
2675 if (--fid->refcount < 0) abort();
2676 if (fid->refcount > 0) return 0;
2678 if (fid->native_font)
2679 jwxyz_release_native_font (fid->dpy, fid->native_font);
2682 free (fid->ps_name);
2683 if (fid->metrics.per_char)
2684 free (fid->metrics.per_char);
2686 // #### DAMMIT! I can't tell what's going wrong here, but I keep getting
2687 // crashes in [NSFont ascender] <- query_font, and it seems to go away
2688 // if I never release the nsfont. So, fuck it, we'll just leak fonts.
2689 // They're probably not very big...
2691 // [fid->nsfont release];
2692 // CFRelease (fid->nsfont);
2699 XFreeFontInfo (char **names, XFontStruct *info, int n)
2703 for (i = 0; i < n; i++)
2704 if (names[i]) free (names[i]);
2708 for (i = 0; i < n; i++)
2709 if (info[i].per_char) {
2710 free (info[i].per_char);
2711 free (info[i].properties);
2719 XFreeFont (Display *dpy, XFontStruct *f)
2722 XFreeFontInfo (0, f, 1);
2723 XUnloadFont (dpy, fid);
2729 XSetFont (Display *dpy, GC gc, Font fid)
2731 Font font2 = copy_font (fid);
2733 XUnloadFont (dpy, gc->gcv.font);
2734 gc->gcv.font = font2;
2740 XCreateFontSet (Display *dpy, char *name,
2741 char ***missing_charset_list_return,
2742 int *missing_charset_count_return,
2743 char **def_string_return)
2745 char *name2 = strdup (name);
2746 char *s = strchr (name, ',');
2749 XFontStruct *f = XLoadQueryFont (dpy, name2);
2752 set = (XFontSet) calloc (1, sizeof(*set));
2756 if (missing_charset_list_return) *missing_charset_list_return = 0;
2757 if (missing_charset_count_return) *missing_charset_count_return = 0;
2758 if (def_string_return) *def_string_return = 0;
2764 XFreeFontSet (Display *dpy, XFontSet set)
2766 XFreeFont (dpy, set->font);
2772 jwxyz_nativeFontName (Font f, float *size)
2774 if (size) *size = f->size;
2780 XFreeStringList (char **list)
2784 for (i = 0; list[i]; i++)
2790 // Given a UTF8 string, return an NSString. Bogus UTF8 characters are ignored.
2791 // We have to do this because stringWithCString returns NULL if there are
2792 // any invalid characters at all.
2796 sanitize_utf8 (const char *in, int in_len, Bool *latin1_pP)
2798 int out_len = in_len * 4; // length of string might increase
2799 char *s2 = (char *) malloc (out_len);
2801 const char *in_end = in + in_len;
2802 const char *out_end = out + out_len;
2803 Bool latin1_p = True;
2808 long L1 = utf8_decode ((const unsigned char *) in, in_end - in, &uc);
2809 long L2 = utf8_encode (uc, out, out_end - out);
2812 if (uc > 255) latin1_p = False;
2816 [NSString stringWithCString:s2 encoding:NSUTF8StringEncoding];
2818 if (latin1_pP) *latin1_pP = latin1_p;
2819 return (nsstr ? nsstr : @"");
2824 XTextExtents (XFontStruct *f, const char *s, int length,
2825 int *dir_ret, int *ascent_ret, int *descent_ret,
2828 // Unfortunately, adding XCharStructs together to get the extents for a
2829 // string doesn't work: Cocoa uses non-integral character advancements, but
2830 // XCharStruct.width is an integer. Plus that doesn't take into account
2831 // kerning pairs, alternate glyphs, and fun stuff like the word "Zapfino" in
2835 Display *dpy = ff->dpy;
2836 jwxyz_render_text (dpy, ff->native_font, s, length, GL_FALSE, cs, 0);
2838 *ascent_ret = f->ascent;
2839 *descent_ret = f->descent;
2844 XTextWidth (XFontStruct *f, const char *s, int length)
2846 int ascent, descent, dir;
2848 XTextExtents (f, s, length, &dir, &ascent, &descent, &cs);
2854 XTextExtents16 (XFontStruct *f, const XChar2b *s, int length,
2855 int *dir_ret, int *ascent_ret, int *descent_ret,
2858 // Bool latin1_p = True;
2859 int i, utf8_len = 0;
2860 char *utf8 = XChar2b_to_utf8 (s, &utf8_len); // already sanitized
2862 for (i = 0; i < length; i++)
2863 if (s[i].byte1 > 0) {
2864 // latin1_p = False;
2870 Display *dpy = ff->dpy;
2871 jwxyz_render_text (dpy, ff->native_font, utf8, strlen(utf8),
2876 *ascent_ret = f->ascent;
2877 *descent_ret = f->descent;
2883 /* "Returns the distance in pixels in the primary draw direction from
2884 the drawing origin to the origin of the next character to be drawn."
2886 "overall_ink_return is set to the bbox of the string's character ink."
2888 "The overall_ink_return for a nondescending, horizontally drawn Latin
2889 character is conventionally entirely above the baseline; that is,
2890 overall_ink_return.height <= -overall_ink_return.y."
2892 [So this means that y is the top of the ink, and height grows down:
2893 For above-the-baseline characters, y is negative.]
2895 "The overall_ink_return for a nonkerned character is entirely at, and to
2896 the right of, the origin; that is, overall_ink_return.x >= 0."
2898 [So this means that x is the left of the ink, and width grows right.
2899 For left-of-the-origin characters, x is negative.]
2901 "A character consisting of a single pixel at the origin would set
2902 overall_ink_return fields y = 0, x = 0, width = 1, and height = 1."
2905 Xutf8TextExtents (XFontSet set, const char *str, int len,
2906 XRectangle *overall_ink_return,
2907 XRectangle *overall_logical_return)
2911 NSString *nsstr = sanitize_utf8 (str, len, &latin1_p);
2914 utf8_metrics (set->font->fid, nsstr, &cs);
2916 /* "The overall_logical_return is the bounding box that provides minimum
2917 spacing to other graphical features for the string. Other graphical
2918 features, for example, a border surrounding the text, should not
2919 intersect this rectangle."
2921 So I think that means they're the same? Or maybe "ink" is the bounding
2922 box, and "logical" is the advancement? But then why is the return value
2925 if (overall_ink_return)
2926 XCharStruct_to_XmbRectangle (cs, *overall_ink_return);
2927 if (overall_logical_return)
2928 XCharStruct_to_XmbRectangle (cs, *overall_logical_return);
2937 draw_string (Display *dpy, Drawable d, GC gc, int x, int y,
2938 const char *str, size_t len, GLboolean utf8)
2940 Font ff = gc->gcv.font;
2944 jwxyz_render_text (dpy, ff->native_font, str, len, utf8, &cs, &data);
2945 int w = cs.rbearing - cs.lbearing;
2946 int h = cs.ascent + cs.descent;
2948 if (w < 0 || h < 0) abort();
2949 if (w == 0 || h == 0) {
2950 if (data) free(data);
2954 XImage *img = XCreateImage (dpy, dpy->screen->visual, 32,
2955 ZPixmap, 0, data, w, h, 0, 0);
2957 /* The image of text is a 32-bit image, in white.
2958 Take the red channel for intensity and use that as alpha.
2959 replace RGB with the GC's foreground color.
2960 This expects that XPutImage respects alpha and only writes
2961 the bits that are not masked out.
2962 This also assumes that XPutImage expects ARGB.
2966 char *end = s + (w * h * 4);
2968 jwxyz_query_color (dpy, gc->gcv.foreground, rgba);
2979 XPutImage (dpy, d, gc, img, 0, 0,
2983 XDestroyImage (img);
2989 XDrawString (Display *dpy, Drawable d, GC gc, int x, int y,
2990 const char *str, int len)
2992 return draw_string (dpy, d, gc, x, y, str, len, GL_FALSE);
2997 XDrawString16 (Display *dpy, Drawable d, GC gc, int x, int y,
2998 const XChar2b *str, int len)
3000 XChar2b *b2 = malloc ((len + 1) * sizeof(*b2));
3003 memcpy (b2, str, len * sizeof(*b2));
3004 b2[len].byte1 = b2[len].byte2 = 0;
3005 s2 = XChar2b_to_utf8 (b2, 0);
3007 ret = draw_string (dpy, d, gc, x, y, s2, strlen(s2), GL_TRUE);
3014 Xutf8DrawString (Display *dpy, Drawable d, XFontSet set, GC gc,
3015 int x, int y, const char *str, int len)
3017 draw_string (dpy, d, gc, x, y, str, len, GL_TRUE);
3022 XDrawImageString (Display *dpy, Drawable d, GC gc, int x, int y,
3023 const char *str, int len)
3025 int ascent, descent, dir;
3027 XTextExtents (&gc->gcv.font->metrics, str, len,
3028 &dir, &ascent, &descent, &cs);
3029 jwxyz_fill_rect (dpy, d, gc,
3030 x + MIN (0, cs.lbearing),
3031 y - MAX (0, ascent),
3032 MAX (MAX (0, cs.rbearing) -
3033 MIN (0, cs.lbearing),
3035 MAX (0, ascent) + MAX (0, descent),
3036 gc->gcv.background);
3037 return XDrawString (dpy, d, gc, x, y, str, len);
3042 XSetClipMask (Display *dpy, GC gc, Pixmap m)
3048 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
3050 if (gc->gcv.clip_mask) {
3051 XFreePixmap (dpy, gc->gcv.clip_mask);
3052 CGImageRelease (gc->clip_mask);
3055 gc->gcv.clip_mask = copy_pixmap (dpy, m);
3056 if (gc->gcv.clip_mask)
3058 CGBitmapContextCreateImage (gc->gcv.clip_mask->cgc);
3067 XSetClipOrigin (Display *dpy, GC gc, int x, int y)
3069 gc->gcv.clip_x_origin = x;
3070 gc->gcv.clip_y_origin = y;
3074 #endif /* JWXYZ_GL -- entire file */