sysprog21 / mado

A window system for resource-constrained devices
MIT License
40 stars 15 forks source link

Port font editor to SDL2 #7

Open jserv opened 4 months ago

jserv commented 4 months ago

For the sake of portability, this project prefers SDL2 over Xlib, meaning better adoption for development. twin-fedit is a tool that allows users to edit specific scalable fonts, which are expected to meet the requirements of embedded systems with larger screens. It is currently implemented in Xlib and should be ported to SDL2 for the above reasons.

The behavior of the function remains the same as in the original X11 version, with Cairo still handling the actual rendering of graphics, while SDL2 takes over window management, input handling, and clearing the window. This will ensure the SDL window behaves as expected while maintaining all the original functionality.

Reference: sdl2-cairo-example

jouae commented 3 months ago

Development log of font-edit.

jserv commented 2 months ago

Consider the following changes:

diff --git a/tools/font-edit/Makefile b/tools/font-edit/Makefile
index 0165bfd..1b25526 100644
--- a/tools/font-edit/Makefile
+++ b/tools/font-edit/Makefile
@@ -1,7 +1,7 @@
 TARGET = twin-fedit

-CFLAGS = $(shell pkg-config --cflags cairo x11) -g -Wall
-LIBS = $(shell pkg-config --libs cairo x11)
+CFLAGS = $(shell pkg-config --cflags cairo) $(shell sdl2-config --cflags) -g -Wall
+LIBS = $(shell pkg-config --libs cairo) $(shell sdl2-config --libs)

 OBJS = \
    twin-fedit.o \
diff --git a/tools/font-edit/sfit.c b/tools/font-edit/sfit.c
index 7ef122b..be99aae 100644
--- a/tools/font-edit/sfit.c
+++ b/tools/font-edit/sfit.c
@@ -20,6 +20,9 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */

+#include <math.h>
+#include <stdlib.h>
+
 #include "twin-fedit.h"

 static double min(double a, double b)
diff --git a/tools/font-edit/twin-fedit.c b/tools/font-edit/twin-fedit.c
index 12e2610..6a58ab2 100644
--- a/tools/font-edit/twin-fedit.c
+++ b/tools/font-edit/twin-fedit.c
@@ -1,4 +1,5 @@
 /*
+ * Copyright (c) 2024 National Cheng Kung University, Taiwan
  * Copyright (c) 2004 Keith Packard
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
@@ -20,15 +21,24 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */

+#include <SDL.h>
+#include <cairo.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
 #include "twin-fedit.h"

-static Display *dpy;
-static Window win;
-static Visual *visual;
-static int depth;
+static SDL_Window *window;
+static SDL_Renderer *renderer;
+static cairo_t *cr;
+static cairo_surface_t *surface;
 static int width = 512;
 static int height = 512;
 static double scale = 8;
+
 static cairo_t *cr;
 static cairo_surface_t *surface;
 static int offset;
@@ -37,49 +47,35 @@ static int offsets[1024];

 static int init(int argc, char **argv)
 {
-    int scr;
-    XSetWindowAttributes wa;
-    XTextProperty wm_name, icon_name;
-    XSizeHints sizeHints;
-    XWMHints wmHints;
-    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;
-    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);
+    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+        printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
+        return 0;
+    }
+
+    window = SDL_CreateWindow("Twin Fedit", SDL_WINDOWPOS_CENTERED,
+                              SDL_WINDOWPOS_CENTERED, width, height,
+                              SDL_WINDOW_SHOWN);
+    if (!window) {
+        printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
+        return 0;
+    }
+
+    /* Create an SDL surface linked to the window */
+    SDL_Surface *sdl_surface = SDL_GetWindowSurface(window);
+
+    /* Create Cairo surface based on the SDL surface */
+    surface = cairo_image_surface_create_for_data(
+        (unsigned char *) sdl_surface->pixels, CAIRO_FORMAT_ARGB32,
+        sdl_surface->w, sdl_surface->h, sdl_surface->pitch);

