[Commit] cairo-demo/X11 Makefile, NONE, 1.1 cairo-demo.c, NONE, 1.1 cairo-freq.c, NONE, 1.1 cairo-knockout.c, NONE, 1.1 cairo-spline.c, NONE, 1.1

Carl Worth commit at keithp.com
Mon Aug 18 12:11:40 PDT 2003


Committed by: cworth

Update of /local/src/CVS/cairo-demo/X11
In directory home.keithp.com:/tmp/cvs-serv20528/X11

Added Files:
	Makefile cairo-demo.c cairo-freq.c cairo-knockout.c 
	cairo-spline.c 
Log Message:
Added demos from OLS paper.

--- NEW FILE: Makefile ---
CFLAGS=-g -Wall -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls `pkg-config --cflags cairo`
LDFLAGS=`pkg-config --libs cairo`

PROGS=cairo-demo cairo-spline cairo-knockout cairo-freq

all: $(PROGS)

clean:
	rm -f $(PROGS) *.o



--- NEW FILE: cairo-demo.c ---
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <cairo.h>

typedef struct win {
    Display *dpy;
    int scr;
    Window win;
    GC gc;
    int width, height;
    KeyCode quit_code;
} win_t;

static void triangle(cairo_t *ct);
static void square(cairo_t *ct);
static void bowtie(cairo_t *ct);
static void win_init(win_t *win, Display *dpy);
static void win_deinit(win_t *win);
static void win_draw(win_t *win);
static void win_select_events(win_t *win);
static void win_handle_events(win_t *win);

int
main(int argc, char *argv[])
{
    win_t win;

    Display *dpy = XOpenDisplay(0);

    XSynchronize (dpy, 1);

    if (dpy == NULL) {
	fprintf(stderr, "Failed to open display: %s\n", XDisplayName(0));
	return 1;
    }

    win_init(&win, dpy);

    win_draw(&win);

    win_handle_events(&win);

    win_deinit(&win);

    XCloseDisplay(dpy);

    return 0;
}

#define SIZE 40
static void
triangle(cairo_t *ct)
{
    cairo_move_to(ct, SIZE, 0);
    cairo_rel_line_to(ct, SIZE,  2*SIZE);
    cairo_rel_line_to(ct, -2*SIZE, 0);
    cairo_close_path(ct);
}

static void
square(cairo_t *ct)
{
    cairo_move_to(ct, 0, 0);
    cairo_rel_line_to(ct,  2*SIZE,   0);
    cairo_rel_line_to(ct,   0,  2*SIZE);
    cairo_rel_line_to(ct, -2*SIZE,   0); 
    cairo_close_path(ct);
}

static void
bowtie(cairo_t *ct)
{
    cairo_move_to(ct, 0, 0);
    cairo_rel_line_to(ct,  2*SIZE,  2*SIZE);
    cairo_rel_line_to(ct, -2*SIZE,   0); 
    cairo_rel_line_to(ct,  2*SIZE, -2*SIZE);
    cairo_close_path(ct);
}

static void
inf(cairo_t *ct)
{
    cairo_move_to(ct, 0, SIZE);
    cairo_rel_curve_to(ct,
		 0, SIZE,
		 SIZE, SIZE,
		 2*SIZE, 0);
    cairo_rel_curve_to(ct,
		 SIZE, -SIZE,
		 2*SIZE, -SIZE,
		 2*SIZE, 0);
    cairo_rel_curve_to(ct,
		 0, SIZE,
		 -SIZE, SIZE,
		 -2*SIZE, 0);
    cairo_rel_curve_to(ct,
		 -SIZE, -SIZE,
		 -2*SIZE, -SIZE,
		 -2*SIZE, 0);
    cairo_close_path(ct);
}

static void
draw_shapes(cairo_t *ct, int x, int y, int fill)
{
    cairo_save(ct);

    cairo_new_path(ct);
    cairo_translate(ct, x+SIZE, y+SIZE);
    bowtie(ct);
    if (fill)
	cairo_fill(ct);
    else
	cairo_stroke(ct);

    cairo_new_path(ct);
    cairo_translate(ct, 4*SIZE, 0);
    square(ct);
    if (fill)
	cairo_fill(ct);
    else
	cairo_stroke(ct);

    cairo_new_path(ct);
    cairo_translate(ct, 4*SIZE, 0);
    triangle(ct);
    if (fill)
	cairo_fill(ct);
    else
	cairo_stroke(ct);

    cairo_new_path(ct);
    cairo_translate(ct, 4*SIZE, 0);
    inf(ct);
    if (fill)
	cairo_fill(ct);
    else
	cairo_stroke(ct);

    cairo_restore(ct);
}

static void
fill_shapes(cairo_t *ct, int x, int y)
{
    draw_shapes(ct, x, y, 1);
}

static void
stroke_shapes(cairo_t *ct, int x, int y)
{
    draw_shapes(ct, x, y, 0);
}

/*
static void
draw_broken_shapes(cairo_t *ct)
{
    cairo_save(ct);

    cairo_set_line_width(ct, 1);
    cairo_set_line_join(ct, CAIRO_LINE_JOIN_BEVEL);
    cairo_set_rgb_color(ct, 1, 1, 1);

    cairo_move_to(ct, 19.153717041015625, 144.93951416015625);
    cairo_line_to(ct, 412.987396240234375, 99.407318115234375);
    cairo_line_to(ct, 412.99383544921875, 99.4071807861328125);
    cairo_line_to(ct, 413.15008544921875, 99.5634307861328125);
    cairo_line_to(ct, 413.082489013671875, 99.6920928955078125);
    cairo_line_to(ct, 413.000274658203125, 99.71954345703125);
    cairo_line_to(ct, 273.852630615234375, 138.1925201416015625);
    cairo_line_to(ct, 273.934844970703125, 138.165069580078125);
    cairo_line_to(ct, 16.463653564453125, 274.753662109375);
    cairo_line_to(ct, 16.286346435546875, 274.496337890625);
    cairo_line_to(ct, 273.757537841796875, 137.907745361328125);
    cairo_line_to(ct, 273.839752197265625, 137.8802947998046875);
    cairo_line_to(ct, 412.987396240234375, 99.407318115234375);
    cairo_line_to(ct, 412.99383544921875, 99.4071807861328125);
    cairo_line_to(ct, 413.15008544921875, 99.5634307861328125);
    cairo_line_to(ct, 413.082489013671875, 99.6920928955078125);
    cairo_line_to(ct, 413.000274658203125, 99.71954345703125);
    cairo_line_to(ct, 19.166595458984375, 145.251739501953125);

    cairo_fill(ct);

    cairo_restore(ct);
}
*/

