From 7fafa994a42e3d8455afbd8d6ba39047b7c1f13d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Br=C3=BCschweiler?= Date: Thu, 6 Sep 2012 18:54:02 +0200 Subject: [PATCH] cursor: add cursor.pcf and extraction program --- cursor/convert_font.c | 531 ++++++++++++++++++++++++++++++++++++++++++ cursor/cursor.pcf | Bin 0 -> 14208 bytes 2 files changed, 531 insertions(+) create mode 100644 cursor/convert_font.c create mode 100644 cursor/cursor.pcf diff --git a/cursor/convert_font.c b/cursor/convert_font.c new file mode 100644 index 0000000..f297125 --- /dev/null +++ b/cursor/convert_font.c @@ -0,0 +1,531 @@ +/* + * Copyright © 2012 Philipp Brüschweiler + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +/* + * This is a small, hacky tool to extract cursors from a .pcf file. + * The information about the file format has been gathered from + * http://fontforge.org/pcf-format.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define max(a, b) ((a) > (b) ? (a) : (b)) + +struct glyph { + char *name; + int16_t left_bearing, right_bearing, ascent, descent; + + int16_t width, height; + int16_t hotx, hoty; + + int32_t data_format; + char *data; +}; + +static struct { + int count; + struct glyph *glyphs; +} extracted_font = {0, NULL}; + +#define PCF_PROPERTIES (1<<0) +#define PCF_ACCELERATORS (1<<1) +#define PCF_METRICS (1<<2) +#define PCF_BITMAPS (1<<3) +#define PCF_INK_METRICS (1<<4) +#define PCF_BDF_ENCODINGS (1<<5) +#define PCF_SWIDTHS (1<<6) +#define PCF_GLYPH_NAMES (1<<7) +#define PCF_BDF_ACCELERATORS (1<<8) + +#define PCF_DEFAULT_FORMAT 0x00000000 +#define PCF_INKBOUNDS 0x00000200 +#define PCF_ACCEL_W_INKBOUNDS 0x00000100 +#define PCF_COMPRESSED_METRICS 0x00000100 + +#define PCF_FORMAT_MASK 0xffffff00 + +struct pcf_header { + char header[4]; + int32_t table_count; + struct toc_entry { + int32_t type; + int32_t format; + int32_t size; + int32_t offset; + } tables[0]; +}; + +struct compressed_metrics { + uint8_t left_sided_bearing; + uint8_t right_side_bearing; + uint8_t character_width; + uint8_t character_ascent; + uint8_t character_descent; +}; + +struct uncompressed_metrics { + int16_t left_sided_bearing; + int16_t right_side_bearing; + int16_t character_width; + int16_t character_ascent; + int16_t character_descent; + uint16_t character_attributes; +}; + +struct metrics { + int32_t format; + union { + struct { + int16_t count; + struct compressed_metrics compressed_metrics[0]; + } compressed; + struct { + int32_t count; + struct uncompressed_metrics uncompressed_metrics[0]; + } uncompressed; + }; +}; + +struct glyph_names { + int32_t format; + int32_t glyph_count; + int32_t offsets[0]; +}; + +struct bitmaps { + int32_t format; + int32_t glyph_count; + int32_t offsets[0]; +}; + +static void +handle_compressed_metrics(int32_t count, struct compressed_metrics *m) +{ + printf("metrics count: %d\n", count); + extracted_font.count = count; + extracted_font.glyphs = calloc(count, sizeof(struct glyph)); + + int i; + for (i = 0; i < count; ++i) { + struct glyph *glyph = &extracted_font.glyphs[i]; + glyph->left_bearing = + ((int16_t) m[i].left_sided_bearing) - 0x80; + glyph->right_bearing = + ((int16_t) m[i].right_side_bearing) - 0x80; + glyph->width = ((int16_t) m[i].character_width) - 0x80; + glyph->ascent = ((int16_t) m[i].character_ascent) - 0x80; + glyph->descent = ((int16_t) m[i].character_descent) - 0x80; + + /* computed stuff */ + glyph->height = glyph->ascent + glyph->descent; + + glyph->hotx = -glyph->left_bearing; + glyph->hoty = glyph->ascent; + } +} + +static void +handle_metrics(void *metricbuf) +{ + struct metrics *metrics = metricbuf; + printf("metric format: %x\n", metrics->format); + + if ((metrics->format & PCF_FORMAT_MASK) == PCF_DEFAULT_FORMAT) { + printf("todo...\n"); + } else if ((metrics->format & PCF_FORMAT_MASK) == + PCF_COMPRESSED_METRICS) { + handle_compressed_metrics( + metrics->compressed.count, + &metrics->compressed.compressed_metrics[0]); + } else { + printf("incompatible format\n"); + abort(); + } +} + +static void +handle_glyph_names(struct glyph_names *names) +{ + printf("glyph count %d\n", names->glyph_count); + + if (names->glyph_count != extracted_font.count) { + abort(); + } + + printf("glyph names format %x\n", names->format); + + void *names_start = ((void*) names) + sizeof(struct glyph_names) + + (names->glyph_count + 1) * sizeof(int32_t); + + int i; + for (i = 0; i < names->glyph_count; ++i) { + int32_t start = names->offsets[i]; + int32_t end = names->offsets[i+1]; + char *name = names_start + start; + extracted_font.glyphs[i].name = calloc(1, end - start + 1); + memcpy(extracted_font.glyphs[i].name, name, end - start); + } +} + +static void +handle_bitmaps(struct bitmaps *bitmaps) +{ + printf("bitmaps count %d\n", bitmaps->glyph_count); + + if (bitmaps->glyph_count != extracted_font.count) { + abort(); + } + + printf("format %x\n", bitmaps->format); + + if (bitmaps->format != 2) { + printf("format not yet supported\n"); + abort(); + } + + void *bitmaps_start = ((void*) bitmaps) + sizeof(struct bitmaps) + + (bitmaps->glyph_count + 4) * sizeof(int32_t); + + int i; + for (i = 0; i < bitmaps->glyph_count; ++i) { + int32_t offset = bitmaps->offsets[i]; + struct glyph *glyph = &extracted_font.glyphs[i]; + glyph->data_format = bitmaps->format; + + glyph->data = bitmaps_start + offset; + } +} + +static void +handle_pcf(void *fontbuf) +{ + struct pcf_header *header = fontbuf; + printf("tablecount %d\n", header->table_count); + + int i; + for (i = 0; i < header->table_count; ++i) { + struct toc_entry *entry = &header->tables[i]; + printf("type: %d\n", entry->type); + if (entry->type == PCF_METRICS) { + handle_metrics(fontbuf + entry->offset); + } else if (entry->type == PCF_GLYPH_NAMES) { + handle_glyph_names(fontbuf + entry->offset); + } else if (entry->type == PCF_BITMAPS) { + handle_bitmaps(fontbuf + entry->offset); + } + } +} + +static char +get_glyph_pixel(struct glyph *glyph, int x, int y) +{ + int absx = glyph->hotx + x; + int absy = glyph->hoty + y; + + if (absx < 0 || absx >= glyph->width || + absy < 0 || absy >= glyph->height) + return 0; + + int stride = (glyph->width + 31) / 32 * 4; + unsigned char block = glyph->data[absy * stride + (absx/8)]; + int idx = absx % 8; + return (block >> idx) & 1; +} + +static struct { + uint32_t *data; + size_t capacity, size; +} data_buffer; + +static void +init_data_buffer() +{ + data_buffer.data = malloc(sizeof(uint32_t) * 10); + data_buffer.capacity = 10; + data_buffer.size = 0; +} + +static void +add_pixel(uint32_t pixel) +{ + if (data_buffer.size == data_buffer.capacity) { + data_buffer.capacity *= 2; + data_buffer.data = + realloc(data_buffer.data, + sizeof(uint32_t) * data_buffer.capacity); + } + data_buffer.data[data_buffer.size++] = pixel; +} + +struct reconstructed_glyph { + int32_t width, height; + int32_t hotspot_x, hotspot_y; + size_t offset; + char *name; +}; + +static void +reconstruct_glyph(struct glyph *cursor, struct glyph *mask, char *name, + struct reconstructed_glyph *glyph) +{ + int minx = min(-cursor->hotx, -mask->hotx); + int maxx = max(cursor->right_bearing, mask->right_bearing); + + int miny = min(-cursor->hoty, -mask->hoty); + int maxy = max(cursor->height - cursor->hoty, + mask->height - mask->hoty); + + int width = maxx - minx; + int height = maxy - miny; + + glyph->name = strdup(name); + glyph->width = width; + glyph->height = height; + glyph->hotspot_x = -minx; + glyph->hotspot_y = -miny; + glyph->offset = data_buffer.size; + + int x, y; + for (y = miny; y < maxy; ++y) { + for (x = minx; x < maxx; ++x) { + char alpha = get_glyph_pixel(mask, x, y); + if (alpha) { + char color = get_glyph_pixel(cursor, x, y); + if (color) + add_pixel(0xff000000); + else + add_pixel(0xffffffff); + } else { + add_pixel(0); + } + } + } +} + +/* From http://cgit.freedesktop.org/xorg/lib/libXfont/tree/src/builtins/fonts.c */ +static const char cursor_licence[] = + "/*\n" + "* Copyright 1999 SuSE, Inc.\n" + "*\n" + "* Permission to use, copy, modify, distribute, and sell this software and its\n" + "* documentation for any purpose is hereby granted without fee, provided that\n" + "* the above copyright notice appear in all copies and that both that\n" + "* copyright notice and this permission notice appear in supporting\n" + "* documentation, and that the name of SuSE not be used in advertising or\n" + "* publicity pertaining to distribution of the software without specific,\n" + "* written prior permission. SuSE makes no representations about the\n" + "* suitability of this software for any purpose. It is provided \"as is\"\n" + "* without express or implied warranty.\n" + "*\n" + "* SuSE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL\n" + "* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL SuSE\n" + "* BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n" + "* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION\n" + "* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN\n" + "* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n" + "*\n" + "* Author: Keith Packard, SuSE, Inc.\n" + "*/\n"; + +static void +write_output_file(struct reconstructed_glyph *glyphs, int n) +{ + int i, j, counter, size; + FILE *file = fopen("cursor_data.c", "w"); + uint32_t *data; + + fprintf(file, "%s\n", cursor_licence); + + fprintf(file, "static uint32_t cursor_data[] = {\n\t"); + + counter = 0; + for (i = 0; i < n; ++i) { + data = data_buffer.data + glyphs[i].offset; + size = glyphs[i].width * glyphs[i].height; + + for (j = 0; j < size; ++j) { + fprintf(file, "0x%08x, ", data[j]); + if (++counter % 6 == 0) + fprintf(file, "\n\t"); + } + } + fprintf(file, "\n};\n\n"); + + fprintf(file, + "static struct {\n" + "\tchar *name;\n" + "\tint width, height;\n" + "\tint hotspot_x, hotspot_y;\n" + "\tsize_t offset;\n" + "} cursor_metadata[] = {\n"); + + for (i = 0; i < n; ++i) + fprintf(file, "\t{ \"%s\", %d, %d, %d, %d, %zu },\n", + glyphs[i].name, + glyphs[i].width, glyphs[i].height, + glyphs[i].hotspot_x, glyphs[i].hotspot_y, + glyphs[i].offset); + + fprintf(file, "};"); + + fclose(file); +} + +struct glyph * +find_mask_glyph(char *name) +{ + const char mask[] = "_mask"; + const int masklen = strlen(mask); + + int len = strlen(name); + int i; + for (i = 0; i < extracted_font.count; ++i) { + struct glyph *g = &extracted_font.glyphs[i]; + int l2 = strlen(g->name); + if ((l2 == len + masklen) && + (memcmp(g->name, name, len) == 0) && + (memcmp(g->name + len, mask, masklen) == 0)) { + return g; + } + } + return NULL; +} + +static void +output_all_cursors() +{ + int i, j; + struct reconstructed_glyph *glyphs = + malloc(sizeof(struct reconstructed_glyph) * + extracted_font.count/2); + j = 0; + + for (i = 0; i < extracted_font.count; ++i) { + struct glyph *g = &extracted_font.glyphs[i]; + if (strstr(g->name, "_mask")) + continue; + + struct glyph *mask = find_mask_glyph(g->name); + + reconstruct_glyph(g, mask, g->name, &glyphs[j]); + j++; + } + + write_output_file(glyphs, extracted_font.count/2); +} + +static void +find_cursor_and_mask(const char *name, + struct glyph **cursor, + struct glyph **mask) +{ + int i; + char mask_name[100]; + sprintf(mask_name, "%s_mask", name); + + *cursor = *mask = NULL; + + for (i = 0; i < extracted_font.count && (!*mask || !*cursor); ++i) { + struct glyph *g = &extracted_font.glyphs[i]; + if (!strcmp(name, g->name)) + *cursor = g; + else if (!strcmp(mask_name, g->name)) + *mask = g; + } +} + +static struct { + char *target_name, *source_name; +} interesting_cursors[] = { + { "bottom_left_corner", "bottom_left_corner" }, + { "bottom_right_corner", "bottom_right_corner" }, + { "bottom_side", "bottom_side" }, + { "grabbing", "fleur" }, + { "left_ptr", "left_ptr" }, + { "left_side", "left_side" }, + { "right_side", "right_side" }, + { "top_left_corner", "top_left_corner" }, + { "top_right_corner", "top_right_corner" }, + { "top_side", "top_side" }, + { "xterm", "xterm" }, + { "hand1", "hand1" }, + { "watch", "watch" } +}; + +static void +output_interesting_cursors() +{ + int i; + int n = sizeof(interesting_cursors) / sizeof(interesting_cursors[0]); + struct reconstructed_glyph *glyphs = + malloc(n * sizeof(*glyphs)); + + for (i = 0; i < n; ++i) { + struct glyph *cursor, *mask; + find_cursor_and_mask(interesting_cursors[i].source_name, + &cursor, &mask); + if (!cursor) { + printf("no cursor for %s\n", + interesting_cursors[i].source_name); + abort(); + } + if (!mask) { + printf("no mask for %s\n", + interesting_cursors[i].source_name); + abort(); + } + reconstruct_glyph(cursor, mask, + interesting_cursors[i].target_name, + &glyphs[i]); + } + + write_output_file(glyphs, n); +} + +int main() +{ + const char filename[] = "cursor.pcf"; + + int fd = open(filename, O_RDONLY); + struct stat filestat; + + fstat(fd, &filestat); + + void *fontbuf = mmap(NULL, filestat.st_size, PROT_READ, + MAP_PRIVATE, fd, 0); + + handle_pcf(fontbuf); + + init_data_buffer(); + + //output_all_cursors(); + output_interesting_cursors(); +} diff --git a/cursor/cursor.pcf b/cursor/cursor.pcf new file mode 100644 index 0000000000000000000000000000000000000000..812fcc54fdf1ad92f7016e69fe6c2dd9f7437b62 GIT binary patch literal 14208 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 literal 0 HcmV?d00001