+    /* Create Cairo context for drawing */
     cr = cairo_create(surface);

+    /* Set transformations */
     cairo_translate(cr, 150, 420);
     cairo_scale(cr, scale, scale);
-
     cairo_set_font_size(cr, 2);
+
     return 1;
 }

@@ -246,8 +242,16 @@ static void draw_char(char_t *c)
     cmd_stack_t *s;
     int i;

-    XClearArea(dpy, win, 0, 0, 0, 0, False);
+    /* Clear the SDL surface */
+    SDL_Surface *sdl_surface = SDL_GetWindowSurface(window);
+    /* Fill with white color to clear */
+    SDL_FillRect(sdl_surface, NULL,
+                 SDL_MapRGB(sdl_surface->format, 255, 255, 255));
+
+    /* Set up Cairo to draw on the surface */
+    cairo_save(cr);

+    /* Loop through the commands and draw points */
     for (cmd = c->cmd; cmd; cmd = cmd->next) {
         double alpha;

@@ -258,37 +262,50 @@ static void draw_char(char_t *c)

         switch (cmd->op) {
         case op_move:
+            /* Yellow dot for move */
             dot(cr, cmd->pt[0].x, cmd->pt[0].y, 1, 1, 0, alpha);
             break;
         case op_line:
+            /* Red dot for line */
             dot(cr, cmd->pt[0].x, cmd->pt[0].y, 1, 0, 0, alpha);
             break;
         case op_curve:
+            /* Blue dot for control point 1 */
             dot(cr, cmd->pt[0].x, cmd->pt[0].y, 0, 0, 1, alpha);
+            /* Blue dot for control point 2 */
             dot(cr, cmd->pt[1].x, cmd->pt[1].y, 0, 0, 1, alpha);
+            /* Green dot for the end point */
             dot(cr, cmd->pt[2].x, cmd->pt[2].y, 0, 1, 0, alpha);
             break;
         default:
             break;
         }
     }
+
+    /* Draw commands from the stack (if present) */
     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 op_move:
+                /* Yellow spot for move */
                 spot(cr, cmd->pt[0].x, cmd->pt[0].y, 1, 1, 0, alpha);
                 break;
             case op_line:
+                /* Red spot for line */
                 spot(cr, cmd->pt[0].x, cmd->pt[0].y, 1, 0, 0, alpha);
                 break;
             case op_curve:
+                /* Blue spot for control point 1 */
                 spot(cr, cmd->pt[0].x, cmd->pt[0].y, 0, 0, 1, alpha);
+                /* Blue spot for control point 2 */
                 spot(cr, cmd->pt[1].x, cmd->pt[1].y, 0, 0, 1, alpha);
+                /* Green spot for end point */
                 spot(cr, cmd->pt[2].x, cmd->pt[2].y, 0, 1, 0, alpha);
                 break;
             default:
@@ -296,6 +313,8 @@ static void draw_char(char_t *c)
             }
         }
     }
+
+    /* Draw lines and curves between points */
     cairo_set_source_rgb(cr, 0, 0, 0);
     cairo_set_line_width(cr, 0.5);

@@ -312,11 +331,13 @@ static void draw_char(char_t *c)
                            cmd->pt[1].y, cmd->pt[2].x, cmd->pt[2].y);
             break;
         default:
+            /* FIXME: Handle unexpected operations */
             abort();
         }
     }
     cairo_stroke(cr);

+    /* Draw text/labels for each command */
     for (cmd = c->cmd, i = 0; cmd; cmd = cmd->next, i++) {
         double tx, ty;
         char buf[11];
@@ -328,21 +349,32 @@ static void draw_char(char_t *c)
             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);