static void
win_draw(win_t *win)
{
#define NUM_DASH 2
    static double dash[NUM_DASH] = {SIZE/4.0, SIZE/4.0};
    Display *dpy = win->dpy;
    cairo_t *ct;
    Drawable drawable = win->win;

    XClearWindow(dpy, win->win);

    ct = cairo_create();
    cairo_set_target_drawable (ct, dpy, drawable);
    cairo_set_rgb_color(ct, 1, 1, 1);

/*
    cairo_scale(ct, 5, 5);
    inf(ct);
    cairo_translate(ct, 0, 2 * SIZE);
    inf(ct);
    cairo_translate(ct, 0, - 2 * SIZE);
    cairo_clip(ct);
    cairo_scale(ct, 1/5.0, 1/5.0);
*/

    /* This is handy for examining problems more closely */
/*    cairo_scale(ct, 8, 8); */

#if XXX_JOINS_ARE_BROKEN_AFTER_SOME_TRANSFORMS
    cairo_scale(ct, 2, -2);
    cairo_translate(ct, 0, -200);
#endif
    cairo_set_line_width(ct, SIZE / 4);

    cairo_set_tolerance(ct, .1);

    cairo_set_line_join(ct, CAIRO_LINE_JOIN_ROUND);
    cairo_set_dash(ct, dash, NUM_DASH, 0);
    stroke_shapes(ct, 0, 0);

    cairo_set_dash(ct, NULL, 0, 0);
    stroke_shapes(ct, 0, 4*SIZE);

    cairo_set_line_join(ct, CAIRO_LINE_JOIN_BEVEL);
    stroke_shapes(ct, 0, 8*SIZE);

    cairo_set_line_join(ct, CAIRO_LINE_JOIN_MITER);
    stroke_shapes(ct, 0, 12*SIZE);

    fill_shapes(ct, 0, 16*SIZE);

    cairo_set_line_join(ct, CAIRO_LINE_JOIN_BEVEL);
    fill_shapes(ct, 0, 20*SIZE);
    cairo_set_rgb_color(ct, 1, 0, 0);
    stroke_shapes(ct, 0, 20*SIZE);
/*
    draw_broken_shapes(ct);
*/

    cairo_destroy(ct);
}

static void
win_init(win_t *win, Display *dpy)
{
    Window root;

    win->dpy = dpy;
    win->width = 400;
    win->height = 400;

    root = DefaultRootWindow(dpy);
    win->scr = DefaultScreen(dpy);

    win->win = XCreateSimpleWindow(dpy, root, 0, 0,
				   win->width, win->height, 0,
				   BlackPixel(dpy, win->scr), BlackPixel(dpy, win->scr));

    win->quit_code = XKeysymToKeycode(dpy, XStringToKeysym("Q"));

    win_select_events(win);

    XMapWindow(dpy, win->win);
}

static void
win_deinit(win_t *win)
{
    XDestroyWindow(win->dpy, win->win);
}

static void
win_select_events(win_t *win)
{
    XSelectInput(win->dpy, win->win,
		 KeyPressMask
		 |StructureNotifyMask
		 |ExposureMask);
}

static void
win_handle_events(win_t *win)
{
    XEvent xev;

    while (1) {
	XNextEvent(win->dpy, &xev);

	switch(xev.type) {
	case KeyPress:
	{
	    XKeyEvent *kev = &xev.xkey;
	    
	    if (kev->keycode == win->quit_code) {
		return;
	    }
	}
	break;
	case ConfigureNotify:
	{
	    XConfigureEvent *cev = &xev.xconfigure;

	    win->width = cev->width;
	    win->height = cev->height;
	}
	break;
	case Expose:
	{
	    XExposeEvent *eev = &xev.xexpose;

	    if (eev->count == 0)
		win_draw(win);
	}
	break;
	}
    }
}

--- NEW FILE: cairo-freq.c ---
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>

#include <cairo.h>

#define EPSILON (1.0 / (2<<16))
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))

typedef struct win {
    Display *dpy;
    int scr;
    Window win;
    GC gc;
    Pixmap pix;
    int width, height;
    long event_mask;

    int needs_refresh;

    double tolerance;
    double zoom;
} win_t;

typedef struct callback_doc {
    void *callback;
    char *doc;
} callback_doc_t;

typedef int (*key_callback_t)(win_t *win);

typedef struct key_binding
{
    char *key;
    int is_alias;
    KeyCode keycode;
    key_callback_t callback;
} key_binding_t;

static void win_init(win_t *win, Display *dpy);
static void win_deinit(win_t *win);
static void win_refresh(win_t *win);
static void win_select_events(win_t *win);
static void win_handle_events(win_t *win);
static void win_print_help(win_t *win);

static int quit_cb(win_t *win);
static int flatten_cb(win_t *win);
static int smooth_cb(win_t *win);

static const double DEFAULT_TOLERANCE = .1;

static const callback_doc_t callback_doc[] = {
    { quit_cb,		"Exit the program" },
    { flatten_cb,	"Decrease rendering accuracy, (tolerance *= 10)" },
    { smooth_cb,	"Increase rendering accuracy, (tolerance /= 10)" },
};

static key_binding_t key_binding[] = {
    /* Keysym, Alias, Keycode, callback */
    { "Q",	0, 0, quit_cb },
    { "greater",0, 0, smooth_cb },
    { "period",	1, 0, smooth_cb },
    { "less",	0, 0, flatten_cb },
    { "comma",	1, 0, flatten_cb },
};

int
main(int argc, char *argv[])
{
    win_t win;

    Display *dpy = XOpenDisplay(0);

    if (dpy == NULL) {
	fprintf(stderr, "Failed to open display: %s\n", XDisplayName(0));
	return 1;
    }

    win_init(&win, dpy);

    win_print_help(&win);

    win_handle_events(&win);

    win_deinit(&win);

    XCloseDisplay(dpy);

    return 0;
}

static void
win_refresh(win_t *win)
{
    Display *dpy = win->dpy;
    double cx, cy;
    double radius, theta, theta_inc;

    cairo_t *ct;
    Drawable drawable = win->pix;

    XFillRectangle(dpy, win->pix, win->gc, 0, 0, win->width, win->height);

    ct = cairo_create();

    cairo_set_target_drawable(ct, dpy, drawable);
    cairo_set_rgb_color(ct, 1, 1, 1);

    cx = win->width / 2.0;
    cy = win->height / 2.0;
    radius = sqrt(cx*cx+cy*cy);

#define FINS 128.0
#define FIN_WIDTH (.25 * (2 * M_PI / FINS))

    theta_inc = 2 * M_PI / FINS;
    for (theta=0; theta < 2 * M_PI; theta += theta_inc) {
	cairo_move_to(ct, cx, cy);
	cairo_line_to(ct, cx + radius * cos(theta), cy + radius * sin(theta));
	cairo_line_to(ct, cx + radius * cos(theta + FIN_WIDTH), cy + radius * sin(theta + FIN_WIDTH));
	cairo_close_path(ct);
	cairo_fill(ct);
    }

    cairo_destroy(ct);

    XCopyArea(win->dpy, win->pix, win->win, win->gc,
	      0, 0, win->width, win->height,
	      0, 0);
}

