blender-gdsimporter/read.c

863 lines
20 KiB
C

#include <stdio.h> // for printf
#include <stdint.h> // for uintxx_t
#include <assert.h> // for assert
#include <errno.h> // for perror
#include <memory.h> // for memcpy
#include <stdlib.h> // for malloc, free
#define LIST_ELEMENTS(a) \
/* file header records */ \
a(HEADER , 0x0002) \
a(BGNLIB , 0x0102) \
a(LIBNAME , 0x0206) \
a(REFLIBS , 0x1F06) \
a(FONTS , 0x2006) \
a(ATTRTABLE , 0x2306) \
a(GENERATIONS , 0x2202) \
a(FORMAT , 0x3602) \
a(MASK , 0x3706) \
a(ENDMASKS , 0x3800) \
a(UNITS , 0x0305) \
/* file tail records */ \
a(ENDLIB , 0x0400) \
/* structure header records */ \
a(BGNSTR , 0x0502) \
a(STRNAME , 0x0606) \
a(ENDEL , 0x1100) \
/* structure tail records */ \
a(ENDSTR , 0x0700) \
/* element header records */ \
a(BOUNDARY , 0x0800) \
a(PATH , 0x0900) \
a(SREF , 0x0A00) \
a(AREF , 0x0B00) \
a(TEXT , 0x0C00) \
a(NODE , 0x1500) \
a(BOX , 0x2D00) \
/* element contents records */ \
a(ELFLAGS , 0x2601) \
a(PLEX , 0x2F03) \
a(LAYER , 0x0D02) \
a(DATATYPE , 0x0E02) \
a(XY , 0x1003) \
a(PATHTYPE , 0x2102) \
a(WIDTH , 0x0F03) \
a(SNAME , 0x1206) \
a(STRANS , 0x1A01) \
a(MAG , 0x1B05) \
a(ANGLE , 0x1C05) \
a(COLROW , 0x1302) \
a(TEXTTYPE , 0x1602) \
a(PRESENTATION, 0x1701) \
a(ASCII_STRING, 0x1906) \
a(NODETYPE , 0x2A02) \
a(BOXTYPE , 0x2E02) \
a(STRING , 0x1906) \
a(PROPATTR , 0x2B02) \
a(PROPVALUE , 0x2C06)
#define a(a,b) \
{ \
.name = #a, \
.ident = b \
},
#define e(a,b) \
a = b,
struct {
const char * name;
uint16_t ident;
} header_elements[] = {
LIST_ELEMENTS(a)
};
enum RECORD_IDENTIFIERS {
LIST_ELEMENTS(e)
};
#undef a
#undef e
#undef LIST_ELEMENTS
typedef struct record_header {
uint16_t len;
uint16_t ident;
} record_header_t;
typedef struct date {
uint16_t year, month, day, hour, minute, second;
} date_t;
void swap(uint8_t * a, uint8_t * b)
{
assert(a);
assert(b);
uint8_t temp = *a;
*a = *b;
*b = temp;
}
void invert(uint8_t * data, size_t s)
{
assert(data);
uint8_t * a = data;
uint8_t * b = data + s - 1;
s /= 2;
while(s--)
{
swap(a,b);
a++;
b--;
}
}
const char * name_from_ident(uint16_t ident)
{
for (int i = 0; i < sizeof(header_elements)/sizeof(header_elements[0]); ++i)
{
if (header_elements[i].ident == ident)
return header_elements[i].name;
}
return NULL;
}
typedef enum {
GSD_ELEM_UNKNOWN = 0,
GDS_ELEM_BOUNDARY,
GDS_ELEM_PATH,
GDS_ELEM_SREF,
GDS_ELEM_AREF,
GDS_ELEM_TEXT,
GDS_ELEM_NODE,
GDS_ELEM_BOX,
} gds_element_type_t;
#define GDS_ELEM_IS_GRAPHICAL(t) ((t == GDS_ELEM_BOUNDARY) || (t == GDS_ELEM_PATH) || (t == GDS_ELEM_SREF) || (t == GDS_ELEM_AREF) || (t == GDS_ELEM_TEXT) || (t == GDS_ELEM_NODE) || (t == GDS_ELEM_BOX))
#define GDS_ELEM_HAS_LAYER(t) ((t == GDS_ELEM_BOUNDARY) || (t == GDS_ELEM_PATH) || (t == GDS_ELEM_TEXT) || (t == GDS_ELEM_NODE) || (t == GDS_ELEM_BOX))
typedef struct {
gds_element_type_t type;
uint16_t elfclass;
int16_t plex;
} gds_element_base_t;
typedef struct {
int32_t x,y;
} gds_coord_t;
typedef struct {
gds_element_base_t base;
size_t point_count;
gds_coord_t * points;
} gds_element_graphical_t;
typedef struct {
gds_element_graphical_t base;
int16_t layer;
int16_t datatype;
} gds_element_boundary_t;
typedef enum {
GDS_PATH_SQUARE = 0,
GDS_PATH_ROUNDED = 1,
GDS_PATH_SQUARE_EXTEND = 2
} gds_path_type_t;
typedef struct {
gds_element_graphical_t base;
int16_t layer;
int16_t datatype;
gds_path_type_t pathtype;
int32_t width;
} gds_element_path_t;
typedef struct {
gds_element_graphical_t base;
int16_t layer;
int16_t nodetype;
} gds_element_node_t;
typedef struct {
gds_element_graphical_t base;
int16_t layer;
int16_t texttype;
int32_t width;
gds_path_type_t pathtype;
uint8_t font_number:2;
struct
{
enum
{
GDS_TEXT_TOP = 0b00,
GDS_TEXT_MIDDLE = 0b01,
GDS_TEXT_BOTTOM = 0b10,
} vertical;
enum
{
GDS_TEXT_LEFT = 0b00,
GDS_TEXT_CENTER = 0b01,
GDS_TEXT_RIGHT = 0b10
} horizontal;
} justification;
char * string;
} gds_element_text_t;
typedef union {
gds_element_type_t type;
gds_element_graphical_t graphical;
/* element containers */
gds_element_boundary_t boundary;
gds_element_path_t path;
gds_element_node_t node;
gds_element_text_t text;
} gds_element_t;
typedef struct structure {
date_t creation, last_access;
char * name;
size_t element_count;
gds_element_t * elements;
} gds_structure_t;
typedef struct gds_file {
uint16_t version; // HEADER data
date_t last_mod, last_access; // BGNLIB data
char * libname; // LIBNAME data, must be freed
enum {
GDS_FMT_ARCHIVED = 0,
GDS_FMT_FILTERED = 1,
} format;
struct {
double units_per_dbunits;
double meters_per_unit;
} units; // UNITS data
size_t structure_count;
gds_structure_t * structures;
} gds_file_t;
/* parser errors */
#define LIST_ERRORS(a) \
a(GDS_NO_ERR , "Success") \
a(GDS_ERR_MISSING_ARGS, "Missing argument(s) to record type") \
a(GDS_ERR_NOMEM , "out of memory") \
a(GDS_ERR_EOF , "unexpected EOF") \
a(GDS_ERR_MISSING_HEADER , "file is missing HEADER record") \
a(GDS_ERR_MISSING_BEGELEM , "found element records without an element start") \
a(GDS_ERR_INVALID_FILE , "file found to be invalid")
#define STR(a, b) #a ": " b,
#define ENUM(a, b) a,
static const char * gds_parser_errstr[] = { LIST_ERRORS(STR) };
typedef enum {
LIST_ERRORS(ENUM)
GDS_ERR_COUNT,
} gds_parser_err_t;
#undef STR
#undef ENUM
#undef LIST_ERRORS
const char * gds_strerror(gds_parser_err_t err) {
if (err >= GDS_ERR_COUNT) {
return "unknown error";
}
return gds_parser_errstr[err];
}
typedef struct parser_state {
uint8_t has_header:1;
gds_structure_t * current_structure;
gds_element_t * current_element;
gds_parser_err_t error;
gds_file_t root;
} parser_state_t;
void free_gds_element(gds_element_t * this)
{
switch(this->type)
{
default:
case GSD_ELEM_UNKNOWN:
break;
case GDS_ELEM_BOUNDARY:
case GDS_ELEM_PATH:
// clear all points
free(this->graphical.points);
this->graphical.point_count = 0;
break;
case GDS_ELEM_TEXT:
free(this->text.string);
break;
}
// clear type
this->type = GSD_ELEM_UNKNOWN;
}
void free_gds_structure(gds_structure_t * this)
{
for (int i = 0; i < this->element_count; ++i)
free_gds_element(this->elements + i);
free(this->name);
free(this->elements);
}
void free_gds_file(gds_file_t * file)
{
for (int i = 0; i < file->structure_count; ++i)
free_gds_structure(file->structures + i);
free(file->libname);
free(file->structures);
}
// cleanup parser state from mem
void free_parser_state(parser_state_t * state)
{
free_gds_file(&state->root);
}
#define READ_STRUCT(file, dest) (fread((dest), sizeof(*(dest)), 1, (file)))
int read_ushort(uint16_t * dest, FILE * file) {
if (!READ_STRUCT(file, dest)) {
return 0;
}
invert((uint8_t*)dest, sizeof(*dest));
return 1;
}
int read_short(int16_t * dest, FILE * file) {
if (!READ_STRUCT(file, dest)) {
return 0;
}
invert((uint8_t*)dest, sizeof(*dest));
return 1;
}
int read_int(int32_t * dest, FILE * file) {
if (!READ_STRUCT(file, dest))
return 0;
invert((uint8_t*)dest, sizeof(*dest));
return 1;
}
int read_date(date_t * dest, FILE * src) {
if (!READ_STRUCT(src, dest)) {
return 0;
}
invert((uint8_t*)&dest->day, 2);
invert((uint8_t*)&dest->month, 2);
invert((uint8_t*)&dest->year, 2);
invert((uint8_t*)&dest->hour, 2);
invert((uint8_t*)&dest->minute, 2);
invert((uint8_t*)&dest->second, 2);
return 1;
}
// reads a 8 byte double value as it is represented in
// a gds file
// from spec: 1 bit sign | 7 bit exp (in 64-excess) | 7 byte mantisse
// with double = sign * 1.mantisse * 64 ** exp
int read_double(double * dest, FILE * file) {
uint8_t src[8];
if (!fread(src, 8, 1, file)) {
return 0;
}
// special value, ZERO
if (*(uint64_t*)(src) == 0) {
*dest = 0;
return 1;
}
// mantissa first
*dest = 1;
for (int i = 1; i < 8; ++i) {
*dest += ((double)src[8-i])/(1L<<i*8);
}
// exp
int exp = src[0] & 0x7F;
exp -= 64;
// exp is base 64
// so implement pow here :)
if (exp > 0)
{
while(exp)
{
*dest *= 16.0;
exp--;
}
}
else
{
while(exp)
{
*dest /= 16.0;
exp++;
}
}
// negation
if (src[0] & 0x80) {
*dest *= -1;
}
return 1;
}
int read_record_header(record_header_t * header, FILE * file) {
if (!read_ushort(&header->len, file) ||
!read_ushort(&header->ident, file))
{
return 0;
}
header->len -= 4;
return 1;
}
int read_coord(gds_coord_t * coord, FILE * file)
{
if (!read_int(&coord->x, file) || !read_int(&coord->y, file))
return 0;
return 1;
}
/* allocate a new element in a structure */
gds_element_t * alloc_elem(gds_structure_t * this, gds_element_type_t type)
{
gds_element_t * new = realloc(this->elements, (this->element_count+1)*sizeof(gds_element_t));
if (new == NULL)
return NULL;
this->elements = new;
new += this->element_count++;
// initialize new element
*new = (gds_element_t){};
new->type = type;
return new;
}
#define DATE_FMT "%2d.%02d.%04d"
#define DATE_ARGS(a) (a).day, (a).month, (a).year
int parse_record(parser_state_t * state, FILE * file)
{
record_header_t header = {};
if (!read_record_header(&header, file))
{
perror("could not read file record");
return 0;
}
#define ASSERT(a) if (!(a)) do { printf("ASSERT %s:%d\n", __FILE__, __LINE__); state->error = GDS_ERR_INVALID_FILE; return 0; } while(0)
#if 0
printf("record %s (%04x)\n", name_from_ident(header.ident), header.ident);
#endif
if (!state->has_header && header.ident != HEADER)
{
state->error = GDS_ERR_MISSING_HEADER;
return 0;
}
switch (header.ident)
{
case HEADER:
ASSERT(header.len == 2);
read_ushort(&state->root.version, file);
state->has_header = 1;
break;
case BGNLIB:
ASSERT(header.len == 2* sizeof(date_t));
read_date(&state->root.last_mod , file);
read_date(&state->root.last_access, file);
break;
case ENDLIB:
if (state->current_structure)
printf("missing (last) ENDSTR\n"); // fixme: better warning system
return 0;
case LIBNAME:
state->root.libname = calloc(1, header.len + 1);
if (state->root.libname == NULL) {
state->error = GDS_ERR_NOMEM;
return 0;
}
// copy libname to new string
if (!fread(state->root.libname, header.len, 1, file)) {
state->error = GDS_ERR_EOF;
return 0;
}
break;
// case REFLIBS:
// break;
// case FONTS:
// break;
// case ATTRTABLE:
// break;
// case GENERATIONS:
// break;
case FORMAT:
ASSERT(header.len);
uint16_t temp;
read_ushort(&temp, file);
if (temp)
state->root.format = GDS_FMT_FILTERED;
break;
case UNITS:
ASSERT(header.len == 16);
read_double(&state->root.units.units_per_dbunits, file);
read_double(&state->root.units.meters_per_unit , file);
// now comes the first structure definition
break;
case BGNSTR:
ASSERT(header.len == 2 * sizeof(date_t));
// create new structure, apppend to list
gds_structure_t * list = realloc(state->root.structures, (state->root.structure_count+1)*sizeof(gds_structure_t));
if (!list)
{
state->error = GDS_ERR_NOMEM;
return 0;
}
// set new list pointer
state->root.structures = list;
state->current_structure = &state->root.structures[state->root.structure_count++];
// read dates
*state->current_structure = (gds_structure_t){};
read_date(&state->current_structure->creation, file);
read_date(&state->current_structure->last_access, file);
break;
case ENDSTR:
// done with structure
state->current_structure = NULL;
break;
case STRNAME:
state->current_structure->name = calloc(1, header.len + 1);
if (state->current_structure->name == NULL)
{
state->error = GDS_ERR_NOMEM;
return 0;
}
if (!fread(state->current_structure->name, header.len, 1, file))
{
state->error = GDS_ERR_EOF;
return 0;
}
break;
case BOUNDARY:
ASSERT(state->current_structure);
if ((state->current_element = alloc_elem(state->current_structure, GDS_ELEM_BOUNDARY)) == NULL)
{
state->error = GDS_ERR_NOMEM;
return 0;
}
break;
case PATH:
ASSERT(state->current_structure);
if ((state->current_element = alloc_elem(state->current_structure, GDS_ELEM_PATH)) == NULL)
{
state->error = GDS_ERR_NOMEM;
return 0;
}
break;
case TEXT:
ASSERT(state->current_structure);
if ((state->current_element = alloc_elem(state->current_structure, GDS_ELEM_TEXT)) == NULL)
{
state->error = GDS_ERR_NOMEM;
return 0;
}
break;
case NODE:
ASSERT(state->current_structure);
if ((state->current_element = alloc_elem(state->current_structure, GDS_ELEM_NODE)) == NULL)
{
state->error = GDS_ERR_NOMEM;
return 0;
}
break;
case ENDEL:
state->current_element = NULL;
break;
case DATATYPE:
if (!state->current_element) {
state->error = GDS_ERR_MISSING_BEGELEM;
return 0;
}
switch (state->current_element->type) {
case GDS_ELEM_PATH:
read_short(&state->current_element->path.datatype, file);
break;
case GDS_ELEM_BOUNDARY:
read_short(&state->current_element->boundary.datatype, file);
break;
default:
state->error = GDS_ERR_INVALID_FILE;
return 0;
}
break;
case LAYER:
if (!state->current_element)
{
state->error = GDS_ERR_MISSING_BEGELEM;
return 0;
}
ASSERT(GDS_ELEM_HAS_LAYER(state->current_element->type));
switch(state->current_element->type)
{
case GDS_ELEM_BOUNDARY:
read_short(&state->current_element->boundary.layer, file);
break;
case GDS_ELEM_PATH:
read_short(&state->current_element->path.layer, file);
break;
default:
// fixme: we should read a layer here, but
// its probably not implemented yet
fseek(file, header.len, SEEK_CUR);
}
break;
case XY:
ASSERT(state->current_element);
ASSERT(GDS_ELEM_IS_GRAPHICAL(state->current_element->type));
if(header.len % sizeof(gds_coord_t))
{
// alignment error
return -1;
}
{
gds_coord_t * current = state->current_element->graphical.points = malloc(header.len);
if (!current)
{
state->error = GDS_ERR_NOMEM;
return 0;
}
size_t size = header.len / sizeof(gds_coord_t);
state->current_element->graphical.point_count = size;
// read all points
for(;size && read_coord(current, file);--size, ++current);
if (size)
{
state->error = GDS_ERR_EOF;
return 0;
}
}
break;
case WIDTH:
ASSERT(state->current_element);
switch(state->current_element->type) {
case GDS_ELEM_PATH:
read_int(&state->current_element->path.width, file);
break;
case GDS_ELEM_TEXT:
read_int(&state->current_element->text.width, file);
default:
state->error = GDS_ERR_INVALID_FILE;
return 0;
}
break;
case PATHTYPE:
ASSERT(state->current_element);
{
int16_t temp;
read_short(&temp, file);
if (temp > GDS_PATH_SQUARE_EXTEND || temp < 0)
{
printf("inalid pathtype found\n");
}
else {
switch(state->current_element->type) {
case GDS_ELEM_PATH:
state->current_element->path.pathtype = temp;
break;
case GDS_ELEM_TEXT:
state->current_element->text.pathtype = temp;
break;
default:
printf("misplaced pathtype\n");
break;
}
}
}
break;
case NODETYPE:
ASSERT(state->current_element);
ASSERT(state->current_element->type == GDS_ELEM_NODE);
read_short(&state->current_element->node.nodetype, file);
break;
default:
// skip record
printf("unhandled record with ident %04x (%s)\n", header.ident, name_from_ident(header.ident));
fseek(file, header.len, SEEK_CUR);
break;
}
return 1;
}
gds_parser_err_t parse_gds(FILE * f, gds_file_t * file)
{
parser_state_t state = {};
// go to beginning of file (just in case)
fseek(f, 0, SEEK_SET);
// parse all records
while(!feof(f) && parse_record(&state, f));
// transfer ownership of file
*file = state.root;
// reset file of parser state
state.root = (gds_file_t){};
// cleanup parser memory
free_parser_state(&state);
return state.error;
}
int main(int argc, char ** argv)
{
printf("start\n");
for (char ** filename = argv + 1; filename != argv + argc; ++filename)
{
printf("reading %s...\n", *filename);
gds_file_t file;
FILE * f = fopen(*filename, "rb");
if (!f)
{
perror("could not open file");
continue;
}
gds_parser_err_t err = parse_gds(f, &file);
if (err != GDS_NO_ERR) {
fprintf(stderr, "error during parsing of file: %s\n", gds_strerror(err));
}
printf("read file\n");
printf("last accessed: " DATE_FMT "\n", DATE_ARGS(file.last_access));
printf("last modification: " DATE_FMT "\n", DATE_ARGS(file.last_mod));
printf("libname: %s\n", file.libname);
printf("units per db units: %g\n", file.units.units_per_dbunits);
printf("m/units : %g\n", file.units.meters_per_unit);
printf("file contains %ld structures\n", file.structure_count);
for (gds_structure_t * it = file.structures; it != file.structures + file.structure_count; ++it)
{
printf("structure with name \"%s\" ", it->name);
printf("contains %ld elements\n", it->element_count);
}
// cleanup
free_gds_file(&file);
fclose(f);
}
printf("done\n");
return 0;
}