Add support for errors with a range of columns
[survex.git] / src / img.c
blobeac86b177c94222941e50916b3613c191bcfa2b9
1 /* img.c
2 * Routines for reading and writing Survex ".3d" image files
3 * Copyright (C) 1993-2004,2005,2006,2010,2011,2013,2014 Olly Betts
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
24 #include <ctype.h>
25 #include <errno.h>
26 #include <limits.h>
27 #include <locale.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
33 #include "img.h"
35 #define TIMENA "?"
36 #ifdef IMG_HOSTED
37 # define INT32_T int32_t
38 # include "debug.h"
39 # include "filelist.h"
40 # include "filename.h"
41 # include "message.h"
42 # include "useful.h"
43 # define TIMEFMT msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107)
44 #else
45 # ifdef HAVE_STDINT_H
46 # include <stdint.h>
47 # define INT32_T int32_t
48 # else
49 # include <limits.h>
50 # if INT_MAX >= 2147483647
51 # define INT32_T int
52 # else
53 # define INT32_T long
54 # endif
55 # endif
56 # define TIMEFMT "%a,%Y.%m.%d %H:%M:%S %Z"
57 # define EXT_SVX_3D "3d"
58 # define EXT_SVX_POS "pos"
59 # define FNM_SEP_EXT '.'
60 # define METRES_PER_FOOT 0.3048 /* exact value */
61 # define xosmalloc(L) malloc((L))
62 # define xosrealloc(L,S) realloc((L),(S))
63 # define osfree(P) free((P))
64 # define osnew(T) (T*)malloc(sizeof(T))
66 /* in IMG_HOSTED mode, this tests if a filename refers to a directory */
67 # define fDirectory(X) 0
68 /* open file FNM with mode MODE, maybe using path PTH and/or extension EXT */
69 /* path isn't used in img.c, but EXT is */
70 # define fopenWithPthAndExt(PTH,FNM,EXT,MODE,X) \
71 ((*(X) = NULL), fopen(FNM,MODE))
72 # ifndef PUTC
73 # define PUTC(C, FH) putc(C, FH)
74 # endif
75 # ifndef GETC
76 # define GETC(FH) getc(FH)
77 # endif
78 # define fputsnl(S, FH) (fputs((S), (FH)) == EOF ? EOF : putc('\n', (FH)))
79 # define SVX_ASSERT(X)
81 #ifdef __cplusplus
82 # include <algorithm>
83 using std::max;
84 using std::min;
85 #else
86 /* Return max/min of two numbers. */
87 /* May be defined already (e.g. by Borland C in stdlib.h) */
88 /* NB Bad news if X or Y has side-effects... */
89 # ifndef max
90 # define max(X, Y) ((X) > (Y) ? (X) : (Y))
91 # endif
92 # ifndef min
93 # define min(X, Y) ((X) < (Y) ? (X) : (Y))
94 # endif
95 #endif
97 static INT32_T
98 get32(FILE *fh)
100 INT32_T w = GETC(fh);
101 w |= (INT32_T)GETC(fh) << 8l;
102 w |= (INT32_T)GETC(fh) << 16l;
103 w |= (INT32_T)GETC(fh) << 24l;
104 return w;
107 static void
108 put32(long w, FILE *fh)
110 PUTC((char)(w), fh);
111 PUTC((char)(w >> 8l), fh);
112 PUTC((char)(w >> 16l), fh);
113 PUTC((char)(w >> 24l), fh);
116 static short
117 get16(FILE *fh)
119 short w = GETC(fh);
120 w |= (short)GETC(fh) << 8l;
121 return w;
124 static void
125 put16(short w, FILE *fh)
127 PUTC((char)(w), fh);
128 PUTC((char)(w >> 8l), fh);
131 static char *
132 baseleaf_from_fnm(const char *fnm)
134 const char *p;
135 const char *q;
136 char * res;
137 size_t len;
139 p = fnm;
140 q = strrchr(p, '/');
141 if (q) p = q + 1;
142 q = strrchr(p, '\\');
143 if (q) p = q + 1;
145 q = strrchr(p, FNM_SEP_EXT);
146 if (q) len = (const char *)q - p; else len = strlen(p);
148 res = (char *)xosmalloc(len + 1);
149 if (!res) return NULL;
150 memcpy(res, p, len);
151 res[len] = '\0';
152 return res;
154 #endif
156 static char * my_strdup(const char *str);
158 static time_t
159 mktime_with_tz(struct tm * tm, const char * tz)
161 time_t r;
162 char * old_tz = getenv("TZ");
163 #ifdef _MSC_VER
164 if (old_tz) {
165 old_tz = my_strdup(old_tz);
166 if (!old_tz)
167 return (time_t)-1;
169 if (_putenv_s("TZ", tz) != 0) {
170 osfree(old_tz);
171 return (time_t)-1;
173 #elif defined HAVE_SETENV
174 if (old_tz) {
175 old_tz = my_strdup(old_tz);
176 if (!old_tz)
177 return (time_t)-1;
179 if (setenv("TZ", tz, 1) < 0) {
180 osfree(old_tz);
181 return (time_t)-1;
183 #else
184 char * p;
185 if (old_tz) {
186 size_t len = strlen(old_tz) + 1;
187 p = (char *)xosmalloc(len + 3);
188 if (!p)
189 return (time_t)-1;
190 memcpy(p, "TZ=", 3);
191 memcpy(p + 3, tz, len);
192 old_tz = p;
194 p = (char *)xosmalloc(strlen(tz) + 4);
195 if (!p) {
196 osfree(old_tz);
197 return (time_t)-1;
199 memcpy(p, "TZ=", 3);
200 strcpy(p + 3, tz);
201 if (putenv(p) != 0) {
202 osfree(p);
203 osfree(old_tz);
204 return (time_t)-1;
206 #define CLEANUP() osfree(p)
207 #endif
208 tzset();
209 r = mktime(tm);
210 if (old_tz) {
211 #ifdef _MSC_VER
212 _putenv_s("TZ", old_tz);
213 #elif !defined HAVE_SETENV
214 putenv(old_tz);
215 #else
216 setenv("TZ", old_tz, 1);
217 #endif
218 osfree(old_tz);
219 } else {
220 #ifdef _MSC_VER
221 _putenv_s("TZ", "");
222 #elif !defined HAVE_UNSETENV
223 putenv((char*)"TZ");
224 #else
225 unsetenv("TZ");
226 #endif
228 #ifdef CLEANUP
229 CLEANUP();
230 #undef CLEANUP
231 #endif
232 return r;
235 static unsigned short
236 getu16(FILE *fh)
238 return (unsigned short)get16(fh);
241 #include <math.h>
243 #if !defined HAVE_LROUND && !defined HAVE_DECL_LROUND
244 /* The autoconf tests are not in use, but C99 and C++11 both added lround(),
245 * so set HAVE_LROUND and HAVE_DECL_LROUND conservatively based on the language
246 * standard version the compiler claims to support. */
247 # if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) || \
248 (defined __cplusplus && __cplusplus >= 201103L)
249 # define HAVE_LROUND 1
250 # define HAVE_DECL_LROUND 1
251 # endif
252 #endif
254 #ifdef HAVE_LROUND
255 # if defined HAVE_DECL_LROUND && !HAVE_DECL_LROUND
256 /* On older systems, the prototype may be missing. */
257 extern long lround(double);
258 # endif
259 # define my_lround lround
260 #else
261 static long
262 my_lround(double x) {
263 return (x >= 0.0) ? (long)(x + 0.5) : -(long)(0.5 - x);
265 #endif
267 /* portable case insensitive string compare */
268 #if defined(strcasecmp) || defined(HAVE_STRCASECMP)
269 # define my_strcasecmp strcasecmp
270 #else
271 static int my_strcasecmp(const char *s1, const char *s2) {
272 unsigned char c1, c2;
273 do {
274 c1 = *s1++;
275 c2 = *s2++;
276 } while (c1 && toupper(c1) == toupper(c2));
277 /* now calculate real difference */
278 return c1 - c2;
280 #endif
282 unsigned int img_output_version = IMG_VERSION_MAX;
284 static img_errcode img_errno = IMG_NONE;
286 #define FILEID "Survex 3D Image File"
288 #define EXT_PLT "plt"
289 #define EXT_PLF "plf"
291 /* Attempt to string paste to ensure we are passed a literal string */
292 #define LITLEN(S) (sizeof(S"") - 1)
294 /* Fake "version numbers" for non-3d formats we can read. */
295 #define VERSION_CMAP_SHOT -4
296 #define VERSION_CMAP_STATION -3
297 #define VERSION_COMPASS_PLT -2
298 #define VERSION_SURVEX_POS -1
300 static char *
301 my_strdup(const char *str)
303 char *p;
304 size_t len = strlen(str) + 1;
305 p = (char *)xosmalloc(len);
306 if (p) memcpy(p, str, len);
307 return p;
310 #define getline_alloc(FH) getline_alloc_len(FH, NULL)
312 static char *
313 getline_alloc_len(FILE *fh, size_t * p_len)
315 int ch;
316 size_t i = 0;
317 size_t len = 16;
318 char *buf = (char *)xosmalloc(len);
319 if (!buf) return NULL;
321 ch = GETC(fh);
322 while (ch != '\n' && ch != '\r' && ch != EOF) {
323 buf[i++] = ch;
324 if (i == len - 1) {
325 char *p;
326 len += len;
327 p = (char *)xosrealloc(buf, len);
328 if (!p) {
329 osfree(buf);
330 return NULL;
332 buf = p;
334 ch = GETC(fh);
336 if (ch == '\n' || ch == '\r') {
337 int otherone = ch ^ ('\n' ^ '\r');
338 ch = GETC(fh);
339 /* if it's not the other eol character, put it back */
340 if (ch != otherone) ungetc(ch, fh);
342 buf[i] = '\0';
343 if (p_len) *p_len = i;
344 return buf;
347 img_errcode
348 img_error(void)
350 return img_errno;
353 static int
354 check_label_space(img *pimg, size_t len)
356 if (len > pimg->buf_len) {
357 char *b = (char *)xosrealloc(pimg->label_buf, len);
358 if (!b) return 0;
359 pimg->label = (pimg->label - pimg->label_buf) + b;
360 pimg->label_buf = b;
361 pimg->buf_len = len;
363 return 1;
366 #define has_ext(F,L,E) ((L) > LITLEN(E) + 1 &&\
367 (F)[(L) - LITLEN(E) - 1] == FNM_SEP_EXT &&\
368 my_strcasecmp((F) + (L) - LITLEN(E), E) == 0)
370 img *
371 img_open_survey(const char *fnm, const char *survey)
373 img *pimg;
374 size_t len;
375 char buf[LITLEN(FILEID) + 9];
376 int ch;
378 if (fDirectory(fnm)) {
379 img_errno = IMG_DIRECTORY;
380 return NULL;
383 pimg = osnew(img);
384 if (pimg == NULL) {
385 img_errno = IMG_OUTOFMEMORY;
386 return NULL;
389 pimg->buf_len = 257;
390 pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
391 if (!pimg->label_buf) {
392 osfree(pimg);
393 img_errno = IMG_OUTOFMEMORY;
394 return NULL;
397 pimg->fh = fopenWithPthAndExt("", fnm, EXT_SVX_3D, "rb", &(pimg->filename_opened));
398 if (pimg->fh == NULL) {
399 osfree(pimg->label_buf);
400 osfree(pimg);
401 img_errno = IMG_FILENOTFOUND;
402 return NULL;
405 pimg->fRead = 1; /* reading from this file */
406 img_errno = IMG_NONE;
408 pimg->flags = 0;
410 /* for version >= 3 we use label_buf to store the prefix for reuse */
411 /* for VERSION_COMPASS_PLT, 0 value indicates we haven't
412 * entered a survey yet */
413 /* for VERSION_CMAP_SHOT, we store the last station here
414 * to detect whether we MOVE or LINE */
415 pimg->label_len = 0;
416 pimg->label_buf[0] = '\0';
418 pimg->survey = NULL;
419 pimg->survey_len = 0;
420 pimg->separator = '.';
421 #if IMG_API_VERSION == 0
422 pimg->date1 = pimg->date2 = 0;
423 #else /* IMG_API_VERSION == 1 */
424 pimg->days1 = pimg->days2 = -1;
425 #endif
426 pimg->is_extended_elevation = 0;
428 pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
430 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
432 pimg->title = pimg->datestamp = pimg->cs = NULL;
433 pimg->datestamp_numeric = (time_t)-1;
435 if (survey) {
436 len = strlen(survey);
437 if (len) {
438 if (survey[len - 1] == '.') len--;
439 if (len) {
440 char *p;
441 pimg->survey = (char *)xosmalloc(len + 2);
442 if (!pimg->survey) {
443 img_errno = IMG_OUTOFMEMORY;
444 goto error;
446 memcpy(pimg->survey, survey, len);
447 /* Set title to leaf survey name */
448 pimg->survey[len] = '\0';
449 p = strchr(pimg->survey, '.');
450 if (p) p++; else p = pimg->survey;
451 pimg->title = my_strdup(p);
452 if (!pimg->title) {
453 img_errno = IMG_OUTOFMEMORY;
454 goto error;
456 pimg->survey[len] = '.';
457 pimg->survey[len + 1] = '\0';
460 pimg->survey_len = len;
463 /* [VERSION_COMPASS_PLT, VERSION_CMAP_STATION, VERSION_CMAP_SHOT] pending
464 * IMG_LINE or IMG_MOVE - both have 4 added.
465 * [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
466 * [version 0] not in the middle of a 'LINE' command
467 * [version >= 3] not in the middle of turning a LINE into a MOVE
469 pimg->pending = 0;
471 len = strlen(fnm);
472 if (has_ext(fnm, len, EXT_SVX_POS)) {
473 pos_file:
474 pimg->version = VERSION_SURVEX_POS;
475 if (!pimg->survey) pimg->title = baseleaf_from_fnm(fnm);
476 pimg->datestamp = my_strdup(TIMENA);
477 if (!pimg->datestamp) {
478 img_errno = IMG_OUTOFMEMORY;
479 goto error;
481 pimg->start = 0;
482 return pimg;
485 if (has_ext(fnm, len, EXT_PLT) || has_ext(fnm, len, EXT_PLF)) {
486 long fpos;
487 plt_file:
488 pimg->version = VERSION_COMPASS_PLT;
489 /* Spaces aren't legal in Compass station names, but dots are, so
490 * use space as the level separator */
491 pimg->separator = ' ';
492 pimg->start = 0;
493 if (!pimg->survey) pimg->title = baseleaf_from_fnm(fnm);
494 pimg->datestamp = my_strdup(TIMENA);
495 if (!pimg->datestamp) {
496 img_errno = IMG_OUTOFMEMORY;
497 goto error;
499 while (1) {
500 ch = GETC(pimg->fh);
501 switch (ch) {
502 case '\x1a':
503 fseek(pimg->fh, -1, SEEK_CUR);
504 /* FALL THRU */
505 case EOF:
506 pimg->start = ftell(pimg->fh);
507 return pimg;
508 case 'N': {
509 char *line, *q;
510 fpos = ftell(pimg->fh) - 1;
511 if (!pimg->survey) {
512 /* FIXME : if there's only one survey in the file, it'd be nice
513 * to use its description as the title here...
515 ungetc('N', pimg->fh);
516 pimg->start = fpos;
517 return pimg;
519 line = getline_alloc(pimg->fh);
520 if (!line) {
521 img_errno = IMG_OUTOFMEMORY;
522 goto error;
524 len = 0;
525 while (line[len] > 32) ++len;
526 if (pimg->survey_len != len ||
527 memcmp(line, pimg->survey, len) != 0) {
528 osfree(line);
529 continue;
531 q = strchr(line + len, 'C');
532 if (q && q[1]) {
533 osfree(pimg->title);
534 pimg->title = my_strdup(q + 1);
535 } else if (!pimg->title) {
536 pimg->title = my_strdup(pimg->label);
538 osfree(line);
539 if (!pimg->title) {
540 img_errno = IMG_OUTOFMEMORY;
541 goto error;
543 if (!pimg->start) pimg->start = fpos;
544 fseek(pimg->fh, pimg->start, SEEK_SET);
545 return pimg;
547 case 'M': case 'D':
548 pimg->start = ftell(pimg->fh) - 1;
549 break;
551 while (ch != '\n' && ch != '\r') {
552 ch = GETC(pimg->fh);
557 /* Although these are often referred to as "CMAP .XYZ files", it seems
558 * that actually, the extension .XYZ isn't used, rather .SHT (shot
559 * variant, produced by CMAP v16 and later), .UNA (unadjusted) and
560 * .ADJ (adjusted) extensions are. Since img has long checked for
561 * .XYZ, we continue to do so in case anyone is relying on it.
563 if (has_ext(fnm, len, "sht") ||
564 has_ext(fnm, len, "adj") ||
565 has_ext(fnm, len, "una") ||
566 has_ext(fnm, len, "xyz")) {
567 char *line;
568 xyz_file:
569 /* Spaces aren't legal in CMAP station names, but dots are, so
570 * use space as the level separator. */
571 pimg->separator = ' ';
572 line = getline_alloc(pimg->fh);
573 if (!line) {
574 img_errno = IMG_OUTOFMEMORY;
575 goto error;
577 /* There doesn't seem to be a spec for what happens after 1999 with cmap
578 * files, so this code allows for:
579 * * 21xx -> xx (up to 2150)
580 * * 21xx -> 1xx (up to 2199)
581 * * full year being specified instead of 2 digits
583 len = strlen(line);
584 if (len > 59) {
585 /* Don't just truncate at column 59, allow for a > 2 digit year. */
586 char * p = strstr(line + len, "Page");
587 if (p) {
588 while (p > line && p[-1] == ' ')
589 --p;
590 *p = '\0';
591 len = p - line;
592 } else {
593 line[59] = '\0';
596 if (len > 45) {
597 /* YY/MM/DD HH:MM */
598 struct tm tm;
599 unsigned long v;
600 char * p;
601 pimg->datestamp = my_strdup(line + 45);
602 p = pimg->datestamp;
603 v = strtoul(p, &p, 10);
604 if (v <= 50) {
605 /* In the absence of a spec for cmap files, assume <= 50 means 21st
606 * century. */
607 v += 2000;
608 } else if (v < 200) {
609 /* Map 100-199 to 21st century. */
610 v += 1900;
612 if (v == ULONG_MAX || *p++ != '/')
613 goto bad_cmap_date;
614 tm.tm_year = v - 1900;
615 v = strtoul(p, &p, 10);
616 if (v < 1 || v > 12 || *p++ != '/')
617 goto bad_cmap_date;
618 tm.tm_mon = v - 1;
619 v = strtoul(p, &p, 10);
620 if (v < 1 || v > 31 || *p++ != ' ')
621 goto bad_cmap_date;
622 tm.tm_mday = v;
623 v = strtoul(p, &p, 10);
624 if (v >= 24 || *p++ != ':')
625 goto bad_cmap_date;
626 tm.tm_hour = v;
627 v = strtoul(p, &p, 10);
628 if (v >= 60)
629 goto bad_cmap_date;
630 tm.tm_min = v;
631 if (*p == ':') {
632 v = strtoul(p + 1, &p, 10);
633 if (v > 60)
634 goto bad_cmap_date;
635 tm.tm_sec = v;
636 } else {
637 tm.tm_sec = 0;
639 tm.tm_isdst = 0;
640 /* We have no indication of what timezone this timestamp is in. It's
641 * probably local time for whoever processed the data, so just assume
642 * UTC, which is at least fairly central in the possibilities.
644 pimg->datestamp_numeric = mktime_with_tz(&tm, "");
645 } else {
646 pimg->datestamp = my_strdup(TIMENA);
648 bad_cmap_date:
649 if (strncmp(line, " Cave Survey Data Processed by CMAP ",
650 LITLEN(" Cave Survey Data Processed by CMAP ")) == 0) {
651 len = 0;
652 } else {
653 if (len > 45) {
654 line[45] = '\0';
655 len = 45;
657 while (len > 2 && line[len - 1] == ' ') --len;
658 if (len > 2) {
659 line[len] = '\0';
660 pimg->title = my_strdup(line + 2);
663 if (len <= 2) pimg->title = baseleaf_from_fnm(fnm);
664 osfree(line);
665 if (!pimg->datestamp || !pimg->title) {
666 img_errno = IMG_OUTOFMEMORY;
667 goto error;
669 line = getline_alloc(pimg->fh);
670 if (!line) {
671 img_errno = IMG_OUTOFMEMORY;
672 goto error;
674 if (line[0] != ' ' || (line[1] != 'S' && line[1] != 'O')) {
675 img_errno = IMG_BADFORMAT;
676 goto error;
678 if (line[1] == 'S') {
679 pimg->version = VERSION_CMAP_STATION;
680 } else {
681 pimg->version = VERSION_CMAP_SHOT;
683 osfree(line);
684 line = getline_alloc(pimg->fh);
685 if (!line) {
686 img_errno = IMG_OUTOFMEMORY;
687 goto error;
689 if (line[0] != ' ' || line[1] != '-') {
690 img_errno = IMG_BADFORMAT;
691 goto error;
693 osfree(line);
694 pimg->start = ftell(pimg->fh);
695 return pimg;
698 if (fread(buf, LITLEN(FILEID) + 1, 1, pimg->fh) != 1 ||
699 memcmp(buf, FILEID"\n", LITLEN(FILEID) + 1) != 0) {
700 if (fread(buf + LITLEN(FILEID) + 1, 8, 1, pimg->fh) == 1 &&
701 memcmp(buf, FILEID"\r\nv0.01\r\n", LITLEN(FILEID) + 9) == 0) {
702 /* v0 3d file with DOS EOLs */
703 pimg->version = 0;
704 goto v03d;
706 rewind(pimg->fh);
707 if (buf[1] == ' ') {
708 if (buf[0] == ' ') {
709 /* Looks like a CMAP .xyz file ... */
710 goto xyz_file;
711 } else if (strchr("ZSNF", buf[0])) {
712 /* Looks like a Compass .plt file ... */
713 /* Almost certainly it'll start "Z " */
714 goto plt_file;
717 if (buf[0] == '(') {
718 /* Looks like a Survex .pos file ... */
719 goto pos_file;
721 img_errno = IMG_BADFORMAT;
722 goto error;
725 /* check file format version */
726 ch = GETC(pimg->fh);
727 pimg->version = 0;
728 if (tolower(ch) == 'b') {
729 /* binary file iff B/b prefix */
730 pimg->version = 1;
731 ch = GETC(pimg->fh);
733 if (ch != 'v') {
734 img_errno = IMG_BADFORMAT;
735 goto error;
737 ch = GETC(pimg->fh);
738 if (ch == '0') {
739 if (fread(buf, 4, 1, pimg->fh) != 1 || memcmp(buf, ".01\n", 4) != 0) {
740 img_errno = IMG_BADFORMAT;
741 goto error;
743 /* nothing special to do */
744 } else if (pimg->version == 0) {
745 if (ch < '2' || ch > '0' + IMG_VERSION_MAX || GETC(pimg->fh) != '\n') {
746 img_errno = IMG_TOONEW;
747 goto error;
749 pimg->version = ch - '0';
750 } else {
751 img_errno = IMG_BADFORMAT;
752 goto error;
755 v03d:
757 size_t title_len;
758 char * title = getline_alloc_len(pimg->fh, &title_len);
759 if (pimg->version == 8 && title) {
760 /* We sneak in an extra field after a zero byte here, containing the
761 * specified coordinate system (if any). Older readers will just
762 * not see it (which is fine), and this trick avoids us having to
763 * bump the 3d format version.
765 size_t real_len = strlen(title);
766 if (real_len != title_len) {
767 pimg->cs = my_strdup(title + real_len + 1);
770 if (!pimg->title) {
771 pimg->title = title;
772 } else {
773 osfree(title);
776 pimg->datestamp = getline_alloc(pimg->fh);
777 if (!pimg->title || !pimg->datestamp) {
778 img_errno = IMG_OUTOFMEMORY;
779 error:
780 osfree(pimg->title);
781 osfree(pimg->cs);
782 osfree(pimg->datestamp);
783 osfree(pimg->filename_opened);
784 fclose(pimg->fh);
785 osfree(pimg);
786 return NULL;
789 if (pimg->version >= 8) {
790 int flags = GETC(pimg->fh);
791 if (flags & img_FFLAG_EXTENDED) pimg->is_extended_elevation = 1;
792 } else {
793 len = strlen(pimg->title);
794 if (len > 11 && strcmp(pimg->title + len - 11, " (extended)") == 0) {
795 pimg->title[len - 11] = '\0';
796 pimg->is_extended_elevation = 1;
800 if (pimg->datestamp[0] == '@') {
801 unsigned long v;
802 char * p;
803 errno = 0;
804 v = strtoul(pimg->datestamp + 1, &p, 10);
805 if (errno == 0 && *p == '\0')
806 pimg->datestamp_numeric = v;
807 /* FIXME: We're assuming here that the C time_t epoch is 1970, which is
808 * true for Unix-like systems, Mac OS X and Windows, but isn't guaranteed
809 * by ISO C.
811 } else {
812 /* %a,%Y.%m.%d %H:%M:%S %Z */
813 struct tm tm;
814 unsigned long v;
815 char * p = pimg->datestamp;
816 while (isalpha((unsigned char)*p)) ++p;
817 if (*p == ',') ++p;
818 while (isspace((unsigned char)*p)) ++p;
819 v = strtoul(p, &p, 10);
820 if (v == ULONG_MAX || *p++ != '.')
821 goto bad_3d_date;
822 tm.tm_year = v - 1900;
823 v = strtoul(p, &p, 10);
824 if (v < 1 || v > 12 || *p++ != '.')
825 goto bad_3d_date;
826 tm.tm_mon = v - 1;
827 v = strtoul(p, &p, 10);
828 if (v < 1 || v > 31 || *p++ != ' ')
829 goto bad_3d_date;
830 tm.tm_mday = v;
831 v = strtoul(p, &p, 10);
832 if (v >= 24 || *p++ != ':')
833 goto bad_3d_date;
834 tm.tm_hour = v;
835 v = strtoul(p, &p, 10);
836 if (v >= 60 || *p++ != ':')
837 goto bad_3d_date;
838 tm.tm_min = v;
839 v = strtoul(p, &p, 10);
840 if (v > 60)
841 goto bad_3d_date;
842 tm.tm_sec = v;
843 tm.tm_isdst = 0;
844 while (isspace((unsigned char)*p)) ++p;
845 /* p now points to the timezone string.
847 * However, it's likely to be a string like "BST", and such strings can
848 * be ambiguous (BST could be UTC+1 or UTC+6), so it is impossible to
849 * reliably convert in all cases. Just pass what we have to tzset() - if
850 * it doesn't handle it, UTC will be used.
852 pimg->datestamp_numeric = mktime_with_tz(&tm, p);
854 bad_3d_date:
856 pimg->start = ftell(pimg->fh);
858 return pimg;
862 img_rewind(img *pimg)
864 if (!pimg->fRead) {
865 img_errno = IMG_WRITEERROR;
866 return 0;
868 if (fseek(pimg->fh, pimg->start, SEEK_SET) != 0) {
869 img_errno = IMG_READERROR;
870 return 0;
872 clearerr(pimg->fh);
873 /* [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
874 * [version 0] not in the middle of a 'LINE' command
875 * [version >= 3] not in the middle of turning a LINE into a MOVE */
876 pimg->pending = 0;
878 img_errno = IMG_NONE;
880 /* for version >= 3 we use label_buf to store the prefix for reuse */
881 /* for VERSION_COMPASS_PLT, 0 value indicates we haven't entered a survey
882 * yet */
883 /* for VERSION_CMAP_SHOT, we store the last station here to detect whether
884 * we MOVE or LINE */
885 pimg->label_len = 0;
886 pimg->style = img_STYLE_UNKNOWN;
887 return 1;
890 img *
891 img_open_write_cs(const char *fnm, const char *title, const char * cs, int flags)
893 time_t tm;
894 img *pimg;
896 if (fDirectory(fnm)) {
897 img_errno = IMG_DIRECTORY;
898 return NULL;
901 pimg = osnew(img);
902 if (pimg == NULL) {
903 img_errno = IMG_OUTOFMEMORY;
904 return NULL;
907 pimg->buf_len = 257;
908 pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
909 if (!pimg->label_buf) {
910 osfree(pimg);
911 img_errno = IMG_OUTOFMEMORY;
912 return NULL;
915 pimg->fh = fopen(fnm, "wb");
916 if (!pimg->fh) {
917 osfree(pimg->label_buf);
918 osfree(pimg);
919 img_errno = IMG_CANTOPENOUT;
920 return NULL;
923 pimg->filename_opened = NULL;
925 /* Output image file header */
926 fputs("Survex 3D Image File\n", pimg->fh); /* file identifier string */
927 if (img_output_version < 2) {
928 pimg->version = 1;
929 fputs("Bv0.01\n", pimg->fh); /* binary file format version number */
930 } else {
931 pimg->version = (img_output_version > IMG_VERSION_MAX) ? IMG_VERSION_MAX : img_output_version;
932 fprintf(pimg->fh, "v%d\n", pimg->version); /* file format version no. */
935 fputs(title, pimg->fh);
936 if (pimg->version < 8 && (flags & img_FFLAG_EXTENDED)) {
937 /* Older format versions append " (extended)" to the title to mark
938 * extended elevations. */
939 size_t len = strlen(title);
940 if (len < 11 || strcmp(title + len - 11, " (extended)") != 0)
941 fputs(" (extended)", pimg->fh);
943 if (pimg->version == 8 && cs && *cs) {
944 /* We sneak in an extra field after a zero byte here, containing the
945 * specified coordinate system (if any). Older readers will just not
946 * see it (which is fine), and this trick avoids us having to bump the
947 * 3d format version.
949 PUTC('\0', pimg->fh);
950 fputs(cs, pimg->fh);
952 PUTC('\n', pimg->fh);
954 tm = time(NULL);
955 if (tm == (time_t)-1) {
956 fputsnl(TIMENA, pimg->fh);
957 } else if (pimg->version <= 7) {
958 char date[256];
959 /* output current date and time in format specified */
960 strftime(date, 256, TIMEFMT, localtime(&tm));
961 fputsnl(date, pimg->fh);
962 } else {
963 fprintf(pimg->fh, "@%ld\n", (long)tm);
966 if (pimg->version >= 8) {
967 /* Clear bit one in case anyone has been passing true for fBinary. */
968 flags &=~ 1;
969 PUTC(flags, pimg->fh);
972 #if 0
973 if (img_output_version >= 5) {
974 static const unsigned char codelengths[32] = {
975 4, 8, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
976 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
978 fwrite(codelengths, 32, 1, pimg->fh);
980 #endif
981 pimg->fRead = 0; /* writing to this file */
982 img_errno = IMG_NONE;
984 /* for version >= 3 we use label_buf to store the prefix for reuse */
985 pimg->label_buf[0] = '\0';
986 pimg->label_len = 0;
988 #if IMG_API_VERSION == 0
989 pimg->date1 = pimg->date2 = 0;
990 pimg->olddate1 = pimg->olddate2 = 0;
991 #else /* IMG_API_VERSION == 1 */
992 pimg->days1 = pimg->days2 = -1;
993 pimg->olddays1 = pimg->olddays2 = -1;
994 #endif
995 pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
997 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
999 pimg->n_legs = 0;
1000 pimg->length = 0.0;
1001 pimg->E = pimg->H = pimg->V = 0.0;
1003 /* Don't check for write errors now - let img_close() report them... */
1004 return pimg;
1007 static void
1008 read_xyz_station_coords(img_point *pt, const char *line)
1010 char num[12];
1011 memcpy(num, line + 6, 9);
1012 num[9] = '\0';
1013 pt->x = atof(num) / METRES_PER_FOOT;
1014 memcpy(num, line + 15, 9);
1015 pt->y = atof(num) / METRES_PER_FOOT;
1016 memcpy(num, line + 24, 8);
1017 num[8] = '\0';
1018 pt->z = atof(num) / METRES_PER_FOOT;
1021 static void
1022 read_xyz_shot_coords(img_point *pt, const char *line)
1024 char num[12];
1025 memcpy(num, line + 40, 10);
1026 num[10] = '\0';
1027 pt->x = atof(num) / METRES_PER_FOOT;
1028 memcpy(num, line + 50, 10);
1029 pt->y = atof(num) / METRES_PER_FOOT;
1030 memcpy(num, line + 60, 9);
1031 num[9] = '\0';
1032 pt->z = atof(num) / METRES_PER_FOOT;
1035 static void
1036 subtract_xyz_shot_deltas(img_point *pt, const char *line)
1038 char num[12];
1039 memcpy(num, line + 15, 9);
1040 num[9] = '\0';
1041 pt->x -= atof(num) / METRES_PER_FOOT;
1042 memcpy(num, line + 24, 8);
1043 num[8] = '\0';
1044 pt->y -= atof(num) / METRES_PER_FOOT;
1045 memcpy(num, line + 32, 8);
1046 pt->z -= atof(num) / METRES_PER_FOOT;
1049 static int
1050 read_coord(FILE *fh, img_point *pt)
1052 SVX_ASSERT(fh);
1053 SVX_ASSERT(pt);
1054 pt->x = get32(fh) / 100.0;
1055 pt->y = get32(fh) / 100.0;
1056 pt->z = get32(fh) / 100.0;
1057 if (ferror(fh) || feof(fh)) {
1058 img_errno = feof(fh) ? IMG_BADFORMAT : IMG_READERROR;
1059 return 0;
1061 return 1;
1064 static int
1065 skip_coord(FILE *fh)
1067 return (fseek(fh, 12, SEEK_CUR) == 0);
1070 static int
1071 read_v3label(img *pimg)
1073 char *q;
1074 long len = GETC(pimg->fh);
1075 if (len == EOF) {
1076 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1077 return img_BAD;
1079 if (len == 0xfe) {
1080 len += get16(pimg->fh);
1081 if (feof(pimg->fh)) {
1082 img_errno = IMG_BADFORMAT;
1083 return img_BAD;
1085 if (ferror(pimg->fh)) {
1086 img_errno = IMG_READERROR;
1087 return img_BAD;
1089 } else if (len == 0xff) {
1090 len = get32(pimg->fh);
1091 if (ferror(pimg->fh)) {
1092 img_errno = IMG_READERROR;
1093 return img_BAD;
1095 if (feof(pimg->fh) || len < 0xfe + 0xffff) {
1096 img_errno = IMG_BADFORMAT;
1097 return img_BAD;
1101 if (!check_label_space(pimg, pimg->label_len + len + 1)) {
1102 img_errno = IMG_OUTOFMEMORY;
1103 return img_BAD;
1105 q = pimg->label_buf + pimg->label_len;
1106 pimg->label_len += len;
1107 if (len && fread(q, len, 1, pimg->fh) != 1) {
1108 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1109 return img_BAD;
1111 q[len] = '\0';
1112 return 0;
1115 static int
1116 read_v8label(img *pimg, int common_flag, size_t common_val)
1118 char *q;
1119 size_t del, add;
1120 if (common_flag) {
1121 if (common_val == 0) return 0;
1122 add = del = common_val;
1123 } else {
1124 int ch = GETC(pimg->fh);
1125 if (ch == EOF) {
1126 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1127 return img_BAD;
1129 if (ch != 0x00) {
1130 del = ch >> 4;
1131 add = ch & 0x0f;
1132 } else {
1133 ch = GETC(pimg->fh);
1134 if (ch == EOF) {
1135 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1136 return img_BAD;
1138 if (ch != 0xff) {
1139 del = ch;
1140 } else {
1141 del = get32(pimg->fh);
1142 if (ferror(pimg->fh)) {
1143 img_errno = IMG_READERROR;
1144 return img_BAD;
1147 ch = GETC(pimg->fh);
1148 if (ch == EOF) {
1149 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1150 return img_BAD;
1152 if (ch != 0xff) {
1153 add = ch;
1154 } else {
1155 add = get32(pimg->fh);
1156 if (ferror(pimg->fh)) {
1157 img_errno = IMG_READERROR;
1158 return img_BAD;
1163 if (add > del && !check_label_space(pimg, pimg->label_len + add - del + 1)) {
1164 img_errno = IMG_OUTOFMEMORY;
1165 return img_BAD;
1168 if (del > pimg->label_len) {
1169 img_errno = IMG_BADFORMAT;
1170 return img_BAD;
1172 pimg->label_len -= del;
1173 q = pimg->label_buf + pimg->label_len;
1174 pimg->label_len += add;
1175 if (add && fread(q, add, 1, pimg->fh) != 1) {
1176 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1177 return img_BAD;
1179 q[add] = '\0';
1180 return 0;
1183 static int img_read_item_new(img *pimg, img_point *p);
1184 static int img_read_item_v3to7(img *pimg, img_point *p);
1185 static int img_read_item_ancient(img *pimg, img_point *p);
1186 static int img_read_item_ascii_wrapper(img *pimg, img_point *p);
1187 static int img_read_item_ascii(img *pimg, img_point *p);
1190 img_read_item(img *pimg, img_point *p)
1192 pimg->flags = 0;
1194 if (pimg->version >= 8) {
1195 return img_read_item_new(pimg, p);
1196 } else if (pimg->version >= 3) {
1197 return img_read_item_v3to7(pimg, p);
1198 } else if (pimg->version >= 1) {
1199 return img_read_item_ancient(pimg, p);
1200 } else {
1201 return img_read_item_ascii_wrapper(pimg, p);
1205 static int
1206 img_read_item_new(img *pimg, img_point *p)
1208 int result;
1209 int opt;
1210 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1211 if (pimg->pending >= 0x40) {
1212 if (pimg->pending == 256) {
1213 pimg->pending = 0;
1214 return img_XSECT_END;
1216 *p = pimg->mv;
1217 pimg->flags = (int)(pimg->pending) & 0x3f;
1218 pimg->pending = 0;
1219 return img_LINE;
1221 again3: /* label to goto if we get a prefix, date, or lrud */
1222 pimg->label = pimg->label_buf;
1223 opt = GETC(pimg->fh);
1224 if (opt == EOF) {
1225 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1226 return img_BAD;
1228 if (opt >> 6 == 0) {
1229 if (opt <= 4) {
1230 if (opt == 0 && pimg->style == 0)
1231 return img_STOP; /* end of data marker */
1232 /* STYLE */
1233 pimg->style = opt;
1234 goto again3;
1236 if (opt >= 0x10) {
1237 switch (opt) {
1238 case 0x10: { /* No date info */
1239 #if IMG_API_VERSION == 0
1240 pimg->date1 = pimg->date2 = 0;
1241 #else /* IMG_API_VERSION == 1 */
1242 pimg->days1 = pimg->days2 = -1;
1243 #endif
1244 break;
1246 case 0x11: { /* Single date */
1247 int days1 = (int)getu16(pimg->fh);
1248 #if IMG_API_VERSION == 0
1249 pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
1250 #else /* IMG_API_VERSION == 1 */
1251 pimg->days2 = pimg->days1 = days1;
1252 #endif
1253 break;
1255 case 0x12: { /* Date range (short) */
1256 int days1 = (int)getu16(pimg->fh);
1257 int days2 = days1 + GETC(pimg->fh) + 1;
1258 #if IMG_API_VERSION == 0
1259 pimg->date1 = (days1 - 25567) * 86400;
1260 pimg->date2 = (days2 - 25567) * 86400;
1261 #else /* IMG_API_VERSION == 1 */
1262 pimg->days1 = days1;
1263 pimg->days2 = days2;
1264 #endif
1265 break;
1267 case 0x13: { /* Date range (long) */
1268 int days1 = (int)getu16(pimg->fh);
1269 int days2 = (int)getu16(pimg->fh);
1270 #if IMG_API_VERSION == 0
1271 pimg->date1 = (days1 - 25567) * 86400;
1272 pimg->date2 = (days2 - 25567) * 86400;
1273 #else /* IMG_API_VERSION == 1 */
1274 pimg->days1 = days1;
1275 pimg->days2 = days2;
1276 #endif
1277 break;
1279 case 0x1f: /* Error info */
1280 pimg->n_legs = get32(pimg->fh);
1281 pimg->length = get32(pimg->fh) / 100.0;
1282 pimg->E = get32(pimg->fh) / 100.0;
1283 pimg->H = get32(pimg->fh) / 100.0;
1284 pimg->V = get32(pimg->fh) / 100.0;
1285 return img_ERROR_INFO;
1286 case 0x30: case 0x31: /* LRUD */
1287 case 0x32: case 0x33: /* Big LRUD! */
1288 if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1289 pimg->flags = (int)opt & 0x01;
1290 if (opt < 0x32) {
1291 pimg->l = get16(pimg->fh) / 100.0;
1292 pimg->r = get16(pimg->fh) / 100.0;
1293 pimg->u = get16(pimg->fh) / 100.0;
1294 pimg->d = get16(pimg->fh) / 100.0;
1295 } else {
1296 pimg->l = get32(pimg->fh) / 100.0;
1297 pimg->r = get32(pimg->fh) / 100.0;
1298 pimg->u = get32(pimg->fh) / 100.0;
1299 pimg->d = get32(pimg->fh) / 100.0;
1301 if (pimg->survey_len) {
1302 size_t l = pimg->survey_len;
1303 const char *s = pimg->label_buf;
1304 if (strncmp(pimg->survey, s, l + 1) != 0) {
1305 return img_XSECT_END;
1307 pimg->label += l;
1308 /* skip the dot if there */
1309 if (*pimg->label) pimg->label++;
1311 /* If this is the last cross-section in this passage, set
1312 * pending so we return img_XSECT_END next time. */
1313 if (pimg->flags & 0x01) {
1314 pimg->pending = 256;
1315 pimg->flags &= ~0x01;
1317 return img_XSECT;
1318 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1319 img_errno = IMG_BADFORMAT;
1320 return img_BAD;
1322 goto again3;
1324 if (opt != 15) {
1325 /* 1-14 and 16-31 reserved */
1326 img_errno = IMG_BADFORMAT;
1327 return img_BAD;
1329 result = img_MOVE;
1330 } else if (opt >= 0x80) {
1331 if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1333 result = img_LABEL;
1335 if (pimg->survey_len) {
1336 size_t l = pimg->survey_len;
1337 const char *s = pimg->label_buf;
1338 if (strncmp(pimg->survey, s, l + 1) != 0) {
1339 if (!skip_coord(pimg->fh)) return img_BAD;
1340 pimg->pending = 0;
1341 goto again3;
1343 pimg->label += l;
1344 /* skip the dot if there */
1345 if (*pimg->label) pimg->label++;
1348 pimg->flags = (int)opt & 0x7f;
1349 } else if ((opt >> 6) == 1) {
1350 if (read_v8label(pimg, opt & 0x20, 0) == img_BAD) return img_BAD;
1352 result = img_LINE;
1354 if (pimg->survey_len) {
1355 size_t l = pimg->survey_len;
1356 const char *s = pimg->label_buf;
1357 if (strncmp(pimg->survey, s, l) != 0 ||
1358 !(s[l] == '.' || s[l] == '\0')) {
1359 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1360 pimg->pending = 15;
1361 goto again3;
1363 pimg->label += l;
1364 /* skip the dot if there */
1365 if (*pimg->label) pimg->label++;
1368 if (pimg->pending) {
1369 *p = pimg->mv;
1370 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1371 pimg->pending = opt;
1372 return img_MOVE;
1374 pimg->flags = (int)opt & 0x1f;
1375 } else {
1376 img_errno = IMG_BADFORMAT;
1377 return img_BAD;
1379 if (!read_coord(pimg->fh, p)) return img_BAD;
1380 pimg->pending = 0;
1381 return result;
1384 static int
1385 img_read_item_v3to7(img *pimg, img_point *p)
1387 int result;
1388 int opt;
1389 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1390 if (pimg->pending == 256) {
1391 pimg->pending = 0;
1392 return img_XSECT_END;
1394 if (pimg->pending >= 0x80) {
1395 *p = pimg->mv;
1396 pimg->flags = (int)(pimg->pending) & 0x3f;
1397 pimg->pending = 0;
1398 return img_LINE;
1400 again3: /* label to goto if we get a prefix, date, or lrud */
1401 pimg->label = pimg->label_buf;
1402 opt = GETC(pimg->fh);
1403 if (opt == EOF) {
1404 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1405 return img_BAD;
1407 switch (opt >> 6) {
1408 case 0:
1409 if (opt == 0) {
1410 if (!pimg->label_len) return img_STOP; /* end of data marker */
1411 pimg->label_len = 0;
1412 goto again3;
1414 if (opt < 15) {
1415 /* 1-14 mean trim that many levels from current prefix */
1416 int c;
1417 if (pimg->label_len <= 17) {
1418 /* zero prefix using "0" */
1419 img_errno = IMG_BADFORMAT;
1420 return img_BAD;
1422 /* extra - 1 because label_len points to one past the end */
1423 c = pimg->label_len - 17 - 1;
1424 while (pimg->label_buf[c] != '.' || --opt > 0) {
1425 if (--c < 0) {
1426 /* zero prefix using "0" */
1427 img_errno = IMG_BADFORMAT;
1428 return img_BAD;
1431 c++;
1432 pimg->label_len = c;
1433 goto again3;
1435 if (opt == 15) {
1436 result = img_MOVE;
1437 break;
1439 if (opt >= 0x20) {
1440 switch (opt) {
1441 case 0x20: /* Single date */
1442 if (pimg->version < 7) {
1443 int date1 = get32(pimg->fh);
1444 #if IMG_API_VERSION == 0
1445 pimg->date2 = pimg->date1 = date1;
1446 #else /* IMG_API_VERSION == 1 */
1447 if (date1 != 0) {
1448 pimg->days2 = pimg->days1 = (date1 / 86400) + 25567;
1449 } else {
1450 pimg->days2 = pimg->days1 = -1;
1452 #endif
1453 } else {
1454 int days1 = (int)getu16(pimg->fh);
1455 #if IMG_API_VERSION == 0
1456 pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
1457 #else /* IMG_API_VERSION == 1 */
1458 pimg->days2 = pimg->days1 = days1;
1459 #endif
1461 break;
1462 case 0x21: /* Date range (short for v7+) */
1463 if (pimg->version < 7) {
1464 INT32_T date1 = get32(pimg->fh);
1465 INT32_T date2 = get32(pimg->fh);
1466 #if IMG_API_VERSION == 0
1467 pimg->date1 = date1;
1468 pimg->date2 = date2;
1469 #else /* IMG_API_VERSION == 1 */
1470 pimg->days1 = (date1 / 86400) + 25567;
1471 pimg->days2 = (date2 / 86400) + 25567;
1472 #endif
1473 } else {
1474 int days1 = (int)getu16(pimg->fh);
1475 int days2 = days1 + GETC(pimg->fh) + 1;
1476 #if IMG_API_VERSION == 0
1477 pimg->date1 = (days1 - 25567) * 86400;
1478 pimg->date2 = (days2 - 25567) * 86400;
1479 #else /* IMG_API_VERSION == 1 */
1480 pimg->days1 = days1;
1481 pimg->days2 = days2;
1482 #endif
1484 break;
1485 case 0x22: /* Error info */
1486 pimg->n_legs = get32(pimg->fh);
1487 pimg->length = get32(pimg->fh) / 100.0;
1488 pimg->E = get32(pimg->fh) / 100.0;
1489 pimg->H = get32(pimg->fh) / 100.0;
1490 pimg->V = get32(pimg->fh) / 100.0;
1491 if (feof(pimg->fh)) {
1492 img_errno = IMG_BADFORMAT;
1493 return img_BAD;
1495 if (ferror(pimg->fh)) {
1496 img_errno = IMG_READERROR;
1497 return img_BAD;
1499 return img_ERROR_INFO;
1500 case 0x23: { /* v7+: Date range (long) */
1501 if (pimg->version < 7) {
1502 img_errno = IMG_BADFORMAT;
1503 return img_BAD;
1505 int days1 = (int)getu16(pimg->fh);
1506 int days2 = (int)getu16(pimg->fh);
1507 if (feof(pimg->fh)) {
1508 img_errno = IMG_BADFORMAT;
1509 return img_BAD;
1511 if (ferror(pimg->fh)) {
1512 img_errno = IMG_READERROR;
1513 return img_BAD;
1515 #if IMG_API_VERSION == 0
1516 pimg->date1 = (days1 - 25567) * 86400;
1517 pimg->date2 = (days2 - 25567) * 86400;
1518 #else /* IMG_API_VERSION == 1 */
1519 pimg->days1 = days1;
1520 pimg->days2 = days2;
1521 #endif
1522 break;
1524 case 0x24: { /* v7+: No date info */
1525 #if IMG_API_VERSION == 0
1526 pimg->date1 = pimg->date2 = 0;
1527 #else /* IMG_API_VERSION == 1 */
1528 pimg->days1 = pimg->days2 = -1;
1529 #endif
1530 break;
1532 case 0x30: case 0x31: /* LRUD */
1533 case 0x32: case 0x33: /* Big LRUD! */
1534 if (read_v3label(pimg) == img_BAD) return img_BAD;
1535 pimg->flags = (int)opt & 0x01;
1536 if (opt < 0x32) {
1537 pimg->l = get16(pimg->fh) / 100.0;
1538 pimg->r = get16(pimg->fh) / 100.0;
1539 pimg->u = get16(pimg->fh) / 100.0;
1540 pimg->d = get16(pimg->fh) / 100.0;
1541 } else {
1542 pimg->l = get32(pimg->fh) / 100.0;
1543 pimg->r = get32(pimg->fh) / 100.0;
1544 pimg->u = get32(pimg->fh) / 100.0;
1545 pimg->d = get32(pimg->fh) / 100.0;
1547 if (feof(pimg->fh)) {
1548 img_errno = IMG_BADFORMAT;
1549 return img_BAD;
1551 if (ferror(pimg->fh)) {
1552 img_errno = IMG_READERROR;
1553 return img_BAD;
1555 if (pimg->survey_len) {
1556 size_t l = pimg->survey_len;
1557 const char *s = pimg->label_buf;
1558 if (strncmp(pimg->survey, s, l + 1) != 0) {
1559 return img_XSECT_END;
1561 pimg->label += l;
1562 /* skip the dot if there */
1563 if (*pimg->label) pimg->label++;
1565 /* If this is the last cross-section in this passage, set
1566 * pending so we return img_XSECT_END next time. */
1567 if (pimg->flags & 0x01) {
1568 pimg->pending = 256;
1569 pimg->flags &= ~0x01;
1571 return img_XSECT;
1572 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1573 img_errno = IMG_BADFORMAT;
1574 return img_BAD;
1576 if (feof(pimg->fh)) {
1577 img_errno = IMG_BADFORMAT;
1578 return img_BAD;
1580 if (ferror(pimg->fh)) {
1581 img_errno = IMG_READERROR;
1582 return img_BAD;
1584 goto again3;
1586 /* 16-31 mean remove (n - 15) characters from the prefix */
1587 /* zero prefix using 0 */
1588 if (pimg->label_len <= (size_t)(opt - 15)) {
1589 img_errno = IMG_BADFORMAT;
1590 return img_BAD;
1592 pimg->label_len -= (opt - 15);
1593 goto again3;
1594 case 1:
1595 if (read_v3label(pimg) == img_BAD) return img_BAD;
1597 result = img_LABEL;
1599 if (pimg->survey_len) {
1600 size_t l = pimg->survey_len;
1601 const char *s = pimg->label_buf;
1602 if (strncmp(pimg->survey, s, l + 1) != 0) {
1603 if (!skip_coord(pimg->fh)) return img_BAD;
1604 pimg->pending = 0;
1605 goto again3;
1607 pimg->label += l;
1608 /* skip the dot if there */
1609 if (*pimg->label) pimg->label++;
1612 pimg->flags = (int)opt & 0x3f;
1613 break;
1614 case 2:
1615 if (read_v3label(pimg) == img_BAD) return img_BAD;
1617 result = img_LINE;
1619 if (pimg->survey_len) {
1620 size_t l = pimg->survey_len;
1621 const char *s = pimg->label_buf;
1622 if (strncmp(pimg->survey, s, l) != 0 ||
1623 !(s[l] == '.' || s[l] == '\0')) {
1624 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1625 pimg->pending = 15;
1626 goto again3;
1628 pimg->label += l;
1629 /* skip the dot if there */
1630 if (*pimg->label) pimg->label++;
1633 if (pimg->pending) {
1634 *p = pimg->mv;
1635 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1636 pimg->pending = opt;
1637 return img_MOVE;
1639 pimg->flags = (int)opt & 0x3f;
1640 break;
1641 default:
1642 img_errno = IMG_BADFORMAT;
1643 return img_BAD;
1645 if (!read_coord(pimg->fh, p)) return img_BAD;
1646 pimg->pending = 0;
1647 return result;
1650 static int
1651 img_read_item_ancient(img *pimg, img_point *p)
1653 int result;
1654 static long opt_lookahead = 0;
1655 static img_point pt = { 0.0, 0.0, 0.0 };
1656 long opt;
1658 again: /* label to goto if we get a cross */
1659 pimg->label = pimg->label_buf;
1660 pimg->label[0] = '\0';
1662 if (pimg->version == 1) {
1663 if (opt_lookahead) {
1664 opt = opt_lookahead;
1665 opt_lookahead = 0;
1666 } else {
1667 opt = get32(pimg->fh);
1669 } else {
1670 opt = GETC(pimg->fh);
1673 if (feof(pimg->fh)) {
1674 img_errno = IMG_BADFORMAT;
1675 return img_BAD;
1677 if (ferror(pimg->fh)) {
1678 img_errno = IMG_READERROR;
1679 return img_BAD;
1682 switch (opt) {
1683 case -1: case 0:
1684 return img_STOP; /* end of data marker */
1685 case 1:
1686 /* skip coordinates */
1687 if (!skip_coord(pimg->fh)) {
1688 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1689 return img_BAD;
1691 goto again;
1692 case 2: case 3: {
1693 size_t len;
1694 result = img_LABEL;
1695 if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
1696 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1697 return img_BAD;
1699 if (pimg->label[0] == '\\') pimg->label++;
1700 len = strlen(pimg->label);
1701 if (len == 0 || pimg->label[len - 1] != '\n') {
1702 img_errno = IMG_BADFORMAT;
1703 return img_BAD;
1705 /* Ignore empty labels in some .3d files (caused by a bug) */
1706 if (len == 1) goto again;
1707 pimg->label[len - 1] = '\0';
1708 pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1709 if (opt == 2) goto done;
1710 break;
1712 case 6: case 7: {
1713 long len;
1714 result = img_LABEL;
1716 if (opt == 7)
1717 pimg->flags = GETC(pimg->fh);
1718 else
1719 pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1721 len = get32(pimg->fh);
1723 if (feof(pimg->fh)) {
1724 img_errno = IMG_BADFORMAT;
1725 return img_BAD;
1727 if (ferror(pimg->fh)) {
1728 img_errno = IMG_READERROR;
1729 return img_BAD;
1732 /* Ignore empty labels in some .3d files (caused by a bug) */
1733 if (len == 0) goto again;
1734 if (!check_label_space(pimg, len + 1)) {
1735 img_errno = IMG_OUTOFMEMORY;
1736 return img_BAD;
1738 if (fread(pimg->label_buf, len, 1, pimg->fh) != 1) {
1739 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1740 return img_BAD;
1742 pimg->label_buf[len] = '\0';
1743 break;
1745 case 4:
1746 result = img_MOVE;
1747 break;
1748 case 5:
1749 result = img_LINE;
1750 break;
1751 default:
1752 switch ((int)opt & 0xc0) {
1753 case 0x80:
1754 pimg->flags = (int)opt & 0x3f;
1755 result = img_LINE;
1756 break;
1757 case 0x40: {
1758 char *q;
1759 pimg->flags = (int)opt & 0x3f;
1760 result = img_LABEL;
1761 if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
1762 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1763 return img_BAD;
1765 q = pimg->label_buf + strlen(pimg->label_buf) - 1;
1766 /* Ignore empty-labels in some .3d files (caused by a bug) */
1767 if (q == pimg->label_buf) goto again;
1768 if (*q != '\n') {
1769 img_errno = IMG_BADFORMAT;
1770 return img_BAD;
1772 *q = '\0';
1773 break;
1775 default:
1776 img_errno = IMG_BADFORMAT;
1777 return img_BAD;
1779 break;
1782 if (!read_coord(pimg->fh, &pt)) return img_BAD;
1784 if (result == img_LABEL && pimg->survey_len) {
1785 if (strncmp(pimg->label, pimg->survey, pimg->survey_len + 1) != 0)
1786 goto again;
1787 pimg->label += pimg->survey_len + 1;
1790 done:
1791 *p = pt;
1793 if (result == img_MOVE && pimg->version == 1) {
1794 /* peek at next code and see if it's an old-style label */
1795 opt_lookahead = get32(pimg->fh);
1797 if (feof(pimg->fh)) {
1798 img_errno = IMG_BADFORMAT;
1799 return img_BAD;
1801 if (ferror(pimg->fh)) {
1802 img_errno = IMG_READERROR;
1803 return img_BAD;
1806 if (opt_lookahead == 2) return img_read_item_ancient(pimg, p);
1809 return result;
1812 static int
1813 img_read_item_ascii_wrapper(img *pimg, img_point *p)
1815 /* We need to set the default locale for fscanf() to work on
1816 * numbers with "." as decimal point. */
1817 int result;
1818 char * current_locale = my_strdup(setlocale(LC_NUMERIC, NULL));
1819 setlocale(LC_NUMERIC, "C");
1820 result = img_read_item_ascii(pimg, p);
1821 setlocale(LC_NUMERIC, current_locale);
1822 free(current_locale);
1823 return result;
1826 /* Handle all ASCII formats. */
1827 static int
1828 img_read_item_ascii(img *pimg, img_point *p)
1830 int result;
1831 pimg->label = pimg->label_buf;
1832 if (pimg->version == 0) {
1833 ascii_again:
1834 pimg->label[0] = '\0';
1835 if (feof(pimg->fh)) return img_STOP;
1836 if (pimg->pending) {
1837 pimg->pending = 0;
1838 result = img_LINE;
1839 } else {
1840 char cmd[7];
1841 /* Stop if nothing found */
1842 if (fscanf(pimg->fh, "%6s", cmd) < 1) return img_STOP;
1843 if (strcmp(cmd, "move") == 0)
1844 result = img_MOVE;
1845 else if (strcmp(cmd, "draw") == 0)
1846 result = img_LINE;
1847 else if (strcmp(cmd, "line") == 0) {
1848 /* set flag to indicate to process second triplet as LINE */
1849 pimg->pending = 1;
1850 result = img_MOVE;
1851 } else if (strcmp(cmd, "cross") == 0) {
1852 if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1853 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1854 return img_BAD;
1856 goto ascii_again;
1857 } else if (strcmp(cmd, "name") == 0) {
1858 size_t off = 0;
1859 int ch = GETC(pimg->fh);
1860 if (ch == ' ') ch = GETC(pimg->fh);
1861 while (ch != ' ') {
1862 if (ch == '\n' || ch == EOF) {
1863 img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1864 return img_BAD;
1866 if (off == pimg->buf_len) {
1867 if (!check_label_space(pimg, pimg->buf_len * 2)) {
1868 img_errno = IMG_OUTOFMEMORY;
1869 return img_BAD;
1872 pimg->label_buf[off++] = ch;
1873 ch = GETC(pimg->fh);
1875 pimg->label_buf[off] = '\0';
1877 pimg->label = pimg->label_buf;
1878 if (pimg->label[0] == '\\') pimg->label++;
1880 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1882 result = img_LABEL;
1883 } else {
1884 img_errno = IMG_BADFORMAT;
1885 return img_BAD; /* unknown keyword */
1889 if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1890 img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1891 return img_BAD;
1894 if (result == img_LABEL && pimg->survey_len) {
1895 if (strncmp(pimg->label, pimg->survey, pimg->survey_len + 1) != 0)
1896 goto ascii_again;
1897 pimg->label += pimg->survey_len + 1;
1900 return result;
1901 } else if (pimg->version == VERSION_SURVEX_POS) {
1902 /* Survex .pos file */
1903 int ch;
1904 size_t off;
1905 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1906 againpos:
1907 off = 0;
1908 while (fscanf(pimg->fh, "(%lf,%lf,%lf )", &p->x, &p->y, &p->z) != 3) {
1909 if (ferror(pimg->fh)) {
1910 img_errno = IMG_READERROR;
1911 return img_BAD;
1913 if (feof(pimg->fh)) return img_STOP;
1914 if (pimg->pending) {
1915 img_errno = IMG_BADFORMAT;
1916 return img_BAD;
1918 pimg->pending = 1;
1919 /* ignore rest of line */
1920 do {
1921 ch = GETC(pimg->fh);
1922 } while (ch != '\n' && ch != '\r' && ch != EOF);
1925 pimg->label_buf[0] = '\0';
1926 do {
1927 ch = GETC(pimg->fh);
1928 } while (ch == ' ' || ch == '\t');
1929 if (ch == '\n' || ch == EOF) {
1930 /* If there's no label, set img_SFLAG_ANON. */
1931 pimg->flags |= img_SFLAG_ANON;
1932 return img_LABEL;
1934 pimg->label_buf[0] = ch;
1935 off = 1;
1936 while (!feof(pimg->fh)) {
1937 if (!fgets(pimg->label_buf + off, pimg->buf_len - off, pimg->fh)) {
1938 img_errno = IMG_READERROR;
1939 return img_BAD;
1942 off += strlen(pimg->label_buf + off);
1943 if (off && pimg->label_buf[off - 1] == '\n') {
1944 pimg->label_buf[off - 1] = '\0';
1945 break;
1947 if (!check_label_space(pimg, pimg->buf_len * 2)) {
1948 img_errno = IMG_OUTOFMEMORY;
1949 return img_BAD;
1953 pimg->label = pimg->label_buf;
1955 if (pimg->label[0] == '\\') pimg->label++;
1957 if (pimg->survey_len) {
1958 size_t l = pimg->survey_len + 1;
1959 if (strncmp(pimg->survey, pimg->label, l) != 0) goto againpos;
1960 pimg->label += l;
1963 return img_LABEL;
1964 } else if (pimg->version == VERSION_COMPASS_PLT) {
1965 /* Compass .plt file */
1966 if (pimg->pending > 0) {
1967 /* -1 signals we've entered the first survey we want to
1968 * read, and need to fudge lots if the first action is 'D'...
1970 /* pending MOVE or LINE */
1971 int r = pimg->pending - 4;
1972 pimg->pending = 0;
1973 pimg->flags = 0;
1974 pimg->label[pimg->label_len] = '\0';
1975 return r;
1978 while (1) {
1979 char *line;
1980 char *q;
1981 size_t len = 0;
1982 int ch = GETC(pimg->fh);
1984 switch (ch) {
1985 case '\x1a': case EOF: /* Don't insist on ^Z at end of file */
1986 return img_STOP;
1987 case 'X': case 'F': case 'S':
1988 /* bounding boX (marks end of survey), Feature survey, or
1989 * new Section - skip to next survey */
1990 if (pimg->survey) return img_STOP;
1991 skip_to_N:
1992 while (1) {
1993 do {
1994 ch = GETC(pimg->fh);
1995 } while (ch != '\n' && ch != '\r' && ch != EOF);
1996 while (ch == '\n' || ch == '\r') ch = GETC(pimg->fh);
1997 if (ch == 'N') break;
1998 if (ch == '\x1a' || ch == EOF) return img_STOP;
2000 /* FALLTHRU */
2001 case 'N':
2002 line = getline_alloc(pimg->fh);
2003 if (!line) {
2004 img_errno = IMG_OUTOFMEMORY;
2005 return img_BAD;
2007 while (line[len] > 32) ++len;
2008 if (pimg->label_len == 0) pimg->pending = -1;
2009 if (!check_label_space(pimg, len + 1)) {
2010 osfree(line);
2011 img_errno = IMG_OUTOFMEMORY;
2012 return img_BAD;
2014 pimg->label_len = len;
2015 pimg->label = pimg->label_buf;
2016 memcpy(pimg->label, line, len);
2017 pimg->label[len] = '\0';
2018 osfree(line);
2019 break;
2020 case 'M': case 'D': {
2021 /* Move or Draw */
2022 long fpos = -1;
2023 if (pimg->survey && pimg->label_len == 0) {
2024 /* We're only holding onto this line in case the first line
2025 * of the 'N' is a 'D', so skip it for now...
2027 goto skip_to_N;
2029 if (ch == 'D' && pimg->pending == -1) {
2030 if (pimg->survey) {
2031 fpos = ftell(pimg->fh) - 1;
2032 fseek(pimg->fh, pimg->start, SEEK_SET);
2033 ch = GETC(pimg->fh);
2034 pimg->pending = 0;
2035 } else {
2036 /* If a file actually has a 'D' before any 'M', then
2037 * pretend the 'D' is an 'M' - one of the examples
2038 * in the docs was like this! */
2039 ch = 'M';
2042 line = getline_alloc(pimg->fh);
2043 if (!line) {
2044 img_errno = IMG_OUTOFMEMORY;
2045 return img_BAD;
2047 /* Compass stores coordinates as North, East, Up = (y,x,z)! */
2048 if (sscanf(line, "%lf%lf%lf", &p->y, &p->x, &p->z) != 3) {
2049 osfree(line);
2050 if (ferror(pimg->fh)) {
2051 img_errno = IMG_READERROR;
2052 } else {
2053 img_errno = IMG_BADFORMAT;
2055 return img_BAD;
2057 p->x *= METRES_PER_FOOT;
2058 p->y *= METRES_PER_FOOT;
2059 p->z *= METRES_PER_FOOT;
2060 q = strchr(line, 'S');
2061 if (!q) {
2062 osfree(line);
2063 img_errno = IMG_BADFORMAT;
2064 return img_BAD;
2066 ++q;
2067 len = 0;
2068 while (q[len] > ' ') ++len;
2069 q[len] = '\0';
2070 len += 2; /* ' ' and '\0' */
2071 if (!check_label_space(pimg, pimg->label_len + len)) {
2072 img_errno = IMG_OUTOFMEMORY;
2073 return img_BAD;
2075 pimg->label = pimg->label_buf;
2076 if (pimg->label_len) {
2077 pimg->label[pimg->label_len] = ' ';
2078 memcpy(pimg->label + pimg->label_len + 1, q, len - 1);
2079 } else {
2080 memcpy(pimg->label, q, len - 1);
2082 q += len - 1;
2083 /* Now read LRUD. Technically, this is optional but virtually
2084 * all PLT files have it (with dummy negative values if no LRUD
2085 * was measured) and some versions of Compass can't read PLT
2086 * files without it!
2088 while (*q && *q <= ' ') q++;
2089 if (*q == 'P') {
2090 if (sscanf(q + 1, "%lf%lf%lf%lf",
2091 &pimg->l, &pimg->r, &pimg->u, &pimg->d) != 4) {
2092 osfree(line);
2093 if (ferror(pimg->fh)) {
2094 img_errno = IMG_READERROR;
2095 } else {
2096 img_errno = IMG_BADFORMAT;
2098 return img_BAD;
2100 pimg->l *= METRES_PER_FOOT;
2101 pimg->r *= METRES_PER_FOOT;
2102 pimg->u *= METRES_PER_FOOT;
2103 pimg->d *= METRES_PER_FOOT;
2104 } else {
2105 pimg->l = pimg->r = pimg->u = pimg->d = -1;
2107 osfree(line);
2108 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
2109 if (fpos != -1) {
2110 fseek(pimg->fh, fpos, SEEK_SET);
2111 } else {
2112 pimg->pending = (ch == 'M' ? img_MOVE : img_LINE) + 4;
2114 return img_LABEL;
2116 default:
2117 img_errno = IMG_BADFORMAT;
2118 return img_BAD;
2121 } else {
2122 /* CMAP .xyz file */
2123 char *line = NULL;
2124 char *q;
2125 size_t len;
2127 if (pimg->pending) {
2128 /* pending MOVE or LINE or LABEL or STOP */
2129 int r = pimg->pending - 4;
2130 /* Set label to empty - don't use "" as we adjust label relative
2131 * to label_buf when label_buf is reallocated. */
2132 pimg->label = pimg->label_buf + strlen(pimg->label_buf);
2133 pimg->flags = 0;
2134 if (r == img_LABEL) {
2135 /* nasty magic */
2136 read_xyz_shot_coords(p, pimg->label_buf + 16);
2137 subtract_xyz_shot_deltas(p, pimg->label_buf + 16);
2138 pimg->pending = img_STOP + 4;
2139 return img_MOVE;
2142 pimg->pending = 0;
2144 if (r == img_STOP) {
2145 /* nasty magic */
2146 read_xyz_shot_coords(p, pimg->label_buf + 16);
2147 return img_LINE;
2150 return r;
2153 pimg->label = pimg->label_buf;
2154 do {
2155 osfree(line);
2156 if (feof(pimg->fh)) return img_STOP;
2157 line = getline_alloc(pimg->fh);
2158 if (!line) {
2159 img_errno = IMG_OUTOFMEMORY;
2160 return img_BAD;
2162 } while (line[0] == ' ' || line[0] == '\0');
2163 if (line[0] == '\x1a') return img_STOP;
2165 len = strlen(line);
2166 if (pimg->version == VERSION_CMAP_STATION) {
2167 /* station variant */
2168 if (len < 37) {
2169 osfree(line);
2170 img_errno = IMG_BADFORMAT;
2171 return img_BAD;
2173 memcpy(pimg->label, line, 6);
2174 q = (char *)memchr(pimg->label, ' ', 6);
2175 if (!q) q = pimg->label + 6;
2176 *q = '\0';
2178 read_xyz_station_coords(p, line);
2180 /* FIXME: look at prev for lines (line + 32, 5) */
2181 /* FIXME: duplicate stations... */
2182 return img_LABEL;
2183 } else {
2184 /* Shot variant (VERSION_CMAP_SHOT) */
2185 char old[8], new_[8];
2186 if (len < 61) {
2187 osfree(line);
2188 img_errno = IMG_BADFORMAT;
2189 return img_BAD;
2192 memcpy(old, line, 7);
2193 q = (char *)memchr(old, ' ', 7);
2194 if (!q) q = old + 7;
2195 *q = '\0';
2197 memcpy(new_, line + 7, 7);
2198 q = (char *)memchr(new_, ' ', 7);
2199 if (!q) q = new_ + 7;
2200 *q = '\0';
2202 pimg->flags = img_SFLAG_UNDERGROUND;
2204 if (strcmp(old, new_) == 0) {
2205 pimg->pending = img_MOVE + 4;
2206 read_xyz_shot_coords(p, line);
2207 strcpy(pimg->label, new_);
2208 osfree(line);
2209 return img_LABEL;
2212 if (strcmp(old, pimg->label) == 0) {
2213 pimg->pending = img_LINE + 4;
2214 read_xyz_shot_coords(p, line);
2215 strcpy(pimg->label, new_);
2216 osfree(line);
2217 return img_LABEL;
2220 pimg->pending = img_LABEL + 4;
2221 read_xyz_shot_coords(p, line);
2222 strcpy(pimg->label, new_);
2223 memcpy(pimg->label + 16, line, 70);
2225 osfree(line);
2226 return img_LABEL;
2231 static void
2232 write_coord(FILE *fh, double x, double y, double z)
2234 SVX_ASSERT(fh);
2235 /* Output in cm */
2236 static INT32_T X_, Y_, Z_;
2237 INT32_T X = my_lround(x * 100.0);
2238 INT32_T Y = my_lround(y * 100.0);
2239 INT32_T Z = my_lround(z * 100.0);
2241 X_ -= X;
2242 Y_ -= Y;
2243 Z_ -= Z;
2244 put32(X, fh);
2245 put32(Y, fh);
2246 put32(Z, fh);
2247 X_ = X; Y_ = Y; Z_ = Z;
2250 static int
2251 write_v3label(img *pimg, int opt, const char *s)
2253 size_t len, n, dot;
2255 /* find length of common prefix */
2256 dot = 0;
2257 for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2258 if (s[len] == '.') dot = len + 1;
2261 SVX_ASSERT(len <= pimg->label_len);
2262 n = pimg->label_len - len;
2263 if (len == 0) {
2264 if (pimg->label_len) PUTC(0, pimg->fh);
2265 } else if (n <= 16) {
2266 if (n) PUTC(n + 15, pimg->fh);
2267 } else if (dot == 0) {
2268 if (pimg->label_len) PUTC(0, pimg->fh);
2269 len = 0;
2270 } else {
2271 const char *p = pimg->label_buf + dot;
2272 n = 1;
2273 for (len = pimg->label_len - dot - 17; len; len--) {
2274 if (*p++ == '.') n++;
2276 if (n <= 14) {
2277 PUTC(n, pimg->fh);
2278 len = dot;
2279 } else {
2280 if (pimg->label_len) PUTC(0, pimg->fh);
2281 len = 0;
2285 n = strlen(s + len);
2286 PUTC(opt, pimg->fh);
2287 if (n < 0xfe) {
2288 PUTC(n, pimg->fh);
2289 } else if (n < 0xffff + 0xfe) {
2290 PUTC(0xfe, pimg->fh);
2291 put16((short)(n - 0xfe), pimg->fh);
2292 } else {
2293 PUTC(0xff, pimg->fh);
2294 put32(n, pimg->fh);
2296 fwrite(s + len, n, 1, pimg->fh);
2298 n += len;
2299 pimg->label_len = n;
2300 if (!check_label_space(pimg, n + 1))
2301 return 0; /* FIXME: distinguish out of memory... */
2302 memcpy(pimg->label_buf + len, s + len, n - len + 1);
2304 return !ferror(pimg->fh);
2307 static int
2308 write_v8label(img *pimg, int opt, int common_flag, size_t common_val,
2309 const char *s)
2311 size_t len, del, add;
2313 /* find length of common prefix */
2314 for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2317 SVX_ASSERT(len <= pimg->label_len);
2318 del = pimg->label_len - len;
2319 add = strlen(s + len);
2321 if (add == common_val && del == common_val) {
2322 PUTC(opt | common_flag, pimg->fh);
2323 } else {
2324 PUTC(opt, pimg->fh);
2325 if (del <= 15 && add <= 15 && (del || add)) {
2326 PUTC((del << 4) | add, pimg->fh);
2327 } else {
2328 PUTC(0x00, pimg->fh);
2329 if (del < 0xff) {
2330 PUTC(del, pimg->fh);
2331 } else {
2332 PUTC(0xff, pimg->fh);
2333 put32(del, pimg->fh);
2335 if (add < 0xff) {
2336 PUTC(add, pimg->fh);
2337 } else {
2338 PUTC(0xff, pimg->fh);
2339 put32(add, pimg->fh);
2344 if (add)
2345 fwrite(s + len, add, 1, pimg->fh);
2347 pimg->label_len = len + add;
2348 if (add > del && !check_label_space(pimg, pimg->label_len + 1))
2349 return 0; /* FIXME: distinguish out of memory... */
2351 memcpy(pimg->label_buf + len, s + len, add + 1);
2353 return !ferror(pimg->fh);
2356 static void
2357 img_write_item_date_new(img *pimg)
2359 int same, unset;
2360 /* Only write dates when they've changed. */
2361 #if IMG_API_VERSION == 0
2362 if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2363 return;
2365 same = (pimg->date1 == pimg->date2);
2366 unset = (pimg->date1 == 0);
2367 #else /* IMG_API_VERSION == 1 */
2368 if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2369 return;
2371 same = (pimg->days1 == pimg->days2);
2372 unset = (pimg->days1 == -1);
2373 #endif
2375 if (same) {
2376 if (unset) {
2377 PUTC(0x10, pimg->fh);
2378 } else {
2379 PUTC(0x11, pimg->fh);
2380 #if IMG_API_VERSION == 0
2381 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2382 #else /* IMG_API_VERSION == 1 */
2383 put16(pimg->days1, pimg->fh);
2384 #endif
2386 } else {
2387 #if IMG_API_VERSION == 0
2388 int diff = (pimg->date2 - pimg->date1) / 86400;
2389 if (diff > 0 && diff <= 256) {
2390 PUTC(0x12, pimg->fh);
2391 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2392 PUTC(diff - 1, pimg->fh);
2393 } else {
2394 PUTC(0x13, pimg->fh);
2395 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2396 put16(pimg->date2 / 86400 + 25567, pimg->fh);
2398 #else /* IMG_API_VERSION == 1 */
2399 int diff = pimg->days2 - pimg->days1;
2400 if (diff > 0 && diff <= 256) {
2401 PUTC(0x12, pimg->fh);
2402 put16(pimg->days1, pimg->fh);
2403 PUTC(diff - 1, pimg->fh);
2404 } else {
2405 PUTC(0x13, pimg->fh);
2406 put16(pimg->days1, pimg->fh);
2407 put16(pimg->days2, pimg->fh);
2409 #endif
2411 #if IMG_API_VERSION == 0
2412 pimg->olddate1 = pimg->date1;
2413 pimg->olddate2 = pimg->date2;
2414 #else /* IMG_API_VERSION == 1 */
2415 pimg->olddays1 = pimg->days1;
2416 pimg->olddays2 = pimg->days2;
2417 #endif
2420 static void
2421 img_write_item_date(img *pimg)
2423 int same, unset;
2424 /* Only write dates when they've changed. */
2425 #if IMG_API_VERSION == 0
2426 if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2427 return;
2429 same = (pimg->date1 == pimg->date2);
2430 unset = (pimg->date1 == 0);
2431 #else /* IMG_API_VERSION == 1 */
2432 if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2433 return;
2435 same = (pimg->days1 == pimg->days2);
2436 unset = (pimg->days1 == -1);
2437 #endif
2439 if (same) {
2440 if (img_output_version < 7) {
2441 PUTC(0x20, pimg->fh);
2442 #if IMG_API_VERSION == 0
2443 put32(pimg->date1, pimg->fh);
2444 #else /* IMG_API_VERSION == 1 */
2445 put32((pimg->days1 - 25567) * 86400, pimg->fh);
2446 #endif
2447 } else {
2448 if (unset) {
2449 PUTC(0x24, pimg->fh);
2450 } else {
2451 PUTC(0x20, pimg->fh);
2452 #if IMG_API_VERSION == 0
2453 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2454 #else /* IMG_API_VERSION == 1 */
2455 put16(pimg->days1, pimg->fh);
2456 #endif
2459 } else {
2460 if (img_output_version < 7) {
2461 PUTC(0x21, pimg->fh);
2462 #if IMG_API_VERSION == 0
2463 put32(pimg->date1, pimg->fh);
2464 put32(pimg->date2, pimg->fh);
2465 #else /* IMG_API_VERSION == 1 */
2466 put32((pimg->days1 - 25567) * 86400, pimg->fh);
2467 put32((pimg->days2 - 25567) * 86400, pimg->fh);
2468 #endif
2469 } else {
2470 #if IMG_API_VERSION == 0
2471 int diff = (pimg->date2 - pimg->date1) / 86400;
2472 if (diff > 0 && diff <= 256) {
2473 PUTC(0x21, pimg->fh);
2474 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2475 PUTC(diff - 1, pimg->fh);
2476 } else {
2477 PUTC(0x23, pimg->fh);
2478 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2479 put16(pimg->date2 / 86400 + 25567, pimg->fh);
2481 #else /* IMG_API_VERSION == 1 */
2482 int diff = pimg->days2 - pimg->days1;
2483 if (diff > 0 && diff <= 256) {
2484 PUTC(0x21, pimg->fh);
2485 put16(pimg->days1, pimg->fh);
2486 PUTC(diff - 1, pimg->fh);
2487 } else {
2488 PUTC(0x23, pimg->fh);
2489 put16(pimg->days1, pimg->fh);
2490 put16(pimg->days2, pimg->fh);
2492 #endif
2495 #if IMG_API_VERSION == 0
2496 pimg->olddate1 = pimg->date1;
2497 pimg->olddate2 = pimg->date2;
2498 #else /* IMG_API_VERSION == 1 */
2499 pimg->olddays1 = pimg->days1;
2500 pimg->olddays2 = pimg->days2;
2501 #endif
2504 static void
2505 img_write_item_new(img *pimg, int code, int flags, const char *s,
2506 double x, double y, double z);
2507 static void
2508 img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2509 double x, double y, double z);
2510 static void
2511 img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2512 double x, double y, double z);
2514 void
2515 img_write_item(img *pimg, int code, int flags, const char *s,
2516 double x, double y, double z)
2518 if (!pimg) return;
2519 if (pimg->version >= 8) {
2520 img_write_item_new(pimg, code, flags, s, x, y, z);
2521 } else if (pimg->version >= 3) {
2522 img_write_item_v3to7(pimg, code, flags, s, x, y, z);
2523 } else {
2524 img_write_item_ancient(pimg, code, flags, s, x, y, z);
2528 static void
2529 img_write_item_new(img *pimg, int code, int flags, const char *s,
2530 double x, double y, double z)
2532 switch (code) {
2533 case img_LABEL:
2534 write_v8label(pimg, 0x80 | flags, 0, -1, s);
2535 break;
2536 case img_XSECT: {
2537 INT32_T l, r, u, d, max_dim;
2538 img_write_item_date_new(pimg);
2539 l = (INT32_T)my_lround(pimg->l * 100.0);
2540 r = (INT32_T)my_lround(pimg->r * 100.0);
2541 u = (INT32_T)my_lround(pimg->u * 100.0);
2542 d = (INT32_T)my_lround(pimg->d * 100.0);
2543 if (l < 0) l = -1;
2544 if (r < 0) r = -1;
2545 if (u < 0) u = -1;
2546 if (d < 0) d = -1;
2547 max_dim = max(max(l, r), max(u, d));
2548 flags = (flags & img_XFLAG_END) ? 1 : 0;
2549 if (max_dim >= 32768) flags |= 2;
2550 write_v8label(pimg, 0x30 | flags, 0, -1, s);
2551 if (flags & 2) {
2552 /* Big passage! Need to use 4 bytes. */
2553 put32(l, pimg->fh);
2554 put32(r, pimg->fh);
2555 put32(u, pimg->fh);
2556 put32(d, pimg->fh);
2557 } else {
2558 put16(l, pimg->fh);
2559 put16(r, pimg->fh);
2560 put16(u, pimg->fh);
2561 put16(d, pimg->fh);
2563 return;
2565 case img_MOVE:
2566 PUTC(15, pimg->fh);
2567 break;
2568 case img_LINE:
2569 img_write_item_date_new(pimg);
2570 if (pimg->style != pimg->oldstyle) {
2571 switch (pimg->style) {
2572 case img_STYLE_NORMAL:
2573 case img_STYLE_DIVING:
2574 case img_STYLE_CARTESIAN:
2575 case img_STYLE_CYLPOLAR:
2576 case img_STYLE_NOSURVEY:
2577 PUTC(pimg->style, pimg->fh);
2578 break;
2580 pimg->oldstyle = pimg->style;
2582 write_v8label(pimg, 0x40 | flags, 0x20, 0x00, s ? s : "");
2583 break;
2584 default: /* ignore for now */
2585 return;
2587 write_coord(pimg->fh, x, y, z);
2590 static void
2591 img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2592 double x, double y, double z)
2594 switch (code) {
2595 case img_LABEL:
2596 write_v3label(pimg, 0x40 | flags, s);
2597 break;
2598 case img_XSECT: {
2599 INT32_T l, r, u, d, max_dim;
2600 /* Need at least version 5 for img_XSECT. */
2601 if (pimg->version < 5) return;
2602 img_write_item_date(pimg);
2603 l = (INT32_T)my_lround(pimg->l * 100.0);
2604 r = (INT32_T)my_lround(pimg->r * 100.0);
2605 u = (INT32_T)my_lround(pimg->u * 100.0);
2606 d = (INT32_T)my_lround(pimg->d * 100.0);
2607 if (l < 0) l = -1;
2608 if (r < 0) r = -1;
2609 if (u < 0) u = -1;
2610 if (d < 0) d = -1;
2611 max_dim = max(max(l, r), max(u, d));
2612 flags = (flags & img_XFLAG_END) ? 1 : 0;
2613 if (max_dim >= 32768) flags |= 2;
2614 write_v3label(pimg, 0x30 | flags, s);
2615 if (flags & 2) {
2616 /* Big passage! Need to use 4 bytes. */
2617 put32(l, pimg->fh);
2618 put32(r, pimg->fh);
2619 put32(u, pimg->fh);
2620 put32(d, pimg->fh);
2621 } else {
2622 put16(l, pimg->fh);
2623 put16(r, pimg->fh);
2624 put16(u, pimg->fh);
2625 put16(d, pimg->fh);
2627 return;
2629 case img_MOVE:
2630 PUTC(15, pimg->fh);
2631 break;
2632 case img_LINE:
2633 if (pimg->version >= 4) {
2634 img_write_item_date(pimg);
2636 write_v3label(pimg, 0x80 | flags, s ? s : "");
2637 break;
2638 default: /* ignore for now */
2639 return;
2641 write_coord(pimg->fh, x, y, z);
2644 static void
2645 img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2646 double x, double y, double z)
2648 size_t len;
2649 INT32_T opt = 0;
2650 SVX_ASSERT(pimg->version > 0);
2651 switch (code) {
2652 case img_LABEL:
2653 if (pimg->version == 1) {
2654 /* put a move before each label */
2655 img_write_item_ancient(pimg, img_MOVE, 0, NULL, x, y, z);
2656 put32(2, pimg->fh);
2657 fputsnl(s, pimg->fh);
2658 return;
2660 len = strlen(s);
2661 if (len > 255 || strchr(s, '\n')) {
2662 /* long label - not in early incarnations of v2 format, but few
2663 * 3d files will need these, so better not to force incompatibility
2664 * with a new version I think... */
2665 PUTC(7, pimg->fh);
2666 PUTC(flags, pimg->fh);
2667 put32(len, pimg->fh);
2668 fputs(s, pimg->fh);
2669 } else {
2670 PUTC(0x40 | (flags & 0x3f), pimg->fh);
2671 fputsnl(s, pimg->fh);
2673 opt = 0;
2674 break;
2675 case img_MOVE:
2676 opt = 4;
2677 break;
2678 case img_LINE:
2679 if (pimg->version > 1) {
2680 opt = 0x80 | (flags & 0x3f);
2681 break;
2683 opt = 5;
2684 break;
2685 default: /* ignore for now */
2686 return;
2688 if (pimg->version == 1) {
2689 put32(opt, pimg->fh);
2690 } else {
2691 if (opt) PUTC(opt, pimg->fh);
2693 write_coord(pimg->fh, x, y, z);
2696 /* Write error information for the current traverse
2697 * n_legs is the number of legs in the traverse
2698 * length is the traverse length (in m)
2699 * E is the ratio of the observed misclosure to the theoretical one
2700 * H is the ratio of the observed horizontal misclosure to the theoretical one
2701 * V is the ratio of the observed vertical misclosure to the theoretical one
2703 void
2704 img_write_errors(img *pimg, int n_legs, double length,
2705 double E, double H, double V)
2707 PUTC((pimg->version >= 8 ? 0x1f : 0x22), pimg->fh);
2708 put32(n_legs, pimg->fh);
2709 put32((INT32_T)my_lround(length * 100.0), pimg->fh);
2710 put32((INT32_T)my_lround(E * 100.0), pimg->fh);
2711 put32((INT32_T)my_lround(H * 100.0), pimg->fh);
2712 put32((INT32_T)my_lround(V * 100.0), pimg->fh);
2716 img_close(img *pimg)
2718 int result = 1;
2719 if (pimg) {
2720 if (pimg->fh) {
2721 if (pimg->fRead) {
2722 osfree(pimg->survey);
2723 osfree(pimg->title);
2724 osfree(pimg->cs);
2725 osfree(pimg->datestamp);
2726 } else {
2727 /* write end of data marker */
2728 switch (pimg->version) {
2729 case 1:
2730 put32((INT32_T)-1, pimg->fh);
2731 break;
2732 default:
2733 if (pimg->version <= 7 ?
2734 (pimg->label_len != 0) :
2735 (pimg->style != img_STYLE_NORMAL)) {
2736 PUTC(0, pimg->fh);
2738 /* FALL THROUGH */
2739 case 2:
2740 PUTC(0, pimg->fh);
2741 break;
2744 if (ferror(pimg->fh)) result = 0;
2745 if (fclose(pimg->fh)) result = 0;
2746 if (!result) img_errno = pimg->fRead ? IMG_READERROR : IMG_WRITEERROR;
2748 osfree(pimg->label_buf);
2749 osfree(pimg->filename_opened);
2750 osfree(pimg);
2752 return result;