static void
win_init(win_t *win, Display *dpy)
{
    int i;
    Window root;
    XGCValues gcv;

    win->dpy = dpy;
    win->width = 400;
    win->height = 400;

    root = DefaultRootWindow(dpy);
    win->scr = DefaultScreen(dpy);

    win->win = XCreateSimpleWindow(dpy, root, 0, 0,
				   win->width, win->height, 0,
				   BlackPixel(dpy, win->scr), BlackPixel(dpy, win->scr));

    win->pix = XCreatePixmap(dpy, win->win, win->width, win->height, DefaultDepth(dpy, win->scr));
    gcv.foreground = BlackPixel(dpy, win->scr);
    win->gc = XCreateGC(dpy, win->pix, GCForeground, &gcv);
    XFillRectangle(dpy, win->pix, win->gc, 0, 0, win->width, win->height);

    for (i=0; i < ARRAY_SIZE(key_binding); i++) {
	KeySym keysym;
	keysym = XStringToKeysym(key_binding[i].key);
	if (keysym == NoSymbol)
	    fprintf(stderr, "ERROR: No keysym for \"%s\"\n", key_binding[i].key);
	else
	    key_binding[i].keycode = XKeysymToKeycode(dpy, keysym);
    }

    win->tolerance = DEFAULT_TOLERANCE;

    win_refresh(win);
    win->needs_refresh = 0;

    win_select_events(win);

    XMapWindow(dpy, win->win);
}

static void
win_deinit(win_t *win)
{
    XFreeGC(win->dpy, win->gc);
    XFreePixmap(win->dpy, win->pix);
    XDestroyWindow(win->dpy, win->win);
}

static void
win_select_events(win_t *win)
{
    win->event_mask = 
	KeyPressMask
	| StructureNotifyMask
	| ExposureMask;
    XSelectInput(win->dpy, win->win, win->event_mask);

}

static char *
get_callback_doc(void *callback)
{
    int i;

    for (i=0; i < ARRAY_SIZE(callback_doc); i++)
	if (callback_doc[i].callback == callback)
	    return callback_doc[i].doc;

    return "<undocumented function>";
}

static void
win_print_help(win_t *win)
{
    int i;

    printf("Xr spline demonstration\n");

    for (i=0; i < ARRAY_SIZE(key_binding); i++)
	if (! key_binding[i].is_alias)
	    printf("%s:\t%s\n",
		   key_binding[i].key,
		   get_callback_doc(key_binding[i].callback));
}

static int
win_handle_key_press(win_t *win, XKeyEvent *kev)
{
    int i;

    for (i=0; i < ARRAY_SIZE(key_binding); i++)
	if (key_binding[i].keycode == kev->keycode)
	    return (key_binding[i].callback)(win);
	
    return 0;
}

static void
win_grow_pixmap(win_t *win)
{
    Pixmap new;

    new = XCreatePixmap(win->dpy, win->win, win->width, win->height,
			DefaultDepth(win->dpy, win->scr));
    XFillRectangle(win->dpy, new, win->gc, 0, 0, win->width, win->height);
    XCopyArea(win->dpy, win->pix, new, win->gc, 0, 0, win->width, win->height, 0, 0);
    XFreePixmap(win->dpy, win->pix);
    win->pix = new;
    win_refresh(win);
}

static void
win_handle_configure(win_t *win, XConfigureEvent *cev)
{
    int has_grown = 0;

    if (cev->width > win->width || cev->height > win->height) {
	has_grown = 1;
    }

    win->width = cev->width;
    win->height = cev->height;

    if (has_grown) {
	win_grow_pixmap(win);
    } else {
	win_refresh(win);
    }
}

static void
win_handle_expose(win_t *win, XExposeEvent *eev)
{
    XCopyArea(win->dpy, win->pix, win->win, win->gc,
	      eev->x, eev->y, eev->width, eev->height,
	      eev->x, eev->y);
}

static void
win_handle_events(win_t *win)
{
    int done;
    XEvent xev;

    while (1) {

	if (!XPending(win->dpy) && win->needs_refresh) {
	    win_refresh(win);
	    win->needs_refresh = 0;
	}

	XNextEvent(win->dpy, &xev);

	switch(xev.type) {
	case KeyPress:
	    done = win_handle_key_press(win, &xev.xkey);
	    if (done)
		return;
	    break;
	case ConfigureNotify:
	    win_handle_configure(win, &xev.xconfigure);
	    break;
	case Expose:
	    win_handle_expose(win, &xev.xexpose);
	    break;
	}
    }
}


/* Callbacks */

static int
quit_cb(win_t *win)
{
    return 1;
}

static int
flatten_cb(win_t *win)
{
    win->tolerance *= 10;

    win->needs_refresh = 1;

    return 0;
}

static int
smooth_cb(win_t *win)
{
    win->tolerance /= 10;

    win->needs_refresh = 1;

    return 0;
}

--- NEW FILE: cairo-knockout.c ---
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/* Small example demonstrating emulating knockout-groups as in PDF-1.4
 * using cairo_set_operator().
 *
 * Owen Taylor,

 * v0.1  30 November 2002
 * v0.2   1 December 2002 - typo fixes from Keith Packard
 * v0.3  17 April    2003 - Tracking changes in Xr, (Removal of Xr{Push,Pop}Group)
 */
#include <X11/Xlib.h>
#include <cairo.h>
#include <math.h>
#include <stdio.h>

/* Create a rectangular path
 */
static void
rect_path (cairo_t *r,
           double x,     double y,
           double width, double height)
{
    cairo_new_path (r);
    cairo_move_to (r, x, y);
    
    cairo_rel_line_to (r, 0, height);
    cairo_rel_line_to (r, width, 0);
    cairo_rel_line_to (r, 0, -height);
    cairo_rel_line_to (r, -width, 0);
    
    cairo_close_path (r);
}

/* Create a path that is roughly a circular oval with
 * radii xr, yr at xc, yc. We only use 4 bezier's
 * but the deviation is already pretty darn small.
 * (max of about 0.02%)
 */
