source: git/src/img.c @ 15e50b0

RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-data
Last change on this file since 15e50b0 was 15e50b0, checked in by Olly Betts <olly@…>, 10 years ago

src/img.c: Simplify reading of labels in 3d v1 and v2 files.

  • Property mode set to 100644
File size: 68.6 KB
Line 
1/* img.c
2 * Routines for reading and writing Survex ".3d" image files
3 * Copyright (C) 1993-2004,2005,2006,2010,2011,2013,2014 Olly Betts
4 *
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.
9 *
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.
14 *
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
18 */
19
20#ifdef HAVE_CONFIG_H
21# include <config.h>
22#endif
23
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>
32
33#include "img.h"
34
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))
65
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)
80
81#ifdef __cplusplus
82# include <algorithm>
83using std::max;
84using 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
96
97static INT32_T
98get32(FILE *fh)
99{
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;
105}
106
107static void
108put32(long w, FILE *fh)
109{
110   PUTC((char)(w), fh);
111   PUTC((char)(w >> 8l), fh);
112   PUTC((char)(w >> 16l), fh);
113   PUTC((char)(w >> 24l), fh);
114}
115
116static short
117get16(FILE *fh)
118{
119   short w = GETC(fh);
120   w |= (short)GETC(fh) << 8l;
121   return w;
122}
123
124static void
125put16(short w, FILE *fh)
126{
127   PUTC((char)(w), fh);
128   PUTC((char)(w >> 8l), fh);
129}
130
131static char *
132baseleaf_from_fnm(const char *fnm)
133{
134   const char *p;
135   const char *q;
136   char * res;
137   size_t len;
138
139   p = fnm;
140   q = strrchr(p, '/');
141   if (q) p = q + 1;
142   q = strrchr(p, '\\');
143   if (q) p = q + 1;
144
145   q = strrchr(p, FNM_SEP_EXT);
146   if (q) len = (const char *)q - p; else len = strlen(p);
147
148   res = (char *)xosmalloc(len + 1);
149   if (!res) return NULL;
150   memcpy(res, p, len);
151   res[len] = '\0';
152   return res;
153}
154#endif
155
156static char * my_strdup(const char *str);
157
158static time_t
159mktime_with_tz(struct tm * tm, const char * tz)
160{
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;
168    }
169    if (_putenv_s("TZ", tz) != 0) {
170        osfree(old_tz);
171        return (time_t)-1;
172    }
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;
178    }
179    if (setenv("TZ", tz, 1) < 0) {
180        osfree(old_tz);
181        return (time_t)-1;
182    }
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;
193    }
194    p = (char *)xosmalloc(strlen(tz) + 4);
195    if (!p) {
196        osfree(old_tz);
197        return (time_t)-1;
198    }
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;
205    }
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
227    }
228#ifdef CLEANUP
229    CLEANUP();
230#undef CLEANUP
231#endif
232    return r;
233}
234
235static unsigned short
236getu16(FILE *fh)
237{
238   return (unsigned short)get16(fh);
239}
240
241#include <math.h>
242
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
253
254#ifdef HAVE_LROUND
255# if defined HAVE_DECL_LROUND && !HAVE_DECL_LROUND
256/* On older systems, the prototype may be missing. */
257extern long lround(double);
258# endif
259# define my_lround lround
260#else
261static long
262my_lround(double x) {
263   return (x >= 0.0) ? (long)(x + 0.5) : -(long)(0.5 - x);
264}
265#endif
266
267/* portable case insensitive string compare */
268#if defined(strcasecmp) || defined(HAVE_STRCASECMP)
269# define my_strcasecmp strcasecmp
270#else
271static 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;
279}
280#endif
281
282unsigned int img_output_version = IMG_VERSION_MAX;
283
284static img_errcode img_errno = IMG_NONE;
285
286#define FILEID "Survex 3D Image File"
287
288#define EXT_PLT "plt"
289#define EXT_PLF "plf"
290
291/* Attempt to string paste to ensure we are passed a literal string */
292#define LITLEN(S) (sizeof(S"") - 1)
293
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
299
300static char *
301my_strdup(const char *str)
302{
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;
308}
309
310static char *
311getline_alloc(FILE *fh)
312{
313   int ch;
314   size_t i = 0;
315   size_t len = 16;
316   char *buf = (char *)xosmalloc(len);
317   if (!buf) return NULL;
318
319   ch = GETC(fh);
320   while (ch != '\n' && ch != '\r' && ch != EOF) {
321      buf[i++] = ch;
322      if (i == len - 1) {
323         char *p;
324         len += len;
325         p = (char *)xosrealloc(buf, len);
326         if (!p) {
327            osfree(buf);
328            return NULL;
329         }
330         buf = p;
331      }
332      ch = GETC(fh);
333   }
334   if (ch == '\n' || ch == '\r') {
335      int otherone = ch ^ ('\n' ^ '\r');
336      ch = GETC(fh);
337      /* if it's not the other eol character, put it back */
338      if (ch != otherone) ungetc(ch, fh);
339   }
340   buf[i++] = '\0';
341   return buf;
342}
343
344img_errcode
345img_error(void)
346{
347   return img_errno;
348}
349
350static int
351check_label_space(img *pimg, size_t len)
352{
353   if (len > pimg->buf_len) {
354      char *b = (char *)xosrealloc(pimg->label_buf, len);
355      if (!b) return 0;
356      pimg->label = (pimg->label - pimg->label_buf) + b;
357      pimg->label_buf = b;
358      pimg->buf_len = len;
359   }
360   return 1;
361}
362
363#define has_ext(F,L,E) ((L) > LITLEN(E) + 1 &&\
364                        (F)[(L) - LITLEN(E) - 1] == FNM_SEP_EXT &&\
365                        my_strcasecmp((F) + (L) - LITLEN(E), E) == 0)
366
367img *
368img_open_survey(const char *fnm, const char *survey)
369{
370   img *pimg;
371   size_t len;
372   char buf[LITLEN(FILEID) + 9];
373   int ch;
374
375   if (fDirectory(fnm)) {
376      img_errno = IMG_DIRECTORY;
377      return NULL;
378   }
379
380   pimg = osnew(img);
381   if (pimg == NULL) {
382      img_errno = IMG_OUTOFMEMORY;
383      return NULL;
384   }
385
386   pimg->buf_len = 257;
387   pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
388   if (!pimg->label_buf) {
389      osfree(pimg);
390      img_errno = IMG_OUTOFMEMORY;
391      return NULL;
392   }
393
394   pimg->fh = fopenWithPthAndExt("", fnm, EXT_SVX_3D, "rb", &(pimg->filename_opened));
395   if (pimg->fh == NULL) {
396      osfree(pimg->label_buf);
397      osfree(pimg);
398      img_errno = IMG_FILENOTFOUND;
399      return NULL;
400   }
401
402   pimg->fRead = 1; /* reading from this file */
403   img_errno = IMG_NONE;
404
405   pimg->flags = 0;
406
407   /* for version >= 3 we use label_buf to store the prefix for reuse */
408   /* for VERSION_COMPASS_PLT, 0 value indicates we haven't
409    * entered a survey yet */
410   /* for VERSION_CMAP_SHOT, we store the last station here
411    * to detect whether we MOVE or LINE */
412   pimg->label_len = 0;
413   pimg->label_buf[0] = '\0';
414
415   pimg->survey = NULL;
416   pimg->survey_len = 0;
417   pimg->separator = '.';
418#if IMG_API_VERSION == 0
419   pimg->date1 = pimg->date2 = 0;
420#else /* IMG_API_VERSION == 1 */
421   pimg->days1 = pimg->days2 = -1;
422#endif
423   pimg->is_extended_elevation = 0;
424
425   pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
426
427   pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
428
429   pimg->title = pimg->datestamp = NULL;
430   pimg->datestamp_numeric = (time_t)-1;
431
432   if (survey) {
433      len = strlen(survey);
434      if (len) {
435         if (survey[len - 1] == '.') len--;
436         if (len) {
437            char *p;
438            pimg->survey = (char *)xosmalloc(len + 2);
439            if (!pimg->survey) {
440               img_errno = IMG_OUTOFMEMORY;
441               goto error;
442            }
443            memcpy(pimg->survey, survey, len);
444            /* Set title to leaf survey name */
445            pimg->survey[len] = '\0';
446            p = strchr(pimg->survey, '.');
447            if (p) p++; else p = pimg->survey;
448            pimg->title = my_strdup(p);
449            if (!pimg->title) {
450               img_errno = IMG_OUTOFMEMORY;
451               goto error;
452            }
453            pimg->survey[len] = '.';
454            pimg->survey[len + 1] = '\0';
455         }
456      }
457      pimg->survey_len = len;
458   }
459
460   /* [VERSION_COMPASS_PLT, VERSION_CMAP_STATION, VERSION_CMAP_SHOT] pending
461    * IMG_LINE or IMG_MOVE - both have 4 added.
462    * [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
463    * [version 0] not in the middle of a 'LINE' command
464    * [version >= 3] not in the middle of turning a LINE into a MOVE
465    */
466   pimg->pending = 0;
467
468   len = strlen(fnm);
469   if (has_ext(fnm, len, EXT_SVX_POS)) {
470pos_file:
471      pimg->version = VERSION_SURVEX_POS;
472      if (!pimg->survey) pimg->title = baseleaf_from_fnm(fnm);
473      pimg->datestamp = my_strdup(TIMENA);
474      if (!pimg->datestamp) {
475         img_errno = IMG_OUTOFMEMORY;
476         goto error;
477      }
478      pimg->start = 0;
479      return pimg;
480   }
481
482   if (has_ext(fnm, len, EXT_PLT) || has_ext(fnm, len, EXT_PLF)) {
483      long fpos;
484plt_file:
485      pimg->version = VERSION_COMPASS_PLT;
486      /* Spaces aren't legal in Compass station names, but dots are, so
487       * use space as the level separator */
488      pimg->separator = ' ';
489      pimg->start = 0;
490      if (!pimg->survey) pimg->title = baseleaf_from_fnm(fnm);
491      pimg->datestamp = my_strdup(TIMENA);
492      if (!pimg->datestamp) {
493         img_errno = IMG_OUTOFMEMORY;
494         goto error;
495      }
496      while (1) {
497         ch = GETC(pimg->fh);
498         switch (ch) {
499          case '\x1a':
500            fseek(pimg->fh, -1, SEEK_CUR);
501            /* FALL THRU */
502          case EOF:
503            pimg->start = ftell(pimg->fh);
504            return pimg;
505          case 'N': {
506            char *line, *q;
507            fpos = ftell(pimg->fh) - 1;
508            if (!pimg->survey) {
509               /* FIXME : if there's only one survey in the file, it'd be nice
510                * to use its description as the title here...
511                */
512               ungetc('N', pimg->fh);
513               pimg->start = fpos;
514               return pimg;
515            }
516            line = getline_alloc(pimg->fh);
517            if (!line) {
518               img_errno = IMG_OUTOFMEMORY;
519               goto error;
520            }
521            len = 0;
522            while (line[len] > 32) ++len;
523            if (pimg->survey_len != len ||
524                memcmp(line, pimg->survey, len) != 0) {
525               osfree(line);
526               continue;
527            }
528            q = strchr(line + len, 'C');
529            if (q && q[1]) {
530                osfree(pimg->title);
531                pimg->title = my_strdup(q + 1);
532            } else if (!pimg->title) {
533                pimg->title = my_strdup(pimg->label);
534            }
535            osfree(line);
536            if (!pimg->title) {
537                img_errno = IMG_OUTOFMEMORY;
538                goto error;
539            }
540            if (!pimg->start) pimg->start = fpos;
541            fseek(pimg->fh, pimg->start, SEEK_SET);
542            return pimg;
543          }
544          case 'M': case 'D':
545            pimg->start = ftell(pimg->fh) - 1;
546            break;
547         }
548         while (ch != '\n' && ch != '\r') {
549            ch = GETC(pimg->fh);
550         }
551      }
552   }
553
554   /* Although these are often referred to as "CMAP .XYZ files", it seems
555    * that actually, the extension .XYZ isn't used, rather .SHT (shot
556    * variant, produced by CMAP v16 and later), .UNA (unadjusted) and
557    * .ADJ (adjusted) extensions are.  Since img has long checked for
558    * .XYZ, we continue to do so in case anyone is relying on it.
559    */
560   if (has_ext(fnm, len, "sht") ||
561       has_ext(fnm, len, "adj") ||
562       has_ext(fnm, len, "una") ||
563       has_ext(fnm, len, "xyz")) {
564      char *line;
565xyz_file:
566      /* Spaces aren't legal in CMAP station names, but dots are, so
567       * use space as the level separator. */
568      pimg->separator = ' ';
569      line = getline_alloc(pimg->fh);
570      if (!line) {
571         img_errno = IMG_OUTOFMEMORY;
572         goto error;
573      }
574      /* There doesn't seem to be a spec for what happens after 1999 with cmap
575       * files, so this code allows for:
576       *  * 21xx -> xx (up to 2150)
577       *  * 21xx -> 1xx (up to 2199)
578       *  * full year being specified instead of 2 digits
579       */
580      len = strlen(line);
581      if (len > 59) {
582         /* Don't just truncate at column 59, allow for a > 2 digit year. */
583         char * p = strstr(line + len, "Page");
584         if (p) {
585            while (p > line && p[-1] == ' ')
586               --p;
587            *p = '\0';
588            len = p - line;
589         } else {
590            line[59] = '\0';
591         }
592      }
593      if (len > 45) {
594         /* YY/MM/DD HH:MM */
595         struct tm tm;
596         unsigned long v;
597         char * p;
598         pimg->datestamp = my_strdup(line + 45);
599         p = pimg->datestamp;
600         v = strtoul(p, &p, 10);
601         if (v <= 50) {
602            /* In the absence of a spec for cmap files, assume <= 50 means 21st
603             * century. */
604            v += 2000;
605         } else if (v < 200) {
606            /* Map 100-199 to 21st century. */
607            v += 1900;
608         }
609         if (v == ULONG_MAX || *p++ != '/')
610            goto bad_cmap_date;
611         tm.tm_year = v - 1900;
612         v = strtoul(p, &p, 10);
613         if (v < 1 || v > 12 || *p++ != '/')
614            goto bad_cmap_date;
615         tm.tm_mon = v - 1;
616         v = strtoul(p, &p, 10);
617         if (v < 1 || v > 31 || *p++ != ' ')
618            goto bad_cmap_date;
619         tm.tm_mday = v;
620         v = strtoul(p, &p, 10);
621         if (v >= 24 || *p++ != ':')
622            goto bad_cmap_date;
623         tm.tm_hour = v;
624         v = strtoul(p, &p, 10);
625         if (v >= 60)
626            goto bad_cmap_date;
627         tm.tm_min = v;
628         if (*p == ':') {
629            v = strtoul(p + 1, &p, 10);
630            if (v > 60)
631               goto bad_cmap_date;
632            tm.tm_sec = v;
633         } else {
634            tm.tm_sec = 0;
635         }
636         tm.tm_isdst = 0;
637         /* We have no indication of what timezone this timestamp is in.  It's
638          * probably local time for whoever processed the data, so just assume
639          * UTC, which is at least fairly central in the possibilities.
640          */
641         pimg->datestamp_numeric = mktime_with_tz(&tm, "");
642      } else {
643         pimg->datestamp = my_strdup(TIMENA);
644      }
645bad_cmap_date:
646      if (strncmp(line, "  Cave Survey Data Processed by CMAP ",
647                  LITLEN("  Cave Survey Data Processed by CMAP ")) == 0) {
648         len = 0;
649      } else {
650         if (len > 45) {
651            line[45] = '\0';
652            len = 45;
653         }
654         while (len > 2 && line[len - 1] == ' ') --len;
655         if (len > 2) {
656            line[len] = '\0';
657            pimg->title = my_strdup(line + 2);
658         }
659      }
660      if (len <= 2) pimg->title = baseleaf_from_fnm(fnm);
661      osfree(line);
662      if (!pimg->datestamp || !pimg->title) {
663         img_errno = IMG_OUTOFMEMORY;
664         goto error;
665      }
666      line = getline_alloc(pimg->fh);
667      if (!line) {
668         img_errno = IMG_OUTOFMEMORY;
669         goto error;
670      }
671      if (line[0] != ' ' || (line[1] != 'S' && line[1] != 'O')) {
672         img_errno = IMG_BADFORMAT;
673         goto error;
674      }
675      if (line[1] == 'S') {
676         pimg->version = VERSION_CMAP_STATION;
677      } else {
678         pimg->version = VERSION_CMAP_SHOT;
679      }
680      osfree(line);
681      line = getline_alloc(pimg->fh);
682      if (!line) {
683         img_errno = IMG_OUTOFMEMORY;
684         goto error;
685      }
686      if (line[0] != ' ' || line[1] != '-') {
687         img_errno = IMG_BADFORMAT;
688         goto error;
689      }
690      osfree(line);
691      pimg->start = ftell(pimg->fh);
692      return pimg;
693   }
694
695   if (fread(buf, LITLEN(FILEID) + 1, 1, pimg->fh) != 1 ||
696       memcmp(buf, FILEID"\n", LITLEN(FILEID) + 1) != 0) {
697      if (fread(buf + LITLEN(FILEID) + 1, 8, 1, pimg->fh) == 1 &&
698          memcmp(buf, FILEID"\r\nv0.01\r\n", LITLEN(FILEID) + 9) == 0) {
699         /* v0 3d file with DOS EOLs */
700         pimg->version = 0;
701         goto v03d;
702      }
703      rewind(pimg->fh);
704      if (buf[1] == ' ') {
705         if (buf[0] == ' ') {
706            /* Looks like a CMAP .xyz file ... */
707            goto xyz_file;
708         } else if (strchr("ZSNF", buf[0])) {
709            /* Looks like a Compass .plt file ... */
710            /* Almost certainly it'll start "Z " */
711            goto plt_file;
712         }
713      }
714      if (buf[0] == '(') {
715         /* Looks like a Survex .pos file ... */
716         goto pos_file;
717      }
718      img_errno = IMG_BADFORMAT;
719      goto error;
720   }
721
722   /* check file format version */
723   ch = GETC(pimg->fh);
724   pimg->version = 0;
725   if (tolower(ch) == 'b') {
726      /* binary file iff B/b prefix */
727      pimg->version = 1;
728      ch = GETC(pimg->fh);
729   }
730   if (ch != 'v') {
731      img_errno = IMG_BADFORMAT;
732      goto error;
733   }
734   ch = GETC(pimg->fh);
735   if (ch == '0') {
736      if (fread(buf, 4, 1, pimg->fh) != 1 || memcmp(buf, ".01\n", 4) != 0) {
737         img_errno = IMG_BADFORMAT;
738         goto error;
739      }
740      /* nothing special to do */
741   } else if (pimg->version == 0) {
742      if (ch < '2' || ch > '0' + IMG_VERSION_MAX || GETC(pimg->fh) != '\n') {
743         img_errno = IMG_TOONEW;
744         goto error;
745      }
746      pimg->version = ch - '0';
747   } else {
748      img_errno = IMG_BADFORMAT;
749      goto error;
750   }
751
752v03d:
753   if (!pimg->title)
754       pimg->title = getline_alloc(pimg->fh);
755   else
756       osfree(getline_alloc(pimg->fh));
757   pimg->datestamp = getline_alloc(pimg->fh);
758   if (!pimg->title || !pimg->datestamp) {
759      img_errno = IMG_OUTOFMEMORY;
760      error:
761      osfree(pimg->title);
762      osfree(pimg->datestamp);
763      osfree(pimg->filename_opened);
764      fclose(pimg->fh);
765      osfree(pimg);
766      return NULL;
767   }
768
769   if (pimg->version >= 8) {
770      int flags = GETC(pimg->fh);
771      if (flags & img_FFLAG_EXTENDED) pimg->is_extended_elevation = 1;
772   } else {
773      len = strlen(pimg->title);
774      if (len > 11 && strcmp(pimg->title + len - 11, " (extended)") == 0) {
775          pimg->title[len - 11] = '\0';
776          pimg->is_extended_elevation = 1;
777      }
778   }
779
780   if (pimg->datestamp[0] == '@') {
781      unsigned long v;
782      char * p;
783      errno = 0;
784      v = strtoul(pimg->datestamp + 1, &p, 10);
785      if (errno == 0 && *p == '\0')
786         pimg->datestamp_numeric = v;
787      /* FIXME: We're assuming here that the C time_t epoch is 1970, which is
788       * true for Unix-like systems, Mac OS X and Windows, but isn't guaranteed
789       * by ISO C.
790       */
791   } else {
792      /* %a,%Y.%m.%d %H:%M:%S %Z */
793      struct tm tm;
794      unsigned long v;
795      char * p = pimg->datestamp;
796      while (isalpha((unsigned char)*p)) ++p;
797      if (*p == ',') ++p;
798      while (isspace((unsigned char)*p)) ++p;
799      v = strtoul(p, &p, 10);
800      if (v == ULONG_MAX || *p++ != '.')
801         goto bad_3d_date;
802      tm.tm_year = v - 1900;
803      v = strtoul(p, &p, 10);
804      if (v < 1 || v > 12 || *p++ != '.')
805         goto bad_3d_date;
806      tm.tm_mon = v - 1;
807      v = strtoul(p, &p, 10);
808      if (v < 1 || v > 31 || *p++ != ' ')
809         goto bad_3d_date;
810      tm.tm_mday = v;
811      v = strtoul(p, &p, 10);
812      if (v >= 24 || *p++ != ':')
813         goto bad_3d_date;
814      tm.tm_hour = v;
815      v = strtoul(p, &p, 10);
816      if (v >= 60 || *p++ != ':')
817         goto bad_3d_date;
818      tm.tm_min = v;
819      v = strtoul(p, &p, 10);
820      if (v > 60)
821         goto bad_3d_date;
822      tm.tm_sec = v;
823      tm.tm_isdst = 0;
824      while (isspace((unsigned char)*p)) ++p;
825      /* p now points to the timezone string.
826       *
827       * However, it's likely to be a string like "BST", and such strings can
828       * be ambiguous (BST could be UTC+1 or UTC+6), so it is impossible to
829       * reliably convert in all cases.  Just pass what we have to tzset() - if
830       * it doesn't handle it, UTC will be used.
831       */
832      pimg->datestamp_numeric = mktime_with_tz(&tm, p);
833   }
834bad_3d_date:
835
836   pimg->start = ftell(pimg->fh);
837
838   return pimg;
839}
840
841int
842img_rewind(img *pimg)
843{
844   if (!pimg->fRead) {
845      img_errno = IMG_WRITEERROR;
846      return 0;
847   }
848   if (fseek(pimg->fh, pimg->start, SEEK_SET) != 0) {
849      img_errno = IMG_READERROR;
850      return 0;
851   }
852   clearerr(pimg->fh);
853   /* [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
854    * [version 0] not in the middle of a 'LINE' command
855    * [version >= 3] not in the middle of turning a LINE into a MOVE */
856   pimg->pending = 0;
857
858   img_errno = IMG_NONE;
859
860   /* for version >= 3 we use label_buf to store the prefix for reuse */
861   /* for VERSION_COMPASS_PLT, 0 value indicates we haven't entered a survey
862    * yet */
863   /* for VERSION_CMAP_SHOT, we store the last station here to detect whether
864    * we MOVE or LINE */
865   pimg->label_len = 0;
866   pimg->style = img_STYLE_UNKNOWN;
867   return 1;
868}
869
870img *
871img_open_write(const char *fnm, char *title, int flags)
872{
873   time_t tm;
874   img *pimg;
875
876   if (fDirectory(fnm)) {
877      img_errno = IMG_DIRECTORY;
878      return NULL;
879   }
880
881   pimg = osnew(img);
882   if (pimg == NULL) {
883      img_errno = IMG_OUTOFMEMORY;
884      return NULL;
885   }
886
887   pimg->buf_len = 257;
888   pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
889   if (!pimg->label_buf) {
890      osfree(pimg);
891      img_errno = IMG_OUTOFMEMORY;
892      return NULL;
893   }
894
895   pimg->fh = fopen(fnm, "wb");
896   if (!pimg->fh) {
897      osfree(pimg->label_buf);
898      osfree(pimg);
899      img_errno = IMG_CANTOPENOUT;
900      return NULL;
901   }
902
903   pimg->filename_opened = NULL;
904
905   /* Output image file header */
906   fputs("Survex 3D Image File\n", pimg->fh); /* file identifier string */
907   if (img_output_version < 2) {
908      pimg->version = 1;
909      fputs("Bv0.01\n", pimg->fh); /* binary file format version number */
910   } else {
911      pimg->version = (img_output_version > IMG_VERSION_MAX) ? IMG_VERSION_MAX : img_output_version;
912      fprintf(pimg->fh, "v%d\n", pimg->version); /* file format version no. */
913   }
914
915   fputs(title, pimg->fh);
916   if (pimg->version < 8 && (flags & img_FFLAG_EXTENDED)) {
917      /* Older format versions append " (extended)" to the title to mark
918       * extended elevations. */
919      size_t len = strlen(title);
920      if (len < 11 || strcmp(title + len - 11, " (extended)") != 0)
921         fputs(" (extended)", pimg->fh);
922   }
923   PUTC('\n', pimg->fh);
924
925   tm = time(NULL);
926   if (tm == (time_t)-1) {
927      fputsnl(TIMENA, pimg->fh);
928   } else if (pimg->version <= 7) {
929      char date[256];
930      /* output current date and time in format specified */
931      strftime(date, 256, TIMEFMT, localtime(&tm));
932      fputsnl(date, pimg->fh);
933   } else {
934      fprintf(pimg->fh, "@%ld\n", (long)tm);
935   }
936
937   if (pimg->version >= 8) {
938      /* Clear bit one in case anyone has been passing true for fBinary. */
939      flags &=~ 1;
940      PUTC(flags, pimg->fh);
941   }
942
943#if 0
944   if (img_output_version >= 5) {
945       static const unsigned char codelengths[32] = {
946           4,  8,  8,  16, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
947           0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
948       };
949       fwrite(codelengths, 32, 1, pimg->fh);
950   }
951#endif
952   pimg->fRead = 0; /* writing to this file */
953   img_errno = IMG_NONE;
954
955   /* for version >= 3 we use label_buf to store the prefix for reuse */
956   pimg->label_buf[0] = '\0';
957   pimg->label_len = 0;
958
959#if IMG_API_VERSION == 0
960   pimg->date1 = pimg->date2 = 0;
961   pimg->olddate1 = pimg->olddate2 = 0;
962#else /* IMG_API_VERSION == 1 */
963   pimg->days1 = pimg->days2 = -1;
964   pimg->olddays1 = pimg->olddays2 = -1;
965#endif
966   pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
967
968   pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
969
970   pimg->n_legs = 0;
971   pimg->length = 0.0;
972   pimg->E = pimg->H = pimg->V = 0.0;
973
974   /* Don't check for write errors now - let img_close() report them... */
975   return pimg;
976}
977
978static void
979read_xyz_station_coords(img_point *pt, const char *line)
980{
981   char num[12];
982   memcpy(num, line + 6, 9);
983   num[9] = '\0';
984   pt->x = atof(num) / METRES_PER_FOOT;
985   memcpy(num, line + 15, 9);
986   pt->y = atof(num) / METRES_PER_FOOT;
987   memcpy(num, line + 24, 8);
988   num[8] = '\0';
989   pt->z = atof(num) / METRES_PER_FOOT;
990}
991
992static void
993read_xyz_shot_coords(img_point *pt, const char *line)
994{
995   char num[12];
996   memcpy(num, line + 40, 10);
997   num[10] = '\0';
998   pt->x = atof(num) / METRES_PER_FOOT;
999   memcpy(num, line + 50, 10);
1000   pt->y = atof(num) / METRES_PER_FOOT;
1001   memcpy(num, line + 60, 9);
1002   num[9] = '\0';
1003   pt->z = atof(num) / METRES_PER_FOOT;
1004}
1005
1006static void
1007subtract_xyz_shot_deltas(img_point *pt, const char *line)
1008{
1009   char num[12];
1010   memcpy(num, line + 15, 9);
1011   num[9] = '\0';
1012   pt->x -= atof(num) / METRES_PER_FOOT;
1013   memcpy(num, line + 24, 8);
1014   num[8] = '\0';
1015   pt->y -= atof(num) / METRES_PER_FOOT;
1016   memcpy(num, line + 32, 8);
1017   pt->z -= atof(num) / METRES_PER_FOOT;
1018}
1019
1020static int
1021read_coord(FILE *fh, img_point *pt)
1022{
1023   SVX_ASSERT(fh);
1024   SVX_ASSERT(pt);
1025   pt->x = get32(fh) / 100.0;
1026   pt->y = get32(fh) / 100.0;
1027   pt->z = get32(fh) / 100.0;
1028   if (ferror(fh) || feof(fh)) {
1029      img_errno = feof(fh) ? IMG_BADFORMAT : IMG_READERROR;
1030      return 0;
1031   }
1032   return 1;
1033}
1034
1035static int
1036skip_coord(FILE *fh)
1037{
1038    return (fseek(fh, 12, SEEK_CUR) == 0);
1039}
1040
1041static int
1042read_v3label(img *pimg)
1043{
1044   char *q;
1045   long len = GETC(pimg->fh);
1046   if (len == EOF) {
1047      img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1048      return img_BAD;
1049   }
1050   if (len == 0xfe) {
1051      len += get16(pimg->fh);
1052      if (feof(pimg->fh)) {
1053         img_errno = IMG_BADFORMAT;
1054         return img_BAD;
1055      }
1056      if (ferror(pimg->fh)) {
1057         img_errno = IMG_READERROR;
1058         return img_BAD;
1059      }
1060   } else if (len == 0xff) {
1061      len = get32(pimg->fh);
1062      if (ferror(pimg->fh)) {
1063         img_errno = IMG_READERROR;
1064         return img_BAD;
1065      }
1066      if (feof(pimg->fh) || len < 0xfe + 0xffff) {
1067         img_errno = IMG_BADFORMAT;
1068         return img_BAD;
1069      }
1070   }
1071
1072   if (!check_label_space(pimg, pimg->label_len + len + 1)) {
1073      img_errno = IMG_OUTOFMEMORY;
1074      return img_BAD;
1075   }
1076   q = pimg->label_buf + pimg->label_len;
1077   pimg->label_len += len;
1078   if (len && fread(q, len, 1, pimg->fh) != 1) {
1079      img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1080      return img_BAD;
1081   }
1082   q[len] = '\0';
1083   return 0;
1084}
1085
1086static int
1087read_v8label(img *pimg, int common_flag, size_t common_val)
1088{
1089   char *q;
1090   size_t del, add;
1091   if (common_flag) {
1092      if (common_val == 0) return 0;
1093      add = del = common_val;
1094   } else {
1095      int ch = GETC(pimg->fh);
1096      if (ch == EOF) {
1097         img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1098         return img_BAD;
1099      }
1100      if (ch != 0x00) {
1101         del = ch >> 4;
1102         add = ch & 0x0f;
1103      } else {
1104         ch = GETC(pimg->fh);
1105         if (ch == EOF) {
1106            img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1107            return img_BAD;
1108         }
1109         if (ch != 0xff) {
1110            del = ch;
1111         } else {
1112            del = get32(pimg->fh);
1113            if (ferror(pimg->fh)) {
1114               img_errno = IMG_READERROR;
1115               return img_BAD;
1116            }
1117         }
1118         ch = GETC(pimg->fh);
1119         if (ch == EOF) {
1120            img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1121            return img_BAD;
1122         }
1123         if (ch != 0xff) {
1124            add = ch;
1125         } else {
1126            add = get32(pimg->fh);
1127            if (ferror(pimg->fh)) {
1128               img_errno = IMG_READERROR;
1129               return img_BAD;
1130            }
1131         }
1132      }
1133
1134      if (add > del && !check_label_space(pimg, pimg->label_len + add - del + 1)) {
1135         img_errno = IMG_OUTOFMEMORY;
1136         return img_BAD;
1137      }
1138   }
1139   if (del > pimg->label_len) {
1140      img_errno = IMG_BADFORMAT;
1141      return img_BAD;
1142   }
1143   pimg->label_len -= del;
1144   q = pimg->label_buf + pimg->label_len;
1145   pimg->label_len += add;
1146   if (add && fread(q, add, 1, pimg->fh) != 1) {
1147      img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1148      return img_BAD;
1149   }
1150   q[add] = '\0';
1151   return 0;
1152}
1153
1154static int img_read_item_new(img *pimg, img_point *p);
1155static int img_read_item_v3to7(img *pimg, img_point *p);
1156static int img_read_item_ancient(img *pimg, img_point *p);
1157static int img_read_item_ascii_wrapper(img *pimg, img_point *p);
1158static int img_read_item_ascii(img *pimg, img_point *p);
1159
1160int
1161img_read_item(img *pimg, img_point *p)
1162{
1163   pimg->flags = 0;
1164
1165   if (pimg->version >= 8) {
1166      return img_read_item_new(pimg, p);
1167   } else if (pimg->version >= 3) {
1168      return img_read_item_v3to7(pimg, p);
1169   } else if (pimg->version >= 1) {
1170      return img_read_item_ancient(pimg, p);
1171   } else {
1172      return img_read_item_ascii_wrapper(pimg, p);
1173   }
1174}
1175
1176static int
1177img_read_item_new(img *pimg, img_point *p)
1178{
1179   int result;
1180   int opt;
1181   pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1182   if (pimg->pending >= 0x40) {
1183      if (pimg->pending == 256) {
1184         pimg->pending = 0;
1185         return img_XSECT_END;
1186      }
1187      *p = pimg->mv;
1188      pimg->flags = (int)(pimg->pending) & 0x3f;
1189      pimg->pending = 0;
1190      return img_LINE;
1191   }
1192   again3: /* label to goto if we get a prefix, date, or lrud */
1193   pimg->label = pimg->label_buf;
1194   opt = GETC(pimg->fh);
1195   if (opt == EOF) {
1196      img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1197      return img_BAD;
1198   }
1199   if (opt >> 6 == 0) {
1200      if (opt <= 4) {
1201         if (opt == 0 && pimg->style == 0)
1202            return img_STOP; /* end of data marker */
1203         /* STYLE */
1204         pimg->style = opt;
1205         goto again3;
1206      }
1207      if (opt >= 0x10) {
1208          switch (opt) {
1209              case 0x10: { /* No date info */
1210#if IMG_API_VERSION == 0
1211                  pimg->date1 = pimg->date2 = 0;
1212#else /* IMG_API_VERSION == 1 */
1213                  pimg->days1 = pimg->days2 = -1;
1214#endif
1215                  break;
1216              }
1217              case 0x11: { /* Single date */
1218                  int days1 = (int)getu16(pimg->fh);
1219#if IMG_API_VERSION == 0
1220                  pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
1221#else /* IMG_API_VERSION == 1 */
1222                  pimg->days2 = pimg->days1 = days1;
1223#endif
1224                  break;
1225              }
1226              case 0x12: { /* Date range (short) */
1227                  int days1 = (int)getu16(pimg->fh);
1228                  int days2 = days1 + GETC(pimg->fh) + 1;
1229#if IMG_API_VERSION == 0
1230                  pimg->date1 = (days1 - 25567) * 86400;
1231                  pimg->date2 = (days2 - 25567) * 86400;
1232#else /* IMG_API_VERSION == 1 */
1233                  pimg->days1 = days1;
1234                  pimg->days2 = days2;
1235#endif
1236                  break;
1237              }
1238              case 0x13: { /* Date range (long) */
1239                  int days1 = (int)getu16(pimg->fh);
1240                  int days2 = (int)getu16(pimg->fh);
1241#if IMG_API_VERSION == 0
1242                  pimg->date1 = (days1 - 25567) * 86400;
1243                  pimg->date2 = (days2 - 25567) * 86400;
1244#else /* IMG_API_VERSION == 1 */
1245                  pimg->days1 = days1;
1246                  pimg->days2 = days2;
1247#endif
1248                  break;
1249              }
1250              case 0x1f: /* Error info */
1251                  pimg->n_legs = get32(pimg->fh);
1252                  pimg->length = get32(pimg->fh) / 100.0;
1253                  pimg->E = get32(pimg->fh) / 100.0;
1254                  pimg->H = get32(pimg->fh) / 100.0;
1255                  pimg->V = get32(pimg->fh) / 100.0;
1256                  return img_ERROR_INFO;
1257              case 0x30: case 0x31: /* LRUD */
1258              case 0x32: case 0x33: /* Big LRUD! */
1259                  if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1260                  pimg->flags = (int)opt & 0x01;
1261                  if (opt < 0x32) {
1262                      pimg->l = get16(pimg->fh) / 100.0;
1263                      pimg->r = get16(pimg->fh) / 100.0;
1264                      pimg->u = get16(pimg->fh) / 100.0;
1265                      pimg->d = get16(pimg->fh) / 100.0;
1266                  } else {
1267                      pimg->l = get32(pimg->fh) / 100.0;
1268                      pimg->r = get32(pimg->fh) / 100.0;
1269                      pimg->u = get32(pimg->fh) / 100.0;
1270                      pimg->d = get32(pimg->fh) / 100.0;
1271                  }
1272                  if (pimg->survey_len) {
1273                      size_t l = pimg->survey_len;
1274                      const char *s = pimg->label_buf;
1275                      if (strncmp(pimg->survey, s, l + 1) != 0) {
1276                          return img_XSECT_END;
1277                      }
1278                      pimg->label += l;
1279                      /* skip the dot if there */
1280                      if (*pimg->label) pimg->label++;
1281                  }
1282                  /* If this is the last cross-section in this passage, set
1283                   * pending so we return img_XSECT_END next time. */
1284                  if (pimg->flags & 0x01) {
1285                      pimg->pending = 256;
1286                      pimg->flags &= ~0x01;
1287                  }
1288                  return img_XSECT;
1289              default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1290                  img_errno = IMG_BADFORMAT;
1291                  return img_BAD;
1292          }
1293          goto again3;
1294      }
1295      if (opt != 15) {
1296         /* 1-14 and 16-31 reserved */
1297         img_errno = IMG_BADFORMAT;
1298         return img_BAD;
1299      }
1300      result = img_MOVE;
1301   } else if (opt >= 0x80) {
1302      if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1303
1304      result = img_LABEL;
1305
1306      if (pimg->survey_len) {
1307         size_t l = pimg->survey_len;
1308         const char *s = pimg->label_buf;
1309         if (strncmp(pimg->survey, s, l + 1) != 0) {
1310            if (!skip_coord(pimg->fh)) return img_BAD;
1311            pimg->pending = 0;
1312            goto again3;
1313         }
1314         pimg->label += l;
1315         /* skip the dot if there */
1316         if (*pimg->label) pimg->label++;
1317      }
1318
1319      pimg->flags = (int)opt & 0x7f;
1320   } else if ((opt >> 6) == 1) {
1321      if (read_v8label(pimg, opt & 0x20, 0) == img_BAD) return img_BAD;
1322
1323      result = img_LINE;
1324
1325      if (pimg->survey_len) {
1326         size_t l = pimg->survey_len;
1327         const char *s = pimg->label_buf;
1328         if (strncmp(pimg->survey, s, l) != 0 ||
1329             !(s[l] == '.' || s[l] == '\0')) {
1330            if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1331            pimg->pending = 15;
1332            goto again3;
1333         }
1334         pimg->label += l;
1335         /* skip the dot if there */
1336         if (*pimg->label) pimg->label++;
1337      }
1338
1339      if (pimg->pending) {
1340         *p = pimg->mv;
1341         if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1342         pimg->pending = opt;
1343         return img_MOVE;
1344      }
1345      pimg->flags = (int)opt & 0x1f;
1346   } else {
1347      img_errno = IMG_BADFORMAT;
1348      return img_BAD;
1349   }
1350   if (!read_coord(pimg->fh, p)) return img_BAD;
1351   pimg->pending = 0;
1352   return result;
1353}
1354
1355static int
1356img_read_item_v3to7(img *pimg, img_point *p)
1357{
1358   int result;
1359   int opt;
1360   pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1361   if (pimg->pending == 256) {
1362      pimg->pending = 0;
1363      return img_XSECT_END;
1364   }
1365   if (pimg->pending >= 0x80) {
1366      *p = pimg->mv;
1367      pimg->flags = (int)(pimg->pending) & 0x3f;
1368      pimg->pending = 0;
1369      return img_LINE;
1370   }
1371   again3: /* label to goto if we get a prefix, date, or lrud */
1372   pimg->label = pimg->label_buf;
1373   opt = GETC(pimg->fh);
1374   if (opt == EOF) {
1375      img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1376      return img_BAD;
1377   }
1378   switch (opt >> 6) {
1379    case 0:
1380      if (opt == 0) {
1381         if (!pimg->label_len) return img_STOP; /* end of data marker */
1382         pimg->label_len = 0;
1383         goto again3;
1384      }
1385      if (opt < 15) {
1386         /* 1-14 mean trim that many levels from current prefix */
1387         int c;
1388         if (pimg->label_len <= 17) {
1389            /* zero prefix using "0" */
1390            img_errno = IMG_BADFORMAT;
1391            return img_BAD;
1392         }
1393         /* extra - 1 because label_len points to one past the end */
1394         c = pimg->label_len - 17 - 1;
1395         while (pimg->label_buf[c] != '.' || --opt > 0) {
1396            if (--c < 0) {
1397               /* zero prefix using "0" */
1398               img_errno = IMG_BADFORMAT;
1399               return img_BAD;
1400            }
1401         }
1402         c++;
1403         pimg->label_len = c;
1404         goto again3;
1405      }
1406      if (opt == 15) {
1407         result = img_MOVE;
1408         break;
1409      }
1410      if (opt >= 0x20) {
1411          switch (opt) {
1412              case 0x20: /* Single date */
1413                  if (pimg->version < 7) {
1414                      int date1 = get32(pimg->fh);
1415#if IMG_API_VERSION == 0
1416                      pimg->date2 = pimg->date1 = date1;
1417#else /* IMG_API_VERSION == 1 */
1418                      if (date1 != 0) {
1419                          pimg->days2 = pimg->days1 = (date1 / 86400) + 25567;
1420                      } else {
1421                          pimg->days2 = pimg->days1 = -1;
1422                      }
1423#endif
1424                  } else {
1425                      int days1 = (int)getu16(pimg->fh);
1426#if IMG_API_VERSION == 0
1427                      pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
1428#else /* IMG_API_VERSION == 1 */
1429                      pimg->days2 = pimg->days1 = days1;
1430#endif
1431                  }
1432                  break;
1433              case 0x21: /* Date range (short for v7+) */
1434                  if (pimg->version < 7) {
1435                      INT32_T date1 = get32(pimg->fh);
1436                      INT32_T date2 = get32(pimg->fh);
1437#if IMG_API_VERSION == 0
1438                      pimg->date1 = date1;
1439                      pimg->date2 = date2;
1440#else /* IMG_API_VERSION == 1 */
1441                      pimg->days1 = (date1 / 86400) + 25567;
1442                      pimg->days2 = (date2 / 86400) + 25567;
1443#endif
1444                  } else {
1445                      int days1 = (int)getu16(pimg->fh);
1446                      int days2 = days1 + GETC(pimg->fh) + 1;
1447#if IMG_API_VERSION == 0
1448                      pimg->date1 = (days1 - 25567) * 86400;
1449                      pimg->date2 = (days2 - 25567) * 86400;
1450#else /* IMG_API_VERSION == 1 */
1451                      pimg->days1 = days1;
1452                      pimg->days2 = days2;
1453#endif
1454                  }
1455                  break;
1456              case 0x22: /* Error info */
1457                  pimg->n_legs = get32(pimg->fh);
1458                  pimg->length = get32(pimg->fh) / 100.0;
1459                  pimg->E = get32(pimg->fh) / 100.0;
1460                  pimg->H = get32(pimg->fh) / 100.0;
1461                  pimg->V = get32(pimg->fh) / 100.0;
1462                  if (feof(pimg->fh)) {
1463                      img_errno = IMG_BADFORMAT;
1464                      return img_BAD;
1465                  }
1466                  if (ferror(pimg->fh)) {
1467                      img_errno = IMG_READERROR;
1468                      return img_BAD;
1469                  }
1470                  return img_ERROR_INFO;
1471              case 0x23: { /* v7+: Date range (long) */
1472                  if (pimg->version < 7) {
1473                      img_errno = IMG_BADFORMAT;
1474                      return img_BAD;
1475                  }
1476                  int days1 = (int)getu16(pimg->fh);
1477                  int days2 = (int)getu16(pimg->fh);
1478                  if (feof(pimg->fh)) {
1479                      img_errno = IMG_BADFORMAT;
1480                      return img_BAD;
1481                  }
1482                  if (ferror(pimg->fh)) {
1483                      img_errno = IMG_READERROR;
1484                      return img_BAD;
1485                  }
1486#if IMG_API_VERSION == 0
1487                  pimg->date1 = (days1 - 25567) * 86400;
1488                  pimg->date2 = (days2 - 25567) * 86400;
1489#else /* IMG_API_VERSION == 1 */
1490                  pimg->days1 = days1;
1491                  pimg->days2 = days2;
1492#endif
1493                  break;
1494              }
1495              case 0x24: { /* v7+: No date info */
1496#if IMG_API_VERSION == 0
1497                  pimg->date1 = pimg->date2 = 0;
1498#else /* IMG_API_VERSION == 1 */
1499                  pimg->days1 = pimg->days2 = -1;
1500#endif
1501                  break;
1502              }
1503              case 0x30: case 0x31: /* LRUD */
1504              case 0x32: case 0x33: /* Big LRUD! */
1505                  if (read_v3label(pimg) == img_BAD) return img_BAD;
1506                  pimg->flags = (int)opt & 0x01;
1507                  if (opt < 0x32) {
1508                      pimg->l = get16(pimg->fh) / 100.0;
1509                      pimg->r = get16(pimg->fh) / 100.0;
1510                      pimg->u = get16(pimg->fh) / 100.0;
1511                      pimg->d = get16(pimg->fh) / 100.0;
1512                  } else {
1513                      pimg->l = get32(pimg->fh) / 100.0;
1514                      pimg->r = get32(pimg->fh) / 100.0;
1515                      pimg->u = get32(pimg->fh) / 100.0;
1516                      pimg->d = get32(pimg->fh) / 100.0;
1517                  }
1518                  if (feof(pimg->fh)) {
1519                      img_errno = IMG_BADFORMAT;
1520                      return img_BAD;
1521                  }
1522                  if (ferror(pimg->fh)) {
1523                      img_errno = IMG_READERROR;
1524                      return img_BAD;
1525                  }
1526                  if (pimg->survey_len) {
1527                      size_t l = pimg->survey_len;
1528                      const char *s = pimg->label_buf;
1529                      if (strncmp(pimg->survey, s, l + 1) != 0) {
1530                          return img_XSECT_END;
1531                      }
1532                      pimg->label += l;
1533                      /* skip the dot if there */
1534                      if (*pimg->label) pimg->label++;
1535                  }
1536                  /* If this is the last cross-section in this passage, set
1537                   * pending so we return img_XSECT_END next time. */
1538                  if (pimg->flags & 0x01) {
1539                      pimg->pending = 256;
1540                      pimg->flags &= ~0x01;
1541                  }
1542                  return img_XSECT;
1543              default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1544                  img_errno = IMG_BADFORMAT;
1545                  return img_BAD;
1546          }
1547          if (feof(pimg->fh)) {
1548              img_errno = IMG_BADFORMAT;
1549              return img_BAD;
1550          }
1551          if (ferror(pimg->fh)) {
1552              img_errno = IMG_READERROR;
1553              return img_BAD;
1554          }
1555          goto again3;
1556      }
1557      /* 16-31 mean remove (n - 15) characters from the prefix */
1558      /* zero prefix using 0 */
1559      if (pimg->label_len <= (size_t)(opt - 15)) {
1560         img_errno = IMG_BADFORMAT;
1561         return img_BAD;
1562      }
1563      pimg->label_len -= (opt - 15);
1564      goto again3;
1565    case 1:
1566      if (read_v3label(pimg) == img_BAD) return img_BAD;
1567
1568      result = img_LABEL;
1569
1570      if (pimg->survey_len) {
1571         size_t l = pimg->survey_len;
1572         const char *s = pimg->label_buf;
1573         if (strncmp(pimg->survey, s, l + 1) != 0) {
1574            if (!skip_coord(pimg->fh)) return img_BAD;
1575            pimg->pending = 0;
1576            goto again3;
1577         }
1578         pimg->label += l;
1579         /* skip the dot if there */
1580         if (*pimg->label) pimg->label++;
1581      }
1582
1583      pimg->flags = (int)opt & 0x3f;
1584      break;
1585    case 2:
1586      if (read_v3label(pimg) == img_BAD) return img_BAD;
1587
1588      result = img_LINE;
1589
1590      if (pimg->survey_len) {
1591         size_t l = pimg->survey_len;
1592         const char *s = pimg->label_buf;
1593         if (strncmp(pimg->survey, s, l) != 0 ||
1594             !(s[l] == '.' || s[l] == '\0')) {
1595            if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1596            pimg->pending = 15;
1597            goto again3;
1598         }
1599         pimg->label += l;
1600         /* skip the dot if there */
1601         if (*pimg->label) pimg->label++;
1602      }
1603
1604      if (pimg->pending) {
1605         *p = pimg->mv;
1606         if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1607         pimg->pending = opt;
1608         return img_MOVE;
1609      }
1610      pimg->flags = (int)opt & 0x3f;
1611      break;
1612    default:
1613      img_errno = IMG_BADFORMAT;
1614      return img_BAD;
1615   }
1616   if (!read_coord(pimg->fh, p)) return img_BAD;
1617   pimg->pending = 0;
1618   return result;
1619}
1620
1621static int
1622img_read_item_ancient(img *pimg, img_point *p)
1623{
1624   int result;
1625   static long opt_lookahead = 0;
1626   static img_point pt = { 0.0, 0.0, 0.0 };
1627   long opt;
1628
1629   again: /* label to goto if we get a cross */
1630   pimg->label = pimg->label_buf;
1631   pimg->label[0] = '\0';
1632
1633   if (pimg->version == 1) {
1634      if (opt_lookahead) {
1635         opt = opt_lookahead;
1636         opt_lookahead = 0;
1637      } else {
1638         opt = get32(pimg->fh);
1639      }
1640   } else {
1641      opt = GETC(pimg->fh);
1642   }
1643
1644   if (feof(pimg->fh)) {
1645      img_errno = IMG_BADFORMAT;
1646      return img_BAD;
1647   }
1648   if (ferror(pimg->fh)) {
1649      img_errno = IMG_READERROR;
1650      return img_BAD;
1651   }
1652
1653   switch (opt) {
1654    case -1: case 0:
1655      return img_STOP; /* end of data marker */
1656    case 1:
1657      /* skip coordinates */
1658      if (!skip_coord(pimg->fh)) {
1659         img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1660         return img_BAD;
1661      }
1662      goto again;
1663    case 2: case 3: {
1664      size_t len;
1665      result = img_LABEL;
1666      if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
1667         img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1668         return img_BAD;
1669      }
1670      if (pimg->label[0] == '\\') pimg->label++;
1671      len = strlen(pimg->label);
1672      if (len == 0 || pimg->label[len - 1] != '\n') {
1673         img_errno = IMG_BADFORMAT;
1674         return img_BAD;
1675      }
1676      /* Ignore empty labels in some .3d files (caused by a bug) */
1677      if (len == 1) goto again;
1678      pimg->label[len - 1] = '\0';
1679      pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1680      if (opt == 2) goto done;
1681      break;
1682    }
1683    case 6: case 7: {
1684      long len;
1685      result = img_LABEL;
1686
1687      if (opt == 7)
1688         pimg->flags = GETC(pimg->fh);
1689      else
1690         pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1691
1692      len = get32(pimg->fh);
1693
1694      if (feof(pimg->fh)) {
1695         img_errno = IMG_BADFORMAT;
1696         return img_BAD;
1697      }
1698      if (ferror(pimg->fh)) {
1699         img_errno = IMG_READERROR;
1700         return img_BAD;
1701      }
1702
1703      /* Ignore empty labels in some .3d files (caused by a bug) */
1704      if (len == 0) goto again;
1705      if (!check_label_space(pimg, len + 1)) {
1706         img_errno = IMG_OUTOFMEMORY;
1707         return img_BAD;
1708      }
1709      if (fread(pimg->label_buf, len, 1, pimg->fh) != 1) {
1710         img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1711         return img_BAD;
1712      }
1713      pimg->label_buf[len] = '\0';
1714      break;
1715    }
1716    case 4:
1717      result = img_MOVE;
1718      break;
1719    case 5:
1720      result = img_LINE;
1721      break;
1722    default:
1723      switch ((int)opt & 0xc0) {
1724       case 0x80:
1725         pimg->flags = (int)opt & 0x3f;
1726         result = img_LINE;
1727         break;
1728       case 0x40: {
1729         char *q;
1730         pimg->flags = (int)opt & 0x3f;
1731         result = img_LABEL;
1732         if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
1733            img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1734            return img_BAD;
1735         }
1736         q = pimg->label_buf + strlen(pimg->label_buf) - 1;
1737         /* Ignore empty-labels in some .3d files (caused by a bug) */
1738         if (q == pimg->label_buf) goto again;
1739         if (*q != '\n') {
1740            img_errno = IMG_BADFORMAT;
1741            return img_BAD;
1742         }
1743         *q = '\0';
1744         break;
1745       }
1746       default:
1747         img_errno = IMG_BADFORMAT;
1748         return img_BAD;
1749      }
1750      break;
1751   }
1752
1753   if (!read_coord(pimg->fh, &pt)) return img_BAD;
1754
1755   if (result == img_LABEL && pimg->survey_len) {
1756      if (strncmp(pimg->label, pimg->survey, pimg->survey_len + 1) != 0)
1757         goto again;
1758      pimg->label += pimg->survey_len + 1;
1759   }
1760
1761   done:
1762   *p = pt;
1763
1764   if (result == img_MOVE && pimg->version == 1) {
1765      /* peek at next code and see if it's an old-style label */
1766      opt_lookahead = get32(pimg->fh);
1767
1768      if (feof(pimg->fh)) {
1769         img_errno = IMG_BADFORMAT;
1770         return img_BAD;
1771      }
1772      if (ferror(pimg->fh)) {
1773         img_errno = IMG_READERROR;
1774         return img_BAD;
1775      }
1776
1777      if (opt_lookahead == 2) return img_read_item_ancient(pimg, p);
1778   }
1779
1780   return result;
1781}
1782
1783static int
1784img_read_item_ascii_wrapper(img *pimg, img_point *p)
1785{
1786   /* We need to set the default locale for fscanf() to work on
1787    * numbers with "." as decimal point. */
1788   int result;
1789   char * current_locale = my_strdup(setlocale(LC_NUMERIC, NULL));
1790   setlocale(LC_NUMERIC, "C");
1791   result = img_read_item_ascii(pimg, p);
1792   setlocale(LC_NUMERIC, current_locale);
1793   free(current_locale);
1794   return result;
1795}
1796
1797/* Handle all ASCII formats. */
1798static int
1799img_read_item_ascii(img *pimg, img_point *p)
1800{
1801   int result;
1802   pimg->label = pimg->label_buf;
1803   if (pimg->version == 0) {
1804      ascii_again:
1805      pimg->label[0] = '\0';
1806      if (feof(pimg->fh)) return img_STOP;
1807      if (pimg->pending) {
1808         pimg->pending = 0;
1809         result = img_LINE;
1810      } else {
1811         char cmd[7];
1812         /* Stop if nothing found */
1813         if (fscanf(pimg->fh, "%6s", cmd) < 1) return img_STOP;
1814         if (strcmp(cmd, "move") == 0)
1815            result = img_MOVE;
1816         else if (strcmp(cmd, "draw") == 0)
1817            result = img_LINE;
1818         else if (strcmp(cmd, "line") == 0) {
1819            /* set flag to indicate to process second triplet as LINE */
1820            pimg->pending = 1;
1821            result = img_MOVE;
1822         } else if (strcmp(cmd, "cross") == 0) {
1823            if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1824               img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1825               return img_BAD;
1826            }
1827            goto ascii_again;
1828         } else if (strcmp(cmd, "name") == 0) {
1829            size_t off = 0;
1830            int ch = GETC(pimg->fh);
1831            if (ch == ' ') ch = GETC(pimg->fh);
1832            while (ch != ' ') {
1833               if (ch == '\n' || ch == EOF) {
1834                  img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1835                  return img_BAD;
1836               }
1837               if (off == pimg->buf_len) {
1838                  if (!check_label_space(pimg, pimg->buf_len * 2)) {
1839                     img_errno = IMG_OUTOFMEMORY;
1840                     return img_BAD;
1841                  }
1842               }
1843               pimg->label_buf[off++] = ch;
1844               ch = GETC(pimg->fh);
1845            }
1846            pimg->label_buf[off] = '\0';
1847
1848            pimg->label = pimg->label_buf;
1849            if (pimg->label[0] == '\\') pimg->label++;
1850
1851            pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1852
1853            result = img_LABEL;
1854         } else {
1855            img_errno = IMG_BADFORMAT;
1856            return img_BAD; /* unknown keyword */
1857         }
1858      }
1859
1860      if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1861         img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1862         return img_BAD;
1863      }
1864
1865      if (result == img_LABEL && pimg->survey_len) {
1866         if (strncmp(pimg->label, pimg->survey, pimg->survey_len + 1) != 0)
1867            goto ascii_again;
1868         pimg->label += pimg->survey_len + 1;
1869      }
1870
1871      return result;
1872   } else if (pimg->version == VERSION_SURVEX_POS) {
1873      /* Survex .pos file */
1874      int ch;
1875      size_t off;
1876      pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1877      againpos:
1878      off = 0;
1879      while (fscanf(pimg->fh, "(%lf,%lf,%lf )", &p->x, &p->y, &p->z) != 3) {
1880         if (ferror(pimg->fh)) {
1881            img_errno = IMG_READERROR;
1882            return img_BAD;
1883         }
1884         if (feof(pimg->fh)) return img_STOP;
1885         if (pimg->pending) {
1886            img_errno = IMG_BADFORMAT;
1887            return img_BAD;
1888         }
1889         pimg->pending = 1;
1890         /* ignore rest of line */
1891         do {
1892            ch = GETC(pimg->fh);
1893         } while (ch != '\n' && ch != '\r' && ch != EOF);
1894      }
1895
1896      pimg->label_buf[0] = '\0';
1897      do {
1898          ch = GETC(pimg->fh);
1899      } while (ch == ' ' || ch == '\t');
1900      if (ch == '\n' || ch == EOF) {
1901          /* If there's no label, set img_SFLAG_ANON. */
1902          pimg->flags |= img_SFLAG_ANON;
1903          return img_LABEL;
1904      }
1905      pimg->label_buf[0] = ch;
1906      off = 1;
1907      while (!feof(pimg->fh)) {
1908         if (!fgets(pimg->label_buf + off, pimg->buf_len - off, pimg->fh)) {
1909            img_errno = IMG_READERROR;
1910            return img_BAD;
1911         }
1912
1913         off += strlen(pimg->label_buf + off);
1914         if (off && pimg->label_buf[off - 1] == '\n') {
1915            pimg->label_buf[off - 1] = '\0';
1916            break;
1917         }
1918         if (!check_label_space(pimg, pimg->buf_len * 2)) {
1919            img_errno = IMG_OUTOFMEMORY;
1920            return img_BAD;
1921         }
1922      }
1923
1924      pimg->label = pimg->label_buf;
1925
1926      if (pimg->label[0] == '\\') pimg->label++;
1927
1928      if (pimg->survey_len) {
1929         size_t l = pimg->survey_len + 1;
1930         if (strncmp(pimg->survey, pimg->label, l) != 0) goto againpos;
1931         pimg->label += l;
1932      }
1933
1934      return img_LABEL;
1935   } else if (pimg->version == VERSION_COMPASS_PLT) {
1936      /* Compass .plt file */
1937      if (pimg->pending > 0) {
1938         /* -1 signals we've entered the first survey we want to
1939          * read, and need to fudge lots if the first action is 'D'...
1940          */
1941         /* pending MOVE or LINE */
1942         int r = pimg->pending - 4;
1943         pimg->pending = 0;
1944         pimg->flags = 0;
1945         pimg->label[pimg->label_len] = '\0';
1946         return r;
1947      }
1948
1949      while (1) {
1950         char *line;
1951         char *q;
1952         size_t len = 0;
1953         int ch = GETC(pimg->fh);
1954
1955         switch (ch) {
1956            case '\x1a': case EOF: /* Don't insist on ^Z at end of file */
1957               return img_STOP;
1958            case 'X': case 'F': case 'S':
1959               /* bounding boX (marks end of survey), Feature survey, or
1960                * new Section - skip to next survey */
1961               if (pimg->survey) return img_STOP;
1962skip_to_N:
1963               while (1) {
1964                  do {
1965                     ch = GETC(pimg->fh);
1966                  } while (ch != '\n' && ch != '\r' && ch != EOF);
1967                  while (ch == '\n' || ch == '\r') ch = GETC(pimg->fh);
1968                  if (ch == 'N') break;
1969                  if (ch == '\x1a' || ch == EOF) return img_STOP;
1970               }
1971               /* FALLTHRU */
1972            case 'N':
1973               line = getline_alloc(pimg->fh);
1974               if (!line) {
1975                  img_errno = IMG_OUTOFMEMORY;
1976                  return img_BAD;
1977               }
1978               while (line[len] > 32) ++len;
1979               if (pimg->label_len == 0) pimg->pending = -1;
1980               if (!check_label_space(pimg, len + 1)) {
1981                  osfree(line);
1982                  img_errno = IMG_OUTOFMEMORY;
1983                  return img_BAD;
1984               }
1985               pimg->label_len = len;
1986               pimg->label = pimg->label_buf;
1987               memcpy(pimg->label, line, len);
1988               pimg->label[len] = '\0';
1989               osfree(line);
1990               break;
1991            case 'M': case 'D': {
1992               /* Move or Draw */
1993               long fpos = -1;
1994               if (pimg->survey && pimg->label_len == 0) {
1995                  /* We're only holding onto this line in case the first line
1996                   * of the 'N' is a 'D', so skip it for now...
1997                   */
1998                  goto skip_to_N;
1999               }
2000               if (ch == 'D' && pimg->pending == -1) {
2001                  if (pimg->survey) {
2002                     fpos = ftell(pimg->fh) - 1;
2003                     fseek(pimg->fh, pimg->start, SEEK_SET);
2004                     ch = GETC(pimg->fh);
2005                     pimg->pending = 0;
2006                  } else {
2007                     /* If a file actually has a 'D' before any 'M', then
2008                      * pretend the 'D' is an 'M' - one of the examples
2009                      * in the docs was like this! */
2010                     ch = 'M';
2011                  }
2012               }
2013               line = getline_alloc(pimg->fh);
2014               if (!line) {
2015                  img_errno = IMG_OUTOFMEMORY;
2016                  return img_BAD;
2017               }
2018               /* Compass stores coordinates as North, East, Up = (y,x,z)! */
2019               if (sscanf(line, "%lf%lf%lf", &p->y, &p->x, &p->z) != 3) {
2020                  osfree(line);
2021                  if (ferror(pimg->fh)) {
2022                     img_errno = IMG_READERROR;
2023                  } else {
2024                     img_errno = IMG_BADFORMAT;
2025                  }
2026                  return img_BAD;
2027               }
2028               p->x *= METRES_PER_FOOT;
2029               p->y *= METRES_PER_FOOT;
2030               p->z *= METRES_PER_FOOT;
2031               q = strchr(line, 'S');
2032               if (!q) {
2033                  osfree(line);
2034                  img_errno = IMG_BADFORMAT;
2035                  return img_BAD;
2036               }
2037               ++q;
2038               len = 0;
2039               while (q[len] > ' ') ++len;
2040               q[len] = '\0';
2041               len += 2; /* ' ' and '\0' */
2042               if (!check_label_space(pimg, pimg->label_len + len)) {
2043                  img_errno = IMG_OUTOFMEMORY;
2044                  return img_BAD;
2045               }
2046               pimg->label = pimg->label_buf;
2047               if (pimg->label_len) {
2048                   pimg->label[pimg->label_len] = ' ';
2049                   memcpy(pimg->label + pimg->label_len + 1, q, len - 1);
2050               } else {
2051                   memcpy(pimg->label, q, len - 1);
2052               }
2053               q += len - 1;
2054               /* Now read LRUD.  Technically, this is optional but virtually
2055                * all PLT files have it (with dummy negative values if no LRUD
2056                * was measured) and some versions of Compass can't read PLT
2057                * files without it!
2058                */
2059               while (*q && *q <= ' ') q++;
2060               if (*q == 'P') {
2061                   if (sscanf(q + 1, "%lf%lf%lf%lf",
2062                              &pimg->l, &pimg->r, &pimg->u, &pimg->d) != 4) {
2063                       osfree(line);
2064                       if (ferror(pimg->fh)) {
2065                           img_errno = IMG_READERROR;
2066                       } else {
2067                           img_errno = IMG_BADFORMAT;
2068                       }
2069                       return img_BAD;
2070                   }
2071                   pimg->l *= METRES_PER_FOOT;
2072                   pimg->r *= METRES_PER_FOOT;
2073                   pimg->u *= METRES_PER_FOOT;
2074                   pimg->d *= METRES_PER_FOOT;
2075               } else {
2076                   pimg->l = pimg->r = pimg->u = pimg->d = -1;
2077               }
2078               osfree(line);
2079               pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
2080               if (fpos != -1) {
2081                  fseek(pimg->fh, fpos, SEEK_SET);
2082               } else {
2083                  pimg->pending = (ch == 'M' ? img_MOVE : img_LINE) + 4;
2084               }
2085               return img_LABEL;
2086            }
2087            default:
2088               img_errno = IMG_BADFORMAT;
2089               return img_BAD;
2090         }
2091      }
2092   } else {
2093      /* CMAP .xyz file */
2094      char *line = NULL;
2095      char *q;
2096      size_t len;
2097
2098      if (pimg->pending) {
2099         /* pending MOVE or LINE or LABEL or STOP */
2100         int r = pimg->pending - 4;
2101         /* Set label to empty - don't use "" as we adjust label relative
2102          * to label_buf when label_buf is reallocated. */
2103         pimg->label = pimg->label_buf + strlen(pimg->label_buf);
2104         pimg->flags = 0;
2105         if (r == img_LABEL) {
2106            /* nasty magic */
2107            read_xyz_shot_coords(p, pimg->label_buf + 16);
2108            subtract_xyz_shot_deltas(p, pimg->label_buf + 16);
2109            pimg->pending = img_STOP + 4;
2110            return img_MOVE;
2111         }
2112
2113         pimg->pending = 0;
2114
2115         if (r == img_STOP) {
2116            /* nasty magic */
2117            read_xyz_shot_coords(p, pimg->label_buf + 16);
2118            return img_LINE;
2119         }
2120
2121         return r;
2122      }
2123
2124      pimg->label = pimg->label_buf;
2125      do {
2126         osfree(line);
2127         if (feof(pimg->fh)) return img_STOP;
2128         line = getline_alloc(pimg->fh);
2129         if (!line) {
2130            img_errno = IMG_OUTOFMEMORY;
2131            return img_BAD;
2132         }
2133      } while (line[0] == ' ' || line[0] == '\0');
2134      if (line[0] == '\x1a') return img_STOP;
2135
2136      len = strlen(line);
2137      if (pimg->version == VERSION_CMAP_STATION) {
2138         /* station variant */
2139         if (len < 37) {
2140            osfree(line);
2141            img_errno = IMG_BADFORMAT;
2142            return img_BAD;
2143         }
2144         memcpy(pimg->label, line, 6);
2145         q = (char *)memchr(pimg->label, ' ', 6);
2146         if (!q) q = pimg->label + 6;
2147         *q = '\0';
2148
2149         read_xyz_station_coords(p, line);
2150
2151         /* FIXME: look at prev for lines (line + 32, 5) */
2152         /* FIXME: duplicate stations... */
2153         return img_LABEL;
2154      } else {
2155         /* Shot variant (VERSION_CMAP_SHOT) */
2156         char old[8], new_[8];
2157         if (len < 61) {
2158            osfree(line);
2159            img_errno = IMG_BADFORMAT;
2160            return img_BAD;
2161         }
2162
2163         memcpy(old, line, 7);
2164         q = (char *)memchr(old, ' ', 7);
2165         if (!q) q = old + 7;
2166         *q = '\0';
2167
2168         memcpy(new_, line + 7, 7);
2169         q = (char *)memchr(new_, ' ', 7);
2170         if (!q) q = new_ + 7;
2171         *q = '\0';
2172
2173         pimg->flags = img_SFLAG_UNDERGROUND;
2174
2175         if (strcmp(old, new_) == 0) {
2176            pimg->pending = img_MOVE + 4;
2177            read_xyz_shot_coords(p, line);
2178            strcpy(pimg->label, new_);
2179            osfree(line);
2180            return img_LABEL;
2181         }
2182
2183         if (strcmp(old, pimg->label) == 0) {
2184            pimg->pending = img_LINE + 4;
2185            read_xyz_shot_coords(p, line);
2186            strcpy(pimg->label, new_);
2187            osfree(line);
2188            return img_LABEL;
2189         }
2190
2191         pimg->pending = img_LABEL + 4;
2192         read_xyz_shot_coords(p, line);
2193         strcpy(pimg->label, new_);
2194         memcpy(pimg->label + 16, line, 70);
2195
2196         osfree(line);
2197         return img_LABEL;
2198      }
2199   }
2200}
2201
2202static void
2203write_coord(FILE *fh, double x, double y, double z)
2204{
2205   SVX_ASSERT(fh);
2206   /* Output in cm */
2207   static INT32_T X_, Y_, Z_;
2208   INT32_T X = my_lround(x * 100.0);
2209   INT32_T Y = my_lround(y * 100.0);
2210   INT32_T Z = my_lround(z * 100.0);
2211
2212   X_ -= X;
2213   Y_ -= Y;
2214   Z_ -= Z;
2215   put32(X, fh);
2216   put32(Y, fh);
2217   put32(Z, fh);
2218   X_ = X; Y_ = Y; Z_ = Z;
2219}
2220
2221static int
2222write_v3label(img *pimg, int opt, const char *s)
2223{
2224   size_t len, n, dot;
2225
2226   /* find length of common prefix */
2227   dot = 0;
2228   for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2229      if (s[len] == '.') dot = len + 1;
2230   }
2231
2232   SVX_ASSERT(len <= pimg->label_len);
2233   n = pimg->label_len - len;
2234   if (len == 0) {
2235      if (pimg->label_len) PUTC(0, pimg->fh);
2236   } else if (n <= 16) {
2237      if (n) PUTC(n + 15, pimg->fh);
2238   } else if (dot == 0) {
2239      if (pimg->label_len) PUTC(0, pimg->fh);
2240      len = 0;
2241   } else {
2242      const char *p = pimg->label_buf + dot;
2243      n = 1;
2244      for (len = pimg->label_len - dot - 17; len; len--) {
2245         if (*p++ == '.') n++;
2246      }
2247      if (n <= 14) {
2248         PUTC(n, pimg->fh);
2249         len = dot;
2250      } else {
2251         if (pimg->label_len) PUTC(0, pimg->fh);
2252         len = 0;
2253      }
2254   }
2255
2256   n = strlen(s + len);
2257   PUTC(opt, pimg->fh);
2258   if (n < 0xfe) {
2259      PUTC(n, pimg->fh);
2260   } else if (n < 0xffff + 0xfe) {
2261      PUTC(0xfe, pimg->fh);
2262      put16((short)(n - 0xfe), pimg->fh);
2263   } else {
2264      PUTC(0xff, pimg->fh);
2265      put32(n, pimg->fh);
2266   }
2267   fwrite(s + len, n, 1, pimg->fh);
2268
2269   n += len;
2270   pimg->label_len = n;
2271   if (!check_label_space(pimg, n + 1))
2272      return 0; /* FIXME: distinguish out of memory... */
2273   memcpy(pimg->label_buf + len, s + len, n - len + 1);
2274
2275   return !ferror(pimg->fh);
2276}
2277
2278static int
2279write_v8label(img *pimg, int opt, int common_flag, size_t common_val,
2280              const char *s)
2281{
2282   size_t len, del, add;
2283
2284   /* find length of common prefix */
2285   for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2286   }
2287
2288   SVX_ASSERT(len <= pimg->label_len);
2289   del = pimg->label_len - len;
2290   add = strlen(s + len);
2291
2292   if (add == common_val && del == common_val) {
2293      PUTC(opt | common_flag, pimg->fh);
2294   } else {
2295      PUTC(opt, pimg->fh);
2296      if (del <= 15 && add <= 15 && (del || add)) {
2297         PUTC((del << 4) | add, pimg->fh);
2298      } else {
2299         PUTC(0x00, pimg->fh);
2300         if (del < 0xff) {
2301            PUTC(del, pimg->fh);
2302         } else {
2303            PUTC(0xff, pimg->fh);
2304            put32(del, pimg->fh);
2305         }
2306         if (add < 0xff) {
2307            PUTC(add, pimg->fh);
2308         } else {
2309            PUTC(0xff, pimg->fh);
2310            put32(add, pimg->fh);
2311         }
2312      }
2313   }
2314
2315   if (add)
2316      fwrite(s + len, add, 1, pimg->fh);
2317
2318   pimg->label_len = len + add;
2319   if (add > del && !check_label_space(pimg, pimg->label_len + 1))
2320      return 0; /* FIXME: distinguish out of memory... */
2321
2322   memcpy(pimg->label_buf + len, s + len, add + 1);
2323
2324   return !ferror(pimg->fh);
2325}
2326
2327static void
2328img_write_item_date_new(img *pimg)
2329{
2330    int same, unset;
2331    /* Only write dates when they've changed. */
2332#if IMG_API_VERSION == 0
2333    if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2334        return;
2335
2336    same = (pimg->date1 == pimg->date2);
2337    unset = (pimg->date1 == 0);
2338#else /* IMG_API_VERSION == 1 */
2339    if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2340        return;
2341
2342    same = (pimg->days1 == pimg->days2);
2343    unset = (pimg->days1 == -1);
2344#endif
2345
2346    if (same) {
2347        if (unset) {
2348            PUTC(0x10, pimg->fh);
2349        } else {
2350            PUTC(0x11, pimg->fh);
2351#if IMG_API_VERSION == 0
2352            put16(pimg->date1 / 86400 + 25567, pimg->fh);
2353#else /* IMG_API_VERSION == 1 */
2354            put16(pimg->days1, pimg->fh);
2355#endif
2356        }
2357    } else {
2358#if IMG_API_VERSION == 0
2359        int diff = (pimg->date2 - pimg->date1) / 86400;
2360        if (diff > 0 && diff <= 256) {
2361            PUTC(0x12, pimg->fh);
2362            put16(pimg->date1 / 86400 + 25567, pimg->fh);
2363            PUTC(diff - 1, pimg->fh);
2364        } else {
2365            PUTC(0x13, pimg->fh);
2366            put16(pimg->date1 / 86400 + 25567, pimg->fh);
2367            put16(pimg->date2 / 86400 + 25567, pimg->fh);
2368        }
2369#else /* IMG_API_VERSION == 1 */
2370        int diff = pimg->days2 - pimg->days1;
2371        if (diff > 0 && diff <= 256) {
2372            PUTC(0x12, pimg->fh);
2373            put16(pimg->days1, pimg->fh);
2374            PUTC(diff - 1, pimg->fh);
2375        } else {
2376            PUTC(0x13, pimg->fh);
2377            put16(pimg->days1, pimg->fh);
2378            put16(pimg->days2, pimg->fh);
2379        }
2380#endif
2381    }
2382#if IMG_API_VERSION == 0
2383    pimg->olddate1 = pimg->date1;
2384    pimg->olddate2 = pimg->date2;
2385#else /* IMG_API_VERSION == 1 */
2386    pimg->olddays1 = pimg->days1;
2387    pimg->olddays2 = pimg->days2;
2388#endif
2389}
2390
2391static void
2392img_write_item_date(img *pimg)
2393{
2394    int same, unset;
2395    /* Only write dates when they've changed. */
2396#if IMG_API_VERSION == 0
2397    if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2398        return;
2399
2400    same = (pimg->date1 == pimg->date2);
2401    unset = (pimg->date1 == 0);
2402#else /* IMG_API_VERSION == 1 */
2403    if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2404        return;
2405
2406    same = (pimg->days1 == pimg->days2);
2407    unset = (pimg->days1 == -1);
2408#endif
2409
2410    if (same) {
2411        if (img_output_version < 7) {
2412            PUTC(0x20, pimg->fh);
2413#if IMG_API_VERSION == 0
2414            put32(pimg->date1, pimg->fh);
2415#else /* IMG_API_VERSION == 1 */
2416            put32((pimg->days1 - 25567) * 86400, pimg->fh);
2417#endif
2418        } else {
2419            if (unset) {
2420                PUTC(0x24, pimg->fh);
2421            } else {
2422                PUTC(0x20, pimg->fh);
2423#if IMG_API_VERSION == 0
2424                put16(pimg->date1 / 86400 + 25567, pimg->fh);
2425#else /* IMG_API_VERSION == 1 */
2426                put16(pimg->days1, pimg->fh);
2427#endif
2428            }
2429        }
2430    } else {
2431        if (img_output_version < 7) {
2432            PUTC(0x21, pimg->fh);
2433#if IMG_API_VERSION == 0
2434            put32(pimg->date1, pimg->fh);
2435            put32(pimg->date2, pimg->fh);
2436#else /* IMG_API_VERSION == 1 */
2437            put32((pimg->days1 - 25567) * 86400, pimg->fh);
2438            put32((pimg->days2 - 25567) * 86400, pimg->fh);
2439#endif
2440        } else {
2441#if IMG_API_VERSION == 0
2442            int diff = (pimg->date2 - pimg->date1) / 86400;
2443            if (diff > 0 && diff <= 256) {
2444                PUTC(0x21, pimg->fh);
2445                put16(pimg->date1 / 86400 + 25567, pimg->fh);
2446                PUTC(diff - 1, pimg->fh);
2447            } else {
2448                PUTC(0x23, pimg->fh);
2449                put16(pimg->date1 / 86400 + 25567, pimg->fh);
2450                put16(pimg->date2 / 86400 + 25567, pimg->fh);
2451            }
2452#else /* IMG_API_VERSION == 1 */
2453            int diff = pimg->days2 - pimg->days1;
2454            if (diff > 0 && diff <= 256) {
2455                PUTC(0x21, pimg->fh);
2456                put16(pimg->days1, pimg->fh);
2457                PUTC(diff - 1, pimg->fh);
2458            } else {
2459                PUTC(0x23, pimg->fh);
2460                put16(pimg->days1, pimg->fh);
2461                put16(pimg->days2, pimg->fh);
2462            }
2463#endif
2464        }
2465    }
2466#if IMG_API_VERSION == 0
2467    pimg->olddate1 = pimg->date1;
2468    pimg->olddate2 = pimg->date2;
2469#else /* IMG_API_VERSION == 1 */
2470    pimg->olddays1 = pimg->days1;
2471    pimg->olddays2 = pimg->days2;
2472#endif
2473}
2474
2475static void
2476img_write_item_new(img *pimg, int code, int flags, const char *s,
2477                   double x, double y, double z);
2478static void
2479img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2480                     double x, double y, double z);
2481static void
2482img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2483                       double x, double y, double z);
2484
2485void
2486img_write_item(img *pimg, int code, int flags, const char *s,
2487               double x, double y, double z)
2488{
2489   if (!pimg) return;
2490   if (pimg->version >= 8) {
2491      img_write_item_new(pimg, code, flags, s, x, y, z);
2492   } else if (pimg->version >= 3) {
2493      img_write_item_v3to7(pimg, code, flags, s, x, y, z);
2494   } else {
2495      img_write_item_ancient(pimg, code, flags, s, x, y, z);
2496   }
2497}
2498
2499static void
2500img_write_item_new(img *pimg, int code, int flags, const char *s,
2501                   double x, double y, double z)
2502{
2503   switch (code) {
2504    case img_LABEL:
2505      write_v8label(pimg, 0x80 | flags, 0, -1, s);
2506      break;
2507    case img_XSECT: {
2508      INT32_T l, r, u, d, max_dim;
2509      img_write_item_date_new(pimg);
2510      l = (INT32_T)my_lround(pimg->l * 100.0);
2511      r = (INT32_T)my_lround(pimg->r * 100.0);
2512      u = (INT32_T)my_lround(pimg->u * 100.0);
2513      d = (INT32_T)my_lround(pimg->d * 100.0);
2514      if (l < 0) l = -1;
2515      if (r < 0) r = -1;
2516      if (u < 0) u = -1;
2517      if (d < 0) d = -1;
2518      max_dim = max(max(l, r), max(u, d));
2519      flags = (flags & img_XFLAG_END) ? 1 : 0;
2520      if (max_dim >= 32768) flags |= 2;
2521      write_v8label(pimg, 0x30 | flags, 0, -1, s);
2522      if (flags & 2) {
2523         /* Big passage!  Need to use 4 bytes. */
2524         put32(l, pimg->fh);
2525         put32(r, pimg->fh);
2526         put32(u, pimg->fh);
2527         put32(d, pimg->fh);
2528      } else {
2529         put16(l, pimg->fh);
2530         put16(r, pimg->fh);
2531         put16(u, pimg->fh);
2532         put16(d, pimg->fh);
2533      }
2534      return;
2535    }
2536    case img_MOVE:
2537      PUTC(15, pimg->fh);
2538      break;
2539    case img_LINE:
2540      img_write_item_date_new(pimg);
2541      if (pimg->style != pimg->oldstyle) {
2542          switch (pimg->style) {
2543            case img_STYLE_NORMAL:
2544            case img_STYLE_DIVING:
2545            case img_STYLE_CARTESIAN:
2546            case img_STYLE_CYLPOLAR:
2547            case img_STYLE_NOSURVEY:
2548               PUTC(pimg->style, pimg->fh);
2549               break;
2550          }
2551          pimg->oldstyle = pimg->style;
2552      }
2553      write_v8label(pimg, 0x40 | flags, 0x20, 0x00, s ? s : "");
2554      break;
2555    default: /* ignore for now */
2556      return;
2557   }
2558   write_coord(pimg->fh, x, y, z);
2559}
2560
2561static void
2562img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2563                     double x, double y, double z)
2564{
2565   switch (code) {
2566    case img_LABEL:
2567      write_v3label(pimg, 0x40 | flags, s);
2568      break;
2569    case img_XSECT: {
2570      INT32_T l, r, u, d, max_dim;
2571      /* Need at least version 5 for img_XSECT. */
2572      if (pimg->version < 5) return;
2573      img_write_item_date(pimg);
2574      l = (INT32_T)my_lround(pimg->l * 100.0);
2575      r = (INT32_T)my_lround(pimg->r * 100.0);
2576      u = (INT32_T)my_lround(pimg->u * 100.0);
2577      d = (INT32_T)my_lround(pimg->d * 100.0);
2578      if (l < 0) l = -1;
2579      if (r < 0) r = -1;
2580      if (u < 0) u = -1;
2581      if (d < 0) d = -1;
2582      max_dim = max(max(l, r), max(u, d));
2583      flags = (flags & img_XFLAG_END) ? 1 : 0;
2584      if (max_dim >= 32768) flags |= 2;
2585      write_v3label(pimg, 0x30 | flags, s);
2586      if (flags & 2) {
2587         /* Big passage!  Need to use 4 bytes. */
2588         put32(l, pimg->fh);
2589         put32(r, pimg->fh);
2590         put32(u, pimg->fh);
2591         put32(d, pimg->fh);
2592      } else {
2593         put16(l, pimg->fh);
2594         put16(r, pimg->fh);
2595         put16(u, pimg->fh);
2596         put16(d, pimg->fh);
2597      }
2598      return;
2599    }
2600    case img_MOVE:
2601      PUTC(15, pimg->fh);
2602      break;
2603    case img_LINE:
2604      if (pimg->version >= 4) {
2605          img_write_item_date(pimg);
2606      }
2607      write_v3label(pimg, 0x80 | flags, s ? s : "");
2608      break;
2609    default: /* ignore for now */
2610      return;
2611   }
2612   write_coord(pimg->fh, x, y, z);
2613}
2614
2615static void
2616img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2617                       double x, double y, double z)
2618{
2619   size_t len;
2620   INT32_T opt = 0;
2621   SVX_ASSERT(pimg->version > 0);
2622   switch (code) {
2623    case img_LABEL:
2624      if (pimg->version == 1) {
2625         /* put a move before each label */
2626         img_write_item_ancient(pimg, img_MOVE, 0, NULL, x, y, z);
2627         put32(2, pimg->fh);
2628         fputsnl(s, pimg->fh);
2629         return;
2630      }
2631      len = strlen(s);
2632      if (len > 255 || strchr(s, '\n')) {
2633         /* long label - not in early incarnations of v2 format, but few
2634          * 3d files will need these, so better not to force incompatibility
2635          * with a new version I think... */
2636         PUTC(7, pimg->fh);
2637         PUTC(flags, pimg->fh);
2638         put32(len, pimg->fh);
2639         fputs(s, pimg->fh);
2640      } else {
2641         PUTC(0x40 | (flags & 0x3f), pimg->fh);
2642         fputsnl(s, pimg->fh);
2643      }
2644      opt = 0;
2645      break;
2646    case img_MOVE:
2647      opt = 4;
2648      break;
2649    case img_LINE:
2650      if (pimg->version > 1) {
2651         opt = 0x80 | (flags & 0x3f);
2652         break;
2653      }
2654      opt = 5;
2655      break;
2656    default: /* ignore for now */
2657      return;
2658   }
2659   if (pimg->version == 1) {
2660      put32(opt, pimg->fh);
2661   } else {
2662      if (opt) PUTC(opt, pimg->fh);
2663   }
2664   write_coord(pimg->fh, x, y, z);
2665}
2666
2667/* Write error information for the current traverse
2668 * n_legs is the number of legs in the traverse
2669 * length is the traverse length (in m)
2670 * E is the ratio of the observed misclosure to the theoretical one
2671 * H is the ratio of the observed horizontal misclosure to the theoretical one
2672 * V is the ratio of the observed vertical misclosure to the theoretical one
2673 */
2674void
2675img_write_errors(img *pimg, int n_legs, double length,
2676                 double E, double H, double V)
2677{
2678    PUTC((pimg->version >= 8 ? 0x1f : 0x22), pimg->fh);
2679    put32(n_legs, pimg->fh);
2680    put32((INT32_T)my_lround(length * 100.0), pimg->fh);
2681    put32((INT32_T)my_lround(E * 100.0), pimg->fh);
2682    put32((INT32_T)my_lround(H * 100.0), pimg->fh);
2683    put32((INT32_T)my_lround(V * 100.0), pimg->fh);
2684}
2685
2686int
2687img_close(img *pimg)
2688{
2689   int result = 1;
2690   if (pimg) {
2691      if (pimg->fh) {
2692         if (pimg->fRead) {
2693            osfree(pimg->survey);
2694            osfree(pimg->title);
2695            osfree(pimg->datestamp);
2696         } else {
2697            /* write end of data marker */
2698            switch (pimg->version) {
2699             case 1:
2700               put32((INT32_T)-1, pimg->fh);
2701               break;
2702             default:
2703               if (pimg->version <= 7 ?
2704                   (pimg->label_len != 0) :
2705                   (pimg->style != img_STYLE_NORMAL)) {
2706                  PUTC(0, pimg->fh);
2707               }
2708               /* FALL THROUGH */
2709             case 2:
2710               PUTC(0, pimg->fh);
2711               break;
2712            }
2713         }
2714         if (ferror(pimg->fh)) result = 0;
2715         if (fclose(pimg->fh)) result = 0;
2716         if (!result) img_errno = pimg->fRead ? IMG_READERROR : IMG_WRITEERROR;
2717      }
2718      osfree(pimg->label_buf);
2719      osfree(pimg->filename_opened);
2720      osfree(pimg);
2721   }
2722   return result;
2723}
Note: See TracBrowser for help on using the repository browser.