diff options
| author | Mattias Andrée <m@maandree.se> | 2026-06-07 10:42:40 +0200 |
|---|---|---|
| committer | Mattias Andrée <m@maandree.se> | 2026-06-07 10:42:40 +0200 |
| commit | cf89c3f538a5c3867ff3ae84c26dea87e54d615e (patch) | |
| tree | fb0b3efbbdcaa9393d2ab6395f2d27568a0ffe0e | |
| parent | style improvement + check mem alloc failure (diff) | |
| download | gcmap-cf89c3f538a5c3867ff3ae84c26dea87e54d615e.tar.gz gcmap-cf89c3f538a5c3867ff3ae84c26dea87e54d615e.tar.bz2 gcmap-cf89c3f538a5c3867ff3ae84c26dea87e54d615e.tar.xz | |
Implement basic character table
Signed-off-by: Mattias Andrée <m@maandree.se>
| -rw-r--r-- | Makefile | 3 | ||||
| -rw-r--r-- | char-table.c | 235 | ||||
| -rw-r--r-- | char-table.h | 3 | ||||
| -rw-r--r-- | colour.c | 47 | ||||
| -rw-r--r-- | common.h | 12 | ||||
| -rw-r--r-- | config.mk | 2 |
6 files changed, 285 insertions, 17 deletions
@@ -5,7 +5,8 @@ include $(CONFIGFILE) OBJ =\ gcmap.o\ - char-table.o + char-table.o\ + colour.o HDR =\ common.h\ diff --git a/char-table.c b/char-table.c index da82544..076ef8f 100644 --- a/char-table.c +++ b/char-table.c @@ -1,39 +1,244 @@ /* See LICENSE file for copyright and license details. */ #include "common.h" +#define FIRST_CHARACTER 0x0000u +#define LAST_CHARACTER 0xFFFFu +#define REFERENCE_CHARACTER "X" +#define CELL_WIDTH_FACTOR 4 +#define CELL_HEIGHT_FACTOR 2 +#define CELL_X_CLEARANCE 2 +#define CELL_Y_CLEARANCE 2 +#define BASE_FONT_FAMILY "Sans" +#define BASE_FONT_SIZE 22 + G_DEFINE_TYPE(CharTable, gcmap_char_table, GTK_TYPE_DRAWING_AREA) -static gboolean +static int +is_noncharacter(gunichar ch) +{ + return (0xFDD0u <= ch && ch <= 0xFDEFu) || (ch & 0xFFFEu) == 0xFFFEu; +} + + +static int +is_printable_character(gunichar ch) +{ + return g_unichar_validate(ch) && + !g_unichar_iscntrl(ch) && + !g_unichar_iszerowidth(ch) && + !is_noncharacter(ch); +} + + +static void +format_character(gunichar ch, char *buf) +{ + if (is_printable_character(ch)) + buf[g_unichar_to_utf8(ch, buf)] = '\0'; +#ifdef TODO /* this part is just for testing */ + else if (ch == 0x7Fu) + stpcpy(buf, "DELETE"); + else + stpcpy(buf, "X"); +#else + else + buf[0u] = '\0'; +#endif +} + + +static void +apply_font_size(CharTable *this, int size, int *text_w_out, int *text_h_out) +{ + pango_font_description_set_size(this->font, size * PANGO_SCALE); + pango_layout_set_font_description(this->layout, this->font); + pango_layout_context_changed(this->layout); + pango_layout_get_pixel_size(this->layout, text_w_out, text_h_out); +} + + +static void +fit_text(CharTable *this, const char *text, int *text_w_out, int *text_h_out) +{ + int size, ok_size = 1; + double scale_x, scale_y, scale; + + /* Set text to measure */ + pango_layout_set_text(this->layout, text, -1); + + /* Try default size */ + apply_font_size(this, BASE_FONT_SIZE, text_w_out, text_h_out); + if (*text_w_out <= this->max_w && *text_h_out <= this->max_h) + return; + + /* Too large? Then estimate largest smaller size that would fit */ + scale_x = (double)this->max_w / (double)*text_w_out; + scale_y = (double)this->max_h / (double)*text_h_out; + scale = MIN(scale_x, scale_y); + scale = MIN(scale, 1) * BASE_FONT_SIZE; + size = (int)scale; + size = MAX(size, 1); + + /* Check and decrease by one until it fits */ + for (; size > 1; size--) { + apply_font_size(this, size, text_w_out, text_h_out); + if (*text_w_out <= this->max_w && *text_h_out <= this->max_h) { + ok_size = size; + break; + } + } + + /* Maybe it was too small, then try larger */ + if (*text_w_out < this->max_w && *text_h_out < this->max_h) { + goto start; + do { + ok_size++; + start: + if (ok_size == BASE_FONT_SIZE) + break; + apply_font_size(this, ok_size + 1, text_w_out, text_h_out); + } while (*text_w_out < this->max_w && *text_h_out < this->max_h); + } + + /* Use the on that fit, 1 if none fit */ + if (ok_size != size) + apply_font_size(this, ok_size, text_w_out, text_h_out); +} + + +static int gcmap_char_table_expose(GtkWidget *widget, GdkEventExpose *event) { - cairo_t *cr = gdk_cairo_create(widget->window); + CharTable *this = (CharTable *)widget; + GdkRectangle area = event->area; + int r0 = MAX(area.y * this->nrows / widget->allocation.height, 0); + int c0 = MAX(area.x * this->ncols / widget->allocation.width, 0); + int r1 = MIN((area.y + area.height - 1) * this->nrows / widget->allocation.height + 1, this->nrows); + int c1 = MIN((area.x + area.width - 1) * this->ncols / widget->allocation.width + 1, this->ncols); + GtkStyle *style = gtk_widget_get_style(widget); + cairo_t *g; + char text[16]; + int r, c, x0, x1, y0, y1; + int text_w, text_h; + size_t index, index1; + gunichar ch; + + g = gdk_cairo_create(widget->window); + if (!g) + eprintf("gdk_cairo_create:"); + + /* Configure drawing area */ + gdk_cairo_rectangle(g, &area); + cairo_clip(g); + + /* Draw background */ + set_source_colour(g, &style->base[GTK_STATE_NORMAL]); + cairo_paint(g); - (void) event; + /* Draw grid lines */ + set_source_colour_blend(g, &style->text[GTK_STATE_NORMAL], 0.5, &style->base[GTK_STATE_NORMAL]); + cairo_set_line_width(g, 1.0); + /* horizontal */ + y0 = widget->allocation.height * (r0 + 1) / this->nrows; + x0 = widget->allocation.width * c0 / this->ncols; + x1 = widget->allocation.width * c1 / this->ncols; + for (r = r0 + 1; r < r1; r++, y0 = y1) { + y1 = widget->allocation.height * (r + 1) / this->nrows; + cairo_move_to(g, x0, y0 + 0.5); + cairo_line_to(g, x1, y0 + 0.5); + cairo_stroke(g); + } + /* vertical */ + x0 = widget->allocation.width * (c0 + 1) / this->ncols; + y0 = widget->allocation.height * r0 / this->nrows; + y1 = widget->allocation.height * r1 / this->nrows; + for (c = c0 + 1; c < c1; c++, x0 = x1) { + x1 = widget->allocation.width * (c + 1) / this->ncols; + cairo_move_to(g, x0 + 0.5, y0); + cairo_line_to(g, x0 + 0.5, y1); + cairo_stroke(g); + } - cairo_set_source_rgb(cr, 0.2f, 0.2f, 0.2f); - cairo_paint(cr); + /* Draw gryphs */ + set_source_colour(g, &style->text[GTK_STATE_NORMAL]); + index = (size_t)r0 * (size_t)this->ncols + (size_t)c0; + y0 = widget->allocation.height * r0 / this->nrows; + for (r = r0; r < r1; r++, y0 = y1, index = index1) { + index1 = index + (size_t)this->ncols; + x0 = widget->allocation.width * c0 / this->ncols; + y1 = widget->allocation.height * (r + 1) / this->nrows; + for (c = c0; c < c1; c++, index++, x0 = x1) { + ch = FIRST_CHARACTER + (gunichar)index; + if (ch > LAST_CHARACTER) + goto out; + x1 = widget->allocation.width * (c + 1) / this->ncols; - cairo_set_source_rgb(cr, 1.0f, 1.0f, 1.0f); - cairo_move_to(cr, 10, 20); - cairo_show_text(cr, "Hello GTK+ 2"); + format_character(ch, text); + fit_text(this, text, &text_w, &text_h); + cairo_move_to(g, (x0 + x1 - text_w) / 2, (y0 + y1 - text_h) / 2); + pango_cairo_show_layout(g, this->layout); + } + } - cairo_destroy(cr); - return FALSE; +out: + cairo_destroy(g); + return 0; } + static void -gcmap_char_table_init(CharTable *self) +gcmap_char_table_size_allocate(GtkWidget *widget, GtkAllocation *allocation) { - gtk_widget_set_size_request(GTK_WIDGET(self), 200, 100); + CharTable *this = (CharTable *)widget; + int text_w, text_h; + int cell_w, cell_h; + + GTK_WIDGET_CLASS(gcmap_char_table_parent_class)->size_allocate(widget, allocation); + + pango_layout_set_text(this->layout, REFERENCE_CHARACTER, -1); + apply_font_size(this, BASE_FONT_SIZE, &text_w, &text_h); + cell_w = MAX(text_w, 1) * CELL_WIDTH_FACTOR; + cell_h = MAX(text_h, 1) * CELL_HEIGHT_FACTOR; + this->ncols = MAX(widget->allocation.width / cell_w, 1); + this->nrows = MAX(widget->allocation.height / cell_h, 1); + cell_w = widget->allocation.width / this->ncols - 1; + cell_h = widget->allocation.height / this->nrows - 1; + this->max_w = cell_w - CELL_X_CLEARANCE * 2; + this->max_h = cell_h - CELL_Y_CLEARANCE * 2; + this->max_w = this->max_w < 1 ? cell_w : this->max_w; + this->max_h = this->max_h < 1 ? cell_h : this->max_h; +} + + +static void +gcmap_char_table_init(CharTable *this) +{ + this->nrows = 1; /* will be set by gcmap_char_table_size_allocate */ + this->ncols = 1; /* will be set by gcmap_char_table_size_allocate */ + + this->font = pango_font_description_new(); + if (!this->font) + eprintf("pango_font_description_new:"); + pango_font_description_set_family(this->font, BASE_FONT_FAMILY); + pango_font_description_set_size(this->font, BASE_FONT_SIZE * PANGO_SCALE); + pango_font_description_set_weight(this->font, PANGO_WEIGHT_NORMAL); + + this->layout = gtk_widget_create_pango_layout((GtkWidget *)this, ""); + if (!this->layout) + eprintf("gtk_widget_create_pango_layout:"); + + /* TODO on tear down: pango_font_description_free(this->font); */ + /* TODO on tear down: g_object_unref(this->layout); */ } static void -gcmap_char_table_class_init(CharTableClass *klass) +gcmap_char_table_class_init(CharTableClass *class) { - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); - widget_class->expose_event = gcmap_char_table_expose; + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(class); + widget_class->expose_event = &gcmap_char_table_expose; + widget_class->size_allocate = &gcmap_char_table_size_allocate; } diff --git a/char-table.h b/char-table.h index 0097e05..87d65d4 100644 --- a/char-table.h +++ b/char-table.h @@ -12,6 +12,9 @@ typedef struct _CharTable { GtkDrawingArea parent_instance; + int nrows, ncols, max_w, max_h; + PangoFontDescription *font; + PangoLayout *layout; } CharTable; typedef struct _CharTableClass { diff --git a/colour.c b/colour.c new file mode 100644 index 0000000..8b5c401 --- /dev/null +++ b/colour.c @@ -0,0 +1,47 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +double +srgb_encode_unsigned(double t) +{ + return t <= 0.0031306684425217108 + ? 12.92 * t + : fma(1.055, pow(t, 1 / 2.4), -0.055); +} + +double +srgb_decode_unsigned(double t) +{ + return t <= 0.0031306684425217108 * 12.92 + ? t / 12.92 + : pow((t + 0.055) / 1.055, 2.4); +} + + +double +srgb_blend(double at0, double t, double at1) +{ + at0 = srgb_decode_unsigned(at0); + at1 = srgb_decode_unsigned(at1); + return srgb_encode_unsigned(fma((1 - t), at0, t * at1)); +} + + +void +set_source_colour_blend(cairo_t *g, const GdkColor *at0, double t, const GdkColor *at1) +{ + double max = (double)UINT16_MAX; + double red = srgb_blend(at0->red / max, t, at1->red / max); + double green = srgb_blend(at0->green / max, t, at1->green / max); + double blue = srgb_blend(at0->blue / max, t, at1->blue / max); + cairo_set_source_rgb(g, red, green, blue); +} + + +void +set_source_colour(cairo_t *g, const GdkColor *colour) +{ + double max = (double)UINT16_MAX; + cairo_set_source_rgb(g, colour->red / max, colour->green / max, colour->blue / max); +} @@ -2,6 +2,10 @@ #ifndef COMMON_H_ #define COMMON_H_ +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunsuffixed-float-constants" +#endif + #include <libsimple.h> #include <libsimple-arg.h> #include <libcmap.h> @@ -15,6 +19,7 @@ #if defined(__GNUC__) # pragma GCC diagnostic pop #endif +#include <math.h> #if GTK_CHECK_VERSION(2, 12, 0) # define HAVE_ITEM_TOOLTIPS @@ -24,4 +29,11 @@ #include "char-table.h" +/* colour.c */ +double srgb_encode_unsigned(double t); +double srgb_decode_unsigned(double t); +double srgb_blend(double at0, double t, double at1); +void set_source_colour_blend(cairo_t *g, const GdkColor *at0, double t, const GdkColor *at1); +void set_source_colour(cairo_t *g, const GdkColor *colour); + #endif @@ -5,7 +5,7 @@ CC = c99 CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE CFLAGS = $$(pkg-config --cflags pango) -LDFLAGS = -lsimple -lcmap $$(pkg-config --libs pango) +LDFLAGS = -lm -lsimple -lcmap $$(pkg-config --libs pango) CFLAGS_GTK2 = $$(pkg-config --cflags gtk+-2.0) LDFLAGS_GTK2 = $$(pkg-config --libs gtk+-2.0) |