static void
oval_path (cairo_t *r,
           double xc, double yc,
           double xr, double yr)
{
    int i;
    
    cairo_new_path (r);
    cairo_move_to (r, xc + xr, yc);

#define TANGENT_MULT (1.65591 / 3.)
    
    for (i = 0; i < 4; i++)
        {
            double angle1 = ((i + 0) / 2.) * M_PI;
            double angle2 = ((i + 1) / 2.) * M_PI;

            double x0 = xc + xr * cos (angle1);
            double y0 = yc - yr * sin (angle1);

            double x1 = x0 - xr * sin (angle1) * TANGENT_MULT;
            double y1 = y0 - yr * cos (angle1) * TANGENT_MULT;
            
            double x3 = xc + xr * cos (angle2);
            double y3 = yc - yr * sin (angle2);
            
            double x2 = x3 + xr * sin (angle2) * TANGENT_MULT;
            double y2 = y3 + yr * cos (angle2) * TANGENT_MULT;
            
            cairo_curve_to (r, x1, y1, x2, y2, x3, y3);
        }
    
    cairo_close_path (r);
}

/* Fill the given area with checks in the standard style
 * for showing compositing effects.
 */
static void
fill_checks (cairo_t *r,
             int x,     int y,
             int width, int height)
{
    cairo_surface_t *check;
    
    cairo_save (r);

#define CHECK_SIZE 32

    check = cairo_surface_create_similar (cairo_get_target_surface (r),
                                   CAIRO_FORMAT_RGB24,
                                   2 * CHECK_SIZE, 2 * CHECK_SIZE);
    cairo_surface_set_repeat (check, 1);

    /* Draw the check */
    {
        cairo_save (r);

        cairo_set_target_surface (r, check);

        cairo_set_operator (r, CAIRO_OPERATOR_SRC);

        cairo_set_rgb_color (r, 0.4, 0.4, 0.4);

        rect_path (r, 0, 0, 2 * CHECK_SIZE, 2 * CHECK_SIZE);
        cairo_fill (r);

        cairo_set_rgb_color (r, 0.7, 0.7, 0.7);

        rect_path (r, x, y, CHECK_SIZE, CHECK_SIZE);
        cairo_fill (r);
        rect_path (r, x + CHECK_SIZE, y + CHECK_SIZE, CHECK_SIZE, CHECK_SIZE);
        cairo_fill (r);

        cairo_restore (r);
    }

    /* Fill the whole surface with the check */

    cairo_set_pattern (r, check);
    rect_path (r, 0, 0, width, height);
    cairo_fill (r);

    cairo_surface_destroy (check);

    cairo_restore (r);
}

/* Draw a red, green, and blue circle equally spaced inside
 * the larger circle of radius r at (xc, yc)
 */
static void
draw_3circles (cairo_t *r,
               double xc, double yc,
               double radius)
{
    double subradius = radius * (2 / 3. - 0.1);
    
    cairo_set_rgb_color (r, 1., 0., 0.);
    oval_path (r,
               xc + radius / 3. * cos (M_PI * (0.5)),
               yc - radius / 3. * sin (M_PI * (0.5)),
               subradius, subradius);
    cairo_fill (r);
    
    cairo_set_rgb_color (r, 0., 1., 0.);
    oval_path (r,
               xc + radius / 3. * cos (M_PI * (0.5 + 2/.3)),
               yc - radius / 3. * sin (M_PI * (0.5 + 2/.3)),
               subradius, subradius);
    cairo_fill (r);
    
    cairo_set_rgb_color (r, 0., 0., 1.);
    oval_path (r,
               xc + radius / 3. * cos (M_PI * (0.5 + 4/.3)),
               yc - radius / 3. * sin (M_PI * (0.5 + 4/.3)),
               subradius, subradius);
    cairo_fill (r);
}

static void
draw (cairo_t *r,
      int      width,
      int      height)
{
    cairo_surface_t *overlay, *punch, *circles;

    /* Fill the background */
    double radius = 0.5 * (width < height ? width : height) - 10;
    double xc = width / 2.;
    double yc = height / 2.;

    overlay = cairo_surface_create_similar (cairo_get_target_surface (r),
                                     CAIRO_FORMAT_ARGB32,
                                     width, height);
    if (overlay == NULL)
        return;

    punch = cairo_surface_create_similar (cairo_get_target_surface (r),
                                   CAIRO_FORMAT_A8,
                                   width, height);
    if (punch == NULL)
        return;

    circles = cairo_surface_create_similar (cairo_get_target_surface (r),
                                     CAIRO_FORMAT_ARGB32,
                                     width, height);
    if (circles == NULL)
        return;
    
    fill_checks (r, 0, 0, width, height);

    cairo_save (r);
    cairo_set_target_surface (r, overlay);

    /* Draw a black circle on the overlay
     */
    cairo_set_rgb_color (r, 0., 0., 0.);
    oval_path (r, xc, yc, radius, radius);
    cairo_fill (r);

    cairo_save (r);
    cairo_set_target_surface (r, punch);

    /* Draw 3 circles to the punch surface, then cut
     * that out of the main circle in the overlay
     */
    draw_3circles (r, xc, yc, radius);

    cairo_restore (r);

    cairo_set_operator (r, CAIRO_OPERATOR_OUT_REVERSE);
    cairo_show_surface (r, punch, width, height);

    /* Now draw the 3 circles in a subgroup again
     * at half intensity, and use OperatorAdd to join up
     * without seams.
     */
    cairo_save (r);
    cairo_set_target_surface (r, circles);

    cairo_set_alpha (r, 0.5);
    cairo_set_operator (r, CAIRO_OPERATOR_OVER);
    draw_3circles (r, xc, yc, radius);

    cairo_restore (r);

    cairo_set_operator (r, CAIRO_OPERATOR_ADD);
    cairo_show_surface (r, circles, width, height);

    cairo_restore (r);

    cairo_show_surface (r, overlay, width, height);

    cairo_surface_destroy (overlay);
    cairo_surface_destroy (punch);
    cairo_surface_destroy (circles);

}

static void
handle_expose (Display *dpy,
               int      screen,
               Window   win,
               int      width,
               int      height,
               Region   region)
{
    Pixmap p;
    cairo_t *r;
    XRectangle clip;
    GC gc;

    /* Create an offscreen pixmap of the size of the
     * area we need to repaint, and a cairo_t object
     * directed to that pixmap.
     */
    XClipBox (region, &clip);
    p = XCreatePixmap (dpy, win, clip.width, clip.height, DefaultDepth (dpy, screen));

    r = cairo_create ();

    cairo_set_target_drawable (r, dpy, p);

    /* By adding a translation, we hide the partial
     * pixmap from our drawing routine
     */
    cairo_translate (r, -clip.x, -clip.y);

    /* It would be nice to be able to set 'region' as
     * clip for our drawing, then only copy that portion
     * of our drawing, but Xr doesn't expose the clipping
     * features of Xrender. So, we redraw the entire
     * bounding rectangle, even if the area is, e.g., L shaped,
     * as frequently happens for exposes.x
     */

    /* Draw the contents
     */
    draw (r, width, height);

    /* Now copy from our offscreen pixmap to the window
     */
    gc = XCreateGC (dpy, p, 0, NULL);
    XCopyArea (dpy, p, win, gc, 0, 0, clip.width, clip.height, clip.x, clip.y);
    XFreeGC (dpy, gc);
    
    XFreePixmap (dpy, p);
    cairo_destroy (r);
}

