summaryrefslogtreecommitdiff
path: root/src/pencil_save.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pencil_save.c')
-rw-r--r--src/pencil_save.c533
1 files changed, 533 insertions, 0 deletions
diff --git a/src/pencil_save.c b/src/pencil_save.c
new file mode 100644
index 0000000..f5c23fb
--- /dev/null
+++ b/src/pencil_save.c
@@ -0,0 +1,533 @@
+/*
+ * This file is part of Pencil
+ * Licensed under the MIT License,
+ * http://www.opensource.org/licenses/mit-license
+ * Copyright 2005 James Bursa <james@semichrome.net>
+ */
+
+/** \file
+ * Saving as a DrawFile (implementation).
+ *
+ * Two passes over the diagram tree are made. The first pass computes the size
+ * that will be required and enumerates the fonts. The second pass creates the
+ * DrawFile in a buffer.
+ */
+
+#define _GNU_SOURCE /* for strndup */
+#include <assert.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <oslib/drawfile.h>
+#include <oslib/osspriteop.h>
+#include <rufl.h>
+#include "pencil_internal.h"
+
+/* Maximum grouping depth (too deep crashes Draw). */
+#define MAX_DEPTH 10
+
+
+struct pencil_save_context {
+ pencil_code code;
+ struct pencil_diagram *diagram;
+ size_t size;
+ char **font_list;
+ unsigned int font_count;
+ struct pencil_item *item;
+ void *buffer;
+ void *b;
+ os_box bbox;
+};
+
+
+static void pencil_save_pass1(struct pencil_save_context *context,
+ struct pencil_item *item, unsigned int depth);
+static void pencil_save_pass1_text_callback(void *c,
+ const char *font_name, unsigned int font_size,
+ const char *s8, unsigned short *s16, unsigned int n,
+ int x, int y);
+static void pencil_save_pass2(struct pencil_save_context *context,
+ struct pencil_item *item, unsigned int depth);
+static void pencil_save_pass2_text_callback(void *c,
+ const char *font_name, unsigned int font_size,
+ const char *s8, unsigned short *s16, unsigned int n,
+ int x, int y);
+
+
+pencil_code pencil_save_drawfile(struct pencil_diagram *diagram,
+ const char *source,
+ char **drawfile_buffer, size_t *drawfile_size)
+{
+ struct pencil_save_context context =
+ { pencil_OK, diagram, 0, 0, 0, 0, 0, 0,
+ { INT_MAX, INT_MAX, INT_MIN, INT_MIN } };
+ unsigned int i;
+ size_t size, font_table_size;
+ void *buffer, *b;
+ drawfile_diagram *header;
+ drawfile_object *font_table;
+ char *f;
+
+ *drawfile_buffer = 0;
+ *drawfile_size = 0;
+
+ /* pass 1 */
+ pencil_save_pass1(&context, diagram->root, 0);
+ if (context.code != pencil_OK) {
+ for (i = 0; i != context.font_count; i++)
+ free(context.font_list[i]);
+ free(context.font_list);
+ return context.code;
+ }
+
+ /* find font table size */
+ font_table_size = 8;
+ for (i = 0; i != context.font_count; i++)
+ font_table_size += 1 + strlen(context.font_list[i]) + 1;
+ font_table_size = (font_table_size + 3) & ~3;
+
+ size = 40 + font_table_size + context.size;
+
+ /* use calloc to prevent information leakage */
+ buffer = calloc(size, 1);
+ if (!buffer) {
+ for (i = 0; i != context.font_count; i++)
+ free(context.font_list[i]);
+ free(context.font_list);
+ return pencil_OUT_OF_MEMORY;
+ }
+
+ /* file headers */
+ header = (drawfile_diagram *) buffer;
+ header->tag[0] = 'D';
+ header->tag[1] = 'r';
+ header->tag[2] = 'a';
+ header->tag[3] = 'w';
+ header->major_version = 201;
+ header->minor_version = 0;
+ strncpy(header->source, source, 12);
+ for (i = strlen(source); i < 12; i++)
+ header->source[i] = ' ';
+ header->bbox = context.bbox;
+ b = (char *) buffer + sizeof(drawfile_diagram_base);
+
+ /* font table */
+ font_table = (drawfile_object *) b;
+ font_table->type = drawfile_TYPE_FONT_TABLE;
+ font_table->size = font_table_size;
+ f = (char *) b + 8;
+ for (i = 0; i != context.font_count; i++) {
+ *f++ = i + 1;
+ strcpy(f, context.font_list[i]);
+ f += strlen(context.font_list[i]) + 1;
+ }
+ b = (char *) b + font_table_size;
+
+ /* pass 2 */
+ context.buffer = buffer;
+ context.b = b;
+ pencil_save_pass2(&context, diagram->root, 0);
+
+ /* free font list */
+ for (i = 0; i != context.font_count; i++)
+ free(context.font_list[i]);
+ free(context.font_list);
+
+ if (context.code != pencil_OK) {
+ free(buffer);
+ return context.code;
+ }
+
+ assert(context.b == buffer + size);
+
+ *drawfile_buffer = buffer;
+ *drawfile_size = size;
+ return pencil_OK;
+}
+
+
+void pencil_save_pass1(struct pencil_save_context *context,
+ struct pencil_item *item, unsigned int depth)
+{
+ rufl_code code;
+ struct pencil_item *child;
+
+ assert(item);
+
+ /* Initialise item bounding box */
+ item->bbox.x0 = INT_MAX;
+ item->bbox.y0 = INT_MAX;
+ item->bbox.x1 = INT_MIN;
+ item->bbox.y1 = INT_MIN;
+
+ for (child = item->children; child; child = child->next) {
+ pencil_save_pass1(context, child, depth + 1);
+ if (context->code != pencil_OK)
+ return;
+
+ /* Update item bounding box to include child */
+ if (child->bbox.x0 < item->bbox.x0)
+ item->bbox.x0 = child->bbox.x0;
+ if (child->bbox.y0 < item->bbox.y0)
+ item->bbox.y0 = child->bbox.y0;
+ if (child->bbox.x1 > item->bbox.x1)
+ item->bbox.x1 = child->bbox.x1;
+ if (child->bbox.y1 > item->bbox.y1)
+ item->bbox.y1 = child->bbox.y1;
+ }
+
+ switch (item->type) {
+ case pencil_GROUP:
+ if (!item->children || MAX_DEPTH <= depth ||
+ (item->bbox.x0 == INT_MAX &&
+ item->bbox.y0 == INT_MAX &&
+ item->bbox.x1 == INT_MIN &&
+ item->bbox.y1 == INT_MIN))
+ break;
+
+ context->size += 36;
+
+ break;
+ case pencil_TEXT:
+ {
+ int bbox[4];
+ int width;
+
+ code = rufl_paint_callback(item->font_family, item->font_style,
+ item->font_size, item->text, strlen(item->text),
+ item->x, item->y,
+ pencil_save_pass1_text_callback, context);
+ if (code != rufl_OK)
+ context->code = code;
+ if (context->code != pencil_OK)
+ return;
+
+ code = rufl_font_bbox(item->font_family, item->font_style,
+ item->font_size, bbox);
+ if (code != rufl_OK)
+ context->code = code;
+ if (context->code != pencil_OK)
+ return;
+
+ code = rufl_width(item->font_family, item->font_style,
+ item->font_size, item->text, strlen(item->text),
+ &width);
+ if (code != rufl_OK)
+ context->code = code;
+ if (context->code != pencil_OK)
+ return;
+
+ item->bbox.x0 = item->x * 256;
+ item->bbox.y0 = item->y * 256;
+ item->bbox.x1 = (item->x + width) * 256;
+ item->bbox.y1 = (item->y + (bbox[3] - bbox[1])) * 256;
+ }
+ break;
+ case pencil_PATH:
+ context->size += 24 + 16 + item->path_size * 4;
+ if (item->pattern != pencil_SOLID)
+ context->size += 12;
+
+ /* Calculate bounding box */
+ for (unsigned int i = 0; i != item->path_size; ) {
+ switch (item->path[i]) {
+ case 0:
+ case 5:
+ i++;
+ break;
+ case 2:
+ case 6:
+ case 8:
+ {
+ int points = item->path[i++] == 6 ? 3 : 1;
+
+ for (; points > 0; points--) {
+ int x = item->path[i++] * 256;
+ int y = item->path[i++] * 256;
+
+ if (x < item->bbox.x0)
+ item->bbox.x0 = x;
+ if (y < item->bbox.y0)
+ item->bbox.y0 = y;
+ if (x > item->bbox.x1)
+ item->bbox.x1 = x;
+ if (y > item->bbox.y1)
+ item->bbox.y1 = y;
+ }
+ }
+ break;
+ default:
+ assert(0);
+ }
+ }
+ break;
+ case pencil_SPRITE:
+ context->size += 24 + ((const osspriteop_header *)
+ item->sprite)->size;
+
+ item->bbox.x0 = item->x * 256;
+ item->bbox.y0 = item->y * 256;
+ item->bbox.x1 = (item->x + item->width) * 256;
+ item->bbox.y1 = (item->y + item->height) * 256;
+ break;
+ default:
+ assert(0);
+ }
+
+ /* Update global bounding box */
+ if (item->bbox.x0 < context->bbox.x0)
+ context->bbox.x0 = item->bbox.x0;
+ if (item->bbox.y0 < context->bbox.y0)
+ context->bbox.y0 = item->bbox.y0;
+ if (item->bbox.x1 > context->bbox.x1)
+ context->bbox.x1 = item->bbox.x1;
+ if (item->bbox.y1 > context->bbox.y1)
+ context->bbox.y1 = item->bbox.y1;
+}
+
+
+void pencil_save_pass1_text_callback(void *c,
+ const char *font_name, unsigned int font_size,
+ const char *s8, unsigned short *s16, unsigned int n,
+ int x, int y)
+{
+ struct pencil_save_context *context = c;
+ unsigned int i;
+ char **font_list;
+
+ (void) font_size; /* unused */
+ (void) x; /* unused */
+ (void) y; /* unused */
+
+ assert(s8 || s16);
+
+ /* check if the font name is new */
+ for (i = 0; i != context->font_count &&
+ strcmp(context->font_list[i], font_name) != 0; i++)
+ ;
+ if (i == context->font_count) {
+ /* add to list of fonts */
+ font_list = realloc(context->font_list,
+ sizeof context->font_list[0] *
+ (context->font_count + 1));
+ if (!font_list) {
+ context->code = pencil_OUT_OF_MEMORY;
+ return;
+ }
+ font_list[context->font_count] = strdup(font_name);
+ if (!font_list[context->font_count]) {
+ context->code = pencil_OUT_OF_MEMORY;
+ return;
+ }
+ context->font_list = font_list;
+ context->font_count++;
+ }
+
+ /* compute size of transformed text object */
+ if (s8) {
+ context->size += 24 + 56 + ((n + 4) & ~3);
+ } else {
+ unsigned int utf8_length = 0;
+ for (i = 0; i != n; i++) {
+ if (s16[i] < 0x80)
+ utf8_length += 1;
+ else if (s16[i] < 0x800)
+ utf8_length += 2;
+ else
+ utf8_length += 3;
+ }
+ context->size += 24 + 56 + ((utf8_length + 4) & ~3);
+ }
+}
+
+
+void pencil_save_pass2(struct pencil_save_context *context,
+ struct pencil_item *item, unsigned int depth)
+{
+ drawfile_object *object = (drawfile_object *) context->b;
+ bool group = false;
+ rufl_code code;
+ int *path;
+ unsigned int i;
+ struct pencil_item *child;
+
+ assert(item);
+
+ switch (item->type) {
+ case pencil_GROUP:
+ if (!item->children || MAX_DEPTH <= depth ||
+ (item->bbox.x0 == INT_MAX &&
+ item->bbox.y0 == INT_MAX &&
+ item->bbox.x1 == INT_MIN &&
+ item->bbox.y1 == INT_MIN))
+ break;
+
+ group = true;
+ object->type = drawfile_TYPE_GROUP;
+ object->size = 36;
+ strncpy(object->data.group.name, item->group_name, 12);
+ for (i = strlen(item->group_name); i < 12; i++)
+ object->data.group.name[i] = ' ';
+ object->data.group.bbox.x0 = item->bbox.x0;
+ object->data.group.bbox.y0 = item->bbox.y0;
+ object->data.group.bbox.x1 = item->bbox.x1;
+ object->data.group.bbox.y1 = item->bbox.y1;
+ context->b = (char *) context->b + object->size;
+ break;
+ case pencil_TEXT:
+ context->item = item;
+ code = rufl_paint_callback(item->font_family, item->font_style,
+ item->font_size, item->text, strlen(item->text),
+ item->x, item->y,
+ pencil_save_pass2_text_callback, context);
+ if (code != rufl_OK)
+ context->code = code;
+ if (context->code != pencil_OK)
+ return;
+ break;
+ case pencil_PATH:
+ object->type = drawfile_TYPE_PATH;
+ object->size = 24 + 16 + item->path_size * 4;
+ object->data.path.bbox.x0 = item->bbox.x0;
+ object->data.path.bbox.y0 = item->bbox.y0;
+ object->data.path.bbox.x1 = item->bbox.x1;
+ object->data.path.bbox.y1 = item->bbox.y1;
+ object->data.path.fill = item->fill_colour;
+ object->data.path.outline = item->outline_colour;
+ object->data.path.width = item->thickness * 256;
+ object->data.path.style.flags = 0;
+ object->data.path.style.cap_width = item->cap_width;
+ object->data.path.style.cap_length = item->cap_length;
+ if (item->pattern != pencil_SOLID) {
+ object->size += 12;
+ object->data.path_with_pattern.pattern.start = 0;
+ object->data.path_with_pattern.pattern.
+ element_count = 1;
+ if (item->pattern != pencil_DOTTED)
+ object->data.path_with_pattern.pattern.
+ elements[0] = 512 * item->thickness;
+ else if (item->pattern != pencil_DASHED)
+ object->data.path_with_pattern.pattern.
+ elements[0] = 1536 * item->thickness;
+ }
+ path = (int *) (void *) ((char *) context->b + object->size -
+ item->path_size * 4);
+ for (i = 0; i != item->path_size; ) {
+ switch (item->path[i]) {
+ case 0:
+ case 5:
+ path[i] = item->path[i]; i++;
+ break;
+ case 2:
+ case 8:
+ path[i] = item->path[i]; i++;
+ path[i] = item->path[i] * 256; i++;
+ path[i] = item->path[i] * 256; i++;
+ break;
+ case 6:
+ path[i] = item->path[i]; i++;
+ path[i] = item->path[i] * 256; i++;
+ path[i] = item->path[i] * 256; i++;
+ path[i] = item->path[i] * 256; i++;
+ path[i] = item->path[i] * 256; i++;
+ path[i] = item->path[i] * 256; i++;
+ path[i] = item->path[i] * 256; i++;
+ break;
+ default:
+ assert(0);
+ }
+ }
+ context->b = (char *) context->b + object->size;
+ break;
+ case pencil_SPRITE:
+ object->type = drawfile_TYPE_SPRITE;
+ object->size = 24 + ((const osspriteop_header *)
+ item->sprite)->size;
+ object->data.sprite.bbox.x0 = item->bbox.x0;
+ object->data.sprite.bbox.y0 = item->bbox.y0;
+ object->data.sprite.bbox.x1 = item->bbox.x1;
+ object->data.sprite.bbox.y1 = item->bbox.y1;
+ memcpy(&object->data.sprite.header, item->sprite,
+ object->size - 24);
+ context->b = (char *) context->b + object->size;
+ break;
+ default:
+ assert(0);
+ }
+
+ for (child = item->children; child; child = child->next) {
+ pencil_save_pass2(context, child, depth + 1);
+ if (context->code != pencil_OK)
+ return;
+ }
+
+ if (group)
+ object->size = (char *) context->b - (char *) object;
+}
+
+
+void pencil_save_pass2_text_callback(void *c,
+ const char *font_name, unsigned int font_size,
+ const char *s8, unsigned short *s16, unsigned int n,
+ int x, int y)
+{
+ struct pencil_save_context *context = c;
+ drawfile_object *object = (drawfile_object *) context->b;
+ unsigned int i;
+
+ assert(s8 || s16);
+
+ /* find font index */
+ for (i = 0; i != context->font_count &&
+ strcmp(context->font_list[i], font_name) != 0; i++)
+ ;
+ assert(i != context->font_count);
+
+ object->type = drawfile_TYPE_TRFM_TEXT;
+ object->data.trfm_text.bbox.x0 = context->item->bbox.x0;
+ object->data.trfm_text.bbox.y0 = context->item->bbox.y0;
+ object->data.trfm_text.bbox.x1 = context->item->bbox.x1;
+ object->data.trfm_text.bbox.y1 = context->item->bbox.y1;
+ object->data.trfm_text.trfm.entries[0][0] = 0x10000;
+ object->data.trfm_text.trfm.entries[0][1] = 0;
+ object->data.trfm_text.trfm.entries[1][0] = 0;
+ object->data.trfm_text.trfm.entries[1][1] = 0x10000;
+ object->data.trfm_text.trfm.entries[2][0] = 0;
+ object->data.trfm_text.trfm.entries[2][1] = 0;
+ object->data.trfm_text.flags = drawfile_TEXT_KERN;
+ object->data.trfm_text.fill = context->item->fill_colour;
+ object->data.trfm_text.bg_hint = os_COLOUR_WHITE;
+ object->data.trfm_text.style.font_index = i + 1;
+ object->data.trfm_text.xsize = font_size * 40;
+ object->data.trfm_text.ysize = font_size * 40;
+ object->data.trfm_text.base.x = x * 256;
+ object->data.trfm_text.base.y = y * 256;
+
+ if (s8) {
+ strncpy(object->data.trfm_text.text, s8, n);
+ object->size = 24 + 56 + ((n + 4) & ~3);
+ } else {
+ char *z = object->data.trfm_text.text;
+ unsigned int utf8_length = 0;
+ for (i = 0; i != n; i++) {
+ if (s16[i] < 0x80) {
+ *z++ = s16[i];
+ utf8_length += 1;
+ } else if (s16[i] < 0x800) {
+ *z++ = 0xc0 | ((s16[i] >> 6) & 0x1f);
+ *z++ = 0x80 | (s16[i] & 0x3f);
+ utf8_length += 2;
+ } else {
+ *z++ = 0xe0 | (s16[i] >> 12);
+ *z++ = 0x80 | ((s16[i] >> 6) & 0x3f);
+ *z++ = 0x80 | (s16[i] & 0x3f);
+ utf8_length += 3;
+ }
+ }
+ object->size = 24 + 56 + ((utf8_length + 4) & ~3);
+ }
+
+ context->b = (char *) context->b + object->size;
+}