Fix compiler warning
[survex.git] / src / img.c
blob1614e79c333c8d3b2f005812d985bd3fc662e1df
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 while (fscanf(pimg->fh, "(%lf,%lf,%lf )", &p->x, &p->y, &p->z) != 3) {
1908 if (ferror(pimg->fh)) {
1909 img_errno = IMG_READERROR;
1910 return img_BAD;
1912 if (feof(pimg->fh)) return img_STOP;
1913 if (pimg->pending) {
1914 img_errno = IMG_BADFORMAT;
1915 return img_BAD;
1917 pimg->pending = 1;
1918 /* ignore rest of line */
1919 do {
1920 ch = GETC(pimg->fh);
1921 } while (ch != '\n' && ch != '\r' && ch != EOF);
1924 pimg->label_buf[0] = '\0';
1925 do {
1926 ch = GETC(pimg->fh);
1927 } while (ch == ' ' || ch == '\t');
1928 if (ch == '\n' || ch == EOF) {
1929 /* If there's no label, set img_SFLAG_ANON. */
1930 pimg->flags |= img_SFLAG_ANON;
1931 return img_LABEL;
1933 pimg->label_buf[0] = ch;
1934 off = 1;
1935 while (!feof(pimg->fh)) {
1936 if (!fgets(pimg->label_buf + off, pimg->buf_len - off, pimg->fh)) {
1937 img_errno = IMG_READERROR;
1938 return img_BAD;
1941 off += strlen(pimg->label_buf + off);
1942 if (off && pimg->label_buf[off - 1] == '\n') {
1943 pimg->label_buf[off - 1] = '\0';
1944 break;
1946 if (!check_label_space(pimg, pimg->buf_len * 2)) {
1947 img_errno = IMG_OUTOFMEMORY;
1948 return img_BAD;
1952 pimg->label = pimg->label_buf;
1954 if (pimg->label[0] == '\\') pimg->label++;
1956 if (pimg->survey_len) {
1957 size_t l = pimg->survey_len + 1;
1958 if (strncmp(pimg->survey, pimg->label, l) != 0) goto againpos;
1959 pimg->label += l;
1962 return img_LABEL;
1963 } else if (pimg->version == VERSION_COMPASS_PLT) {
1964 /* Compass .plt file */
1965 if (pimg->pending > 0) {
1966 /* -1 signals we've entered the first survey we want to
1967 * read, and need to fudge lots if the first action is 'D'...
1969 /* pending MOVE or LINE */
1970 int r = pimg->pending - 4;
1971 pimg->pending = 0;
1972 pimg->flags = 0;
1973 pimg->label[pimg->label_len] = '\0';
1974 return r;
1977 while (1) {
1978 char *line;
1979 char *q;
1980 size_t len = 0;
1981 int ch = GETC(pimg->fh);
1983 switch (ch) {
1984 case '\x1a': case EOF: /* Don't insist on ^Z at end of file */
1985 return img_STOP;
1986 case 'X': case 'F': case 'S':
1987 /* bounding boX (marks end of survey), Feature survey, or
1988 * new Section - skip to next survey */
1989 if (pimg->survey) return img_STOP;
1990 skip_to_N:
1991 while (1) {
1992 do {
1993 ch = GETC(pimg->fh);
1994 } while (ch != '\n' && ch != '\r' && ch != EOF);
1995 while (ch == '\n' || ch == '\r') ch = GETC(pimg->fh);
1996 if (ch == 'N') break;
1997 if (ch == '\x1a' || ch == EOF) return img_STOP;
1999 /* FALLTHRU */
2000 case 'N':
2001 line = getline_alloc(pimg->fh);
2002 if (!line) {
2003 img_errno = IMG_OUTOFMEMORY;
2004 return img_BAD;
2006 while (line[len] > 32) ++len;
2007 if (pimg->label_len == 0) pimg->pending = -1;
2008 if (!check_label_space(pimg, len + 1)) {
2009 osfree(line);
2010 img_errno = IMG_OUTOFMEMORY;
2011 return img_BAD;
2013 pimg->label_len = len;
2014 pimg->label = pimg->label_buf;
2015 memcpy(pimg->label, line, len);
2016 pimg->label[len] = '\0';
2017 osfree(line);
2018 break;
2019 case 'M': case 'D': {
2020 /* Move or Draw */
2021 long fpos = -1;
2022 if (pimg->survey && pimg->label_len == 0) {
2023 /* We're only holding onto this line in case the first line
2024 * of the 'N' is a 'D', so skip it for now...
2026 goto skip_to_N;
2028 if (ch == 'D' && pimg->pending == -1) {
2029 if (pimg->survey) {
2030 fpos = ftell(pimg->fh) - 1;
2031 fseek(pimg->fh, pimg->start, SEEK_SET);
2032 ch = GETC(pimg->fh);
2033 pimg->pending = 0;
2034 } else {
2035 /* If a file actually has a 'D' before any 'M', then
2036 * pretend the 'D' is an 'M' - one of the examples
2037 * in the docs was like this! */
2038 ch = 'M';
2041 line = getline_alloc(pimg->fh);
2042 if (!line) {
2043 img_errno = IMG_OUTOFMEMORY;
2044 return img_BAD;
2046 /* Compass stores coordinates as North, East, Up = (y,x,z)! */
2047 if (sscanf(line, "%lf%lf%lf", &p->y, &p->x, &p->z) != 3) {
2048 osfree(line);
2049 if (ferror(pimg->fh)) {
2050 img_errno = IMG_READERROR;
2051 } else {
2052 img_errno = IMG_BADFORMAT;
2054 return img_BAD;
2056 p->x *= METRES_PER_FOOT;
2057 p->y *= METRES_PER_FOOT;
2058 p->z *= METRES_PER_FOOT;
2059 q = strchr(line, 'S');
2060 if (!q) {
2061 osfree(line);
2062 img_errno = IMG_BADFORMAT;
2063 return img_BAD;
2065 ++q;
2066 len = 0;
2067 while (q[len] > ' ') ++len;
2068 q[len] = '\0';
2069 len += 2; /* ' ' and '\0' */
2070 if (!check_label_space(pimg, pimg->label_len + len)) {
2071 img_errno = IMG_OUTOFMEMORY;
2072 return img_BAD;
2074 pimg->label = pimg->label_buf;
2075 if (pimg->label_len) {
2076 pimg->label[pimg->label_len] = ' ';
2077 memcpy(pimg->label + pimg->label_len + 1, q, len - 1);
2078 } else {
2079 memcpy(pimg->label, q, len - 1);
2081 q += len - 1;
2082 /* Now read LRUD. Technically, this is optional but virtually
2083 * all PLT files have it (with dummy negative values if no LRUD
2084 * was measured) and some versions of Compass can't read PLT
2085 * files without it!
2087 while (*q && *q <= ' ') q++;
2088 if (*q == 'P') {
2089 if (sscanf(q + 1, "%lf%lf%lf%lf",
2090 &pimg->l, &pimg->r, &pimg->u, &pimg->d) != 4) {
2091 osfree(line);
2092 if (ferror(pimg->fh)) {
2093 img_errno = IMG_READERROR;
2094 } else {
2095 img_errno = IMG_BADFORMAT;
2097 return img_BAD;
2099 pimg->l *= METRES_PER_FOOT;
2100 pimg->r *= METRES_PER_FOOT;
2101 pimg->u *= METRES_PER_FOOT;
2102 pimg->d *= METRES_PER_FOOT;
2103 } else {
2104 pimg->l = pimg->r = pimg->u = pimg->d = -1;
2106 osfree(line);
2107 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
2108 if (fpos != -1) {
2109 fseek(pimg->fh, fpos, SEEK_SET);
2110 } else {
2111 pimg->pending = (ch == 'M' ? img_MOVE : img_LINE) + 4;
2113 return img_LABEL;
2115 default:
2116 img_errno = IMG_BADFORMAT;
2117 return img_BAD;
2120 } else {
2121 /* CMAP .xyz file */
2122 char *line = NULL;
2123 char *q;
2124 size_t len;
2126 if (pimg->pending) {
2127 /* pending MOVE or LINE or LABEL or STOP */
2128 int r = pimg->pending - 4;
2129 /* Set label to empty - don't use "" as we adjust label relative
2130 * to label_buf when label_buf is reallocated. */
2131 pimg->label = pimg->label_buf + strlen(pimg->label_buf);
2132 pimg->flags = 0;
2133 if (r == img_LABEL) {
2134 /* nasty magic */
2135 read_xyz_shot_coords(p, pimg->label_buf + 16);
2136 subtract_xyz_shot_deltas(p, pimg->label_buf + 16);
2137 pimg->pending = img_STOP + 4;
2138 return img_MOVE;
2141 pimg->pending = 0;
2143 if (r == img_STOP) {
2144 /* nasty magic */
2145 read_xyz_shot_coords(p, pimg->label_buf + 16);
2146 return img_LINE;
2149 return r;
2152 pimg->label = pimg->label_buf;
2153 do {
2154 osfree(line);
2155 if (feof(pimg->fh)) return img_STOP;
2156 line = getline_alloc(pimg->fh);
2157 if (!line) {
2158 img_errno = IMG_OUTOFMEMORY;
2159 return img_BAD;
2161 } while (line[0] == ' ' || line[0] == '\0');
2162 if (line[0] == '\x1a') return img_STOP;
2164 len = strlen(line);
2165 if (pimg->version == VERSION_CMAP_STATION) {
2166 /* station variant */
2167 if (len < 37) {
2168 osfree(line);
2169 img_errno = IMG_BADFORMAT;
2170 return img_BAD;
2172 memcpy(pimg->label, line, 6);
2173 q = (char *)memchr(pimg->label, ' ', 6);
2174 if (!q) q = pimg->label + 6;
2175 *q = '\0';
2177 read_xyz_station_coords(p, line);
2179 /* FIXME: look at prev for lines (line + 32, 5) */
2180 /* FIXME: duplicate stations... */
2181 return img_LABEL;
2182 } else {
2183 /* Shot variant (VERSION_CMAP_SHOT) */
2184 char old[8], new_[8];
2185 if (len < 61) {
2186 osfree(line);
2187 img_errno = IMG_BADFORMAT;
2188 return img_BAD;
2191 memcpy(old, line, 7);
2192 q = (char *)memchr(old, ' ', 7);
2193 if (!q) q = old + 7;
2194 *q = '\0';
2196 memcpy(new_, line + 7, 7);
2197 q = (char *)memchr(new_, ' ', 7);
2198 if (!q) q = new_ + 7;
2199 *q = '\0';
2201 pimg->flags = img_SFLAG_UNDERGROUND;
2203 if (strcmp(old, new_) == 0) {
2204 pimg->pending = img_MOVE + 4;
2205 read_xyz_shot_coords(p, line);
2206 strcpy(pimg->label, new_);
2207 osfree(line);
2208 return img_LABEL;
2211 if (strcmp(old, pimg->label) == 0) {
2212 pimg->pending = img_LINE + 4;
2213 read_xyz_shot_coords(p, line);
2214 strcpy(pimg->label, new_);
2215 osfree(line);
2216 return img_LABEL;
2219 pimg->pending = img_LABEL + 4;
2220 read_xyz_shot_coords(p, line);
2221 strcpy(pimg->label, new_);
2222 memcpy(pimg->label + 16, line, 70);
2224 osfree(line);
2225 return img_LABEL;
2230 static void
2231 write_coord(FILE *fh, double x, double y, double z)
2233 SVX_ASSERT(fh);
2234 /* Output in cm */
2235 static INT32_T X_, Y_, Z_;
2236 INT32_T X = my_lround(x * 100.0);
2237 INT32_T Y = my_lround(y * 100.0);
2238 INT32_T Z = my_lround(z * 100.0);
2240 X_ -= X;
2241 Y_ -= Y;
2242 Z_ -= Z;
2243 put32(X, fh);
2244 put32(Y, fh);
2245 put32(Z, fh);
2246 X_ = X; Y_ = Y; Z_ = Z;
2249 static int
2250 write_v3label(img *pimg, int opt, const char *s)
2252 size_t len, n, dot;
2254 /* find length of common prefix */
2255 dot = 0;
2256 for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2257 if (s[len] == '.') dot = len + 1;
2260 SVX_ASSERT(len <= pimg->label_len);
2261 n = pimg->label_len - len;
2262 if (len == 0) {
2263 if (pimg->label_len) PUTC(0, pimg->fh);
2264 } else if (n <= 16) {
2265 if (n) PUTC(n + 15, pimg->fh);
2266 } else if (dot == 0) {
2267 if (pimg->label_len) PUTC(0, pimg->fh);
2268 len = 0;
2269 } else {
2270 const char *p = pimg->label_buf + dot;
2271 n = 1;
2272 for (len = pimg->label_len - dot - 17; len; len--) {
2273 if (*p++ == '.') n++;
2275 if (n <= 14) {
2276 PUTC(n, pimg->fh);
2277 len = dot;
2278 } else {
2279 if (pimg->label_len) PUTC(0, pimg->fh);
2280 len = 0;
2284 n = strlen(s + len);
2285 PUTC(opt, pimg->fh);
2286 if (n < 0xfe) {
2287 PUTC(n, pimg->fh);
2288 } else if (n < 0xffff + 0xfe) {
2289 PUTC(0xfe, pimg->fh);
2290 put16((short)(n - 0xfe), pimg->fh);
2291 } else {
2292 PUTC(0xff, pimg->fh);
2293 put32(n, pimg->fh);
2295 fwrite(s + len, n, 1, pimg->fh);
2297 n += len;
2298 pimg->label_len = n;
2299 if (!check_label_space(pimg, n + 1))
2300 return 0; /* FIXME: distinguish out of memory... */
2301 memcpy(pimg->label_buf + len, s + len, n - len + 1);
2303 return !ferror(pimg->fh);
2306 static int
2307 write_v8label(img *pimg, int opt, int common_flag, size_t common_val,
2308 const char *s)
2310 size_t len, del, add;
2312 /* find length of common prefix */
2313 for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2316 SVX_ASSERT(len <= pimg->label_len);
2317 del = pimg->label_len - len;
2318 add = strlen(s + len);
2320 if (add == common_val && del == common_val) {
2321 PUTC(opt | common_flag, pimg->fh);
2322 } else {
2323 PUTC(opt, pimg->fh);
2324 if (del <= 15 && add <= 15 && (del || add)) {
2325 PUTC((del << 4) | add, pimg->fh);
2326 } else {
2327 PUTC(0x00, pimg->fh);
2328 if (del < 0xff) {
2329 PUTC(del, pimg->fh);
2330 } else {
2331 PUTC(0xff, pimg->fh);
2332 put32(del, pimg->fh);
2334 if (add < 0xff) {
2335 PUTC(add, pimg->fh);
2336 } else {
2337 PUTC(0xff, pimg->fh);
2338 put32(add, pimg->fh);
2343 if (add)
2344 fwrite(s + len, add, 1, pimg->fh);
2346 pimg->label_len = len + add;
2347 if (add > del && !check_label_space(pimg, pimg->label_len + 1))
2348 return 0; /* FIXME: distinguish out of memory... */
2350 memcpy(pimg->label_buf + len, s + len, add + 1);
2352 return !ferror(pimg->fh);
2355 static void
2356 img_write_item_date_new(img *pimg)
2358 int same, unset;
2359 /* Only write dates when they've changed. */
2360 #if IMG_API_VERSION == 0
2361 if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2362 return;
2364 same = (pimg->date1 == pimg->date2);
2365 unset = (pimg->date1 == 0);
2366 #else /* IMG_API_VERSION == 1 */
2367 if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2368 return;
2370 same = (pimg->days1 == pimg->days2);
2371 unset = (pimg->days1 == -1);
2372 #endif
2374 if (same) {
2375 if (unset) {
2376 PUTC(0x10, pimg->fh);
2377 } else {
2378 PUTC(0x11, pimg->fh);
2379 #if IMG_API_VERSION == 0
2380 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2381 #else /* IMG_API_VERSION == 1 */
2382 put16(pimg->days1, pimg->fh);
2383 #endif
2385 } else {
2386 #if IMG_API_VERSION == 0
2387 int diff = (pimg->date2 - pimg->date1) / 86400;
2388 if (diff > 0 && diff <= 256) {
2389 PUTC(0x12, pimg->fh);
2390 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2391 PUTC(diff - 1, pimg->fh);
2392 } else {
2393 PUTC(0x13, pimg->fh);
2394 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2395 put16(pimg->date2 / 86400 + 25567, pimg->fh);
2397 #else /* IMG_API_VERSION == 1 */
2398 int diff = pimg->days2 - pimg->days1;
2399 if (diff > 0 && diff <= 256) {
2400 PUTC(0x12, pimg->fh);
2401 put16(pimg->days1, pimg->fh);
2402 PUTC(diff - 1, pimg->fh);
2403 } else {
2404 PUTC(0x13, pimg->fh);
2405 put16(pimg->days1, pimg->fh);
2406 put16(pimg->days2, pimg->fh);
2408 #endif
2410 #if IMG_API_VERSION == 0
2411 pimg->olddate1 = pimg->date1;
2412 pimg->olddate2 = pimg->date2;
2413 #else /* IMG_API_VERSION == 1 */
2414 pimg->olddays1 = pimg->days1;
2415 pimg->olddays2 = pimg->days2;
2416 #endif
2419 static void
2420 img_write_item_date(img *pimg)
2422 int same, unset;
2423 /* Only write dates when they've changed. */
2424 #if IMG_API_VERSION == 0
2425 if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2426 return;
2428 same = (pimg->date1 == pimg->date2);
2429 unset = (pimg->date1 == 0);
2430 #else /* IMG_API_VERSION == 1 */
2431 if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2432 return;
2434 same = (pimg->days1 == pimg->days2);
2435 unset = (pimg->days1 == -1);
2436 #endif
2438 if (same) {
2439 if (img_output_version < 7) {
2440 PUTC(0x20, pimg->fh);
2441 #if IMG_API_VERSION == 0
2442 put32(pimg->date1, pimg->fh);
2443 #else /* IMG_API_VERSION == 1 */
2444 put32((pimg->days1 - 25567) * 86400, pimg->fh);
2445 #endif
2446 } else {
2447 if (unset) {
2448 PUTC(0x24, pimg->fh);
2449 } else {
2450 PUTC(0x20, pimg->fh);
2451 #if IMG_API_VERSION == 0
2452 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2453 #else /* IMG_API_VERSION == 1 */
2454 put16(pimg->days1, pimg->fh);
2455 #endif
2458 } else {
2459 if (img_output_version < 7) {
2460 PUTC(0x21, pimg->fh);
2461 #if IMG_API_VERSION == 0
2462 put32(pimg->date1, pimg->fh);
2463 put32(pimg->date2, pimg->fh);
2464 #else /* IMG_API_VERSION == 1 */
2465 put32((pimg->days1 - 25567) * 86400, pimg->fh);
2466 put32((pimg->days2 - 25567) * 86400, pimg->fh);
2467 #endif
2468 } else {
2469 #if IMG_API_VERSION == 0
2470 int diff = (pimg->date2 - pimg->date1) / 86400;
2471 if (diff > 0 && diff <= 256) {
2472 PUTC(0x21, pimg->fh);
2473 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2474 PUTC(diff - 1, pimg->fh);
2475 } else {
2476 PUTC(0x23, pimg->fh);
2477 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2478 put16(pimg->date2 / 86400 + 25567, pimg->fh);
2480 #else /* IMG_API_VERSION == 1 */
2481 int diff = pimg->days2 - pimg->days1;
2482 if (diff > 0 && diff <= 256) {
2483 PUTC(0x21, pimg->fh);
2484 put16(pimg->days1, pimg->fh);
2485 PUTC(diff - 1, pimg->fh);
2486 } else {
2487 PUTC(0x23, pimg->fh);
2488 put16(pimg->days1, pimg->fh);
2489 put16(pimg->days2, pimg->fh);
2491 #endif
2494 #if IMG_API_VERSION == 0
2495 pimg->olddate1 = pimg->date1;
2496 pimg->olddate2 = pimg->date2;
2497 #else /* IMG_API_VERSION == 1 */
2498 pimg->olddays1 = pimg->days1;
2499 pimg->olddays2 = pimg->days2;
2500 #endif
2503 static void
2504 img_write_item_new(img *pimg, int code, int flags, const char *s,
2505 double x, double y, double z);
2506 static void
2507 img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2508 double x, double y, double z);
2509 static void
2510 img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2511 double x, double y, double z);
2513 void
2514 img_write_item(img *pimg, int code, int flags, const char *s,
2515 double x, double y, double z)
2517 if (!pimg) return;
2518 if (pimg->version >= 8) {
2519 img_write_item_new(pimg, code, flags, s, x, y, z);
2520 } else if (pimg->version >= 3) {
2521 img_write_item_v3to7(pimg, code, flags, s, x, y, z);
2522 } else {
2523 img_write_item_ancient(pimg, code, flags, s, x, y, z);
2527 static void
2528 img_write_item_new(img *pimg, int code, int flags, const char *s,
2529 double x, double y, double z)
2531 switch (code) {
2532 case img_LABEL:
2533 write_v8label(pimg, 0x80 | flags, 0, -1, s);
2534 break;
2535 case img_XSECT: {
2536 INT32_T l, r, u, d, max_dim;
2537 img_write_item_date_new(pimg);
2538 l = (INT32_T)my_lround(pimg->l * 100.0);
2539 r = (INT32_T)my_lround(pimg->r * 100.0);
2540 u = (INT32_T)my_lround(pimg->u * 100.0);
2541 d = (INT32_T)my_lround(pimg->d * 100.0);
2542 if (l < 0) l = -1;
2543 if (r < 0) r = -1;
2544 if (u < 0) u = -1;
2545 if (d < 0) d = -1;
2546 max_dim = max(max(l, r), max(u, d));
2547 flags = (flags & img_XFLAG_END) ? 1 : 0;
2548 if (max_dim >= 32768) flags |= 2;
2549 write_v8label(pimg, 0x30 | flags, 0, -1, s);
2550 if (flags & 2) {
2551 /* Big passage! Need to use 4 bytes. */
2552 put32(l, pimg->fh);
2553 put32(r, pimg->fh);
2554 put32(u, pimg->fh);
2555 put32(d, pimg->fh);
2556 } else {
2557 put16(l, pimg->fh);
2558 put16(r, pimg->fh);
2559 put16(u, pimg->fh);
2560 put16(d, pimg->fh);
2562 return;
2564 case img_MOVE:
2565 PUTC(15, pimg->fh);
2566 break;
2567 case img_LINE:
2568 img_write_item_date_new(pimg);
2569 if (pimg->style != pimg->oldstyle) {
2570 switch (pimg->style) {
2571 case img_STYLE_NORMAL:
2572 case img_STYLE_DIVING:
2573 case img_STYLE_CARTESIAN:
2574 case img_STYLE_CYLPOLAR:
2575 case img_STYLE_NOSURVEY:
2576 PUTC(pimg->style, pimg->fh);
2577 break;
2579 pimg->oldstyle = pimg->style;
2581 write_v8label(pimg, 0x40 | flags, 0x20, 0x00, s ? s : "");
2582 break;
2583 default: /* ignore for now */
2584 return;
2586 write_coord(pimg->fh, x, y, z);
2589 static void
2590 img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2591 double x, double y, double z)
2593 switch (code) {
2594 case img_LABEL:
2595 write_v3label(pimg, 0x40 | flags, s);
2596 break;
2597 case img_XSECT: {
2598 INT32_T l, r, u, d, max_dim;
2599 /* Need at least version 5 for img_XSECT. */
2600 if (pimg->version < 5) return;
2601 img_write_item_date(pimg);
2602 l = (INT32_T)my_lround(pimg->l * 100.0);
2603 r = (INT32_T)my_lround(pimg->r * 100.0);
2604 u = (INT32_T)my_lround(pimg->u * 100.0);
2605 d = (INT32_T)my_lround(pimg->d * 100.0);
2606 if (l < 0) l = -1;
2607 if (r < 0) r = -1;
2608 if (u < 0) u = -1;
2609 if (d < 0) d = -1;
2610 max_dim = max(max(l, r), max(u, d));
2611 flags = (flags & img_XFLAG_END) ? 1 : 0;
2612 if (max_dim >= 32768) flags |= 2;
2613 write_v3label(pimg, 0x30 | flags, s);
2614 if (flags & 2) {
2615 /* Big passage! Need to use 4 bytes. */
2616 put32(l, pimg->fh);
2617 put32(r, pimg->fh);
2618 put32(u, pimg->fh);
2619 put32(d, pimg->fh);
2620 } else {
2621 put16(l, pimg->fh);
2622 put16(r, pimg->fh);
2623 put16(u, pimg->fh);
2624 put16(d, pimg->fh);
2626 return;
2628 case img_MOVE:
2629 PUTC(15, pimg->fh);
2630 break;
2631 case img_LINE:
2632 if (pimg->version >= 4) {
2633 img_write_item_date(pimg);
2635 write_v3label(pimg, 0x80 | flags, s ? s : "");
2636 break;
2637 default: /* ignore for now */
2638 return;
2640 write_coord(pimg->fh, x, y, z);
2643 static void
2644 img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2645 double x, double y, double z)
2647 size_t len;
2648 INT32_T opt = 0;
2649 SVX_ASSERT(pimg->version > 0);
2650 switch (code) {
2651 case img_LABEL:
2652 if (pimg->version == 1) {
2653 /* put a move before each label */
2654 img_write_item_ancient(pimg, img_MOVE, 0, NULL, x, y, z);
2655 put32(2, pimg->fh);
2656 fputsnl(s, pimg->fh);
2657 return;
2659 len = strlen(s);
2660 if (len > 255 || strchr(s, '\n')) {
2661 /* long label - not in early incarnations of v2 format, but few
2662 * 3d files will need these, so better not to force incompatibility
2663 * with a new version I think... */
2664 PUTC(7, pimg->fh);
2665 PUTC(flags, pimg->fh);
2666 put32(len, pimg->fh);
2667 fputs(s, pimg->fh);
2668 } else {
2669 PUTC(0x40 | (flags & 0x3f), pimg->fh);
2670 fputsnl(s, pimg->fh);
2672 opt = 0;
2673 break;
2674 case img_MOVE:
2675 opt = 4;
2676 break;
2677 case img_LINE:
2678 if (pimg->version > 1) {
2679 opt = 0x80 | (flags & 0x3f);
2680 break;
2682 opt = 5;
2683 break;
2684 default: /* ignore for now */
2685 return;
2687 if (pimg->version == 1) {
2688 put32(opt, pimg->fh);
2689 } else {
2690 if (opt) PUTC(opt, pimg->fh);
2692 write_coord(pimg->fh, x, y, z);
2695 /* Write error information for the current traverse
2696 * n_legs is the number of legs in the traverse
2697 * length is the traverse length (in m)
2698 * E is the ratio of the observed misclosure to the theoretical one
2699 * H is the ratio of the observed horizontal misclosure to the theoretical one
2700 * V is the ratio of the observed vertical misclosure to the theoretical one
2702 void
2703 img_write_errors(img *pimg, int n_legs, double length,
2704 double E, double H, double V)
2706 PUTC((pimg->version >= 8 ? 0x1f : 0x22), pimg->fh);
2707 put32(n_legs, pimg->fh);
2708 put32((INT32_T)my_lround(length * 100.0), pimg->fh);
2709 put32((INT32_T)my_lround(E * 100.0), pimg->fh);
2710 put32((INT32_T)my_lround(H * 100.0), pimg->fh);
2711 put32((INT32_T)my_lround(V * 100.0), pimg->fh);
2715 img_close(img *pimg)
2717 int result = 1;
2718 if (pimg) {
2719 if (pimg->fh) {
2720 if (pimg->fRead) {
2721 osfree(pimg->survey);
2722 osfree(pimg->title);
2723 osfree(pimg->cs);
2724 osfree(pimg->datestamp);
2725 } else {
2726 /* write end of data marker */
2727 switch (pimg->version) {
2728 case 1:
2729 put32((INT32_T)-1, pimg->fh);
2730 break;
2731 default:
2732 if (pimg->version <= 7 ?
2733 (pimg->label_len != 0) :
2734 (pimg->style != img_STYLE_NORMAL)) {
2735 PUTC(0, pimg->fh);
2737 /* FALL THROUGH */
2738 case 2:
2739 PUTC(0, pimg->fh);
2740 break;
2743 if (ferror(pimg->fh)) result = 0;
2744 if (fclose(pimg->fh)) result = 0;
2745 if (!result) img_errno = pimg->fRead ? IMG_READERROR : IMG_WRITEERROR;
2747 osfree(pimg->label_buf);
2748 osfree(pimg->filename_opened);
2749 osfree(pimg);
2751 return result;