int
main (int argc, char **argv)
{
  Display *dpy;
  int screen;
  Window w;
  char *title = "Knockout Groups";
  XTextProperty title_prop;
  unsigned int quit_keycode;
  
  int width = 400;
  int height = 400;
  Region update_region = XCreateRegion ();
  
  dpy = XOpenDisplay (NULL);
  screen = DefaultScreen (dpy);

  w = XCreateSimpleWindow (dpy, RootWindow (dpy, screen),
			   0, 0, width, height, 0,
			   BlackPixel (dpy, screen), WhitePixel (dpy, screen));

  if (XStringListToTextProperty (&title, 1, &title_prop)) {
      XSetWMName (dpy, w, &title_prop);
      XFree (title_prop.value);
  }

  quit_keycode = XKeysymToKeycode(dpy, XStringToKeysym("Q"));

  XSelectInput (dpy, w, ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask);
  XMapWindow (dpy, w);
  
  while (1) {
      XEvent xev;

      /* We accumulate the area to repaint until the event
       * queue, then repaint. This avoids us getting behind
       * on our repaint or painting the same area over and
       * over needlessly.
       */
      if (!XPending (dpy) && !XEmptyRegion (update_region)) {
          handle_expose (dpy, screen, w,
                         width, height,
                         update_region);
          XDestroyRegion (update_region);
          update_region = XCreateRegion ();
      }
      
      XNextEvent (dpy, &xev);

      switch (xev.xany.type) {
      case ButtonPress:
          /* A click on the canvas ends the program */
          goto DONE;
      case KeyPress:
          if (xev.xkey.keycode == quit_keycode)
              goto DONE;
      case ConfigureNotify:
          /* Note new size */
	  width = xev.xconfigure.width;
	  height = xev.xconfigure.height;
	  break;
      case Expose:
          /* Accumulate area that needs redraw */
          {
              XRectangle r;
              
              r.x = xev.xexpose.x;
              r.y = xev.xexpose.y;
              r.width = xev.xexpose.width;
              r.height = xev.xexpose.height;
              
              XUnionRectWithRegion (&r, update_region, update_region);
          }
	  break;
      }
  }
  DONE:

  XDestroyRegion (update_region);
  XCloseDisplay (dpy);

  return 0;
}

--- NEW FILE: cairo-spline.c ---
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>

#include <cairo.h>

#define EPSILON (1.0 / (2<<16))
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))

typedef struct color {
    double red;
    double green;
    double blue;
} color_t;

typedef struct pt {
    double x;
    double y;
} pt_t;

typedef struct spline {
    pt_t pt[4];
} spline_t;

typedef struct quadr {
    pt_t pt[4];
} quadr_t;

typedef struct win {
    Display *dpy;
    int scr;
    Window win;
    GC gc;
    Pixmap pix;
    int width, height;
    long event_mask;

    int needs_refresh;

    double tolerance;
    double line_width;
    cairo_line_cap_t line_cap;
    spline_t spline;
    double zoom;
    double xtrans;
    double ytrans;
    int show_path;

    int drag;
    pt_t drag_pt;
    pt_t *active_pt;
    int active;
} win_t;

typedef struct callback_doc {
    void *callback;
    char *doc;
} callback_doc_t;

typedef int (*key_callback_t)(win_t *win);

typedef struct key_binding
{
    char *key;
    int is_alias;
    KeyCode keycode;
    key_callback_t callback;
} key_binding_t;

typedef int (*button_callback_t)(win_t *win, int x, int y);

typedef struct button_binding
{
    int button;
    button_callback_t callback;
} button_binding_t;

static void win_init(win_t *win, Display *dpy);
static void win_deinit(win_t *win);
static void win_refresh(win_t *win);
static void win_select_events(win_t *win);
static void win_handle_events(win_t *win);
static void win_print_help(win_t *win);

static int select_point_cb(win_t *win, int x, int y);
static int snap_point_cb(win_t *win, int x, int y);

static int quit_cb(win_t *win);
static int select_next_cb(win_t *win);
static int left_epsilon_cb(win_t *win);
static int right_epsilon_cb(win_t *win);
static int up_epsilon_cb(win_t *win);
static int down_epsilon_cb(win_t *win);
static int print_spline_cb(win_t *win);
static int zoom_in_cb(win_t *win);
static int zoom_out_cb(win_t *win);
static int trans_left_cb(win_t *win);
static int trans_right_cb(win_t *win);
static int trans_up_cb(win_t *win);
static int trans_down_cb(win_t *win);
static int flatten_cb(win_t *win);
static int smooth_cb(win_t *win);
static int toggle_path(win_t *win);
static int widen_line(win_t *win);
static int narrow_line(win_t *win);

static const double DEFAULT_XTRANS = 0.0;
static const double DEFAULT_YTRANS = 0.0;
static const double DEFAULT_TOLERANCE = .1;
static const double DEFAULT_LINE_WIDTH = 10;
static const cairo_line_cap_t DEFAULT_LINE_CAP = CAIRO_LINE_CAP_ROUND;

/* This was breaking the polygon tessellation code. All fixed now. */
static const spline_t polygon_killer = {
    { { 613.125, 4643.06 }, { 21957.1, 3763.06 }, { 12906, 256 }, { 524, 8788 } }
};

static const spline_t intersection_killer = {
    { { 244.56999999999999318, 268.56999999999993634 }, { 364.56000000000000227, 747.55999999999994543 }, { 34.380000000000002558, 412.56000000000000227 }, { 576.37000000000000455, 203.56999999999999318 } }
};

/*
#define DEFAULT_SPLINE intersection_killer
#define DEFAULT_ZOOM 1.0
*/

/* Showing off the problems with wide butt-capped splines that turn
   sharply at the end. */
static const spline_t funky_fangs = {
    { { 69.25, 48.185 }, { 40.225, 43.06 }, { 59.5, 34.5 }, { 59.4998, 35.2514 } }
};

/* Adjust any point by an epsilon to see the fangs appear.
   (The fact that they are missing is a bug)
   -- Actually, it looks like this one is working now.
*/
static const spline_t touchy_fangs = {
    { { 18.25, 21.875 }, { 18.25, 23.875 }, { 30, 30.375 }, { 30, 27 } }
};

/* Here's another one that starts off buggy, but small adjustments
   change it, (here you need to change a control point by a pixel or
   two in order to have an effect) */
static const spline_t touchy_shell = {
    { { 8.9375, 6.32812 }, { 8.23434, 10.3594 }, { 8.5625, 4.25 }, { 11.9531, 7.6875 } }
};

