source: git/src/img.c @ bca9107

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

src/img.c: Remove pointless "future expansion" comment from
img_read_item_ancient().

  • Property mode set to 100644
File size: 68.8 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   pimg->label = pimg->label_buf;
1630
1631   again: /* label to goto if we get a cross */
1632   pimg->label[0] = '\0';
1633
1634   if (pimg->version == 1) {
1635      if (opt_lookahead) {
1636         opt = opt_lookahead;
1637         opt_lookahead = 0;
1638      } else {
1639         opt = get32(pimg->fh);
1640      }
1641   } else {
1642      opt = GETC(pimg->fh);
1643   }
1644
1645   if (feof(pimg->fh)) {
1646      img_errno = IMG_BADFORMAT;
1647      return img_BAD;
1648   }
1649   if (ferror(pimg->fh)) {
1650      img_errno = IMG_READERROR;
1651      return img_BAD;
1652   }
1653
1654   switch (opt) {
1655    case -1: case 0:
1656      return img_STOP; /* end of data marker */
1657    case 1:
1658      /* skip coordinates */
1659      if (!skip_coord(pimg->fh)) {
1660         img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1661         return img_BAD;
1662      }
1663      goto again;
1664    case 2: case 3: {
1665      char *q;
1666      int ch;
1667      result = img_LABEL;
1668      ch = GETC(pimg->fh);
1669      if (ch == EOF) {
1670         img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1671         return img_BAD;
1672      }
1673      if (ch != '\\') ungetc(ch, pimg->fh);
1674      fgets(pimg->label_buf, 257, pimg->fh);
1675      if (feof(pimg->fh)) {
1676         img_errno = IMG_BADFORMAT;
1677         return img_BAD;
1678      }
1679      if (ferror(pimg->fh)) {
1680         img_errno = IMG_READERROR;
1681         return img_BAD;
1682      }
1683      q = pimg->label_buf + strlen(pimg->label_buf) - 1;
1684      if (*q != '\n') {
1685         img_errno = IMG_BADFORMAT;
1686         return img_BAD;
1687      }
1688      /* Ignore empty labels in some .3d files (caused by a bug) */
1689      if (q == pimg->label_buf) goto again;
1690      *q = '\0';
1691      pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1692      if (opt == 2) goto done;
1693      break;
1694    }
1695    case 6: case 7: {
1696      long len;
1697      result = img_LABEL;
1698
1699      if (opt == 7)
1700         pimg->flags = GETC(pimg->fh);
1701      else
1702         pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1703
1704      len = get32(pimg->fh);
1705
1706      if (feof(pimg->fh)) {
1707         img_errno = IMG_BADFORMAT;
1708         return img_BAD;
1709      }
1710      if (ferror(pimg->fh)) {
1711         img_errno = IMG_READERROR;
1712         return img_BAD;
1713      }
1714
1715      /* Ignore empty labels in some .3d files (caused by a bug) */
1716      if (len == 0) goto again;
1717      if (!check_label_space(pimg, len + 1)) {
1718         img_errno = IMG_OUTOFMEMORY;
1719         return img_BAD;
1720      }
1721      if (fread(pimg->label_buf, len, 1, pimg->fh) != 1) {
1722         img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1723         return img_BAD;
1724      }
1725      pimg->label_buf[len] = '\0';
1726      break;
1727    }
1728    case 4:
1729      result = img_MOVE;
1730      break;
1731    case 5:
1732      result = img_LINE;
1733      break;
1734    default:
1735      switch ((int)opt & 0xc0) {
1736       case 0x80:
1737         pimg->flags = (int)opt & 0x3f;
1738         result = img_LINE;
1739         break;
1740       case 0x40: {
1741         char *q;
1742         pimg->flags = (int)opt & 0x3f;
1743         result = img_LABEL;
1744         if (!fgets(pimg->label_buf, 257, pimg->fh)) {
1745            img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1746            return img_BAD;
1747         }
1748         q = pimg->label_buf + strlen(pimg->label_buf) - 1;
1749         /* Ignore empty-labels in some .3d files (caused by a bug) */
1750         if (q == pimg->label_buf) goto again;
1751         if (*q != '\n') {
1752            img_errno = IMG_BADFORMAT;
1753            return img_BAD;
1754         }
1755         *q = '\0';
1756         break;
1757       }
1758       default:
1759         img_errno = IMG_BADFORMAT;
1760         return img_BAD;
1761      }
1762      break;
1763   }
1764
1765   if (!read_coord(pimg->fh, &pt)) return img_BAD;
1766
1767   if (result == img_LABEL && pimg->survey_len) {
1768      if (strncmp(pimg->label_buf, pimg->survey, pimg->survey_len + 1) != 0)
1769         goto again;
1770      pimg->label += pimg->survey_len + 1;
1771   }
1772
1773   done:
1774   *p = pt;
1775
1776   if (result == img_MOVE && pimg->version == 1) {
1777      /* peek at next code and see if it's an old-style label */
1778      opt_lookahead = get32(pimg->fh);
1779
1780      if (feof(pimg->fh)) {
1781         img_errno = IMG_BADFORMAT;
1782         return img_BAD;
1783      }
1784      if (ferror(pimg->fh)) {
1785         img_errno = IMG_READERROR;
1786         return img_BAD;
1787      }
1788
1789      if (opt_lookahead == 2) return img_read_item_ancient(pimg, p);
1790   }
1791
1792   return result;
1793}
1794
1795static int
1796img_read_item_ascii_wrapper(img *pimg, img_point *p)
1797{
1798   /* We need to set the default locale for fscanf() to work on
1799    * numbers with "." as decimal point. */
1800   int result;
1801   char * current_locale = my_strdup(setlocale(LC_NUMERIC, NULL));
1802   setlocale(LC_NUMERIC, "C");
1803   result = img_read_item_ascii(pimg, p);
1804   setlocale(LC_NUMERIC, current_locale);
1805   free(current_locale);
1806   return result;
1807}
1808
1809/* Handle all ASCII formats. */
1810static int
1811img_read_item_ascii(img *pimg, img_point *p)
1812{
1813   int result;
1814   pimg->label = pimg->label_buf;
1815   if (pimg->version == 0) {
1816      ascii_again:
1817      pimg->label[0] = '\0';
1818      if (feof(pimg->fh)) return img_STOP;
1819      if (pimg->pending) {
1820         pimg->pending = 0;
1821         result = img_LINE;
1822      } else {
1823         char cmd[7];
1824         /* Stop if nothing found */
1825         if (fscanf(pimg->fh, "%6s", cmd) < 1) return img_STOP;
1826         if (strcmp(cmd, "move") == 0)
1827            result = img_MOVE;
1828         else if (strcmp(cmd, "draw") == 0)
1829            result = img_LINE;
1830         else if (strcmp(cmd, "line") == 0) {
1831            /* set flag to indicate to process second triplet as LINE */
1832            pimg->pending = 1;
1833            result = img_MOVE;
1834         } else if (strcmp(cmd, "cross") == 0) {
1835            if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1836               img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1837               return img_BAD;
1838            }
1839            goto ascii_again;
1840         } else if (strcmp(cmd, "name") == 0) {
1841            size_t off = 0;
1842            int ch = GETC(pimg->fh);
1843            if (ch == ' ') ch = GETC(pimg->fh);
1844            while (ch != ' ') {
1845               if (ch == '\n' || ch == EOF) {
1846                  img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1847                  return img_BAD;
1848               }
1849               if (off == pimg->buf_len) {
1850                  if (!check_label_space(pimg, pimg->buf_len * 2)) {
1851                     img_errno = IMG_OUTOFMEMORY;
1852                     return img_BAD;
1853                  }
1854               }
1855               pimg->label_buf[off++] = ch;
1856               ch = GETC(pimg->fh);
1857            }
1858            pimg->label_buf[off] = '\0';
1859
1860            pimg->label = pimg->label_buf;
1861            if (pimg->label[0] == '\\') pimg->label++;
1862
1863            pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1864
1865            result = img_LABEL;
1866         } else {
1867            img_errno = IMG_BADFORMAT;
1868            return img_BAD; /* unknown keyword */
1869         }
1870      }
1871
1872      if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1873         img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1874         return img_BAD;
1875      }
1876
1877      if (result == img_LABEL && pimg->survey_len) {
1878         if (strncmp(pimg->label, pimg->survey, pimg->survey_len + 1) != 0)
1879            goto ascii_again;
1880         pimg->label += pimg->survey_len + 1;
1881      }
1882
1883      return result;
1884   } else if (pimg->version == VERSION_SURVEX_POS) {
1885      /* Survex .pos file */
1886      int ch;
1887      size_t off;
1888      pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1889      againpos:
1890      off = 0;
1891      while (fscanf(pimg->fh, "(%lf,%lf,%lf )", &p->x, &p->y, &p->z) != 3) {
1892         if (ferror(pimg->fh)) {
1893            img_errno = IMG_READERROR;
1894            return img_BAD;
1895         }
1896         if (feof(pimg->fh)) return img_STOP;
1897         if (pimg->pending) {
1898            img_errno = IMG_BADFORMAT;
1899            return img_BAD;
1900         }
1901         pimg->pending = 1;
1902         /* ignore rest of line */
1903         do {
1904            ch = GETC(pimg->fh);
1905         } while (ch != '\n' && ch != '\r' && ch != EOF);
1906      }
1907
1908      pimg->label_buf[0] = '\0';
1909      do {
1910          ch = GETC(pimg->fh);
1911      } while (ch == ' ' || ch == '\t');
1912      if (ch == '\n' || ch == EOF) {
1913          /* If there's no label, set img_SFLAG_ANON. */
1914          pimg->flags |= img_SFLAG_ANON;
1915          return img_LABEL;
1916      }
1917      pimg->label_buf[0] = ch;
1918      off = 1;
1919      while (!feof(pimg->fh)) {
1920         if (!fgets(pimg->label_buf + off, pimg->buf_len - off, pimg->fh)) {
1921            img_errno = IMG_READERROR;
1922            return img_BAD;
1923         }
1924
1925         off += strlen(pimg->label_buf + off);
1926         if (off && pimg->label_buf[off - 1] == '\n') {
1927            pimg->label_buf[off - 1] = '\0';
1928            break;
1929         }
1930         if (!check_label_space(pimg, pimg->buf_len * 2)) {
1931            img_errno = IMG_OUTOFMEMORY;
1932            return img_BAD;
1933         }
1934      }
1935
1936      pimg->label = pimg->label_buf;
1937
1938      if (pimg->label[0] == '\\') pimg->label++;
1939
1940      if (pimg->survey_len) {
1941         size_t l = pimg->survey_len + 1;
1942         if (strncmp(pimg->survey, pimg->label, l) != 0) goto againpos;
1943         pimg->label += l;
1944      }
1945
1946      return img_LABEL;
1947   } else if (pimg->version == VERSION_COMPASS_PLT) {
1948      /* Compass .plt file */
1949      if (pimg->pending > 0) {
1950         /* -1 signals we've entered the first survey we want to
1951          * read, and need to fudge lots if the first action is 'D'...
1952          */
1953         /* pending MOVE or LINE */
1954         int r = pimg->pending - 4;
1955         pimg->pending = 0;
1956         pimg->flags = 0;
1957         pimg->label[pimg->label_len] = '\0';
1958         return r;
1959      }
1960
1961      while (1) {
1962         char *line;
1963         char *q;
1964         size_t len = 0;
1965         int ch = GETC(pimg->fh);
1966
1967         switch (ch) {
1968            case '\x1a': case EOF: /* Don't insist on ^Z at end of file */
1969               return img_STOP;
1970            case 'X': case 'F': case 'S':
1971               /* bounding boX (marks end of survey), Feature survey, or
1972                * new Section - skip to next survey */
1973               if (pimg->survey) return img_STOP;
1974skip_to_N:
1975               while (1) {
1976                  do {
1977                     ch = GETC(pimg->fh);
1978                  } while (ch != '\n' && ch != '\r' && ch != EOF);
1979                  while (ch == '\n' || ch == '\r') ch = GETC(pimg->fh);
1980                  if (ch == 'N') break;
1981                  if (ch == '\x1a' || ch == EOF) return img_STOP;
1982               }
1983               /* FALLTHRU */
1984            case 'N':
1985               line = getline_alloc(pimg->fh);
1986               if (!line) {
1987                  img_errno = IMG_OUTOFMEMORY;
1988                  return img_BAD;
1989               }
1990               while (line[len] > 32) ++len;
1991               if (pimg->label_len == 0) pimg->pending = -1;
1992               if (!check_label_space(pimg, len + 1)) {
1993                  osfree(line);
1994                  img_errno = IMG_OUTOFMEMORY;
1995                  return img_BAD;
1996               }
1997               pimg->label_len = len;
1998               pimg->label = pimg->label_buf;
1999               memcpy(pimg->label, line, len);
2000               pimg->label[len] = '\0';
2001               osfree(line);
2002               break;
2003            case 'M': case 'D': {
2004               /* Move or Draw */
2005               long fpos = -1;
2006               if (pimg->survey && pimg->label_len == 0) {
2007                  /* We're only holding onto this line in case the first line
2008                   * of the 'N' is a 'D', so skip it for now...
2009                   */
2010                  goto skip_to_N;
2011               }
2012               if (ch == 'D' && pimg->pending == -1) {
2013                  if (pimg->survey) {
2014                     fpos = ftell(pimg->fh) - 1;
2015                     fseek(pimg->fh, pimg->start, SEEK_SET);
2016                     ch = GETC(pimg->fh);
2017                     pimg->pending = 0;
2018                  } else {
2019                     /* If a file actually has a 'D' before any 'M', then
2020                      * pretend the 'D' is an 'M' - one of the examples
2021                      * in the docs was like this! */
2022                     ch = 'M';
2023                  }
2024               }
2025               line = getline_alloc(pimg->fh);
2026               if (!line) {
2027                  img_errno = IMG_OUTOFMEMORY;
2028                  return img_BAD;
2029               }
2030               /* Compass stores coordinates as North, East, Up = (y,x,z)! */
2031               if (sscanf(line, "%lf%lf%lf", &p->y, &p->x, &p->z) != 3) {
2032                  osfree(line);
2033                  if (ferror(pimg->fh)) {
2034                     img_errno = IMG_READERROR;
2035                  } else {
2036                     img_errno = IMG_BADFORMAT;
2037                  }
2038                  return img_BAD;
2039               }
2040               p->x *= METRES_PER_FOOT;
2041               p->y *= METRES_PER_FOOT;
2042               p->z *= METRES_PER_FOOT;
2043               q = strchr(line, 'S');
2044               if (!q) {
2045                  osfree(line);
2046                  img_errno = IMG_BADFORMAT;
2047                  return img_BAD;
2048               }
2049               ++q;
2050               len = 0;
2051               while (q[len] > ' ') ++len;
2052               q[len] = '\0';
2053               len += 2; /* ' ' and '\0' */
2054               if (!check_label_space(pimg, pimg->label_len + len)) {
2055                  img_errno = IMG_OUTOFMEMORY;
2056                  return img_BAD;
2057               }
2058               pimg->label = pimg->label_buf;
2059               if (pimg->label_len) {
2060                   pimg->label[pimg->label_len] = ' ';
2061                   memcpy(pimg->label + pimg->label_len + 1, q, len - 1);
2062               } else {
2063                   memcpy(pimg->label, q, len - 1);
2064               }
2065               q += len - 1;
2066               /* Now read LRUD.  Technically, this is optional but virtually
2067                * all PLT files have it (with dummy negative values if no LRUD
2068                * was measured) and some versions of Compass can't read PLT
2069                * files without it!
2070                */
2071               while (*q && *q <= ' ') q++;
2072               if (*q == 'P') {
2073                   if (sscanf(q + 1, "%lf%lf%lf%lf",
2074                              &pimg->l, &pimg->r, &pimg->u, &pimg->d) != 4) {
2075                       osfree(line);
2076                       if (ferror(pimg->fh)) {
2077                           img_errno = IMG_READERROR;
2078                       } else {
2079                           img_errno = IMG_BADFORMAT;
2080                       }
2081                       return img_BAD;
2082                   }
2083                   pimg->l *= METRES_PER_FOOT;
2084                   pimg->r *= METRES_PER_FOOT;
2085                   pimg->u *= METRES_PER_FOOT;
2086                   pimg->d *= METRES_PER_FOOT;
2087               } else {
2088                   pimg->l = pimg->r = pimg->u = pimg->d = -1;
2089               }
2090               osfree(line);
2091               pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
2092               if (fpos != -1) {
2093                  fseek(pimg->fh, fpos, SEEK_SET);
2094               } else {
2095                  pimg->pending = (ch == 'M' ? img_MOVE : img_LINE) + 4;
2096               }
2097               return img_LABEL;
2098            }
2099            default:
2100               img_errno = IMG_BADFORMAT;
2101               return img_BAD;
2102         }
2103      }
2104   } else {
2105      /* CMAP .xyz file */
2106      char *line = NULL;
2107      char *q;
2108      size_t len;
2109
2110      if (pimg->pending) {
2111         /* pending MOVE or LINE or LABEL or STOP */
2112         int r = pimg->pending - 4;
2113         /* Set label to empty - don't use "" as we adjust label relative
2114          * to label_buf when label_buf is reallocated. */
2115         pimg->label = pimg->label_buf + strlen(pimg->label_buf);
2116         pimg->flags = 0;
2117         if (r == img_LABEL) {
2118            /* nasty magic */
2119            read_xyz_shot_coords(p, pimg->label_buf + 16);
2120            subtract_xyz_shot_deltas(p, pimg->label_buf + 16);
2121            pimg->pending = img_STOP + 4;
2122            return img_MOVE;
2123         }
2124
2125         pimg->pending = 0;
2126
2127         if (r == img_STOP) {
2128            /* nasty magic */
2129            read_xyz_shot_coords(p, pimg->label_buf + 16);
2130            return img_LINE;
2131         }
2132
2133         return r;
2134      }
2135
2136      pimg->label = pimg->label_buf;
2137      do {
2138         osfree(line);
2139         if (feof(pimg->fh)) return img_STOP;
2140         line = getline_alloc(pimg->fh);
2141         if (!line) {
2142            img_errno = IMG_OUTOFMEMORY;
2143            return img_BAD;
2144         }
2145      } while (line[0] == ' ' || line[0] == '\0');
2146      if (line[0] == '\x1a') return img_STOP;
2147
2148      len = strlen(line);
2149      if (pimg->version == VERSION_CMAP_STATION) {
2150         /* station variant */
2151         if (len < 37) {
2152            osfree(line);
2153            img_errno = IMG_BADFORMAT;
2154            return img_BAD;
2155         }
2156         memcpy(pimg->label, line, 6);
2157         q = (char *)memchr(pimg->label, ' ', 6);
2158         if (!q) q = pimg->label + 6;
2159         *q = '\0';
2160
2161         read_xyz_station_coords(p, line);
2162
2163         /* FIXME: look at prev for lines (line + 32, 5) */
2164         /* FIXME: duplicate stations... */
2165         return img_LABEL;
2166      } else {
2167         /* Shot variant (VERSION_CMAP_SHOT) */
2168         char old[8], new_[8];
2169         if (len < 61) {
2170            osfree(line);
2171            img_errno = IMG_BADFORMAT;
2172            return img_BAD;
2173         }
2174
2175         memcpy(old, line, 7);
2176         q = (char *)memchr(old, ' ', 7);
2177         if (!q) q = old + 7;
2178         *q = '\0';
2179
2180         memcpy(new_, line + 7, 7);
2181         q = (char *)memchr(new_, ' ', 7);
2182         if (!q) q = new_ + 7;
2183         *q = '\0';
2184
2185         pimg->flags = img_SFLAG_UNDERGROUND;
2186
2187         if (strcmp(old, new_) == 0) {
2188            pimg->pending = img_MOVE + 4;
2189            read_xyz_shot_coords(p, line);
2190            strcpy(pimg->label, new_);
2191            osfree(line);
2192            return img_LABEL;
2193         }
2194
2195         if (strcmp(old, pimg->label) == 0) {
2196            pimg->pending = img_LINE + 4;
2197            read_xyz_shot_coords(p, line);
2198            strcpy(pimg->label, new_);
2199            osfree(line);
2200            return img_LABEL;
2201         }
2202
2203         pimg->pending = img_LABEL + 4;
2204         read_xyz_shot_coords(p, line);
2205         strcpy(pimg->label, new_);
2206         memcpy(pimg->label + 16, line, 70);
2207
2208         osfree(line);
2209         return img_LABEL;
2210      }
2211   }
2212}
2213
2214static void
2215write_coord(FILE *fh, double x, double y, double z)
2216{
2217   SVX_ASSERT(fh);
2218   /* Output in cm */
2219   static INT32_T X_, Y_, Z_;
2220   INT32_T X = my_lround(x * 100.0);
2221   INT32_T Y = my_lround(y * 100.0);
2222   INT32_T Z = my_lround(z * 100.0);
2223
2224   X_ -= X;
2225   Y_ -= Y;
2226   Z_ -= Z;
2227   put32(X, fh);
2228   put32(Y, fh);
2229   put32(Z, fh);
2230   X_ = X; Y_ = Y; Z_ = Z;
2231}
2232
2233static int
2234write_v3label(img *pimg, int opt, const char *s)
2235{
2236   size_t len, n, dot;
2237
2238   /* find length of common prefix */
2239   dot = 0;
2240   for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2241      if (s[len] == '.') dot = len + 1;
2242   }
2243
2244   SVX_ASSERT(len <= pimg->label_len);
2245   n = pimg->label_len - len;
2246   if (len == 0) {
2247      if (pimg->label_len) PUTC(0, pimg->fh);
2248   } else if (n <= 16) {
2249      if (n) PUTC(n + 15, pimg->fh);
2250   } else if (dot == 0) {
2251      if (pimg->label_len) PUTC(0, pimg->fh);
2252      len = 0;
2253   } else {
2254      const char *p = pimg->label_buf + dot;
2255      n = 1;
2256      for (len = pimg->label_len - dot - 17; len; len--) {
2257         if (*p++ == '.') n++;
2258      }
2259      if (n <= 14) {
2260         PUTC(n, pimg->fh);
2261         len = dot;
2262      } else {
2263         if (pimg->label_len) PUTC(0, pimg->fh);
2264         len = 0;
2265      }
2266   }
2267
2268   n = strlen(s + len);
2269   PUTC(opt, pimg->fh);
2270   if (n < 0xfe) {
2271      PUTC(n, pimg->fh);
2272   } else if (n < 0xffff + 0xfe) {
2273      PUTC(0xfe, pimg->fh);
2274      put16((short)(n - 0xfe), pimg->fh);
2275   } else {
2276      PUTC(0xff, pimg->fh);
2277      put32(n, pimg->fh);
2278   }
2279   fwrite(s + len, n, 1, pimg->fh);
2280
2281   n += len;
2282   pimg->label_len = n;
2283   if (!check_label_space(pimg, n + 1))
2284      return 0; /* FIXME: distinguish out of memory... */
2285   memcpy(pimg->label_buf + len, s + len, n - len + 1);
2286
2287   return !ferror(pimg->fh);
2288}
2289
2290static int
2291write_v8label(img *pimg, int opt, int common_flag, size_t common_val,
2292              const char *s)
2293{
2294   size_t len, del, add;
2295
2296   /* find length of common prefix */
2297   for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2298   }
2299
2300   SVX_ASSERT(len <= pimg->label_len);
2301   del = pimg->label_len - len;
2302   add = strlen(s + len);
2303
2304   if (add == common_val && del == common_val) {
2305      PUTC(opt | common_flag, pimg->fh);
2306   } else {
2307      PUTC(opt, pimg->fh);
2308      if (del <= 15 && add <= 15 && (del || add)) {
2309         PUTC((del << 4) | add, pimg->fh);
2310      } else {
2311         PUTC(0x00, pimg->fh);
2312         if (del < 0xff) {
2313            PUTC(del, pimg->fh);
2314         } else {
2315            PUTC(0xff, pimg->fh);
2316            put32(del, pimg->fh);
2317         }
2318         if (add < 0xff) {
2319            PUTC(add, pimg->fh);
2320         } else {
2321            PUTC(0xff, pimg->fh);
2322            put32(add, pimg->fh);
2323         }
2324      }
2325   }
2326
2327   if (add)
2328      fwrite(s + len, add, 1, pimg->fh);
2329
2330   pimg->label_len = len + add;
2331   if (add > del && !check_label_space(pimg, pimg->label_len + 1))
2332      return 0; /* FIXME: distinguish out of memory... */
2333
2334   memcpy(pimg->label_buf + len, s + len, add + 1);
2335
2336   return !ferror(pimg->fh);
2337}
2338
2339static void
2340img_write_item_date_new(img *pimg)
2341{
2342    int same, unset;
2343    /* Only write dates when they've changed. */
2344#if IMG_API_VERSION == 0
2345    if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2346        return;
2347
2348    same = (pimg->date1 == pimg->date2);
2349    unset = (pimg->date1 == 0);
2350#else /* IMG_API_VERSION == 1 */
2351    if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2352        return;
2353
2354    same = (pimg->days1 == pimg->days2);
2355    unset = (pimg->days1 == -1);
2356#endif
2357
2358    if (same) {
2359        if (unset) {
2360            PUTC(0x10, pimg->fh);
2361        } else {
2362            PUTC(0x11, pimg->fh);
2363#if IMG_API_VERSION == 0
2364            put16(pimg->date1 / 86400 + 25567, pimg->fh);
2365#else /* IMG_API_VERSION == 1 */
2366            put16(pimg->days1, pimg->fh);
2367#endif
2368        }
2369    } else {
2370#if IMG_API_VERSION == 0
2371        int diff = (pimg->date2 - pimg->date1) / 86400;
2372        if (diff > 0 && diff <= 256) {
2373            PUTC(0x12, pimg->fh);
2374            put16(pimg->date1 / 86400 + 25567, pimg->fh);
2375            PUTC(diff - 1, pimg->fh);
2376        } else {
2377            PUTC(0x13, pimg->fh);
2378            put16(pimg->date1 / 86400 + 25567, pimg->fh);
2379            put16(pimg->date2 / 86400 + 25567, pimg->fh);
2380        }
2381#else /* IMG_API_VERSION == 1 */
2382        int diff = pimg->days2 - pimg->days1;
2383        if (diff > 0 && diff <= 256) {
2384            PUTC(0x12, pimg->fh);
2385            put16(pimg->days1, pimg->fh);
2386            PUTC(diff - 1, pimg->fh);
2387        } else {
2388            PUTC(0x13, pimg->fh);
2389            put16(pimg->days1, pimg->fh);
2390            put16(pimg->days2, pimg->fh);
2391        }
2392#endif
2393    }
2394#if IMG_API_VERSION == 0
2395    pimg->olddate1 = pimg->date1;
2396    pimg->olddate2 = pimg->date2;
2397#else /* IMG_API_VERSION == 1 */
2398    pimg->olddays1 = pimg->days1;
2399    pimg->olddays2 = pimg->days2;
2400#endif
2401}
2402
2403static void
2404img_write_item_date(img *pimg)
2405{
2406    int same, unset;
2407    /* Only write dates when they've changed. */
2408#if IMG_API_VERSION == 0
2409    if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2410        return;
2411
2412    same = (pimg->date1 == pimg->date2);
2413    unset = (pimg->date1 == 0);
2414#else /* IMG_API_VERSION == 1 */
2415    if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2416        return;
2417
2418    same = (pimg->days1 == pimg->days2);
2419    unset = (pimg->days1 == -1);
2420#endif
2421
2422    if (same) {
2423        if (img_output_version < 7) {
2424            PUTC(0x20, pimg->fh);
2425#if IMG_API_VERSION == 0
2426            put32(pimg->date1, pimg->fh);
2427#else /* IMG_API_VERSION == 1 */
2428            put32((pimg->days1 - 25567) * 86400, pimg->fh);
2429#endif
2430        } else {
2431            if (unset) {
2432                PUTC(0x24, pimg->fh);
2433            } else {
2434                PUTC(0x20, pimg->fh);
2435#if IMG_API_VERSION == 0
2436                put16(pimg->date1 / 86400 + 25567, pimg->fh);
2437#else /* IMG_API_VERSION == 1 */
2438                put16(pimg->days1, pimg->fh);
2439#endif
2440            }
2441        }
2442    } else {
2443        if (img_output_version < 7) {
2444            PUTC(0x21, pimg->fh);
2445#if IMG_API_VERSION == 0
2446            put32(pimg->date1, pimg->fh);
2447            put32(pimg->date2, pimg->fh);
2448#else /* IMG_API_VERSION == 1 */
2449            put32((pimg->days1 - 25567) * 86400, pimg->fh);
2450            put32((pimg->days2 - 25567) * 86400, pimg->fh);
2451#endif
2452        } else {
2453#if IMG_API_VERSION == 0
2454            int diff = (pimg->date2 - pimg->date1) / 86400;
2455            if (diff > 0 && diff <= 256) {
2456                PUTC(0x21, pimg->fh);
2457                put16(pimg->date1 / 86400 + 25567, pimg->fh);
2458                PUTC(diff - 1, pimg->fh);
2459            } else {
2460                PUTC(0x23, pimg->fh);
2461                put16(pimg->date1 / 86400 + 25567, pimg->fh);
2462                put16(pimg->date2 / 86400 + 25567, pimg->fh);
2463            }
2464#else /* IMG_API_VERSION == 1 */
2465            int diff = pimg->days2 - pimg->days1;
2466            if (diff > 0 && diff <= 256) {
2467                PUTC(0x21, pimg->fh);
2468                put16(pimg->days1, pimg->fh);
2469                PUTC(diff - 1, pimg->fh);
2470            } else {
2471                PUTC(0x23, pimg->fh);
2472                put16(pimg->days1, pimg->fh);
2473                put16(pimg->days2, pimg->fh);
2474            }
2475#endif
2476        }
2477    }
2478#if IMG_API_VERSION == 0
2479    pimg->olddate1 = pimg->date1;
2480    pimg->olddate2 = pimg->date2;
2481#else /* IMG_API_VERSION == 1 */
2482    pimg->olddays1 = pimg->days1;
2483    pimg->olddays2 = pimg->days2;
2484#endif
2485}
2486
2487static void
2488img_write_item_new(img *pimg, int code, int flags, const char *s,
2489                   double x, double y, double z);
2490static void
2491img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2492                     double x, double y, double z);
2493static void
2494img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2495                       double x, double y, double z);
2496
2497void
2498img_write_item(img *pimg, int code, int flags, const char *s,
2499               double x, double y, double z)
2500{
2501   if (!pimg) return;
2502   if (pimg->version >= 8) {
2503      img_write_item_new(pimg, code, flags, s, x, y, z);
2504   } else if (pimg->version >= 3) {
2505      img_write_item_v3to7(pimg, code, flags, s, x, y, z);
2506   } else {
2507      img_write_item_ancient(pimg, code, flags, s, x, y, z);
2508   }
2509}
2510
2511static void
2512img_write_item_new(img *pimg, int code, int flags, const char *s,
2513                   double x, double y, double z)
2514{
2515   switch (code) {
2516    case img_LABEL:
2517      write_v8label(pimg, 0x80 | flags, 0, -1, s);
2518      break;
2519    case img_XSECT: {
2520      INT32_T l, r, u, d, max_dim;
2521      img_write_item_date_new(pimg);
2522      l = (INT32_T)my_lround(pimg->l * 100.0);
2523      r = (INT32_T)my_lround(pimg->r * 100.0);
2524      u = (INT32_T)my_lround(pimg->u * 100.0);
2525      d = (INT32_T)my_lround(pimg->d * 100.0);
2526      if (l < 0) l = -1;
2527      if (r < 0) r = -1;
2528      if (u < 0) u = -1;
2529      if (d < 0) d = -1;
2530      max_dim = max(max(l, r), max(u, d));
2531      flags = (flags & img_XFLAG_END) ? 1 : 0;
2532      if (max_dim >= 32768) flags |= 2;
2533      write_v8label(pimg, 0x30 | flags, 0, -1, s);
2534      if (flags & 2) {
2535         /* Big passage!  Need to use 4 bytes. */
2536         put32(l, pimg->fh);
2537         put32(r, pimg->fh);
2538         put32(u, pimg->fh);
2539         put32(d, pimg->fh);
2540      } else {
2541         put16(l, pimg->fh);
2542         put16(r, pimg->fh);
2543         put16(u, pimg->fh);
2544         put16(d, pimg->fh);
2545      }
2546      return;
2547    }
2548    case img_MOVE:
2549      PUTC(15, pimg->fh);
2550      break;
2551    case img_LINE:
2552      img_write_item_date_new(pimg);
2553      if (pimg->style != pimg->oldstyle) {
2554          switch (pimg->style) {
2555            case img_STYLE_NORMAL:
2556            case img_STYLE_DIVING:
2557            case img_STYLE_CARTESIAN:
2558            case img_STYLE_CYLPOLAR:
2559            case img_STYLE_NOSURVEY:
2560               PUTC(pimg->style, pimg->fh);
2561               break;
2562          }
2563          pimg->oldstyle = pimg->style;
2564      }
2565      write_v8label(pimg, 0x40 | flags, 0x20, 0x00, s ? s : "");
2566      break;
2567    default: /* ignore for now */
2568      return;
2569   }
2570   write_coord(pimg->fh, x, y, z);
2571}
2572
2573static void
2574img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2575                     double x, double y, double z)
2576{
2577   switch (code) {
2578    case img_LABEL:
2579      write_v3label(pimg, 0x40 | flags, s);
2580      break;
2581    case img_XSECT: {
2582      INT32_T l, r, u, d, max_dim;
2583      /* Need at least version 5 for img_XSECT. */
2584      if (pimg->version < 5) return;
2585      img_write_item_date(pimg);
2586      l = (INT32_T)my_lround(pimg->l * 100.0);
2587      r = (INT32_T)my_lround(pimg->r * 100.0);
2588      u = (INT32_T)my_lround(pimg->u * 100.0);
2589      d = (INT32_T)my_lround(pimg->d * 100.0);
2590      if (l < 0) l = -1;
2591      if (r < 0) r = -1;
2592      if (u < 0) u = -1;
2593      if (d < 0) d = -1;
2594      max_dim = max(max(l, r), max(u, d));
2595      flags = (flags & img_XFLAG_END) ? 1 : 0;
2596      if (max_dim >= 32768) flags |= 2;
2597      write_v3label(pimg, 0x30 | flags, s);
2598      if (flags & 2) {
2599         /* Big passage!  Need to use 4 bytes. */
2600         put32(l, pimg->fh);
2601         put32(r, pimg->fh);
2602         put32(u, pimg->fh);
2603         put32(d, pimg->fh);
2604      } else {
2605         put16(l, pimg->fh);
2606         put16(r, pimg->fh);
2607         put16(u, pimg->fh);
2608         put16(d, pimg->fh);
2609      }
2610      return;
2611    }
2612    case img_MOVE:
2613      PUTC(15, pimg->fh);
2614      break;
2615    case img_LINE:
2616      if (pimg->version >= 4) {
2617          img_write_item_date(pimg);
2618      }
2619      write_v3label(pimg, 0x80 | flags, s ? s : "");
2620      break;
2621    default: /* ignore for now */
2622      return;
2623   }
2624   write_coord(pimg->fh, x, y, z);
2625}
2626
2627static void
2628img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2629                       double x, double y, double z)
2630{
2631   size_t len;
2632   INT32_T opt = 0;
2633   SVX_ASSERT(pimg->version > 0);
2634   switch (code) {
2635    case img_LABEL:
2636      if (pimg->version == 1) {
2637         /* put a move before each label */
2638         img_write_item_ancient(pimg, img_MOVE, 0, NULL, x, y, z);
2639         put32(2, pimg->fh);
2640         fputsnl(s, pimg->fh);
2641         return;
2642      }
2643      len = strlen(s);
2644      if (len > 255 || strchr(s, '\n')) {
2645         /* long label - not in early incarnations of v2 format, but few
2646          * 3d files will need these, so better not to force incompatibility
2647          * with a new version I think... */
2648         PUTC(7, pimg->fh);
2649         PUTC(flags, pimg->fh);
2650         put32(len, pimg->fh);
2651         fputs(s, pimg->fh);
2652      } else {
2653         PUTC(0x40 | (flags & 0x3f), pimg->fh);
2654         fputsnl(s, pimg->fh);
2655      }
2656      opt = 0;
2657      break;
2658    case img_MOVE:
2659      opt = 4;
2660      break;
2661    case img_LINE:
2662      if (pimg->version > 1) {
2663         opt = 0x80 | (flags & 0x3f);
2664         break;
2665      }
2666      opt = 5;
2667      break;
2668    default: /* ignore for now */
2669      return;
2670   }
2671   if (pimg->version == 1) {
2672      put32(opt, pimg->fh);
2673   } else {
2674      if (opt) PUTC(opt, pimg->fh);
2675   }
2676   write_coord(pimg->fh, x, y, z);
2677}
2678
2679/* Write error information for the current traverse
2680 * n_legs is the number of legs in the traverse
2681 * length is the traverse length (in m)
2682 * E is the ratio of the observed misclosure to the theoretical one
2683 * H is the ratio of the observed horizontal misclosure to the theoretical one
2684 * V is the ratio of the observed vertical misclosure to the theoretical one
2685 */
2686void
2687img_write_errors(img *pimg, int n_legs, double length,
2688                 double E, double H, double V)
2689{
2690    PUTC((pimg->version >= 8 ? 0x1f : 0x22), pimg->fh);
2691    put32(n_legs, pimg->fh);
2692    put32((INT32_T)my_lround(length * 100.0), pimg->fh);
2693    put32((INT32_T)my_lround(E * 100.0), pimg->fh);
2694    put32((INT32_T)my_lround(H * 100.0), pimg->fh);
2695    put32((INT32_T)my_lround(V * 100.0), pimg->fh);
2696}
2697
2698int
2699img_close(img *pimg)
2700{
2701   int result = 1;
2702   if (pimg) {
2703      if (pimg->fh) {
2704         if (pimg->fRead) {
2705            osfree(pimg->survey);
2706            osfree(pimg->title);
2707            osfree(pimg->datestamp);
2708         } else {
2709            /* write end of data marker */
2710            switch (pimg->version) {
2711             case 1:
2712               put32((INT32_T)-1, pimg->fh);
2713               break;
2714             default:
2715               if (pimg->version <= 7 ?
2716                   (pimg->label_len != 0) :
2717                   (pimg->style != img_STYLE_NORMAL)) {
2718                  PUTC(0, pimg->fh);
2719               }
2720               /* FALL THROUGH */
2721             case 2:
2722               PUTC(0, pimg->fh);
2723               break;
2724            }
2725         }
2726         if (ferror(pimg->fh)) result = 0;
2727         if (fclose(pimg->fh)) result = 0;
2728         if (!result) img_errno = pimg->fRead ? IMG_READERROR : IMG_WRITEERROR;
2729      }
2730      osfree(pimg->label_buf);
2731      osfree(pimg->filename_opened);
2732      osfree(pimg);
2733   }
2734   return result;
2735}
Note: See TracBrowser for help on using the repository browser.