blob: af2fde3379bc1572b427dfcc54a26b93ee6d210d [file] [log] [blame]
/*
* $Id$
*
* Copyright © 2004 Keith Packard
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of Keith Packard not be used in
* advertising or publicity pertaining to distribution of the software without
* specific, written prior permission. Keith Packard makes no
* representations about the suitability of this software for any purpose. It
* is provided "as is" without express or implied warranty.
*
* KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "twin-fedit.h"
Display *dpy;
Window win;
Visual *visual;
int depth;
int width = 512;
int height = 512;
double scale = 8;
cairo_t *cr;
cairo_surface_t *surface;
int offset;
int offsets[1024];
int
init (int argc, char **argv)
{
int scr;
XSetWindowAttributes wa;
XTextProperty wm_name, icon_name;
XSizeHints sizeHints;
XWMHints wmHints;
XClassHint classHints;
Atom wm_delete_window;
dpy = XOpenDisplay (0);
scr = DefaultScreen (dpy);
visual = DefaultVisual (dpy, scr);
depth = DefaultDepth (dpy, scr);
wa.background_pixel = WhitePixel (dpy,scr);
wa.event_mask = (KeyPressMask|
KeyReleaseMask|
ButtonPressMask|
ButtonReleaseMask|
PointerMotionMask|
ExposureMask|
StructureNotifyMask);
wm_name.value = (unsigned char *) argv[0];
wm_name.encoding = XA_STRING;
wm_name.format = 8;
wm_name.nitems = strlen ((char *) wm_name.value) + 1;
icon_name = wm_name;
win = XCreateWindow (dpy, RootWindow (dpy, scr),
0, 0, width, height, 0,
depth, InputOutput,
visual, CWBackPixel|CWEventMask, &wa);
sizeHints.flags = 0;
wmHints.flags = InputHint;
wmHints.input = True;
classHints.res_name = argv[0];
classHints.res_class = argv[0];
XSetWMProperties (dpy, win,
&wm_name, &icon_name,
argv, argc,
&sizeHints, &wmHints, 0);
XSetWMProtocols (dpy, win, &wm_delete_window, 1);
XMapWindow (dpy, win);
surface = cairo_xlib_surface_create (dpy,
win,
visual,
width,
height);
cr = cairo_create (surface);
cairo_translate (cr, 150, 420);
cairo_scale (cr, scale, scale);
cairo_set_font_size (cr, 2);
return 1;
}
cmd_t *
copy_cmd (cmd_t *cmd)
{
cmd_t *n = malloc (sizeof (cmd_t));
if (!cmd)
return 0;
*n = *cmd;
n->next = copy_cmd (cmd->next);
return n;
}
void
free_cmd (cmd_t *cmd)
{
if (cmd)
{
free_cmd (cmd->next);
free (cmd);
}
}
cmd_t **
before (cmd_t **head, cmd_t *cmd)
{
cmd_t **prev;
for (prev = head; *prev != cmd; prev = &(*prev)->next)
;
return prev;
}
cmd_t *
insert_cmd (cmd_t **prev)
{
cmd_t *n = malloc (sizeof (cmd_t));
n->op = OpNoop;
n->next = *prev;
*prev = n;
return n;
}
void
delete_cmd (cmd_t **head, cmd_t *cmd)
{
while (*head != cmd)
head = &(*head)->next;
*head = cmd->next;
free (cmd);
}
void
push (char_t *c)
{
cmd_stack_t *s = malloc (sizeof (cmd_stack_t));
s->cmd = copy_cmd (c->cmd);
s->prev = c->stack;
c->stack = s;
}
void
pop (char_t *c)
{
cmd_stack_t *s = c->stack;
if (!s)
return;
free_cmd (c->cmd);
c->cmd = s->cmd;
c->stack = s->prev;
c->first = 0;
c->last = 0;
free (s);
}
cmd_t *
append_cmd (char_t *c)
{
cmd_t **prev;
for (prev = &c->cmd; *prev; prev = &(*prev)->next);
return insert_cmd (prev);
}
int commas (char *line)
{
int n = 0;
char c;
while ((c = *line++))
if (c == ',')
++n;
return n;
}
char_t *
read_char (void)
{
char_t *c = malloc (sizeof (char_t));
char line[1024];
cmd_t *cmd;
c->cmd = 0;
c->stack = 0;
c->first = 0;
c->last = 0;
while (fgets (line, sizeof (line), stdin))
{
if (line[0] == '/')
{
int ucs4;
if (sscanf (line + 5, "%x", &ucs4) == 1)
offsets[ucs4] = offset;
line[strlen(line)-3] = '\0';
printf ("%s offset %d */\n", line, offset);
continue;
}
if (line[0] != ' ' || line[4] != '\'')
{
offset += commas (line);
printf ("%s", line);
continue;
}
switch (line[5]) {
case 'm':
cmd = append_cmd (c);
cmd->op = OpMove;
sscanf (line + 8, "%lf, %lf", &cmd->pt[0].x, &cmd->pt[0].y);
break;
case 'l':
cmd = append_cmd (c);
cmd->op = OpLine;
sscanf (line + 8, "%lf, %lf", &cmd->pt[0].x, &cmd->pt[0].y);
break;
case 'c':
cmd = append_cmd (c);
cmd->op = OpCurve;
sscanf (line + 8, "%lf, %lf, %lf, %lf, %lf, %lf",
&cmd->pt[0].x, &cmd->pt[0].y,
&cmd->pt[1].x, &cmd->pt[1].y,
&cmd->pt[2].x, &cmd->pt[2].y);
break;
case 'e':
return c;
}
}
return 0;
}
#define DOT_SIZE 1
void
dot (cairo_t *cr, double x, double y, double red, double blue, double green, double alpha)
{
cairo_set_source_rgba (cr, red, blue, green, alpha);
cairo_set_line_width (cr, 0.7);
cairo_move_to (cr, x + DOT_SIZE, y);
cairo_arc (cr, x, y, DOT_SIZE, 0, M_PI * 2);
cairo_stroke (cr);
}
void
spot (cairo_t *cr, double x, double y, double red, double blue, double green, double alpha)
{
cairo_set_source_rgba (cr, red, blue, green, alpha);
cairo_move_to (cr, x - DOT_SIZE, y);
cairo_arc (cr, x, y, DOT_SIZE, 0, M_PI * 2);
cairo_fill (cr);
}
void
draw_char (char_t *c)
{
cmd_t *cmd;
cmd_stack_t *s;
int i;
XClearArea (dpy, win, 0, 0, 0, 0, False);
for (cmd = c->cmd; cmd; cmd = cmd->next)
{
double alpha;
double tx, ty;
if (cmd == c->first || cmd == c->last)
alpha = 1;
else
alpha = 0.5;
tx = cmd->pt[0].x;
ty = cmd->pt[0].y;
switch (cmd->op) {
case OpMove:
dot (cr, cmd->pt[0].x, cmd->pt[0].y, 1, 1, 0, alpha);
break;
case OpLine:
dot (cr, cmd->pt[0].x, cmd->pt[0].y, 1, 0, 0, alpha);
break;
case OpCurve:
dot (cr, cmd->pt[0].x, cmd->pt[0].y, 0, 0, 1, alpha);
dot (cr, cmd->pt[1].x, cmd->pt[1].y, 0, 0, 1, alpha);
dot (cr, cmd->pt[2].x, cmd->pt[2].y, 0, 1, 0, alpha);
tx = cmd->pt[2].x;
ty = cmd->pt[2].y;
break;
}
}
for (s = c->stack; s; s = s->prev)
if (!s->prev)
break;
if (s)
{
for (cmd = s->cmd; cmd; cmd = cmd->next)
{
double alpha = 1;
switch (cmd->op) {
case OpMove:
spot (cr, cmd->pt[0].x, cmd->pt[0].y, 1, 1, 0, alpha);
break;
case OpLine:
spot (cr, cmd->pt[0].x, cmd->pt[0].y, 1, 0, 0, alpha);
break;
case OpCurve:
spot (cr, cmd->pt[0].x, cmd->pt[0].y, 0, 0, 1, alpha);
spot (cr, cmd->pt[1].x, cmd->pt[1].y, 0, 0, 1, alpha);
spot (cr, cmd->pt[2].x, cmd->pt[2].y, 0, 1, 0, alpha);
break;
}
}
}
cairo_set_source_rgb (cr, 0, 0, 0);
cairo_set_line_width (cr, 0.5);
for (cmd = c->cmd; cmd; cmd = cmd->next)
{
switch (cmd->op) {
case OpMove:
cairo_move_to (cr, cmd->pt[0].x, cmd->pt[0].y);
break;
case OpLine:
cairo_line_to (cr, cmd->pt[0].x, cmd->pt[0].y);
break;
case OpCurve:
cairo_curve_to (cr,
cmd->pt[0].x, cmd->pt[0].y,
cmd->pt[1].x, cmd->pt[1].y,
cmd->pt[2].x, cmd->pt[2].y);
break;
default:
abort ();
}
}
cairo_stroke (cr);
for (cmd = c->cmd, i = 0; cmd; cmd = cmd->next, i++)
{
double tx, ty;
char buf[10];
if (cmd->op == OpCurve)
{
tx = cmd->pt[2].x;
ty = cmd->pt[2].y;
}
else
{
tx = cmd->pt[0].x;
ty = cmd->pt[0].y;
}
{
cairo_save (cr);
if (cmd == c->first)
cairo_set_source_rgb (cr, 0, .5, 0);
else if (cmd == c->last)
cairo_set_source_rgb (cr, 0, 0, .5);
else
cairo_set_source_rgb (cr, 0, .5, .5);
cairo_move_to (cr, tx - 2, ty + 3);
sprintf (buf, "%d", i);
cairo_show_text (cr, buf);
cairo_restore (cr);
}
}
}
cmd_t *
pos_to_cmd (char_t *c, cmd_t *start, int ix, int iy)
{
double x = ix, y = iy;
double best_err = 1;
cmd_t *cmd, *best_cmd = 0;
cairo_device_to_user (cr, &x, &y);
if (start)
start = start->next;
if (!start)
start = c->cmd;
cmd = start;
while (cmd)
{
int i = cmd->op == OpCurve ? 2 : 0;
double dx = cmd->pt[i].x - x;
double dy = cmd->pt[i].y - y;
double err = sqrt (dx * dx + dy * dy);
if (err < best_err)
{
best_err = err;
best_cmd = cmd;
}
if (cmd->next)
cmd = cmd->next;
else
cmd = c->cmd;
if (cmd == start)
cmd = 0;
}
return best_cmd;
}
int
is_before (cmd_t *before, cmd_t *after)
{
if (!before) return 0;
if (before->next == after) return 1;
return is_before (before->next, after);
}
void
order (cmd_t **first_p, cmd_t **last_p)
{
if (!is_before (*first_p, *last_p))
{
cmd_t *t = *first_p;
*first_p = *last_p;
*last_p = t;
}
}
void
replace_with_spline (char_t *c, cmd_t *first, cmd_t *last)
{
pts_t *pts = new_pts ();
spline_t s;
cmd_t *cmd, *next, *save;
order (&first, &last);
for (cmd = first; cmd != last->next; cmd = cmd->next)
{
int i = cmd->op == OpCurve ? 2 : 0;
add_pt (pts, &cmd->pt[i]);
}
s = fit (pts->pt, pts->n);
push (c);
save = last->next;
for (cmd = first->next; cmd != save; cmd = next)
{
next = cmd->next;
delete_cmd (&c->cmd, cmd);
}
cmd = insert_cmd (&first->next);
cmd->op = OpCurve;
cmd->pt[0] = s.b;
cmd->pt[1] = s.c;
cmd->pt[2] = s.d;
dispose_pts (pts);
c->first = c->last = 0;
}
void
split (char_t *c, cmd_t *first, cmd_t *last)
{
cmd_t *cmd;
push (c);
cmd = insert_cmd (&first->next);
cmd->op = OpLine;
cmd->pt[0] = lerp (&first->pt[0], &last->pt[0]);
if (last->op == OpMove)
{
cmd_t *extra = insert_cmd (&last->next);
extra->op = OpLine;
extra->pt[0] = last->pt[0];
last->pt[0] = cmd->pt[0];
}
c->first = c->last = 0;
}
void
delete (char_t *c, cmd_t *first)
{
push (c);
delete_cmd (&c->cmd, first);
c->first = c->last = 0;
}
void
tweak_spline (char_t *c, cmd_t *first, int p2, double dx, double dy)
{
int i = p2 ? 1 : 0;
push (c);
first->pt[i].x += dx;
first->pt[i].y += dy;
}
void
undo (char_t *c)
{
pop (c);
}
void
button (char_t *c, XButtonEvent *bev)
{
cmd_t *first = bev->button == 1 ? c->first : c->last;
cmd_t *where = pos_to_cmd (c, first, bev->x, bev->y);
if (!where)
{
XBell (dpy, 50);
return;
}
switch (bev->button) {
case 1:
c->first = where;
break;
case 2:
case 3:
c->last = where;
break;
}
draw_char (c);
}
void
play (char_t *c)
{
XEvent ev;
char key_string[10];
XClearArea (dpy, win, 0, 0, 0, 0, True);
for (;;)
{
XNextEvent (dpy, &ev);
switch (ev.type) {
case KeyPress:
if (XLookupString ((XKeyEvent *) &ev, key_string, sizeof (key_string), 0, 0) == 1) {
switch (key_string[0]) {
case 'q':
return;
case 'c':
XClearArea (dpy, ev.xkey.window, 0, 0, 0, 0, True);
break;
case 's':
if (c->first && c->last)
{
split (c, c->first, c->last);
draw_char (c);
}
break;
case 'u':
undo (c);
draw_char (c);
break;
case 'f':
if (c->first && c->last)
{
replace_with_spline (c, c->first, c->last);
draw_char (c);
}
break;
case 'd':
if (c->first)
{
delete (c, c->first);
draw_char (c);
}
break;
}
}
else
{
cmd_t *spline;
if (c->first && c->first->op == OpCurve)
spline = c->first;
else if (c->last && c->last->op == OpCurve)
spline = c->last;
else
spline = 0;
if (spline) {
switch (XKeycodeToKeysym (dpy, ev.xkey.keycode, 0)) {
case XK_Left:
tweak_spline (c, spline,
ev.xkey.state & ShiftMask,
-1, 0);
draw_char (c);
break;
case XK_Right:
tweak_spline (c, spline,
ev.xkey.state & ShiftMask,
1, 0);
draw_char (c);
break;
case XK_Up:
tweak_spline (c, spline,
ev.xkey.state & ShiftMask,
0, -1);
draw_char (c);
break;
case XK_Down:
tweak_spline (c, spline,
ev.xkey.state & ShiftMask,
0, 1);
draw_char (c);
break;
}
}
}
break;
case Expose:
if (ev.xexpose.count == 0)
draw_char (c);
break;
case ButtonPress:
button (c, &ev.xbutton);
break;
}
}
}
void
write_char (char_t *c)
{
cmd_t *cmd;
for (cmd = c->cmd; cmd; cmd = cmd->next)
{
switch (cmd->op) {
case OpMove:
printf (" 'm', %g, %g,\n", cmd->pt[0].x, cmd->pt[0].y);
offset += 3;
break;
case OpLine:
printf (" 'l', %g, %g,\n", cmd->pt[0].x, cmd->pt[0].y);
offset += 3;
break;
case OpCurve:
printf (" 'c', %g, %g, %g, %g, %g, %g,\n",
cmd->pt[0].x, cmd->pt[0].y,
cmd->pt[1].x, cmd->pt[1].y,
cmd->pt[2].x, cmd->pt[2].y);
offset += 7;
break;
}
}
printf (" 'e',\n");
offset += 1;
}
int
main (int argc, char **argv)
{
char_t *c;
int ucs4;
if (!init (argc, argv))
exit (1);
while ((c = read_char ()))
{
play (c);
write_char (c);
}
for (ucs4 = 0; ucs4 < 0x80; ucs4++)
{
if ((ucs4 & 7) == 0) printf ("\n ");
printf (" %4d,", offsets[ucs4]);
}
printf ("\n");
exit (0);
}