/* A simple looping spline. No known problems. */
static const spline_t ribbon = {
    { {110, 20}, {310, 300}, {10, 310}, {210, 20} }
};

static const spline_t simple = {
    { {10, 10}, {70, 10}, {100, 40}, {100, 100} }
};

/* This one's fixed with some proper sorting/duplicate elimination on the pen */
static const spline_t non_uniform_width = {
    { { 18, 12 }, { 18.0001373291015625, 30.26085662841796875 }, { 99.9996185302734375, 48.72466278076171875 }, { 100, 100 } }
};

/* This one shows why my incremental trapexoid generation scheme was flawed */
static const spline_t warts = {
    { { 14.75, 25.25 }, { 30.5001373291015625, 57.76085662841796875 }, { 127.4996185302734375, -17.77533721923828125 }, { 61.75, 33.75 } }
};

/* This one is designed to torture ghostscript */
static const spline_t gstorture = {
    {{51.57, 51.57}, {412.56, 412.56}, {34.38, 412.56}, {395.37, 51.57}}
};

/*
#define DEFAULT_SPLINE gstorture
#define DEFAULT_ZOOM 1.0
*/

/*
#define DEFAULT_SPLINE simple
#define DEFAULT_ZOOM 1.0
*/

/* This was causing an infitie loop at one point.
   The bug is now fixed, (the while loop no longer exists) */
static const spline_t infinite_loop = {
    { {32 *  4008192 / 65536.0, 32 * 10819706 / 65536.0},
      {32 * 44968140 / 65536.0, 32 *  7706746 / 65536.0},
      {32 * 26431488 / 65536.0, 32 *   524288 / 65536.0},
      {32 *  1073152 / 65536.0, 32 * 17997824 / 65536.0} }
};

/* This one shows a tiny sub-pixel trapezoid that was being rasterized
   with a fully-lit pixel. The fix for this has now been applied to
   the trap code in the server. */
static const spline_t wart = {
    { { 179, 410 }, { 448, 475 }, { 33, 514 }, { 313, 399 } }
};

/* A new wart, (right on the tip of the spike). I haven't chased this
   one down yet. */
static const spline_t another_wart = {
    { { 37.5, 22.125 }, { 40.875, 23.625 }, { 13.375, 57 }, { 34.25, 35.375 } }
};

/* What happens when the spline folds over on itself at the end? */
static const spline_t overlap = {
    { { 3.03125, 5.1875 }, { 12.25, 11.1875 }, { 15.1875, 7.375 }, { 14.25, 7.45312 } }
};

/*
#define DEFAULT_SPLINE funky_fangs
#define DEFAULT_ZOOM   8.0
*/

/*
#define DEFAULT_SPLINE touchy_fangs
#define DEFAULT_ZOOM   8.0
*/

/*
#define DEFAULT_SPLINE touchy_shell
#define DEFAULT_ZOOM   16.0
*/

/*
#define DEFAULT_SPLINE polygon_killer
#define DEFAULT_ZOOM   (1 / 32.0)
*/

/*
#define DEFAULT_SPLINE infinite_loop
#define DEFAULT_ZOOM   1 / 32.0
*/

#define DEFAULT_SPLINE ribbon
#define DEFAULT_ZOOM   1.0

/*
#define DEFAULT_SPLINE wart
#define DEFAULT_ZOOM   1.0
*/

/*
#define DEFAULT_SPLINE another_wart
#define DEFAULT_ZOOM   8.0
#define DEFAULT_WIDTH  256
#define DEFAULT_TOLERANCE .001
*/

/*
#define DEFAULT_SPLINE overlap
#define DEFAULT_ZOOM 64
*/

static const callback_doc_t callback_doc[] = {
    { quit_cb,		"Exit the program" },
    { select_point_cb,	"Activate closest control point" },
    { select_next_cb,	"Activate next control point" },
    { snap_point_cb,	"Snap active point to closest control point" },
    { left_epsilon_cb,	"Move active point left by an epsilon" },
    { right_epsilon_cb,	"Move active point right by an epsilon" },
    { up_epsilon_cb,	"Move active point up by an epsilon" },
    { down_epsilon_cb,	"Move active point down by an epsilon" },
    { print_spline_cb,	"Print current spline coordinates on stdout" },
    { zoom_in_cb,	"Zoom in (2X)" },
    { zoom_out_cb,	"Zoom out (2X)" },
    { trans_left_cb,	"Translate left (25%)" },
    { trans_right_cb,	"Translate right (25%)" },
    { trans_up_cb,	"Translate up (25%)" },
    { trans_down_cb,	"Translate down (25%)" },
    { flatten_cb,	"Decrease rendering accuracy, (tolerance *= 10)" },
    { smooth_cb,	"Increase rendering accuracy, (tolerance /= 10)" },
    { toggle_path,	"Toggle thin display of spline path" },
    { widen_line,	"Widen line width" },
    { narrow_line,	"Narrow line width" },
};

static key_binding_t key_binding[] = {
    /* Keysym, Alias, Keycode, callback */
    { "Q",	0, 0, quit_cb },
    { "Left",	0, 0, left_epsilon_cb },
    { "Right",	0, 0, right_epsilon_cb },
    { "Up",	0, 0, up_epsilon_cb },
    { "Down",	0, 0, down_epsilon_cb },
    { "Return",	0, 0, print_spline_cb },
    { "space",	0, 0, select_next_cb },
    { "plus",	0, 0, zoom_in_cb },
    { "equal",	1, 0, zoom_in_cb },
    { "minus",	0, 0, zoom_out_cb },
    { "greater",0, 0, smooth_cb },
    { "period",	1, 0, smooth_cb },
    { "less",	0, 0, flatten_cb },
    { "comma",	1, 0, flatten_cb },
    { "P",	0, 0, toggle_path },
    { "W",	0, 0, widen_line },
    { "N",	0, 0, narrow_line },
};

static const button_binding_t button_binding[] = {
    { 1,	select_point_cb },
    { 3,	snap_point_cb }
};

int
main(int argc, char *argv[])
{
    win_t win;

    Display *dpy = XOpenDisplay(0);

    if (dpy == NULL) {
	fprintf(stderr, "Failed to open display: %s\n", XDisplayName(0));
	return 1;
    }

    win_init(&win, dpy);

    win_print_help(&win);

    win_handle_events(&win);

    win_deinit(&win);

    XCloseDisplay(dpy);

    return 0;
}

static void
draw_control_line(cairo_t *ct, pt_t *a, pt_t *b, double width)
{
    cairo_save(ct);

    cairo_set_rgb_color(ct, 0, 0, 1);
    cairo_set_line_width(ct, width);

    cairo_move_to(ct, a->x, a->y);
    cairo_line_to(ct, b->x, b->y);
    cairo_stroke(ct);

    cairo_restore(ct);
}

