aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2026-06-07 10:42:40 +0200
committerMattias Andrée <m@maandree.se>2026-06-07 10:42:40 +0200
commitcf89c3f538a5c3867ff3ae84c26dea87e54d615e (patch)
treefb0b3efbbdcaa9393d2ab6395f2d27568a0ffe0e
parentstyle improvement + check mem alloc failure (diff)
downloadgcmap-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--Makefile3
-rw-r--r--char-table.c235
-rw-r--r--char-table.h3
-rw-r--r--colour.c47
-rw-r--r--common.h12
-rw-r--r--config.mk2
6 files changed, 285 insertions, 17 deletions
diff --git a/Makefile b/Makefile
index 5eb2cab..0725b98 100644
--- a/Makefile
+++ b/Makefile
@@ -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);
+}
diff --git a/common.h b/common.h
index a7cd690..a30d4c7 100644
--- a/common.h
+++ b/common.h
@@ -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
diff --git a/config.mk b/config.mk
index ba12191..578c9b5 100644
--- a/config.mk
+++ b/config.mk
@@ -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)