-            snprintf(buf, sizeof(buf), "%d", i);
-            cairo_show_text(cr, buf);
-            cairo_restore(cr);
+
+        /* Save state before rendering text */
+        cairo_save(cr);
+        if (cmd == c->first) {
+            /* Green for the first command */
+            cairo_set_source_rgb(cr, 0, .5, 0);
+        } else if (cmd == c->last) {
+            /* Blue for the last command */
+            cairo_set_source_rgb(cr, 0, 0, .5);
+        } else {
+            /* Cyan for intermediate commands */
+            cairo_set_source_rgb(cr, 0, .5, .5);
         }
+
+        cairo_move_to(cr, tx - 2, ty + 3);
+        /* Label with the index and draw */
+        snprintf(buf, sizeof(buf), "%d", i);
+        cairo_show_text(cr, buf);
+        /* Restore after text drawing */
+        cairo_restore(cr);
     }
+
+    cairo_restore(cr);
+
+    /* Finally, update the SDL surface with the new Cairo drawing */
+    SDL_UpdateWindowSurface(window);
 }

 static cmd_t *pos_to_cmd(char_t *c, cmd_t *start, int ix, int iy)
@@ -410,17 +442,19 @@ static void replace_with_spline(char_t *c, cmd_t *first, cmd_t *last)

     s = fit(pts->pt, pts->n);

+    /* Push the state to the stack for undo */
     push(c);

     save = last->next;

+    /* Delete intermediate commands */
     for (cmd = first->next; cmd != save; cmd = next) {
         next = cmd->next;
         delete_cmd(&c->cmd, cmd);
     }

+    /* Insert the new spline command */
     cmd = insert_cmd(&first->next);
-
     cmd->op = op_curve;
     cmd->pt[0] = s.b;
     cmd->pt[1] = s.c;
