|
@@ -0,0 +1,516 @@
|
|
|
+#include <math.h>
|
|
|
+
|
|
|
+#include <cairo/cairo.h>
|
|
|
+#include <pango/pangocairo.h>
|
|
|
+
|
|
|
+#define NICKNAME_TEXT_SIZE 23
|
|
|
+#define MESSAGE_TEXT_SIZE 24
|
|
|
+
|
|
|
+#define HPADDING 18
|
|
|
+#define VPADDING 8
|
|
|
+
|
|
|
+#define AVATAR_OFFSET 3
|
|
|
+#define AVATAR_PADDING 12
|
|
|
+#define AVATAR_DIM 75
|
|
|
+#define AVATAR_WIDTH (AVATAR_DIM + AVATAR_PADDING)
|
|
|
+
|
|
|
+#define TAIL_WIDTH 18
|
|
|
+#define TAIL_HEIGHT 30
|
|
|
+
|
|
|
+#define MESSAGE_BOX_RADIUS 8
|
|
|
+#define MESSAGE_BOX_MIN 160
|
|
|
+
|
|
|
+#define WRAP_WIDTH 400
|
|
|
+
|
|
|
+#define TAIL_PATH "./resources/tail.png"
|
|
|
+
|
|
|
+#define MAX(x, y) ((x) >= (y)? (x): (y))
|
|
|
+#define MIN(x, y) ((x) <= (y)? (x): (y))
|
|
|
+
|
|
|
+typedef unsigned int uint_t;
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ int r, g, b;
|
|
|
+} color_t;
|
|
|
+
|
|
|
+color_t COLORS_TABLE[] = {
|
|
|
+ /* Nickname colors */
|
|
|
+ {0xee, 0x49, 0x28}, /* 0 */
|
|
|
+ {0x41, 0xa9, 0x03}, /* 1 */
|
|
|
+ {0xe0, 0x96, 0x02}, /* 2 */
|
|
|
+ {0x0f, 0x94, 0xed}, /* 3 */
|
|
|
+ {0x8f, 0x3b, 0xf7}, /* 4 */
|
|
|
+ {0xfc, 0x43, 0x80}, /* 5 */
|
|
|
+ {0x00, 0xa1, 0xc4}, /* 6 */
|
|
|
+ {0xeb, 0x70, 0x02}, /* 7 */
|
|
|
+
|
|
|
+ /* Message text color */
|
|
|
+ {0xff, 0xff, 0xff}, /* 8 */
|
|
|
+
|
|
|
+ /* Message box color */
|
|
|
+ {0x2b, 0x2b, 0x2b} /* 9 */
|
|
|
+};
|
|
|
+
|
|
|
+#define COLOR(i) (&COLORS_TABLE[i])
|
|
|
+
|
|
|
+#define C_MESSAGE_TEXT COLOR(8)
|
|
|
+#define C_MESSAGE_BOX COLOR(9)
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ PangoLayout *layout;
|
|
|
+
|
|
|
+ uint_t w, h;
|
|
|
+} text_t;
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ unsigned char *data;
|
|
|
+ size_t size, allocated;
|
|
|
+} buffer_t;
|
|
|
+
|
|
|
+#define FLAG_BOLD (1 << 0)
|
|
|
+#define FLAG_STRIKE (1 << 1)
|
|
|
+#define FLAG_ITALIC (1 << 2)
|
|
|
+#define FLAG_TT (1 << 3)
|
|
|
+
|
|
|
+typedef struct _node_t {
|
|
|
+ uint_t flags;
|
|
|
+ buffer_t *buffer;
|
|
|
+
|
|
|
+ struct _node_t *next;
|
|
|
+} node_t;
|
|
|
+
|
|
|
+void *malloc_protected(size_t z) {
|
|
|
+ void *p;
|
|
|
+
|
|
|
+ if (!(p = malloc(z))) {
|
|
|
+ fputs("out of memory.\n", stderr);
|
|
|
+
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ return p;
|
|
|
+}
|
|
|
+
|
|
|
+void *realloc_protected(void *old_p, size_t z) {
|
|
|
+ void *p;
|
|
|
+
|
|
|
+ if (!(p = realloc(old_p, z))) {
|
|
|
+ fputs("out of memory.\n", stderr);
|
|
|
+
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ return p;
|
|
|
+}
|
|
|
+
|
|
|
+buffer_t *new_buffer() {
|
|
|
+ buffer_t *buffer;
|
|
|
+
|
|
|
+ buffer = malloc_protected(sizeof(buffer_t));
|
|
|
+
|
|
|
+ buffer->data = NULL;
|
|
|
+ buffer->size = 0;
|
|
|
+ buffer->allocated = 0;
|
|
|
+
|
|
|
+ return buffer;
|
|
|
+}
|
|
|
+
|
|
|
+void free_buffer(buffer_t *buffer) {
|
|
|
+ free(buffer->data);
|
|
|
+ free(buffer);
|
|
|
+}
|
|
|
+
|
|
|
+void buffer_expand(buffer_t *buffer, size_t size) {
|
|
|
+ buffer->allocated += size;
|
|
|
+
|
|
|
+ buffer->data = realloc_protected(buffer->data, buffer->allocated);
|
|
|
+}
|
|
|
+
|
|
|
+void buffer_append_data(buffer_t *buffer, unsigned char *data, size_t data_length) {
|
|
|
+ size_t tail = buffer->size;
|
|
|
+
|
|
|
+ buffer->size += data_length;
|
|
|
+
|
|
|
+ if (buffer->size > buffer->allocated)
|
|
|
+ buffer_expand(buffer, data_length);
|
|
|
+
|
|
|
+ for (size_t i = 0; i < data_length; i++)
|
|
|
+ buffer->data[tail + i] = data[i];
|
|
|
+}
|
|
|
+
|
|
|
+#define BUFFER_APPEND(b, s) buffer_append_data(b, s, strlen(s))
|
|
|
+#define BUFFER_CONCAT(b1, b2) buffer_append_data(b1, (b2)->data, (b2)->size);
|
|
|
+
|
|
|
+node_t *new_node() {
|
|
|
+ node_t *node;
|
|
|
+
|
|
|
+ node = malloc_protected(sizeof(node_t));
|
|
|
+
|
|
|
+ node->flags = 0;
|
|
|
+ node->buffer = new_buffer();
|
|
|
+ node->next = NULL;
|
|
|
+
|
|
|
+ return node;
|
|
|
+}
|
|
|
+
|
|
|
+void free_node(node_t *node) {
|
|
|
+ if (node->next)
|
|
|
+ free_node(node->next);
|
|
|
+
|
|
|
+ free_buffer(node->buffer);
|
|
|
+ free(node);
|
|
|
+}
|
|
|
+
|
|
|
+#define SET(flag) {\
|
|
|
+ if (tail->flags & FLAG_##flag) {\
|
|
|
+ tail = tail->next = new_node();\
|
|
|
+ continue;\
|
|
|
+ }\
|
|
|
+ uint_t flags = tail->flags;\
|
|
|
+ tail = tail->next = new_node();\
|
|
|
+ tail->flags = flags | FLAG_##flag;\
|
|
|
+}
|
|
|
+
|
|
|
+void preprocess_text(buffer_t *buffer, char *text) {
|
|
|
+ uint_t state;
|
|
|
+ node_t *head;
|
|
|
+ node_t *tail;
|
|
|
+
|
|
|
+ head = tail = new_node();
|
|
|
+
|
|
|
+ for (size_t i = 0; i < strlen(text); i++) {
|
|
|
+ unsigned char c, nc;
|
|
|
+
|
|
|
+ c = text[i];
|
|
|
+ nc = text[i+1];
|
|
|
+
|
|
|
+ if (c == '`') {
|
|
|
+ SET(TT);
|
|
|
+ } else if (c == '*' && nc == '*') {
|
|
|
+ i++;
|
|
|
+
|
|
|
+ SET(BOLD);
|
|
|
+ } else if (c == '~' && nc == '~') {
|
|
|
+ i++;
|
|
|
+
|
|
|
+ SET(STRIKE);
|
|
|
+ } else if (c == '_' && nc == '_') {
|
|
|
+ i++;
|
|
|
+
|
|
|
+ SET(ITALIC);
|
|
|
+ } else {
|
|
|
+ buffer_append_data(tail->buffer, &c, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tail->flags) {
|
|
|
+ tail->flags = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ node_t *start = head;
|
|
|
+
|
|
|
+ while (head) {
|
|
|
+ if (head->flags & FLAG_BOLD)
|
|
|
+ BUFFER_APPEND(buffer, "<b>");
|
|
|
+ if (head->flags & FLAG_STRIKE)
|
|
|
+ BUFFER_APPEND(buffer, "<s>");
|
|
|
+ if (head->flags & FLAG_ITALIC)
|
|
|
+ BUFFER_APPEND(buffer, "<i>");
|
|
|
+ if (head->flags & FLAG_TT)
|
|
|
+ BUFFER_APPEND(buffer, "<tt>");
|
|
|
+
|
|
|
+ //printf("%d %d %d\n", head->flags & FLAG_BOLD, head->flags & FLAG_STRIKE, head->flags & FLAG_ITALIC);
|
|
|
+
|
|
|
+ BUFFER_CONCAT(buffer, head->buffer);
|
|
|
+
|
|
|
+ if (head->flags & FLAG_TT)
|
|
|
+ BUFFER_APPEND(buffer, "</tt>");
|
|
|
+ if (head->flags & FLAG_ITALIC)
|
|
|
+ BUFFER_APPEND(buffer, "</i>");
|
|
|
+ if (head->flags & FLAG_STRIKE)
|
|
|
+ BUFFER_APPEND(buffer, "</s>");
|
|
|
+ if (head->flags & FLAG_BOLD)
|
|
|
+ BUFFER_APPEND(buffer, "</b>");
|
|
|
+
|
|
|
+ head = head->next;
|
|
|
+ }
|
|
|
+
|
|
|
+ free_node(start);
|
|
|
+}
|
|
|
+
|
|
|
+text_t *new_text(cairo_t *cr, char *s, int size, int weight, int preprocess) {
|
|
|
+ text_t *text;
|
|
|
+ PangoFontDescription *font_description;
|
|
|
+ buffer_t *buffer;
|
|
|
+
|
|
|
+ text = malloc_protected(sizeof(text_t));
|
|
|
+
|
|
|
+ font_description = pango_font_description_from_string("Apple Color Emoji 16,Sans 16");
|
|
|
+ pango_font_description_set_absolute_size(font_description, size * PANGO_SCALE);
|
|
|
+ pango_font_description_set_weight(font_description, weight);
|
|
|
+
|
|
|
+ text->layout = pango_cairo_create_layout(cr);
|
|
|
+ pango_layout_set_font_description(text->layout, font_description);
|
|
|
+ pango_layout_set_wrap(text->layout, PANGO_WRAP_WORD);
|
|
|
+ pango_layout_set_width(text->layout, WRAP_WIDTH * PANGO_SCALE);
|
|
|
+
|
|
|
+ pango_font_description_free(font_description);
|
|
|
+
|
|
|
+ if (preprocess) {
|
|
|
+ buffer = new_buffer();
|
|
|
+ preprocess_text(buffer, s);
|
|
|
+
|
|
|
+ pango_layout_set_markup(text->layout, buffer->data, buffer->size);
|
|
|
+
|
|
|
+ free_buffer(buffer);
|
|
|
+ } else
|
|
|
+ pango_layout_set_text(text->layout, s, -1);
|
|
|
+
|
|
|
+ pango_layout_get_pixel_size(text->layout, &text->w, &text->h);
|
|
|
+
|
|
|
+ return text;
|
|
|
+}
|
|
|
+
|
|
|
+void free_text(text_t *text) {
|
|
|
+ g_object_unref(text->layout);
|
|
|
+
|
|
|
+ free(text);
|
|
|
+}
|
|
|
+
|
|
|
+/* TO-DO: do this better */
|
|
|
+void get_text_size(char *s, uint_t size, uint_t weight, uint_t *w, uint_t *h, int preprocess) {
|
|
|
+ cairo_surface_t *surface;
|
|
|
+ cairo_t *cr;
|
|
|
+
|
|
|
+ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
|
|
|
+ cr = cairo_create(surface);
|
|
|
+
|
|
|
+ text_t *text = new_text(cr, s, size, weight, preprocess);
|
|
|
+
|
|
|
+ *w = text->w;
|
|
|
+ *h = text->h;
|
|
|
+
|
|
|
+ free_text(text);
|
|
|
+ cairo_destroy(cr);
|
|
|
+ cairo_surface_destroy(surface);
|
|
|
+}
|
|
|
+
|
|
|
+void select_color(cairo_t *cr, color_t *color) {
|
|
|
+ cairo_set_source_rgb(cr, color->r / 255.0F, color->g / 255.0F, color->b / 255.0F);
|
|
|
+}
|
|
|
+
|
|
|
+void paste_clipped_image(cairo_t *cr, uint_t x, uint_t y, char *path) {
|
|
|
+ cairo_surface_t *surface, *surface2;
|
|
|
+ cairo_t *ic;
|
|
|
+
|
|
|
+ surface = cairo_image_surface_create_from_png(path);
|
|
|
+ ic = cairo_create(surface);
|
|
|
+
|
|
|
+ cairo_scale(ic, ((double)AVATAR_DIM) / cairo_image_surface_get_width(surface), ((double)AVATAR_DIM) / cairo_image_surface_get_height(surface));
|
|
|
+ cairo_set_source_surface(ic, surface, 0, 0);
|
|
|
+ cairo_paint(ic);
|
|
|
+
|
|
|
+ cairo_destroy(ic);
|
|
|
+
|
|
|
+ surface2 = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, AVATAR_DIM, AVATAR_DIM);
|
|
|
+ ic = cairo_create(surface2);
|
|
|
+
|
|
|
+ cairo_arc(ic, AVATAR_DIM/2, AVATAR_DIM/2, AVATAR_DIM/2, 0, 2*M_PI);
|
|
|
+ cairo_clip(ic);
|
|
|
+ cairo_new_path(ic);
|
|
|
+
|
|
|
+ cairo_set_source_surface(ic, surface, 0, 0);
|
|
|
+ cairo_paint(ic);
|
|
|
+
|
|
|
+ cairo_set_source_surface(cr, surface2, x, y);
|
|
|
+ cairo_paint(cr);
|
|
|
+
|
|
|
+ cairo_destroy(ic);
|
|
|
+ cairo_surface_destroy(surface);
|
|
|
+ cairo_surface_destroy(surface2);
|
|
|
+}
|
|
|
+
|
|
|
+void rounded_rectangle(cairo_t *cr, uint_t x, uint_t y, uint_t w, uint_t h, uint_t r) {
|
|
|
+ cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
|
|
|
+
|
|
|
+ cairo_new_sub_path(cr);
|
|
|
+ cairo_arc(cr, x + r, y + r, r, M_PI, 3 * M_PI / 2);
|
|
|
+ cairo_arc(cr, x + w - r, y + r, r, 3 *M_PI / 2, 2 * M_PI);
|
|
|
+ cairo_arc(cr, x + w - r, y + h - r, r, 0, M_PI / 2);
|
|
|
+ cairo_arc(cr, x + r, y + h - r, r, M_PI / 2, M_PI);
|
|
|
+ cairo_close_path(cr);
|
|
|
+
|
|
|
+ cairo_fill(cr);
|
|
|
+}
|
|
|
+
|
|
|
+void text(cairo_t *cr, uint_t x, uint_t y, text_t *text) {
|
|
|
+ cairo_move_to(cr, x, y);
|
|
|
+ pango_cairo_update_layout(cr, text->layout);
|
|
|
+ pango_cairo_show_layout(cr, text->layout);
|
|
|
+}
|
|
|
+
|
|
|
+void paste_image(cairo_t *cr, uint_t x, uint_t y, char *path) {
|
|
|
+ cairo_surface_t *surface;
|
|
|
+
|
|
|
+ surface = cairo_image_surface_create_from_png(path);
|
|
|
+ cairo_set_source_surface(cr, surface, x, y);
|
|
|
+ cairo_paint(cr);
|
|
|
+
|
|
|
+ cairo_surface_destroy(surface);
|
|
|
+}
|
|
|
+
|
|
|
+void fit(cairo_surface_t **surface, uint_t width, uint_t height) {
|
|
|
+ uint_t image_width, image_height;
|
|
|
+ uint_t new_width, new_height;
|
|
|
+ double image_ratio, new_ratio;
|
|
|
+
|
|
|
+ cairo_surface_t *surface2;
|
|
|
+ cairo_t *cr;
|
|
|
+
|
|
|
+ image_width = cairo_image_surface_get_width(*surface);
|
|
|
+ image_height = cairo_image_surface_get_height(*surface);
|
|
|
+
|
|
|
+ if (image_width <= width && image_height <= height)
|
|
|
+ return;
|
|
|
+
|
|
|
+ new_width = width;
|
|
|
+ new_height = height;
|
|
|
+
|
|
|
+ image_ratio = ((double)image_width) / ((double)image_height);
|
|
|
+ new_ratio = ((double)width) / ((double)height);
|
|
|
+
|
|
|
+ if (image_ratio > new_ratio)
|
|
|
+ new_height = ((double)image_height) / ((double)image_width) * ((double)width);
|
|
|
+ else
|
|
|
+ new_width = ((double)image_width) / ((double)image_height) * ((double)height);
|
|
|
+
|
|
|
+ surface2 = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, new_width, new_height);
|
|
|
+ cr = cairo_create(surface2);
|
|
|
+
|
|
|
+ cairo_scale(cr, ((double)new_width) / ((double)image_width), ((double)new_height) / ((double)image_height));
|
|
|
+ cairo_set_source_surface(cr, *surface, 0, 0);
|
|
|
+ cairo_paint(cr);
|
|
|
+
|
|
|
+ cairo_destroy(cr);
|
|
|
+ cairo_surface_destroy(*surface);
|
|
|
+
|
|
|
+ *surface = surface2;
|
|
|
+}
|
|
|
+
|
|
|
+void read_uint(uint_t *r) {
|
|
|
+ *r = 0;
|
|
|
+
|
|
|
+ for (uint_t i = 0; i < sizeof(uint_t); i++)
|
|
|
+ *r |= ((uint_t)fgetc(stdin)) << (i*8);
|
|
|
+}
|
|
|
+
|
|
|
+void read_string(buffer_t *buffer) {
|
|
|
+ uint_t length;
|
|
|
+
|
|
|
+ read_uint(&length);
|
|
|
+ buffer_expand(buffer, length);
|
|
|
+
|
|
|
+ buffer->size += fread(buffer->data + buffer->size, 1, length, stdin);
|
|
|
+
|
|
|
+ buffer_append_data(buffer, &"\0", 1);
|
|
|
+}
|
|
|
+
|
|
|
+main() {
|
|
|
+ uint_t avatar_width;
|
|
|
+ uint_t image_width;
|
|
|
+ uint_t image_height;
|
|
|
+
|
|
|
+ uint_t x, y;
|
|
|
+ uint_t w, h;
|
|
|
+
|
|
|
+ cairo_surface_t *surface;
|
|
|
+ cairo_t *cr;
|
|
|
+
|
|
|
+ text_t *nickname;
|
|
|
+ text_t *message;
|
|
|
+
|
|
|
+ buffer_t *output_path;
|
|
|
+ buffer_t *avatar_path;
|
|
|
+ buffer_t *username_text;
|
|
|
+ uint_t username_color;
|
|
|
+ buffer_t *message_text;
|
|
|
+
|
|
|
+ uint_t nickname_w, nickname_h;
|
|
|
+ uint_t message_w, message_h;
|
|
|
+
|
|
|
+ output_path = new_buffer();
|
|
|
+ avatar_path = new_buffer();
|
|
|
+ username_text = new_buffer();
|
|
|
+ message_text = new_buffer();
|
|
|
+
|
|
|
+ read_string(output_path);
|
|
|
+ read_string(avatar_path);
|
|
|
+ read_string(username_text);
|
|
|
+ read_uint(&username_color);
|
|
|
+ read_string(message_text);
|
|
|
+
|
|
|
+ username_color = MIN(username_color, 6);
|
|
|
+
|
|
|
+ get_text_size(username_text->data, NICKNAME_TEXT_SIZE, PANGO_WEIGHT_BOLD, &nickname_w, &nickname_h, 0);
|
|
|
+ get_text_size(message_text->data, MESSAGE_TEXT_SIZE, PANGO_WEIGHT_NORMAL, &message_w, &message_h, 1);
|
|
|
+
|
|
|
+ image_width = AVATAR_WIDTH + MAX(message_w, nickname_w) + HPADDING*2;
|
|
|
+ image_height = message_h + nickname_h + VPADDING*2;
|
|
|
+
|
|
|
+ if (image_width - AVATAR_WIDTH < MESSAGE_BOX_MIN)
|
|
|
+ image_width = AVATAR_WIDTH + MESSAGE_BOX_MIN;
|
|
|
+
|
|
|
+ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, image_width, image_height+AVATAR_OFFSET);
|
|
|
+ cr = cairo_create(surface);
|
|
|
+
|
|
|
+ nickname = new_text(cr, username_text->data, NICKNAME_TEXT_SIZE, PANGO_WEIGHT_BOLD, 0);
|
|
|
+ message = new_text(cr, message_text->data, MESSAGE_TEXT_SIZE, PANGO_WEIGHT_NORMAL, 1);
|
|
|
+
|
|
|
+ free_buffer(output_path);
|
|
|
+ free_buffer(avatar_path);
|
|
|
+ free_buffer(username_text);
|
|
|
+ free_buffer(message_text);
|
|
|
+
|
|
|
+ x = 0;
|
|
|
+ y = image_height - AVATAR_DIM + AVATAR_OFFSET;
|
|
|
+
|
|
|
+ paste_clipped_image(cr, x, y, avatar_path->data);
|
|
|
+
|
|
|
+ x = AVATAR_WIDTH;
|
|
|
+ y = 0;
|
|
|
+ w = MAX(message->w, nickname->w) + HPADDING*2;
|
|
|
+ h = message->h + nickname->h + VPADDING*2;
|
|
|
+
|
|
|
+ if (w < MESSAGE_BOX_MIN)
|
|
|
+ w = MESSAGE_BOX_MIN;
|
|
|
+
|
|
|
+ select_color(cr, C_MESSAGE_BOX);
|
|
|
+ rounded_rectangle(cr, x, y, w, h, MESSAGE_BOX_RADIUS);
|
|
|
+
|
|
|
+ x = AVATAR_WIDTH - TAIL_WIDTH + 9;
|
|
|
+ y = image_height - TAIL_HEIGHT;
|
|
|
+
|
|
|
+ paste_image(cr, x, y, TAIL_PATH);
|
|
|
+
|
|
|
+ x = AVATAR_WIDTH + HPADDING;
|
|
|
+ y = VPADDING / 2;
|
|
|
+
|
|
|
+ select_color(cr, COLOR(username_color));
|
|
|
+ text(cr, x, y, nickname);
|
|
|
+
|
|
|
+ x = AVATAR_WIDTH + HPADDING;
|
|
|
+ y = nickname->h + VPADDING - 3;
|
|
|
+
|
|
|
+ select_color(cr, C_MESSAGE_TEXT);
|
|
|
+ text(cr, x, y, message);
|
|
|
+
|
|
|
+ cairo_destroy(cr);
|
|
|
+
|
|
|
+ fit(&surface, 512, 512);
|
|
|
+ cairo_surface_write_to_png(surface, output_path->data);
|
|
|
+
|
|
|
+ cairo_surface_destroy(surface);
|
|
|
+
|
|
|
+ free_text(nickname);
|
|
|
+ free_text(message);
|
|
|
+}
|