Fix coding style
[survex.git] / src / img.c
blobfc0337d4a69cd351aa7b94c166889c5a5c5d677a
1 /* img.c
2 * Routines for reading and writing Survex ".3d" image files
3 * Copyright (C) 1993-2004,2005,2006,2010,2011,2013,2014,2017,2018 Olly Betts
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
24 #include <ctype.h>
25 #include <errno.h>
26 #include <limits.h>
27 #include <locale.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
33 #include "img.h"
35 #define TIMENA "?"
36 #ifdef IMG_HOSTED
37 # define INT32_T int32_t
38 # include "debug.h"
39 # include "filelist.h"
40 # include "filename.h"
41 # include "message.h"
42 # include "useful.h"
43 # define TIMEFMT msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107)
44 #else
45 # ifdef HAVE_STDINT_H
46 # include <stdint.h>
47 # define INT32_T int32_t
48 # else
49 # include <limits.h>
50 # if INT_MAX >= 2147483647
51 # define INT32_T int
52 # else
53 # define INT32_T long
54 # endif
55 # endif
56 # define TIMEFMT "%a,%Y.%m.%d %H:%M:%S %Z"
57 # define EXT_SVX_3D "3d"
58 # define EXT_SVX_POS "pos"
59 # define FNM_SEP_EXT '.'
60 # define METRES_PER_FOOT 0.3048 /* exact value */
61 # define xosmalloc(L) malloc((L))
62 # define xosrealloc(L,S) realloc((L),(S))
63 # define osfree(P) free((P))
64 # define osnew(T) (T*)malloc(sizeof(T))
66 /* in IMG_HOSTED mode, this tests if a filename refers to a directory */
67 # define fDirectory(X) 0
68 /* open file FNM with mode MODE, maybe using path PTH and/or extension EXT */
69 /* path isn't used in img.c, but EXT is */
70 # define fopenWithPthAndExt(PTH,FNM,EXT,MODE,X) \
71 ((*(X) = NULL), fopen(FNM,MODE))
72 # ifndef PUTC
73 # define PUTC(C, FH) putc(C, FH)
74 # endif
75 # ifndef GETC
76 # define GETC(FH) getc(FH)
77 # endif
78 # define fputsnl(S, FH) (fputs((S), (FH)) == EOF ? EOF : putc('\n', (FH)))
79 # define SVX_ASSERT(X)
81 #ifdef __cplusplus
82 # include <algorithm>
83 using std::max;
84 using std::min;
85 #else
86 /* Return max/min of two numbers. */
87 /* May be defined already (e.g. by Borland C in stdlib.h) */
88 /* NB Bad news if X or Y has side-effects... */
89 # ifndef max
90 # define max(X, Y) ((X) > (Y) ? (X) : (Y))
91 # endif
92 # ifndef min
93 # define min(X, Y) ((X) < (Y) ? (X) : (Y))
94 # endif
95 #endif
97 static INT32_T
98 get32(FILE *fh)
100 INT32_T w = GETC(fh);
101 w |= (INT32_T)GETC(fh) << 8l;
102 w |= (INT32_T)GETC(fh) << 16l;
103 w |= (INT32_T)GETC(fh) << 24l;
104 return w;
107 static void
108 put32(long w, FILE *fh)
110 PUTC((char)(w), fh);
111 PUTC((char)(w >> 8l), fh);
112 PUTC((char)(w >> 16l), fh);
113 PUTC((char)(w >> 24l), fh);
116 static short
117 get16(FILE *fh)
119 short w = GETC(fh);
120 w |= (short)GETC(fh) << 8l;
121 return w;
124 static void
125 put16(short w, FILE *fh)
127 PUTC((char)(w), fh);
128 PUTC((char)(w >> 8l), fh);
131 static char *
132 baseleaf_from_fnm(const char *fnm)
134 const char *p;
135 const char *q;
136 char * res;
137 size_t len;
139 p = fnm;
140 q = strrchr(p, '/');
141 if (q) p = q + 1;
142 q = strrchr(p, '\\');
143 if (q) p = q + 1;
145 q = strrchr(p, FNM_SEP_EXT);
146 if (q) len = (const char *)q - p; else len = strlen(p);
148 res = (char *)xosmalloc(len + 1);
149 if (!res) return NULL;
150 memcpy(res, p, len);
151 res[len] = '\0';
152 return res;
154 #endif
156 static char * my_strdup(const char *str);
158 static time_t
159 mktime_with_tz(struct tm * tm, const char * tz)
161 time_t r;
162 char * old_tz = getenv("TZ");
163 #ifdef _MSC_VER
164 if (old_tz) {
165 old_tz = my_strdup(old_tz);
166 if (!old_tz)
167 return (time_t)-1;
169 if (_putenv_s("TZ", tz) != 0) {
170 osfree(old_tz);
171 return (time_t)-1;
173 #elif defined HAVE_SETENV
174 if (old_tz) {
175 old_tz = my_strdup(old_tz);
176 if (!old_tz)
177 return (time_t)-1;
179 if (setenv("TZ", tz, 1) < 0) {
180 osfree(old_tz);
181 return (time_t)-1;
183 #else
184 char * p;
185 if (old_tz) {
186 size_t len = strlen(old_tz) + 1;
187 p = (char *)xosmalloc(len + 3);
188 if (!p)
189 return (time_t)-1;
190 memcpy(p, "TZ=", 3);
191 memcpy(p + 3, tz, len);
192 old_tz = p;
194 p = (char *)xosmalloc(strlen(tz) + 4);
195 if (!p) {
196 osfree(old_tz);
197 return (time_t)-1;
199 memcpy(p, "TZ=", 3);
200 strcpy(p + 3, tz);
201 if (putenv(p) != 0) {
202 osfree(p);
203 osfree(old_tz);
204 return (time_t)-1;
206 #define CLEANUP() osfree(p)
207 #endif
208 tzset();
209 r = mktime(tm);
210 if (old_tz) {
211 #ifdef _MSC_VER
212 _putenv_s("TZ", old_tz);
213 #elif !defined HAVE_SETENV
214 putenv(old_tz);
215 #else
216 setenv("TZ", old_tz, 1);
217 #endif
218 osfree(old_tz);
219 } else {
220 #ifdef _MSC_VER
221 _putenv_s("TZ", "");
222 #elif !defined HAVE_UNSETENV
223 putenv((char*)"TZ");
224 #else
225 unsetenv("TZ");
226 #endif
228 #ifdef CLEANUP
229 CLEANUP();
230 #undef CLEANUP
231 #endif
232 return r;
235 static unsigned short
236 getu16(FILE *fh)
238 return (unsigned short)get16(fh);
241 #include <math.h>
243 #if !defined HAVE_LROUND && !defined HAVE_DECL_LROUND
244 /* The autoconf tests are not in use, but C99 and C++11 both added lround(),
245 * so set HAVE_LROUND and HAVE_DECL_LROUND conservatively based on the language
246 * standard version the compiler claims to support. */
247 # if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) || \
248 (defined __cplusplus && __cplusplus >= 201103L)
249 # define HAVE_LROUND 1
250 # define HAVE_DECL_LROUND 1
251 # endif
252 #endif
254 #ifdef HAVE_LROUND
255 # if defined HAVE_DECL_LROUND && !HAVE_DECL_LROUND
256 /* On older systems, the prototype may be missing. */
257 extern long lround(double);
258 # endif
259 # define my_lround lround
260 #else
261 static long
262 my_lround(double x) {
263 return (x >= 0.0) ? (long)(x + 0.5) : -(long)(0.5 - x);
265 #endif
267 /* portable case insensitive string compare */
268 #if defined(strcasecmp) || defined(HAVE_STRCASECMP)
269 # define my_strcasecmp strcasecmp
270 #else
271 static int my_strcasecmp(const char *s1, const char *s2) {
272 unsigned char c1, c2;
273 do {
274 c1 = *s1++;
275 c2 = *s2++;
276 } while (c1 && toupper(c1) == toupper(c2));
277 /* now calculate real difference */
278 return c1 - c2;
280 #endif
282 unsigned int img_output_version = IMG_VERSION_MAX;
284 static img_errcode img_errno = IMG_NONE;
286 #define FILEID "Survex 3D Image File"
288 #define EXT_PLT "plt"
289 #define EXT_PLF "plf"
291 /* Attempt to string paste to ensure we are passed a literal string */
292 #define LITLEN(S) (sizeof(S"") - 1)
294 /* Fake "version numbers" for non-3d formats we can read. */
295 #define VERSION_CMAP_SHOT -4
296 #define VERSION_CMAP_STATION -3
297 #define VERSION_COMPASS_PLT -2
298 #define VERSION_SURVEX_POS -1
300 static char *
301 my_strdup(const char *str)
303 char *p;
304 size_t len = strlen(str) + 1;
305 p = (char *)xosmalloc(len);
306 if (p) memcpy(p, str, len);
307 return p;
310 #define getline_alloc(FH) getline_alloc_len(FH, NULL)
312 static char *
313 getline_alloc_len(FILE *fh, size_t * p_len)
315 int ch;
316 size_t i = 0;
317 size_t len = 16;
318 char *buf = (char *)xosmalloc(len);
319 if (!buf) return NULL;
321 ch = GETC(fh);
322 while (ch != '\n' && ch != '\r' && ch != EOF) {
323 buf[i++] = ch;
324 if (i == len - 1) {
325 char *p;
326 len += len;
327 p = (char *)xosrealloc(buf, len);
328 if (!p) {
329 osfree(buf);
330 return NULL;
332 buf = p;
334 ch = GETC(fh);
336 if (ch == '\n' || ch == '\r') {
337 int otherone = ch ^ ('\n' ^ '\r');
338 ch = GETC(fh);
339 /* if it's not the other eol character, put it back */
340 if (ch != otherone) ungetc(ch, fh);
342 buf[i] = '\0';
343 if (p_len) *p_len = i;
344 return buf;
347 img_errcode
348 img_error(void)
350 return img_errno;
353 static int
354 check_label_space(img *pimg, size_t len)
356 if (len > pimg->buf_len) {
357 char *b = (char *)xosrealloc(pimg->label_buf, len);
358 if (!b) return 0;
359 pimg->label = (pimg->label - pimg->label_buf) + b;
360 pimg->label_buf = b;
361 pimg->buf_len = len;
363 return 1;
366 /* Check if a station name should be included. */
367 static int
368 stn_included(img *pimg)
370 if (!pimg->survey_len) return 1;
371 size_t l = pimg->survey_len;
372 const char *s = pimg->label_buf;
373 if (strncmp(pimg->survey, s, l + 1) != 0) {
374 return 0;
376 pimg->label += l + 1;
377 return 1;
380 /* Check if a survey name should be included. */
381 static int
382 survey_included(img *pimg)
384 if (!pimg->survey_len) return 1;
385 size_t l = pimg->survey_len;
386 const char *s = pimg->label_buf;
387 if (strncmp(pimg->survey, s, l) != 0 ||
388 !(s[l] == '.' || s[l] == '\0')) {
389 return 0;
391 pimg->label += l;
392 /* skip the dot if there */
393 if (*pimg->label) pimg->label++;
394 return 1;
397 /* Check if a survey name in a buffer should be included.
399 * For "foreign" formats which just have one level of surveys.
401 static int
402 buf_included(img *pimg, const char *buf, size_t len)
404 return pimg->survey_len == len && strncmp(buf, pimg->survey, len) == 0;
407 #define has_ext(F,L,E) ((L) > LITLEN(E) + 1 &&\
408 (F)[(L) - LITLEN(E) - 1] == FNM_SEP_EXT &&\
409 my_strcasecmp((F) + (L) - LITLEN(E), E) == 0)
411 img *
412 img_open_survey(const char *fnm, const char *survey)
414 img *pimg;
415 FILE *fh;
416 char* filename_opened = NULL;
418 if (fDirectory(fnm)) {
419 img_errno = IMG_DIRECTORY;
420 return NULL;
423 fh = fopenWithPthAndExt("", fnm, EXT_SVX_3D, "rb", &filename_opened);
424 pimg = img_read_stream_survey(fh, fclose, filename_opened, survey);
425 if (pimg) {
426 pimg->filename_opened = filename_opened;
427 } else {
428 osfree(filename_opened);
430 return pimg;
433 img *
434 img_read_stream_survey(FILE *stream, int (*close_func)(FILE*),
435 const char *fnm,
436 const char *survey)
438 img *pimg;
439 size_t len;
440 char buf[LITLEN(FILEID) + 9];
441 int ch;
443 if (stream == NULL) {
444 img_errno = IMG_FILENOTFOUND;
445 return NULL;
448 pimg = osnew(img);
449 if (pimg == NULL) {
450 img_errno = IMG_OUTOFMEMORY;
451 if (close_func) close_func(stream);
452 return NULL;
455 pimg->fh = stream;
456 pimg->close_func = close_func;
458 pimg->buf_len = 257;
459 pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
460 if (!pimg->label_buf) {
461 if (pimg->close_func) pimg->close_func(pimg->fh);
462 osfree(pimg);
463 img_errno = IMG_OUTOFMEMORY;
464 return NULL;
467 pimg->fRead = 1; /* reading from this file */
468 img_errno = IMG_NONE;
470 pimg->flags = 0;
471 pimg->filename_opened = NULL;
473 /* for version >= 3 we use label_buf to store the prefix for reuse */
474 /* for VERSION_COMPASS_PLT, 0 value indicates we haven't
475 * entered a survey yet */
476 /* for VERSION_CMAP_SHOT, we store the last station here
477 * to detect whether we MOVE or LINE */
478 pimg->label_len = 0;
479 pimg->label_buf[0] = '\0';
481 pimg->survey = NULL;
482 pimg->survey_len = 0;
483 pimg->separator = '.';
484 #if IMG_API_VERSION == 0
485 pimg->date1 = pimg->date2 = 0;
486 #else /* IMG_API_VERSION == 1 */
487 pimg->days1 = pimg->days2 = -1;
488 #endif
489 pimg->is_extended_elevation = 0;
491 pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
493 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
495 pimg->title = pimg->datestamp = pimg->cs = NULL;
496 pimg->datestamp_numeric = (time_t)-1;
498 if (survey) {
499 len = strlen(survey);
500 if (len) {
501 if (survey[len - 1] == '.') len--;
502 if (len) {
503 char *p;
504 pimg->survey = (char *)xosmalloc(len + 2);
505 if (!pimg->survey) {
506 img_errno = IMG_OUTOFMEMORY;
507 goto error;
509 memcpy(pimg->survey, survey, len);
510 /* Set title to leaf survey name */
511 pimg->survey[len] = '\0';
512 p = strrchr(pimg->survey, '.');
513 if (p) p++; else p = pimg->survey;
514 pimg->title = my_strdup(p);
515 if (!pimg->title) {
516 img_errno = IMG_OUTOFMEMORY;
517 goto error;
519 pimg->survey[len] = '.';
520 pimg->survey[len + 1] = '\0';
523 pimg->survey_len = len;
526 /* [VERSION_COMPASS_PLT, VERSION_CMAP_STATION, VERSION_CMAP_SHOT] pending
527 * IMG_LINE or IMG_MOVE - both have 4 added.
528 * [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
529 * [version 0] not in the middle of a 'LINE' command
530 * [version >= 3] not in the middle of turning a LINE into a MOVE
532 pimg->pending = 0;
534 len = strlen(fnm);
535 if (has_ext(fnm, len, EXT_SVX_POS)) {
536 pos_file:
537 pimg->version = VERSION_SURVEX_POS;
538 if (!pimg->survey) pimg->title = baseleaf_from_fnm(fnm);
539 pimg->datestamp = my_strdup(TIMENA);
540 if (!pimg->datestamp) {
541 img_errno = IMG_OUTOFMEMORY;
542 goto error;
544 pimg->start = 0;
545 return pimg;
548 if (has_ext(fnm, len, EXT_PLT) || has_ext(fnm, len, EXT_PLF)) {
549 long fpos;
550 plt_file:
551 pimg->version = VERSION_COMPASS_PLT;
552 /* Spaces aren't legal in Compass station names, but dots are, so
553 * use space as the level separator */
554 pimg->separator = ' ';
555 pimg->start = 0;
556 if (!pimg->survey) pimg->title = baseleaf_from_fnm(fnm);
557 pimg->datestamp = my_strdup(TIMENA);
558 if (!pimg->datestamp) {
559 img_errno = IMG_OUTOFMEMORY;
560 goto error;
562 while (1) {
563 ch = GETC(pimg->fh);
564 switch (ch) {
565 case '\x1a':
566 fseek(pimg->fh, -1, SEEK_CUR);
567 /* FALL THRU */
568 case EOF:
569 pimg->start = ftell(pimg->fh);
570 return pimg;
571 case 'N': {
572 char *line, *q;
573 fpos = ftell(pimg->fh) - 1;
574 if (!pimg->survey) {
575 /* FIXME : if there's only one survey in the file, it'd be nice
576 * to use its description as the title here...
578 ungetc('N', pimg->fh);
579 pimg->start = fpos;
580 return pimg;
582 line = getline_alloc(pimg->fh);
583 if (!line) {
584 img_errno = IMG_OUTOFMEMORY;
585 goto error;
587 len = 0;
588 while (line[len] > 32) ++len;
589 if (!buf_included(pimg, line, len)) {
590 osfree(line);
591 continue;
593 q = strchr(line + len, 'C');
594 if (q && q[1]) {
595 osfree(pimg->title);
596 pimg->title = my_strdup(q + 1);
597 } else if (!pimg->title) {
598 pimg->title = my_strdup(pimg->label);
600 osfree(line);
601 if (!pimg->title) {
602 img_errno = IMG_OUTOFMEMORY;
603 goto error;
605 if (!pimg->start) pimg->start = fpos;
606 fseek(pimg->fh, pimg->start, SEEK_SET);
607 return pimg;
609 case 'M': case 'D':
610 pimg->start = ftell(pimg->fh) - 1;
611 break;
613 while (ch != '\n' && ch != '\r') {
614 ch = GETC(pimg->fh);
619 /* Although these are often referred to as "CMAP .XYZ files", it seems
620 * that actually, the extension .XYZ isn't used, rather .SHT (shot
621 * variant, produced by CMAP v16 and later), .UNA (unadjusted) and
622 * .ADJ (adjusted) extensions are. Since img has long checked for
623 * .XYZ, we continue to do so in case anyone is relying on it.
625 if (has_ext(fnm, len, "sht") ||
626 has_ext(fnm, len, "adj") ||
627 has_ext(fnm, len, "una") ||
628 has_ext(fnm, len, "xyz")) {
629 char *line;
630 xyz_file:
631 /* Spaces aren't legal in CMAP station names, but dots are, so
632 * use space as the level separator. */
633 pimg->separator = ' ';
634 line = getline_alloc(pimg->fh);
635 if (!line) {
636 img_errno = IMG_OUTOFMEMORY;
637 goto error;
639 /* There doesn't seem to be a spec for what happens after 1999 with cmap
640 * files, so this code allows for:
641 * * 21xx -> xx (up to 2150)
642 * * 21xx -> 1xx (up to 2199)
643 * * full year being specified instead of 2 digits
645 len = strlen(line);
646 if (len > 59) {
647 /* Don't just truncate at column 59, allow for a > 2 digit year. */
648 char * p = strstr(line + len, "Page");
649 if (p) {
650 while (p > line && p[-1] == ' ')
651 --p;
652 *p = '\0';
653 len = p - line;
654 } else {
655 line[59] = '\0';
658 if (len > 45) {
659 /* YY/MM/DD HH:MM */
660 struct tm tm;
661 unsigned long v;
662 char * p;
663 pimg->datestamp = my_strdup(line + 45);
664 p = pimg->datestamp;
665 v = strtoul(p, &p, 10);
666 if (v <= 50) {
667 /* In the absence of a spec for cmap files, assume <= 50 means 21st
668 * century. */
669 v += 2000;
670 } else if (v < 200) {
671 /* Map 100-199 to 21st century. */
672 v += 1900;
674 if (v == ULONG_MAX || *p++ != '/')
675 goto bad_cmap_date;
676 tm.tm_year = v - 1900;
677 v = strtoul(p, &p, 10);
678 if (v < 1 || v > 12 || *p++ != '/')
679 goto bad_cmap_date;
680 tm.tm_mon = v - 1;
681 v = strtoul(p, &p, 10);
682 if (v < 1 || v > 31 || *p++ != ' ')
683 goto bad_cmap_date;
684 tm.tm_mday = v;
685 v = strtoul(p, &p, 10);
686 if (v >= 24 || *p++ != ':')
687 goto bad_cmap_date;
688 tm.tm_hour = v;
689 v = strtoul(p, &p, 10);
690 if (v >= 60)
691 goto bad_cmap_date;
692 tm.tm_min = v;
693 if (*p == ':') {
694 v = strtoul(p + 1, &p, 10);
695 if (v > 60)
696 goto bad_cmap_date;
697 tm.tm_sec = v;
698 } else {
699 tm.tm_sec = 0;
701 tm.tm_isdst = 0;
702 /* We have no indication of what timezone this timestamp is in. It's
703 * probably local time for whoever processed the data, so just assume
704 * UTC, which is at least fairly central in the possibilities.
706 pimg->datestamp_numeric = mktime_with_tz(&tm, "");
707 } else {
708 pimg->datestamp = my_strdup(TIMENA);
710 bad_cmap_date:
711 if (strncmp(line, " Cave Survey Data Processed by CMAP ",
712 LITLEN(" Cave Survey Data Processed by CMAP ")) == 0) {
713 len = 0;
714 } else {
715 if (len > 45) {
716 line[45] = '\0';
717 len = 45;
719 while (len > 2 && line[len - 1] == ' ') --len;
720 if (len > 2) {
721 line[len] = '\0';
722 pimg->title = my_strdup(line + 2);
725 if (len <= 2) pimg->title = baseleaf_from_fnm(fnm);
726 osfree(line);
727 if (!pimg->datestamp || !pimg->title) {
728 img_errno = IMG_OUTOFMEMORY;
729 goto error;
731 line = getline_alloc(pimg->fh);
732 if (!line) {
733 img_errno = IMG_OUTOFMEMORY;
734 goto error;
736 if (line[0] != ' ' || (line[1] != 'S' && line[1] != 'O')) {
737 img_errno = IMG_BADFORMAT;
738 goto error;
740 if (line[1] == 'S') {
741 pimg->version = VERSION_CMAP_STATION;
742 } else {
743 pimg->version = VERSION_CMAP_SHOT;
745 osfree(line);
746 line = getline_alloc(pimg->fh);
747 if (!line) {
748 img_errno = IMG_OUTOFMEMORY;
749 goto error;
751 if (line[0] != ' ' || line[1] != '-') {
752 img_errno = IMG_BADFORMAT;
753 goto error;
755 osfree(line);
756 pimg->start = ftell(pimg->fh);
757 return pimg;
760 if (fread(buf, LITLEN(FILEID) + 1, 1, pimg->fh) != 1 ||
761 memcmp(buf, FILEID"\n", LITLEN(FILEID) + 1) != 0) {
762 if (fread(buf + LITLEN(FILEID) + 1, 8, 1, pimg->fh) == 1 &&
763 memcmp(buf, FILEID"\r\nv0.01\r\n", LITLEN(FILEID) + 9) == 0) {
764 /* v0 3d file with DOS EOLs */
765 pimg->version = 0;
766 goto v03d;
768 rewind(pimg->fh);
769 if (buf[1] == ' ') {
770 if (buf[0] == ' ') {
771 /* Looks like a CMAP .xyz file ... */
772 goto xyz_file;
773 } else if (strchr("ZSNF", buf[0])) {
774 /* Looks like a Compass .plt file ... */
775 /* Almost certainly it'll start "Z " */
776 goto plt_file;
779 if (buf[0] == '(') {
780 /* Looks like a Survex .pos file ... */
781 goto pos_file;
783 img_errno = IMG_BADFORMAT;
784 goto error;
787 /* check file format version */
788 ch = GETC(pimg->fh);
789 pimg->version = 0;
790 if (tolower(ch) == 'b') {
791 /* binary file iff B/b prefix */
792 pimg->version = 1;
793 ch = GETC(pimg->fh);
795 if (ch != 'v') {
796 img_errno = IMG_BADFORMAT;
797 goto error;
799 ch = GETC(pimg->fh);
800 if (ch == '0') {
801 if (fread(buf, 4, 1, pimg->fh) != 1 || memcmp(buf, ".01\n", 4) != 0) {
802 img_errno = IMG_BADFORMAT;
803 goto error;
805 /* nothing special to do */
806 } else if (pimg->version == 0) {
807 if (ch < '2' || ch > '0' + IMG_VERSION_MAX || GETC(pimg->fh) != '\n') {
808 img_errno = IMG_TOONEW;
809 goto error;
811 pimg->version = ch - '0';
812 } else {
813 img_errno = IMG_BADFORMAT;
814 goto error;
817 v03d:
819 size_t title_len;
820 char * title = getline_alloc_len(pimg->fh, &title_len);
821 if (pimg->version == 8 && title) {
822 /* We sneak in an extra field after a zero byte here, containing the
823 * specified coordinate system (if any). Older readers will just
824 * not see it (which is fine), and this trick avoids us having to
825 * bump the 3d format version.
827 size_t real_len = strlen(title);
828 if (real_len != title_len) {
829 pimg->cs = my_strdup(title + real_len + 1);
832 if (!pimg->title) {
833 pimg->title = title;
834 } else {
835 osfree(title);
838 pimg->datestamp = getline_alloc(pimg->fh);
839 if (!pimg->title || !pimg->datestamp) {
840 img_errno = IMG_OUTOFMEMORY;
841 error:
842 osfree(pimg->title);
843 osfree(pimg->cs);
844 osfree(pimg->datestamp);
845 osfree(pimg->filename_opened);
846 if (pimg->close_func) pimg->close_func(pimg->fh);
847 osfree(pimg);
848 return NULL;
851 if (pimg->version >= 8) {
852 int flags = GETC(pimg->fh);
853 if (flags & img_FFLAG_EXTENDED) pimg->is_extended_elevation = 1;
854 } else {
855 len = strlen(pimg->title);
856 if (len > 11 && strcmp(pimg->title + len - 11, " (extended)") == 0) {
857 pimg->title[len - 11] = '\0';
858 pimg->is_extended_elevation = 1;
862 if (pimg->datestamp[0] == '@') {
863 unsigned long v;
864 char * p;
865 errno = 0;
866 v = strtoul(pimg->datestamp + 1, &p, 10);
867 if (errno == 0 && *p == '\0')
868 pimg->datestamp_numeric = v;
869 /* FIXME: We're assuming here that the C time_t epoch is 1970, which is
870 * true for Unix-like systems, Mac OS X and Windows, but isn't guaranteed
871 * by ISO C.
873 } else {
874 /* %a,%Y.%m.%d %H:%M:%S %Z */
875 struct tm tm;
876 unsigned long v;
877 char * p = pimg->datestamp;
878 while (isalpha((unsigned char)*p)) ++p;
879 if (*p == ',') ++p;
880 while (isspace((unsigned char)*p)) ++p;
881 v = strtoul(p, &p, 10);
882 if (v == ULONG_MAX || *p++ != '.')
883 goto bad_3d_date;
884 tm.tm_year = v - 1900;
885 v = strtoul(p, &p, 10);
886 if (v < 1 || v > 12 || *p++ != '.')
887 goto bad_3d_date;
888 tm.tm_mon = v - 1;
889 v = strtoul(p, &p, 10);
890 if (v < 1 || v > 31 || *p++ != ' ')
891 goto bad_3d_date;
892 tm.tm_mday = v;
893 v = strtoul(p, &p, 10);
894 if (v >= 24 || *p++ != ':')
895 goto bad_3d_date;
896 tm.tm_hour = v;
897 v = strtoul(p, &p, 10);
898 if (v >= 60 || *p++ != ':')
899 goto bad_3d_date;
900 tm.tm_min = v;
901 v = strtoul(p, &p, 10);
902 if (v > 60)
903 goto bad_3d_date;
904 tm.tm_sec = v;
905 tm.tm_isdst = 0;
906 while (isspace((unsigned char)*p)) ++p;
907 /* p now points to the timezone string.
909 * However, it's likely to be a string like "BST", and such strings can
910 * be ambiguous (BST could be UTC+1 or UTC+6), so it is impossible to
911 * reliably convert in all cases. Just pass what we have to tzset() - if
912 * it doesn't handle it, UTC will be used.
914 pimg->datestamp_numeric = mktime_with_tz(&tm, p);
916 bad_3d_date:
918 pimg->start = ftell(pimg->fh);
920 return pimg;
924 img_rewind(img *pimg)
926 if (!pimg->fRead) {
927 img_errno = IMG_WRITEERROR;
928 return 0;
930 if (fseek(pimg->fh, pimg->start, SEEK_SET) != 0) {
931 img_errno = IMG_READERROR;
932 return 0;
934 clearerr(pimg->fh);
935 /* [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
936 * [version 0] not in the middle of a 'LINE' command
937 * [version >= 3] not in the middle of turning a LINE into a MOVE */
938 pimg->pending = 0;
940 img_errno = IMG_NONE;
942 /* for version >= 3 we use label_buf to store the prefix for reuse */
943 /* for VERSION_COMPASS_PLT, 0 value indicates we haven't entered a survey
944 * yet */
945 /* for VERSION_CMAP_SHOT, we store the last station here to detect whether
946 * we MOVE or LINE */
947 pimg->label_len = 0;
948 pimg->style = img_STYLE_UNKNOWN;
949 return 1;
952 img *
953 img_open_write_cs(const char *fnm, const char *title, const char *cs, int flags)
955 if (fDirectory(fnm)) {
956 img_errno = IMG_DIRECTORY;
957 return NULL;
960 return img_write_stream(fopen(fnm, "wb"), fclose, title, cs, flags);
963 img *
964 img_write_stream(FILE *stream, int (*close_func)(FILE*),
965 const char *title, const char *cs, int flags)
967 time_t tm;
968 img *pimg;
970 if (stream == NULL) {
971 img_errno = IMG_FILENOTFOUND;
972 return NULL;
975 pimg = osnew(img);
976 if (pimg == NULL) {
977 img_errno = IMG_OUTOFMEMORY;
978 if (close_func) close_func(stream);
979 return NULL;
982 pimg->fh = stream;
983 pimg->close_func = close_func;
984 pimg->buf_len = 257;
985 pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
986 if (!pimg->label_buf) {
987 if (pimg->close_func) pimg->close_func(pimg->fh);
988 osfree(pimg);
989 img_errno = IMG_OUTOFMEMORY;
990 return NULL;
993 pimg->filename_opened = NULL;
995 /* Output image file header */
996 fputs("Survex 3D Image File\n", pimg->fh); /* file identifier string */
997 if (img_output_version < 2) {
998 pimg->version = 1;
999 fputs("Bv0.01\n", pimg->fh); /* binary file format version number */
1000 } else {
1001 pimg->version = (img_output_version > IMG_VERSION_MAX) ? IMG_VERSION_MAX : img_output_version;
1002 fprintf(pimg->fh, "v%d\n", pimg->version); /* file format version no. */
1005 fputs(title, pimg->fh);
1006 if (pimg->version < 8 && (flags & img_FFLAG_EXTENDED)) {
1007 /* Older format versions append " (extended)" to the title to mark
1008 * extended elevations. */
1009 size_t len = strlen(title);
1010 if (len < 11 || strcmp(title + len - 11, " (extended)") != 0)
1011 fputs(" (extended)", pimg->fh);
1013 if (pimg->version == 8 && cs && *cs) {
1014 /* We sneak in an extra field after a zero byte here, containing the
1015 * specified coordinate system (if any). Older readers will just not
1016 * see it (which is fine), and this trick avoids us having to bump the
1017 * 3d format version.
1019 PUTC('\0', pimg->fh);
1020 fputs(cs, pimg->fh);
1022 PUTC('\n', pimg->fh);
1024 tm = time(NULL);
1025 if (tm == (time_t)-1) {
1026 fputsnl(TIMENA, pimg->fh);
1027 } else if (pimg->version <= 7) {
1028 char date[256];
1029 /* output current date and time in format specified */
1030 strftime(date, 256, TIMEFMT, localtime(&tm));
1031 fputsnl(date, pimg->fh);
1032 } else {
1033 fprintf(pimg->fh, "@%ld\n", (long)tm);
1036 if (pimg->version >= 8) {
1037 /* Clear bit one in case anyone has been passing true for fBinary. */
1038 flags &=~ 1;
1039 PUTC(flags, pimg->fh);
1042 #if 0
1043 if (img_output_version >= 5) {
1044 static const unsigned char codelengths[32] = {
1045 4, 8, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1046 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1048 fwrite(codelengths, 32, 1, pimg->fh);
1050 #endif
1051 pimg->fRead = 0; /* writing to this file */
1052 img_errno = IMG_NONE;
1054 /* for version >= 3 we use label_buf to store the prefix for reuse */
1055 pimg->label_buf[0] = '\0';
1056 pimg->label_len = 0;
1058 #if IMG_API_VERSION == 0
1059 pimg->date1 = pimg->date2 = 0;
1060 pimg->olddate1 = pimg->olddate2 = 0;
1061 #else /* IMG_API_VERSION == 1 */
1062 pimg->days1 = pimg->days2 = -1;
1063 pimg->olddays1 = pimg->olddays2 = -1;
1064 #endif
1065 pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
1067 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1069 pimg->n_legs = 0;
1070 pimg->length = 0.0;
1071 pimg->E = pimg->H = pimg->V = 0.0;
1073 /* Don't check for write errors now - let img_close() report them... */
1074 return pimg;
1077 static void
1078 read_xyz_station_coords(img_point *pt, const char *line)
1080 char num[12];
1081 memcpy(num, line + 6, 9);
1082 num[9] = '\0';
1083 pt->x = atof(num) / METRES_PER_FOOT;
1084 memcpy(num, line + 15, 9);
1085 pt->y = atof(num) / METRES_PER_FOOT;
1086 memcpy(num, line + 24, 8);
1087 num[8] = '\0';
1088 pt->z = atof(num) / METRES_PER_FOOT;
1091 static void
1092 read_xyz_shot_coords(img_point *pt, const char *line)
1094 char num[12];
1095 memcpy(num, line + 40, 10);
1096 num[10] = '\0';
1097 pt->x = atof(num) / METRES_PER_FOOT;
1098 memcpy(num, line + 50, 10);
1099 pt->y = atof(num) / METRES_PER_FOOT;
1100 memcpy(num, line + 60, 9);
1101 num[9] = '\0';
1102 pt->z = atof(num) / METRES_PER_FOOT;
1105 static void
1106 subtract_xyz_shot_deltas(img_point *pt, const char *line)
1108 char num[12];
1109 memcpy(num, line + 15, 9);
1110 num[9] = '\0';
1111 pt->x -= atof(num) / METRES_PER_FOOT;
1112 memcpy(num, line + 24, 8);
1113 num[8] = '\0';
1114 pt->y -= atof(num) / METRES_PER_FOOT;
1115 memcpy(num, line + 32, 8);
1116 pt->z -= atof(num) / METRES_PER_FOOT;
1119 static int
1120 read_coord(FILE *fh, img_point *pt)
1122 SVX_ASSERT(fh);
1123 SVX_ASSERT(pt);
1124 pt->x = get32(fh) / 100.0;
1125 pt->y = get32(fh) / 100.0;
1126 pt->z = get32(fh) / 100.0;
1127 if (ferror(fh) || feof(fh)) {
1128 img_errno = feof(fh) ? IMG_BADFORMAT : IMG_READERROR;
1129 return 0;
1131 return 1;
1134 static int
1135 skip_coord(FILE *fh)
1137 return (fseek(fh, 12, SEEK_CUR) == 0);
1140 static int
1141 read_v3label(img *pimg)
1143 char *q;
1144 long len = GETC(pimg->fh);
1145 if (len == EOF) {
1146 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1147 return img_BAD;
1149 if (len == 0xfe) {
1150 len += get16(pimg->fh);
1151 if (feof(pimg->fh)) {
1152 img_errno = IMG_BADFORMAT;
1153 return img_BAD;
1155 if (ferror(pimg->fh)) {
1156 img_errno = IMG_READERROR;
1157 return img_BAD;
1159 } else if (len == 0xff) {
1160 len = get32(pimg->fh);
1161 if (ferror(pimg->fh)) {
1162 img_errno = IMG_READERROR;
1163 return img_BAD;
1165 if (feof(pimg->fh) || len < 0xfe + 0xffff) {
1166 img_errno = IMG_BADFORMAT;
1167 return img_BAD;
1171 if (!check_label_space(pimg, pimg->label_len + len + 1)) {
1172 img_errno = IMG_OUTOFMEMORY;
1173 return img_BAD;
1175 q = pimg->label_buf + pimg->label_len;
1176 pimg->label_len += len;
1177 if (len && fread(q, len, 1, pimg->fh) != 1) {
1178 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1179 return img_BAD;
1181 q[len] = '\0';
1182 return 0;
1185 static int
1186 read_v8label(img *pimg, int common_flag, size_t common_val)
1188 char *q;
1189 size_t del, add;
1190 if (common_flag) {
1191 if (common_val == 0) return 0;
1192 add = del = common_val;
1193 } else {
1194 int ch = GETC(pimg->fh);
1195 if (ch == EOF) {
1196 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1197 return img_BAD;
1199 if (ch != 0x00) {
1200 del = ch >> 4;
1201 add = ch & 0x0f;
1202 } else {
1203 ch = GETC(pimg->fh);
1204 if (ch == EOF) {
1205 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1206 return img_BAD;
1208 if (ch != 0xff) {
1209 del = ch;
1210 } else {
1211 del = get32(pimg->fh);
1212 if (ferror(pimg->fh)) {
1213 img_errno = IMG_READERROR;
1214 return img_BAD;
1217 ch = GETC(pimg->fh);
1218 if (ch == EOF) {
1219 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1220 return img_BAD;
1222 if (ch != 0xff) {
1223 add = ch;
1224 } else {
1225 add = get32(pimg->fh);
1226 if (ferror(pimg->fh)) {
1227 img_errno = IMG_READERROR;
1228 return img_BAD;
1233 if (add > del && !check_label_space(pimg, pimg->label_len + add - del + 1)) {
1234 img_errno = IMG_OUTOFMEMORY;
1235 return img_BAD;
1238 if (del > pimg->label_len) {
1239 img_errno = IMG_BADFORMAT;
1240 return img_BAD;
1242 pimg->label_len -= del;
1243 q = pimg->label_buf + pimg->label_len;
1244 pimg->label_len += add;
1245 if (add && fread(q, add, 1, pimg->fh) != 1) {
1246 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1247 return img_BAD;
1249 q[add] = '\0';
1250 return 0;
1253 static int img_read_item_new(img *pimg, img_point *p);
1254 static int img_read_item_v3to7(img *pimg, img_point *p);
1255 static int img_read_item_ancient(img *pimg, img_point *p);
1256 static int img_read_item_ascii_wrapper(img *pimg, img_point *p);
1257 static int img_read_item_ascii(img *pimg, img_point *p);
1260 img_read_item(img *pimg, img_point *p)
1262 pimg->flags = 0;
1264 if (pimg->version >= 8) {
1265 return img_read_item_new(pimg, p);
1266 } else if (pimg->version >= 3) {
1267 return img_read_item_v3to7(pimg, p);
1268 } else if (pimg->version >= 1) {
1269 return img_read_item_ancient(pimg, p);
1270 } else {
1271 return img_read_item_ascii_wrapper(pimg, p);
1275 static int
1276 img_read_item_new(img *pimg, img_point *p)
1278 int result;
1279 int opt;
1280 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1281 if (pimg->pending >= 0x40) {
1282 if (pimg->pending == 256) {
1283 pimg->pending = 0;
1284 return img_XSECT_END;
1286 *p = pimg->mv;
1287 pimg->flags = (int)(pimg->pending) & 0x3f;
1288 pimg->pending = 0;
1289 return img_LINE;
1291 again3: /* label to goto if we get a prefix, date, or lrud */
1292 pimg->label = pimg->label_buf;
1293 opt = GETC(pimg->fh);
1294 if (opt == EOF) {
1295 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1296 return img_BAD;
1298 if (opt >> 6 == 0) {
1299 if (opt <= 4) {
1300 if (opt == 0 && pimg->style == 0)
1301 return img_STOP; /* end of data marker */
1302 /* STYLE */
1303 pimg->style = opt;
1304 goto again3;
1306 if (opt >= 0x10) {
1307 switch (opt) {
1308 case 0x10: { /* No date info */
1309 #if IMG_API_VERSION == 0
1310 pimg->date1 = pimg->date2 = 0;
1311 #else /* IMG_API_VERSION == 1 */
1312 pimg->days1 = pimg->days2 = -1;
1313 #endif
1314 break;
1316 case 0x11: { /* Single date */
1317 int days1 = (int)getu16(pimg->fh);
1318 #if IMG_API_VERSION == 0
1319 pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
1320 #else /* IMG_API_VERSION == 1 */
1321 pimg->days2 = pimg->days1 = days1;
1322 #endif
1323 break;
1325 case 0x12: { /* Date range (short) */
1326 int days1 = (int)getu16(pimg->fh);
1327 int days2 = days1 + GETC(pimg->fh) + 1;
1328 #if IMG_API_VERSION == 0
1329 pimg->date1 = (days1 - 25567) * 86400;
1330 pimg->date2 = (days2 - 25567) * 86400;
1331 #else /* IMG_API_VERSION == 1 */
1332 pimg->days1 = days1;
1333 pimg->days2 = days2;
1334 #endif
1335 break;
1337 case 0x13: { /* Date range (long) */
1338 int days1 = (int)getu16(pimg->fh);
1339 int days2 = (int)getu16(pimg->fh);
1340 #if IMG_API_VERSION == 0
1341 pimg->date1 = (days1 - 25567) * 86400;
1342 pimg->date2 = (days2 - 25567) * 86400;
1343 #else /* IMG_API_VERSION == 1 */
1344 pimg->days1 = days1;
1345 pimg->days2 = days2;
1346 #endif
1347 break;
1349 case 0x1f: /* Error info */
1350 pimg->n_legs = get32(pimg->fh);
1351 pimg->length = get32(pimg->fh) / 100.0;
1352 pimg->E = get32(pimg->fh) / 100.0;
1353 pimg->H = get32(pimg->fh) / 100.0;
1354 pimg->V = get32(pimg->fh) / 100.0;
1355 return img_ERROR_INFO;
1356 case 0x30: case 0x31: /* LRUD */
1357 case 0x32: case 0x33: /* Big LRUD! */
1358 if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1359 pimg->flags = (int)opt & 0x01;
1360 if (opt < 0x32) {
1361 pimg->l = get16(pimg->fh) / 100.0;
1362 pimg->r = get16(pimg->fh) / 100.0;
1363 pimg->u = get16(pimg->fh) / 100.0;
1364 pimg->d = get16(pimg->fh) / 100.0;
1365 } else {
1366 pimg->l = get32(pimg->fh) / 100.0;
1367 pimg->r = get32(pimg->fh) / 100.0;
1368 pimg->u = get32(pimg->fh) / 100.0;
1369 pimg->d = get32(pimg->fh) / 100.0;
1371 if (!stn_included(pimg)) {
1372 return img_XSECT_END;
1374 /* If this is the last cross-section in this passage, set
1375 * pending so we return img_XSECT_END next time. */
1376 if (pimg->flags & 0x01) {
1377 pimg->pending = 256;
1378 pimg->flags &= ~0x01;
1380 return img_XSECT;
1381 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1382 img_errno = IMG_BADFORMAT;
1383 return img_BAD;
1385 goto again3;
1387 if (opt != 15) {
1388 /* 1-14 and 16-31 reserved */
1389 img_errno = IMG_BADFORMAT;
1390 return img_BAD;
1392 result = img_MOVE;
1393 } else if (opt >= 0x80) {
1394 if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1396 result = img_LABEL;
1398 if (!stn_included(pimg)) {
1399 if (!skip_coord(pimg->fh)) return img_BAD;
1400 pimg->pending = 0;
1401 goto again3;
1404 pimg->flags = (int)opt & 0x7f;
1405 } else if ((opt >> 6) == 1) {
1406 if (read_v8label(pimg, opt & 0x20, 0) == img_BAD) return img_BAD;
1408 result = img_LINE;
1410 if (!survey_included(pimg)) {
1411 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1412 pimg->pending = 15;
1413 goto again3;
1416 if (pimg->pending) {
1417 *p = pimg->mv;
1418 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1419 pimg->pending = opt;
1420 return img_MOVE;
1422 pimg->flags = (int)opt & 0x1f;
1423 } else {
1424 img_errno = IMG_BADFORMAT;
1425 return img_BAD;
1427 if (!read_coord(pimg->fh, p)) return img_BAD;
1428 pimg->pending = 0;
1429 return result;
1432 static int
1433 img_read_item_v3to7(img *pimg, img_point *p)
1435 int result;
1436 int opt;
1437 pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1438 if (pimg->pending == 256) {
1439 pimg->pending = 0;
1440 return img_XSECT_END;
1442 if (pimg->pending >= 0x80) {
1443 *p = pimg->mv;
1444 pimg->flags = (int)(pimg->pending) & 0x3f;
1445 pimg->pending = 0;
1446 return img_LINE;
1448 again3: /* label to goto if we get a prefix, date, or lrud */
1449 pimg->label = pimg->label_buf;
1450 opt = GETC(pimg->fh);
1451 if (opt == EOF) {
1452 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1453 return img_BAD;
1455 switch (opt >> 6) {
1456 case 0:
1457 if (opt == 0) {
1458 if (!pimg->label_len) return img_STOP; /* end of data marker */
1459 pimg->label_len = 0;
1460 goto again3;
1462 if (opt < 15) {
1463 /* 1-14 mean trim that many levels from current prefix */
1464 int c;
1465 if (pimg->label_len <= 17) {
1466 /* zero prefix using "0" */
1467 img_errno = IMG_BADFORMAT;
1468 return img_BAD;
1470 /* extra - 1 because label_len points to one past the end */
1471 c = pimg->label_len - 17 - 1;
1472 while (pimg->label_buf[c] != '.' || --opt > 0) {
1473 if (--c < 0) {
1474 /* zero prefix using "0" */
1475 img_errno = IMG_BADFORMAT;
1476 return img_BAD;
1479 c++;
1480 pimg->label_len = c;
1481 goto again3;
1483 if (opt == 15) {
1484 result = img_MOVE;
1485 break;
1487 if (opt >= 0x20) {
1488 switch (opt) {
1489 case 0x20: /* Single date */
1490 if (pimg->version < 7) {
1491 int date1 = get32(pimg->fh);
1492 #if IMG_API_VERSION == 0
1493 pimg->date2 = pimg->date1 = date1;
1494 #else /* IMG_API_VERSION == 1 */
1495 if (date1 != 0) {
1496 pimg->days2 = pimg->days1 = (date1 / 86400) + 25567;
1497 } else {
1498 pimg->days2 = pimg->days1 = -1;
1500 #endif
1501 } else {
1502 int days1 = (int)getu16(pimg->fh);
1503 #if IMG_API_VERSION == 0
1504 pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
1505 #else /* IMG_API_VERSION == 1 */
1506 pimg->days2 = pimg->days1 = days1;
1507 #endif
1509 break;
1510 case 0x21: /* Date range (short for v7+) */
1511 if (pimg->version < 7) {
1512 INT32_T date1 = get32(pimg->fh);
1513 INT32_T date2 = get32(pimg->fh);
1514 #if IMG_API_VERSION == 0
1515 pimg->date1 = date1;
1516 pimg->date2 = date2;
1517 #else /* IMG_API_VERSION == 1 */
1518 pimg->days1 = (date1 / 86400) + 25567;
1519 pimg->days2 = (date2 / 86400) + 25567;
1520 #endif
1521 } else {
1522 int days1 = (int)getu16(pimg->fh);
1523 int days2 = days1 + GETC(pimg->fh) + 1;
1524 #if IMG_API_VERSION == 0
1525 pimg->date1 = (days1 - 25567) * 86400;
1526 pimg->date2 = (days2 - 25567) * 86400;
1527 #else /* IMG_API_VERSION == 1 */
1528 pimg->days1 = days1;
1529 pimg->days2 = days2;
1530 #endif
1532 break;
1533 case 0x22: /* Error info */
1534 pimg->n_legs = get32(pimg->fh);
1535 pimg->length = get32(pimg->fh) / 100.0;
1536 pimg->E = get32(pimg->fh) / 100.0;
1537 pimg->H = get32(pimg->fh) / 100.0;
1538 pimg->V = get32(pimg->fh) / 100.0;
1539 if (feof(pimg->fh)) {
1540 img_errno = IMG_BADFORMAT;
1541 return img_BAD;
1543 if (ferror(pimg->fh)) {
1544 img_errno = IMG_READERROR;
1545 return img_BAD;
1547 return img_ERROR_INFO;
1548 case 0x23: { /* v7+: Date range (long) */
1549 if (pimg->version < 7) {
1550 img_errno = IMG_BADFORMAT;
1551 return img_BAD;
1553 int days1 = (int)getu16(pimg->fh);
1554 int days2 = (int)getu16(pimg->fh);
1555 if (feof(pimg->fh)) {
1556 img_errno = IMG_BADFORMAT;
1557 return img_BAD;
1559 if (ferror(pimg->fh)) {
1560 img_errno = IMG_READERROR;
1561 return img_BAD;
1563 #if IMG_API_VERSION == 0
1564 pimg->date1 = (days1 - 25567) * 86400;
1565 pimg->date2 = (days2 - 25567) * 86400;
1566 #else /* IMG_API_VERSION == 1 */
1567 pimg->days1 = days1;
1568 pimg->days2 = days2;
1569 #endif
1570 break;
1572 case 0x24: { /* v7+: No date info */
1573 #if IMG_API_VERSION == 0
1574 pimg->date1 = pimg->date2 = 0;
1575 #else /* IMG_API_VERSION == 1 */
1576 pimg->days1 = pimg->days2 = -1;
1577 #endif
1578 break;
1580 case 0x30: case 0x31: /* LRUD */
1581 case 0x32: case 0x33: /* Big LRUD! */
1582 if (read_v3label(pimg) == img_BAD) return img_BAD;
1583 pimg->flags = (int)opt & 0x01;
1584 if (opt < 0x32) {
1585 pimg->l = get16(pimg->fh) / 100.0;
1586 pimg->r = get16(pimg->fh) / 100.0;
1587 pimg->u = get16(pimg->fh) / 100.0;
1588 pimg->d = get16(pimg->fh) / 100.0;
1589 } else {
1590 pimg->l = get32(pimg->fh) / 100.0;
1591 pimg->r = get32(pimg->fh) / 100.0;
1592 pimg->u = get32(pimg->fh) / 100.0;
1593 pimg->d = get32(pimg->fh) / 100.0;
1595 if (feof(pimg->fh)) {
1596 img_errno = IMG_BADFORMAT;
1597 return img_BAD;
1599 if (ferror(pimg->fh)) {
1600 img_errno = IMG_READERROR;
1601 return img_BAD;
1603 if (!stn_included(pimg)) {
1604 return img_XSECT_END;
1606 /* If this is the last cross-section in this passage, set
1607 * pending so we return img_XSECT_END next time. */
1608 if (pimg->flags & 0x01) {
1609 pimg->pending = 256;
1610 pimg->flags &= ~0x01;
1612 return img_XSECT;
1613 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1614 img_errno = IMG_BADFORMAT;
1615 return img_BAD;
1617 if (feof(pimg->fh)) {
1618 img_errno = IMG_BADFORMAT;
1619 return img_BAD;
1621 if (ferror(pimg->fh)) {
1622 img_errno = IMG_READERROR;
1623 return img_BAD;
1625 goto again3;
1627 /* 16-31 mean remove (n - 15) characters from the prefix */
1628 /* zero prefix using 0 */
1629 if (pimg->label_len <= (size_t)(opt - 15)) {
1630 img_errno = IMG_BADFORMAT;
1631 return img_BAD;
1633 pimg->label_len -= (opt - 15);
1634 goto again3;
1635 case 1:
1636 if (read_v3label(pimg) == img_BAD) return img_BAD;
1638 result = img_LABEL;
1640 if (!stn_included(pimg)) {
1641 if (!skip_coord(pimg->fh)) return img_BAD;
1642 pimg->pending = 0;
1643 goto again3;
1646 pimg->flags = (int)opt & 0x3f;
1647 break;
1648 case 2:
1649 if (read_v3label(pimg) == img_BAD) return img_BAD;
1651 result = img_LINE;
1653 if (!survey_included(pimg)) {
1654 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1655 pimg->pending = 15;
1656 goto again3;
1659 if (pimg->pending) {
1660 *p = pimg->mv;
1661 if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1662 pimg->pending = opt;
1663 return img_MOVE;
1665 pimg->flags = (int)opt & 0x3f;
1666 break;
1667 default:
1668 img_errno = IMG_BADFORMAT;
1669 return img_BAD;
1671 if (!read_coord(pimg->fh, p)) return img_BAD;
1672 pimg->pending = 0;
1673 return result;
1676 static int
1677 img_read_item_ancient(img *pimg, img_point *p)
1679 int result;
1680 static long opt_lookahead = 0;
1681 static img_point pt = { 0.0, 0.0, 0.0 };
1682 long opt;
1684 again: /* label to goto if we get a cross */
1685 pimg->label = pimg->label_buf;
1686 pimg->label[0] = '\0';
1688 if (pimg->version == 1) {
1689 if (opt_lookahead) {
1690 opt = opt_lookahead;
1691 opt_lookahead = 0;
1692 } else {
1693 opt = get32(pimg->fh);
1695 } else {
1696 opt = GETC(pimg->fh);
1699 if (feof(pimg->fh)) {
1700 img_errno = IMG_BADFORMAT;
1701 return img_BAD;
1703 if (ferror(pimg->fh)) {
1704 img_errno = IMG_READERROR;
1705 return img_BAD;
1708 switch (opt) {
1709 case -1: case 0:
1710 return img_STOP; /* end of data marker */
1711 case 1:
1712 /* skip coordinates */
1713 if (!skip_coord(pimg->fh)) {
1714 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1715 return img_BAD;
1717 goto again;
1718 case 2: case 3: {
1719 size_t len;
1720 result = img_LABEL;
1721 if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
1722 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1723 return img_BAD;
1725 if (pimg->label[0] == '\\') pimg->label++;
1726 len = strlen(pimg->label);
1727 if (len == 0 || pimg->label[len - 1] != '\n') {
1728 img_errno = IMG_BADFORMAT;
1729 return img_BAD;
1731 /* Ignore empty labels in some .3d files (caused by a bug) */
1732 if (len == 1) goto again;
1733 pimg->label[len - 1] = '\0';
1734 pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1735 if (opt == 2) goto done;
1736 break;
1738 case 6: case 7: {
1739 long len;
1740 result = img_LABEL;
1742 if (opt == 7)
1743 pimg->flags = GETC(pimg->fh);
1744 else
1745 pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1747 len = get32(pimg->fh);
1749 if (feof(pimg->fh)) {
1750 img_errno = IMG_BADFORMAT;
1751 return img_BAD;
1753 if (ferror(pimg->fh)) {
1754 img_errno = IMG_READERROR;
1755 return img_BAD;
1758 /* Ignore empty labels in some .3d files (caused by a bug) */
1759 if (len == 0) goto again;
1760 if (!check_label_space(pimg, len + 1)) {
1761 img_errno = IMG_OUTOFMEMORY;
1762 return img_BAD;
1764 if (fread(pimg->label_buf, len, 1, pimg->fh) != 1) {
1765 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1766 return img_BAD;
1768 pimg->label_buf[len] = '\0';
1769 break;
1771 case 4:
1772 result = img_MOVE;
1773 break;
1774 case 5:
1775 result = img_LINE;
1776 break;
1777 default:
1778 switch ((int)opt & 0xc0) {
1779 case 0x80:
1780 pimg->flags = (int)opt & 0x3f;
1781 result = img_LINE;
1782 break;
1783 case 0x40: {
1784 char *q;
1785 pimg->flags = (int)opt & 0x3f;
1786 result = img_LABEL;
1787 if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
1788 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1789 return img_BAD;
1791 q = pimg->label_buf + strlen(pimg->label_buf) - 1;
1792 /* Ignore empty-labels in some .3d files (caused by a bug) */
1793 if (q == pimg->label_buf) goto again;
1794 if (*q != '\n') {
1795 img_errno = IMG_BADFORMAT;
1796 return img_BAD;
1798 *q = '\0';
1799 break;
1801 default:
1802 img_errno = IMG_BADFORMAT;
1803 return img_BAD;
1805 break;
1808 if (!read_coord(pimg->fh, &pt)) return img_BAD;
1810 if (result == img_LABEL && !stn_included(pimg)) {
1811 goto again;
1814 done:
1815 *p = pt;
1817 if (result == img_MOVE && pimg->version == 1) {
1818 /* peek at next code and see if it's an old-style label */
1819 opt_lookahead = get32(pimg->fh);
1821 if (feof(pimg->fh)) {
1822 img_errno = IMG_BADFORMAT;
1823 return img_BAD;
1825 if (ferror(pimg->fh)) {
1826 img_errno = IMG_READERROR;
1827 return img_BAD;
1830 if (opt_lookahead == 2) return img_read_item_ancient(pimg, p);
1833 return result;
1836 static int
1837 img_read_item_ascii_wrapper(img *pimg, img_point *p)
1839 /* We need to set the default locale for fscanf() to work on
1840 * numbers with "." as decimal point. */
1841 int result;
1842 char * current_locale = my_strdup(setlocale(LC_NUMERIC, NULL));
1843 setlocale(LC_NUMERIC, "C");
1844 result = img_read_item_ascii(pimg, p);
1845 setlocale(LC_NUMERIC, current_locale);
1846 free(current_locale);
1847 return result;
1850 /* Handle all ASCII formats. */
1851 static int
1852 img_read_item_ascii(img *pimg, img_point *p)
1854 int result;
1855 pimg->label = pimg->label_buf;
1856 if (pimg->version == 0) {
1857 ascii_again:
1858 pimg->label[0] = '\0';
1859 if (feof(pimg->fh)) return img_STOP;
1860 if (pimg->pending) {
1861 pimg->pending = 0;
1862 result = img_LINE;
1863 } else {
1864 char cmd[7];
1865 /* Stop if nothing found */
1866 if (fscanf(pimg->fh, "%6s", cmd) < 1) return img_STOP;
1867 if (strcmp(cmd, "move") == 0)
1868 result = img_MOVE;
1869 else if (strcmp(cmd, "draw") == 0)
1870 result = img_LINE;
1871 else if (strcmp(cmd, "line") == 0) {
1872 /* set flag to indicate to process second triplet as LINE */
1873 pimg->pending = 1;
1874 result = img_MOVE;
1875 } else if (strcmp(cmd, "cross") == 0) {
1876 if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1877 img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1878 return img_BAD;
1880 goto ascii_again;
1881 } else if (strcmp(cmd, "name") == 0) {
1882 size_t off = 0;
1883 int ch = GETC(pimg->fh);
1884 if (ch == ' ') ch = GETC(pimg->fh);
1885 while (ch != ' ') {
1886 if (ch == '\n' || ch == EOF) {
1887 img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1888 return img_BAD;
1890 if (off == pimg->buf_len) {
1891 if (!check_label_space(pimg, pimg->buf_len * 2)) {
1892 img_errno = IMG_OUTOFMEMORY;
1893 return img_BAD;
1896 pimg->label_buf[off++] = ch;
1897 ch = GETC(pimg->fh);
1899 pimg->label_buf[off] = '\0';
1901 pimg->label = pimg->label_buf;
1902 if (pimg->label[0] == '\\') pimg->label++;
1904 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1906 result = img_LABEL;
1907 } else {
1908 img_errno = IMG_BADFORMAT;
1909 return img_BAD; /* unknown keyword */
1913 if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1914 img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1915 return img_BAD;
1918 if (result == img_LABEL && !stn_included(pimg)) {
1919 goto ascii_again;
1922 return result;
1923 } else if (pimg->version == VERSION_SURVEX_POS) {
1924 /* Survex .pos file */
1925 int ch;
1926 size_t off;
1927 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1928 againpos:
1929 while (fscanf(pimg->fh, "(%lf,%lf,%lf )", &p->x, &p->y, &p->z) != 3) {
1930 if (ferror(pimg->fh)) {
1931 img_errno = IMG_READERROR;
1932 return img_BAD;
1934 if (feof(pimg->fh)) return img_STOP;
1935 if (pimg->pending) {
1936 img_errno = IMG_BADFORMAT;
1937 return img_BAD;
1939 pimg->pending = 1;
1940 /* ignore rest of line */
1941 do {
1942 ch = GETC(pimg->fh);
1943 } while (ch != '\n' && ch != '\r' && ch != EOF);
1946 pimg->label_buf[0] = '\0';
1947 do {
1948 ch = GETC(pimg->fh);
1949 } while (ch == ' ' || ch == '\t');
1950 if (ch == '\n' || ch == EOF) {
1951 /* If there's no label, set img_SFLAG_ANON. */
1952 pimg->flags |= img_SFLAG_ANON;
1953 return img_LABEL;
1955 pimg->label_buf[0] = ch;
1956 off = 1;
1957 while (!feof(pimg->fh)) {
1958 if (!fgets(pimg->label_buf + off, pimg->buf_len - off, pimg->fh)) {
1959 img_errno = IMG_READERROR;
1960 return img_BAD;
1963 off += strlen(pimg->label_buf + off);
1964 if (off && pimg->label_buf[off - 1] == '\n') {
1965 pimg->label_buf[off - 1] = '\0';
1966 break;
1968 if (!check_label_space(pimg, pimg->buf_len * 2)) {
1969 img_errno = IMG_OUTOFMEMORY;
1970 return img_BAD;
1974 pimg->label = pimg->label_buf;
1976 if (pimg->label[0] == '\\') pimg->label++;
1978 if (!stn_included(pimg)) goto againpos;
1980 return img_LABEL;
1981 } else if (pimg->version == VERSION_COMPASS_PLT) {
1982 /* Compass .plt file */
1983 if (pimg->pending > 0) {
1984 /* -1 signals we've entered the first survey we want to
1985 * read, and need to fudge lots if the first action is 'D'...
1987 /* pending MOVE or LINE */
1988 int r = pimg->pending - 4;
1989 pimg->pending = 0;
1990 pimg->flags = 0;
1991 pimg->label[pimg->label_len] = '\0';
1992 return r;
1995 while (1) {
1996 char *line;
1997 char *q;
1998 size_t len = 0;
1999 int ch = GETC(pimg->fh);
2001 switch (ch) {
2002 case '\x1a': case EOF: /* Don't insist on ^Z at end of file */
2003 return img_STOP;
2004 case 'X': case 'F': case 'S':
2005 /* bounding boX (marks end of survey), Feature survey, or
2006 * new Section - skip to next survey */
2007 if (pimg->survey) return img_STOP;
2008 skip_to_N:
2009 while (1) {
2010 do {
2011 ch = GETC(pimg->fh);
2012 } while (ch != '\n' && ch != '\r' && ch != EOF);
2013 while (ch == '\n' || ch == '\r') ch = GETC(pimg->fh);
2014 if (ch == 'N') break;
2015 if (ch == '\x1a' || ch == EOF) return img_STOP;
2017 /* FALLTHRU */
2018 case 'N':
2019 line = getline_alloc(pimg->fh);
2020 if (!line) {
2021 img_errno = IMG_OUTOFMEMORY;
2022 return img_BAD;
2024 while (line[len] > 32) ++len;
2025 if (pimg->label_len == 0) pimg->pending = -1;
2026 if (!check_label_space(pimg, len + 1)) {
2027 osfree(line);
2028 img_errno = IMG_OUTOFMEMORY;
2029 return img_BAD;
2031 pimg->label_len = len;
2032 pimg->label = pimg->label_buf;
2033 memcpy(pimg->label, line, len);
2034 pimg->label[len] = '\0';
2035 osfree(line);
2036 break;
2037 case 'M': case 'D': {
2038 /* Move or Draw */
2039 long fpos = -1;
2040 if (pimg->survey && pimg->label_len == 0) {
2041 /* We're only holding onto this line in case the first line
2042 * of the 'N' is a 'D', so skip it for now...
2044 goto skip_to_N;
2046 if (ch == 'D' && pimg->pending == -1) {
2047 if (pimg->survey) {
2048 fpos = ftell(pimg->fh) - 1;
2049 fseek(pimg->fh, pimg->start, SEEK_SET);
2050 ch = GETC(pimg->fh);
2051 pimg->pending = 0;
2052 } else {
2053 /* If a file actually has a 'D' before any 'M', then
2054 * pretend the 'D' is an 'M' - one of the examples
2055 * in the docs was like this! */
2056 ch = 'M';
2059 line = getline_alloc(pimg->fh);
2060 if (!line) {
2061 img_errno = IMG_OUTOFMEMORY;
2062 return img_BAD;
2064 /* Compass stores coordinates as North, East, Up = (y,x,z)! */
2065 if (sscanf(line, "%lf%lf%lf", &p->y, &p->x, &p->z) != 3) {
2066 osfree(line);
2067 if (ferror(pimg->fh)) {
2068 img_errno = IMG_READERROR;
2069 } else {
2070 img_errno = IMG_BADFORMAT;
2072 return img_BAD;
2074 p->x *= METRES_PER_FOOT;
2075 p->y *= METRES_PER_FOOT;
2076 p->z *= METRES_PER_FOOT;
2077 q = strchr(line, 'S');
2078 if (!q) {
2079 osfree(line);
2080 img_errno = IMG_BADFORMAT;
2081 return img_BAD;
2083 ++q;
2084 len = 0;
2085 while (q[len] > ' ') ++len;
2086 q[len] = '\0';
2087 len += 2; /* ' ' and '\0' */
2088 if (!check_label_space(pimg, pimg->label_len + len)) {
2089 img_errno = IMG_OUTOFMEMORY;
2090 return img_BAD;
2092 pimg->label = pimg->label_buf;
2093 if (pimg->label_len) {
2094 pimg->label[pimg->label_len] = ' ';
2095 memcpy(pimg->label + pimg->label_len + 1, q, len - 1);
2096 } else {
2097 memcpy(pimg->label, q, len - 1);
2099 q += len - 1;
2100 /* Now read LRUD. Technically, this is optional but virtually
2101 * all PLT files have it (with dummy negative values if no LRUD
2102 * was measured) and some versions of Compass can't read PLT
2103 * files without it!
2105 while (*q && *q <= ' ') q++;
2106 if (*q == 'P') {
2107 if (sscanf(q + 1, "%lf%lf%lf%lf",
2108 &pimg->l, &pimg->r, &pimg->u, &pimg->d) != 4) {
2109 osfree(line);
2110 if (ferror(pimg->fh)) {
2111 img_errno = IMG_READERROR;
2112 } else {
2113 img_errno = IMG_BADFORMAT;
2115 return img_BAD;
2117 pimg->l *= METRES_PER_FOOT;
2118 pimg->r *= METRES_PER_FOOT;
2119 pimg->u *= METRES_PER_FOOT;
2120 pimg->d *= METRES_PER_FOOT;
2121 } else {
2122 pimg->l = pimg->r = pimg->u = pimg->d = -1;
2124 osfree(line);
2125 pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
2126 if (fpos != -1) {
2127 fseek(pimg->fh, fpos, SEEK_SET);
2128 } else {
2129 pimg->pending = (ch == 'M' ? img_MOVE : img_LINE) + 4;
2131 return img_LABEL;
2133 default:
2134 img_errno = IMG_BADFORMAT;
2135 return img_BAD;
2138 } else {
2139 /* CMAP .xyz file */
2140 char *line = NULL;
2141 char *q;
2142 size_t len;
2144 if (pimg->pending) {
2145 /* pending MOVE or LINE or LABEL or STOP */
2146 int r = pimg->pending - 4;
2147 /* Set label to empty - don't use "" as we adjust label relative
2148 * to label_buf when label_buf is reallocated. */
2149 pimg->label = pimg->label_buf + strlen(pimg->label_buf);
2150 pimg->flags = 0;
2151 if (r == img_LABEL) {
2152 /* nasty magic */
2153 read_xyz_shot_coords(p, pimg->label_buf + 16);
2154 subtract_xyz_shot_deltas(p, pimg->label_buf + 16);
2155 pimg->pending = img_STOP + 4;
2156 return img_MOVE;
2159 pimg->pending = 0;
2161 if (r == img_STOP) {
2162 /* nasty magic */
2163 read_xyz_shot_coords(p, pimg->label_buf + 16);
2164 return img_LINE;
2167 return r;
2170 pimg->label = pimg->label_buf;
2171 do {
2172 osfree(line);
2173 if (feof(pimg->fh)) return img_STOP;
2174 line = getline_alloc(pimg->fh);
2175 if (!line) {
2176 img_errno = IMG_OUTOFMEMORY;
2177 return img_BAD;
2179 } while (line[0] == ' ' || line[0] == '\0');
2180 if (line[0] == '\x1a') return img_STOP;
2182 len = strlen(line);
2183 if (pimg->version == VERSION_CMAP_STATION) {
2184 /* station variant */
2185 if (len < 37) {
2186 osfree(line);
2187 img_errno = IMG_BADFORMAT;
2188 return img_BAD;
2190 memcpy(pimg->label, line, 6);
2191 q = (char *)memchr(pimg->label, ' ', 6);
2192 if (!q) q = pimg->label + 6;
2193 *q = '\0';
2195 read_xyz_station_coords(p, line);
2197 /* FIXME: look at prev for lines (line + 32, 5) */
2198 /* FIXME: duplicate stations... */
2199 return img_LABEL;
2200 } else {
2201 /* Shot variant (VERSION_CMAP_SHOT) */
2202 char old[8], new_[8];
2203 if (len < 61) {
2204 osfree(line);
2205 img_errno = IMG_BADFORMAT;
2206 return img_BAD;
2209 memcpy(old, line, 7);
2210 q = (char *)memchr(old, ' ', 7);
2211 if (!q) q = old + 7;
2212 *q = '\0';
2214 memcpy(new_, line + 7, 7);
2215 q = (char *)memchr(new_, ' ', 7);
2216 if (!q) q = new_ + 7;
2217 *q = '\0';
2219 pimg->flags = img_SFLAG_UNDERGROUND;
2221 if (strcmp(old, new_) == 0) {
2222 pimg->pending = img_MOVE + 4;
2223 read_xyz_shot_coords(p, line);
2224 strcpy(pimg->label, new_);
2225 osfree(line);
2226 return img_LABEL;
2229 if (strcmp(old, pimg->label) == 0) {
2230 pimg->pending = img_LINE + 4;
2231 read_xyz_shot_coords(p, line);
2232 strcpy(pimg->label, new_);
2233 osfree(line);
2234 return img_LABEL;
2237 pimg->pending = img_LABEL + 4;
2238 read_xyz_shot_coords(p, line);
2239 strcpy(pimg->label, new_);
2240 memcpy(pimg->label + 16, line, 70);
2242 osfree(line);
2243 return img_LABEL;
2248 static void
2249 write_coord(FILE *fh, double x, double y, double z)
2251 SVX_ASSERT(fh);
2252 /* Output in cm */
2253 static INT32_T X_, Y_, Z_;
2254 INT32_T X = my_lround(x * 100.0);
2255 INT32_T Y = my_lround(y * 100.0);
2256 INT32_T Z = my_lround(z * 100.0);
2258 X_ -= X;
2259 Y_ -= Y;
2260 Z_ -= Z;
2261 put32(X, fh);
2262 put32(Y, fh);
2263 put32(Z, fh);
2264 X_ = X; Y_ = Y; Z_ = Z;
2267 static int
2268 write_v3label(img *pimg, int opt, const char *s)
2270 size_t len, n, dot;
2272 /* find length of common prefix */
2273 dot = 0;
2274 for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2275 if (s[len] == '.') dot = len + 1;
2278 SVX_ASSERT(len <= pimg->label_len);
2279 n = pimg->label_len - len;
2280 if (len == 0) {
2281 if (pimg->label_len) PUTC(0, pimg->fh);
2282 } else if (n <= 16) {
2283 if (n) PUTC(n + 15, pimg->fh);
2284 } else if (dot == 0) {
2285 if (pimg->label_len) PUTC(0, pimg->fh);
2286 len = 0;
2287 } else {
2288 const char *p = pimg->label_buf + dot;
2289 n = 1;
2290 for (len = pimg->label_len - dot - 17; len; len--) {
2291 if (*p++ == '.') n++;
2293 if (n <= 14) {
2294 PUTC(n, pimg->fh);
2295 len = dot;
2296 } else {
2297 if (pimg->label_len) PUTC(0, pimg->fh);
2298 len = 0;
2302 n = strlen(s + len);
2303 PUTC(opt, pimg->fh);
2304 if (n < 0xfe) {
2305 PUTC(n, pimg->fh);
2306 } else if (n < 0xffff + 0xfe) {
2307 PUTC(0xfe, pimg->fh);
2308 put16((short)(n - 0xfe), pimg->fh);
2309 } else {
2310 PUTC(0xff, pimg->fh);
2311 put32(n, pimg->fh);
2313 fwrite(s + len, n, 1, pimg->fh);
2315 n += len;
2316 pimg->label_len = n;
2317 if (!check_label_space(pimg, n + 1))
2318 return 0; /* FIXME: distinguish out of memory... */
2319 memcpy(pimg->label_buf + len, s + len, n - len + 1);
2321 return !ferror(pimg->fh);
2324 static int
2325 write_v8label(img *pimg, int opt, int common_flag, size_t common_val,
2326 const char *s)
2328 size_t len, del, add;
2330 /* find length of common prefix */
2331 for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2334 SVX_ASSERT(len <= pimg->label_len);
2335 del = pimg->label_len - len;
2336 add = strlen(s + len);
2338 if (add == common_val && del == common_val) {
2339 PUTC(opt | common_flag, pimg->fh);
2340 } else {
2341 PUTC(opt, pimg->fh);
2342 if (del <= 15 && add <= 15 && (del || add)) {
2343 PUTC((del << 4) | add, pimg->fh);
2344 } else {
2345 PUTC(0x00, pimg->fh);
2346 if (del < 0xff) {
2347 PUTC(del, pimg->fh);
2348 } else {
2349 PUTC(0xff, pimg->fh);
2350 put32(del, pimg->fh);
2352 if (add < 0xff) {
2353 PUTC(add, pimg->fh);
2354 } else {
2355 PUTC(0xff, pimg->fh);
2356 put32(add, pimg->fh);
2361 if (add)
2362 fwrite(s + len, add, 1, pimg->fh);
2364 pimg->label_len = len + add;
2365 if (add > del && !check_label_space(pimg, pimg->label_len + 1))
2366 return 0; /* FIXME: distinguish out of memory... */
2368 memcpy(pimg->label_buf + len, s + len, add + 1);
2370 return !ferror(pimg->fh);
2373 static void
2374 img_write_item_date_new(img *pimg)
2376 int same, unset;
2377 /* Only write dates when they've changed. */
2378 #if IMG_API_VERSION == 0
2379 if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2380 return;
2382 same = (pimg->date1 == pimg->date2);
2383 unset = (pimg->date1 == 0);
2384 #else /* IMG_API_VERSION == 1 */
2385 if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2386 return;
2388 same = (pimg->days1 == pimg->days2);
2389 unset = (pimg->days1 == -1);
2390 #endif
2392 if (same) {
2393 if (unset) {
2394 PUTC(0x10, pimg->fh);
2395 } else {
2396 PUTC(0x11, pimg->fh);
2397 #if IMG_API_VERSION == 0
2398 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2399 #else /* IMG_API_VERSION == 1 */
2400 put16(pimg->days1, pimg->fh);
2401 #endif
2403 } else {
2404 #if IMG_API_VERSION == 0
2405 int diff = (pimg->date2 - pimg->date1) / 86400;
2406 if (diff > 0 && diff <= 256) {
2407 PUTC(0x12, pimg->fh);
2408 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2409 PUTC(diff - 1, pimg->fh);
2410 } else {
2411 PUTC(0x13, pimg->fh);
2412 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2413 put16(pimg->date2 / 86400 + 25567, pimg->fh);
2415 #else /* IMG_API_VERSION == 1 */
2416 int diff = pimg->days2 - pimg->days1;
2417 if (diff > 0 && diff <= 256) {
2418 PUTC(0x12, pimg->fh);
2419 put16(pimg->days1, pimg->fh);
2420 PUTC(diff - 1, pimg->fh);
2421 } else {
2422 PUTC(0x13, pimg->fh);
2423 put16(pimg->days1, pimg->fh);
2424 put16(pimg->days2, pimg->fh);
2426 #endif
2428 #if IMG_API_VERSION == 0
2429 pimg->olddate1 = pimg->date1;
2430 pimg->olddate2 = pimg->date2;
2431 #else /* IMG_API_VERSION == 1 */
2432 pimg->olddays1 = pimg->days1;
2433 pimg->olddays2 = pimg->days2;
2434 #endif
2437 static void
2438 img_write_item_date(img *pimg)
2440 int same, unset;
2441 /* Only write dates when they've changed. */
2442 #if IMG_API_VERSION == 0
2443 if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2444 return;
2446 same = (pimg->date1 == pimg->date2);
2447 unset = (pimg->date1 == 0);
2448 #else /* IMG_API_VERSION == 1 */
2449 if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2450 return;
2452 same = (pimg->days1 == pimg->days2);
2453 unset = (pimg->days1 == -1);
2454 #endif
2456 if (same) {
2457 if (img_output_version < 7) {
2458 PUTC(0x20, pimg->fh);
2459 #if IMG_API_VERSION == 0
2460 put32(pimg->date1, pimg->fh);
2461 #else /* IMG_API_VERSION == 1 */
2462 put32((pimg->days1 - 25567) * 86400, pimg->fh);
2463 #endif
2464 } else {
2465 if (unset) {
2466 PUTC(0x24, pimg->fh);
2467 } else {
2468 PUTC(0x20, pimg->fh);
2469 #if IMG_API_VERSION == 0
2470 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2471 #else /* IMG_API_VERSION == 1 */
2472 put16(pimg->days1, pimg->fh);
2473 #endif
2476 } else {
2477 if (img_output_version < 7) {
2478 PUTC(0x21, pimg->fh);
2479 #if IMG_API_VERSION == 0
2480 put32(pimg->date1, pimg->fh);
2481 put32(pimg->date2, pimg->fh);
2482 #else /* IMG_API_VERSION == 1 */
2483 put32((pimg->days1 - 25567) * 86400, pimg->fh);
2484 put32((pimg->days2 - 25567) * 86400, pimg->fh);
2485 #endif
2486 } else {
2487 #if IMG_API_VERSION == 0
2488 int diff = (pimg->date2 - pimg->date1) / 86400;
2489 if (diff > 0 && diff <= 256) {
2490 PUTC(0x21, pimg->fh);
2491 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2492 PUTC(diff - 1, pimg->fh);
2493 } else {
2494 PUTC(0x23, pimg->fh);
2495 put16(pimg->date1 / 86400 + 25567, pimg->fh);
2496 put16(pimg->date2 / 86400 + 25567, pimg->fh);
2498 #else /* IMG_API_VERSION == 1 */
2499 int diff = pimg->days2 - pimg->days1;
2500 if (diff > 0 && diff <= 256) {
2501 PUTC(0x21, pimg->fh);
2502 put16(pimg->days1, pimg->fh);
2503 PUTC(diff - 1, pimg->fh);
2504 } else {
2505 PUTC(0x23, pimg->fh);
2506 put16(pimg->days1, pimg->fh);
2507 put16(pimg->days2, pimg->fh);
2509 #endif
2512 #if IMG_API_VERSION == 0
2513 pimg->olddate1 = pimg->date1;
2514 pimg->olddate2 = pimg->date2;
2515 #else /* IMG_API_VERSION == 1 */
2516 pimg->olddays1 = pimg->days1;
2517 pimg->olddays2 = pimg->days2;
2518 #endif
2521 static void
2522 img_write_item_new(img *pimg, int code, int flags, const char *s,
2523 double x, double y, double z);
2524 static void
2525 img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2526 double x, double y, double z);
2527 static void
2528 img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2529 double x, double y, double z);
2531 void
2532 img_write_item(img *pimg, int code, int flags, const char *s,
2533 double x, double y, double z)
2535 if (!pimg) return;
2536 if (pimg->version >= 8) {
2537 img_write_item_new(pimg, code, flags, s, x, y, z);
2538 } else if (pimg->version >= 3) {
2539 img_write_item_v3to7(pimg, code, flags, s, x, y, z);
2540 } else {
2541 img_write_item_ancient(pimg, code, flags, s, x, y, z);
2545 static void
2546 img_write_item_new(img *pimg, int code, int flags, const char *s,
2547 double x, double y, double z)
2549 switch (code) {
2550 case img_LABEL:
2551 write_v8label(pimg, 0x80 | flags, 0, -1, s);
2552 break;
2553 case img_XSECT: {
2554 INT32_T l, r, u, d, max_dim;
2555 img_write_item_date_new(pimg);
2556 l = (INT32_T)my_lround(pimg->l * 100.0);
2557 r = (INT32_T)my_lround(pimg->r * 100.0);
2558 u = (INT32_T)my_lround(pimg->u * 100.0);
2559 d = (INT32_T)my_lround(pimg->d * 100.0);
2560 if (l < 0) l = -1;
2561 if (r < 0) r = -1;
2562 if (u < 0) u = -1;
2563 if (d < 0) d = -1;
2564 max_dim = max(max(l, r), max(u, d));
2565 flags = (flags & img_XFLAG_END) ? 1 : 0;
2566 if (max_dim >= 32768) flags |= 2;
2567 write_v8label(pimg, 0x30 | flags, 0, -1, s);
2568 if (flags & 2) {
2569 /* Big passage! Need to use 4 bytes. */
2570 put32(l, pimg->fh);
2571 put32(r, pimg->fh);
2572 put32(u, pimg->fh);
2573 put32(d, pimg->fh);
2574 } else {
2575 put16(l, pimg->fh);
2576 put16(r, pimg->fh);
2577 put16(u, pimg->fh);
2578 put16(d, pimg->fh);
2580 return;
2582 case img_MOVE:
2583 PUTC(15, pimg->fh);
2584 break;
2585 case img_LINE:
2586 img_write_item_date_new(pimg);
2587 if (pimg->style != pimg->oldstyle) {
2588 switch (pimg->style) {
2589 case img_STYLE_NORMAL:
2590 case img_STYLE_DIVING:
2591 case img_STYLE_CARTESIAN:
2592 case img_STYLE_CYLPOLAR:
2593 case img_STYLE_NOSURVEY:
2594 PUTC(pimg->style, pimg->fh);
2595 break;
2597 pimg->oldstyle = pimg->style;
2599 write_v8label(pimg, 0x40 | flags, 0x20, 0x00, s ? s : "");
2600 break;
2601 default: /* ignore for now */
2602 return;
2604 write_coord(pimg->fh, x, y, z);
2607 static void
2608 img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2609 double x, double y, double z)
2611 switch (code) {
2612 case img_LABEL:
2613 write_v3label(pimg, 0x40 | flags, s);
2614 break;
2615 case img_XSECT: {
2616 INT32_T l, r, u, d, max_dim;
2617 /* Need at least version 5 for img_XSECT. */
2618 if (pimg->version < 5) return;
2619 img_write_item_date(pimg);
2620 l = (INT32_T)my_lround(pimg->l * 100.0);
2621 r = (INT32_T)my_lround(pimg->r * 100.0);
2622 u = (INT32_T)my_lround(pimg->u * 100.0);
2623 d = (INT32_T)my_lround(pimg->d * 100.0);
2624 if (l < 0) l = -1;
2625 if (r < 0) r = -1;
2626 if (u < 0) u = -1;
2627 if (d < 0) d = -1;
2628 max_dim = max(max(l, r), max(u, d));
2629 flags = (flags & img_XFLAG_END) ? 1 : 0;
2630 if (max_dim >= 32768) flags |= 2;
2631 write_v3label(pimg, 0x30 | flags, s);
2632 if (flags & 2) {
2633 /* Big passage! Need to use 4 bytes. */
2634 put32(l, pimg->fh);
2635 put32(r, pimg->fh);
2636 put32(u, pimg->fh);
2637 put32(d, pimg->fh);
2638 } else {
2639 put16(l, pimg->fh);
2640 put16(r, pimg->fh);
2641 put16(u, pimg->fh);
2642 put16(d, pimg->fh);
2644 return;
2646 case img_MOVE:
2647 PUTC(15, pimg->fh);
2648 break;
2649 case img_LINE:
2650 if (pimg->version >= 4) {
2651 img_write_item_date(pimg);
2653 write_v3label(pimg, 0x80 | flags, s ? s : "");
2654 break;
2655 default: /* ignore for now */
2656 return;
2658 write_coord(pimg->fh, x, y, z);
2661 static void
2662 img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2663 double x, double y, double z)
2665 size_t len;
2666 INT32_T opt = 0;
2667 SVX_ASSERT(pimg->version > 0);
2668 switch (code) {
2669 case img_LABEL:
2670 if (pimg->version == 1) {
2671 /* put a move before each label */
2672 img_write_item_ancient(pimg, img_MOVE, 0, NULL, x, y, z);
2673 put32(2, pimg->fh);
2674 fputsnl(s, pimg->fh);
2675 return;
2677 len = strlen(s);
2678 if (len > 255 || strchr(s, '\n')) {
2679 /* long label - not in early incarnations of v2 format, but few
2680 * 3d files will need these, so better not to force incompatibility
2681 * with a new version I think... */
2682 PUTC(7, pimg->fh);
2683 PUTC(flags, pimg->fh);
2684 put32(len, pimg->fh);
2685 fputs(s, pimg->fh);
2686 } else {
2687 PUTC(0x40 | (flags & 0x3f), pimg->fh);
2688 fputsnl(s, pimg->fh);
2690 opt = 0;
2691 break;
2692 case img_MOVE:
2693 opt = 4;
2694 break;
2695 case img_LINE:
2696 if (pimg->version > 1) {
2697 opt = 0x80 | (flags & 0x3f);
2698 break;
2700 opt = 5;
2701 break;
2702 default: /* ignore for now */
2703 return;
2705 if (pimg->version == 1) {
2706 put32(opt, pimg->fh);
2707 } else {
2708 if (opt) PUTC(opt, pimg->fh);
2710 write_coord(pimg->fh, x, y, z);
2713 /* Write error information for the current traverse
2714 * n_legs is the number of legs in the traverse
2715 * length is the traverse length (in m)
2716 * E is the ratio of the observed misclosure to the theoretical one
2717 * H is the ratio of the observed horizontal misclosure to the theoretical one
2718 * V is the ratio of the observed vertical misclosure to the theoretical one
2720 void
2721 img_write_errors(img *pimg, int n_legs, double length,
2722 double E, double H, double V)
2724 PUTC((pimg->version >= 8 ? 0x1f : 0x22), pimg->fh);
2725 put32(n_legs, pimg->fh);
2726 put32((INT32_T)my_lround(length * 100.0), pimg->fh);
2727 put32((INT32_T)my_lround(E * 100.0), pimg->fh);
2728 put32((INT32_T)my_lround(H * 100.0), pimg->fh);
2729 put32((INT32_T)my_lround(V * 100.0), pimg->fh);
2733 img_close(img *pimg)
2735 int result = 1;
2736 if (pimg) {
2737 if (pimg->fh) {
2738 if (pimg->fRead) {
2739 osfree(pimg->survey);
2740 osfree(pimg->title);
2741 osfree(pimg->cs);
2742 osfree(pimg->datestamp);
2743 } else {
2744 /* write end of data marker */
2745 switch (pimg->version) {
2746 case 1:
2747 put32((INT32_T)-1, pimg->fh);
2748 break;
2749 default:
2750 if (pimg->version <= 7 ?
2751 (pimg->label_len != 0) :
2752 (pimg->style != img_STYLE_NORMAL)) {
2753 PUTC(0, pimg->fh);
2755 /* FALL THROUGH */
2756 case 2:
2757 PUTC(0, pimg->fh);
2758 break;
2761 if (ferror(pimg->fh)) result = 0;
2762 if (pimg->close_func && pimg->close_func(pimg->fh))
2763 result = 0;
2764 if (!result) img_errno = pimg->fRead ? IMG_READERROR : IMG_WRITEERROR;
2766 osfree(pimg->label_buf);
2767 osfree(pimg->filename_opened);
2768 osfree(pimg);
2770 return result;