source: git/src/img.c @ 4f70ebc

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

src/: Add new "datestamp_numeric" field to struct img giving the
datestamp as a time_t in UTC (or (time_t)-1 if there's no datestamp
or we failed to convert it). For .3d >= v8, this field is reliable.
We attempt to convert date strings in .3d <= v7 and CMAP XYZ
files, but may get the timezone wrong.

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