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/README.md b/tools/font-edit/README.md index 0170bf8..37640b6 100644 --- a/tools/font-edit/README.md +++ b/tools/font-edit/README.md @@ -1,11 +1,16 @@ # twin-fedit `twin-fedit` is a tool allowing users to edit specific scalable fonts -which are expected to fit the requirements of embedded systems with larger -screens. +which are expected to fit the requirements of embedded systems with larger screens. + +The fonts that can be modified using a font editor provide the self-defined font used in [twin.h](/include/twin.h). + +

+ +

## Build Dependency ```shell -sudo apt-get install libx11-dev libcairo2-dev +sudo apt-get install libsdl2-dev libcairo2-dev ``` ## Usage @@ -14,4 +19,77 @@ make ./twin-fedit < nchars ``` -(press 'q' to next character) +## Background +The glyphs in `twin-fedit` is originated from [Hershey vector fonts](https://en.wikipedia.org/wiki/Hershey_fonts), which were created by Dr. A. V. Hershey while working at the U. S. National Bureau of Standards. + +The Hershey vector fonts set of `twin-fedit` is [`nchars`](./nchars), for example, the interpolation points and operations used to draw the font `1` are as follows +``` +/* 0x31 '1' offset 666 */ + 0, 10, 42, 0, 2, 3, + 0, 10, /* snap_x */ + -21, -15, 0, /* snap_y */ + 'm', 0, -34, + 'c', 4, -35, 8, -38, 10, -42, + 'l', 10, 0, + 'e', +``` + +The first line to 4-th line are used to draw the glyph. For more detail, see [font.c](/src/font.c). + +The first line of is the header that contains the information of the character `1`, `0x31` is the ASCII code of `1` and offset `666` is the + +The character `m` is an abbreviation for `move to`, and the values following `m` represent the x and y positions to move to in the drawing window's coordinate system, respectively. + +The character `c` is an abbreviation for `curve to`, and the values following `c` represent three x-y coordinate points used to draw a cubic Bézier curve, in the order of the first control point, the second control point, and the endpoint. + +The character `l` is an abbreviation for `line to`, and the values following `l` represent the x and y positions to move to, relative to the position from the previous operation, in the drawing window's coordinate system, respectively. + +The character `e` is an abbreviation for `end`. + +According to the steps outlined above for drawing glyph `1`, it can be split into the following steps: + +1. `'m' 0,-34`: Move to `0,-34` and plot a point at `0,-34`. +2. `'c' 4, -35, 8, -38, 10, -42`: Starting from `0,-34` and ending at `10,-42`, draw a curve using Bézier curve with the first control point at `4,-35` and the second control point at `8,-38`. +3. `'l' 10,0`: Starting from `10,-42` and ending at `10,0`, draw a straight line. +4. `'e'`: End the drawing of glyph `1`. + +Each point seen in `twin-fedit` corresponds to an operation. By selecting a point in `twin-fedit`, you can modify the coordinates to edit any glyph. + +## Quick Guide +For each glyph, there are the following shortcut keys used for editing the glyph. + +| Key | Functionality | +| --- | --- | +| ESC | Exit program | +| left mouse button | Select a point as the first operation for that glyph | +| right mouse button | Select a point as the last operation for that glyph | +| d | Delete selected point| +| f | Replace a line with a spline | +| q | Switch to next character | +| s | Split a spline into two splines by start point and end point | +| u | Undo the last operation | + + +To move a point +1. Select a point by left mouse button, +2. Use arrow keys to move the selected point. + +To move a control point +1. Select a point with two control points by left mouse button, +2. Use arrow keys to move the first control point, +3. Keep press shift key and use arrow keys to move the second control point. + +To split a spline or line +1. Select two point by left and right mouse button, +2. Use s key to split the line or spline into two segments. + +To replace the line or spline by another spline +1. Select two point by left and right mouse button as start and end of the another spline, +2. Use f key to replace the line or spline with another spline. + +To delete a point +1. Select a point by left mouse button, +2. Use d key to delete the selected point. + +To undo any operations above +1. Use u key. \ No newline at end of file diff --git a/tools/font-edit/assets/demo.gif b/tools/font-edit/assets/demo.gif new file mode 100644 index 0000000..fb8c5a8 Binary files /dev/null and b/tools/font-edit/assets/demo.gif differ diff --git a/tools/font-edit/twin-fedit.c b/tools/font-edit/twin-fedit.c index 12e2610..c50f391 100644 --- a/tools/font-edit/twin-fedit.c +++ b/tools/font-edit/twin-fedit.c @@ -22,57 +22,51 @@ #include "twin-fedit.h" -static Display *dpy; -static Window win; -static Visual *visual; -static int depth; +#include +#include +#include +#include + +static SDL_Window *window; +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; static int offsets[1024]; +/* + * exit_window - bool. Control the life of the window. + * if the value is false, the window remains open; + * otherwise, the window closes. + */ +static bool exit_window = false; + 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("Failed to initialize SDL video. Reason: %s\n", SDL_GetError()); + return 0; + } + + window = SDL_CreateWindow("Font Editor", SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, width, height, + SDL_WINDOW_SHOWN); + if (!window) { + printf("Failed to create SDL window. Reason: %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); cr = cairo_create(surface); @@ -111,6 +105,15 @@ static cmd_t *insert_cmd(cmd_t **prev) return n; } +static cmd_t *append_cmd(char_t *c) +{ + cmd_t **prev; + + for (prev = &c->cmd; *prev; prev = &(*prev)->next) + ; + return insert_cmd(prev); +} + static void delete_cmd(cmd_t **head, cmd_t *cmd) { while (*head != cmd) @@ -141,13 +144,16 @@ static void pop(char_t *c) free(s); } -static cmd_t *append_cmd(char_t *c) +static void delete_first_cmd(char_t *c, cmd_t *first) { - cmd_t **prev; + push(c); + delete_cmd(&c->cmd, first); + c->first = c->last = 0; +} - for (prev = &c->cmd; *prev; prev = &(*prev)->next) - ; - return insert_cmd(prev); +static void undo(char_t *c) +{ + pop(c); } static int commas(char *line) @@ -246,7 +252,14 @@ 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); for (cmd = c->cmd; cmd; cmd = cmd->next) { double alpha; @@ -328,21 +341,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) @@ -378,12 +402,12 @@ static cmd_t *pos_to_cmd(char_t *c, cmd_t *start, int ix, int iy) return best_cmd; } -static int is_before(cmd_t *before, cmd_t *after) +static bool is_before(cmd_t *before, cmd_t *after) { if (!before) - return 0; + return false; if (before->next == after) - return 1; + return true; return is_before(before->next, after); } @@ -448,139 +472,152 @@ static void split(char_t *c, cmd_t *first, cmd_t *last) c->first = c->last = 0; } -static void delete(char_t *c, cmd_t *first) +static void tweak_spline(char_t *c, + cmd_t *first, + int is_2nd_point, + double dx, + double dy) { - push(c); - delete_cmd(&c->cmd, first); - c->first = c->last = 0; -} - -static void tweak_spline(char_t *c, cmd_t *first, int p2, double dx, double dy) -{ - int i = p2 ? 1 : 0; + int i = !!is_2nd_point; push(c); first->pt[i].x += dx; first->pt[i].y += dy; } -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]; + /* Ensures that SDL only focuses on key events*/ + SDL_StopTextInput(); + + SDL_Event event; + SDL_Keycode key_event; + + /* keep track of the selected spline */ + cmd_t *spline = NULL; + draw_char(c); - 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': + while (SDL_PollEvent(&event)) { + switch (event.type) { + /* If SDL event is detected */ + case SDL_QUIT: + /* Click the "X" on the top of screen to exit the program */ + exit_window = true; + return; + case SDL_KEYDOWN: + /* If any key event is detected */ + key_event = event.key.keysym.sym; + + switch (key_event) { + case SDLK_q: + /* To exit play() + * This allows the user to display the next character. + */ return; - case 'c': - XClearArea(dpy, ev.xkey.window, 0, 0, 0, 0, True); - break; - case 's': + case SDLK_ESCAPE: + /* To quit the program */ + exit_window = true; + return; + case SDLK_s: + /* To split the line or curve formed by two adjacent points + * into two segments + */ if (c->first && c->last) { split(c, c->first, c->last); - draw_char(c); } + draw_char(c); break; - case 'u': + case SDLK_u: + /* To undo the last operation */ undo(c); draw_char(c); break; - case 'f': + case SDLK_f: + /* To replace a line or a curve with another spline */ if (c->first && c->last) { replace_with_spline(c, c->first, c->last); - draw_char(c); } + draw_char(c); break; - case 'd': + case SDLK_d: + /* To delete the first pointer */ if (c->first) { - delete (c, c->first); - draw_char(c); + delete_first_cmd(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); + /* Move the points */ + case SDLK_LEFT: + if (!spline) break; - case XK_Right: - tweak_spline(c, spline, ev.xkey.state & ShiftMask, 1, - 0); - draw_char(c); + tweak_spline(c, spline, event.key.keysym.mod & KMOD_SHIFT, + -1, 0); + draw_char(c); + break; + case SDLK_RIGHT: + if (!spline) break; - case XK_Up: - tweak_spline(c, spline, ev.xkey.state & ShiftMask, 0, - -1); - draw_char(c); + + tweak_spline(c, spline, event.key.keysym.mod & KMOD_SHIFT, + 1, 0); + draw_char(c); + break; + case SDLK_UP: + if (!spline) break; - case XK_Down: - tweak_spline(c, spline, ev.xkey.state & ShiftMask, 0, - 1); - draw_char(c); + + tweak_spline(c, spline, event.key.keysym.mod & KMOD_SHIFT, + 0, -1); + draw_char(c); + break; + case SDLK_DOWN: + if (!spline) break; - } - XFree(keysym); + + tweak_spline(c, spline, event.key.keysym.mod & KMOD_SHIFT, + 0, 1); + draw_char(c); + break; } - } - break; - case Expose: - if (ev.xexpose.count == 0) + break; + /* End if key event detected */ + + case SDL_MOUSEBUTTONDOWN: + /* Redraw the content after mouse button interaction */ + button(c, &event.button); + spline = c->first; draw_char(c); - break; - case ButtonPress: - button(c, &ev.xbutton); - break; + break; + } + /* End if SDL event detected */ } + + /* Ensure the SDL window surface is updated */ + SDL_UpdateWindowSurface(window); } } -static void write_char(char_t *c) +static void print_char(char_t *c) { cmd_t *cmd; @@ -608,23 +645,36 @@ static void write_char(char_t *c) offset += 1; } -int main(int argc, char **argv) +static void generate_font_metrics(void) { - 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"); +} + +int main(int argc, char **argv) +{ + char_t *c; + + /* + * Use the following command to start twin-fedit: + * + * make + * ./twin-fedit < nchars + * + */ + if (!init(argc, argv)) + exit(1); + while ((c = read_char()) && !exit_window) { + play(c); + print_char(c); + } + + generate_font_metrics(); return 0; } diff --git a/tools/font-edit/twin-fedit.h b/tools/font-edit/twin-fedit.h index ef8e2f3..475bd37 100644 --- a/tools/font-edit/twin-fedit.h +++ b/tools/font-edit/twin-fedit.h @@ -23,12 +23,6 @@ #ifndef _TWIN_FEDIT_H_ #define _TWIN_FEDIT_H_ -#include -#include -#include -#include -#include -#include #include #include #include