static void
draw_handle(cairo_t *ct, pt_t *p, int is_active, double size, double width)
{
    cairo_save(ct);

    if (is_active)
	cairo_set_rgb_color(ct, 0, 1, 0);
    else
	cairo_set_rgb_color(ct, 1, 0, 0);

    cairo_set_line_width(ct, width);

    cairo_move_to(ct, p->x - size / 2.0, p->y - size / 2.0);
    cairo_rel_line_to(ct, size, 0);
    cairo_rel_line_to(ct, 0, size);
    cairo_rel_line_to(ct, -size, 0);
    cairo_rel_line_to(ct, 0, -size);
    cairo_stroke(ct);

    cairo_restore(ct);
}

static void
draw_spline(cairo_t *ct, win_t *win)
{
    spline_t *spline = &win->spline;
    double zoom = win->zoom;

    int i;

    cairo_save(ct);

    cairo_move_to(ct, spline->pt[0].x, spline->pt[0].y);
    cairo_curve_to(ct,
	      spline->pt[1].x, spline->pt[1].y,
	      spline->pt[2].x, spline->pt[2].y,
	      spline->pt[3].x, spline->pt[3].y);
    cairo_stroke(ct);

    if (win->show_path) {
	cairo_set_line_width(ct, 1 / zoom);
	cairo_set_rgb_color(ct, 1, 1, 1);
	cairo_move_to(ct, spline->pt[0].x, spline->pt[0].y);
	cairo_curve_to(ct,
		  spline->pt[1].x, spline->pt[1].y,
		  spline->pt[2].x, spline->pt[2].y,
		  spline->pt[3].x, spline->pt[3].y);
	cairo_stroke(ct);
    }

    draw_control_line(ct, &spline->pt[0], &spline->pt[1], 2.0 / zoom);
    draw_control_line(ct, &spline->pt[3], &spline->pt[2], 2.0 / zoom);

    for (i=0; i < 4; i++) {
	draw_handle(ct, &spline->pt[i], i == win->active, 5.0 / zoom, 1.0 / zoom);
    }

    cairo_restore(ct);
}

static void
win_refresh(win_t *win)
{
    Display *dpy = win->dpy;

    cairo_t *ct;
    cairo_status_t status;
    Drawable drawable = win->pix;

    XFillRectangle(dpy, win->pix, win->gc, 0, 0, win->width, win->height);

    ct = cairo_create();

    /*
    if (win->restrict_traps)
	cairo_restrict_spline_traps(ct, win->trap_start, win->trap_stop);
    */

    cairo_set_target_drawable (ct, dpy, drawable);

    cairo_set_rgb_color(ct, 0, 0, 0);

    cairo_set_line_width(ct, win->line_width);
    cairo_set_line_cap(ct, win->line_cap);
    cairo_translate(ct, win->xtrans, win->ytrans);
    cairo_scale(ct, win->zoom, win->zoom);
    cairo_set_tolerance(ct, win->tolerance);

    draw_spline(ct, win);

    status = cairo_get_status(ct);
    if (status) {
	fprintf(stderr, "Xr is unhappy: %s\n", cairo_get_status_string(ct));
    }

    cairo_destroy(ct);

    XCopyArea(win->dpy, win->pix, win->win, win->gc,
	      0, 0, win->width, win->height,
	      0, 0);
}

static void
win_init(win_t *win, Display *dpy)
{
    int i;
    Window root;
    XGCValues gcv;

    win->dpy = dpy;
    win->width = 400;
    win->height = 400;

    root = DefaultRootWindow(dpy);
    win->scr = DefaultScreen(dpy);

    win->win = XCreateSimpleWindow(dpy, root, 0, 0,
				   win->width, win->height, 0,
				   WhitePixel(dpy, win->scr), WhitePixel(dpy, win->scr));

    win->pix = XCreatePixmap(dpy, win->win, win->width, win->height, DefaultDepth (dpy, win->scr));
    gcv.foreground = WhitePixel(dpy, win->scr);
    win->gc = XCreateGC(dpy, win->pix, GCForeground, &gcv);
    XFillRectangle(dpy, win->pix, win->gc, 0, 0, win->width, win->height);

    for (i=0; i < ARRAY_SIZE(key_binding); i++) {
	KeySym keysym;
	keysym = XStringToKeysym(key_binding[i].key);
	if (keysym == NoSymbol)
	    fprintf(stderr, "ERROR: No keysym for \"%s\"\n", key_binding[i].key);
	else
	    key_binding[i].keycode = XKeysymToKeycode(dpy, keysym);
    }

    win->active = 0;
    win->active_pt = &win->spline.pt[win->active];
    win->spline = DEFAULT_SPLINE;
    win->tolerance = DEFAULT_TOLERANCE;
    win->line_width = DEFAULT_LINE_WIDTH;
    win->line_cap = DEFAULT_LINE_CAP;
    win->zoom = DEFAULT_ZOOM;
    win->show_path = 1;

    win->drag = 0;

    win_refresh(win);
    win->needs_refresh = 0;

    win_select_events(win);

    XMapWindow(dpy, win->win);
}

static void
win_deinit(win_t *win)
{
    XFreeGC(win->dpy, win->gc);
    XFreePixmap(win->dpy, win->pix);
    XDestroyWindow(win->dpy, win->win);
}

static void
win_select_events(win_t *win)
{
    win->event_mask = 
	  ButtonPressMask
	| ButtonReleaseMask
	| PointerMotionMask
	| KeyPressMask
	| StructureNotifyMask
	| ExposureMask;
    XSelectInput(win->dpy, win->win, win->event_mask);

}

static char *
get_callback_doc(void *callback)
{
    int i;

    for (i=0; i < ARRAY_SIZE(callback_doc); i++)
	if (callback_doc[i].callback == callback)
	    return callback_doc[i].doc;

    return "<undocumented function>";
}

static void
win_print_help(win_t *win)
{
    int i;

    printf("Xr spline demonstration\n");
    printf("Click and drag to move spline endpoints and control points, or:\n\n");

    for (i=0; i < ARRAY_SIZE(button_binding); i++)
	printf("Button %d:\t%s\n",
	       button_binding[i].button,
	       get_callback_doc(button_binding[i].callback));

    printf("\n");

    for (i=0; i < ARRAY_SIZE(key_binding); i++)
	if (! key_binding[i].is_alias)
	    printf("%s:\t%s\n",
		   key_binding[i].key,
		   get_callback_doc(key_binding[i].callback));
}

static double
distance_sq(pt_t *a, pt_t *b)
{
    double dx = b->x - a->x;
    double dy = b->y - a->y;

    return dx*dx + dy*dy;
}

