source: git/src/img.c @ 22753f1

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

img: Support writing 3d file to an existing FILE*

This complements the support for reading from an existing FILE*.

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