@@ -434,13 +468,15 @@ static void split(char_t *c, cmd_t *first, cmd_t *last)
 {
     cmd_t *cmd;

+    /* Save the current state for undo */
     push(c);
+    /* Insert a new command between first and last */
     cmd = insert_cmd(&first->next);
     cmd->op = op_line;
+    /* Linear interpolation between points */
     cmd->pt[0] = lerp(&first->pt[0], &last->pt[0]);
     if (last->op == op_move) {
         cmd_t *extra = insert_cmd(&last->next);
-
         extra->op = op_line;
         extra->pt[0] = last->pt[0];
         last->pt[0] = cmd->pt[0];
@@ -459,6 +495,7 @@ static void tweak_spline(char_t *c, cmd_t *first, int p2, double dx, double dy)
 {
     int i = p2 ? 1 : 0;

+    /* Push the state to the stack for undo */
     push(c);
     first->pt[i].x += dx;
     first->pt[i].y += dy;
@@ -469,114 +506,119 @@ static void undo(char_t *c)
     pop(c);
 }

-static void button(char_t *c, XButtonEvent *bev)
+static void button(char_t *c, SDL_MouseButtonEvent *bev)
 {
-    cmd_t *first = bev->button == 1 ? c->first : c->last;
+    cmd_t *first = bev->button == SDL_BUTTON_LEFT ? c->first : c->last;
     cmd_t *where = pos_to_cmd(c, first, bev->x, bev->y);

     if (!where) {
-        XBell(dpy, 50);
+        SDL_Log("Button click outside target");
         return;
     }
+
     switch (bev->button) {
-    case 1:
+    case SDL_BUTTON_LEFT:
         c->first = where;
         break;
-    case 2:
-    case 3:
+    case SDL_BUTTON_RIGHT:
         c->last = where;
         break;
     }
+
     draw_char(c);
 }

 static 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);
+    SDL_Event event;
+    int quit = 0;
+    /* keep track of the selected spline */
+    cmd_t *spline = NULL;
+
+    while (!quit) {
+        while (SDL_PollEvent(&event)) {
+            switch (event.type) {
+            case SDL_QUIT:
+                quit = 1;
+                break;
+            case SDL_KEYDOWN:
+                switch (event.key.keysym.sym) {
+                case SDLK_q:
+                    quit = 1;
+                    break;
+                case SDLK_c:
+                    /* Clear the screen */
+                    SDL_FillRect(
+                        SDL_GetWindowSurface(window), NULL,
+                        SDL_MapRGB(SDL_GetWindowSurface(window)->format, 255,
+                                   255, 255));
                     break;
-                case 's':
+                case SDLK_s:
+                    /* Split the command between first and last */
                     if (c->first && c->last) {
                         split(c, c->first, c->last);
                         draw_char(c);
                     }
                     break;
-                case 'u':
+                case SDLK_u:
+                    /* Undo the last operation */
                     undo(c);
                     draw_char(c);
                     break;
-                case 'f':
+                case SDLK_f:
+                    /* Replace with spline between first and last */
                     if (c->first && c->last) {
                         replace_with_spline(c, c->first, c->last);
                         draw_char(c);
                     }
                     break;
-                case 'd':
+                case SDLK_d:
+                    /* Delete the first command */
                     if (c->first) {
                         delete (c, c->first);
                         draw_char(c);
                     }
                     break;
-                }
-            } else {
-                cmd_t *spline;
-                if (c->first && c->first->op == op_curve)
-                    spline = c->first;
-                else if (c->last && c->last->op == op_curve)
-                    spline = c->last;
-                else
-                    spline = 0;
-                if (spline) {
-                    int keysyms_keycode;
-                    KeySym *keysym = XGetKeyboardMapping(dpy, ev.xkey.keycode,
-                                                         1, &keysyms_keycode);
-                    switch (keysyms_keycode) {
-                    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);
+                case SDLK_LEFT:
+                case SDLK_RIGHT:
+                case SDLK_UP:
+                case SDLK_DOWN:
+                    /* Adjust spline control points with arrow keys */
+                    if (spline) {
+                        int dx = 0, dy = 0;
+                        if (event.key.keysym.sym == SDLK_LEFT)
+                            dx = -1;
+                        if (event.key.keysym.sym == SDLK_RIGHT)
+                            dx = 1;
+                        if (event.key.keysym.sym == SDLK_UP)
+                            dy = -1;
+                        if (event.key.keysym.sym == SDLK_DOWN)
+                            dy = 1;
+                        /* Use Shift for different control point */
+                        tweak_spline(c, spline,
+                                     event.key.keysym.mod & KMOD_SHIFT, dx, dy);
                         draw_char(c);
-                        break;
-                    case XK_Down:
-                        tweak_spline(c, spline, ev.xkey.state & ShiftMask, 0,
-                                     1);
-                        draw_char(c);
-                        break;
                     }
-                    XFree(keysym);
+                    break;
+                default:
+                    break;
                 }
+                break;
+            case SDL_MOUSEBUTTONDOWN:
+                /* Handle mouse clicks to select commands */
+                button(c, &event.button);
+                break;
+            case SDL_WINDOWEVENT:
+                if (event.window.event == SDL_WINDOWEVENT_EXPOSED)
+                    draw_char(c);
+                break;
+            default:
+                break;
             }
-            break;
-        case Expose:
-            if (ev.xexpose.count == 0)
-                draw_char(c);
-            break;
-        case ButtonPress:
-            button(c, &ev.xbutton);
-            break;
         }
+
+        /* Ensure the window is updated after any event */
+        SDL_UpdateWindowSurface(window);
     }
 }

diff --git a/tools/font-edit/twin-fedit.h b/tools/font-edit/twin-fedit.h
index ef8e2f3..9226081 100644
--- a/tools/font-edit/twin-fedit.h
+++ b/tools/font-edit/twin-fedit.h
@@ -23,18 +23,6 @@
 #ifndef _TWIN_FEDIT_H_
 #define _TWIN_FEDIT_H_

-#include <X11/Xatom.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/keysym.h>
-#include <cairo-xlib.h>
-#include <cairo.h>
-#include <math.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
 /* Geometric types */

 /*

After make, execute the command ./twin-fedit < nchars, and you should launch the window created by SDL2.

Press q to select the next character and press u to update the screen. Press c to clear the screen and press u to restore. Press d on certain stroke to delete.

Known issue: