#include // for printf #include // for uintxx_t #include // for assert #include // for perror #include // for memcpy #include // 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< 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; }