/* * $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); }