diff --git a/Makefile b/Makefile index 4667b75f..ce8a608f 100644 --- a/Makefile +++ b/Makefile @@ -175,7 +175,8 @@ font-edit_cflags-y := \ $(shell sdl2-config --cflags) font-edit_ldflags-y := \ $(shell pkg-config --libs cairo) \ - $(shell sdl2-config --libs) + $(shell sdl2-config --libs) \ + -lm # Headless control tool target-$(CONFIG_TOOL_HEADLESS_CTL) += headless-ctl diff --git a/tools/font-edit/font-edit.c b/tools/font-edit/font-edit.c index a0121070..2e2e8ded 100644 --- a/tools/font-edit/font-edit.c +++ b/tools/font-edit/font-edit.c @@ -39,19 +39,19 @@ static int offset; static int offsets[1024]; -/* - * exit_window - bool. Control the life of the window. +/* 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 __attribute__((unused)), - char **argv __attribute__((unused))) +static void free_cmd(cmd_t *cmd); + +static bool init(int argc maybe_unused, char **argv maybe_unused) { if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("Failed to initialize SDL video. Reason: %s\n", SDL_GetError()); - return 0; + return false; } window = SDL_CreateWindow("Font Editor", SDL_WINDOWPOS_CENTERED, @@ -59,7 +59,7 @@ static int init(int argc __attribute__((unused)), SDL_WINDOW_SHOWN); if (!window) { printf("Failed to create SDL window. Reason: %s\n", SDL_GetError()); - return 0; + return false; } /* Create an SDL surface linked to the window */ @@ -76,30 +76,65 @@ static int init(int argc __attribute__((unused)), cairo_scale(cr, scale, scale); cairo_set_font_size(cr, 2); - return 1; + return true; +} + +static void cleanup(void) +{ + if (cr) { + cairo_destroy(cr); + cr = NULL; + } + if (surface) { + cairo_surface_destroy(surface); + surface = NULL; + } + if (window) { + SDL_DestroyWindow(window); + window = NULL; + } + SDL_Quit(); } static 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; + return NULL; + + cmd_t *head = NULL; + cmd_t **tail = &head; + + while (cmd) { + cmd_t *n = malloc(sizeof(cmd_t)); + if (!n) { + /* Cleanup on failure */ + free_cmd(head); + return NULL; + } + *n = *cmd; + n->next = NULL; + *tail = n; + tail = &n->next; + cmd = cmd->next; + } + + return head; } static void free_cmd(cmd_t *cmd) { - if (cmd) { - free_cmd(cmd->next); + while (cmd) { + cmd_t *next = cmd->next; free(cmd); + cmd = next; } } static cmd_t *insert_cmd(cmd_t **prev) { cmd_t *n = malloc(sizeof(cmd_t)); + if (!n) + return NULL; n->op = op_noop; n->next = *prev; @@ -124,13 +159,21 @@ static void delete_cmd(cmd_t **head, cmd_t *cmd) free(cmd); } -static void push(char_t *c) +static bool push(char_t *c) { cmd_stack_t *s = malloc(sizeof(cmd_stack_t)); + if (!s) + return false; s->cmd = copy_cmd(c->cmd); + if (!s->cmd && c->cmd) { + free(s); + return false; + } + s->prev = c->stack; c->stack = s; + return true; } static void pop(char_t *c) @@ -148,7 +191,8 @@ static void pop(char_t *c) static void delete_first_cmd(char_t *c, cmd_t *first) { - push(c); + if (!push(c)) + fprintf(stderr, "Warning: cannot save undo state\n"); delete_cmd(&c->cmd, first); c->first = c->last = 0; } @@ -171,6 +215,9 @@ static int commas(char *line) static char_t *read_char(FILE *file) { char_t *c = malloc(sizeof(char_t)); + if (!c) + return NULL; + char line[1024]; cmd_t *cmd; @@ -195,16 +242,22 @@ static char_t *read_char(FILE *file) switch (line[5]) { case 'm': cmd = append_cmd(c); + if (!cmd) + goto error; cmd->op = op_move; sscanf(line + 8, "%lf, %lf", &cmd->pt[0].x, &cmd->pt[0].y); break; case 'l': cmd = append_cmd(c); + if (!cmd) + goto error; cmd->op = op_line; sscanf(line + 8, "%lf, %lf", &cmd->pt[0].x, &cmd->pt[0].y); break; case 'c': cmd = append_cmd(c); + if (!cmd) + goto error; cmd->op = op_curve; 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, @@ -214,7 +267,18 @@ static char_t *read_char(FILE *file) return c; } } - return 0; + return NULL; + +error: + free_cmd(c->cmd); + while (c->stack) { + cmd_stack_t *s = c->stack; + c->stack = s->prev; + free_cmd(s->cmd); + free(s); + } + free(c); + return NULL; } #define DOT_SIZE 1 @@ -408,9 +472,13 @@ static bool is_before(cmd_t *before, cmd_t *after) { if (!before) return false; - if (before->next == after) - return true; - return is_before(before->next, after); + + while (before) { + if (before->next == after) + return true; + before = before->next; + } + return false; } static void order(cmd_t **first_p, cmd_t **last_p) @@ -425,18 +493,28 @@ static void order(cmd_t **first_p, cmd_t **last_p) static void replace_with_spline(char_t *c, cmd_t *first, cmd_t *last) { pts_t *pts = new_pts(); + if (!pts) { + fprintf(stderr, "Error: out of memory\n"); + return; + } + spline_t s; cmd_t *cmd, *next, *save; order(&first, &last); for (cmd = first; cmd != last->next; cmd = cmd->next) { int i = cmd->op == op_curve ? 2 : 0; - add_pt(pts, &cmd->pt[i]); + if (!add_pt(pts, &cmd->pt[i])) { + dispose_pts(pts); + fprintf(stderr, "Error: out of memory\n"); + return; + } } s = fit(pts->pt, pts->n); - push(c); + if (!push(c)) + fprintf(stderr, "Warning: cannot save undo state\n"); save = last->next; @@ -446,6 +524,12 @@ static void replace_with_spline(char_t *c, cmd_t *first, cmd_t *last) } cmd = insert_cmd(&first->next); + if (!cmd) { + pop(c); + dispose_pts(pts); + fprintf(stderr, "Error: out of memory\n"); + return; + } cmd->op = op_curve; cmd->pt[0] = s.b; @@ -460,13 +544,23 @@ static void split(char_t *c, cmd_t *first, cmd_t *last) { cmd_t *cmd; - push(c); + if (!push(c)) + fprintf(stderr, "Warning: cannot save undo state\n"); cmd = insert_cmd(&first->next); + if (!cmd) { + pop(c); + fprintf(stderr, "Error: out of memory\n"); + return; + } cmd->op = op_line; cmd->pt[0] = lerp(&first->pt[0], &last->pt[0]); if (last->op == op_move) { cmd_t *extra = insert_cmd(&last->next); - + if (!extra) { + pop(c); + fprintf(stderr, "Error: out of memory\n"); + return; + } extra->op = op_line; extra->pt[0] = last->pt[0]; last->pt[0] = cmd->pt[0]; @@ -482,7 +576,8 @@ static void tweak_spline(char_t *c, { int i = !!is_2nd_point; - push(c); + if (!push(c)) + fprintf(stderr, "Warning: cannot save undo state\n"); first->pt[i].x += dx; first->pt[i].y += dy; } @@ -658,6 +753,21 @@ static void generate_font_metrics(void) printf("\n"); } +static void free_char(char_t *c) +{ + if (!c) + return; + + free_cmd(c->cmd); + while (c->stack) { + cmd_stack_t *s = c->stack; + c->stack = s->prev; + free_cmd(s->cmd); + free(s); + } + free(c); +} + int main(int argc, char **argv) { char_t *c; @@ -673,16 +783,22 @@ int main(int argc, char **argv) return 2; } - if (!init(argc, argv)) - exit(1); + if (!init(argc, argv)) { + fclose(file); + return 1; + } + while ((c = read_char(file)) && !exit_window) { play(c); print_char(c); + free_char(c); } fclose(file); generate_font_metrics(); + cleanup(); + return 0; } diff --git a/tools/font-edit/font-edit.h b/tools/font-edit/font-edit.h index 475bd37a..7ee705a9 100644 --- a/tools/font-edit/font-edit.h +++ b/tools/font-edit/font-edit.h @@ -24,11 +24,14 @@ #define _TWIN_FEDIT_H_ #include +#include #include #include #include #include +#define maybe_unused __attribute__((unused)) + /* Geometric types */ /* @@ -104,8 +107,10 @@ void dispose_pts(pts_t *pts); * add_pt() - Add a point to pts_t * @pts: the object that receives the added points * @pt: the point to be added + * + * Return: true on success, false on memory allocation failure */ -void add_pt(pts_t *pts, pt_t *pt); +bool add_pt(pts_t *pts, pt_t *pt); /* * distance_to_point() - Calculate distance between two points diff --git a/tools/font-edit/sfit.c b/tools/font-edit/sfit.c index f0db0718..efbf79fe 100644 --- a/tools/font-edit/sfit.c +++ b/tools/font-edit/sfit.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2004 Keith Packard + * Copyright (c) 2025 National Cheng Kung University, Taiwan * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -22,29 +23,11 @@ #include "font-edit.h" -static double min(double a, double b) -{ - return a < b ? a : b; -} - -static double max(double a, double b) -{ - return a > b ? a : b; -} - -double sqrt(double x) -{ - double i = x / 2; - if (x < 0) - return 0; - while (fabs(i - (x / i)) / i > 0.000000000000001) - i = (i + (x / i)) / 2; - return i; -} - pts_t *new_pts(void) { pts_t *pts = malloc(sizeof(pts_t)); + if (!pts) + return NULL; pts->n = 0; pts->s = 0; @@ -60,18 +43,25 @@ void dispose_pts(pts_t *pts) free(pts); } -void add_pt(pts_t *pts, pt_t *pt) +bool add_pt(pts_t *pts, pt_t *pt) { if (pts->n == pts->s) { int ns = pts->s ? pts->s * 2 : 16; + pt_t *new_pt; if (pts->pt) - pts->pt = realloc(pts->pt, ns * sizeof(pt_t)); + new_pt = realloc(pts->pt, ns * sizeof(pt_t)); else - pts->pt = malloc(ns * sizeof(pt_t)); + new_pt = malloc(ns * sizeof(pt_t)); + + if (!new_pt) + return false; + + pts->pt = new_pt; pts->s = ns; } pts->pt[pts->n++] = *pt; + return true; } double distance_to_point(pt_t *a, pt_t *b) @@ -84,8 +74,7 @@ double distance_to_point(pt_t *a, pt_t *b) double distance_to_line(pt_t *p, pt_t *p1, pt_t *p2) { - /* - * Convert to normal form (AX + BY + C = 0) + /* Convert to normal form (AX + BY + C = 0) * * (X - x1) * (y2 - y1) = (Y - y1) * (x2 - x1) * @@ -147,17 +136,6 @@ double distance_to_segment(pt_t *p, pt_t *p1, pt_t *p2) return distance_to_point(p, &px); } -static double distance_to_segments(pt_t *p, pts_t *s) -{ - double m = distance_to_segment(p, &s->pt[0], &s->pt[1]); - int i; - for (i = 1; i < s->n - 1; i++) { - double d = distance_to_segment(p, &s->pt[i], &s->pt[i + 1]); - m = min(d, m); - } - return m; -} - pt_t lerp(pt_t *a, pt_t *b) { pt_t p; @@ -167,127 +145,128 @@ pt_t lerp(pt_t *a, pt_t *b) return p; } -static void dcj(spline_t *s, spline_t *s1, spline_t *s2) -{ - pt_t ab = lerp(&s->a, &s->b); - pt_t bc = lerp(&s->b, &s->c); - pt_t cd = lerp(&s->c, &s->d); - pt_t abbc = lerp(&ab, &bc); - pt_t bccd = lerp(&bc, &cd); - pt_t final = lerp(&abbc, &bccd); - - s1->a = s->a; - s1->b = ab; - s1->c = abbc; - s1->d = final; - - s2->a = final; - s2->b = bccd; - s2->c = cd; - s2->d = s->d; -} - -static double spline_error(spline_t *s) +/* Chord-length parameterization + * Assigns parameter t to each point based on cumulative chord length + */ +static void chord_length_parameterize(pt_t *p, int n, double *t) { - double berr, cerr; + double total_len = 0.0; + int i; - berr = distance_to_line(&s->b, &s->a, &s->d); - cerr = distance_to_line(&s->c, &s->a, &s->d); - return max(berr, cerr); -} + t[0] = 0.0; -static void decomp(pts_t *pts, spline_t *s, double tolerance) -{ - if (spline_error(s) <= tolerance) - add_pt(pts, &s->a); - else { - spline_t s1, s2; - dcj(s, &s1, &s2); - decomp(pts, &s1, tolerance); - decomp(pts, &s2, tolerance); + /* Calculate cumulative chord lengths */ + for (i = 1; i < n; i++) { + double dx = p[i].x - p[i - 1].x; + double dy = p[i].y - p[i - 1].y; + double len = sqrt(dx * dx + dy * dy); + total_len += len; + t[i] = total_len; } -} - -static pts_t *decompose(spline_t *s, double tolerance) -{ - pts_t *result = new_pts(); - - decomp(result, s, tolerance); - add_pt(result, &s->d); - return result; -} - -static double spline_fit_error(pt_t *p, int n, spline_t *s, double tolerance) -{ - pts_t *sp = decompose(s, tolerance); - double err = 0; - int i; - for (i = 0; i < n; i++) { - double e = distance_to_segments(&p[i], sp); - err += e * e; + /* Normalize to [0, 1] */ + if (total_len > 0.0) { + for (i = 1; i < n; i++) + t[i] /= total_len; } - dispose_pts(sp); - return err; } +/* Least-squares cubic Bézier curve fitting - O(n) algorithm + * + * Given n points and fixed endpoints (a, d), finds control points (b, c) + * that minimize squared distance to the curve. + * + * Uses chord-length parameterization and normal equations: + * For cubic Bézier: B(t) = (1-t)³a + 3(1-t)²t·b + 3(1-t)t²·c + t³d + * + * Rearranged: p - (1-t)³a - t³d = A(t)·b + B(t)·c + * where A(t) = 3(1-t)²t, B(t) = 3(1-t)t² + * + * Solves 2x2 linear system for b and c using normal equations. + */ spline_t fit(pt_t *p, int n) { spline_t s; - spline_t best_s; - - double tol = 0.5; - double best_err = 10000; - double sbx_min; - double sbx_max; - double sby_min; - double sby_max; - double scx_min; - double scx_max; - double scy_min; - double scy_max; + double *t; + double C[2][2] = {{0, 0}, {0, 0}}; /* Coefficient matrix CᵀC */ + double X[2] = {0, 0}; /* Right-hand side for x coords */ + double Y[2] = {0, 0}; /* Right-hand side for y coords */ + double det; + int i; + /* Fixed endpoints */ s.a = p[0]; s.d = p[n - 1]; - if (s.a.x < s.d.x) { - sbx_min = s.a.x; - sbx_max = s.d.x; + /* Handle degenerate cases */ + if (n < 2) { + s.b = s.a; + s.c = s.d; + return s; + } - scx_max = s.d.x; - scx_min = s.a.x; - } else { - sbx_max = s.a.x; - sbx_min = s.d.x; + /* Allocate parameter array */ + t = malloc(n * sizeof(double)); + if (!t) { + /* Fallback: linear interpolation */ + s.b.x = s.a.x + (s.d.x - s.a.x) / 3.0; + s.b.y = s.a.y + (s.d.y - s.a.y) / 3.0; + s.c.x = s.a.x + 2.0 * (s.d.x - s.a.x) / 3.0; + s.c.y = s.a.y + 2.0 * (s.d.y - s.a.y) / 3.0; + return s; + } - scx_min = s.d.x; - scx_max = s.a.x; + /* Compute parameter values */ + chord_length_parameterize(p, n, t); + + /* Build normal equations: CᵀC and Cᵀrhs */ + for (i = 0; i < n; i++) { + double ti = t[i]; + double ti2 = ti * ti; + double ti3 = ti2 * ti; + double one_minus_t = 1.0 - ti; + double one_minus_t2 = one_minus_t * one_minus_t; + double one_minus_t3 = one_minus_t2 * one_minus_t; + + /* Basis functions for control points b and c */ + double A = 3.0 * one_minus_t2 * ti; /* coefficient for b */ + double B = 3.0 * one_minus_t * ti2; /* coefficient for c */ + + /* Subtract fixed endpoint contributions from points */ + double tmp_x = p[i].x - one_minus_t3 * s.a.x - ti3 * s.d.x; + double tmp_y = p[i].y - one_minus_t3 * s.a.y - ti3 * s.d.y; + + /* Accumulate normal equations */ + C[0][0] += A * A; + C[0][1] += A * B; + C[1][1] += B * B; + + X[0] += A * tmp_x; + X[1] += B * tmp_x; + Y[0] += A * tmp_y; + Y[1] += B * tmp_y; } - if (s.a.y < s.d.y) { - sby_min = s.a.y; - sby_max = s.d.y; + C[1][0] = C[0][1]; /* Matrix is symmetric */ - scy_max = s.d.y; - scy_min = s.a.y; - } else { - sby_max = s.a.y; - sby_min = s.d.y; + free(t); - scy_min = s.d.y; - scy_max = s.a.y; + /* Solve 2x2 system using Cramer's rule */ + det = C[0][0] * C[1][1] - C[0][1] * C[1][0]; + + if (fabs(det) < 1e-10) { + /* Singular matrix: fallback to linear interpolation */ + s.b.x = s.a.x + (s.d.x - s.a.x) / 3.0; + s.b.y = s.a.y + (s.d.y - s.a.y) / 3.0; + s.c.x = s.a.x + 2.0 * (s.d.x - s.a.x) / 3.0; + s.c.y = s.a.y + 2.0 * (s.d.y - s.a.y) / 3.0; + } else { + /* Solve for control points */ + s.b.x = (X[0] * C[1][1] - X[1] * C[0][1]) / det; + s.c.x = (C[0][0] * X[1] - C[1][0] * X[0]) / det; + s.b.y = (Y[0] * C[1][1] - Y[1] * C[0][1]) / det; + s.c.y = (C[0][0] * Y[1] - C[1][0] * Y[0]) / det; } - tol = 0.5; - for (s.b.x = sbx_min; s.b.x <= sbx_max; s.b.x += 1.0) - for (s.b.y = sby_min; s.b.y <= sby_max; s.b.y += 1.0) - for (s.c.x = scx_min; s.c.x <= scx_max; s.c.x += 1.0) - for (s.c.y = scy_min; s.c.y <= scy_max; s.c.y += 1.0) { - double err = spline_fit_error(p, n, &s, tol); - if (err < best_err) { - best_err = err; - best_s = s; - } - } - return best_s; + return s; }