Fix aven startup with no file on macos
[survex.git] / src / img.c
blobfa21df3ccf10783fd1d64b1782003d480e5ab49f
1 /* img.c
2 * Routines for reading and writing Survex ".3d" image files
3 * Copyright (C) 1993-2021 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 # define UINT32_T uint32_t
39 # include "debug.h"
40 # include "filelist.h"
41 # include "filename.h"
42 # include "message.h"
43 # include "useful.h"
44 # define TIMEFMT msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107)
45 #else
46 # ifdef HAVE_STDINT_H
47 # include <stdint.h>
48 # define INT32_T int32_t
49 # define UINT32_T uint32_t
50 # else
51 # include <limits.h>
52 # if INT_MAX >= 2147483647
53 # define INT32_T int
54 # define UINT32_T unsigned
55 # else
56 # define INT32_T long
57 # define UINT32_T unsigned long
58 # endif
59 # endif
60 # define TIMEFMT "%a,%Y.%m.%d %H:%M:%S %Z"
61 # define EXT_SVX_3D "3d"
62 # define EXT_SVX_POS "pos"
63 # define FNM_SEP_EXT '.'
64 # define METRES_PER_FOOT 0.3048 /* exact value */
65 # define xosmalloc(L) malloc((L))
66 # define xosrealloc(L,S) realloc((L),(S))
67 # define osfree(P) free((P))
68 # define osnew(T) (T*)malloc(sizeof(T))
70 /* in IMG_HOSTED mode, this tests if a filename refers to a directory */
71 # define fDirectory(X) 0
72 /* open file FNM with mode MODE, maybe using path PTH and/or extension EXT */
73 /* path isn't used in img.c, but EXT is */
74 # define fopenWithPthAndExt(PTH,FNM,EXT,MODE,X) \
75 ((*(X) = NULL), fopen(FNM,MODE))
76 # ifndef PUTC
77 # define PUTC(C, FH) putc(C, FH)
78 # endif
79 # ifndef GETC
80 # define GETC(FH) getc(FH)
81 # endif
82 # define fputsnl(S, FH) (fputs((S), (FH)) == EOF ? EOF : putc('\n', (FH)))
83 # define SVX_ASSERT(X)
85 #ifdef __cplusplus
86 # include <algorithm>
87 using std::max;
88 using std::min;
89 #else
90 /* Return max/min of two numbers. */
91 /* May be defined already (e.g. by Borland C in stdlib.h) */
92 /* NB Bad news if X or Y has side-effects... */
93 # ifndef max
94 # define max(X, Y) ((X) > (Y) ? (X) : (Y))
95 # endif
96 # ifndef min
97 # define min(X, Y) ((X) < (Y) ? (X) : (Y))
98 # endif
99 #endif
101 static INT32_T
102 get32(FILE *fh)
104 UINT32_T w = GETC(fh);
105 w |= (UINT32_T)GETC(fh) << 8l;
106 w |= (UINT32_T)GETC(fh) << 16l;
107 w |= (UINT32_T)GETC(fh) << 24l;
108 return (INT32_T)w;
111 static void
112 put32(UINT32_T w, FILE *fh)
114 PUTC((char)(w), fh);
115 PUTC((char)(w >> 8l), fh);
116 PUTC((char)(w >> 16l), fh);
117 PUTC((char)(w >> 24l), fh);
120 static short
121 get16(FILE *fh)
123 UINT32_T w = GETC(fh);
124 w |= (UINT32_T)GETC(fh) << 8l;
125 return (short)w;
128 static void
129 put16(short word, FILE *fh)
131 unsigned short w = (unsigned short)word;
132 PUTC((char)(w), fh);
133 PUTC((char)(w >> 8l), fh);
136 static char *
137 baseleaf_from_fnm(const char *fnm)
139 const char *p;
140 const char *q;
141 char * res;
142 size_t len;
144 p = fnm;
145 q = strrchr(p, '/');
146 if (q) p = q + 1;
147 q = strrchr(p, '\\');
148 if (q) p = q + 1;
150 q = strrchr(p, FNM_SEP_EXT);
151 if (q) len = (const char *)q - p; else len = strlen(p);
153 res = (char *)xosmalloc(len + 1);
154 if (!res) return NULL;
155 memcpy(res, p, len);
156 res[len] = '\0';
157 return res;
159 #endif
161 static char * my_strdup(const char *str);
163 static time_t
164 mktime_with_tz(struct tm * tm, const char * tz)
166 time_t r;
167 char * old_tz = getenv("TZ");
168 #ifdef _MSC_VER
169 if (old_tz) {
170 old_tz = my_strdup(old_tz);
171 if (!old_tz)
172 return (time_t)-1;
174 if (_putenv_s("TZ", tz) != 0) {
175 osfree(old_tz);
176 return (time_t)-1;
178 #elif defined HAVE_SETENV
179 if (old_tz) {
180 old_tz = my_strdup(old_tz);
181 if (!old_tz)
182 return (time_t)-1;
184 if (setenv("TZ", tz, 1) < 0) {
185 osfree(old_tz);
186 return (time_t)-1;
188 #else
189 char * p;
190 if (old_tz) {
191 size_t len = strlen(old_tz) + 1;
192 p = (char *)xosmalloc(len + 3);
193 if (!p)
194 return (time_t)-1;
195 memcpy(p, "TZ=", 3);
196 memcpy(p + 3, tz, len);
197 old_tz = p;
199 p = (char *)xosmalloc(strlen(tz) + 4);
200 if (!p) {
201 osfree(old_tz);
202 return (time_t)-1;
204 memcpy(p, "TZ=", 3);
205 strcpy(p + 3, tz);
206 if (putenv(p) != 0) {
207 osfree(p);
208 osfree(old_tz);
209 return (time_t)-1;
211 #define CLEANUP() osfree(p)
212 #endif
213 tzset();
214 r = mktime(tm);
215 if (old_tz) {
216 #ifdef _MSC_VER
217 _putenv_s("TZ", old_tz);
218 #elif !defined HAVE_SETENV
219 putenv(old_tz);
220 #else
221 setenv("TZ", old_tz, 1);
222 #endif
223 osfree(old_tz);
224 } else {
225 #ifdef _MSC_VER
226 _putenv_s("TZ", "");
227 #elif !defined HAVE_UNSETENV
228 putenv((char*)"TZ");
229 #else
230 unsetenv("TZ");
231 #endif
233 #ifdef CLEANUP
234 CLEANUP();
235 #undef CLEANUP
236 #endif
237 return r;
240 static unsigned short
241 getu16(FILE *fh)
243 return (unsigned short)get16(fh);
246 #include <math.h>
248 #if !defined HAVE_LROUND && !defined HAVE_DECL_LROUND
249 /* The autoconf tests are not in use, but C99 and C++11 both added lround(),
250 * so set HAVE_LROUND and HAVE_DECL_LROUND conservatively based on the language
251 * standard version the compiler claims to support. */
252 # if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) || \
253 (defined __cplusplus && __cplusplus >= 201103L)
254 # define HAVE_LROUND 1
255 # define HAVE_DECL_LROUND 1
256 # endif
257 #endif
259 #ifdef HAVE_LROUND
260 # if defined HAVE_DECL_LROUND && !HAVE_DECL_LROUND
261 /* On older systems, the prototype may be missing. */
262 extern long lround(double);
263 # endif
264 # define my_lround lround
265 #else
266 static long
267 my_lround(double x) {
268 return (x >= 0.0) ? (long)(x + 0.5) : -(long)(0.5 - x);
270 #endif
272 /* portable case insensitive string compare */
273 #if defined(strcasecmp) || defined(HAVE_STRCASECMP)
274 # define my_strcasecmp strcasecmp
275 #else
276 static int my_strcasecmp(const char *s1, const char *s2) {
277 unsigned char c1, c2;
278 do {
279 c1 = *s1++;
280 c2 = *s2++;
281 } while (c1 && toupper(c1) == toupper(c2));
282 /* now calculate real difference */
283 return c1 - c2;
285 #endif
287 unsigned int img_output_version = IMG_VERSION_MAX;
289 static img_errcode img_errno = IMG_NONE;
291 #define FILEID "Survex 3D Image File"
293 #define EXT_PLT "plt"
294 #define EXT_PLF "plf"
296 /* Attempt to string paste to ensure we are passed a literal string */
297 #define LITLEN(S) (sizeof(S"") - 1)
299 /* Fake "version numbers" for non-3d formats we can read. */
300 #define VERSION_CMAP_SHOT -4
301 #define VERSION_CMAP_STATION -3
302 #define VERSION_COMPASS_PLT -2
303 #define VERSION_SURVEX_POS -1
305 static char *
306 my_strdup(const char *str)
308 char *p;
309 size_t len = strlen(str) + 1;
310 p = (char *)xosmalloc(len);
311 if (p) memcpy(p, str, len);
312 return p;
315 #define getline_alloc(FH) getline_alloc_len(FH, NULL)
317 static char *
318 getline_alloc_len(FILE *fh, size_t * p_len)
320 int ch;
321 size_t i = 0;
322 size_t len = 16;
323 char *buf = (char *)xosmalloc(len);
324 if (!buf) return NULL;
326 ch = GETC(fh);
327 while (ch != '\n' && ch != '\r' && ch != EOF) {
328 buf[i++] = ch;
329 if (i == len - 1) {
330 char *p;
331 len += len;
332 p = (char *)xosrealloc(buf, len);
333 if (!p) {
334 osfree(buf);
335 return NULL;
337 buf = p;
339 ch = GETC(fh);
341 if (ch == '\n' || ch == '\r') {
342 int otherone = ch ^ ('\n' ^ '\r');
343 ch = GETC(fh);
344 /* if it's not the other eol character, put it back */
345 if (ch != otherone) ungetc(ch, fh);
347 buf[i] = '\0';
348 if (p_len) *p_len = i;
349 return buf;
352 img_errcode
353 img_error(void)
355 return img_errno;
358 static int
359 check_label_space(img *pimg, size_t len)
361 if (len > pimg->buf_len) {
362 char *b = (char *)xosrealloc(pimg->label_buf, len);
363 if (!b) return 0;
364 pimg->label = (pimg->label - pimg->label_buf) + b;
365 pimg->label_buf = b;
366 pimg->buf_len = len;
368 return 1;
371 /* Check if a station name should be included. */
372 static int
373 stn_included(img *pimg)
375 if (!pimg->survey_len) return 1;
376 size_t l = pimg->survey_len;
377 const char *s = pimg->label_buf;
378 if (strncmp(pimg->survey, s, l + 1) != 0) {
379 return 0;
381 pimg->label += l + 1;
382 return 1;
385 /* Check if a survey name should be included. */
386 static int
387 survey_included(img *pimg)
389 if (!pimg->survey_len) return 1;
390 size_t l = pimg->survey_len;
391 const char *s = pimg->label_buf;
392 if (strncmp(pimg->survey, s, l) != 0 ||
393 !(s[l] == '.' || s[l] == '\0')) {
394 return 0;
396 pimg->label += l;
397 /* skip the dot if there */
398 if (*pimg->label) pimg->label++;
399 return 1;
402 /* Check if a survey name in a buffer should be included.
404 * For "foreign" formats which just have one level of surveys.
406 static int
407 buf_included(img *pimg, const char *buf, size_t len)
409 return pimg->survey_len == len && strncmp(buf, pimg->survey, len) == 0;
412 #define has_ext(F,L,E) ((L) > LITLEN(E) + 1 &&\
413 (F)[(L) - LITLEN(E) - 1] == FNM_SEP_EXT &&\
414 my_strcasecmp((F) + (L) - LITLEN(E), E) == 0)
416 img *
417 img_open_survey(const char *fnm, const char *survey)
419 img *pimg;
420 FILE *fh;
421 char* filename_opened = NULL;
423 if (fDirectory(fnm)) {
424 img_errno = IMG_DIRECTORY;
425 return NULL;
428 fh = fopenWithPthAndExt("", fnm, EXT_SVX_3D, "rb", &filename_opened);
429 pimg = img_read_stream_survey(fh, fclose,
430 filename_opened ? filename_opened : fnm,
431 survey);
432 if (pimg) {
433 pimg->filename_opened = filename_opened;
434 } else {
435 osfree(filename_opened);
437 return pimg;
440 img *
441 img_read_stream_survey(FILE *stream, int (*close_func)(FILE*),
442 const char *fnm,
443 const char *survey)
445 img *pimg;
446 size_t len;
447 char buf[LITLEN(FILEID) + 9];
448 int ch;
450 if (stream == NULL) {
451 img_errno = IMG_FILENOTFOUND;
452 return NULL;
455 pimg = osnew(img);
456 if (pimg == NULL) {
457 img_errno = IMG_OUTOFMEMORY;
458 if (close_func) close_func(stream);
459 return NULL;
462 pimg->fh = stream;
463 pimg->close_func = close_func;
465 pimg->buf_len = 257;
466 pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
467 if (!pimg->label_buf) {
468 if (pimg->close_func) pimg->close_func(pimg->fh);
469 osfree(pimg);
470 img_errno = IMG_OUTOFMEMORY;
471 return NULL;
474 pimg->fRead = 1; /* reading from this file */
475 img_errno = IMG_NONE;
477 pimg->flags = 0;
478 pimg->filename_opened = NULL;
480 /* for version >= 3 we use label_buf to store the prefix for reuse */
481 /* for VERSION_COMPASS_PLT, 0 value indicates we haven't
482 * entered a survey yet */
483 /* for VERSION_CMAP_SHOT, we store the last station here
484 * to detect whether we MOVE or LINE */
485 pimg->label_len = 0;
486 pimg->label_buf[0] = '\0';
488 pimg->survey = NULL;
489 pimg->survey_len = 0;
490 pimg->separator = '.';
491 #if IMG_API_VERSION == 0
492 pimg->date1 = pimg->date2 = 0;
493 #else /* IMG_API_VERSION == 1 */
494 pimg->days1 = pimg->days2 = -1;
495 #endif
496 pimg->is_extended_elevation = 0;
498 pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
500 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
502 pimg->title = pimg->datestamp = pimg->cs = NULL;
503 pimg->datestamp_numeric = (time_t)-1;
505 if (survey) {
506 len = strlen(survey);
507 if (len) {
508 if (survey[len - 1] == '.') len--;
509 if (len) {
510 char *p;
511 pimg->survey = (char *)xosmalloc(len + 2);
512 if (!pimg->survey) {
513 img_errno = IMG_OUTOFMEMORY;
514 goto error;
516 memcpy(pimg->survey, survey, len);
517 /* Set title to leaf survey name */
518 pimg->survey[len] = '\0';
519 p = strrchr(pimg->survey, '.');
520 if (p) p++; else p = pimg->survey;
521 pimg->title = my_strdup(p);
522 if (!pimg->title) {
523 img_errno = IMG_OUTOFMEMORY;
524 goto error;
526 pimg->survey[len] = '.';
527 pimg->survey[len + 1] = '\0';
530 pimg->survey_len = len;
533 /* [VERSION_COMPASS_PLT, VERSION_CMAP_STATION, VERSION_CMAP_SHOT] pending
534 * IMG_LINE or IMG_MOVE - both have 4 added.
535 * [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
536 * [version 0] not in the middle of a 'LINE' command
537 * [version >= 3] not in the middle of turning a LINE into a MOVE
539 pimg->pending = 0;
541 len = strlen(fnm);
542 if (has_ext(fnm, len, EXT_SVX_POS)) {
543 pos_file:
544 pimg->version = VERSION_SURVEX_POS;
545 if (!pimg->survey) pimg->title = baseleaf_from_fnm(fnm);
546 pimg->datestamp = my_strdup(TIMENA);
547 if (!pimg->datestamp) {
548 img_errno = IMG_OUTOFMEMORY;
549 goto error;
551 pimg->start = 0;
552 return pimg;
555 if (has_ext(fnm, len, EXT_PLT) || has_ext(fnm, len, EXT_PLF)) {
556 long fpos;
557 plt_file:
558 pimg->version = VERSION_COMPASS_PLT;
559 /* Spaces aren't legal in Compass station names, but dots are, so
560 * use space as the level separator */
561 pimg->separator = ' ';
562 pimg->start = 0;
563 if (!pimg->survey) pimg->title = baseleaf_from_fnm(fnm);
564 pimg->datestamp = my_strdup(TIMENA);
565 if (!pimg->datestamp) {
566 img_errno = IMG_OUTOFMEMORY;
567 goto error;
569 while (1) {
570 ch = GETC(pimg->fh);
571 switch (ch) {
572 case '\x1a':
573 fseek(pimg->fh, -1, SEEK_CUR);
574 /* FALL THRU */
575 case EOF:
576 pimg->start = ftell(pimg->fh);
577 return pimg;
578 case 'N': {
579 char *line, *q;
580 fpos = ftell(pimg->fh) - 1;
581 if (!pimg->survey) {
582 /* FIXME : if there's only one survey in the file, it'd be nice
583 * to use its description as the title here...
585 ungetc('N', pimg->fh);
586 pimg->start = fpos;
587 return pimg;
589 line = getline_alloc(pimg->fh);
590 if (!line) {
591 img_errno = IMG_OUTOFMEMORY;
592 goto error;
594 len = 0;
595 while (line[len] > 32) ++len;
596 if (!buf_included(pimg, line, len)) {
597 osfree(line);
598 continue;
600 q = strchr(line + len, 'C');
601 if (q && q[1]) {
602 osfree(pimg->title);
603 pimg->title = my_strdup(q + 1);
604 } else if (!pimg->title) {
605 pimg->title = my_strdup(pimg->label);
607 osfree(line);
608 if (!pimg->title) {
609 img_errno = IMG_OUTOFMEMORY;
610 goto error;
612 if (!pimg->start) pimg->start = fpos;
613 fseek(pimg->fh, pimg->start, SEEK_SET);
614 return pimg;
616 case 'M': case 'D':
617 pimg->start = ftell(pimg->fh) - 1;
618 break;
620 while (ch != '\n' && ch != '\r') {
621 ch = GETC(pimg->fh);
626 /* Although these are often referred to as "CMAP .XYZ files", it seems
627 * that actually, the extension .XYZ isn't used, rather .SHT (shot
628 * variant, produced by CMAP v16 and later), .UNA (unadjusted) and
629 * .ADJ (adjusted) extensions are. Since img has long checked for
630 * .XYZ, we continue to do so in case anyone is relying on it.
632 if (has_ext(fnm, len, "sht") ||
633 has_ext(fnm, len, "adj") ||
634 has_ext(fnm, len, "una") ||
635 has_ext(fnm, len, "xyz")) {
636 char *line;
637 xyz_file:
638 /* Spaces aren't legal in CMAP station names, but dots are, so
639 * use space as the level separator. */
640 pimg->separator = ' ';
641 line = getline_alloc(pimg->fh);
642 if (!line) {
643 img_errno = IMG_OUTOFMEMORY;
644 goto error;
646 /* There doesn't seem to be a spec for what happens after 1999 with cmap
647 * files, so this code allows for:
648 * * 21xx -> xx (up to 2150)
649 * * 21xx -> 1xx (up to 2199)
650 * * full year being specified instead of 2 digits
652 len = strlen(line);
653 if (len > 59) {
654 /* Don't just truncate at column 59, allow for a > 2 digit year. */
655 char * p = strstr(line + len, "Page");
656 if (p) {
657 while (p > line && p[-1] == ' ')
658 --p;
659 *p = '\0';
660 len = p - line;
661 } else {
662 line[59] = '\0';
665 if (len > 45) {
666 /* YY/MM/DD HH:MM */
667 struct tm tm;
668 unsigned long v;
669 char * p;
670 pimg->datestamp = my_strdup(line + 45);
671 p = pimg->datestamp;
672 v = strtoul(p, &p, 10);
673 if (v <= 50) {
674 /* In the absence of a spec for cmap files, assume <= 50 means 21st
675 * century. */
676 v += 2000;
677 } else if (v < 200) {
678 /* Map 100-199 to 21st century. */
679 v += 1900;
681 if (v == ULONG_MAX || *p++ != '/')
682 goto bad_cmap_date;
683 tm.tm_year = v - 1900;
684 v = strtoul(p, &p, 10);
685 if (v < 1 || v > 12 || *p++ != '/')
686 goto bad_cmap_date;
687 tm.tm_mon = v - 1;
688 v = strtoul(p, &p, 10);
689 if (v < 1 || v > 31 || *p++ != ' ')
690 goto bad_cmap_date;
691 tm.tm_mday = v;
692 v = strtoul(p, &p, 10);
693 if (v >= 24 || *p++ != ':')
694 goto bad_cmap_date;
695 tm.tm_hour = v;
696 v = strtoul(p, &p, 10);
697 if (v >= 60)
698 goto bad_cmap_date;
699 tm.tm_min = v;
700 if (*p == ':') {
701 v = strtoul(p + 1, &p, 10);
702 if (v > 60)
703 goto bad_cmap_date;
704 tm.tm_sec = v;
705 } else {
706 tm.tm_sec = 0;
708 tm.tm_isdst = 0;
709 /* We have no indication of what timezone this timestamp is in. It's
710 * probably local time for whoever processed the data, so just assume
711 * UTC, which is at least fairly central in the possibilities.
713 pimg->datestamp_numeric = mktime_with_tz(&tm, "");
714 } else {
715 pimg->datestamp = my_strdup(TIMENA);
717 bad_cmap_date:
718 if (strncmp(line, " Cave Survey Data Processed by CMAP ",
719 LITLEN(" Cave Survey Data Processed by CMAP ")) == 0) {
720 len = 0;
721 } else {
722 if (len > 45) {
723 line[45] = '\0';
724 len = 45;
726 while (len > 2 && line[len - 1] == ' ') --len;
727 if (len > 2) {
728 line[len] = '\0';
729 pimg->title = my_strdup(line + 2);
732 if (len <= 2) pimg->title = baseleaf_from_fnm(fnm);
733 osfree(line);
734 if (!pimg->datestamp || !pimg->title) {
735 img_errno = IMG_OUTOFMEMORY;
736 goto error;
738 line = getline_alloc(pimg->fh);
739 if (!line) {
740 img_errno = IMG_OUTOFMEMORY;
741 goto error;
743 if (line[0] != ' ' || (line[1] != 'S' && line[1] != 'O')) {
744 img_errno = IMG_BADFORMAT;
745 goto error;
747 if (line[1] == 'S') {
748 pimg->version = VERSION_CMAP_STATION;
749 } else {
750 pimg->version = VERSION_CMAP_SHOT;
752 osfree(line);
753 line = getline_alloc(pimg->fh);
754 if (!line) {
755 img_errno = IMG_OUTOFMEMORY;
756 goto error;
758 if (line[0] != ' ' || line[1] != '-') {
759 img_errno = IMG_BADFORMAT;
760 goto error;
762 osfree(line);
763 pimg->start = ftell(pimg->fh);
764 return pimg;
767 if (fread(buf, LITLEN(FILEID) + 1, 1, pimg->fh) != 1 ||
768 memcmp(buf, FILEID"\n", LITLEN(FILEID) + 1) != 0) {
769 if (fread(buf + LITLEN(FILEID) + 1, 8, 1, pimg->fh) == 1 &&
770 memcmp(buf, FILEID"\r\nv0.01\r\n", LITLEN(FILEID) + 9) == 0) {
771 /* v0 3d file with DOS EOLs */
772 pimg->version = 0;
773 goto v03d;
775 rewind(pimg->fh);
776 if (buf[1] == ' ') {
777 if (buf[0] == ' ') {
778 /* Looks like a CMAP .xyz file ... */
779 goto xyz_file;
780 } else if (strchr("ZSNF", buf[0])) {
781 /* Looks like a Compass .plt file ... */
782 /* Almost certainly it'll start "Z " */
783 goto plt_file;
786 if (buf[0] == '(') {
787 /* Looks like a Survex .pos file ... */
788 goto pos_file;
790 img_errno = IMG_BADFORMAT;
791 goto error;
794 /* check file format version */
795 ch = GETC(pimg->fh);
796 pimg->version = 0;
797 if (tolower(ch) == 'b') {
798 /* binary file iff B/b prefix */
799 pimg->version = 1;
800 ch = GETC(pimg->fh);
802 if (ch != 'v') {
803 img_errno = IMG_BADFORMAT;
804 goto error;
806 ch = GETC(pimg->fh);
807 if (ch == '0') {
808 if (fread(buf, 4, 1, pimg->fh) != 1 || memcmp(buf, ".01\n", 4) != 0) {
809 img_errno = IMG_BADFORMAT;
810 goto error;
812 /* nothing special to do */
813 } else if (pimg->version == 0) {
814 if (ch < '2' || ch > '0' + IMG_VERSION_MAX || GETC(pimg->fh) != '\n') {
815 img_errno = IMG_TOONEW;
816 goto error;
818 pimg->version = ch - '0';
819 } else {
820 img_errno = IMG_BADFORMAT;
821 goto error;
824 v03d:
826 size_t title_len;
827 char * title = getline_alloc_len(pimg->fh, &title_len);
828 if (pimg->version == 8 && title) {
829 /* We sneak in an extra field after a zero byte here, containing the
830 * specified coordinate system (if any). Older readers will just
831 * not see it (which is fine), and this trick avoids us having to
832 * bump the 3d format version.
834 size_t real_len = strlen(title);
835 if (real_len != title_len) {
836 char * cs = title + real_len + 1;
837 if (memcmp(cs, "+init=", 6) == 0) {
838 /* PROJ 5 and later don't handle +init=esri:<number> but
839 * that's what cavern used to put in .3d files for
840 * coordinate systems specified using ESRI codes. We parse
841 * and convert the strings cavern used to generate and
842 * convert to the form ESRI:<number> which is still
843 * understood.
845 * PROJ 6 and later don't recognise +init=epsg:<number>
846 * by default and don't apply datum shift terms in some
847 * cases, so we also convert these to the form
848 * EPSG:<number>.
850 char * p = cs + 6;
851 if (p[4] == ':' && isdigit((unsigned char)p[5]) &&
852 ((memcmp(p, "epsg", 4) == 0 || memcmp(p, "esri", 4) == 0))) {
853 p = p + 6;
854 while (isdigit((unsigned char)*p)) {
855 ++p;
857 /* Allow +no_defs to be omitted as it seems to not
858 * actually do anything with recent PROJ - cavern always
859 * included it, but other software generating 3d files
860 * may not.
862 if (*p == '\0' || strcmp(p, " +no_defs") == 0) {
863 int i;
864 cs = cs + 6;
865 for (i = 0; i < 4; ++i) {
866 cs[i] = toupper(cs[i]);
868 *p = '\0';
871 } else if (memcmp(cs, "+proj=", 6) == 0) {
872 /* Convert S_MERC and UTM proj strings which cavern used
873 * to generate to their corresponding EPSG:<number> codes.
875 char * p = cs + 6;
876 if (memcmp(p, "utm +ellps=WGS84 +datum=WGS84 +units=m +zone=", 45) == 0) {
877 int n = 0;
878 p += 45;
879 while (isdigit((unsigned char)*p)) {
880 n = n * 10 + (*p - '0');
881 ++p;
883 if (memcmp(p, " +south", 7) == 0) {
884 p += 7;
885 n += 32700;
886 } else {
887 n += 32600;
889 /* Allow +no_defs to be omitted as it seems to not
890 * actually do anything with recent PROJ - cavern always
891 * included it, but other software generating 3d files
892 * may not have.
894 if (*p == '\0' || strcmp(p, " +no_defs") == 0) {
895 sprintf(cs, "EPSG:%d", n);
897 } else if (memcmp(p, "merc +lat_ts=0 +lon_0=0 +k=1 +x_0=0 +y_0=0 +a=6378137 +b=6378137 +units=m +nadgrids=@null", 89) == 0) {
898 p = p + 89;
899 /* Allow +no_defs to be omitted as it seems to not
900 * actually do anything with recent PROJ - cavern always
901 * included it, but other software generating 3d files
902 * may not have.
904 if (*p == '\0' || strcmp(p, " +no_defs") == 0) {
905 strcpy(cs, "EPSG:3857");
909 pimg->cs = my_strdup(cs);
912 if (!pimg->title) {
913 pimg->title = title;
914 } else {
915 osfree(title);
918 pimg->datestamp = getline_alloc(pimg->fh);
919 if (!pimg->title || !pimg->datestamp) {
920 img_errno = IMG_OUTOFMEMORY;
921 error:
922 osfree(pimg->title);
923 osfree(pimg->cs);
924 osfree(pimg->datestamp);
925 osfree(pimg->filename_opened);
926 if (pimg->close_func) pimg->close_func(pimg->fh);
927 osfree(pimg);
928 return NULL;
931 if (pimg->version >= 8) {
932 int flags = GETC(pimg->fh);
933 if (flags & img_FFLAG_EXTENDED) pimg->is_extended_elevation = 1;
934 } else {
935 len = strlen(pimg->title);
936 if (len > 11 && strcmp(pimg->title + len - 11, " (extended)") == 0) {
937 pimg->title[len - 11] = '\0';
938 pimg->is_extended_elevation = 1;
942 if (pimg->datestamp[0] == '@') {
943 unsigned long v;
944 char * p;
945 errno = 0;
946 v = strtoul(pimg->datestamp + 1, &p, 10);
947 if (errno == 0 && *p == '\0')
948 pimg->datestamp_numeric = v;
949 /* FIXME: We're assuming here that the C time_t epoch is 1970, which is
950 * true for Unix-like systems, macOS and Windows, but isn't guaranteed
951 * by ISO C.
953 } else {
954 /* %a,%Y.%m.%d %H:%M:%S %Z */
955 struct tm tm;
956 unsigned long v;
957 char * p = pimg->datestamp;
958 while (isalpha((unsigned char)*p)) ++p;
959 if (*p == ',') ++p;
960 while (isspace((unsigned char)*p)) ++p;
961 v = strtoul(p, &p, 10);
962 if (v == ULONG_MAX || *p++ != '.')
963 goto bad_3d_date;
964 tm.tm_year = v - 1900;
965 v = strtoul(p, &p, 10);
966 if (v < 1 || v > 12 || *p++ != '.')
967 goto bad_3d_date;
968 tm.tm_mon = v - 1;
969 v = strtoul(p, &p, 10);
970 if (v < 1 || v > 31 || *p++ != ' ')
971 goto bad_3d_date;
972 tm.tm_mday = v;
973 v = strtoul(p, &p, 10);
974 if (v >= 24 || *p++ != ':')
975 goto bad_3d_date;
976 tm.tm_hour = v;
977 v = strtoul(p, &p, 10);
978 if (v >= 60 || *p++ != ':')
979 goto bad_3d_date;
980 tm.tm_min = v;
981 v = strtoul(p, &p, 10);
982 if (v > 60)
983 goto bad_3d_date;
984 tm.tm_sec = v;
985 tm.tm_isdst = 0;
986 while (isspace((unsigned char)*p)) ++p;
987 /* p now points to the timezone string.
989 * However, it's likely to be a string like "BST", and such strings can
990 * be ambiguous (BST could be UTC+1 or UTC+6), so it is impossible to
991 * reliably convert in all cases. Just pass what we have to tzset() - if
992 * it doesn't handle it, UTC will be used.
994 pimg->datestamp_numeric = mktime_with_tz(&tm, p);
996 bad_3d_date:
998 pimg->start = ftell(pimg->fh);
1000 return pimg;
1004 img_rewind(img *pimg)
1006 if (!pimg->fRead) {
1007 img_errno = IMG_WRITEERROR;
1008 return 0;
1010 if (fseek(pimg->fh, pimg->start, SEEK_SET) != 0) {
1011 img_errno = IMG_READERROR;
1012 return 0;
1014 clearerr(pimg->fh);
1015 /* [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
1016 * [version 0] not in the middle of a 'LINE' command
1017 * [version >= 3] not in the middle of turning a LINE into a MOVE */
1018 pimg->pending = 0;
1020 img_errno = IMG_NONE;
1022 /* for version >= 3 we use label_buf to store the prefix for reuse */
1023 /* for VERSION_COMPASS_PLT, 0 value indicates we haven't entered a survey
1024 * yet */
1025 /* for VERSION_CMAP_SHOT, we store the last station here to detect whether
1026 * we MOVE or LINE */
1027 pimg->label_len = 0;
1028 pimg->style = img_STYLE_UNKNOWN;
1029 return 1;
1032 img *
1033 img_open_write_cs(const char *fnm, const char *title, const char *cs, int flags)
1035 if (fDirectory(fnm)) {
1036 img_errno = IMG_DIRECTORY;
1037 return NULL;
1040 return img_write_stream(fopen(fnm, "wb"), fclose, title, cs, flags);
1043 img *
1044 img_write_stream(FILE *stream, int (*close_func)(FILE*),
1045 const char *title, const char *cs, int flags)
1047 time_t tm;
1048 img *pimg;
1050 if (stream == NULL) {
1051 img_errno = IMG_FILENOTFOUND;
1052 return NULL;
1055 pimg = osnew(img);
1056 if (pimg == NULL) {
1057 img_errno = IMG_OUTOFMEMORY;
1058 if (close_func) close_func(stream);
1059 return NULL;
1062 pimg->fh = stream;
1063 pimg->close_func = close_func;
1064 pimg->buf_len = 257;
1065 pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
1066 if (!pimg->label_buf) {
1067 if (pimg->close_func) pimg->close_func(pimg->fh);
1068 osfree(pimg);
1069 img_errno = IMG_OUTOFMEMORY;
1070 return NULL;
1073 pimg->filename_opened = NULL;
1075 /* Output image file header */
1076 fputs("Survex 3D Image File\n", pimg->fh); /* file identifier string */
1077 if (img_output_version < 2) {
1078 pimg->version = 1;
1079 fputs("Bv0.01\n", pimg->fh); /* binary file format version number */
1080 } else {
1081 pimg->version = (img_output_version > IMG_VERSION_MAX) ? IMG_VERSION_MAX : img_output_version;
1082 fprintf(pimg->fh, "v%d\n", pimg->version); /* file format version no. */
1085 fputs(title, pimg->fh);
1086 if (pimg->version < 8 && (flags & img_FFLAG_EXTENDED)) {
1087 /* Older format versions append " (extended)" to the title to mark
1088 * extended elevations. */
1089 size_t len = strlen(title);
1090 if (len < 11 || strcmp(title + len - 11, " (extended)") != 0)
1091 fputs(" (extended)", pimg->fh);
1093 if (pimg->version == 8 && cs && *cs) {
1094 /* We sneak in an extra field after a zero byte here, containing the
1095 * specified coordinate system (if any). Older readers will just not
1096 * see it (which is fine), and this trick avoids us having to bump the
1097 * 3d format version.
1099 PUTC('\0', pimg->fh);
1100 fputs(cs, pimg->fh);
1102 PUTC('\n', pimg->fh);
1104 tm = time(NULL);
1105 if (tm == (time_t)-1) {
1106 fputsnl(TIMENA, pimg->fh);
1107 } else if (pimg->version <= 7) {
1108 char date[256];
1109 /* output current date and time in format specified */
1110 strftime(date, 256, TIMEFMT, localtime(&tm));
1111 fputsnl(date, pimg->fh);
1112 } else {
1113 fprintf(pimg->fh, "@%ld\n", (long)tm);
1116 if (pimg->version >= 8) {
1117 /* Clear bit one in case anyone has been passing true for fBinary. */
1118 flags &=~ 1;
1119 PUTC(flags, pimg->fh);
1122 #if 0
1123 if (img_output_version >= 5) {
1124 static const unsigned char codelengths[32] = {
1125 4, 8, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1126 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1128 fwrite(codelengths, 32, 1, pimg->fh);
1130 #endif
1131 pimg->fRead = 0; /* writing to this file */
1132 img_errno = IMG_NONE;
1134 /* for version >= 3 we use label_buf to store the prefix for reuse */
1135 pimg->label_buf[0] = '\0';
1136 pimg->label_len = 0;
1138 #if IMG_API_VERSION == 0
1139 pimg->date1 = pimg->date2 = 0;
1140 pimg->olddate1 = pimg->olddate2 = 0;
1141 #else /* IMG_API_VERSION == 1 */
1142 pimg->days1 = pimg->days2 = -1;
1143 pimg->olddays1 = pimg->olddays2 = -1;
1144 #endif
1145 pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
1147 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1149 pimg->n_legs = 0;
1150 pimg->length = 0.0;
1151 pimg->E = pimg->H = pimg->V = 0.0;
1153 /* Don't check for write errors now - let img_close() report them... */
1154 return pimg;
1157 static void
1158 read_xyz_station_coords(img_point *pt, const char *line)
1160 char num[12];
1161 memcpy(num, line + 6, 9);
1162 num[9] = '\0';
1163 pt->x = atof(num) / METRES_PER_FOOT;
1164 memcpy(num, line + 15, 9);
1165 pt->y = atof(num) / METRES_PER_FOOT;
1166 memcpy(num, line + 24, 8);
1167 num[8] = '\0';
1168 pt->z = atof(num) / METRES_PER_FOOT;
1171 static void
1172 read_xyz_shot_coords(img_point *pt, const char *line)
1174 char num[12];
1175 memcpy(num, line + 40, 10);
1176 num[10] = '\0';
1177 pt->x = atof(num) / METRES_PER_FOOT;
1178 memcpy(num, line + 50, 10);
1179 pt->y = atof(num) / METRES_PER_FOOT;
1180 memcpy(num, line + 60, 9);
1181 num[9] = '\0';
1182 pt->z = atof(num) / METRES_PER_FOOT;
1185 static void
1186 subtract_xyz_shot_deltas(img_point *pt, const char *line)
1188 char num[12];
1189 memcpy(num, line + 15, 9);
1190 num[9] = '\0';
1191 pt->x -= atof(num) / METRES_PER_FOOT;
1192 memcpy(num, line + 24, 8);
1193 num[8] = '\0';
1194 pt->y -= atof(num) / METRES_PER_FOOT;
1195 memcpy(num, line + 32, 8);
1196 pt->z -= atof(num) / METRES_PER_FOOT;
1199 static int
1200 read_coord(FILE *fh, img_point *pt)
1202 SVX_ASSERT(fh);
1203 SVX_ASSERT(pt);
1204 pt->x = get32(fh) / 100.0;
1205 pt->y = get32(fh) / 100.0;
1206 pt->z = get32(fh) / 100.0;
1207 if (ferror(fh) || feof(fh)) {
1208 img_errno = feof(fh) ? IMG_BADFORMAT : IMG_READERROR;
1209 return 0;
1211 return 1;
1214 static int
1215 skip_coord(FILE *fh)
1217 return (fseek(fh, 12, SEEK_CUR) == 0);
1220 static int
1221 read_v3label(img *pimg)
1223 char *q;
1224 long len = GETC(pimg->fh);
1225 if (len == EOF) {
1226 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1227 return img_BAD;
1229 if (len == 0xfe) {
1230 len += get16(pimg->fh);
1231 if (feof(pimg->fh)) {
1232 img_errno = IMG_BADFORMAT;
1233 return img_BAD;
1235 if (ferror(pimg->fh)) {
1236 img_errno = IMG_READERROR;
1237 return img_BAD;
1239 } else if (len == 0xff) {
1240 len = get32(pimg->fh);
1241 if (ferror(pimg->fh)) {
1242 img_errno = IMG_READERROR;
1243 return img_BAD;
1245 if (feof(pimg->fh) || len < 0xfe + 0xffff) {
1246 img_errno = IMG_BADFORMAT;
1247 return img_BAD;
1251 if (!check_label_space(pimg, pimg->label_len + len + 1)) {
1252 img_errno = IMG_OUTOFMEMORY;
1253 return img_BAD;
1255 q = pimg->label_buf + pimg->label_len;
1256 pimg->label_len += len;
1257 if (len && fread(q, len, 1, pimg->fh) != 1) {
1258 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1259 return img_BAD;
1261 q[len] = '\0';
1262 return 0;
1265 static int
1266 read_v8label(img *pimg, int common_flag, size_t common_val)
1268 char *q;
1269 size_t del, add;
1270 if (common_flag) {
1271 if (common_val == 0) return 0;
1272 add = del = common_val;
1273 } else {
1274 int ch = GETC(pimg->fh);
1275 if (ch == EOF) {
1276 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1277 return img_BAD;
1279 if (ch != 0x00) {
1280 del = ch >> 4;
1281 add = ch & 0x0f;
1282 } else {
1283 ch = GETC(pimg->fh);
1284 if (ch == EOF) {
1285 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1286 return img_BAD;
1288 if (ch != 0xff) {
1289 del = ch;
1290 } else {
1291 del = get32(pimg->fh);
1292 if (ferror(pimg->fh)) {
1293 img_errno = IMG_READERROR;
1294 return img_BAD;
1297 ch = GETC(pimg->fh);
1298 if (ch == EOF) {
1299 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1300 return img_BAD;
1302 if (ch != 0xff) {
1303 add = ch;
1304 } else {
1305 add = get32(pimg->fh);
1306 if (ferror(pimg->fh)) {
1307 img_errno = IMG_READERROR;
1308 return img_BAD;
1313 if (add > del && !check_label_space(pimg, pimg->label_len + add - del + 1)) {
1314 img_errno = IMG_OUTOFMEMORY;
1315 return img_BAD;
1318 if (del > pimg->label_len) {
1319 img_errno = IMG_BADFORMAT;
1320 return img_BAD;
1322 pimg->label_len -= del;
1323 q = pimg->label_buf + pimg->label_len;
1324 pimg->label_len += add;
1325 if (add && fread(q, add, 1, pimg->fh) != 1) {
1326 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1327 return img_BAD;
1329 q[add] = '\0';
1330 return 0;
1333 static int img_read_item_new(img *pimg, img_point *p);
1334 static int img_read_item_v3to7(img *pimg, img_point *p);
1335 static int img_read_item_ancient(img *pimg, img_point *p);
1336 static int img_read_item_ascii_wrapper(img *pimg, img_point *p);
1337 static int img_read_item_ascii(img *pimg, img_point *p);
1340 img_read_item(img *pimg, img_point *p)
1342 pimg->flags = 0;
1344 if (pimg->version >= 8) {
1345 return img_read_item_new(pimg, p);
1346 } else if (pimg->version >= 3) {
1347 return img_read_item_v3to7(pimg, p);
1348 } else if (pimg->version >= 1) {
1349 return img_read_item_ancient(pimg, p);
1350 } else {
1351 return img_read_item_ascii_wrapper(pimg, p);
1355 static int
1356 img_read_item_new(img *pimg, img_point *p)
1358 int result;
1359 int opt;
1360 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1361 if (pimg->pending >= 0x40) {
1362 if (pimg->pending == 256) {
1363 pimg->pending = 0;
1364 return img_XSECT_END;
1366 *p = pimg->mv;
1367 pimg->flags = (int)(pimg->pending) & 0x3f;
1368 pimg->pending = 0;
1369 return img_LINE;
1371 again3: /* label to goto if we get a prefix, date, or lrud */
1372 pimg->label = pimg->label_buf;
1373 opt = GETC(pimg->fh);
1374 if (opt == EOF) {
1375 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1376 return img_BAD;
1378 if (opt >> 6 == 0) {
1379 if (opt <= 4) {
1380 if (opt == 0 && pimg->style == 0)
1381 return img_STOP; /* end of data marker */
1382 /* STYLE */
1383 pimg->style = opt;
1384 goto again3;
1386 if (opt >= 0x10) {
1387 switch (opt) {
1388 case 0x10: { /* No date info */
1389 #if IMG_API_VERSION == 0
1390 pimg->date1 = pimg->date2 = 0;
1391 #else /* IMG_API_VERSION == 1 */
1392 pimg->days1 = pimg->days2 = -1;
1393 #endif
1394 break;
1396 case 0x11: { /* Single date */
1397 int days1 = (int)getu16(pimg->fh);
1398 #if IMG_API_VERSION == 0
1399 pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
1400 #else /* IMG_API_VERSION == 1 */
1401 pimg->days2 = pimg->days1 = days1;
1402 #endif
1403 break;
1405 case 0x12: { /* Date range (short) */
1406 int days1 = (int)getu16(pimg->fh);
1407 int days2 = days1 + GETC(pimg->fh) + 1;
1408 #if IMG_API_VERSION == 0
1409 pimg->date1 = (days1 - 25567) * 86400;
1410 pimg->date2 = (days2 - 25567) * 86400;
1411 #else /* IMG_API_VERSION == 1 */
1412 pimg->days1 = days1;
1413 pimg->days2 = days2;
1414 #endif
1415 break;
1417 case 0x13: { /* Date range (long) */
1418 int days1 = (int)getu16(pimg->fh);
1419 int days2 = (int)getu16(pimg->fh);
1420 #if IMG_API_VERSION == 0
1421 pimg->date1 = (days1 - 25567) * 86400;
1422 pimg->date2 = (days2 - 25567) * 86400;
1423 #else /* IMG_API_VERSION == 1 */
1424 pimg->days1 = days1;
1425 pimg->days2 = days2;
1426 #endif
1427 break;
1429 case 0x1f: /* Error info */
1430 pimg->n_legs = get32(pimg->fh);
1431 pimg->length = get32(pimg->fh) / 100.0;
1432 pimg->E = get32(pimg->fh) / 100.0;
1433 pimg->H = get32(pimg->fh) / 100.0;
1434 pimg->V = get32(pimg->fh) / 100.0;
1435 return img_ERROR_INFO;
1436 case 0x30: case 0x31: /* LRUD */
1437 case 0x32: case 0x33: /* Big LRUD! */
1438 if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1439 pimg->flags = (int)opt & 0x01;
1440 if (opt < 0x32) {
1441 pimg->l = get16(pimg->fh) / 100.0;
1442 pimg->r = get16(pimg->fh) / 100.0;
1443 pimg->u = get16(pimg->fh) / 100.0;
1444 pimg->d = get16(pimg->fh) / 100.0;
1445 } else {
1446 pimg->l = get32(pimg->fh) / 100.0;
1447 pimg->r = get32(pimg->fh) / 100.0;
1448 pimg->u = get32(pimg->fh) / 100.0;
1449 pimg->d = get32(pimg->fh) / 100.0;
1451 if (!stn_included(pimg)) {
1452 return img_XSECT_END;
1454 /* If this is the last cross-section in this passage, set
1455 * pending so we return img_XSECT_END next time. */
1456 if (pimg->flags & 0x01) {
1457 pimg->pending = 256;
1458 pimg->flags &= ~0x01;
1460 return img_XSECT;
1461 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1462 img_errno = IMG_BADFORMAT;
1463 return img_BAD;
1465 goto again3;
1467 if (opt != 15) {
1468 /* 1-14 and 16-31 reserved */
1469 img_errno = IMG_BADFORMAT;
1470 return img_BAD;
1472 result = img_MOVE;
1473 } else if (opt >= 0x80) {
1474 if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1476 result = img_LABEL;
1478 if (!stn_included(pimg)) {
1479 if (!skip_coord(pimg->fh)) return img_BAD;
1480 pimg->pending = 0;
1481 goto again3;
1484 pimg->flags = (int)opt & 0x7f;
1485 } else if ((opt >> 6) == 1) {
1486 if (read_v8label(pimg, opt & 0x20, 0) == img_BAD) return img_BAD;
1488 result = img_LINE;
1490 if (!survey_included(pimg)) {
1491 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1492 pimg->pending = 15;
1493 goto again3;
1496 if (pimg->pending) {
1497 *p = pimg->mv;
1498 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1499 pimg->pending = opt;
1500 return img_MOVE;
1502 pimg->flags = (int)opt & 0x1f;
1503 } else {
1504 img_errno = IMG_BADFORMAT;
1505 return img_BAD;
1507 if (!read_coord(pimg->fh, p)) return img_BAD;
1508 pimg->pending = 0;
1509 return result;
1512 static int
1513 img_read_item_v3to7(img *pimg, img_point *p)
1515 int result;
1516 int opt;
1517 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1518 if (pimg->pending == 256) {
1519 pimg->pending = 0;
1520 return img_XSECT_END;
1522 if (pimg->pending >= 0x80) {
1523 *p = pimg->mv;
1524 pimg->flags = (int)(pimg->pending) & 0x3f;
1525 pimg->pending = 0;
1526 return img_LINE;
1528 again3: /* label to goto if we get a prefix, date, or lrud */
1529 pimg->label = pimg->label_buf;
1530 opt = GETC(pimg->fh);
1531 if (opt == EOF) {
1532 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1533 return img_BAD;
1535 switch (opt >> 6) {
1536 case 0:
1537 if (opt == 0) {
1538 if (!pimg->label_len) return img_STOP; /* end of data marker */
1539 pimg->label_len = 0;
1540 goto again3;
1542 if (opt < 15) {
1543 /* 1-14 mean trim that many levels from current prefix */
1544 int c;
1545 if (pimg->label_len <= 17) {
1546 /* zero prefix using "0" */
1547 img_errno = IMG_BADFORMAT;
1548 return img_BAD;
1550 /* extra - 1 because label_len points to one past the end */
1551 c = pimg->label_len - 17 - 1;
1552 while (pimg->label_buf[c] != '.' || --opt > 0) {
1553 if (--c < 0) {
1554 /* zero prefix using "0" */
1555 img_errno = IMG_BADFORMAT;
1556 return img_BAD;
1559 c++;
1560 pimg->label_len = c;
1561 goto again3;
1563 if (opt == 15) {
1564 result = img_MOVE;
1565 break;
1567 if (opt >= 0x20) {
1568 switch (opt) {
1569 case 0x20: /* Single date */
1570 if (pimg->version < 7) {
1571 int date1 = get32(pimg->fh);
1572 #if IMG_API_VERSION == 0
1573 pimg->date2 = pimg->date1 = date1;
1574 #else /* IMG_API_VERSION == 1 */
1575 if (date1 != 0) {
1576 pimg->days2 = pimg->days1 = (date1 / 86400) + 25567;
1577 } else {
1578 pimg->days2 = pimg->days1 = -1;
1580 #endif
1581 } else {
1582 int days1 = (int)getu16(pimg->fh);
1583 #if IMG_API_VERSION == 0
1584 pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
1585 #else /* IMG_API_VERSION == 1 */
1586 pimg->days2 = pimg->days1 = days1;
1587 #endif
1589 break;
1590 case 0x21: /* Date range (short for v7+) */
1591 if (pimg->version < 7) {
1592 INT32_T date1 = get32(pimg->fh);
1593 INT32_T date2 = get32(pimg->fh);
1594 #if IMG_API_VERSION == 0
1595 pimg->date1 = date1;
1596 pimg->date2 = date2;
1597 #else /* IMG_API_VERSION == 1 */
1598 pimg->days1 = (date1 / 86400) + 25567;
1599 pimg->days2 = (date2 / 86400) + 25567;
1600 #endif
1601 } else {
1602 int days1 = (int)getu16(pimg->fh);
1603 int days2 = days1 + GETC(pimg->fh) + 1;
1604 #if IMG_API_VERSION == 0
1605 pimg->date1 = (days1 - 25567) * 86400;
1606 pimg->date2 = (days2 - 25567) * 86400;
1607 #else /* IMG_API_VERSION == 1 */
1608 pimg->days1 = days1;
1609 pimg->days2 = days2;
1610 #endif
1612 break;
1613 case 0x22: /* Error info */
1614 pimg->n_legs = get32(pimg->fh);
1615 pimg->length = get32(pimg->fh) / 100.0;
1616 pimg->E = get32(pimg->fh) / 100.0;
1617 pimg->H = get32(pimg->fh) / 100.0;
1618 pimg->V = get32(pimg->fh) / 100.0;
1619 if (feof(pimg->fh)) {
1620 img_errno = IMG_BADFORMAT;
1621 return img_BAD;
1623 if (ferror(pimg->fh)) {
1624 img_errno = IMG_READERROR;
1625 return img_BAD;
1627 return img_ERROR_INFO;
1628 case 0x23: { /* v7+: Date range (long) */
1629 if (pimg->version < 7) {
1630 img_errno = IMG_BADFORMAT;
1631 return img_BAD;
1633 int days1 = (int)getu16(pimg->fh);
1634 int days2 = (int)getu16(pimg->fh);
1635 if (feof(pimg->fh)) {
1636 img_errno = IMG_BADFORMAT;
1637 return img_BAD;
1639 if (ferror(pimg->fh)) {
1640 img_errno = IMG_READERROR;
1641 return img_BAD;
1643 #if IMG_API_VERSION == 0
1644 pimg->date1 = (days1 - 25567) * 86400;
1645 pimg->date2 = (days2 - 25567) * 86400;
1646 #else /* IMG_API_VERSION == 1 */
1647 pimg->days1 = days1;
1648 pimg->days2 = days2;
1649 #endif
1650 break;
1652 case 0x24: { /* v7+: No date info */
1653 #if IMG_API_VERSION == 0
1654 pimg->date1 = pimg->date2 = 0;
1655 #else /* IMG_API_VERSION == 1 */
1656 pimg->days1 = pimg->days2 = -1;
1657 #endif
1658 break;
1660 case 0x30: case 0x31: /* LRUD */
1661 case 0x32: case 0x33: /* Big LRUD! */
1662 if (read_v3label(pimg) == img_BAD) return img_BAD;
1663 pimg->flags = (int)opt & 0x01;
1664 if (opt < 0x32) {
1665 pimg->l = get16(pimg->fh) / 100.0;
1666 pimg->r = get16(pimg->fh) / 100.0;
1667 pimg->u = get16(pimg->fh) / 100.0;
1668 pimg->d = get16(pimg->fh) / 100.0;
1669 } else {
1670 pimg->l = get32(pimg->fh) / 100.0;
1671 pimg->r = get32(pimg->fh) / 100.0;
1672 pimg->u = get32(pimg->fh) / 100.0;
1673 pimg->d = get32(pimg->fh) / 100.0;
1675 if (feof(pimg->fh)) {
1676 img_errno = IMG_BADFORMAT;
1677 return img_BAD;
1679 if (ferror(pimg->fh)) {
1680 img_errno = IMG_READERROR;
1681 return img_BAD;
1683 if (!stn_included(pimg)) {
1684 return img_XSECT_END;
1686 /* If this is the last cross-section in this passage, set
1687 * pending so we return img_XSECT_END next time. */
1688 if (pimg->flags & 0x01) {
1689 pimg->pending = 256;
1690 pimg->flags &= ~0x01;
1692 return img_XSECT;
1693 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1694 img_errno = IMG_BADFORMAT;
1695 return img_BAD;
1697 if (feof(pimg->fh)) {
1698 img_errno = IMG_BADFORMAT;
1699 return img_BAD;
1701 if (ferror(pimg->fh)) {
1702 img_errno = IMG_READERROR;
1703 return img_BAD;
1705 goto again3;
1707 /* 16-31 mean remove (n - 15) characters from the prefix */
1708 /* zero prefix using 0 */
1709 if (pimg->label_len <= (size_t)(opt - 15)) {
1710 img_errno = IMG_BADFORMAT;
1711 return img_BAD;
1713 pimg->label_len -= (opt - 15);
1714 goto again3;
1715 case 1:
1716 if (read_v3label(pimg) == img_BAD) return img_BAD;
1718 result = img_LABEL;
1720 if (!stn_included(pimg)) {
1721 if (!skip_coord(pimg->fh)) return img_BAD;
1722 pimg->pending = 0;
1723 goto again3;
1726 pimg->flags = (int)opt & 0x3f;
1727 break;
1728 case 2:
1729 if (read_v3label(pimg) == img_BAD) return img_BAD;
1731 result = img_LINE;
1733 if (!survey_included(pimg)) {
1734 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1735 pimg->pending = 15;
1736 goto again3;
1739 if (pimg->pending) {
1740 *p = pimg->mv;
1741 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1742 pimg->pending = opt;
1743 return img_MOVE;
1745 pimg->flags = (int)opt & 0x3f;
1746 break;
1747 default:
1748 img_errno = IMG_BADFORMAT;
1749 return img_BAD;
1751 if (!read_coord(pimg->fh, p)) return img_BAD;
1752 pimg->pending = 0;
1753 return result;
1756 static int
1757 img_read_item_ancient(img *pimg, img_point *p)
1759 int result;
1760 static long opt_lookahead = 0;
1761 static img_point pt = { 0.0, 0.0, 0.0 };
1762 long opt;
1764 again: /* label to goto if we get a cross */
1765 pimg->label = pimg->label_buf;
1766 pimg->label[0] = '\0';
1768 if (pimg->version == 1) {
1769 if (opt_lookahead) {
1770 opt = opt_lookahead;
1771 opt_lookahead = 0;
1772 } else {
1773 opt = get32(pimg->fh);
1775 } else {
1776 opt = GETC(pimg->fh);
1779 if (feof(pimg->fh)) {
1780 img_errno = IMG_BADFORMAT;
1781 return img_BAD;
1783 if (ferror(pimg->fh)) {
1784 img_errno = IMG_READERROR;
1785 return img_BAD;
1788 switch (opt) {
1789 case -1: case 0:
1790 return img_STOP; /* end of data marker */
1791 case 1:
1792 /* skip coordinates */
1793 if (!skip_coord(pimg->fh)) {
1794 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1795 return img_BAD;
1797 goto again;
1798 case 2: case 3: {
1799 size_t len;
1800 result = img_LABEL;
1801 if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
1802 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1803 return img_BAD;
1805 if (pimg->label[0] == '\\') pimg->label++;
1806 len = strlen(pimg->label);
1807 if (len == 0 || pimg->label[len - 1] != '\n') {
1808 img_errno = IMG_BADFORMAT;
1809 return img_BAD;
1811 /* Ignore empty labels in some .3d files (caused by a bug) */
1812 if (len == 1) goto again;
1813 pimg->label[len - 1] = '\0';
1814 pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1815 if (opt == 2) goto done;
1816 break;
1818 case 6: case 7: {
1819 long len;
1820 result = img_LABEL;
1822 if (opt == 7)
1823 pimg->flags = GETC(pimg->fh);
1824 else
1825 pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1827 len = get32(pimg->fh);
1829 if (feof(pimg->fh)) {
1830 img_errno = IMG_BADFORMAT;
1831 return img_BAD;
1833 if (ferror(pimg->fh)) {
1834 img_errno = IMG_READERROR;
1835 return img_BAD;
1838 /* Ignore empty labels in some .3d files (caused by a bug) */
1839 if (len == 0) goto again;
1840 if (!check_label_space(pimg, len + 1)) {
1841 img_errno = IMG_OUTOFMEMORY;
1842 return img_BAD;
1844 if (fread(pimg->label_buf, len, 1, pimg->fh) != 1) {
1845 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1846 return img_BAD;
1848 pimg->label_buf[len] = '\0';
1849 break;
1851 case 4:
1852 result = img_MOVE;
1853 break;
1854 case 5:
1855 result = img_LINE;
1856 break;
1857 default:
1858 switch ((int)opt & 0xc0) {
1859 case 0x80:
1860 pimg->flags = (int)opt & 0x3f;
1861 result = img_LINE;
1862 break;
1863 case 0x40: {
1864 char *q;
1865 pimg->flags = (int)opt & 0x3f;
1866 result = img_LABEL;
1867 if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
1868 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1869 return img_BAD;
1871 q = pimg->label_buf + strlen(pimg->label_buf) - 1;
1872 /* Ignore empty-labels in some .3d files (caused by a bug) */
1873 if (q == pimg->label_buf) goto again;
1874 if (*q != '\n') {
1875 img_errno = IMG_BADFORMAT;
1876 return img_BAD;
1878 *q = '\0';
1879 break;
1881 default:
1882 img_errno = IMG_BADFORMAT;
1883 return img_BAD;
1885 break;
1888 if (!read_coord(pimg->fh, &pt)) return img_BAD;
1890 if (result == img_LABEL && !stn_included(pimg)) {
1891 goto again;
1894 done:
1895 *p = pt;
1897 if (result == img_MOVE && pimg->version == 1) {
1898 /* peek at next code and see if it's an old-style label */
1899 opt_lookahead = get32(pimg->fh);
1901 if (feof(pimg->fh)) {
1902 img_errno = IMG_BADFORMAT;
1903 return img_BAD;
1905 if (ferror(pimg->fh)) {
1906 img_errno = IMG_READERROR;
1907 return img_BAD;
1910 if (opt_lookahead == 2) return img_read_item_ancient(pimg, p);
1913 return result;
1916 static int
1917 img_read_item_ascii_wrapper(img *pimg, img_point *p)
1919 /* We need to set the default locale for fscanf() to work on
1920 * numbers with "." as decimal point. */
1921 int result;
1922 char * current_locale = my_strdup(setlocale(LC_NUMERIC, NULL));
1923 setlocale(LC_NUMERIC, "C");
1924 result = img_read_item_ascii(pimg, p);
1925 setlocale(LC_NUMERIC, current_locale);
1926 free(current_locale);
1927 return result;
1930 /* Handle all ASCII formats. */
1931 static int
1932 img_read_item_ascii(img *pimg, img_point *p)
1934 int result;
1935 pimg->label = pimg->label_buf;
1936 if (pimg->version == 0) {
1937 ascii_again:
1938 pimg->label[0] = '\0';
1939 if (feof(pimg->fh)) return img_STOP;
1940 if (pimg->pending) {
1941 pimg->pending = 0;
1942 result = img_LINE;
1943 } else {
1944 char cmd[7];
1945 /* Stop if nothing found */
1946 if (fscanf(pimg->fh, "%6s", cmd) < 1) return img_STOP;
1947 if (strcmp(cmd, "move") == 0)
1948 result = img_MOVE;
1949 else if (strcmp(cmd, "draw") == 0)
1950 result = img_LINE;
1951 else if (strcmp(cmd, "line") == 0) {
1952 /* set flag to indicate to process second triplet as LINE */
1953 pimg->pending = 1;
1954 result = img_MOVE;
1955 } else if (strcmp(cmd, "cross") == 0) {
1956 if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1957 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1958 return img_BAD;
1960 goto ascii_again;
1961 } else if (strcmp(cmd, "name") == 0) {
1962 size_t off = 0;
1963 int ch = GETC(pimg->fh);
1964 if (ch == ' ') ch = GETC(pimg->fh);
1965 while (ch != ' ') {
1966 if (ch == '\n' || ch == EOF) {
1967 img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1968 return img_BAD;
1970 if (off == pimg->buf_len) {
1971 if (!check_label_space(pimg, pimg->buf_len * 2)) {
1972 img_errno = IMG_OUTOFMEMORY;
1973 return img_BAD;
1976 pimg->label_buf[off++] = ch;
1977 ch = GETC(pimg->fh);
1979 pimg->label_buf[off] = '\0';
1981 pimg->label = pimg->label_buf;
1982 if (pimg->label[0] == '\\') pimg->label++;
1984 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1986 result = img_LABEL;
1987 } else {
1988 img_errno = IMG_BADFORMAT;
1989 return img_BAD; /* unknown keyword */
1993 if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1994 img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1995 return img_BAD;
1998 if (result == img_LABEL && !stn_included(pimg)) {
1999 goto ascii_again;
2002 return result;
2003 } else if (pimg->version == VERSION_SURVEX_POS) {
2004 /* Survex .pos file */
2005 int ch;
2006 size_t off;
2007 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
2008 againpos:
2009 while (fscanf(pimg->fh, "(%lf,%lf,%lf )", &p->x, &p->y, &p->z) != 3) {
2010 if (ferror(pimg->fh)) {
2011 img_errno = IMG_READERROR;
2012 return img_BAD;
2014 if (feof(pimg->fh)) return img_STOP;
2015 if (pimg->pending) {
2016 img_errno = IMG_BADFORMAT;
2017 return img_BAD;
2019 pimg->pending = 1;
2020 /* ignore rest of line */
2021 do {
2022 ch = GETC(pimg->fh);
2023 } while (ch != '\n' && ch != '\r' && ch != EOF);
2026 pimg->label_buf[0] = '\0';
2027 do {
2028 ch = GETC(pimg->fh);
2029 } while (ch == ' ' || ch == '\t');
2030 if (ch == '\n' || ch == EOF) {
2031 /* If there's no label, set img_SFLAG_ANON. */
2032 pimg->flags |= img_SFLAG_ANON;
2033 return img_LABEL;
2035 pimg->label_buf[0] = ch;
2036 off = 1;
2037 while (!feof(pimg->fh)) {
2038 if (!fgets(pimg->label_buf + off, pimg->buf_len - off, pimg->fh)) {
2039 img_errno = IMG_READERROR;
2040 return img_BAD;
2043 off += strlen(pimg->label_buf + off);
2044 if (off && pimg->label_buf[off - 1] == '\n') {
2045 pimg->label_buf[off - 1] = '\0';
2046 break;
2048 if (!check_label_space(pimg, pimg->buf_len * 2)) {
2049 img_errno = IMG_OUTOFMEMORY;
2050 return img_BAD;
2054 pimg->label = pimg->label_buf;
2056 if (pimg->label[0] == '\\') pimg->label++;
2058 if (!stn_included(pimg)) goto againpos;
2060 return img_LABEL;
2061 } else if (pimg->version == VERSION_COMPASS_PLT) {
2062 /* Compass .plt file */
2063 if (pimg->pending > 0) {
2064 /* -1 signals we've entered the first survey we want to
2065 * read, and need to fudge lots if the first action is 'D'...
2067 /* pending MOVE or LINE */
2068 int r = pimg->pending - 4;
2069 pimg->pending = 0;
2070 pimg->flags = 0;
2071 pimg->label[pimg->label_len] = '\0';
2072 return r;
2075 while (1) {
2076 char *line;
2077 char *q;
2078 size_t len = 0;
2079 int ch = GETC(pimg->fh);
2081 switch (ch) {
2082 case '\x1a': case EOF: /* Don't insist on ^Z at end of file */
2083 return img_STOP;
2084 case 'X': case 'F': case 'S':
2085 /* bounding boX (marks end of survey), Feature survey, or
2086 * new Section - skip to next survey */
2087 if (pimg->survey) return img_STOP;
2088 skip_to_N:
2089 while (1) {
2090 do {
2091 ch = GETC(pimg->fh);
2092 } while (ch != '\n' && ch != '\r' && ch != EOF);
2093 while (ch == '\n' || ch == '\r') ch = GETC(pimg->fh);
2094 if (ch == 'N') break;
2095 if (ch == '\x1a' || ch == EOF) return img_STOP;
2097 /* FALLTHRU */
2098 case 'N':
2099 line = getline_alloc(pimg->fh);
2100 if (!line) {
2101 img_errno = IMG_OUTOFMEMORY;
2102 return img_BAD;
2104 while (line[len] > 32) ++len;
2105 if (pimg->label_len == 0) pimg->pending = -1;
2106 if (!check_label_space(pimg, len + 1)) {
2107 osfree(line);
2108 img_errno = IMG_OUTOFMEMORY;
2109 return img_BAD;
2111 pimg->label_len = len;
2112 pimg->label = pimg->label_buf;
2113 memcpy(pimg->label, line, len);
2114 pimg->label[len] = '\0';
2115 osfree(line);
2116 break;
2117 case 'M': case 'D': {
2118 /* Move or Draw */
2119 long fpos = -1;
2120 if (pimg->survey && pimg->label_len == 0) {
2121 /* We're only holding onto this line in case the first line
2122 * of the 'N' is a 'D', so skip it for now...
2124 goto skip_to_N;
2126 if (ch == 'D' && pimg->pending == -1) {
2127 if (pimg->survey) {
2128 fpos = ftell(pimg->fh) - 1;
2129 fseek(pimg->fh, pimg->start, SEEK_SET);
2130 ch = GETC(pimg->fh);
2131 pimg->pending = 0;
2132 } else {
2133 /* If a file actually has a 'D' before any 'M', then
2134 * pretend the 'D' is an 'M' - one of the examples
2135 * in the docs was like this! */
2136 ch = 'M';
2139 line = getline_alloc(pimg->fh);
2140 if (!line) {
2141 img_errno = IMG_OUTOFMEMORY;
2142 return img_BAD;
2144 /* Compass stores coordinates as North, East, Up = (y,x,z)! */
2145 if (sscanf(line, "%lf%lf%lf", &p->y, &p->x, &p->z) != 3) {
2146 osfree(line);
2147 if (ferror(pimg->fh)) {
2148 img_errno = IMG_READERROR;
2149 } else {
2150 img_errno = IMG_BADFORMAT;
2152 return img_BAD;
2154 p->x *= METRES_PER_FOOT;
2155 p->y *= METRES_PER_FOOT;
2156 p->z *= METRES_PER_FOOT;
2157 q = strchr(line, 'S');
2158 if (!q) {
2159 osfree(line);
2160 img_errno = IMG_BADFORMAT;
2161 return img_BAD;
2163 ++q;
2164 len = 0;
2165 while (q[len] > ' ') ++len;
2166 q[len] = '\0';
2167 len += 2; /* ' ' and '\0' */
2168 if (!check_label_space(pimg, pimg->label_len + len)) {
2169 img_errno = IMG_OUTOFMEMORY;
2170 return img_BAD;
2172 pimg->label = pimg->label_buf;
2173 if (pimg->label_len) {
2174 pimg->label[pimg->label_len] = ' ';
2175 memcpy(pimg->label + pimg->label_len + 1, q, len - 1);
2176 } else {
2177 memcpy(pimg->label, q, len - 1);
2179 q += len - 1;
2180 /* Now read LRUD. Technically, this is optional but virtually
2181 * all PLT files have it (with dummy negative values if no LRUD
2182 * was measured) and some versions of Compass can't read PLT
2183 * files without it!
2185 while (*q && *q <= ' ') q++;
2186 if (*q == 'P') {
2187 if (sscanf(q + 1, "%lf%lf%lf%lf",
2188 &pimg->l, &pimg->r, &pimg->u, &pimg->d) != 4) {
2189 osfree(line);
2190 if (ferror(pimg->fh)) {
2191 img_errno = IMG_READERROR;
2192 } else {
2193 img_errno = IMG_BADFORMAT;
2195 return img_BAD;
2197 pimg->l *= METRES_PER_FOOT;
2198 pimg->r *= METRES_PER_FOOT;
2199 pimg->u *= METRES_PER_FOOT;
2200 pimg->d *= METRES_PER_FOOT;
2201 } else {
2202 pimg->l = pimg->r = pimg->u = pimg->d = -1;
2204 osfree(line);
2205 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
2206 if (fpos != -1) {
2207 fseek(pimg->fh, fpos, SEEK_SET);
2208 } else {
2209 pimg->pending = (ch == 'M' ? img_MOVE : img_LINE) + 4;
2211 return img_LABEL;
2213 default:
2214 img_errno = IMG_BADFORMAT;
2215 return img_BAD;
2218 } else {
2219 /* CMAP .xyz file */
2220 char *line = NULL;
2221 char *q;
2222 size_t len;
2224 if (pimg->pending) {
2225 /* pending MOVE or LINE or LABEL or STOP */
2226 int r = pimg->pending - 4;
2227 /* Set label to empty - don't use "" as we adjust label relative
2228 * to label_buf when label_buf is reallocated. */
2229 pimg->label = pimg->label_buf + strlen(pimg->label_buf);
2230 pimg->flags = 0;
2231 if (r == img_LABEL) {
2232 /* nasty magic */
2233 read_xyz_shot_coords(p, pimg->label_buf + 16);
2234 subtract_xyz_shot_deltas(p, pimg->label_buf + 16);
2235 pimg->pending = img_STOP + 4;
2236 return img_MOVE;
2239 pimg->pending = 0;
2241 if (r == img_STOP) {
2242 /* nasty magic */
2243 read_xyz_shot_coords(p, pimg->label_buf + 16);
2244 return img_LINE;
2247 return r;
2250 pimg->label = pimg->label_buf;
2251 do {
2252 osfree(line);
2253 if (feof(pimg->fh)) return img_STOP;
2254 line = getline_alloc(pimg->fh);
2255 if (!line) {
2256 img_errno = IMG_OUTOFMEMORY;
2257 return img_BAD;
2259 } while (line[0] == ' ' || line[0] == '\0');
2260 if (line[0] == '\x1a') return img_STOP;
2262 len = strlen(line);
2263 if (pimg->version == VERSION_CMAP_STATION) {
2264 /* station variant */
2265 if (len < 37) {
2266 osfree(line);
2267 img_errno = IMG_BADFORMAT;
2268 return img_BAD;
2270 memcpy(pimg->label, line, 6);
2271 q = (char *)memchr(pimg->label, ' ', 6);
2272 if (!q) q = pimg->label + 6;
2273 *q = '\0';
2275 read_xyz_station_coords(p, line);
2277 /* FIXME: look at prev for lines (line + 32, 5) */
2278 /* FIXME: duplicate stations... */
2279 return img_LABEL;
2280 } else {
2281 /* Shot variant (VERSION_CMAP_SHOT) */
2282 char old[8], new_[8];
2283 if (len < 61) {
2284 osfree(line);
2285 img_errno = IMG_BADFORMAT;
2286 return img_BAD;
2289 memcpy(old, line, 7);
2290 q = (char *)memchr(old, ' ', 7);
2291 if (!q) q = old + 7;
2292 *q = '\0';
2294 memcpy(new_, line + 7, 7);
2295 q = (char *)memchr(new_, ' ', 7);
2296 if (!q) q = new_ + 7;
2297 *q = '\0';
2299 pimg->flags = img_SFLAG_UNDERGROUND;
2301 if (strcmp(old, new_) == 0) {
2302 pimg->pending = img_MOVE + 4;
2303 read_xyz_shot_coords(p, line);
2304 strcpy(pimg->label, new_);
2305 osfree(line);
2306 return img_LABEL;
2309 if (strcmp(old, pimg->label) == 0) {
2310 pimg->pending = img_LINE + 4;
2311 read_xyz_shot_coords(p, line);
2312 strcpy(pimg->label, new_);
2313 osfree(line);
2314 return img_LABEL;
2317 pimg->pending = img_LABEL + 4;
2318 read_xyz_shot_coords(p, line);
2319 strcpy(pimg->label, new_);
2320 memcpy(pimg->label + 16, line, 70);
2322 osfree(line);
2323 return img_LABEL;
2328 static void
2329 write_coord(FILE *fh, double x, double y, double z)
2331 SVX_ASSERT(fh);
2332 /* Output in cm */
2333 static INT32_T X_, Y_, Z_;
2334 INT32_T X = my_lround(x * 100.0);
2335 INT32_T Y = my_lround(y * 100.0);
2336 INT32_T Z = my_lround(z * 100.0);
2338 X_ -= X;
2339 Y_ -= Y;
2340 Z_ -= Z;
2341 put32(X, fh);
2342 put32(Y, fh);
2343 put32(Z, fh);
2344 X_ = X; Y_ = Y; Z_ = Z;
2347 static int
2348 write_v3label(img *pimg, int opt, const char *s)
2350 size_t len, n, dot;
2352 /* find length of common prefix */
2353 dot = 0;
2354 for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2355 if (s[len] == '.') dot = len + 1;
2358 SVX_ASSERT(len <= pimg->label_len);
2359 n = pimg->label_len - len;
2360 if (len == 0) {
2361 if (pimg->label_len) PUTC(0, pimg->fh);
2362 } else if (n <= 16) {
2363 if (n) PUTC(n + 15, pimg->fh);
2364 } else if (dot == 0) {
2365 if (pimg->label_len) PUTC(0, pimg->fh);
2366 len = 0;
2367 } else {
2368 const char *p = pimg->label_buf + dot;
2369 n = 1;
2370 for (len = pimg->label_len - dot - 17; len; len--) {
2371 if (*p++ == '.') n++;
2373 if (n <= 14) {
2374 PUTC(n, pimg->fh);
2375 len = dot;
2376 } else {
2377 if (pimg->label_len) PUTC(0, pimg->fh);
2378 len = 0;
2382 n = strlen(s + len);
2383 PUTC(opt, pimg->fh);
2384 if (n < 0xfe) {
2385 PUTC(n, pimg->fh);
2386 } else if (n < 0xffff + 0xfe) {
2387 PUTC(0xfe, pimg->fh);
2388 put16((short)(n - 0xfe), pimg->fh);
2389 } else {
2390 PUTC(0xff, pimg->fh);
2391 put32(n, pimg->fh);
2393 fwrite(s + len, n, 1, pimg->fh);
2395 n += len;
2396 pimg->label_len = n;
2397 if (!check_label_space(pimg, n + 1))
2398 return 0; /* FIXME: distinguish out of memory... */
2399 memcpy(pimg->label_buf + len, s + len, n - len + 1);
2401 return !ferror(pimg->fh);
2404 static int
2405 write_v8label(img *pimg, int opt, int common_flag, size_t common_val,
2406 const char *s)
2408 size_t len, del, add;
2410 /* find length of common prefix */
2411 for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2414 SVX_ASSERT(len <= pimg->label_len);
2415 del = pimg->label_len - len;
2416 add = strlen(s + len);
2418 if (add == common_val && del == common_val) {
2419 PUTC(opt | common_flag, pimg->fh);
2420 } else {
2421 PUTC(opt, pimg->fh);
2422 if (del <= 15 && add <= 15 && (del || add)) {
2423 PUTC((del << 4) | add, pimg->fh);
2424 } else {
2425 PUTC(0x00, pimg->fh);
2426 if (del < 0xff) {
2427 PUTC(del, pimg->fh);
2428 } else {
2429 PUTC(0xff, pimg->fh);
2430 put32(del, pimg->fh);
2432 if (add < 0xff) {
2433 PUTC(add, pimg->fh);
2434 } else {
2435 PUTC(0xff, pimg->fh);
2436 put32(add, pimg->fh);
2441 if (add)
2442 fwrite(s + len, add, 1, pimg->fh);
2444 pimg->label_len = len + add;
2445 if (add > del && !check_label_space(pimg, pimg->label_len + 1))
2446 return 0; /* FIXME: distinguish out of memory... */
2448 memcpy(pimg->label_buf + len, s + len, add + 1);
2450 return !ferror(pimg->fh);
2453 static void
2454 img_write_item_date_new(img *pimg)
2456 int same, unset;
2457 /* Only write dates when they've changed. */
2458 #if IMG_API_VERSION == 0
2459 if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2460 return;
2462 same = (pimg->date1 == pimg->date2);
2463 unset = (pimg->date1 == 0);
2464 #else /* IMG_API_VERSION == 1 */
2465 if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2466 return;
2468 same = (pimg->days1 == pimg->days2);
2469 unset = (pimg->days1 == -1);
2470 #endif
2472 if (same) {
2473 if (unset) {
2474 PUTC(0x10, pimg->fh);
2475 } else {
2476 PUTC(0x11, pimg->fh);
2477 #if IMG_API_VERSION == 0
2478 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2479 #else /* IMG_API_VERSION == 1 */
2480 put16(pimg->days1, pimg->fh);
2481 #endif
2483 } else {
2484 #if IMG_API_VERSION == 0
2485 int diff = (pimg->date2 - pimg->date1) / 86400;
2486 if (diff > 0 && diff <= 256) {
2487 PUTC(0x12, pimg->fh);
2488 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2489 PUTC(diff - 1, pimg->fh);
2490 } else {
2491 PUTC(0x13, pimg->fh);
2492 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2493 put16(pimg->date2 / 86400 + 25567, pimg->fh);
2495 #else /* IMG_API_VERSION == 1 */
2496 int diff = pimg->days2 - pimg->days1;
2497 if (diff > 0 && diff <= 256) {
2498 PUTC(0x12, pimg->fh);
2499 put16(pimg->days1, pimg->fh);
2500 PUTC(diff - 1, pimg->fh);
2501 } else {
2502 PUTC(0x13, pimg->fh);
2503 put16(pimg->days1, pimg->fh);
2504 put16(pimg->days2, pimg->fh);
2506 #endif
2508 #if IMG_API_VERSION == 0
2509 pimg->olddate1 = pimg->date1;
2510 pimg->olddate2 = pimg->date2;
2511 #else /* IMG_API_VERSION == 1 */
2512 pimg->olddays1 = pimg->days1;
2513 pimg->olddays2 = pimg->days2;
2514 #endif
2517 static void
2518 img_write_item_date(img *pimg)
2520 int same, unset;
2521 /* Only write dates when they've changed. */
2522 #if IMG_API_VERSION == 0
2523 if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2524 return;
2526 same = (pimg->date1 == pimg->date2);
2527 unset = (pimg->date1 == 0);
2528 #else /* IMG_API_VERSION == 1 */
2529 if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2530 return;
2532 same = (pimg->days1 == pimg->days2);
2533 unset = (pimg->days1 == -1);
2534 #endif
2536 if (same) {
2537 if (img_output_version < 7) {
2538 PUTC(0x20, pimg->fh);
2539 #if IMG_API_VERSION == 0
2540 put32(pimg->date1, pimg->fh);
2541 #else /* IMG_API_VERSION == 1 */
2542 put32((pimg->days1 - 25567) * 86400, pimg->fh);
2543 #endif
2544 } else {
2545 if (unset) {
2546 PUTC(0x24, pimg->fh);
2547 } else {
2548 PUTC(0x20, pimg->fh);
2549 #if IMG_API_VERSION == 0
2550 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2551 #else /* IMG_API_VERSION == 1 */
2552 put16(pimg->days1, pimg->fh);
2553 #endif
2556 } else {
2557 if (img_output_version < 7) {
2558 PUTC(0x21, pimg->fh);
2559 #if IMG_API_VERSION == 0
2560 put32(pimg->date1, pimg->fh);
2561 put32(pimg->date2, pimg->fh);
2562 #else /* IMG_API_VERSION == 1 */
2563 put32((pimg->days1 - 25567) * 86400, pimg->fh);
2564 put32((pimg->days2 - 25567) * 86400, pimg->fh);
2565 #endif
2566 } else {
2567 #if IMG_API_VERSION == 0
2568 int diff = (pimg->date2 - pimg->date1) / 86400;
2569 if (diff > 0 && diff <= 256) {
2570 PUTC(0x21, pimg->fh);
2571 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2572 PUTC(diff - 1, pimg->fh);
2573 } else {
2574 PUTC(0x23, pimg->fh);
2575 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2576 put16(pimg->date2 / 86400 + 25567, pimg->fh);
2578 #else /* IMG_API_VERSION == 1 */
2579 int diff = pimg->days2 - pimg->days1;
2580 if (diff > 0 && diff <= 256) {
2581 PUTC(0x21, pimg->fh);
2582 put16(pimg->days1, pimg->fh);
2583 PUTC(diff - 1, pimg->fh);
2584 } else {
2585 PUTC(0x23, pimg->fh);
2586 put16(pimg->days1, pimg->fh);
2587 put16(pimg->days2, pimg->fh);
2589 #endif
2592 #if IMG_API_VERSION == 0
2593 pimg->olddate1 = pimg->date1;
2594 pimg->olddate2 = pimg->date2;
2595 #else /* IMG_API_VERSION == 1 */
2596 pimg->olddays1 = pimg->days1;
2597 pimg->olddays2 = pimg->days2;
2598 #endif
2601 static void
2602 img_write_item_new(img *pimg, int code, int flags, const char *s,
2603 double x, double y, double z);
2604 static void
2605 img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2606 double x, double y, double z);
2607 static void
2608 img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2609 double x, double y, double z);
2611 void
2612 img_write_item(img *pimg, int code, int flags, const char *s,
2613 double x, double y, double z)
2615 if (!pimg) return;
2616 if (pimg->version >= 8) {
2617 img_write_item_new(pimg, code, flags, s, x, y, z);
2618 } else if (pimg->version >= 3) {
2619 img_write_item_v3to7(pimg, code, flags, s, x, y, z);
2620 } else {
2621 img_write_item_ancient(pimg, code, flags, s, x, y, z);
2625 static void
2626 img_write_item_new(img *pimg, int code, int flags, const char *s,
2627 double x, double y, double z)
2629 switch (code) {
2630 case img_LABEL:
2631 write_v8label(pimg, 0x80 | flags, 0, -1, s);
2632 break;
2633 case img_XSECT: {
2634 INT32_T l, r, u, d, max_dim;
2635 img_write_item_date_new(pimg);
2636 l = (INT32_T)my_lround(pimg->l * 100.0);
2637 r = (INT32_T)my_lround(pimg->r * 100.0);
2638 u = (INT32_T)my_lround(pimg->u * 100.0);
2639 d = (INT32_T)my_lround(pimg->d * 100.0);
2640 if (l < 0) l = -1;
2641 if (r < 0) r = -1;
2642 if (u < 0) u = -1;
2643 if (d < 0) d = -1;
2644 max_dim = max(max(l, r), max(u, d));
2645 flags = (flags & img_XFLAG_END) ? 1 : 0;
2646 if (max_dim >= 32768) flags |= 2;
2647 write_v8label(pimg, 0x30 | flags, 0, -1, s);
2648 if (flags & 2) {
2649 /* Big passage! Need to use 4 bytes. */
2650 put32(l, pimg->fh);
2651 put32(r, pimg->fh);
2652 put32(u, pimg->fh);
2653 put32(d, pimg->fh);
2654 } else {
2655 put16(l, pimg->fh);
2656 put16(r, pimg->fh);
2657 put16(u, pimg->fh);
2658 put16(d, pimg->fh);
2660 return;
2662 case img_MOVE:
2663 PUTC(15, pimg->fh);
2664 break;
2665 case img_LINE:
2666 img_write_item_date_new(pimg);
2667 if (pimg->style != pimg->oldstyle) {
2668 switch (pimg->style) {
2669 case img_STYLE_NORMAL:
2670 case img_STYLE_DIVING:
2671 case img_STYLE_CARTESIAN:
2672 case img_STYLE_CYLPOLAR:
2673 case img_STYLE_NOSURVEY:
2674 PUTC(pimg->style, pimg->fh);
2675 break;
2677 pimg->oldstyle = pimg->style;
2679 write_v8label(pimg, 0x40 | flags, 0x20, 0x00, s ? s : "");
2680 break;
2681 default: /* ignore for now */
2682 return;
2684 write_coord(pimg->fh, x, y, z);
2687 static void
2688 img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2689 double x, double y, double z)
2691 switch (code) {
2692 case img_LABEL:
2693 write_v3label(pimg, 0x40 | flags, s);
2694 break;
2695 case img_XSECT: {
2696 INT32_T l, r, u, d, max_dim;
2697 /* Need at least version 5 for img_XSECT. */
2698 if (pimg->version < 5) return;
2699 img_write_item_date(pimg);
2700 l = (INT32_T)my_lround(pimg->l * 100.0);
2701 r = (INT32_T)my_lround(pimg->r * 100.0);
2702 u = (INT32_T)my_lround(pimg->u * 100.0);
2703 d = (INT32_T)my_lround(pimg->d * 100.0);
2704 if (l < 0) l = -1;
2705 if (r < 0) r = -1;
2706 if (u < 0) u = -1;
2707 if (d < 0) d = -1;
2708 max_dim = max(max(l, r), max(u, d));
2709 flags = (flags & img_XFLAG_END) ? 1 : 0;
2710 if (max_dim >= 32768) flags |= 2;
2711 write_v3label(pimg, 0x30 | flags, s);
2712 if (flags & 2) {
2713 /* Big passage! Need to use 4 bytes. */
2714 put32(l, pimg->fh);
2715 put32(r, pimg->fh);
2716 put32(u, pimg->fh);
2717 put32(d, pimg->fh);
2718 } else {
2719 put16(l, pimg->fh);
2720 put16(r, pimg->fh);
2721 put16(u, pimg->fh);
2722 put16(d, pimg->fh);
2724 return;
2726 case img_MOVE:
2727 PUTC(15, pimg->fh);
2728 break;
2729 case img_LINE:
2730 if (pimg->version >= 4) {
2731 img_write_item_date(pimg);
2733 write_v3label(pimg, 0x80 | flags, s ? s : "");
2734 break;
2735 default: /* ignore for now */
2736 return;
2738 write_coord(pimg->fh, x, y, z);
2741 static void
2742 img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2743 double x, double y, double z)
2745 size_t len;
2746 INT32_T opt = 0;
2747 SVX_ASSERT(pimg->version > 0);
2748 switch (code) {
2749 case img_LABEL:
2750 if (pimg->version == 1) {
2751 /* put a move before each label */
2752 img_write_item_ancient(pimg, img_MOVE, 0, NULL, x, y, z);
2753 put32(2, pimg->fh);
2754 fputsnl(s, pimg->fh);
2755 return;
2757 len = strlen(s);
2758 if (len > 255 || strchr(s, '\n')) {
2759 /* long label - not in early incarnations of v2 format, but few
2760 * 3d files will need these, so better not to force incompatibility
2761 * with a new version I think... */
2762 PUTC(7, pimg->fh);
2763 PUTC(flags, pimg->fh);
2764 put32(len, pimg->fh);
2765 fputs(s, pimg->fh);
2766 } else {
2767 PUTC(0x40 | (flags & 0x3f), pimg->fh);
2768 fputsnl(s, pimg->fh);
2770 opt = 0;
2771 break;
2772 case img_MOVE:
2773 opt = 4;
2774 break;
2775 case img_LINE:
2776 if (pimg->version > 1) {
2777 opt = 0x80 | (flags & 0x3f);
2778 break;
2780 opt = 5;
2781 break;
2782 default: /* ignore for now */
2783 return;
2785 if (pimg->version == 1) {
2786 put32(opt, pimg->fh);
2787 } else {
2788 if (opt) PUTC(opt, pimg->fh);
2790 write_coord(pimg->fh, x, y, z);
2793 /* Write error information for the current traverse
2794 * n_legs is the number of legs in the traverse
2795 * length is the traverse length (in m)
2796 * E is the ratio of the observed misclosure to the theoretical one
2797 * H is the ratio of the observed horizontal misclosure to the theoretical one
2798 * V is the ratio of the observed vertical misclosure to the theoretical one
2800 void
2801 img_write_errors(img *pimg, int n_legs, double length,
2802 double E, double H, double V)
2804 PUTC((pimg->version >= 8 ? 0x1f : 0x22), pimg->fh);
2805 put32(n_legs, pimg->fh);
2806 put32((INT32_T)my_lround(length * 100.0), pimg->fh);
2807 put32((INT32_T)my_lround(E * 100.0), pimg->fh);
2808 put32((INT32_T)my_lround(H * 100.0), pimg->fh);
2809 put32((INT32_T)my_lround(V * 100.0), pimg->fh);
2813 img_close(img *pimg)
2815 int result = 1;
2816 if (pimg) {
2817 if (pimg->fh) {
2818 if (pimg->fRead) {
2819 osfree(pimg->survey);
2820 osfree(pimg->title);
2821 osfree(pimg->cs);
2822 osfree(pimg->datestamp);
2823 } else {
2824 /* write end of data marker */
2825 switch (pimg->version) {
2826 case 1:
2827 put32((INT32_T)-1, pimg->fh);
2828 break;
2829 default:
2830 if (pimg->version <= 7 ?
2831 (pimg->label_len != 0) :
2832 (pimg->style != img_STYLE_NORMAL)) {
2833 PUTC(0, pimg->fh);
2835 /* FALL THROUGH */
2836 case 2:
2837 PUTC(0, pimg->fh);
2838 break;
2841 if (ferror(pimg->fh)) result = 0;
2842 if (pimg->close_func && pimg->close_func(pimg->fh))
2843 result = 0;
2844 if (!result) img_errno = pimg->fRead ? IMG_READERROR : IMG_WRITEERROR;
2846 osfree(pimg->label_buf);
2847 osfree(pimg->filename_opened);
2848 osfree(pimg);
2850 return result;