Port to proj.h API
[survex.git] / src / img.c
blob925ac9bb7b9440010a44a8e06a4873f250a71638
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.
861 if (*p == '\0' || strcmp(p, " +no_defs") == 0) {
862 int i;
863 cs = cs + 6;
864 for (i = 0; i < 4; ++i) {
865 cs[i] = toupper(cs[i]);
867 *p = '\0';
871 pimg->cs = my_strdup(cs);
874 if (!pimg->title) {
875 pimg->title = title;
876 } else {
877 osfree(title);
880 pimg->datestamp = getline_alloc(pimg->fh);
881 if (!pimg->title || !pimg->datestamp) {
882 img_errno = IMG_OUTOFMEMORY;
883 error:
884 osfree(pimg->title);
885 osfree(pimg->cs);
886 osfree(pimg->datestamp);
887 osfree(pimg->filename_opened);
888 if (pimg->close_func) pimg->close_func(pimg->fh);
889 osfree(pimg);
890 return NULL;
893 if (pimg->version >= 8) {
894 int flags = GETC(pimg->fh);
895 if (flags & img_FFLAG_EXTENDED) pimg->is_extended_elevation = 1;
896 } else {
897 len = strlen(pimg->title);
898 if (len > 11 && strcmp(pimg->title + len - 11, " (extended)") == 0) {
899 pimg->title[len - 11] = '\0';
900 pimg->is_extended_elevation = 1;
904 if (pimg->datestamp[0] == '@') {
905 unsigned long v;
906 char * p;
907 errno = 0;
908 v = strtoul(pimg->datestamp + 1, &p, 10);
909 if (errno == 0 && *p == '\0')
910 pimg->datestamp_numeric = v;
911 /* FIXME: We're assuming here that the C time_t epoch is 1970, which is
912 * true for Unix-like systems, macOS and Windows, but isn't guaranteed
913 * by ISO C.
915 } else {
916 /* %a,%Y.%m.%d %H:%M:%S %Z */
917 struct tm tm;
918 unsigned long v;
919 char * p = pimg->datestamp;
920 while (isalpha((unsigned char)*p)) ++p;
921 if (*p == ',') ++p;
922 while (isspace((unsigned char)*p)) ++p;
923 v = strtoul(p, &p, 10);
924 if (v == ULONG_MAX || *p++ != '.')
925 goto bad_3d_date;
926 tm.tm_year = v - 1900;
927 v = strtoul(p, &p, 10);
928 if (v < 1 || v > 12 || *p++ != '.')
929 goto bad_3d_date;
930 tm.tm_mon = v - 1;
931 v = strtoul(p, &p, 10);
932 if (v < 1 || v > 31 || *p++ != ' ')
933 goto bad_3d_date;
934 tm.tm_mday = v;
935 v = strtoul(p, &p, 10);
936 if (v >= 24 || *p++ != ':')
937 goto bad_3d_date;
938 tm.tm_hour = v;
939 v = strtoul(p, &p, 10);
940 if (v >= 60 || *p++ != ':')
941 goto bad_3d_date;
942 tm.tm_min = v;
943 v = strtoul(p, &p, 10);
944 if (v > 60)
945 goto bad_3d_date;
946 tm.tm_sec = v;
947 tm.tm_isdst = 0;
948 while (isspace((unsigned char)*p)) ++p;
949 /* p now points to the timezone string.
951 * However, it's likely to be a string like "BST", and such strings can
952 * be ambiguous (BST could be UTC+1 or UTC+6), so it is impossible to
953 * reliably convert in all cases. Just pass what we have to tzset() - if
954 * it doesn't handle it, UTC will be used.
956 pimg->datestamp_numeric = mktime_with_tz(&tm, p);
958 bad_3d_date:
960 pimg->start = ftell(pimg->fh);
962 return pimg;
966 img_rewind(img *pimg)
968 if (!pimg->fRead) {
969 img_errno = IMG_WRITEERROR;
970 return 0;
972 if (fseek(pimg->fh, pimg->start, SEEK_SET) != 0) {
973 img_errno = IMG_READERROR;
974 return 0;
976 clearerr(pimg->fh);
977 /* [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
978 * [version 0] not in the middle of a 'LINE' command
979 * [version >= 3] not in the middle of turning a LINE into a MOVE */
980 pimg->pending = 0;
982 img_errno = IMG_NONE;
984 /* for version >= 3 we use label_buf to store the prefix for reuse */
985 /* for VERSION_COMPASS_PLT, 0 value indicates we haven't entered a survey
986 * yet */
987 /* for VERSION_CMAP_SHOT, we store the last station here to detect whether
988 * we MOVE or LINE */
989 pimg->label_len = 0;
990 pimg->style = img_STYLE_UNKNOWN;
991 return 1;
994 img *
995 img_open_write_cs(const char *fnm, const char *title, const char *cs, int flags)
997 if (fDirectory(fnm)) {
998 img_errno = IMG_DIRECTORY;
999 return NULL;
1002 return img_write_stream(fopen(fnm, "wb"), fclose, title, cs, flags);
1005 img *
1006 img_write_stream(FILE *stream, int (*close_func)(FILE*),
1007 const char *title, const char *cs, int flags)
1009 time_t tm;
1010 img *pimg;
1012 if (stream == NULL) {
1013 img_errno = IMG_FILENOTFOUND;
1014 return NULL;
1017 pimg = osnew(img);
1018 if (pimg == NULL) {
1019 img_errno = IMG_OUTOFMEMORY;
1020 if (close_func) close_func(stream);
1021 return NULL;
1024 pimg->fh = stream;
1025 pimg->close_func = close_func;
1026 pimg->buf_len = 257;
1027 pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
1028 if (!pimg->label_buf) {
1029 if (pimg->close_func) pimg->close_func(pimg->fh);
1030 osfree(pimg);
1031 img_errno = IMG_OUTOFMEMORY;
1032 return NULL;
1035 pimg->filename_opened = NULL;
1037 /* Output image file header */
1038 fputs("Survex 3D Image File\n", pimg->fh); /* file identifier string */
1039 if (img_output_version < 2) {
1040 pimg->version = 1;
1041 fputs("Bv0.01\n", pimg->fh); /* binary file format version number */
1042 } else {
1043 pimg->version = (img_output_version > IMG_VERSION_MAX) ? IMG_VERSION_MAX : img_output_version;
1044 fprintf(pimg->fh, "v%d\n", pimg->version); /* file format version no. */
1047 fputs(title, pimg->fh);
1048 if (pimg->version < 8 && (flags & img_FFLAG_EXTENDED)) {
1049 /* Older format versions append " (extended)" to the title to mark
1050 * extended elevations. */
1051 size_t len = strlen(title);
1052 if (len < 11 || strcmp(title + len - 11, " (extended)") != 0)
1053 fputs(" (extended)", pimg->fh);
1055 if (pimg->version == 8 && cs && *cs) {
1056 /* We sneak in an extra field after a zero byte here, containing the
1057 * specified coordinate system (if any). Older readers will just not
1058 * see it (which is fine), and this trick avoids us having to bump the
1059 * 3d format version.
1061 PUTC('\0', pimg->fh);
1062 fputs(cs, pimg->fh);
1064 PUTC('\n', pimg->fh);
1066 tm = time(NULL);
1067 if (tm == (time_t)-1) {
1068 fputsnl(TIMENA, pimg->fh);
1069 } else if (pimg->version <= 7) {
1070 char date[256];
1071 /* output current date and time in format specified */
1072 strftime(date, 256, TIMEFMT, localtime(&tm));
1073 fputsnl(date, pimg->fh);
1074 } else {
1075 fprintf(pimg->fh, "@%ld\n", (long)tm);
1078 if (pimg->version >= 8) {
1079 /* Clear bit one in case anyone has been passing true for fBinary. */
1080 flags &=~ 1;
1081 PUTC(flags, pimg->fh);
1084 #if 0
1085 if (img_output_version >= 5) {
1086 static const unsigned char codelengths[32] = {
1087 4, 8, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1088 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1090 fwrite(codelengths, 32, 1, pimg->fh);
1092 #endif
1093 pimg->fRead = 0; /* writing to this file */
1094 img_errno = IMG_NONE;
1096 /* for version >= 3 we use label_buf to store the prefix for reuse */
1097 pimg->label_buf[0] = '\0';
1098 pimg->label_len = 0;
1100 #if IMG_API_VERSION == 0
1101 pimg->date1 = pimg->date2 = 0;
1102 pimg->olddate1 = pimg->olddate2 = 0;
1103 #else /* IMG_API_VERSION == 1 */
1104 pimg->days1 = pimg->days2 = -1;
1105 pimg->olddays1 = pimg->olddays2 = -1;
1106 #endif
1107 pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
1109 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1111 pimg->n_legs = 0;
1112 pimg->length = 0.0;
1113 pimg->E = pimg->H = pimg->V = 0.0;
1115 /* Don't check for write errors now - let img_close() report them... */
1116 return pimg;
1119 static void
1120 read_xyz_station_coords(img_point *pt, const char *line)
1122 char num[12];
1123 memcpy(num, line + 6, 9);
1124 num[9] = '\0';
1125 pt->x = atof(num) / METRES_PER_FOOT;
1126 memcpy(num, line + 15, 9);
1127 pt->y = atof(num) / METRES_PER_FOOT;
1128 memcpy(num, line + 24, 8);
1129 num[8] = '\0';
1130 pt->z = atof(num) / METRES_PER_FOOT;
1133 static void
1134 read_xyz_shot_coords(img_point *pt, const char *line)
1136 char num[12];
1137 memcpy(num, line + 40, 10);
1138 num[10] = '\0';
1139 pt->x = atof(num) / METRES_PER_FOOT;
1140 memcpy(num, line + 50, 10);
1141 pt->y = atof(num) / METRES_PER_FOOT;
1142 memcpy(num, line + 60, 9);
1143 num[9] = '\0';
1144 pt->z = atof(num) / METRES_PER_FOOT;
1147 static void
1148 subtract_xyz_shot_deltas(img_point *pt, const char *line)
1150 char num[12];
1151 memcpy(num, line + 15, 9);
1152 num[9] = '\0';
1153 pt->x -= atof(num) / METRES_PER_FOOT;
1154 memcpy(num, line + 24, 8);
1155 num[8] = '\0';
1156 pt->y -= atof(num) / METRES_PER_FOOT;
1157 memcpy(num, line + 32, 8);
1158 pt->z -= atof(num) / METRES_PER_FOOT;
1161 static int
1162 read_coord(FILE *fh, img_point *pt)
1164 SVX_ASSERT(fh);
1165 SVX_ASSERT(pt);
1166 pt->x = get32(fh) / 100.0;
1167 pt->y = get32(fh) / 100.0;
1168 pt->z = get32(fh) / 100.0;
1169 if (ferror(fh) || feof(fh)) {
1170 img_errno = feof(fh) ? IMG_BADFORMAT : IMG_READERROR;
1171 return 0;
1173 return 1;
1176 static int
1177 skip_coord(FILE *fh)
1179 return (fseek(fh, 12, SEEK_CUR) == 0);
1182 static int
1183 read_v3label(img *pimg)
1185 char *q;
1186 long len = GETC(pimg->fh);
1187 if (len == EOF) {
1188 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1189 return img_BAD;
1191 if (len == 0xfe) {
1192 len += get16(pimg->fh);
1193 if (feof(pimg->fh)) {
1194 img_errno = IMG_BADFORMAT;
1195 return img_BAD;
1197 if (ferror(pimg->fh)) {
1198 img_errno = IMG_READERROR;
1199 return img_BAD;
1201 } else if (len == 0xff) {
1202 len = get32(pimg->fh);
1203 if (ferror(pimg->fh)) {
1204 img_errno = IMG_READERROR;
1205 return img_BAD;
1207 if (feof(pimg->fh) || len < 0xfe + 0xffff) {
1208 img_errno = IMG_BADFORMAT;
1209 return img_BAD;
1213 if (!check_label_space(pimg, pimg->label_len + len + 1)) {
1214 img_errno = IMG_OUTOFMEMORY;
1215 return img_BAD;
1217 q = pimg->label_buf + pimg->label_len;
1218 pimg->label_len += len;
1219 if (len && fread(q, len, 1, pimg->fh) != 1) {
1220 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1221 return img_BAD;
1223 q[len] = '\0';
1224 return 0;
1227 static int
1228 read_v8label(img *pimg, int common_flag, size_t common_val)
1230 char *q;
1231 size_t del, add;
1232 if (common_flag) {
1233 if (common_val == 0) return 0;
1234 add = del = common_val;
1235 } else {
1236 int ch = GETC(pimg->fh);
1237 if (ch == EOF) {
1238 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1239 return img_BAD;
1241 if (ch != 0x00) {
1242 del = ch >> 4;
1243 add = ch & 0x0f;
1244 } else {
1245 ch = GETC(pimg->fh);
1246 if (ch == EOF) {
1247 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1248 return img_BAD;
1250 if (ch != 0xff) {
1251 del = ch;
1252 } else {
1253 del = get32(pimg->fh);
1254 if (ferror(pimg->fh)) {
1255 img_errno = IMG_READERROR;
1256 return img_BAD;
1259 ch = GETC(pimg->fh);
1260 if (ch == EOF) {
1261 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1262 return img_BAD;
1264 if (ch != 0xff) {
1265 add = ch;
1266 } else {
1267 add = get32(pimg->fh);
1268 if (ferror(pimg->fh)) {
1269 img_errno = IMG_READERROR;
1270 return img_BAD;
1275 if (add > del && !check_label_space(pimg, pimg->label_len + add - del + 1)) {
1276 img_errno = IMG_OUTOFMEMORY;
1277 return img_BAD;
1280 if (del > pimg->label_len) {
1281 img_errno = IMG_BADFORMAT;
1282 return img_BAD;
1284 pimg->label_len -= del;
1285 q = pimg->label_buf + pimg->label_len;
1286 pimg->label_len += add;
1287 if (add && fread(q, add, 1, pimg->fh) != 1) {
1288 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1289 return img_BAD;
1291 q[add] = '\0';
1292 return 0;
1295 static int img_read_item_new(img *pimg, img_point *p);
1296 static int img_read_item_v3to7(img *pimg, img_point *p);
1297 static int img_read_item_ancient(img *pimg, img_point *p);
1298 static int img_read_item_ascii_wrapper(img *pimg, img_point *p);
1299 static int img_read_item_ascii(img *pimg, img_point *p);
1302 img_read_item(img *pimg, img_point *p)
1304 pimg->flags = 0;
1306 if (pimg->version >= 8) {
1307 return img_read_item_new(pimg, p);
1308 } else if (pimg->version >= 3) {
1309 return img_read_item_v3to7(pimg, p);
1310 } else if (pimg->version >= 1) {
1311 return img_read_item_ancient(pimg, p);
1312 } else {
1313 return img_read_item_ascii_wrapper(pimg, p);
1317 static int
1318 img_read_item_new(img *pimg, img_point *p)
1320 int result;
1321 int opt;
1322 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1323 if (pimg->pending >= 0x40) {
1324 if (pimg->pending == 256) {
1325 pimg->pending = 0;
1326 return img_XSECT_END;
1328 *p = pimg->mv;
1329 pimg->flags = (int)(pimg->pending) & 0x3f;
1330 pimg->pending = 0;
1331 return img_LINE;
1333 again3: /* label to goto if we get a prefix, date, or lrud */
1334 pimg->label = pimg->label_buf;
1335 opt = GETC(pimg->fh);
1336 if (opt == EOF) {
1337 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1338 return img_BAD;
1340 if (opt >> 6 == 0) {
1341 if (opt <= 4) {
1342 if (opt == 0 && pimg->style == 0)
1343 return img_STOP; /* end of data marker */
1344 /* STYLE */
1345 pimg->style = opt;
1346 goto again3;
1348 if (opt >= 0x10) {
1349 switch (opt) {
1350 case 0x10: { /* No date info */
1351 #if IMG_API_VERSION == 0
1352 pimg->date1 = pimg->date2 = 0;
1353 #else /* IMG_API_VERSION == 1 */
1354 pimg->days1 = pimg->days2 = -1;
1355 #endif
1356 break;
1358 case 0x11: { /* Single date */
1359 int days1 = (int)getu16(pimg->fh);
1360 #if IMG_API_VERSION == 0
1361 pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
1362 #else /* IMG_API_VERSION == 1 */
1363 pimg->days2 = pimg->days1 = days1;
1364 #endif
1365 break;
1367 case 0x12: { /* Date range (short) */
1368 int days1 = (int)getu16(pimg->fh);
1369 int days2 = days1 + GETC(pimg->fh) + 1;
1370 #if IMG_API_VERSION == 0
1371 pimg->date1 = (days1 - 25567) * 86400;
1372 pimg->date2 = (days2 - 25567) * 86400;
1373 #else /* IMG_API_VERSION == 1 */
1374 pimg->days1 = days1;
1375 pimg->days2 = days2;
1376 #endif
1377 break;
1379 case 0x13: { /* Date range (long) */
1380 int days1 = (int)getu16(pimg->fh);
1381 int days2 = (int)getu16(pimg->fh);
1382 #if IMG_API_VERSION == 0
1383 pimg->date1 = (days1 - 25567) * 86400;
1384 pimg->date2 = (days2 - 25567) * 86400;
1385 #else /* IMG_API_VERSION == 1 */
1386 pimg->days1 = days1;
1387 pimg->days2 = days2;
1388 #endif
1389 break;
1391 case 0x1f: /* Error info */
1392 pimg->n_legs = get32(pimg->fh);
1393 pimg->length = get32(pimg->fh) / 100.0;
1394 pimg->E = get32(pimg->fh) / 100.0;
1395 pimg->H = get32(pimg->fh) / 100.0;
1396 pimg->V = get32(pimg->fh) / 100.0;
1397 return img_ERROR_INFO;
1398 case 0x30: case 0x31: /* LRUD */
1399 case 0x32: case 0x33: /* Big LRUD! */
1400 if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1401 pimg->flags = (int)opt & 0x01;
1402 if (opt < 0x32) {
1403 pimg->l = get16(pimg->fh) / 100.0;
1404 pimg->r = get16(pimg->fh) / 100.0;
1405 pimg->u = get16(pimg->fh) / 100.0;
1406 pimg->d = get16(pimg->fh) / 100.0;
1407 } else {
1408 pimg->l = get32(pimg->fh) / 100.0;
1409 pimg->r = get32(pimg->fh) / 100.0;
1410 pimg->u = get32(pimg->fh) / 100.0;
1411 pimg->d = get32(pimg->fh) / 100.0;
1413 if (!stn_included(pimg)) {
1414 return img_XSECT_END;
1416 /* If this is the last cross-section in this passage, set
1417 * pending so we return img_XSECT_END next time. */
1418 if (pimg->flags & 0x01) {
1419 pimg->pending = 256;
1420 pimg->flags &= ~0x01;
1422 return img_XSECT;
1423 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1424 img_errno = IMG_BADFORMAT;
1425 return img_BAD;
1427 goto again3;
1429 if (opt != 15) {
1430 /* 1-14 and 16-31 reserved */
1431 img_errno = IMG_BADFORMAT;
1432 return img_BAD;
1434 result = img_MOVE;
1435 } else if (opt >= 0x80) {
1436 if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1438 result = img_LABEL;
1440 if (!stn_included(pimg)) {
1441 if (!skip_coord(pimg->fh)) return img_BAD;
1442 pimg->pending = 0;
1443 goto again3;
1446 pimg->flags = (int)opt & 0x7f;
1447 } else if ((opt >> 6) == 1) {
1448 if (read_v8label(pimg, opt & 0x20, 0) == img_BAD) return img_BAD;
1450 result = img_LINE;
1452 if (!survey_included(pimg)) {
1453 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1454 pimg->pending = 15;
1455 goto again3;
1458 if (pimg->pending) {
1459 *p = pimg->mv;
1460 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1461 pimg->pending = opt;
1462 return img_MOVE;
1464 pimg->flags = (int)opt & 0x1f;
1465 } else {
1466 img_errno = IMG_BADFORMAT;
1467 return img_BAD;
1469 if (!read_coord(pimg->fh, p)) return img_BAD;
1470 pimg->pending = 0;
1471 return result;
1474 static int
1475 img_read_item_v3to7(img *pimg, img_point *p)
1477 int result;
1478 int opt;
1479 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1480 if (pimg->pending == 256) {
1481 pimg->pending = 0;
1482 return img_XSECT_END;
1484 if (pimg->pending >= 0x80) {
1485 *p = pimg->mv;
1486 pimg->flags = (int)(pimg->pending) & 0x3f;
1487 pimg->pending = 0;
1488 return img_LINE;
1490 again3: /* label to goto if we get a prefix, date, or lrud */
1491 pimg->label = pimg->label_buf;
1492 opt = GETC(pimg->fh);
1493 if (opt == EOF) {
1494 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1495 return img_BAD;
1497 switch (opt >> 6) {
1498 case 0:
1499 if (opt == 0) {
1500 if (!pimg->label_len) return img_STOP; /* end of data marker */
1501 pimg->label_len = 0;
1502 goto again3;
1504 if (opt < 15) {
1505 /* 1-14 mean trim that many levels from current prefix */
1506 int c;
1507 if (pimg->label_len <= 17) {
1508 /* zero prefix using "0" */
1509 img_errno = IMG_BADFORMAT;
1510 return img_BAD;
1512 /* extra - 1 because label_len points to one past the end */
1513 c = pimg->label_len - 17 - 1;
1514 while (pimg->label_buf[c] != '.' || --opt > 0) {
1515 if (--c < 0) {
1516 /* zero prefix using "0" */
1517 img_errno = IMG_BADFORMAT;
1518 return img_BAD;
1521 c++;
1522 pimg->label_len = c;
1523 goto again3;
1525 if (opt == 15) {
1526 result = img_MOVE;
1527 break;
1529 if (opt >= 0x20) {
1530 switch (opt) {
1531 case 0x20: /* Single date */
1532 if (pimg->version < 7) {
1533 int date1 = get32(pimg->fh);
1534 #if IMG_API_VERSION == 0
1535 pimg->date2 = pimg->date1 = date1;
1536 #else /* IMG_API_VERSION == 1 */
1537 if (date1 != 0) {
1538 pimg->days2 = pimg->days1 = (date1 / 86400) + 25567;
1539 } else {
1540 pimg->days2 = pimg->days1 = -1;
1542 #endif
1543 } else {
1544 int days1 = (int)getu16(pimg->fh);
1545 #if IMG_API_VERSION == 0
1546 pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
1547 #else /* IMG_API_VERSION == 1 */
1548 pimg->days2 = pimg->days1 = days1;
1549 #endif
1551 break;
1552 case 0x21: /* Date range (short for v7+) */
1553 if (pimg->version < 7) {
1554 INT32_T date1 = get32(pimg->fh);
1555 INT32_T date2 = get32(pimg->fh);
1556 #if IMG_API_VERSION == 0
1557 pimg->date1 = date1;
1558 pimg->date2 = date2;
1559 #else /* IMG_API_VERSION == 1 */
1560 pimg->days1 = (date1 / 86400) + 25567;
1561 pimg->days2 = (date2 / 86400) + 25567;
1562 #endif
1563 } else {
1564 int days1 = (int)getu16(pimg->fh);
1565 int days2 = days1 + GETC(pimg->fh) + 1;
1566 #if IMG_API_VERSION == 0
1567 pimg->date1 = (days1 - 25567) * 86400;
1568 pimg->date2 = (days2 - 25567) * 86400;
1569 #else /* IMG_API_VERSION == 1 */
1570 pimg->days1 = days1;
1571 pimg->days2 = days2;
1572 #endif
1574 break;
1575 case 0x22: /* Error info */
1576 pimg->n_legs = get32(pimg->fh);
1577 pimg->length = get32(pimg->fh) / 100.0;
1578 pimg->E = get32(pimg->fh) / 100.0;
1579 pimg->H = get32(pimg->fh) / 100.0;
1580 pimg->V = get32(pimg->fh) / 100.0;
1581 if (feof(pimg->fh)) {
1582 img_errno = IMG_BADFORMAT;
1583 return img_BAD;
1585 if (ferror(pimg->fh)) {
1586 img_errno = IMG_READERROR;
1587 return img_BAD;
1589 return img_ERROR_INFO;
1590 case 0x23: { /* v7+: Date range (long) */
1591 if (pimg->version < 7) {
1592 img_errno = IMG_BADFORMAT;
1593 return img_BAD;
1595 int days1 = (int)getu16(pimg->fh);
1596 int days2 = (int)getu16(pimg->fh);
1597 if (feof(pimg->fh)) {
1598 img_errno = IMG_BADFORMAT;
1599 return img_BAD;
1601 if (ferror(pimg->fh)) {
1602 img_errno = IMG_READERROR;
1603 return img_BAD;
1605 #if IMG_API_VERSION == 0
1606 pimg->date1 = (days1 - 25567) * 86400;
1607 pimg->date2 = (days2 - 25567) * 86400;
1608 #else /* IMG_API_VERSION == 1 */
1609 pimg->days1 = days1;
1610 pimg->days2 = days2;
1611 #endif
1612 break;
1614 case 0x24: { /* v7+: No date info */
1615 #if IMG_API_VERSION == 0
1616 pimg->date1 = pimg->date2 = 0;
1617 #else /* IMG_API_VERSION == 1 */
1618 pimg->days1 = pimg->days2 = -1;
1619 #endif
1620 break;
1622 case 0x30: case 0x31: /* LRUD */
1623 case 0x32: case 0x33: /* Big LRUD! */
1624 if (read_v3label(pimg) == img_BAD) return img_BAD;
1625 pimg->flags = (int)opt & 0x01;
1626 if (opt < 0x32) {
1627 pimg->l = get16(pimg->fh) / 100.0;
1628 pimg->r = get16(pimg->fh) / 100.0;
1629 pimg->u = get16(pimg->fh) / 100.0;
1630 pimg->d = get16(pimg->fh) / 100.0;
1631 } else {
1632 pimg->l = get32(pimg->fh) / 100.0;
1633 pimg->r = get32(pimg->fh) / 100.0;
1634 pimg->u = get32(pimg->fh) / 100.0;
1635 pimg->d = get32(pimg->fh) / 100.0;
1637 if (feof(pimg->fh)) {
1638 img_errno = IMG_BADFORMAT;
1639 return img_BAD;
1641 if (ferror(pimg->fh)) {
1642 img_errno = IMG_READERROR;
1643 return img_BAD;
1645 if (!stn_included(pimg)) {
1646 return img_XSECT_END;
1648 /* If this is the last cross-section in this passage, set
1649 * pending so we return img_XSECT_END next time. */
1650 if (pimg->flags & 0x01) {
1651 pimg->pending = 256;
1652 pimg->flags &= ~0x01;
1654 return img_XSECT;
1655 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1656 img_errno = IMG_BADFORMAT;
1657 return img_BAD;
1659 if (feof(pimg->fh)) {
1660 img_errno = IMG_BADFORMAT;
1661 return img_BAD;
1663 if (ferror(pimg->fh)) {
1664 img_errno = IMG_READERROR;
1665 return img_BAD;
1667 goto again3;
1669 /* 16-31 mean remove (n - 15) characters from the prefix */
1670 /* zero prefix using 0 */
1671 if (pimg->label_len <= (size_t)(opt - 15)) {
1672 img_errno = IMG_BADFORMAT;
1673 return img_BAD;
1675 pimg->label_len -= (opt - 15);
1676 goto again3;
1677 case 1:
1678 if (read_v3label(pimg) == img_BAD) return img_BAD;
1680 result = img_LABEL;
1682 if (!stn_included(pimg)) {
1683 if (!skip_coord(pimg->fh)) return img_BAD;
1684 pimg->pending = 0;
1685 goto again3;
1688 pimg->flags = (int)opt & 0x3f;
1689 break;
1690 case 2:
1691 if (read_v3label(pimg) == img_BAD) return img_BAD;
1693 result = img_LINE;
1695 if (!survey_included(pimg)) {
1696 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1697 pimg->pending = 15;
1698 goto again3;
1701 if (pimg->pending) {
1702 *p = pimg->mv;
1703 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1704 pimg->pending = opt;
1705 return img_MOVE;
1707 pimg->flags = (int)opt & 0x3f;
1708 break;
1709 default:
1710 img_errno = IMG_BADFORMAT;
1711 return img_BAD;
1713 if (!read_coord(pimg->fh, p)) return img_BAD;
1714 pimg->pending = 0;
1715 return result;
1718 static int
1719 img_read_item_ancient(img *pimg, img_point *p)
1721 int result;
1722 static long opt_lookahead = 0;
1723 static img_point pt = { 0.0, 0.0, 0.0 };
1724 long opt;
1726 again: /* label to goto if we get a cross */
1727 pimg->label = pimg->label_buf;
1728 pimg->label[0] = '\0';
1730 if (pimg->version == 1) {
1731 if (opt_lookahead) {
1732 opt = opt_lookahead;
1733 opt_lookahead = 0;
1734 } else {
1735 opt = get32(pimg->fh);
1737 } else {
1738 opt = GETC(pimg->fh);
1741 if (feof(pimg->fh)) {
1742 img_errno = IMG_BADFORMAT;
1743 return img_BAD;
1745 if (ferror(pimg->fh)) {
1746 img_errno = IMG_READERROR;
1747 return img_BAD;
1750 switch (opt) {
1751 case -1: case 0:
1752 return img_STOP; /* end of data marker */
1753 case 1:
1754 /* skip coordinates */
1755 if (!skip_coord(pimg->fh)) {
1756 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1757 return img_BAD;
1759 goto again;
1760 case 2: case 3: {
1761 size_t len;
1762 result = img_LABEL;
1763 if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
1764 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1765 return img_BAD;
1767 if (pimg->label[0] == '\\') pimg->label++;
1768 len = strlen(pimg->label);
1769 if (len == 0 || pimg->label[len - 1] != '\n') {
1770 img_errno = IMG_BADFORMAT;
1771 return img_BAD;
1773 /* Ignore empty labels in some .3d files (caused by a bug) */
1774 if (len == 1) goto again;
1775 pimg->label[len - 1] = '\0';
1776 pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1777 if (opt == 2) goto done;
1778 break;
1780 case 6: case 7: {
1781 long len;
1782 result = img_LABEL;
1784 if (opt == 7)
1785 pimg->flags = GETC(pimg->fh);
1786 else
1787 pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1789 len = get32(pimg->fh);
1791 if (feof(pimg->fh)) {
1792 img_errno = IMG_BADFORMAT;
1793 return img_BAD;
1795 if (ferror(pimg->fh)) {
1796 img_errno = IMG_READERROR;
1797 return img_BAD;
1800 /* Ignore empty labels in some .3d files (caused by a bug) */
1801 if (len == 0) goto again;
1802 if (!check_label_space(pimg, len + 1)) {
1803 img_errno = IMG_OUTOFMEMORY;
1804 return img_BAD;
1806 if (fread(pimg->label_buf, len, 1, pimg->fh) != 1) {
1807 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1808 return img_BAD;
1810 pimg->label_buf[len] = '\0';
1811 break;
1813 case 4:
1814 result = img_MOVE;
1815 break;
1816 case 5:
1817 result = img_LINE;
1818 break;
1819 default:
1820 switch ((int)opt & 0xc0) {
1821 case 0x80:
1822 pimg->flags = (int)opt & 0x3f;
1823 result = img_LINE;
1824 break;
1825 case 0x40: {
1826 char *q;
1827 pimg->flags = (int)opt & 0x3f;
1828 result = img_LABEL;
1829 if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
1830 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1831 return img_BAD;
1833 q = pimg->label_buf + strlen(pimg->label_buf) - 1;
1834 /* Ignore empty-labels in some .3d files (caused by a bug) */
1835 if (q == pimg->label_buf) goto again;
1836 if (*q != '\n') {
1837 img_errno = IMG_BADFORMAT;
1838 return img_BAD;
1840 *q = '\0';
1841 break;
1843 default:
1844 img_errno = IMG_BADFORMAT;
1845 return img_BAD;
1847 break;
1850 if (!read_coord(pimg->fh, &pt)) return img_BAD;
1852 if (result == img_LABEL && !stn_included(pimg)) {
1853 goto again;
1856 done:
1857 *p = pt;
1859 if (result == img_MOVE && pimg->version == 1) {
1860 /* peek at next code and see if it's an old-style label */
1861 opt_lookahead = get32(pimg->fh);
1863 if (feof(pimg->fh)) {
1864 img_errno = IMG_BADFORMAT;
1865 return img_BAD;
1867 if (ferror(pimg->fh)) {
1868 img_errno = IMG_READERROR;
1869 return img_BAD;
1872 if (opt_lookahead == 2) return img_read_item_ancient(pimg, p);
1875 return result;
1878 static int
1879 img_read_item_ascii_wrapper(img *pimg, img_point *p)
1881 /* We need to set the default locale for fscanf() to work on
1882 * numbers with "." as decimal point. */
1883 int result;
1884 char * current_locale = my_strdup(setlocale(LC_NUMERIC, NULL));
1885 setlocale(LC_NUMERIC, "C");
1886 result = img_read_item_ascii(pimg, p);
1887 setlocale(LC_NUMERIC, current_locale);
1888 free(current_locale);
1889 return result;
1892 /* Handle all ASCII formats. */
1893 static int
1894 img_read_item_ascii(img *pimg, img_point *p)
1896 int result;
1897 pimg->label = pimg->label_buf;
1898 if (pimg->version == 0) {
1899 ascii_again:
1900 pimg->label[0] = '\0';
1901 if (feof(pimg->fh)) return img_STOP;
1902 if (pimg->pending) {
1903 pimg->pending = 0;
1904 result = img_LINE;
1905 } else {
1906 char cmd[7];
1907 /* Stop if nothing found */
1908 if (fscanf(pimg->fh, "%6s", cmd) < 1) return img_STOP;
1909 if (strcmp(cmd, "move") == 0)
1910 result = img_MOVE;
1911 else if (strcmp(cmd, "draw") == 0)
1912 result = img_LINE;
1913 else if (strcmp(cmd, "line") == 0) {
1914 /* set flag to indicate to process second triplet as LINE */
1915 pimg->pending = 1;
1916 result = img_MOVE;
1917 } else if (strcmp(cmd, "cross") == 0) {
1918 if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1919 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1920 return img_BAD;
1922 goto ascii_again;
1923 } else if (strcmp(cmd, "name") == 0) {
1924 size_t off = 0;
1925 int ch = GETC(pimg->fh);
1926 if (ch == ' ') ch = GETC(pimg->fh);
1927 while (ch != ' ') {
1928 if (ch == '\n' || ch == EOF) {
1929 img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1930 return img_BAD;
1932 if (off == pimg->buf_len) {
1933 if (!check_label_space(pimg, pimg->buf_len * 2)) {
1934 img_errno = IMG_OUTOFMEMORY;
1935 return img_BAD;
1938 pimg->label_buf[off++] = ch;
1939 ch = GETC(pimg->fh);
1941 pimg->label_buf[off] = '\0';
1943 pimg->label = pimg->label_buf;
1944 if (pimg->label[0] == '\\') pimg->label++;
1946 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1948 result = img_LABEL;
1949 } else {
1950 img_errno = IMG_BADFORMAT;
1951 return img_BAD; /* unknown keyword */
1955 if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1956 img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1957 return img_BAD;
1960 if (result == img_LABEL && !stn_included(pimg)) {
1961 goto ascii_again;
1964 return result;
1965 } else if (pimg->version == VERSION_SURVEX_POS) {
1966 /* Survex .pos file */
1967 int ch;
1968 size_t off;
1969 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1970 againpos:
1971 while (fscanf(pimg->fh, "(%lf,%lf,%lf )", &p->x, &p->y, &p->z) != 3) {
1972 if (ferror(pimg->fh)) {
1973 img_errno = IMG_READERROR;
1974 return img_BAD;
1976 if (feof(pimg->fh)) return img_STOP;
1977 if (pimg->pending) {
1978 img_errno = IMG_BADFORMAT;
1979 return img_BAD;
1981 pimg->pending = 1;
1982 /* ignore rest of line */
1983 do {
1984 ch = GETC(pimg->fh);
1985 } while (ch != '\n' && ch != '\r' && ch != EOF);
1988 pimg->label_buf[0] = '\0';
1989 do {
1990 ch = GETC(pimg->fh);
1991 } while (ch == ' ' || ch == '\t');
1992 if (ch == '\n' || ch == EOF) {
1993 /* If there's no label, set img_SFLAG_ANON. */
1994 pimg->flags |= img_SFLAG_ANON;
1995 return img_LABEL;
1997 pimg->label_buf[0] = ch;
1998 off = 1;
1999 while (!feof(pimg->fh)) {
2000 if (!fgets(pimg->label_buf + off, pimg->buf_len - off, pimg->fh)) {
2001 img_errno = IMG_READERROR;
2002 return img_BAD;
2005 off += strlen(pimg->label_buf + off);
2006 if (off && pimg->label_buf[off - 1] == '\n') {
2007 pimg->label_buf[off - 1] = '\0';
2008 break;
2010 if (!check_label_space(pimg, pimg->buf_len * 2)) {
2011 img_errno = IMG_OUTOFMEMORY;
2012 return img_BAD;
2016 pimg->label = pimg->label_buf;
2018 if (pimg->label[0] == '\\') pimg->label++;
2020 if (!stn_included(pimg)) goto againpos;
2022 return img_LABEL;
2023 } else if (pimg->version == VERSION_COMPASS_PLT) {
2024 /* Compass .plt file */
2025 if (pimg->pending > 0) {
2026 /* -1 signals we've entered the first survey we want to
2027 * read, and need to fudge lots if the first action is 'D'...
2029 /* pending MOVE or LINE */
2030 int r = pimg->pending - 4;
2031 pimg->pending = 0;
2032 pimg->flags = 0;
2033 pimg->label[pimg->label_len] = '\0';
2034 return r;
2037 while (1) {
2038 char *line;
2039 char *q;
2040 size_t len = 0;
2041 int ch = GETC(pimg->fh);
2043 switch (ch) {
2044 case '\x1a': case EOF: /* Don't insist on ^Z at end of file */
2045 return img_STOP;
2046 case 'X': case 'F': case 'S':
2047 /* bounding boX (marks end of survey), Feature survey, or
2048 * new Section - skip to next survey */
2049 if (pimg->survey) return img_STOP;
2050 skip_to_N:
2051 while (1) {
2052 do {
2053 ch = GETC(pimg->fh);
2054 } while (ch != '\n' && ch != '\r' && ch != EOF);
2055 while (ch == '\n' || ch == '\r') ch = GETC(pimg->fh);
2056 if (ch == 'N') break;
2057 if (ch == '\x1a' || ch == EOF) return img_STOP;
2059 /* FALLTHRU */
2060 case 'N':
2061 line = getline_alloc(pimg->fh);
2062 if (!line) {
2063 img_errno = IMG_OUTOFMEMORY;
2064 return img_BAD;
2066 while (line[len] > 32) ++len;
2067 if (pimg->label_len == 0) pimg->pending = -1;
2068 if (!check_label_space(pimg, len + 1)) {
2069 osfree(line);
2070 img_errno = IMG_OUTOFMEMORY;
2071 return img_BAD;
2073 pimg->label_len = len;
2074 pimg->label = pimg->label_buf;
2075 memcpy(pimg->label, line, len);
2076 pimg->label[len] = '\0';
2077 osfree(line);
2078 break;
2079 case 'M': case 'D': {
2080 /* Move or Draw */
2081 long fpos = -1;
2082 if (pimg->survey && pimg->label_len == 0) {
2083 /* We're only holding onto this line in case the first line
2084 * of the 'N' is a 'D', so skip it for now...
2086 goto skip_to_N;
2088 if (ch == 'D' && pimg->pending == -1) {
2089 if (pimg->survey) {
2090 fpos = ftell(pimg->fh) - 1;
2091 fseek(pimg->fh, pimg->start, SEEK_SET);
2092 ch = GETC(pimg->fh);
2093 pimg->pending = 0;
2094 } else {
2095 /* If a file actually has a 'D' before any 'M', then
2096 * pretend the 'D' is an 'M' - one of the examples
2097 * in the docs was like this! */
2098 ch = 'M';
2101 line = getline_alloc(pimg->fh);
2102 if (!line) {
2103 img_errno = IMG_OUTOFMEMORY;
2104 return img_BAD;
2106 /* Compass stores coordinates as North, East, Up = (y,x,z)! */
2107 if (sscanf(line, "%lf%lf%lf", &p->y, &p->x, &p->z) != 3) {
2108 osfree(line);
2109 if (ferror(pimg->fh)) {
2110 img_errno = IMG_READERROR;
2111 } else {
2112 img_errno = IMG_BADFORMAT;
2114 return img_BAD;
2116 p->x *= METRES_PER_FOOT;
2117 p->y *= METRES_PER_FOOT;
2118 p->z *= METRES_PER_FOOT;
2119 q = strchr(line, 'S');
2120 if (!q) {
2121 osfree(line);
2122 img_errno = IMG_BADFORMAT;
2123 return img_BAD;
2125 ++q;
2126 len = 0;
2127 while (q[len] > ' ') ++len;
2128 q[len] = '\0';
2129 len += 2; /* ' ' and '\0' */
2130 if (!check_label_space(pimg, pimg->label_len + len)) {
2131 img_errno = IMG_OUTOFMEMORY;
2132 return img_BAD;
2134 pimg->label = pimg->label_buf;
2135 if (pimg->label_len) {
2136 pimg->label[pimg->label_len] = ' ';
2137 memcpy(pimg->label + pimg->label_len + 1, q, len - 1);
2138 } else {
2139 memcpy(pimg->label, q, len - 1);
2141 q += len - 1;
2142 /* Now read LRUD. Technically, this is optional but virtually
2143 * all PLT files have it (with dummy negative values if no LRUD
2144 * was measured) and some versions of Compass can't read PLT
2145 * files without it!
2147 while (*q && *q <= ' ') q++;
2148 if (*q == 'P') {
2149 if (sscanf(q + 1, "%lf%lf%lf%lf",
2150 &pimg->l, &pimg->r, &pimg->u, &pimg->d) != 4) {
2151 osfree(line);
2152 if (ferror(pimg->fh)) {
2153 img_errno = IMG_READERROR;
2154 } else {
2155 img_errno = IMG_BADFORMAT;
2157 return img_BAD;
2159 pimg->l *= METRES_PER_FOOT;
2160 pimg->r *= METRES_PER_FOOT;
2161 pimg->u *= METRES_PER_FOOT;
2162 pimg->d *= METRES_PER_FOOT;
2163 } else {
2164 pimg->l = pimg->r = pimg->u = pimg->d = -1;
2166 osfree(line);
2167 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
2168 if (fpos != -1) {
2169 fseek(pimg->fh, fpos, SEEK_SET);
2170 } else {
2171 pimg->pending = (ch == 'M' ? img_MOVE : img_LINE) + 4;
2173 return img_LABEL;
2175 default:
2176 img_errno = IMG_BADFORMAT;
2177 return img_BAD;
2180 } else {
2181 /* CMAP .xyz file */
2182 char *line = NULL;
2183 char *q;
2184 size_t len;
2186 if (pimg->pending) {
2187 /* pending MOVE or LINE or LABEL or STOP */
2188 int r = pimg->pending - 4;
2189 /* Set label to empty - don't use "" as we adjust label relative
2190 * to label_buf when label_buf is reallocated. */
2191 pimg->label = pimg->label_buf + strlen(pimg->label_buf);
2192 pimg->flags = 0;
2193 if (r == img_LABEL) {
2194 /* nasty magic */
2195 read_xyz_shot_coords(p, pimg->label_buf + 16);
2196 subtract_xyz_shot_deltas(p, pimg->label_buf + 16);
2197 pimg->pending = img_STOP + 4;
2198 return img_MOVE;
2201 pimg->pending = 0;
2203 if (r == img_STOP) {
2204 /* nasty magic */
2205 read_xyz_shot_coords(p, pimg->label_buf + 16);
2206 return img_LINE;
2209 return r;
2212 pimg->label = pimg->label_buf;
2213 do {
2214 osfree(line);
2215 if (feof(pimg->fh)) return img_STOP;
2216 line = getline_alloc(pimg->fh);
2217 if (!line) {
2218 img_errno = IMG_OUTOFMEMORY;
2219 return img_BAD;
2221 } while (line[0] == ' ' || line[0] == '\0');
2222 if (line[0] == '\x1a') return img_STOP;
2224 len = strlen(line);
2225 if (pimg->version == VERSION_CMAP_STATION) {
2226 /* station variant */
2227 if (len < 37) {
2228 osfree(line);
2229 img_errno = IMG_BADFORMAT;
2230 return img_BAD;
2232 memcpy(pimg->label, line, 6);
2233 q = (char *)memchr(pimg->label, ' ', 6);
2234 if (!q) q = pimg->label + 6;
2235 *q = '\0';
2237 read_xyz_station_coords(p, line);
2239 /* FIXME: look at prev for lines (line + 32, 5) */
2240 /* FIXME: duplicate stations... */
2241 return img_LABEL;
2242 } else {
2243 /* Shot variant (VERSION_CMAP_SHOT) */
2244 char old[8], new_[8];
2245 if (len < 61) {
2246 osfree(line);
2247 img_errno = IMG_BADFORMAT;
2248 return img_BAD;
2251 memcpy(old, line, 7);
2252 q = (char *)memchr(old, ' ', 7);
2253 if (!q) q = old + 7;
2254 *q = '\0';
2256 memcpy(new_, line + 7, 7);
2257 q = (char *)memchr(new_, ' ', 7);
2258 if (!q) q = new_ + 7;
2259 *q = '\0';
2261 pimg->flags = img_SFLAG_UNDERGROUND;
2263 if (strcmp(old, new_) == 0) {
2264 pimg->pending = img_MOVE + 4;
2265 read_xyz_shot_coords(p, line);
2266 strcpy(pimg->label, new_);
2267 osfree(line);
2268 return img_LABEL;
2271 if (strcmp(old, pimg->label) == 0) {
2272 pimg->pending = img_LINE + 4;
2273 read_xyz_shot_coords(p, line);
2274 strcpy(pimg->label, new_);
2275 osfree(line);
2276 return img_LABEL;
2279 pimg->pending = img_LABEL + 4;
2280 read_xyz_shot_coords(p, line);
2281 strcpy(pimg->label, new_);
2282 memcpy(pimg->label + 16, line, 70);
2284 osfree(line);
2285 return img_LABEL;
2290 static void
2291 write_coord(FILE *fh, double x, double y, double z)
2293 SVX_ASSERT(fh);
2294 /* Output in cm */
2295 static INT32_T X_, Y_, Z_;
2296 INT32_T X = my_lround(x * 100.0);
2297 INT32_T Y = my_lround(y * 100.0);
2298 INT32_T Z = my_lround(z * 100.0);
2300 X_ -= X;
2301 Y_ -= Y;
2302 Z_ -= Z;
2303 put32(X, fh);
2304 put32(Y, fh);
2305 put32(Z, fh);
2306 X_ = X; Y_ = Y; Z_ = Z;
2309 static int
2310 write_v3label(img *pimg, int opt, const char *s)
2312 size_t len, n, dot;
2314 /* find length of common prefix */
2315 dot = 0;
2316 for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2317 if (s[len] == '.') dot = len + 1;
2320 SVX_ASSERT(len <= pimg->label_len);
2321 n = pimg->label_len - len;
2322 if (len == 0) {
2323 if (pimg->label_len) PUTC(0, pimg->fh);
2324 } else if (n <= 16) {
2325 if (n) PUTC(n + 15, pimg->fh);
2326 } else if (dot == 0) {
2327 if (pimg->label_len) PUTC(0, pimg->fh);
2328 len = 0;
2329 } else {
2330 const char *p = pimg->label_buf + dot;
2331 n = 1;
2332 for (len = pimg->label_len - dot - 17; len; len--) {
2333 if (*p++ == '.') n++;
2335 if (n <= 14) {
2336 PUTC(n, pimg->fh);
2337 len = dot;
2338 } else {
2339 if (pimg->label_len) PUTC(0, pimg->fh);
2340 len = 0;
2344 n = strlen(s + len);
2345 PUTC(opt, pimg->fh);
2346 if (n < 0xfe) {
2347 PUTC(n, pimg->fh);
2348 } else if (n < 0xffff + 0xfe) {
2349 PUTC(0xfe, pimg->fh);
2350 put16((short)(n - 0xfe), pimg->fh);
2351 } else {
2352 PUTC(0xff, pimg->fh);
2353 put32(n, pimg->fh);
2355 fwrite(s + len, n, 1, pimg->fh);
2357 n += len;
2358 pimg->label_len = n;
2359 if (!check_label_space(pimg, n + 1))
2360 return 0; /* FIXME: distinguish out of memory... */
2361 memcpy(pimg->label_buf + len, s + len, n - len + 1);
2363 return !ferror(pimg->fh);
2366 static int
2367 write_v8label(img *pimg, int opt, int common_flag, size_t common_val,
2368 const char *s)
2370 size_t len, del, add;
2372 /* find length of common prefix */
2373 for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2376 SVX_ASSERT(len <= pimg->label_len);
2377 del = pimg->label_len - len;
2378 add = strlen(s + len);
2380 if (add == common_val && del == common_val) {
2381 PUTC(opt | common_flag, pimg->fh);
2382 } else {
2383 PUTC(opt, pimg->fh);
2384 if (del <= 15 && add <= 15 && (del || add)) {
2385 PUTC((del << 4) | add, pimg->fh);
2386 } else {
2387 PUTC(0x00, pimg->fh);
2388 if (del < 0xff) {
2389 PUTC(del, pimg->fh);
2390 } else {
2391 PUTC(0xff, pimg->fh);
2392 put32(del, pimg->fh);
2394 if (add < 0xff) {
2395 PUTC(add, pimg->fh);
2396 } else {
2397 PUTC(0xff, pimg->fh);
2398 put32(add, pimg->fh);
2403 if (add)
2404 fwrite(s + len, add, 1, pimg->fh);
2406 pimg->label_len = len + add;
2407 if (add > del && !check_label_space(pimg, pimg->label_len + 1))
2408 return 0; /* FIXME: distinguish out of memory... */
2410 memcpy(pimg->label_buf + len, s + len, add + 1);
2412 return !ferror(pimg->fh);
2415 static void
2416 img_write_item_date_new(img *pimg)
2418 int same, unset;
2419 /* Only write dates when they've changed. */
2420 #if IMG_API_VERSION == 0
2421 if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2422 return;
2424 same = (pimg->date1 == pimg->date2);
2425 unset = (pimg->date1 == 0);
2426 #else /* IMG_API_VERSION == 1 */
2427 if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2428 return;
2430 same = (pimg->days1 == pimg->days2);
2431 unset = (pimg->days1 == -1);
2432 #endif
2434 if (same) {
2435 if (unset) {
2436 PUTC(0x10, pimg->fh);
2437 } else {
2438 PUTC(0x11, pimg->fh);
2439 #if IMG_API_VERSION == 0
2440 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2441 #else /* IMG_API_VERSION == 1 */
2442 put16(pimg->days1, pimg->fh);
2443 #endif
2445 } else {
2446 #if IMG_API_VERSION == 0
2447 int diff = (pimg->date2 - pimg->date1) / 86400;
2448 if (diff > 0 && diff <= 256) {
2449 PUTC(0x12, pimg->fh);
2450 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2451 PUTC(diff - 1, pimg->fh);
2452 } else {
2453 PUTC(0x13, pimg->fh);
2454 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2455 put16(pimg->date2 / 86400 + 25567, pimg->fh);
2457 #else /* IMG_API_VERSION == 1 */
2458 int diff = pimg->days2 - pimg->days1;
2459 if (diff > 0 && diff <= 256) {
2460 PUTC(0x12, pimg->fh);
2461 put16(pimg->days1, pimg->fh);
2462 PUTC(diff - 1, pimg->fh);
2463 } else {
2464 PUTC(0x13, pimg->fh);
2465 put16(pimg->days1, pimg->fh);
2466 put16(pimg->days2, pimg->fh);
2468 #endif
2470 #if IMG_API_VERSION == 0
2471 pimg->olddate1 = pimg->date1;
2472 pimg->olddate2 = pimg->date2;
2473 #else /* IMG_API_VERSION == 1 */
2474 pimg->olddays1 = pimg->days1;
2475 pimg->olddays2 = pimg->days2;
2476 #endif
2479 static void
2480 img_write_item_date(img *pimg)
2482 int same, unset;
2483 /* Only write dates when they've changed. */
2484 #if IMG_API_VERSION == 0
2485 if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2486 return;
2488 same = (pimg->date1 == pimg->date2);
2489 unset = (pimg->date1 == 0);
2490 #else /* IMG_API_VERSION == 1 */
2491 if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2492 return;
2494 same = (pimg->days1 == pimg->days2);
2495 unset = (pimg->days1 == -1);
2496 #endif
2498 if (same) {
2499 if (img_output_version < 7) {
2500 PUTC(0x20, pimg->fh);
2501 #if IMG_API_VERSION == 0
2502 put32(pimg->date1, pimg->fh);
2503 #else /* IMG_API_VERSION == 1 */
2504 put32((pimg->days1 - 25567) * 86400, pimg->fh);
2505 #endif
2506 } else {
2507 if (unset) {
2508 PUTC(0x24, pimg->fh);
2509 } else {
2510 PUTC(0x20, pimg->fh);
2511 #if IMG_API_VERSION == 0
2512 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2513 #else /* IMG_API_VERSION == 1 */
2514 put16(pimg->days1, pimg->fh);
2515 #endif
2518 } else {
2519 if (img_output_version < 7) {
2520 PUTC(0x21, pimg->fh);
2521 #if IMG_API_VERSION == 0
2522 put32(pimg->date1, pimg->fh);
2523 put32(pimg->date2, pimg->fh);
2524 #else /* IMG_API_VERSION == 1 */
2525 put32((pimg->days1 - 25567) * 86400, pimg->fh);
2526 put32((pimg->days2 - 25567) * 86400, pimg->fh);
2527 #endif
2528 } else {
2529 #if IMG_API_VERSION == 0
2530 int diff = (pimg->date2 - pimg->date1) / 86400;
2531 if (diff > 0 && diff <= 256) {
2532 PUTC(0x21, pimg->fh);
2533 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2534 PUTC(diff - 1, pimg->fh);
2535 } else {
2536 PUTC(0x23, pimg->fh);
2537 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2538 put16(pimg->date2 / 86400 + 25567, pimg->fh);
2540 #else /* IMG_API_VERSION == 1 */
2541 int diff = pimg->days2 - pimg->days1;
2542 if (diff > 0 && diff <= 256) {
2543 PUTC(0x21, pimg->fh);
2544 put16(pimg->days1, pimg->fh);
2545 PUTC(diff - 1, pimg->fh);
2546 } else {
2547 PUTC(0x23, pimg->fh);
2548 put16(pimg->days1, pimg->fh);
2549 put16(pimg->days2, pimg->fh);
2551 #endif
2554 #if IMG_API_VERSION == 0
2555 pimg->olddate1 = pimg->date1;
2556 pimg->olddate2 = pimg->date2;
2557 #else /* IMG_API_VERSION == 1 */
2558 pimg->olddays1 = pimg->days1;
2559 pimg->olddays2 = pimg->days2;
2560 #endif
2563 static void
2564 img_write_item_new(img *pimg, int code, int flags, const char *s,
2565 double x, double y, double z);
2566 static void
2567 img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2568 double x, double y, double z);
2569 static void
2570 img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2571 double x, double y, double z);
2573 void
2574 img_write_item(img *pimg, int code, int flags, const char *s,
2575 double x, double y, double z)
2577 if (!pimg) return;
2578 if (pimg->version >= 8) {
2579 img_write_item_new(pimg, code, flags, s, x, y, z);
2580 } else if (pimg->version >= 3) {
2581 img_write_item_v3to7(pimg, code, flags, s, x, y, z);
2582 } else {
2583 img_write_item_ancient(pimg, code, flags, s, x, y, z);
2587 static void
2588 img_write_item_new(img *pimg, int code, int flags, const char *s,
2589 double x, double y, double z)
2591 switch (code) {
2592 case img_LABEL:
2593 write_v8label(pimg, 0x80 | flags, 0, -1, s);
2594 break;
2595 case img_XSECT: {
2596 INT32_T l, r, u, d, max_dim;
2597 img_write_item_date_new(pimg);
2598 l = (INT32_T)my_lround(pimg->l * 100.0);
2599 r = (INT32_T)my_lround(pimg->r * 100.0);
2600 u = (INT32_T)my_lround(pimg->u * 100.0);
2601 d = (INT32_T)my_lround(pimg->d * 100.0);
2602 if (l < 0) l = -1;
2603 if (r < 0) r = -1;
2604 if (u < 0) u = -1;
2605 if (d < 0) d = -1;
2606 max_dim = max(max(l, r), max(u, d));
2607 flags = (flags & img_XFLAG_END) ? 1 : 0;
2608 if (max_dim >= 32768) flags |= 2;
2609 write_v8label(pimg, 0x30 | flags, 0, -1, s);
2610 if (flags & 2) {
2611 /* Big passage! Need to use 4 bytes. */
2612 put32(l, pimg->fh);
2613 put32(r, pimg->fh);
2614 put32(u, pimg->fh);
2615 put32(d, pimg->fh);
2616 } else {
2617 put16(l, pimg->fh);
2618 put16(r, pimg->fh);
2619 put16(u, pimg->fh);
2620 put16(d, pimg->fh);
2622 return;
2624 case img_MOVE:
2625 PUTC(15, pimg->fh);
2626 break;
2627 case img_LINE:
2628 img_write_item_date_new(pimg);
2629 if (pimg->style != pimg->oldstyle) {
2630 switch (pimg->style) {
2631 case img_STYLE_NORMAL:
2632 case img_STYLE_DIVING:
2633 case img_STYLE_CARTESIAN:
2634 case img_STYLE_CYLPOLAR:
2635 case img_STYLE_NOSURVEY:
2636 PUTC(pimg->style, pimg->fh);
2637 break;
2639 pimg->oldstyle = pimg->style;
2641 write_v8label(pimg, 0x40 | flags, 0x20, 0x00, s ? s : "");
2642 break;
2643 default: /* ignore for now */
2644 return;
2646 write_coord(pimg->fh, x, y, z);
2649 static void
2650 img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2651 double x, double y, double z)
2653 switch (code) {
2654 case img_LABEL:
2655 write_v3label(pimg, 0x40 | flags, s);
2656 break;
2657 case img_XSECT: {
2658 INT32_T l, r, u, d, max_dim;
2659 /* Need at least version 5 for img_XSECT. */
2660 if (pimg->version < 5) return;
2661 img_write_item_date(pimg);
2662 l = (INT32_T)my_lround(pimg->l * 100.0);
2663 r = (INT32_T)my_lround(pimg->r * 100.0);
2664 u = (INT32_T)my_lround(pimg->u * 100.0);
2665 d = (INT32_T)my_lround(pimg->d * 100.0);
2666 if (l < 0) l = -1;
2667 if (r < 0) r = -1;
2668 if (u < 0) u = -1;
2669 if (d < 0) d = -1;
2670 max_dim = max(max(l, r), max(u, d));
2671 flags = (flags & img_XFLAG_END) ? 1 : 0;
2672 if (max_dim >= 32768) flags |= 2;
2673 write_v3label(pimg, 0x30 | flags, s);
2674 if (flags & 2) {
2675 /* Big passage! Need to use 4 bytes. */
2676 put32(l, pimg->fh);
2677 put32(r, pimg->fh);
2678 put32(u, pimg->fh);
2679 put32(d, pimg->fh);
2680 } else {
2681 put16(l, pimg->fh);
2682 put16(r, pimg->fh);
2683 put16(u, pimg->fh);
2684 put16(d, pimg->fh);
2686 return;
2688 case img_MOVE:
2689 PUTC(15, pimg->fh);
2690 break;
2691 case img_LINE:
2692 if (pimg->version >= 4) {
2693 img_write_item_date(pimg);
2695 write_v3label(pimg, 0x80 | flags, s ? s : "");
2696 break;
2697 default: /* ignore for now */
2698 return;
2700 write_coord(pimg->fh, x, y, z);
2703 static void
2704 img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2705 double x, double y, double z)
2707 size_t len;
2708 INT32_T opt = 0;
2709 SVX_ASSERT(pimg->version > 0);
2710 switch (code) {
2711 case img_LABEL:
2712 if (pimg->version == 1) {
2713 /* put a move before each label */
2714 img_write_item_ancient(pimg, img_MOVE, 0, NULL, x, y, z);
2715 put32(2, pimg->fh);
2716 fputsnl(s, pimg->fh);
2717 return;
2719 len = strlen(s);
2720 if (len > 255 || strchr(s, '\n')) {
2721 /* long label - not in early incarnations of v2 format, but few
2722 * 3d files will need these, so better not to force incompatibility
2723 * with a new version I think... */
2724 PUTC(7, pimg->fh);
2725 PUTC(flags, pimg->fh);
2726 put32(len, pimg->fh);
2727 fputs(s, pimg->fh);
2728 } else {
2729 PUTC(0x40 | (flags & 0x3f), pimg->fh);
2730 fputsnl(s, pimg->fh);
2732 opt = 0;
2733 break;
2734 case img_MOVE:
2735 opt = 4;
2736 break;
2737 case img_LINE:
2738 if (pimg->version > 1) {
2739 opt = 0x80 | (flags & 0x3f);
2740 break;
2742 opt = 5;
2743 break;
2744 default: /* ignore for now */
2745 return;
2747 if (pimg->version == 1) {
2748 put32(opt, pimg->fh);
2749 } else {
2750 if (opt) PUTC(opt, pimg->fh);
2752 write_coord(pimg->fh, x, y, z);
2755 /* Write error information for the current traverse
2756 * n_legs is the number of legs in the traverse
2757 * length is the traverse length (in m)
2758 * E is the ratio of the observed misclosure to the theoretical one
2759 * H is the ratio of the observed horizontal misclosure to the theoretical one
2760 * V is the ratio of the observed vertical misclosure to the theoretical one
2762 void
2763 img_write_errors(img *pimg, int n_legs, double length,
2764 double E, double H, double V)
2766 PUTC((pimg->version >= 8 ? 0x1f : 0x22), pimg->fh);
2767 put32(n_legs, pimg->fh);
2768 put32((INT32_T)my_lround(length * 100.0), pimg->fh);
2769 put32((INT32_T)my_lround(E * 100.0), pimg->fh);
2770 put32((INT32_T)my_lround(H * 100.0), pimg->fh);
2771 put32((INT32_T)my_lround(V * 100.0), pimg->fh);
2775 img_close(img *pimg)
2777 int result = 1;
2778 if (pimg) {
2779 if (pimg->fh) {
2780 if (pimg->fRead) {
2781 osfree(pimg->survey);
2782 osfree(pimg->title);
2783 osfree(pimg->cs);
2784 osfree(pimg->datestamp);
2785 } else {
2786 /* write end of data marker */
2787 switch (pimg->version) {
2788 case 1:
2789 put32((INT32_T)-1, pimg->fh);
2790 break;
2791 default:
2792 if (pimg->version <= 7 ?
2793 (pimg->label_len != 0) :
2794 (pimg->style != img_STYLE_NORMAL)) {
2795 PUTC(0, pimg->fh);
2797 /* FALL THROUGH */
2798 case 2:
2799 PUTC(0, pimg->fh);
2800 break;
2803 if (ferror(pimg->fh)) result = 0;
2804 if (pimg->close_func && pimg->close_func(pimg->fh))
2805 result = 0;
2806 if (!result) img_errno = pimg->fRead ? IMG_READERROR : IMG_WRITEERROR;
2808 osfree(pimg->label_buf);
2809 osfree(pimg->filename_opened);
2810 osfree(pimg);
2812 return result;