From f74369afb8418a1c991bc9c11cb6d22ff788838e Mon Sep 17 00:00:00 2001 From: Alexey Fedoseev Date: Wed, 12 Jun 2024 17:15:40 +0300 Subject: [PATCH] new geometry implementation + API update --- CMakeLists.txt | 4 +- cyb_error.h | 38 ++ cyb_types.c | 52 ++- cyb_types.h | 8 +- cyberiadaml.c | 669 ++++++++++----------------- cyberiadaml.h | 106 ++++- geometry.c | 1209 ++++++++++++++++++++++++++++++++++++++++++++++++ geometry.h | 59 +++ 8 files changed, 1689 insertions(+), 456 deletions(-) create mode 100644 cyb_error.h create mode 100644 geometry.c create mode 100644 geometry.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e806e2..17afd4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,9 @@ if(!LibXml2_FOUND) message(FATAL_ERROR "Cannot find libxml2 library") endif() -add_library(cyberiadaml SHARED cyberiadaml.c utf8enc.c cyb_types.c cyb_string.c) +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D__DEBUG__") + +add_library(cyberiadaml SHARED cyberiadaml.c utf8enc.c cyb_types.c cyb_string.c geometry.c) target_include_directories(cyberiadaml PUBLIC $ $ diff --git a/cyb_error.h b/cyb_error.h new file mode 100644 index 0000000..1d6639f --- /dev/null +++ b/cyb_error.h @@ -0,0 +1,38 @@ +/* ----------------------------------------------------------------------------- + * The Cyberiada GraphML library implemention + * + * Error handling + * + * Copyright (C) 2024 Alexey Fedoseev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/ + * + * ----------------------------------------------------------------------------- */ + +#ifndef __CYBERIADA_ERROR_H +#define __CYBERIADA_ERROR_H + +#ifdef __DEBUG__ +#define DEBUG(...) fprintf(stderr, __VA_ARGS__) +#else +#define DEBUG(...) +#endif + +#ifndef __SILENT__ +#define ERROR(...) fprintf(stderr, __VA_ARGS__) +#else +#define ERROR(...) +#endif + +#endif diff --git a/cyb_types.c b/cyb_types.c index 27f54be..84d0184 100644 --- a/cyb_types.c +++ b/cyb_types.c @@ -38,7 +38,7 @@ int cyberiada_stack_update_top_key(CyberiadaStack** stack, const char* new_key if (!stack || !*stack) { return -1; } - (*stack)->key = new_key; + (*stack)->key = (void*)new_key; return 0; } @@ -98,7 +98,7 @@ int cyberiada_list_add(CyberiadaList** list, const char* key, void* data) return -1; } new_item = (CyberiadaList*)malloc(sizeof(CyberiadaList)); - new_item->key = key; + new_item->key = (void*)key; new_item->data = data; new_item->next = NULL; if (!*list) { @@ -119,7 +119,7 @@ void* cyberiada_list_find(CyberiadaList** list, const char* key) } item = *list; while (item) { - if (item->key && strcmp(item->key, key) == 0) { + if (item->key && strcmp((const char*)item->key, key) == 0) { return item->data; } item = item->next; @@ -140,3 +140,49 @@ int cyberiada_list_free(CyberiadaList** list) } return 0; } + +int cyberiada_queue_add(CyberiadaQueue** queue, void* key, void* data) +{ + CyberiadaQueue* new_item = (CyberiadaQueue*)malloc(sizeof(CyberiadaQueue)); + memset(new_item, 0, sizeof(CyberiadaQueue)); + new_item->key = key; + new_item->data = data; + new_item->next = (*queue); + *queue = new_item; + return 0; +} + +int cyberiada_queue_get(CyberiadaQueue** queue, void** key, void** data) +{ + CyberiadaQueue *q, *prev = NULL; + if (!queue || !key || !data) return 1; + q = *queue; + while (q->next) { + prev = q; + q = q->next; + } + *key = q->key; + *data = q->data; + free(q); + if (prev) { + prev->next = NULL; + } else { + *queue = NULL; + } + return 0; +} + +int cyberiada_queue_free(CyberiadaQueue** queue) +{ + CyberiadaQueue* item; + if (!queue) { + return 0; + } + while(*queue) { + item = *queue; + *queue = item->next; + free(item); + } + return 0; +} + diff --git a/cyb_types.h b/cyb_types.h index 77d2acf..2536f79 100644 --- a/cyb_types.h +++ b/cyb_types.h @@ -32,7 +32,7 @@ extern "C" { * ----------------------------------------------------------------------------- */ struct _CyberiadaStruct { - const char* key; + void* key; void* data; struct _CyberiadaStruct* next; }; @@ -52,6 +52,12 @@ extern "C" { int cyberiada_list_add(CyberiadaList** list, const char* key, void* data); void* cyberiada_list_find(CyberiadaList** list, const char* key); int cyberiada_list_free(CyberiadaList** list); + + typedef struct _CyberiadaStruct CyberiadaQueue; + + int cyberiada_queue_add(CyberiadaQueue** queue, void* key, void* data); + int cyberiada_queue_get(CyberiadaQueue** queue, void** key, void**data); + int cyberiada_queue_free(CyberiadaQueue** queue); #ifdef __cplusplus } diff --git a/cyberiadaml.c b/cyberiadaml.c index eb17af0..9e98193 100644 --- a/cyberiadaml.c +++ b/cyberiadaml.c @@ -35,6 +35,8 @@ #include "utf8enc.h" #include "cyb_types.h" #include "cyb_string.h" +#include "geometry.h" +#include "cyb_error.h" /* ----------------------------------------------------------------------------- * Cyberiada parser constants @@ -173,16 +175,9 @@ /* Misc. constants */ #define PSEUDO_NODE_SIZE 20 +#define DEFAULT_NODE_SIZE 100 #define EMPTY_TITLE "" -#ifdef __DEBUG__ -#define DEBUG(...) fprintf(stderr, __VA_ARGS__) -#else -#define DEBUG(...) -#endif - -#define ERROR(...) fprintf(stderr, __VA_ARGS__) - /* Types & macros for XML/GraphML processing */ #define INDENT_STR " " @@ -231,6 +226,7 @@ fprintf(stderr, "xml close element error %d at %s:%d", res, __FILE__, __LINE__); \ return CYBERIADA_XML_ERROR; \ } +# define XML_READMEMORY_BASENAME "noname.xml" typedef struct { char* attr_id; @@ -438,97 +434,6 @@ static CyberiadaLink* cyberiada_copy_link(CyberiadaLink* src) return cyberiada_new_link(src->ref); } -CyberiadaPoint* cyberiada_new_point(void) -{ - CyberiadaPoint* p = (CyberiadaPoint*)malloc(sizeof(CyberiadaPoint)); - memset(p, 0, sizeof(CyberiadaPoint)); - return p; -} - -static CyberiadaPoint* cyberiada_copy_point(CyberiadaPoint* src) -{ - CyberiadaPoint* dst; - if (!src) { - return NULL; - } - dst = cyberiada_new_point(); - dst->x = src->x; - dst->y = src->y; - return dst; -} - -static void cyberiada_update_point(CyberiadaPoint* p, float dx, float dy) -{ - if (!p) return ; - p->x += dx; - p->y += dy; -} - -CyberiadaRect* cyberiada_new_rect(void) -{ - CyberiadaRect* r = (CyberiadaRect*)malloc(sizeof(CyberiadaRect)); - memset(r, 0, sizeof(CyberiadaRect)); - return r; -} - -static CyberiadaRect* cyberiada_copy_rect(CyberiadaRect* src) -{ - CyberiadaRect* dst; - if (!src) { - return NULL; - } - dst = cyberiada_new_rect(); - dst->x = src->x; - dst->y = src->y; - dst->width = src->width; - dst->height = src->height; - return dst; -} - -static void cyberiada_update_rect(CyberiadaRect* r, float dx, float dy) -{ - if (!r) return ; - r->x += dx; - r->y += dy; -} - -CyberiadaPolyline* cyberiada_new_polyline(void) -{ - CyberiadaPolyline* pl = (CyberiadaPolyline*)malloc(sizeof(CyberiadaPolyline)); - memset(pl, 0, sizeof(CyberiadaPolyline)); - return pl; -} - -static CyberiadaPolyline* cyberiada_copy_polyline(CyberiadaPolyline* src) -{ - CyberiadaPolyline *dst = NULL, *prev, *pl; - if (!src) { - return NULL; - } - do { - pl = cyberiada_new_polyline(); - pl->point.x = src->point.x; - pl->point.y = src->point.y; - if (dst) { - prev->next = pl; - } else { - dst = pl; - } - prev = pl; - src = src->next; - } while (src); - return dst; -} - -/*static void cyberiada_update_polyline(CyberiadaPolyline* pl, float dx, float dy) -{ - if (!pl) return ; - do { - cyberiada_update_point(&(pl->point), dx, dy); - pl = pl->next; - } while (pl); - }*/ - CyberiadaAction* cyberiada_new_action(CyberiadaActionType type, const char* trigger, const char* guard, @@ -545,21 +450,10 @@ CyberiadaAction* cyberiada_new_action(CyberiadaActionType type, static CyberiadaAction* cyberiada_copy_action(CyberiadaAction* src) { - CyberiadaAction *dst = NULL, *prev, *a; if (!src) { return NULL; } - do { - a = cyberiada_new_action(src->type, src->trigger, src->guard, src->behavior); - if (dst) { - prev->next = a; - } else { - dst = a; - } - prev = a; - src = src->next; - } while (src); - return dst; + return cyberiada_new_action(src->type, src->trigger, src->guard, src->behavior); } CyberiadaNode* cyberiada_new_node(const char* id) @@ -1256,7 +1150,6 @@ static CyberiadaEdge* cyberiada_graph_find_last_edge(CyberiadaSM* sm) static int cyberiada_destroy_edge(CyberiadaEdge* e) { - CyberiadaPolyline *polyline, *pl; if (e->id) free(e->id); if (e->source_id) free(e->source_id); if (e->target_id) free(e->target_id); @@ -1266,12 +1159,7 @@ static int cyberiada_destroy_edge(CyberiadaEdge* e) free(e->comment_subject); } if (e->geometry_polyline) { - polyline = e->geometry_polyline; - do { - pl = polyline; - polyline = polyline->next; - free(pl); - } while (polyline); + cyberiada_destroy_polyline(e->geometry_polyline); } if (e->geometry_source_point) free(e->geometry_source_point); if (e->geometry_target_point) free(e->geometry_target_point); @@ -1311,7 +1199,7 @@ static void cyberiada_free_name_list(NamesList** nl) cyberiada_list_free(nl); } -static int cyberiada_graphs_reconstruct_nodes(CyberiadaNode* root, NamesList** nl) +static int cyberiada_graphs_reconstruct_node_identifiers(CyberiadaNode* root, NamesList** nl) { char buffer[MAX_STR_LEN]; size_t buffer_len = sizeof(buffer) - 1; @@ -1340,14 +1228,14 @@ static int cyberiada_graphs_reconstruct_nodes(CyberiadaNode* root, NamesList** n cyberiada_add_name_to_list(nl, "", node->id); } if (node->children) { - cyberiada_graphs_reconstruct_nodes(node->children, nl); + cyberiada_graphs_reconstruct_node_identifiers(node->children, nl); } node = node->next; } return CYBERIADA_NO_ERROR; } -static int cyberiada_graphs_reconstruct_edges(CyberiadaDocument* doc, NamesList** nl) +static int cyberiada_graphs_reconstruct_edge_identifiers(CyberiadaDocument* doc, NamesList** nl) { char buffer[MAX_STR_LEN]; size_t buffer_len = sizeof(buffer) - 1; @@ -1410,249 +1298,6 @@ static int cyberiada_graphs_reconstruct_edges(CyberiadaDocument* doc, NamesList* return CYBERIADA_NO_ERROR; } -static int cyberiada_graphs_reconstruct_node_geometry_from_yed(CyberiadaNode* node, float parent_x, float parent_y) -{ - float new_root_x; - float new_root_y; - - while (node) { - if (node->geometry_point) { - cyberiada_update_point(node->geometry_point, -parent_x, -parent_y); - } - - if (node->geometry_rect) { - new_root_x = node->geometry_rect->x; - new_root_y = node->geometry_rect->y; - - cyberiada_update_rect(node->geometry_rect, -parent_x, -parent_y); - } else { - new_root_x = parent_x; - new_root_y = parent_y; - } - - if (node->children) { - cyberiada_graphs_reconstruct_node_geometry_from_yed(node->children, new_root_x, new_root_y); - } - node = node->next; - } - return CYBERIADA_NO_ERROR; -} - -static int cyberiada_graphs_reconstruct_geometry_from_yed(CyberiadaDocument* doc) -{ - CyberiadaSM* sm; - CyberiadaNode* node; - CyberiadaEdge* edge; - CyberiadaPolyline* pl; - float from_x, from_y, to_x, to_y; - float src_from_x, src_from_y, src_to_x, src_to_y, tgt_from_x, tgt_from_y, tgt_to_x, tgt_to_y; - - for (sm = doc->state_machines; sm; sm = sm->next) { - - for (edge = sm->edges; edge; edge = edge->next) { - if (edge->source && (edge->source->geometry_rect || edge->source->geometry_point) && - edge->target && (edge->target->geometry_rect || edge->target->geometry_point) && - (edge->geometry_source_point || - edge->geometry_target_point || - edge->geometry_polyline || - edge->geometry_label_point)) { - - from_x = from_y = to_x = to_y = 0.0f; - if (edge->source->geometry_point) { - from_x += edge->source->geometry_point->x; - from_y += edge->source->geometry_point->y; - } else { - from_x += edge->source->geometry_rect->x + edge->source->geometry_rect->width / 2.0f; - from_y += edge->source->geometry_rect->y + edge->source->geometry_rect->height / 2.0f; - if (edge->geometry_source_point) { - from_x += edge->geometry_source_point->x; - from_y += edge->geometry_source_point->y; - } - } - if (edge->target->geometry_point) { - to_x += edge->target->geometry_point->x; - to_y += edge->target->geometry_point->y; - } else { - to_x += edge->target->geometry_rect->x + edge->target->geometry_rect->width / 2.0f; - to_y += edge->target->geometry_rect->y + edge->target->geometry_rect->height / 2.0f; - if (edge->geometry_target_point) { - to_x += edge->geometry_target_point->x; - to_y += edge->geometry_target_point->y; - } - } - - if (edge->geometry_polyline) { - float first_p_x = edge->geometry_polyline->point.x; - float first_p_y = edge->geometry_polyline->point.y; - float last_p_x; - float last_p_y; - - pl = edge->geometry_polyline; - while (pl->next) pl = pl->next; - last_p_x = pl->point.x; - last_p_y = pl->point.y; - - src_from_x = from_x; - src_from_y = from_y; - src_to_x = first_p_x; - src_to_y = first_p_y; - tgt_from_x = last_p_x; - tgt_from_y = last_p_y; - tgt_to_x = to_x; - tgt_to_y = to_y; - } else { - src_from_x = tgt_from_x = from_x; - src_from_y = tgt_from_y = from_y; - src_to_x = tgt_to_x = to_x; - src_to_y = tgt_to_y = to_y; - } - - /* DEBUG("edge (%f; %f) -> (%f; %f) and (%f; %f) -> (%f; %f)\n", */ - /* src_from_x, src_from_y, src_to_x, src_to_y, */ - /* tgt_from_x, tgt_from_y, tgt_to_x, tgt_to_y); */ - - if (edge->geometry_source_point) { - if (edge->source->geometry_point) { - if (src_from_x == src_to_x) { - edge->geometry_source_point->x = 0.0f; - if (src_from_y >= src_to_y) { - edge->geometry_source_point->y = -PSEUDO_NODE_SIZE / 2.0f; - } else { - edge->geometry_source_point->y = PSEUDO_NODE_SIZE / 2.0f; - } - } else { - float alpha = (float)atan((src_from_y - src_to_y) / (src_to_x - src_from_x)); - edge->geometry_source_point->x = (float)cos(alpha) * PSEUDO_NODE_SIZE / 2.0f; - edge->geometry_source_point->y = -(float)sin(alpha) * PSEUDO_NODE_SIZE / 2.0f; - } - } else { - if (src_from_x == src_to_x) { - edge->geometry_source_point->x = edge->source->geometry_rect->width / 2.0f; - if (src_from_y >= src_to_y) { - edge->geometry_source_point->y = 0.0f; - } else { - edge->geometry_source_point->y = edge->source->geometry_rect->height; - } - } else { - float alpha = (float)atan((src_from_y - src_to_y) / (src_to_x - src_from_x)); - if (src_to_x < src_from_x) alpha += (float)M_PI; - float alpha_g = 180.0f * alpha / (float)M_PI; -/* DEBUG("src alpha %f\n", alpha_g);*/ - if (alpha_g < 0.0f) alpha_g += 360.0f; - if (alpha_g <= 45.0f || alpha_g > 315.0f) { - edge->geometry_source_point->x = edge->source->geometry_rect->width; - edge->geometry_source_point->y += (-(float)tan(alpha) * edge->source->geometry_rect->width + - edge->source->geometry_rect->height) / 2.0f; - } else if (alpha_g > 45.0f && alpha_g <= 135.0f) { - edge->geometry_source_point->x += ((float)tan(alpha) * edge->source->geometry_rect->height + - edge->source->geometry_rect->width) / 2.0f; - edge->geometry_source_point->y = 0.0f; - } else if (alpha_g > 135.0f && alpha_g <= 225.0f) { - edge->geometry_source_point->x = 0.0f; - edge->geometry_source_point->y += (-(float)tan(alpha) * edge->source->geometry_rect->width + - edge->source->geometry_rect->height) / 2.0f; - } else { - edge->geometry_source_point->x += (-(float)tan(alpha) * edge->source->geometry_rect->height + - edge->source->geometry_rect->width) / 2.0f; - edge->geometry_source_point->y = edge->source->geometry_rect->height; - } - - if (edge->geometry_source_point->x < 0) { - edge->geometry_source_point->x = 0.0f; - } - if (edge->geometry_source_point->x > edge->source->geometry_rect->width) { - edge->geometry_source_point->x = edge->source->geometry_rect->width; - } - if (edge->geometry_source_point->y < 0) { - edge->geometry_source_point->y = 0.0f; - } - if (edge->geometry_source_point->y > edge->source->geometry_rect->height) { - edge->geometry_source_point->y = edge->source->geometry_rect->height; - } - } - } - /* DEBUG("sp: (%f; %f)\n", edge->geometry_source_point->x, edge->geometry_source_point->y); */ - } - - if (edge->geometry_target_point) { - if (edge->target->geometry_point) { - if (tgt_from_x == tgt_to_x) { - edge->geometry_target_point->x = 0.0f; - if (tgt_from_y >= tgt_to_y) { - edge->geometry_target_point->y = PSEUDO_NODE_SIZE / 2.0f; - } else { - edge->geometry_target_point->y = -PSEUDO_NODE_SIZE / 2.0f; - } - } else { - float alpha = (float)atan((tgt_from_y - tgt_to_y) / (tgt_to_x - tgt_from_x)); - edge->geometry_target_point->x = (float)cos(alpha) * PSEUDO_NODE_SIZE / 2.0f; - edge->geometry_target_point->y = -(float)sin(alpha) * PSEUDO_NODE_SIZE / 2.0f; - } - } else { - if (tgt_from_x == tgt_to_x) { - edge->geometry_target_point->x = edge->target->geometry_rect->width / 2.0f; - if (tgt_from_y >= tgt_to_y) { - edge->geometry_target_point->y = edge->target->geometry_rect->height; - } else { - edge->geometry_target_point->y = 0.0f; - } - } else { - float alpha = (float)atan((tgt_from_y - tgt_to_y) / (tgt_to_x - tgt_from_x)); - if (tgt_to_x < tgt_from_x) alpha += (float)M_PI; - alpha += (float)M_PI; /* target = incoming edge */ - float alpha_g = 180.0f * alpha / (float)M_PI; -/* DEBUG("tgt alpha %f\n", alpha_g);*/ - if (alpha_g < 0.0f) alpha_g += 360.0f; - if (alpha_g <= 45.0f || alpha_g > 315.0f) { - edge->geometry_target_point->x = edge->target->geometry_rect->width; - edge->geometry_target_point->y += (-(float)tan(alpha) * edge->target->geometry_rect->width + - edge->target->geometry_rect->height) / 2.0f; - } else if (alpha_g > 45.0f && alpha_g <= 135.0f) { - edge->geometry_target_point->x += ((float)tan(alpha) * edge->target->geometry_rect->height + - edge->target->geometry_rect->width) / 2.0f; - edge->geometry_target_point->y = 0.0f; - } else if (alpha_g > 135.0f && alpha_g <= 225.0f) { - edge->geometry_target_point->x = 0.0f; - edge->geometry_target_point->y += (-(float)tan(alpha) * edge->target->geometry_rect->width + - edge->target->geometry_rect->height) / 2.0f; - } else { - edge->geometry_target_point->x += (-(float)tan(alpha) * edge->target->geometry_rect->height + - edge->target->geometry_rect->width) / 2.0f; - edge->geometry_target_point->y = edge->target->geometry_rect->height; - } - } - - if (edge->geometry_target_point->x < 0) { - edge->geometry_target_point->x = 0.0f; - } - if (edge->geometry_target_point->x > edge->target->geometry_rect->width) { - edge->geometry_target_point->x = edge->target->geometry_rect->width; - } - if (edge->geometry_target_point->y < 0) { - edge->geometry_target_point->y = 0.0f; - } - if (edge->geometry_target_point->y > edge->target->geometry_rect->height) { - edge->geometry_target_point->y = edge->target->geometry_rect->height; - } - } - /* DEBUG("tp: (%f; %f)\n", edge->geometry_target_point->x, edge->geometry_target_point->y); */ - } - } - } - - for (node = sm->nodes; node; node = node->next) { - cyberiada_graphs_reconstruct_node_geometry_from_yed(node, 0.0f, 0.0f); - } - } - - return CYBERIADA_NO_ERROR; -} - -static int cyberiada_graphs_reconstruct_geometry_to_yed(CyberiadaDocument* doc) -{ - return CYBERIADA_NO_ERROR; -} - typedef struct { const char* name; size_t value_offset; @@ -1961,20 +1606,10 @@ static int cyberiada_decode_meta(CyberiadaDocument* doc, char* metadata) return CYBERIADA_NO_ERROR; } -static int cyberiada_init_sm(CyberiadaSM* sm) -{ - if (sm) { - sm->nodes = NULL; - sm->edges = NULL; - sm->next = NULL; - } - return CYBERIADA_NO_ERROR; -} - CyberiadaSM* cyberiada_new_sm(void) { CyberiadaSM* sm = (CyberiadaSM*)malloc(sizeof(CyberiadaSM)); - cyberiada_init_sm(sm); + memset(sm, 0, sizeof(CyberiadaSM)); return sm; } @@ -1982,6 +1617,9 @@ static int cyberiada_destroy_sm(CyberiadaSM* sm) { CyberiadaEdge *edge, *e; if (sm) { + if (sm->bounding_rect) { + free(sm->bounding_rect); + } if (sm->nodes) { cyberiada_destroy_all_nodes(sm->nodes); } @@ -2266,7 +1904,7 @@ static int cyberiada_get_element_text(char* buffer, size_t buffer_len, static int cyberiada_xml_read_coord(xmlNode* xml_node, const char* attr_name, - float* result) + double* result) { char buffer[MAX_STR_LEN]; size_t buffer_len = sizeof(buffer) - 1; @@ -2275,7 +1913,7 @@ static int cyberiada_xml_read_coord(xmlNode* xml_node, attr_name) != CYBERIADA_NO_ERROR) { return CYBERIADA_BAD_PARAMETER; } - *result = (float)atof(buffer); + *result = (double)atof(buffer); return CYBERIADA_NO_ERROR; } @@ -2528,8 +2166,8 @@ static GraphProcessorState handle_node_geometry(xmlNode* xml_node, } if (type == cybNodeInitial) { current->geometry_point = cyberiada_new_point(); - current->geometry_point->x = rect->x + rect->width / 2.0f; - current->geometry_point->y = rect->y + rect->height / 2.0f; + current->geometry_point->x = rect->x + rect->width / 2.0; + current->geometry_point->y = rect->y + rect->height / 2.0; free(rect); return gpsNodeStart; } else if (type == cybNodeComment) { @@ -2574,8 +2212,10 @@ static GraphProcessorState handle_node_title(xmlNode* xml_node, return gpsInvalid; } cyberiada_get_element_text(buffer, buffer_len, xml_node); - /* DEBUG("Set node %s title %s\n", current->id, buffer); */ + DEBUG("Set node %s title '%s'\n", current->id, buffer); cyberiada_copy_string(&(current->title), &(current->title_len), buffer); + cyberiada_string_trim(current->title); + DEBUG("After trim: '%s'\n", current->title); return gpsNodeAction; } @@ -2658,7 +2298,7 @@ static GraphProcessorState handle_edge_label(xmlNode* xml_node, { char buffer[MAX_STR_LEN]; size_t buffer_len = sizeof(buffer) - 1; - float x = 0.0, y = 0.0; + double x = 0.0, y = 0.0; CyberiadaEdge *current; CyberiadaSM* sm = doc->state_machines; while (sm->next) sm = sm->next; @@ -2870,6 +2510,7 @@ static GraphProcessorState handle_node_data(xmlNode* xml_node, } /* DEBUG("Set node %s title %s\n", current->id, buffer); */ cyberiada_copy_string(&(current->title), &(current->title_len), buffer); + cyberiada_string_trim(current->title); } else if (strcmp(key_name, GRAPHML_CYB_KEY_STATE_MACHINE_NAME) == 0) { if (current->type != cybNodeSM) { ERROR("Using state machine key outside the graph element in %s\n", current->id); @@ -3466,40 +3107,74 @@ static int cyberiada_check_graphml_ns(xmlNode* root, CyberiadaXMLFormat* format) /* ----------------------------------------------------------------------------- * GraphML reader interface * ----------------------------------------------------------------------------- */ - -int cyberiada_read_sm_document(CyberiadaDocument* cyb_doc, const char* filename, CyberiadaXMLFormat format) +static int cyberiada_process_decode_sm_document(CyberiadaDocument* cyb_doc, xmlDoc* doc, CyberiadaXMLFormat format, int flags) { int res; - xmlDoc* doc = NULL; xmlNode* root = NULL; CyberiadaSM* sm; NamesList* nl = NULL; + int geom_flags; + + if (flags & CYBERIADA_FLAG_ROUND_GEOMETRY) { + ERROR("Round geometry flag is not supported on import\n"); + return CYBERIADA_BAD_PARAMETER; + } + + if (flags & CYBERIADA_FLAG_SKIP_GEOMETRY && + flags != CYBERIADA_FLAG_SKIP_GEOMETRY) { + ERROR("The skip geometry flag is not compatible with other flags\n"); + return CYBERIADA_BAD_PARAMETER; + } + + geom_flags = flags & (CYBERIADA_FLAG_ABSOLUTE_GEOMETRY | + CYBERIADA_FLAG_LEFTTOP_LOCAL_GEOMETRY | + CYBERIADA_FLAG_CENTER_LOCAL_GEOMETRY); + if (geom_flags && + geom_flags != CYBERIADA_FLAG_ABSOLUTE_GEOMETRY && + geom_flags != CYBERIADA_FLAG_LEFTTOP_LOCAL_GEOMETRY && + geom_flags != CYBERIADA_FLAG_CENTER_LOCAL_GEOMETRY) { + ERROR("Single geometry flag (abs, left-top, center) can be used at the same time\n"); + return CYBERIADA_BAD_PARAMETER; + } + + if (!(flags & CYBERIADA_FLAG_SKIP_GEOMETRY) && !geom_flags) { + /* set default geometry flag */ + flags |= CYBERIADA_FLAG_CENTER_LOCAL_GEOMETRY; + } + + geom_flags = flags & (CYBERIADA_FLAG_CENTER_EDGE_GEOMETRY | + CYBERIADA_FLAG_LEFTTOP_BORDER_EDGE_GEOMETRY | + CYBERIADA_FLAG_CENTER_BORDER_EDGE_GEOMETRY); + if (geom_flags && + geom_flags != CYBERIADA_FLAG_CENTER_EDGE_GEOMETRY && + geom_flags != CYBERIADA_FLAG_LEFTTOP_BORDER_EDGE_GEOMETRY && + geom_flags != CYBERIADA_FLAG_CENTER_BORDER_EDGE_GEOMETRY) { + ERROR("Single geometry edge flag (border, center) can be used at the same time\n"); + return CYBERIADA_BAD_PARAMETER; + } + + if (!(flags & CYBERIADA_FLAG_SKIP_GEOMETRY) && !geom_flags) { + /* set default edge geometry flag */ + flags |= CYBERIADA_FLAG_CENTER_BORDER_EDGE_GEOMETRY; + } cyberiada_init_sm_document(cyb_doc); cyberiada_init_action_regexps(); - xmlInitParser(); do { - - /* parse the file and get the DOM */ - if ((doc = xmlReadFile(filename, NULL, 0)) == NULL) { - ERROR("error: could not parse file %s\n", filename); - res = CYBERIADA_XML_ERROR; - break; - } /* get the root element node */ root = xmlDocGetRootElement(doc); if (strcmp((const char*)root->name, GRAPHML_GRAPHML_ELEMENT) != 0) { - ERROR("error: could not find GraphML root node %s\n", filename); + ERROR("error: could not find GraphML root node\n"); res = CYBERIADA_XML_ERROR; break; } /* check whether the xml is graphml */ if (cyberiada_check_graphml_ns(root, &format)) { - ERROR("error: no valid graphml namespace in %s\n", filename); + ERROR("error: no valid graphml namespace\n"); res = CYBERIADA_XML_ERROR; break; } @@ -3512,8 +3187,7 @@ int cyberiada_read_sm_document(CyberiadaDocument* cyb_doc, const char* filename, } else if (format == cybxmlCyberiada10) { res = cyberiada_decode_cyberiada_xml(root, cyb_doc); } else { - ERROR("error: unsupported GraphML format of file %s\n", - filename); + ERROR("error: unsupported GraphML format of file\n"); res = CYBERIADA_XML_ERROR; break; } @@ -3523,31 +3197,77 @@ int cyberiada_read_sm_document(CyberiadaDocument* cyb_doc, const char* filename, } for (sm = cyb_doc->state_machines; sm; sm = sm->next) { - if ((res = cyberiada_graphs_reconstruct_nodes(sm->nodes, &nl)) != CYBERIADA_NO_ERROR) { - ERROR("error: cannot reconstruct graph nodes file %s\n", - filename); + if ((res = cyberiada_graphs_reconstruct_node_identifiers(sm->nodes, &nl)) != CYBERIADA_NO_ERROR) { + ERROR("error: cannot reconstruct graph nodes' indentifiers\n"); break; } } - if ((res = cyberiada_graphs_reconstruct_edges(cyb_doc, &nl)) != CYBERIADA_NO_ERROR) { - ERROR("error: cannot reconstruct graph edges from file %s\n", - filename); + if ((res = cyberiada_graphs_reconstruct_edge_identifiers(cyb_doc, &nl)) != CYBERIADA_NO_ERROR) { + ERROR("error: cannot reconstruct graph edges' identifier\n"); break; } - - if (format == cybxmlYED && res == CYBERIADA_NO_ERROR) { - cyberiada_graphs_reconstruct_geometry_from_yed(cyb_doc); + + if (res == CYBERIADA_NO_ERROR) { + if (flags & CYBERIADA_FLAG_SKIP_GEOMETRY) { + cyberiada_clean_document_geometry(cyb_doc); + } else { + cyberiada_import_document_geometry(cyb_doc, flags, format); + } } } while(0); cyberiada_free_name_list(&nl); cyberiada_free_action_regexps(); - xmlFreeDoc(doc); - xmlCleanupParser(); - return res; + return res; +} + +int cyberiada_read_sm_document(CyberiadaDocument* cyb_doc, const char* filename, + CyberiadaXMLFormat format, int flags) +{ + int res; + xmlDoc* doc = NULL; + xmlInitParser(); + + /* parse the file and get the DOM */ + if ((doc = xmlReadFile(filename, NULL, 0)) == NULL) { + ERROR("error: could not parse file %s\n", filename); + xmlCleanupParser(); + return CYBERIADA_XML_ERROR; + } + + res = cyberiada_process_decode_sm_document(cyb_doc, doc, format, flags); + + if (doc) { + xmlFreeDoc(doc); + } + xmlCleanupParser(); + return res; +} + +int cyberiada_decode_sm_document(CyberiadaDocument* cyb_doc, const char* buffer, size_t buffer_size, + CyberiadaXMLFormat format, int flags) +{ + int res; + xmlDoc* doc = NULL; + xmlInitParser(); + + /* parse the file and get the DOM */ + if ((doc = xmlReadMemory(buffer, buffer_size, XML_READMEMORY_BASENAME, NULL, 0)) == NULL) { + ERROR("error: could not read buffer\n"); + xmlCleanupParser(); + return CYBERIADA_XML_ERROR; + } + + res = cyberiada_process_decode_sm_document(cyb_doc, doc, format, flags); + + if (doc) { + xmlFreeDoc(doc); + } + xmlCleanupParser(); + return res; } /* ----------------------------------------------------------------------------- @@ -4695,11 +4415,38 @@ static int cyberiada_write_sm_document_yed(CyberiadaDocument* doc, xmlTextWriter * GraphML writer interface * ----------------------------------------------------------------------------- */ -int cyberiada_write_sm_document(CyberiadaDocument* doc, const char* filename, CyberiadaXMLFormat format) +static int cyberiada_process_encode_sm_document(CyberiadaDocument* doc, xmlTextWriterPtr writer, + CyberiadaXMLFormat format, int flags) { CyberiadaDocument* copy_doc = NULL; - xmlTextWriterPtr writer; int res; + + if (flags & CYBERIADA_FLAG_RECONSTRUCT_GEOMETRY) { + ERROR("Geometry reconstructioin flag is not supported on export\n"); + return CYBERIADA_BAD_PARAMETER; + } + + if (flags & CYBERIADA_FLAG_SKIP_GEOMETRY) { + if (flags != CYBERIADA_FLAG_SKIP_GEOMETRY) { + ERROR("The skip geometry flag is not compatible with other flags\n"); + return CYBERIADA_BAD_PARAMETER; + } + if (format == cybxmlYED) { + ERROR("Skip geometry flag is not allowed for YED export\n"); + return CYBERIADA_BAD_PARAMETER; + } + } + + if (CYBERIADA_FLAG_ABSOLUTE_GEOMETRY | + CYBERIADA_FLAG_LEFTTOP_LOCAL_GEOMETRY | + CYBERIADA_FLAG_CENTER_LOCAL_GEOMETRY | + CYBERIADA_FLAG_LEFTTOP_BORDER_EDGE_GEOMETRY | + CYBERIADA_FLAG_CENTER_BORDER_EDGE_GEOMETRY | + CYBERIADA_FLAG_CENTER_EDGE_GEOMETRY) { + + ERROR("Geometry flags (abs, left-top, center) & edge geometry flags are not allowed for export\n"); + return CYBERIADA_BAD_PARAMETER; + } if (format != cybxmlCyberiada10 && format != cybxmlYED) { ERROR("unsupported SM format for write: %d\n", format); @@ -4711,16 +4458,12 @@ int cyberiada_write_sm_document(CyberiadaDocument* doc, const char* filename, Cy return CYBERIADA_BAD_PARAMETER; } - xmlInitParser(); - + if (format == cybxmlYED && (!doc->state_machines ||doc->state_machines->next)) { + ERROR("YED format supports only single SM documents\n"); + return CYBERIADA_BAD_PARAMETER; + } + do { - - writer = xmlNewTextWriterFilename(filename, 0); - if (!writer) { - ERROR("cannot open xml writter for file %s\n", filename); - res = CYBERIADA_XML_ERROR; - } - res = xmlTextWriterStartDocument(writer, NULL, GRAPHML_XML_ENCODING, NULL); if (res < 0) { ERROR("error writing xml start document: %d\n", res); @@ -4728,22 +4471,20 @@ int cyberiada_write_sm_document(CyberiadaDocument* doc, const char* filename, Cy break; } + copy_doc = cyberiada_copy_sm_document(doc); + + if (flags & CYBERIADA_FLAG_SKIP_GEOMETRY) { + cyberiada_clean_document_geometry(copy_doc); + } else { + cyberiada_export_document_geometry(copy_doc, flags, format); + } if (format == cybxmlYED) { - if (!doc->state_machines || doc->state_machines->next) { - ERROR("YED format supports only single SM documents\n"); - res = CYBERIADA_BAD_PARAMETER; - break; - } - - copy_doc = cyberiada_copy_sm_document(doc); - cyberiada_graphs_reconstruct_geometry_to_yed(copy_doc); - res = cyberiada_write_sm_document_yed(copy_doc, writer); - - cyberiada_destroy_sm_document(copy_doc); } else if (format == cybxmlCyberiada10) { - res = cyberiada_write_sm_document_cyberiada(doc, writer); + res = cyberiada_write_sm_document_cyberiada(copy_doc, writer); } + cyberiada_destroy_sm_document(copy_doc); + if (res != CYBERIADA_NO_ERROR) { ERROR("error writing xml %d\n", res); res = CYBERIADA_XML_ERROR; @@ -4760,12 +4501,72 @@ int cyberiada_write_sm_document(CyberiadaDocument* doc, const char* filename, Cy res = CYBERIADA_NO_ERROR; } while (0); - xmlFreeTextWriter(writer); - xmlCleanupParser(); - if (res != CYBERIADA_NO_ERROR) { return res; } else { return CYBERIADA_NO_ERROR; } } + +int cyberiada_write_sm_document(CyberiadaDocument* doc, const char* filename, + CyberiadaXMLFormat format, int flags) +{ + int res; + xmlTextWriterPtr writer = NULL; + xmlInitParser(); + writer = xmlNewTextWriterFilename(filename, 0); + if (!writer) { + ERROR("cannot open xml writter for file %s\n", filename); + xmlCleanupParser(); + return CYBERIADA_XML_ERROR; + } + + res = cyberiada_process_encode_sm_document(doc, writer, format, flags); + + xmlFreeTextWriter(writer); + xmlCleanupParser(); + + return res; +} + +int cyberiada_encode_sm_document(CyberiadaDocument* doc, char** buffer, size_t* buffer_size, + CyberiadaXMLFormat format, int flags) +{ + int res; + xmlBufferPtr xml_buffer; + xmlTextWriterPtr writer = NULL; + + xmlInitParser(); + xml_buffer = xmlBufferCreate(); + if (!xml_buffer) { + ERROR("cannot create xml buffer\n"); + xmlCleanupParser(); + return CYBERIADA_XML_ERROR; + } + writer = xmlNewTextWriterMemory(xml_buffer, 0); + if (!writer) { + ERROR("cannot create buffer writter\n"); + xmlBufferFree(xml_buffer); + xmlCleanupParser(); + return CYBERIADA_XML_ERROR; + } + + res = cyberiada_process_encode_sm_document(doc, writer, format, flags); + + if (res == CYBERIADA_NO_ERROR) { + size_t size = xml_buffer->use; + *buffer = (char*)malloc(size + 1); + memcpy(*buffer, xml_buffer->content, size); + (*buffer)[size] = 0; + *buffer_size = size; + } else { + *buffer = NULL; + *buffer_size = 0; + } + + xmlBufferFree(xml_buffer); + xmlFreeTextWriter(writer); + xmlCleanupParser(); + + return res; +} diff --git a/cyberiadaml.h b/cyberiadaml.h index 58373b5..049ff27 100644 --- a/cyberiadaml.h +++ b/cyberiadaml.h @@ -70,11 +70,11 @@ typedef enum { /* SM node & transitions geometry */ typedef struct { - float x, y; + double x, y; } CyberiadaPoint; typedef struct { - float x, y, width, height; + double x, y, width, height; } CyberiadaRect; typedef struct _CyberiadaPolyline { @@ -108,14 +108,26 @@ typedef struct _CyberiadaLink { size_t ref_len; } CyberiadaLink; -/* SM node (state) */ +/* SM node (state) + * + * Comment on the node geometry: + * - if the top-level node (state machine) has geometry, it is calculated in + * absolute coordinates (x: left-to-right, y: top-to-bottom); + * - if the state machine has no geometry, the first level nodes (comporite states, initial + * vertexes, etc.) are calculated in absolute coordinates; + * - the child nodes are calculated in local coordinates relative to the _center_ of the + * parent node. + * + * NOTE: this geomerty is different from the Cyberiada GraphML 1.0 / YED formats geometry + * and is being converted during the document import/export. + */ typedef struct _CyberiadaNode { CyberiadaNodeType type; char* id; size_t id_len; char* title; size_t title_len; - CyberiadaAction* actions; /* for simple & composite nodes */ + CyberiadaAction* actions; /* for simple & composite state nodes */ CyberiadaCommentData* comment_data; /* for comments */ CyberiadaLink* link; /* for submachine states */ CyberiadaPoint* geometry_point; /* for some pseudostates & final state */ @@ -139,7 +151,19 @@ typedef struct _CyberiadaCommentSubject { size_t fragment_len; } CyberiadaCommentSubject; -/* SM edge (transition) */ +/* SM edge (transition) + * + * Comment on the edge geometry: + * - the source and target points are calculated in local coordinates relative + * to the center of the corresponding source/target node; + * - the label point is calculated in local coordinates relative to the geometrical + * center of the edge (based on the bounding rectangle); + * - the polyline points are calculated in local coordinated relative to the center of + * the source node. + * + * NOTE: this geomerty is different from the Cyberiada GraphML 1.0 / YED formats geometry + * and is being converted during the document import/export. + */ typedef struct _CyberiadaEdge { CyberiadaEdgeType type; char* id; @@ -165,6 +189,7 @@ typedef struct _CyberiadaEdge { typedef struct _CyberiadaSM { CyberiadaNode* nodes; CyberiadaEdge* edges; + CyberiadaRect* bounding_rect; struct _CyberiadaSM* next; } CyberiadaSM; @@ -211,22 +236,52 @@ typedef struct { char* markup_language; size_t markup_language_len; } CyberiadaMetainformation; - + +/* SM document node coordinates format */ +typedef enum { + cybCoordNone = 0, /* no geometry information */ + cybCoordAbsolute = 1, /* absolute coordinates (used in YED) */ + cybCoordLeftTop = 2, /* left-top-oriented local coordinates (used in Cyberiada10) */ + cybCoordLocalCenter = 4, /* center-oriented local coordinates (used by default) */ +} CyberiadaGeometryCoordFormat; + +/* SM document edges source/target point placement & coordinates format */ +typedef enum { + cybEdgeNone = 0, + cybEdgeLocalCenter = 1, /* source & target points are bind to a node's center */ + cybEdgeLeftTopBorder = 2, /* source & target points are placed on a node's border with left-top coordinates */ + cybEdgeCenterBorder = 4, /* source & target points are placed on a node's border with center coordinates */ +} CyberiadaGeometryEdgeFormat; + /* SM document */ typedef struct { - char* format; /* SM document format string */ - size_t format_len; /* SM document format string length */ - CyberiadaMetainformation* meta_info; /* SM document metainformation */ - CyberiadaSM* state_machines; /* State machines */ + char* format; /* SM document format string (additional info) */ + size_t format_len; /* SM document format string length */ + CyberiadaMetainformation* meta_info; /* SM document metainformation */ + CyberiadaGeometryCoordFormat geometry_format; /* SM document geometry coordinates format */ + CyberiadaGeometryEdgeFormat edge_geom_format; /* SM document edges geometry format */ + CyberiadaSM* state_machines; /* State machines */ } CyberiadaDocument; /* Cyberiada GraphML Library supported formats */ typedef enum { - cybxmlCyberiada10 = 0, /* Cyberiada 1.0 format */ - cybxmlYED = 1, /* Old YED-based Berloga/Ostranna format */ - cybxmlUnknown = 99 /* Format is not specified */ + cybxmlCyberiada10 = 0, /* Cyberiada 1.0 format */ + cybxmlYED = 1, /* Old YED-based Berloga/Ostranna format */ + cybxmlUnknown = 99 /* Format is not specified */ } CyberiadaXMLFormat; - + +/* Cyberiada GraphML Library import/export flags */ +#define CYBERIADA_FLAG_NO 0 +#define CYBERIADA_FLAG_ABSOLUTE_GEOMETRY 1 /* convert imported geometry to absolute coordinates */ +#define CYBERIADA_FLAG_LEFTTOP_LOCAL_GEOMETRY 2 /* convert imported geometry to left-top-oriented local coordinates */ +#define CYBERIADA_FLAG_CENTER_LOCAL_GEOMETRY 4 /* convert imported geometry to center-oriented local coordinates */ +#define CYBERIADA_FLAG_CENTER_EDGE_GEOMETRY 8 /* convert imported geometry to center edge coordinates */ +#define CYBERIADA_FLAG_LEFTTOP_BORDER_EDGE_GEOMETRY 16 /* convert imported geometry to border edge coordinates (left-top) */ +#define CYBERIADA_FLAG_CENTER_BORDER_EDGE_GEOMETRY 32 /* convert imported geometry to border edge coordinates (center) */ +#define CYBERIADA_FLAG_RECONSTRUCT_GEOMETRY 64 /* reconstruct absent node/edge geometry on import */ +#define CYBERIADA_FLAG_SKIP_GEOMETRY 128 /* skip geometry node/edge during import/export */ +#define CYBERIADA_FLAG_ROUND_GEOMETRY 256 /* export geometry with round coordinates to 0.001 */ + /* ----------------------------------------------------------------------------- * The Cyberiada GraphML error codes * ----------------------------------------------------------------------------- */ @@ -263,10 +318,19 @@ typedef enum { /* Read an XML file and decode the SM structure */ /* Allocate the SM document structure first */ - int cyberiada_read_sm_document(CyberiadaDocument* doc, const char* filename, CyberiadaXMLFormat format); + int cyberiada_read_sm_document(CyberiadaDocument* doc, const char* filename, CyberiadaXMLFormat format, int flags); /* Encode the SM document structure and write the data to an XML file */ - int cyberiada_write_sm_document(CyberiadaDocument* doc, const char* filename, CyberiadaXMLFormat format); + int cyberiada_write_sm_document(CyberiadaDocument* doc, const char* filename, CyberiadaXMLFormat format, int flags); + + /* Decode the SM structure */ + /* Allocate the SM document structure first */ + int cyberiada_decode_sm_document(CyberiadaDocument* doc, const char* buffer, size_t buffer_size, + CyberiadaXMLFormat format, int flags); + + /* Encode the SM document structure */ + int cyberiada_encode_sm_document(CyberiadaDocument* doc, char** buffer, size_t* buffer_size, + CyberiadaXMLFormat format, int flags); /* Print the SM structure to stdout */ int cyberiada_print_sm_document(CyberiadaDocument* doc); @@ -301,7 +365,15 @@ typedef enum { /* Allocate and initialize the SM polyline structure in memory */ CyberiadaPolyline* cyberiada_new_polyline(void); - /* Allocate and initialize the SM metainformation structure in memory */ + /* Free the SM polyline structure in memory */ + int cyberiada_destroy_polyline(CyberiadaPolyline* polyline); + + /* Change the SM document geometry format and convert the SMs geometry data */ + int cyberiada_convert_document_geometry(CyberiadaDocument* doc, + CyberiadaGeometryCoordFormat new_format, + CyberiadaGeometryEdgeFormat new_edge_format); + + /* Allocate and initialize the SM metainformation structure in memory */ CyberiadaMetainformation* cyberiada_new_meta(void); /* Initialize and copy string. Use this function to initialize strings in Cyberiada structures */ diff --git a/geometry.c b/geometry.c new file mode 100644 index 0000000..5c85d58 --- /dev/null +++ b/geometry.c @@ -0,0 +1,1209 @@ +/* ----------------------------------------------------------------------------- + * The Cyberiada GraphML library implemention + * + * The geometry utilities + * + * Copyright (C) 2024 Alexey Fedoseev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/ + * + * ----------------------------------------------------------------------------- */ + +#include +#include +#include + +#include "geometry.h" +#include "cyb_error.h" + +#define DEFAULT_NODE_SIZE 100 + +/* ----------------------------------------------------------------------------- + * Basic geometry functions + * ----------------------------------------------------------------------------- */ + +CyberiadaPoint* cyberiada_new_point(void) +{ + CyberiadaPoint* p = (CyberiadaPoint*)malloc(sizeof(CyberiadaPoint)); + memset(p, 0, sizeof(CyberiadaPoint)); + return p; +} + +CyberiadaPoint* cyberiada_copy_point(CyberiadaPoint* src) +{ + CyberiadaPoint* dst; + if (!src) { + return NULL; + } + dst = cyberiada_new_point(); + dst->x = src->x; + dst->y = src->y; + return dst; +} + +static void cyberiada_update_point(CyberiadaPoint* p, double dx, double dy) +{ + if (!p) return ; + p->x += dx; + p->y += dy; +} + +static double round_number(double num) +{ + double value = (int)(num * 1000.0 + .5); + return (double)value / 1000.0; +} + +static void cyberiada_round_point(CyberiadaPoint* p) +{ + if (!p) return ; + p->x = round_number(p->x); + p->y = round_number(p->y); +} + +CyberiadaRect* cyberiada_new_rect(void) +{ + CyberiadaRect* r = (CyberiadaRect*)malloc(sizeof(CyberiadaRect)); + memset(r, 0, sizeof(CyberiadaRect)); + return r; +} + +CyberiadaRect* cyberiada_copy_rect(CyberiadaRect* src) +{ + CyberiadaRect* dst; + if (!src) { + return NULL; + } + dst = cyberiada_new_rect(); + dst->x = src->x; + dst->y = src->y; + dst->width = src->width; + dst->height = src->height; + return dst; +} + +static void cyberiada_update_rect(CyberiadaRect* r, double dx, double dy) +{ + if (!r) return ; + r->x += dx; + r->y += dy; +} + +static void cyberiada_round_rect(CyberiadaRect* r) +{ + if (!r) return ; + r->x = round_number(r->x); + r->y = round_number(r->y); + r->width = round_number(r->width); + r->height = round_number(r->height); +} + +CyberiadaPolyline* cyberiada_new_polyline(void) +{ + CyberiadaPolyline* pl = (CyberiadaPolyline*)malloc(sizeof(CyberiadaPolyline)); + memset(pl, 0, sizeof(CyberiadaPolyline)); + return pl; +} + +CyberiadaPolyline* cyberiada_copy_polyline(CyberiadaPolyline* src) +{ + CyberiadaPolyline *dst = NULL, *prev, *pl; + if (!src) { + return NULL; + } + do { + pl = cyberiada_new_polyline(); + pl->point.x = src->point.x; + pl->point.y = src->point.y; + if (dst) { + prev->next = pl; + } else { + dst = pl; + } + prev = pl; + src = src->next; + } while (src); + return dst; +} + +int cyberiada_destroy_polyline(CyberiadaPolyline* polyline) +{ + CyberiadaPolyline* pl; + do { + pl = polyline; + polyline = polyline->next; + free(pl); + } while (polyline); +} + +static void cyberiada_update_polyline(CyberiadaPolyline* pl, double dx, double dy) +{ + if (!pl) return ; + do { + cyberiada_update_point(&(pl->point), dx, dy); + pl = pl->next; + } while (pl); +} + +/* ----------------------------------------------------------------------------- + * Geometry transformations + * ----------------------------------------------------------------------------- */ + +static int cyberiada_extend_rect_p(CyberiadaRect* rect, + double x, double y, + CyberiadaGeometryCoordFormat format) +{ + double delta; + if (!rect) { + ERROR("Cannot extend rect with point\n"); + return CYBERIADA_BAD_PARAMETER; + } + if (format == cybCoordAbsolute || format == cybCoordLeftTop) { + if (x < rect->x) { + delta = rect->x - x; + rect->width += delta; + rect->x -= delta; + } + if (x > rect->x + rect->width) { + delta = x - rect->x - rect->width; + rect->width += delta; + rect->x += delta; + } + if (y < rect->y) { + delta = rect->y - y; + rect->height += delta; + rect->y -= delta; + } + if (y > rect->y + rect->height) { + delta = y - rect->y - rect->height; + rect->height += delta; + rect->y += delta; + } + } else if (format == cybCoordLocalCenter) { + double r_w = rect->width / 2.0; + double r_h = rect->height / 2.0; + if (x < rect->x - r_w) { + delta = rect->x - r_w - x; + rect->width += delta; + rect->x -= delta / 2.0; + } + if (x > rect->x + r_w) { + delta = x - rect->x - r_w; + rect->width += delta; + rect->x += delta / 2.0; + } + if (y < rect->y - r_h) { + delta = rect->y - r_h - y; + rect->height += delta; + rect->y -= delta / 2.0; + } + if (y > rect->y + r_h) { + delta = y - rect->y - r_h; + rect->height += delta; + rect->y += delta / 2.0; + } + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_extend_rect_r(CyberiadaRect* result, + CyberiadaRect* rect, + CyberiadaGeometryCoordFormat format) +{ + if (!result || !rect) { + ERROR("Cannot extend rect with rect\n"); + return CYBERIADA_BAD_PARAMETER; + } + if (format == cybCoordLocalCenter) { + cyberiada_extend_rect_p(result, + rect->x - rect->width / 2.0, + rect->y - rect->height / 2.0, + format); + cyberiada_extend_rect_p(result, + rect->x + rect->width / 2.0, + rect->y + rect->height / 2.0, + format); + } else { + cyberiada_extend_rect_p(result, + rect->x, + rect->y, + format); + cyberiada_extend_rect_p(result, + rect->x + rect->width, + rect->y + rect->height, + format); + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_convert_geometry(CyberiadaRect* rect, + CyberiadaGeometryCoordFormat from_format, + CyberiadaGeometryCoordFormat to_format, + double* delta_x, double* delta_y) +{ + if (!rect || !delta_x || !delta_y) { + ERROR("Cannot convert geometry\n"); + return CYBERIADA_BAD_PARAMETER; + } + + if (from_format == to_format) { + *delta_x = *delta_y = 0.0; + return CYBERIADA_NO_ERROR; + } + + if (from_format == cybCoordAbsolute && to_format == cybCoordLeftTop) { + /* (x, y, w, h) -> (x - px, y - py, w, h) */ + *delta_x = -rect->x; + *delta_y = -rect->y; + } else if (from_format == cybCoordAbsolute && to_format == cybCoordLocalCenter) { + /* (x, y, w, h) -> (x - px - pw/2, y - py - ph/2, w, h) */ + *delta_x = rect->x - rect->x - rect->width / 2.0; + *delta_y = rect->y - rect->y - rect->height / 2.0; + } else if (from_format == cybCoordLeftTop && to_format == cybCoordLocalCenter) { + /* (x, y, w, h) -> (x - pw/2, y - ph/2, w, h) */ + *delta_x = rect->x - rect->width / 2.0; + *delta_y = rect->y - rect->height / 2.0; + } else if (from_format == cybCoordLeftTop && to_format == cybCoordAbsolute) { + /* (x, y, w, h) -> (x + px, y + py, w, h) */ + *delta_x = rect->x; + *delta_y = rect->y; + } else if (from_format == cybCoordLocalCenter && to_format == cybCoordAbsolute) { + /* (x, y, w, h) -> (x + px + pw/2, y + py + ph/2, w, h) */ + *delta_x = rect->x + rect->x + rect->width / 2.0; + *delta_y = rect->y + rect->y + rect->height / 2.0; + } else if (from_format == cybCoordLocalCenter && to_format == cybCoordLeftTop) { + /* (x, y, w, h) -> (x + pw/2, y + ph/2, w, h) */ + *delta_x = rect->x + rect->width / 2.0; + *delta_y = rect->y + rect->height / 2.0; + } else { + ERROR("bad node coordinates conversion from %d to %d\n", from_format, to_format); + return CYBERIADA_BAD_PARAMETER; + } + + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_convert_coordinates(CyberiadaNode* parent, + double x, double y, + double* to_x, double* to_y, + CyberiadaGeometryCoordFormat from_format, + CyberiadaGeometryCoordFormat to_format) +{ + if (!to_x || !to_y) { + ERROR("Cannot convert coordinates\n"); + return CYBERIADA_BAD_PARAMETER; + } + if (!parent || !parent->geometry_rect || from_format == to_format) { + *to_x = x; + *to_y = y; + } else { + CyberiadaRect* rect = parent->geometry_rect; + double delta_x, delta_y; + cyberiada_convert_geometry(rect, from_format, to_format, &delta_x, &delta_y); + cyberiada_convert_coordinates(parent->parent, + x + delta_x, y + delta_y, + to_x, to_y, + from_format, + to_format); + } + + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_build_bounding_rect(CyberiadaNode* node, + CyberiadaRect* result, + CyberiadaGeometryCoordFormat format) +{ + if (!node || !result) { + ERROR("Cannot build bounding rect\n"); + return CYBERIADA_BAD_PARAMETER; + } + while (node) { + if (node->geometry_point) { + cyberiada_extend_rect_p(result, + node->geometry_point->x, + node->geometry_point->y, + format); + } + if (node->geometry_rect) { + cyberiada_extend_rect_r(result, + node->geometry_rect, + format); + } + if (node->children) { + int res = cyberiada_build_bounding_rect(node->children, result, format); + if (res != CYBERIADA_NO_ERROR) { + return res; + } + } + node = node->next; + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_convert_node_geometry(CyberiadaNode* node, + CyberiadaRect* parent_rect, + CyberiadaGeometryCoordFormat from_format, + CyberiadaGeometryCoordFormat to_format) +{ + double delta_x, delta_y; + + if (!node || !parent_rect) { + ERROR("Cannot convert node geometry\n"); + return CYBERIADA_BAD_PARAMETER; + } + + if (from_format == to_format) { + return CYBERIADA_NO_ERROR; + } + + cyberiada_convert_geometry(parent_rect, from_format, to_format, &delta_x, &delta_y); + + while (node) { + if (node->geometry_point) { + cyberiada_update_point(node->geometry_point, delta_x, delta_y); + } + if (node->geometry_rect) { + cyberiada_update_rect(node->geometry_rect, delta_x, delta_y); + } + if (node->children) { + int res = cyberiada_convert_node_geometry(node->children, + node->geometry_rect ? node->geometry_rect: parent_rect, + from_format, + to_format); + if (res != CYBERIADA_NO_ERROR) { + return res; + } + } + node = node->next; + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_is_point_node(CyberiadaNode* node) +{ + if (!node) return 0; + return node->type & (cybNodeInitial | cybNodeFinal | cybNodeTerminate | + cybNodeEntryPoint | cybNodeExitPoint | + cybNodeShallowHistory | cybNodeDeepHistory | + cybNodeFork | cybNodeJoin); +} + +static int cyberiada_is_rect_node(CyberiadaNode* node) +{ + if (!node) return 0; + return !cyberiada_is_point_node(node); +} + +static int cyberiada_rect_contains_point(CyberiadaRect* rect, + CyberiadaPoint* point, + CyberiadaGeometryCoordFormat format) +{ + if (!rect || !point) return 0; + if (format == cybCoordAbsolute || format == cybCoordLeftTop) { + return (point->x >= rect->x && + point->y >= rect->y && + point->x <= rect->x + rect->width && + point->y <= rect->y + rect->height); + } else if (format == cybCoordLocalCenter) { + return (point->x >= -rect->width / 2.0 + rect->x && + point->y >= -rect->height / 2.0 + rect->y && + point->x <= rect->width / 2.0 + rect->x && + point->y <= rect->height / 2.0 + rect->y); + } else { + return 0; + } +} + +static int cyberiada_rect_contains_rect(CyberiadaRect* rect, + CyberiadaRect* r, + CyberiadaGeometryCoordFormat format) +{ + if (!rect || !r) return 0; + if (format == cybCoordAbsolute || format == cybCoordLeftTop) { + return (r->x >= rect->x && + r->y >= rect->y && + r->x + r->width <= rect->x + rect->width && + r->y + r->height <= rect->y + rect->height); + } else if (format == cybCoordLocalCenter) { + return (rect->x - r->width / 2.0 >= rect->x - rect->width / 2.0 && + rect->y - r->height / 2.0 >= rect->y - rect->height / 2.0 && + rect->x + r->width / 2.0 <= rect->x + rect->width / 2.0 && + rect->y + r->height / 2.0 <= rect->y + rect->height / 2.0); + } else { + return 0; + } +} + +static int cyberiada_node_contains_point(CyberiadaNode* node, + CyberiadaPoint* point, + CyberiadaGeometryCoordFormat format) +{ + int res; + CyberiadaRect* bound; + if (!node || !point) return 0; + if (!node->geometry_rect) return 0; + + bound = cyberiada_copy_rect(node->geometry_rect); + cyberiada_build_bounding_rect(node, bound, format); + + res = cyberiada_rect_contains_point(bound, point, format); + free(bound); + return res; +} + +static int cyberiada_reconstruct_nodes(CyberiadaNode* node, + CyberiadaRect* rect, + CyberiadaGeometryCoordFormat format) +{ + /* TODO reconstruction algorithm: + + - init the reconstruct queue + - examine the children nodes + - child is the parent itself and has geometry - recurcively continue the algorithm + - child is the parent but has no geometry + - build the bounding rect as possible limitation + - save the node in the reconstruct queue + - child is atomar but has no geometry - save the node in the reconstruct list + - recunstruct geometry for nodes from the list inside the box [if parent has geometry] + - go through the reconstructed children + - if parent has no geometry, reconstruct the geometry + + Right now: + + - determine the parent borders + - put nodes w/o geometry somewhere in the parent + */ + + CyberiadaNode* start_node = node; + if (!node && !rect) { + ERROR("Cannot reconstruct node geometry\n"); + return CYBERIADA_BAD_PARAMETER; + } + + while (node) { + if (cyberiada_is_point_node(node) && !node->geometry_point) { + CyberiadaPoint* new_point = cyberiada_new_point(); + CyberiadaNode* n; + int intersects; + do { + if (format == cybCoordAbsolute) { + new_point->x = rect->x + rect->width * rand(); + new_point->y = rect->y + rect->height * rand(); + } else if (format == cybCoordLeftTop) { + new_point->x = rect->width * rand(); + new_point->y = rect->height * rand(); + } else if (format == cybCoordLocalCenter) { + new_point->x = rect->width * (-0.5 + rand()); + new_point->y = rect->height * (-0.5 + rand()); + } else { + free(new_point); + return CYBERIADA_BAD_PARAMETER; + } + + intersects = 0; + for (n = start_node; n->next; n = n->next) { + if (n == node) continue; + if (n->geometry_rect && cyberiada_rect_contains_point(n->geometry_rect, new_point, format)) { + intersects = 1; + break; + } + } + + } while (intersects); + + node->geometry_point = new_point; + } + + if (cyberiada_is_rect_node(node) && !node->geometry_rect) { + CyberiadaRect* new_rect = cyberiada_new_rect(); + CyberiadaNode* n; + int intersects; + double node_width = DEFAULT_NODE_SIZE, node_height = DEFAULT_NODE_SIZE; + if (node_width < rect->width) { + node_width = rect->width / 2.0; + } + if (node_height < rect->height) { + node_height = rect->height / 2.0; + } + do { + new_rect->width = node_width; + new_rect->height = node_height; + if (format == cybCoordAbsolute) { + new_rect->x = rect->x + (rect->width - node_width) * rand(); + new_rect->y = rect->y + (rect->height - node_height) * rand(); + } else if (format == cybCoordLeftTop) { + new_rect->x = (rect->width - node_width) * rand(); + new_rect->y = (rect->height - node_height) * rand(); + } else if (format == cybCoordLocalCenter) { + new_rect->x = (rect->width - node_width) * (-0.5 + rand()); + new_rect->y = (rect->height - node_height) * (-0.5 + rand()); + } else { + free(new_rect); + return CYBERIADA_BAD_PARAMETER; + } + + intersects = 0; + for (n = start_node; n->next; n = n->next) { + if (n == node) continue; + if (n->geometry_rect + && cyberiada_rect_contains_rect(n->geometry_rect, new_rect, format)) { + intersects = 1; + break; + } + } + + } while (intersects); + + node->geometry_rect = new_rect; + } + + if (node->children) { + cyberiada_build_bounding_rect(node->children, node->geometry_rect, format); + int res = cyberiada_reconstruct_nodes(node->children, node->geometry_rect, format); + if (res != CYBERIADA_NO_ERROR) { + return res; + } + } + node = node->next; + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_reconstruct_edge(CyberiadaPoint** source_point, + CyberiadaNode* source, + CyberiadaPoint** target_point, + CyberiadaNode* target, + CyberiadaGeometryCoordFormat format, + CyberiadaGeometryEdgeFormat edge_format) +{ + CyberiadaPoint* point; + + if (!source_point || !source || !target_point || !target) { + ERROR("Cannot reconstruct edge\n"); + return CYBERIADA_BAD_PARAMETER; + } + + if (edge_format == cybEdgeLocalCenter) { + + if (!*source_point) { + *source_point = cyberiada_new_point(); + } + + if (!*target_point) { + *target_point = cyberiada_new_point(); + } + + return CYBERIADA_NO_ERROR; + + } + + return CYBERIADA_NO_ERROR; + /* + + if (source->geometry_point && !*source_point) { + *source_point = cyberiada_new_point(); + } + if (target->geometry_point && !*target_point) { + *target_point = cyberiada_new_point(); + } + + } + + if (*source_point && *target_point) { + return CYBERIADA_NO_ERROR; + } + + if (!*source_point) { + CyberiadaPoint* to_point; + if (*target_point) { + to_point = *target_point; + } else if (target->geometry_point) { + to_point = target->geometry_point; + } else { + } + cyberiada_edge_build_border_point(source_point, + source->geometry_rect, + to_point, + edge_format); + if (source->geometry_rect) { + } + + + if (edge_format == cybEdgeLeftTopBorder) { + source->geometry_rect->width / 2.0; + + + } else if (edge_format == cybEdgeCenterBorder) { + + } else { + ERROR("Wrong edge format %d\n", edge_format); + return CYBERIADA_BAD_PARAMETER; + } + + return CYBERIADA_NO_ERROR;*/ +} + +static int cyberiada_reconstruct_edges(CyberiadaEdge* edge, + CyberiadaGeometryCoordFormat format, + CyberiadaGeometryEdgeFormat edge_format) +{ + while (edge) { + if (edge->source && (edge->source->geometry_rect || edge->source->geometry_point) && + edge->target && (edge->target->geometry_rect || edge->target->geometry_point)) { + + if (!edge->geometry_source_point || !edge->geometry_target_point) { + cyberiada_reconstruct_edge(&(edge->geometry_source_point), + edge->source, + &(edge->geometry_target_point), + edge->target, + format, + edge_format); + } + + } + edge = edge->next; + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_reconstruct_document_geometry(CyberiadaDocument* doc) +{ + CyberiadaSM* sm; + if (!doc || !doc->state_machines) { + ERROR("Cannot round SM document geometry\n"); + return CYBERIADA_BAD_PARAMETER; + } + for (sm = doc->state_machines; sm; sm = sm->next) { + cyberiada_reconstruct_nodes(sm->nodes, sm->bounding_rect, doc->geometry_format); + cyberiada_reconstruct_edges(sm->edges, doc->geometry_format, doc->edge_geom_format); + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_clean_nodes_geometry(CyberiadaNode* node) +{ + while (node) { + if (node->geometry_point) { + free(node->geometry_point); + node->geometry_point = NULL; + } + if (node->geometry_rect) { + free(node->geometry_rect); + node->geometry_rect = NULL; + } + if (node->children) { + int res = cyberiada_clean_nodes_geometry(node->children); + if (res != CYBERIADA_NO_ERROR) { + return res; + } + } + node = node->next; + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_clean_edge_geometry(CyberiadaEdge* edge) +{ + if (edge->geometry_polyline) { + cyberiada_destroy_polyline(edge->geometry_polyline); + edge->geometry_polyline = NULL; + } + if (edge->geometry_source_point) { + free(edge->geometry_source_point); + edge->geometry_source_point = NULL; + } + if (edge->geometry_target_point) { + free(edge->geometry_target_point); + edge->geometry_target_point = NULL; + } + if (edge->geometry_label_point) { + free(edge->geometry_label_point); + edge->geometry_label_point = NULL; + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_clean_edges_geometry(CyberiadaEdge* edge) +{ + while (edge) { + cyberiada_clean_edge_geometry(edge); + edge = edge->next; + } + return CYBERIADA_NO_ERROR; +} + +int cyberiada_clean_document_geometry(CyberiadaDocument* doc) +{ + CyberiadaSM* sm; + + if (!doc) { + return CYBERIADA_BAD_PARAMETER; + } + + for (sm = doc->state_machines; sm; sm = sm->next) { + cyberiada_clean_nodes_geometry(sm->nodes); + cyberiada_clean_edges_geometry(sm->edges); + if (sm->bounding_rect) { + free(sm->bounding_rect); + sm->bounding_rect = NULL; + } + } + + doc->geometry_format = cybCoordNone; + doc->edge_geom_format = cybEdgeNone; + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_convert_edges_geometry(CyberiadaEdge* edges, + CyberiadaRect* parent_rect, + CyberiadaGeometryCoordFormat from_format, + CyberiadaGeometryCoordFormat to_format) +{ +/* CyberiadaSM* sm; + CyberiadaNode* node; + CyberiadaEdge* edge; + CyberiadaPolyline* pl; + double from_x, from_y, to_x, to_y; + double src_from_x, src_from_y, src_to_x, src_to_y, tgt_from_x, tgt_from_y, tgt_to_x, tgt_to_y; + + for (edge = edges; edge; edge = edge->next) { + if (edge->source && (edge->source->geometry_rect || edge->source->geometry_point) && + edge->target && (edge->target->geometry_rect || edge->target->geometry_point) && + (edge->geometry_source_point || + edge->geometry_target_point || + edge->geometry_polyline || + edge->geometry_label_point)) { + + from_x = from_y = to_x = to_y = 0.0; + if (edge->source->geometry_point) { + from_x += edge->source->geometry_point->x; + from_y += edge->source->geometry_point->y; + } else { + from_x += edge->source->geometry_rect->x + edge->source->geometry_rect->width / 2.0; + from_y += edge->source->geometry_rect->y + edge->source->geometry_rect->height / 2.0; + if (edge->geometry_source_point) { + from_x += edge->geometry_source_point->x; + from_y += edge->geometry_source_point->y; + } + } + if (edge->target->geometry_point) { + to_x += edge->target->geometry_point->x; + to_y += edge->target->geometry_point->y; + } else { + to_x += edge->target->geometry_rect->x + edge->target->geometry_rect->width / 2.0; + to_y += edge->target->geometry_rect->y + edge->target->geometry_rect->height / 2.0; + if (edge->geometry_target_point) { + to_x += edge->geometry_target_point->x; + to_y += edge->geometry_target_point->y; + } + } + + if (edge->geometry_polyline) { + double first_p_x = edge->geometry_polyline->point.x; + double first_p_y = edge->geometry_polyline->point.y; + double last_p_x; + double last_p_y; + + pl = edge->geometry_polyline; + while (pl->next) pl = pl->next; + last_p_x = pl->point.x; + last_p_y = pl->point.y; + + src_from_x = from_x; + src_from_y = from_y; + src_to_x = first_p_x; + src_to_y = first_p_y; + tgt_from_x = last_p_x; + tgt_from_y = last_p_y; + tgt_to_x = to_x; + tgt_to_y = to_y; + } else { + src_from_x = tgt_from_x = from_x; + src_from_y = tgt_from_y = from_y; + src_to_x = tgt_to_x = to_x; + src_to_y = tgt_to_y = to_y; + } + + DEBUG("edge (%f; %f) -> (%f; %f) and (%f; %f) -> (%f; %f)\n", + src_from_x, src_from_y, src_to_x, src_to_y, + tgt_from_x, tgt_from_y, tgt_to_x, tgt_to_y); + + if (edge->geometry_source_point) { + if (edge->source->geometry_point) { + if (src_from_x == src_to_x) { + edge->geometry_source_point->x = 0.0; + if (src_from_y >= src_to_y) { + edge->geometry_source_point->y = -PSEUDO_NODE_SIZE / 2.0; + } else { + edge->geometry_source_point->y = PSEUDO_NODE_SIZE / 2.0; + } + } else { + double alpha = atan((src_from_y - src_to_y) / (src_to_x - src_from_x)); + edge->geometry_source_point->x = cos(alpha) * PSEUDO_NODE_SIZE / 2.0; + edge->geometry_source_point->y = -sin(alpha) * PSEUDO_NODE_SIZE / 2.0; + } + } else { + if (src_from_x == src_to_x) { + edge->geometry_source_point->x = edge->source->geometry_rect->width / 2.0; + if (src_from_y >= src_to_y) { + edge->geometry_source_point->y = 0.0; + } else { + edge->geometry_source_point->y = edge->source->geometry_rect->height; + } + } else { + double alpha = atan((src_from_y - src_to_y) / (src_to_x - src_from_x)); + if (src_to_x < src_from_x) alpha += M_PI; + double alpha_g = 180.0 * alpha / M_PI; + DEBUG("src alpha %f\n", alpha_g); + if (alpha_g < 0.0) alpha_g += 360.0; + if (alpha_g <= 45.0 || alpha_g > 315.0) { + edge->geometry_source_point->x = edge->source->geometry_rect->width; + edge->geometry_source_point->y += (-tan(alpha) * edge->source->geometry_rect->width + + edge->source->geometry_rect->height) / 2.0; + } else if (alpha_g > 45.0 && alpha_g <= 135.0) { + edge->geometry_source_point->x += ((double)tan(alpha) * edge->source->geometry_rect->height + + edge->source->geometry_rect->width) / 2.0; + edge->geometry_source_point->y = 0.0; + } else if (alpha_g > 135.0 && alpha_g <= 225.0) { + edge->geometry_source_point->x = 0.0; + edge->geometry_source_point->y += (-(double)tan(alpha) * edge->source->geometry_rect->width + + edge->source->geometry_rect->height) / 2.0; + } else { + edge->geometry_source_point->x += (-(double)tan(alpha) * edge->source->geometry_rect->height + + edge->source->geometry_rect->width) / 2.0; + edge->geometry_source_point->y = edge->source->geometry_rect->height; + } + + if (edge->geometry_source_point->x < 0) { + edge->geometry_source_point->x = 0.0; + } + if (edge->geometry_source_point->x > edge->source->geometry_rect->width) { + edge->geometry_source_point->x = edge->source->geometry_rect->width; + } + if (edge->geometry_source_point->y < 0) { + edge->geometry_source_point->y = 0.0; + } + if (edge->geometry_source_point->y > edge->source->geometry_rect->height) { + edge->geometry_source_point->y = edge->source->geometry_rect->height; + } + } + } + DEBUG("sp: (%f; %f)\n", edge->geometry_source_point->x, edge->geometry_source_point->y); + } + + if (edge->geometry_target_point) { + if (edge->target->geometry_point) { + if (tgt_from_x == tgt_to_x) { + edge->geometry_target_point->x = 0.0; + if (tgt_from_y >= tgt_to_y) { + edge->geometry_target_point->y = PSEUDO_NODE_SIZE / 2.0; + } else { + edge->geometry_target_point->y = -PSEUDO_NODE_SIZE / 2.0; + } + } else { + double alpha = atan((tgt_from_y - tgt_to_y) / (tgt_to_x - tgt_from_x)); + edge->geometry_target_point->x = cos(alpha) * PSEUDO_NODE_SIZE / 2.0; + edge->geometry_target_point->y = -sin(alpha) * PSEUDO_NODE_SIZE / 2.0; + } + } else { + if (tgt_from_x == tgt_to_x) { + edge->geometry_target_point->x = edge->target->geometry_rect->width / 2.0; + if (tgt_from_y >= tgt_to_y) { + edge->geometry_target_point->y = edge->target->geometry_rect->height; + } else { + edge->geometry_target_point->y = 0.0; + } + } else { + double alpha = atan((tgt_from_y - tgt_to_y) / (tgt_to_x - tgt_from_x)); + if (tgt_to_x < tgt_from_x) alpha += M_PI; + alpha += M_PI; /* target = incoming edge */ + /*double alpha_g = 180.0 * alpha / M_PI; + DEBUG("tgt alpha %f\n", alpha_g); + if (alpha_g < 0.0) alpha_g += 360.0; + if (alpha_g <= 45.0 || alpha_g > 315.0) { + edge->geometry_target_point->x = edge->target->geometry_rect->width; + edge->geometry_target_point->y += (-tan(alpha) * edge->target->geometry_rect->width + + edge->target->geometry_rect->height) / 2.0; + } else if (alpha_g > 45.0 && alpha_g <= 135.0) { + edge->geometry_target_point->x += (tan(alpha) * edge->target->geometry_rect->height + + edge->target->geometry_rect->width) / 2.0; + edge->geometry_target_point->y = 0.0; + } else if (alpha_g > 135.0 && alpha_g <= 225.0) { + edge->geometry_target_point->x = 0.0; + edge->geometry_target_point->y += (-tan(alpha) * edge->target->geometry_rect->width + + edge->target->geometry_rect->height) / 2.0; + } else { + edge->geometry_target_point->x += (-tan(alpha) * edge->target->geometry_rect->height + + edge->target->geometry_rect->width) / 2.0; + edge->geometry_target_point->y = edge->target->geometry_rect->height; + } + } + + if (edge->geometry_target_point->x < 0) { + edge->geometry_target_point->x = 0.0; + } + if (edge->geometry_target_point->x > edge->target->geometry_rect->width) { + edge->geometry_target_point->x = edge->target->geometry_rect->width; + } + if (edge->geometry_target_point->y < 0) { + edge->geometry_target_point->y = 0.0; + } + if (edge->geometry_target_point->y > edge->target->geometry_rect->height) { + edge->geometry_target_point->y = edge->target->geometry_rect->height; + } + } + DEBUG("tp: (%f; %f)\n", edge->geometry_target_point->x, edge->geometry_target_point->y); + } + } + }*/ + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_convert_edge_geometry(CyberiadaEdge* edge, + CyberiadaGeometryCoordFormat from_format, + CyberiadaGeometryCoordFormat to_format, + CyberiadaGeometryEdgeFormat from_edge_format, + CyberiadaGeometryEdgeFormat to_edge_format) +{ + while (edge) { + if (edge->source && (edge->source->geometry_rect || edge->source->geometry_point) && + edge->target && (edge->target->geometry_rect || edge->target->geometry_point)) { + /* + if (edge->geometry_source_point) { + + } + + if (edge->geometry_target_point) { + cyberiada_convert_edge_source_target_geometry(edge, + from_format, + to_fotmat); + } + + if (edge->geometry_polyline) { + } + + if (edge->geometry_label_point) { + }*/ + } else { + /* TODO: update non-trivial edges properly */ + cyberiada_clean_edge_geometry(edge); + } + edge = edge->next; + } + return CYBERIADA_NO_ERROR; +} + +int cyberiada_convert_document_geometry(CyberiadaDocument* doc, + CyberiadaGeometryCoordFormat new_format, + CyberiadaGeometryEdgeFormat new_edge_format) +{ + CyberiadaSM* sm; + CyberiadaRect* bounding_rect; + for (sm = doc->state_machines; sm; sm = sm->next) { + if (!sm->nodes || sm->nodes->next) { + ERROR("SM should have single root node\n"); + return CYBERIADA_BAD_PARAMETER; + } + cyberiada_convert_node_geometry(sm->nodes, sm->bounding_rect, + doc->geometry_format, new_format); + if (sm->edges) { + cyberiada_convert_edge_geometry(sm->edges, + doc->geometry_format, new_format, + doc->edge_geom_format, new_edge_format); + } + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_round_node_geometry(CyberiadaNode* node) +{ + while (node) { + if (node->children) { + if (node->geometry_point) { + cyberiada_round_point(node->geometry_point); + } + if (node->geometry_rect) { + cyberiada_round_rect(node->geometry_rect); + } + cyberiada_round_node_geometry(node->children); + } + node = node->next; + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_round_edges_geometry(CyberiadaEdge* edges) +{ + while (edges) { + if (edges->geometry_source_point) { + cyberiada_round_point(edges->geometry_source_point); + } + if (edges->geometry_target_point) { + cyberiada_round_point(edges->geometry_target_point); + } + if (edges->geometry_label_point) { + cyberiada_round_point(edges->geometry_label_point); + } + if (edges->geometry_polyline) { + CyberiadaPolyline* pl = edges->geometry_polyline; + while (pl) { + cyberiada_round_point(&(pl->point)); + pl = pl->next; + } + } + edges = edges->next; + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_round_document_geometry(CyberiadaDocument* doc) +{ + CyberiadaSM* sm; + if (!doc || !doc->state_machines) { + ERROR("Cannot round SM document geometry\n"); + return CYBERIADA_BAD_PARAMETER; + } + for (sm = doc->state_machines; sm; sm = sm->next) { + cyberiada_round_rect(sm->bounding_rect); + cyberiada_round_node_geometry(sm->nodes); + cyberiada_round_edges_geometry(sm->edges); + } + return CYBERIADA_NO_ERROR; +} + +int cyberiada_import_document_geometry(CyberiadaDocument* doc, + int flags, CyberiadaXMLFormat file_format) +{ + CyberiadaSM* sm; + CyberiadaGeometryCoordFormat old_format, new_format; + CyberiadaGeometryEdgeFormat old_edge_format, new_edge_format; + + if (!doc) { + ERROR("Cannot import document geometry\n"); + return CYBERIADA_BAD_PARAMETER; + } + + if (file_format == cybxmlYED) { + old_format = cybCoordAbsolute; + old_edge_format = cybEdgeLocalCenter; + } else if (file_format == cybxmlCyberiada10) { + old_format = cybCoordLeftTop; + old_edge_format = cybEdgeLeftTopBorder; + } else { + ERROR("Bad XML format %d\n", file_format); + return CYBERIADA_BAD_PARAMETER; + } + + if (flags & CYBERIADA_FLAG_ABSOLUTE_GEOMETRY) { + new_format = cybCoordAbsolute; + } else if (flags & CYBERIADA_FLAG_LEFTTOP_LOCAL_GEOMETRY) { + new_format = cybCoordLeftTop; + } else if (flags & CYBERIADA_FLAG_CENTER_LOCAL_GEOMETRY) { + new_format = cybCoordLocalCenter; + } else { + ERROR("No geometry coordinates flag for import\n"); + return CYBERIADA_BAD_PARAMETER; + } + + if (flags & CYBERIADA_FLAG_CENTER_BORDER_EDGE_GEOMETRY) { + new_edge_format = cybEdgeCenterBorder; + } else if (flags & CYBERIADA_FLAG_LEFTTOP_BORDER_EDGE_GEOMETRY) { + new_edge_format = cybEdgeLeftTopBorder; + } else if (flags & CYBERIADA_FLAG_CENTER_EDGE_GEOMETRY) { + new_edge_format = cybEdgeLocalCenter; + } else { + ERROR("No edge geometry flag for import\n"); + return CYBERIADA_BAD_PARAMETER; + } + + for (sm = doc->state_machines; sm; sm = sm->next) { + if (sm->nodes->geometry_rect) { + sm->bounding_rect = cyberiada_copy_rect(sm->nodes->geometry_rect); + } else { + CyberiadaRect* bounding_rect = cyberiada_new_rect(); + bounding_rect->x = bounding_rect->y = bounding_rect->width = bounding_rect->height = 0.0; + cyberiada_build_bounding_rect(sm->nodes, bounding_rect, old_format); + sm->bounding_rect = bounding_rect; + } + + cyberiada_convert_node_geometry(sm->nodes, sm->bounding_rect, + old_format, new_format); + } + + doc->geometry_format = new_format; + doc->edge_geom_format = new_edge_format; + + if (flags & CYBERIADA_FLAG_RECONSTRUCT_GEOMETRY) { + cyberiada_reconstruct_document_geometry(doc); + } + + for (sm = doc->state_machines; sm; sm = sm->next) { + if (sm->edges) { + cyberiada_convert_edge_geometry(sm->edges, + old_format, new_format, + old_edge_format, new_edge_format); + } + } + + if (flags & CYBERIADA_FLAG_ROUND_GEOMETRY) { + cyberiada_round_document_geometry(doc); + } + return CYBERIADA_NO_ERROR; +} + +int cyberiada_export_document_geometry(CyberiadaDocument* doc, + int flags, CyberiadaXMLFormat file_format) +{ + CyberiadaSM* sm; + CyberiadaGeometryCoordFormat to_format; + CyberiadaGeometryEdgeFormat to_edge_format; + + if (!doc) { + ERROR("Cannot export document geometry\n"); + return CYBERIADA_BAD_PARAMETER; + } + + if (file_format == cybxmlYED) { + to_format = cybCoordAbsolute; + to_edge_format = cybEdgeLocalCenter; + } else if (file_format == cybxmlCyberiada10) { + to_format = cybCoordLeftTop; + to_edge_format = cybEdgeLeftTopBorder; + } else { + ERROR("Bad XML format %d\n", file_format); + return CYBERIADA_BAD_PARAMETER; + } + + if (flags & CYBERIADA_FLAG_RECONSTRUCT_GEOMETRY) { + cyberiada_reconstruct_document_geometry(doc); + } + + if (flags & CYBERIADA_FLAG_ROUND_GEOMETRY) { + cyberiada_round_document_geometry(doc); + } + + for (sm = doc->state_machines; sm; sm = sm->next) { + cyberiada_convert_node_geometry(sm->nodes, sm->bounding_rect, + doc->geometry_format, to_format); + if (sm->edges) { + cyberiada_convert_edge_geometry(sm->edges, + doc->geometry_format, to_format, + doc->edge_geom_format, to_edge_format); + } + } + + return CYBERIADA_NO_ERROR; +} + diff --git a/geometry.h b/geometry.h new file mode 100644 index 0000000..9b40336 --- /dev/null +++ b/geometry.h @@ -0,0 +1,59 @@ +/* ----------------------------------------------------------------------------- + * The Cyberiada GraphML library implemention + * + * The geometry utilities + * + * Copyright (C) 2024 Alexey Fedoseev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/ + * + * ----------------------------------------------------------------------------- */ + +#ifndef __CYBERIADA_GEOMETRY_H +#define __CYBERIADA_GEOMRTRY_H + +#include "cyberiadaml.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----------------------------------------------------------------------------- + * The Cyberiada GraphML geometry utilities + * ----------------------------------------------------------------------------- */ + + CyberiadaPoint* cyberiada_new_point(void); + CyberiadaPoint* cyberiada_copy_point(CyberiadaPoint* src); + + CyberiadaRect* cyberiada_new_rect(void); + CyberiadaRect* cyberiada_copy_rect(CyberiadaRect* src); + + CyberiadaPolyline* cyberiada_new_polyline(void); + CyberiadaPolyline* cyberiada_copy_polyline(CyberiadaPolyline* src); + int cyberiada_destroy_polyline(CyberiadaPolyline* polyline); + + int cyberiada_clean_document_geometry(CyberiadaDocument* doc); + int cyberiada_convert_document_geometry(CyberiadaDocument* doc, + CyberiadaGeometryCoordFormat new_format, + CyberiadaGeometryEdgeFormat new_edge_format); + int cyberiada_import_document_geometry(CyberiadaDocument* doc, + int flags, CyberiadaXMLFormat file_format); + int cyberiada_export_document_geometry(CyberiadaDocument* doc, + int flags, CyberiadaXMLFormat file_format); + +#ifdef __cplusplus +} +#endif + +#endif