static int
win_handle_button_press(win_t *win, XButtonEvent *bev)
{
    int i;

    for (i=0; i < ARRAY_SIZE(button_binding); i++)
	if (button_binding[i].button == bev->button)
	    return (button_binding[i].callback)(win, bev->x, bev->y);

    return 0;
}

static void
win_handle_motion(win_t *win, XMotionEvent *mev)
{
    if (win->drag) {
	win->active_pt->x += (mev->x - win->drag_pt.x) / win->zoom;
	win->active_pt->y += (mev->y - win->drag_pt.y) / win->zoom;
	
	win->needs_refresh = 1;
	
	win->drag_pt.x = mev->x;
	win->drag_pt.y = mev->y;
    }
}

static int
win_handle_key_press(win_t *win, XKeyEvent *kev)
{
    int i;

    for (i=0; i < ARRAY_SIZE(key_binding); i++)
	if (key_binding[i].keycode == kev->keycode)
	    return (key_binding[i].callback)(win);
	
    return 0;
}

static void
win_grow_pixmap(win_t *win)
{
    Pixmap new;

    new = XCreatePixmap(win->dpy, win->win, win->width, win->height, DefaultDepth (win->dpy, win->scr));
    XFillRectangle(win->dpy, new, win->gc, 0, 0, win->width, win->height);
    XCopyArea(win->dpy, win->pix, new, win->gc, 0, 0, win->width, win->height, 0, 0);
    XFreePixmap(win->dpy, win->pix);
    win->pix = new;
    win_refresh(win);
}

static void
win_handle_configure(win_t *win, XConfigureEvent *cev)
{
    int has_grown = 0;

    if (cev->width > win->width || cev->height > win->height) {
	has_grown = 1;
    }

    win->width = cev->width;
    win->height = cev->height;

    if (has_grown) {
	win_grow_pixmap(win);
    }
}

static void
win_handle_expose(win_t *win, XExposeEvent *eev)
{
    XCopyArea(win->dpy, win->pix, win->win, win->gc,
	      eev->x, eev->y, eev->width, eev->height,
	      eev->x, eev->y);
}

static void
win_handle_events(win_t *win)
{
    int done;
    XEvent xev;

    while (1) {

	if (!XPending(win->dpy) && win->needs_refresh) {
	    win_refresh(win);
	    win->needs_refresh = 0;
	}

	XNextEvent(win->dpy, &xev);

	switch(xev.type) {
	case ButtonPress:
	    win_handle_button_press(win, &xev.xbutton);
	    break;
	case MotionNotify:
	    win_handle_motion(win, &xev.xmotion);
	    break;
	case ButtonRelease:
	    win->drag = 0;
	    break;
	case KeyPress:
	    done = win_handle_key_press(win, &xev.xkey);
	    if (done)
		return;
	    break;
	case ConfigureNotify:
	    win_handle_configure(win, &xev.xconfigure);
	    break;
	case Expose:
	    win_handle_expose(win, &xev.xexpose);
	    break;
	}
    }
}


/* Callbacks */

/* nearest control point in spline to given point */
static int
find_nearest(spline_t *spline, double x, double y)
{
    int i, nearest;
    double dist, min_dist;
    pt_t pt;

    pt.x = x;
    pt.y = y;

    for (i=0; i<4; i++) {
	dist = distance_sq(&spline->pt[i], &pt);
	if (i==0 || dist < min_dist) {
	    nearest = i;
	    min_dist = dist;
	}
    }

    return nearest;
}

static int
select_point_cb(win_t *win, int x, int y)
{
    win->drag = 1;
    win->drag_pt.x = x;
    win->drag_pt.y = y;
 
    win->active = find_nearest(&win->spline, x / win->zoom, y / win->zoom);
    win->active_pt = &win->spline.pt[win->active];

    win->needs_refresh = 1;

    return 0;
}

static int
snap_point_cb(win_t *win, int x, int y)
{
    *win->active_pt = win->spline.pt[find_nearest(&win->spline, x / win->zoom, y / win->zoom)];

    win->needs_refresh = 1;

    return 0;
}

static int
quit_cb(win_t *win)
{
    return 1;
}

static int
select_next_cb(win_t *win)
{
    win->active = (win->active + 1) % 4;
    win->active_pt = &win->spline.pt[win->active];

    win->needs_refresh = 1;

    return 0;
}

static int
left_epsilon_cb(win_t *win)
{
    win->active_pt->x -= EPSILON;

    win->needs_refresh = 1;

    return 0;
}

static int
right_epsilon_cb(win_t *win)
{
    win->active_pt->x += EPSILON;

    win->needs_refresh = 1;

    return 0;
}

static int
up_epsilon_cb(win_t *win)
{
    win->active_pt->y -= EPSILON;

    win->needs_refresh = 1;

    return 0;
}

static int
down_epsilon_cb(win_t *win)
{
    win->active_pt->y += EPSILON;

    win->needs_refresh = 1;

    return 0;
}

static int
print_spline_cb(win_t *win)
{
    pt_t *pt = win->spline.pt;

    printf("{ { %.20g, %.20g }, { %.20g, %.20g }, { %.20g, %.20g }, { %.20g, %.20g } }\n",
	   pt[0].x, pt[0].y,
	   pt[1].x, pt[1].y,
	   pt[2].x, pt[2].y,
	   pt[3].x, pt[3].y);

    return 0;
}

static int
zoom_in_cb(win_t *win)
{
    win->zoom *= 2.0;

    win->needs_refresh = 1;

    return 0;
}

static int
trans_left_cb(win_t *win)
{
    win->xtrans -= win->width / 4.0;

    win->needs_refresh = 1;

    return 0;
}

static int
trans_right_cb(win_t *win)
{
    win->xtrans += win->width / 4.0;

    win->needs_refresh = 1;

    return 0;
}

static int
trans_up_cb(win_t *win)
{
    win->ytrans -= win->height / 4.0;

    win->needs_refresh = 1;

    return 0;
}

static int
trans_down_cb(win_t *win)
{
    win->ytrans += win->height / 4.0;

    win->needs_refresh = 1;

    return 0;
}

static int
zoom_out_cb(win_t *win)
{
    win->zoom /= 2.0;

    win->needs_refresh = 1;

    return 0;
}

static int
flatten_cb(win_t *win)
{
    win->tolerance *= 10;

    win->needs_refresh = 1;

    return 0;
}

static int
smooth_cb(win_t *win)
{
    win->tolerance /= 10;

    win->needs_refresh = 1;

    return 0;
}

static int
toggle_path(win_t *win)
{
    win->show_path = ! win->show_path;

    win->needs_refresh = 1;

    return 0;
}

static int
widen_line(win_t *win)
{
    win->line_width *= 2;

    win->needs_refresh = 1;

    return 0;
}

static int
narrow_line(win_t *win)
{
    win->line_width /= 2;

    win->needs_refresh = 1;

    return 0;
}




More information about the Commit mailing list