source: git/src/img.c @ 2acbff0a

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

src/img.c: Only define INT32_T if it isn't already defined (it will
be for 1.0.x).

git-svn-id: file:///home/survex-svn/survex/trunk@3680 4b37db11-9a0c-4f06-9ece-9ab7cdaee568

  • Property mode set to 100644
File size: 49.2 KB
Line 
1/* img.c
2 * Routines for reading and writing Survex ".3d" image files
3 * Copyright (C) 1993-2004,2005,2006,2010,2011 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 <stdio.h>
25#include <string.h>
26#include <stdlib.h>
27#include <time.h>
28#include <ctype.h>
29#include <locale.h>
30
31#include "img.h"
32
33#ifndef IMG_API_VERSION
34# define IMG_API_VERSION 0
35#elif IMG_API_VERSION > 1
36# error IMG_API_VERSION > 1 too new
37#endif
38
39#ifdef IMG_HOSTED
40# include "debug.h"
41# include "filelist.h"
42# include "filename.h"
43# include "message.h"
44# include "useful.h"
45# define TIMENA msg(/*Date and time not available.*/108)
46# define TIMEFMT msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107)
47# ifndef INT32_T
48#  define INT32_T int32_t
49# endif
50#else
51# define INT32_T int
52# define TIMENA "Time not available."
53# define TIMEFMT "%a,%Y.%m.%d %H:%M:%S %Z"
54# define EXT_SVX_3D "3d"
55# define EXT_SVX_POS "pos"
56# define FNM_SEP_EXT '.'
57# define METRES_PER_FOOT 0.3048 /* exact value */
58# define xosmalloc(L) malloc((L))
59# define xosrealloc(L,S) realloc((L),(S))
60# define osfree(P) free((P))
61# define ossizeof(T) sizeof(T)
62/* in IMG_HOSTED mode, this tests if a filename refers to a directory */
63# define fDirectory(X) 0
64/* open file FNM with mode MODE, maybe using path PTH and/or extension EXT */
65/* path isn't used in img.c, but EXT is */
66# define fopenWithPthAndExt(PTH,FNM,EXT,MODE,X) fopen(FNM,MODE)
67# define PUTC(C, FH) putc(C, FH)
68# define GETC(FH) getc(FH)
69# define fFalse 0
70# define fTrue 1
71# define bool int
72/* dummy do {...} while(0) hack to permit stuff like
73 * if (s) fputsnl(s,fh); else exit(1);
74 * to work as intended
75 */
76# define fputsnl(S, FH) do {fputs((S), (FH)); PUTC('\n', (FH));} while(0)
77# define SVX_ASSERT(X)
78
79static INT32_T
80get32(FILE *fh)
81{
82   INT32_T w = GETC(fh);
83   w |= (INT32_T)GETC(fh) << 8l;
84   w |= (INT32_T)GETC(fh) << 16l;
85   w |= (INT32_T)GETC(fh) << 24l;
86   return w;
87}
88
89static void
90put32(long w, FILE *fh)
91{
92   PUTC((char)(w), fh);
93   PUTC((char)(w >> 8l), fh);
94   PUTC((char)(w >> 16l), fh);
95   PUTC((char)(w >> 24l), fh);
96}
97
98static short
99get16(FILE *fh)
100{
101   short w = GETC(fh);
102   w |= (short)GETC(fh) << 8l;
103   return w;
104}
105
106static void
107put16(short w, FILE *fh)
108{
109   PUTC((char)(w), fh);
110   PUTC((char)(w >> 8l), fh);
111}
112
113static char *
114baseleaf_from_fnm(const char *fnm)
115{
116   const char *p;
117   char *q;
118   size_t len;
119
120   p = fnm;
121   q = strrchr(p, '/');
122   if (q) p = q + 1;
123   q = strrchr(p, '\\');
124   if (q) p = q + 1;
125
126   q = strrchr(p, FNM_SEP_EXT);
127   if (q) len = (const char *)q - p; else len = strlen(p);
128
129   q = xosmalloc(len + 1);
130   if (!q) return NULL;
131   memcpy(q, p, len);
132   q[len] = '\0';
133   return q;
134}
135#endif
136
137static unsigned short
138getu16(FILE *fh)
139{
140   return (unsigned short)get16(fh);
141}
142
143#include <math.h>
144#ifdef HAVE_ROUND
145extern double round(double); /* prototype is often missing... */
146# define my_round round
147#else
148static double
149my_round(double x) {
150   if (x >= 0.0) return floor(x + 0.5);
151   return ceil(x - 0.5);
152}
153#endif
154
155/* portable case insensitive string compare */
156#if defined(strcasecmp) || defined(HAVE_STRCASECMP)
157# define my_strcasecmp strcasecmp
158#else
159/* What about top bit set chars? */
160int my_strcasecmp(const char *s1, const char *s2) {
161   register int c1, c2;
162   do {
163      c1 = *s1++;
164      c2 = *s2++;
165   } while (c1 && toupper(c1) == toupper(c2));
166   /* now calculate real difference */
167   return c1 - c2;
168}
169#endif
170
171unsigned int img_output_version = IMG_VERSION_MAX;
172
173#ifdef IMG_HOSTED
174static enum {
175   IMG_NONE = 0,
176   IMG_FILENOTFOUND = /*Couldn't open data file `%s'*/24,
177   IMG_OUTOFMEMORY  = /*Out of memory %.0s*/38,
178   IMG_DIRECTORY    = /*Filename `%s' refers to directory*/44,
179   IMG_CANTOPENOUT  = /*Failed to open output file `%s'*/47,
180   IMG_BADFORMAT    = /*Bad 3d image file `%s'*/106,
181   IMG_READERROR    = /*Error reading from file `%s'*/109,
182   IMG_WRITEERROR   = /*Error writing to file `%s'*/110,
183   IMG_TOONEW       = /*File `%s' has a newer format than this program can understand*/114
184} img_errno = IMG_NONE;
185#else
186static img_errcode img_errno = IMG_NONE;
187#endif
188
189#define FILEID "Survex 3D Image File"
190
191#define EXT_PLT "plt"
192#define EXT_PLF "plf"
193#define EXT_XYZ "xyz"
194
195/* Attempt to string paste to ensure we are passed a literal string */
196#define LITLEN(S) (sizeof(S"") - 1)
197
198/* Fake "version numbers" for non-3d formats we can read. */
199#define VERSION_CMAP_SHOT       -4
200#define VERSION_CMAP_STATION    -3
201#define VERSION_COMPASS_PLT     -2
202#define VERSION_SURVEX_POS      -1
203
204static char *
205my_strdup(const char *str)
206{
207   char *p;
208   size_t len = strlen(str) + 1;
209   p = xosmalloc(len);
210   if (p) memcpy(p, str, len);
211   return p;
212}
213
214static char *
215getline_alloc(FILE *fh)
216{
217   int ch;
218   size_t i = 0;
219   size_t len = 16;
220   char *buf = xosmalloc(len);
221   if (!buf) return NULL;
222
223   ch = GETC(fh);
224   while (ch != '\n' && ch != '\r' && ch != EOF) {
225      buf[i++] = ch;
226      if (i == len - 1) {
227         char *p;
228         len += len;
229         p = xosrealloc(buf, len);
230         if (!p) {
231            osfree(buf);
232            return NULL;
233         }
234         buf = p;
235      }
236      ch = GETC(fh);
237   }
238   if (ch == '\n' || ch == '\r') {
239      int otherone = ch ^ ('\n' ^ '\r');
240      ch = GETC(fh);
241      /* if it's not the other eol character, put it back */
242      if (ch != otherone) ungetc(ch, fh);
243   }
244   buf[i++] = '\0';
245   return buf;
246}
247
248#ifndef IMG_HOSTED
249img_errcode
250img_error(void)
251{
252   return img_errno;
253}
254#else
255int
256img_error(void)
257{
258   return (int)img_errno;
259}
260#endif
261
262static bool
263check_label_space(img *pimg, size_t len)
264{
265   if (len > pimg->buf_len) {
266      char *b = xosrealloc(pimg->label_buf, len);
267      if (!b) return fFalse;
268      pimg->label = (pimg->label - pimg->label_buf) + b;
269      pimg->label_buf = b;
270      pimg->buf_len = len;
271   }
272   return fTrue;
273}
274
275#define has_ext(F,L,E) ((L) > LITLEN(E) + 1 &&\
276                        (F)[(L) - LITLEN(E) - 1] == FNM_SEP_EXT &&\
277                        my_strcasecmp((F) + (L) - LITLEN(E), E) == 0)
278
279img *
280img_open_survey(const char *fnm, const char *survey)
281{
282   img *pimg;
283   size_t len;
284   char buf[LITLEN(FILEID) + 9];
285   int ch;
286
287   if (fDirectory(fnm)) {
288      img_errno = IMG_DIRECTORY;
289      return NULL;
290   }
291
292   pimg = (img *)xosmalloc(ossizeof(img));
293   if (pimg == NULL) {
294      img_errno = IMG_OUTOFMEMORY;
295      return NULL;
296   }
297
298   pimg->buf_len = 257;
299   pimg->label_buf = xosmalloc(pimg->buf_len);
300   if (!pimg->label_buf) {
301      osfree(pimg);
302      img_errno = IMG_OUTOFMEMORY;
303      return NULL;
304   }
305
306   pimg->fh = fopenWithPthAndExt("", fnm, EXT_SVX_3D, "rb", &(pimg->filename_opened));
307   if (pimg->fh == NULL) {
308      osfree(pimg->label_buf);
309      osfree(pimg);
310      img_errno = IMG_FILENOTFOUND;
311      return NULL;
312   }
313
314   pimg->fRead = fTrue; /* reading from this file */
315   img_errno = IMG_NONE;
316
317   pimg->flags = 0;
318
319   /* for version >= 3 we use label_buf to store the prefix for reuse */
320   /* for VERSION_COMPASS_PLT, 0 value indicates we haven't
321    * entered a survey yet */
322   /* for VERSION_CMAP_SHOT, we store the last station here
323    * to detect whether we MOVE or LINE */
324   pimg->label_len = 0;
325   pimg->label_buf[0] = '\0';
326
327   pimg->survey = NULL;
328   pimg->survey_len = 0;
329   pimg->separator = '.';
330#if IMG_API_VERSION == 0
331   pimg->date1 = pimg->date2 = 0;
332#else /* IMG_API_VERSION == 1 */
333   pimg->days1 = pimg->days2 = -1;
334#endif
335   pimg->is_extended_elevation = 0;
336
337   pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
338
339   pimg->title = pimg->datestamp = NULL;
340   if (survey) {
341      len = strlen(survey);
342      if (len) {
343         if (survey[len - 1] == '.') len--;
344         if (len) {
345            char *p;
346            pimg->survey = xosmalloc(len + 2);
347            if (!pimg->survey) {
348               img_errno = IMG_OUTOFMEMORY;
349               goto error;
350            }
351            memcpy(pimg->survey, survey, len);
352            /* Set title to leaf survey name */
353            pimg->survey[len] = '\0';
354            p = strchr(pimg->survey, '.');
355            if (p) p++; else p = pimg->survey;
356            pimg->title = my_strdup(p);
357            if (!pimg->title) {
358               img_errno = IMG_OUTOFMEMORY;
359               goto error;
360            }
361            pimg->survey[len] = '.';
362            pimg->survey[len + 1] = '\0';
363         }
364      }
365      pimg->survey_len = len;
366   }
367
368   /* [VERSION_COMPASS_PLT, VERSION_CMAP_STATION, VERSION_CMAP_SHOT] pending
369    * IMG_LINE or IMG_MOVE - both have 4 added.
370    * [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
371    * [version 0] not in the middle of a 'LINE' command
372    * [version >= 3] not in the middle of turning a LINE into a MOVE
373    */
374   pimg->pending = 0;
375
376   len = strlen(fnm);
377   if (has_ext(fnm, len, EXT_SVX_POS)) {
378pos_file:
379      pimg->version = VERSION_SURVEX_POS;
380      if (!pimg->survey) pimg->title = baseleaf_from_fnm(fnm);
381      pimg->datestamp = my_strdup(TIMENA);
382      if (!pimg->datestamp) {
383         img_errno = IMG_OUTOFMEMORY;
384         goto error;
385      }
386      pimg->start = 0;
387      return pimg;
388   }
389
390   if (has_ext(fnm, len, EXT_PLT) || has_ext(fnm, len, EXT_PLF)) {
391      long fpos;
392plt_file:
393      pimg->version = VERSION_COMPASS_PLT;
394      /* Spaces aren't legal in Compass station names, but dots are, so
395       * use space as the level separator */
396      pimg->separator = ' ';
397      pimg->start = 0;
398      if (!pimg->survey) pimg->title = baseleaf_from_fnm(fnm);
399      pimg->datestamp = my_strdup(TIMENA);
400      if (!pimg->datestamp) {
401         img_errno = IMG_OUTOFMEMORY;
402         goto error;
403      }
404      while (1) {
405         ch = GETC(pimg->fh);
406         switch (ch) {
407          case '\x1a':
408            fseek(pimg->fh, -1, SEEK_CUR);
409            /* FALL THRU */
410          case EOF:
411            pimg->start = ftell(pimg->fh);
412            return pimg;
413          case 'N': {
414            char *line, *q;
415            fpos = ftell(pimg->fh) - 1;
416            if (!pimg->survey) {
417               /* FIXME : if there's only one survey in the file, it'd be nice
418                * to use its description as the title here...
419                */
420               ungetc('N', pimg->fh);
421               pimg->start = fpos;
422               return pimg;
423            }
424            line = getline_alloc(pimg->fh);
425            if (!line) {
426               img_errno = IMG_OUTOFMEMORY;
427               goto error;
428            }
429            len = 0;
430            while (line[len] > 32) ++len;
431            if (pimg->survey_len != len ||
432                memcmp(line, pimg->survey, len) != 0) {
433               osfree(line);
434               continue;
435            }
436            q = strchr(line + len, 'C');
437            if (q && q[1]) {
438                osfree(pimg->title);
439                pimg->title = my_strdup(q + 1);
440            } else if (!pimg->title) {
441                pimg->title = my_strdup(pimg->label);
442            }
443            osfree(line);
444            if (!pimg->title) {
445                img_errno = IMG_OUTOFMEMORY;
446                goto error;
447            }
448            if (!pimg->start) pimg->start = fpos;
449            fseek(pimg->fh, pimg->start, SEEK_SET);
450            return pimg;
451          }
452          case 'M': case 'D':
453            pimg->start = ftell(pimg->fh) - 1;
454            break;
455         }
456         while (ch != '\n' && ch != '\r') {
457            ch = GETC(pimg->fh);
458         }
459      }
460   }
461
462   if (has_ext(fnm, len, EXT_XYZ)) {
463      char *line;
464xyz_file:
465      /* Spaces aren't legal in CMAP station names, but dots are, so
466       * use space as the level separator. */
467      pimg->separator = ' ';
468      line = getline_alloc(pimg->fh);
469      if (!line) {
470         img_errno = IMG_OUTOFMEMORY;
471         goto error;
472      }
473      /* FIXME: reparse date? */
474      len = strlen(line);
475      if (len > 59) line[59] = '\0';
476      if (len > 45) {
477         pimg->datestamp = my_strdup(line + 45);
478      } else {
479         pimg->datestamp = my_strdup(TIMENA);
480      }
481      if (strncmp(line, "  Cave Survey Data Processed by CMAP ",
482                  LITLEN("  Cave Survey Data Processed by CMAP ")) == 0) {
483         len = 0;
484      } else {
485         if (len > 45) {
486            line[45] = '\0';
487            len = 45;
488         }
489         while (len > 2 && line[len - 1] == ' ') --len;
490         if (len > 2) {
491            line[len] = '\0';
492            pimg->title = my_strdup(line + 2);
493         }
494      }
495      if (len <= 2) pimg->title = baseleaf_from_fnm(fnm);
496      osfree(line);
497      if (!pimg->datestamp || !pimg->title) {
498         img_errno = IMG_OUTOFMEMORY;
499         goto error;
500      }
501      line = getline_alloc(pimg->fh);
502      if (!line) {
503         img_errno = IMG_OUTOFMEMORY;
504         goto error;
505      }
506      if (line[0] != ' ' || (line[1] != 'S' && line[1] != 'O')) {
507         img_errno = IMG_BADFORMAT;
508         goto error;
509      }
510      if (line[1] == 'S') {
511         pimg->version = VERSION_CMAP_STATION;
512      } else {
513         pimg->version = VERSION_CMAP_SHOT;
514      }
515      osfree(line);
516      line = getline_alloc(pimg->fh);
517      if (!line) {
518         img_errno = IMG_OUTOFMEMORY;
519         goto error;
520      }
521      if (line[0] != ' ' || line[1] != '-') {
522         img_errno = IMG_BADFORMAT;
523         goto error;
524      }
525      osfree(line);
526      pimg->start = ftell(pimg->fh);
527      return pimg;
528   }
529
530   if (fread(buf, LITLEN(FILEID) + 1, 1, pimg->fh) != 1 ||
531       memcmp(buf, FILEID"\n", LITLEN(FILEID) + 1) != 0) {
532      if (fread(buf + LITLEN(FILEID) + 1, 8, 1, pimg->fh) == 1 &&
533          memcmp(buf, FILEID"\r\nv0.01\r\n", LITLEN(FILEID) + 9) == 0) {
534         /* v0 3d file with DOS EOLs */
535         pimg->version = 0;
536         goto v03d;
537      }
538      rewind(pimg->fh);
539      if (buf[1] == ' ') {
540         if (buf[0] == ' ') {
541            /* Looks like a CMAP .xyz file ... */
542            goto xyz_file;
543         } else if (strchr("ZSNF", buf[0])) {
544            /* Looks like a Compass .plt file ... */
545            /* Almost certainly it'll start "Z " */
546            goto plt_file;
547         }
548      }
549      if (buf[0] == '(') {
550         /* Looks like a Survex .pos file ... */
551         goto pos_file;
552      }
553      img_errno = IMG_BADFORMAT;
554      goto error;
555   }
556
557   /* check file format version */
558   ch = GETC(pimg->fh);
559   pimg->version = 0;
560   if (tolower(ch) == 'b') {
561      /* binary file iff B/b prefix */
562      pimg->version = 1;
563      ch = GETC(pimg->fh);
564   }
565   if (ch != 'v') {
566      img_errno = IMG_BADFORMAT;
567      goto error;
568   }
569   ch = GETC(pimg->fh);
570   if (ch == '0') {
571      if (fread(buf, 4, 1, pimg->fh) != 1 || memcmp(buf, ".01\n", 4) != 0) {
572         img_errno = IMG_BADFORMAT;
573         goto error;
574      }
575      /* nothing special to do */
576   } else if (pimg->version == 0) {
577      if (ch < '2' || ch > '0' + IMG_VERSION_MAX || GETC(pimg->fh) != '\n') {
578         img_errno = IMG_TOONEW;
579         goto error;
580      }
581      pimg->version = ch - '0';
582   } else {
583      img_errno = IMG_BADFORMAT;
584      goto error;
585   }
586
587v03d:
588   if (!pimg->title)
589       pimg->title = getline_alloc(pimg->fh);
590   else
591       osfree(getline_alloc(pimg->fh));
592   pimg->datestamp = getline_alloc(pimg->fh);
593   if (!pimg->title || !pimg->datestamp) {
594      img_errno = IMG_OUTOFMEMORY;
595      error:
596      osfree(pimg->title);
597      osfree(pimg->datestamp);
598      osfree(pimg->filename_opened);
599      fclose(pimg->fh);
600      osfree(pimg);
601      return NULL;
602   }
603
604   pimg->start = ftell(pimg->fh);
605
606   len = strlen(pimg->title);
607   if (len > 11 && strcmp(pimg->title + len - 11, " (extended)") == 0) {
608       pimg->title[len - 11] = '\0';
609       pimg->is_extended_elevation = 1;
610   }
611
612   return pimg;
613}
614
615int
616img_rewind(img *pimg)
617{
618   if (!pimg->fRead) {
619      img_errno = IMG_WRITEERROR;
620      return 0;
621   }
622   if (fseek(pimg->fh, pimg->start, SEEK_SET) != 0) {
623      img_errno = IMG_READERROR;
624      return 0;
625   }
626   clearerr(pimg->fh);
627   /* [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
628    * [version 0] not in the middle of a 'LINE' command
629    * [version >= 3] not in the middle of turning a LINE into a MOVE */
630   pimg->pending = 0;
631
632   img_errno = IMG_NONE;
633
634   /* for version >= 3 we use label_buf to store the prefix for reuse */
635   /* for VERSION_COMPASS_PLT, 0 value indicates we haven't entered a survey
636    * yet */
637   /* for VERSION_CMAP_SHOT, we store the last station here to detect whether
638    * we MOVE or LINE */
639   pimg->label_len = 0;
640   return 1;
641}
642
643img *
644img_open_write(const char *fnm, char *title_buf, bool fBinary)
645{
646   time_t tm;
647   img *pimg;
648
649   fBinary = fBinary;
650
651   if (fDirectory(fnm)) {
652      img_errno = IMG_DIRECTORY;
653      return NULL;
654   }
655
656   pimg = (img *)xosmalloc(ossizeof(img));
657   if (pimg == NULL) {
658      img_errno = IMG_OUTOFMEMORY;
659      return NULL;
660   }
661
662   pimg->buf_len = 257;
663   pimg->label_buf = xosmalloc(pimg->buf_len);
664   if (!pimg->label_buf) {
665      osfree(pimg);
666      img_errno = IMG_OUTOFMEMORY;
667      return NULL;
668   }
669
670   pimg->fh = fopen(fnm, "wb");
671   if (!pimg->fh) {
672      osfree(pimg->label_buf);
673      osfree(pimg);
674      img_errno = IMG_CANTOPENOUT;
675      return NULL;
676   }
677
678   pimg->filename_opened = NULL;
679
680   /* Output image file header */
681   fputs("Survex 3D Image File\n", pimg->fh); /* file identifier string */
682   if (img_output_version < 2) {
683      pimg->version = 1;
684      fputs("Bv0.01\n", pimg->fh); /* binary file format version number */
685   } else {
686      pimg->version = (img_output_version > IMG_VERSION_MAX) ? IMG_VERSION_MAX : img_output_version;
687      fprintf(pimg->fh, "v%d\n", pimg->version); /* file format version no. */
688   }
689   fputsnl(title_buf, pimg->fh);
690   tm = time(NULL);
691   if (tm == (time_t)-1) {
692      fputsnl(TIMENA, pimg->fh);
693   } else {
694      char date[256];
695      /* output current date and time in format specified */
696      strftime(date, 256, TIMEFMT, localtime(&tm));
697      fputsnl(date, pimg->fh);
698   }
699#if 0
700   if (img_output_version >= 5) {
701       static const unsigned char codelengths[32] = {
702           4,  8,  8,  16, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
703           0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
704       };
705       fwrite(codelengths, 32, 1, pimg->fh);
706   }
707#endif
708   pimg->fRead = fFalse; /* writing to this file */
709   img_errno = IMG_NONE;
710
711   /* for version >= 3 we use label_buf to store the prefix for reuse */
712   pimg->label_buf[0] = '\0';
713   pimg->label_len = 0;
714
715#if IMG_API_VERSION == 0
716   pimg->date1 = pimg->date2 = 0;
717   pimg->olddate1 = pimg->olddate2 = 0;
718#else /* IMG_API_VERSION == 1 */
719   pimg->days1 = pimg->days2 = -1;
720   pimg->olddays1 = pimg->olddays2 = -1;
721#endif
722
723   pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
724
725   pimg->n_legs = 0;
726   pimg->length = 0.0;
727   pimg->E = pimg->H = pimg->V = 0.0;
728
729   /* Don't check for write errors now - let img_close() report them... */
730   return pimg;
731}
732
733static void
734read_xyz_station_coords(img_point *pt, const char *line)
735{
736   char num[12];
737   memcpy(num, line + 6, 9);
738   num[9] = '\0';
739   pt->x = atof(num) / METRES_PER_FOOT;
740   memcpy(num, line + 15, 9);
741   pt->y = atof(num) / METRES_PER_FOOT;
742   memcpy(num, line + 24, 8);
743   num[8] = '\0';
744   pt->z = atof(num) / METRES_PER_FOOT;
745}
746
747static void
748read_xyz_shot_coords(img_point *pt, const char *line)
749{
750   char num[12];
751   memcpy(num, line + 40, 10);
752   num[10] = '\0';
753   pt->x = atof(num) / METRES_PER_FOOT;
754   memcpy(num, line + 50, 10);
755   pt->y = atof(num) / METRES_PER_FOOT;
756   memcpy(num, line + 60, 9);
757   num[9] = '\0';
758   pt->z = atof(num) / METRES_PER_FOOT;
759}
760
761static void
762subtract_xyz_shot_deltas(img_point *pt, const char *line)
763{
764   char num[12];
765   memcpy(num, line + 15, 9);
766   num[9] = '\0';
767   pt->x -= atof(num) / METRES_PER_FOOT;
768   memcpy(num, line + 24, 8);
769   num[8] = '\0';
770   pt->y -= atof(num) / METRES_PER_FOOT;
771   memcpy(num, line + 32, 8);
772   pt->z -= atof(num) / METRES_PER_FOOT;
773}
774
775static int
776read_coord(FILE *fh, img_point *pt)
777{
778   SVX_ASSERT(fh);
779   SVX_ASSERT(pt);
780   pt->x = get32(fh) / 100.0;
781   pt->y = get32(fh) / 100.0;
782   pt->z = get32(fh) / 100.0;
783   if (ferror(fh) || feof(fh)) {
784      img_errno = feof(fh) ? IMG_BADFORMAT : IMG_READERROR;
785      return 0;
786   }
787   return 1;
788}
789
790static int
791skip_coord(FILE *fh)
792{
793    return (fseek(fh, 12, SEEK_CUR) == 0);
794}
795
796static int
797read_v3label(img *pimg)
798{
799   char *q;
800   long len = GETC(pimg->fh);
801   if (len == EOF) {
802      img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
803      return img_BAD;
804   }
805   if (len == 0xfe) {
806      len += get16(pimg->fh);
807      if (feof(pimg->fh)) {
808         img_errno = IMG_BADFORMAT;
809         return img_BAD;
810      }
811      if (ferror(pimg->fh)) {
812         img_errno = IMG_READERROR;
813         return img_BAD;
814      }
815   } else if (len == 0xff) {
816      len = get32(pimg->fh);
817      if (ferror(pimg->fh)) {
818         img_errno = IMG_READERROR;
819         return img_BAD;
820      }
821      if (feof(pimg->fh) || len < 0xfe + 0xffff) {
822         img_errno = IMG_BADFORMAT;
823         return img_BAD;
824      }
825   }
826
827   if (!check_label_space(pimg, pimg->label_len + len + 1)) {
828      img_errno = IMG_OUTOFMEMORY;
829      return img_BAD;
830   }
831   q = pimg->label_buf + pimg->label_len;
832   pimg->label_len += len;
833   if (len && fread(q, len, 1, pimg->fh) != 1) {
834      img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
835      return img_BAD;
836   }
837   q[len] = '\0';
838   return 0;
839}
840
841static int img_read_item_ascii(img *pimg, img_point *p);
842
843int
844img_read_item(img *pimg, img_point *p)
845{
846   int result;
847   pimg->flags = 0;
848
849   if (pimg->version >= 3) {
850      int opt;
851      pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
852      if (pimg->pending == 256) {
853         pimg->pending = 0;
854         return img_XSECT_END;
855      }
856      if (pimg->pending >= 0x80) {
857         *p = pimg->mv;
858         pimg->flags = (int)(pimg->pending) & 0x3f;
859         pimg->pending = 0;
860         return img_LINE;
861      }
862      again3: /* label to goto if we get a prefix, date, or lrud */
863      pimg->label = pimg->label_buf;
864      opt = GETC(pimg->fh);
865      if (opt == EOF) {
866         img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
867         return img_BAD;
868      }
869      switch (opt >> 6) {
870       case 0:
871         if (opt == 0) {
872            if (!pimg->label_len) return img_STOP; /* end of data marker */
873            pimg->label_len = 0;
874            goto again3;
875         }
876         if (opt < 15) {
877            /* 1-14 mean trim that many levels from current prefix */
878            int c;
879            if (pimg->label_len <= 17) {
880               /* zero prefix using "0" */
881               img_errno = IMG_BADFORMAT;
882               return img_BAD;
883            }
884            /* extra - 1 because label_len points to one past the end */
885            c = pimg->label_len - 17 - 1;
886            while (pimg->label_buf[c] != '.' || --opt > 0) {
887               if (--c < 0) {
888                  /* zero prefix using "0" */
889                  img_errno = IMG_BADFORMAT;
890                  return img_BAD;
891               }
892            }
893            c++;
894            pimg->label_len = c;
895            goto again3;
896         }
897         if (opt == 15) {
898            result = img_MOVE;
899            break;
900         }
901         if (opt >= 0x20) {
902             switch (opt) {
903                 case 0x20: /* Single date */
904                     if (pimg->version < 7) {
905                         int date1 = get32(pimg->fh);
906#if IMG_API_VERSION == 0
907                         pimg->date2 = pimg->date1 = date1;
908#else /* IMG_API_VERSION == 1 */
909                         if (date1 != 0) {
910                             pimg->days2 = pimg->days1 = (date1 / 86400) + 25567;
911                         } else {
912                             pimg->days2 = pimg->days1 = -1;
913                         }
914#endif
915                     } else {
916                         int days1 = (int)getu16(pimg->fh);
917#if IMG_API_VERSION == 0
918                         pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
919#else /* IMG_API_VERSION == 1 */
920                         pimg->days2 = pimg->days1 = days1;
921#endif
922                     }
923                     break;
924                 case 0x21: /* Date range (short for v7+) */
925                     if (pimg->version < 7) {
926                         INT32_T date1 = get32(pimg->fh);
927                         INT32_T date2 = get32(pimg->fh);
928#if IMG_API_VERSION == 0
929                         pimg->date1 = date1;
930                         pimg->date2 = date2;
931#else /* IMG_API_VERSION == 1 */
932                         pimg->days1 = (date1 / 86400) + 25567;
933                         pimg->days2 = (date2 / 86400) + 25567;
934#endif
935                     } else {
936                         int days1 = (int)getu16(pimg->fh);
937                         int days2 = days1 + GETC(pimg->fh) + 1;
938#if IMG_API_VERSION == 0
939                         pimg->date1 = (days1 - 25567) * 86400;
940                         pimg->date2 = (days2 - 25567) * 86400;
941#else /* IMG_API_VERSION == 1 */
942                         pimg->days1 = days1;
943                         pimg->days2 = days2;
944#endif
945                     }
946                     break;
947                 case 0x22: /* Error info */
948                     pimg->n_legs = get32(pimg->fh);
949                     pimg->length = get32(pimg->fh) / 100.0;
950                     pimg->E = get32(pimg->fh) / 100.0;
951                     pimg->H = get32(pimg->fh) / 100.0;
952                     pimg->V = get32(pimg->fh) / 100.0;
953                     return img_ERROR_INFO;
954                 case 0x23: { /* v7+: Date range (long) */
955                     if (pimg->version < 7) {
956                         img_errno = IMG_BADFORMAT;
957                         return img_BAD;
958                     }
959                     int days1 = (int)getu16(pimg->fh);
960                     int days2 = (int)getu16(pimg->fh);
961#if IMG_API_VERSION == 0
962                     pimg->date1 = (days1 - 25567) * 86400;
963                     pimg->date2 = (days2 - 25567) * 86400;
964#else /* IMG_API_VERSION == 1 */
965                     pimg->days1 = days1;
966                     pimg->days2 = days2;
967#endif
968                     break;
969                 }
970                 case 0x24: { /* v7+: No date info */
971#if IMG_API_VERSION == 0
972                     pimg->date1 = pimg->date2 = 0;
973#else /* IMG_API_VERSION == 1 */
974                     pimg->days1 = pimg->days2 = -1;
975#endif
976                     break;
977                 }
978                 case 0x30: case 0x31: /* LRUD */
979                 case 0x32: case 0x33: /* Big LRUD! */
980                     if (read_v3label(pimg) == img_BAD) return img_BAD;
981                     pimg->flags = (int)opt & 0x01;
982                     if (opt < 0x32) {
983                         pimg->l = get16(pimg->fh) / 100.0;
984                         pimg->r = get16(pimg->fh) / 100.0;
985                         pimg->u = get16(pimg->fh) / 100.0;
986                         pimg->d = get16(pimg->fh) / 100.0;
987                     } else {
988                         pimg->l = get32(pimg->fh) / 100.0;
989                         pimg->r = get32(pimg->fh) / 100.0;
990                         pimg->u = get32(pimg->fh) / 100.0;
991                         pimg->d = get32(pimg->fh) / 100.0;
992                     }
993                     if (pimg->survey_len) {
994                         size_t l = pimg->survey_len;
995                         const char *s = pimg->label_buf;
996                         if (strncmp(pimg->survey, s, l + 1) != 0) {
997                             return img_XSECT_END;
998                         }
999                         pimg->label += l;
1000                         /* skip the dot if there */
1001                         if (*pimg->label) pimg->label++;
1002                     }
1003                     /* If this is the last cross-section in this passage, set
1004                      * pending so we return img_XSECT_END next time. */
1005                     if (pimg->flags & 0x01) {
1006                         pimg->pending = 256;
1007                         pimg->flags &= ~0x01;
1008                     }
1009                     return img_XSECT;
1010                 default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1011                     img_errno = IMG_BADFORMAT;
1012                     return img_BAD;
1013             }
1014             goto again3;
1015         }
1016         /* 16-31 mean remove (n - 15) characters from the prefix */
1017         /* zero prefix using 0 */
1018         if (pimg->label_len <= (size_t)(opt - 15)) {
1019            img_errno = IMG_BADFORMAT;
1020            return img_BAD;
1021         }
1022         pimg->label_len -= (opt - 15);
1023         goto again3;
1024       case 1:
1025         if (read_v3label(pimg) == img_BAD) return img_BAD;
1026
1027         result = img_LABEL;
1028
1029         if (pimg->survey_len) {
1030            size_t l = pimg->survey_len;
1031            const char *s = pimg->label_buf;
1032            if (strncmp(pimg->survey, s, l + 1) != 0) {
1033               if (!skip_coord(pimg->fh)) return img_BAD;
1034               pimg->pending = 0;
1035               goto again3;
1036            }
1037            pimg->label += l;
1038            /* skip the dot if there */
1039            if (*pimg->label) pimg->label++;
1040         }
1041
1042         pimg->flags = (int)opt & 0x3f;
1043         break;
1044       case 2:
1045         if (read_v3label(pimg) == img_BAD) return img_BAD;
1046
1047         result = img_LINE;
1048
1049         if (pimg->survey_len) {
1050            size_t l = pimg->survey_len;
1051            const char *s = pimg->label_buf;
1052            if (strncmp(pimg->survey, s, l) != 0 ||
1053                !(s[l] == '.' || s[l] == '\0')) {
1054               if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1055               pimg->pending = 15;
1056               goto again3;
1057            }
1058            pimg->label += l;
1059            /* skip the dot if there */
1060            if (*pimg->label) pimg->label++;
1061         }
1062
1063         if (pimg->pending) {
1064            *p = pimg->mv;
1065            if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1066            pimg->pending = opt;
1067            return img_MOVE;
1068         }
1069         pimg->flags = (int)opt & 0x3f;
1070         break;
1071       default:
1072         img_errno = IMG_BADFORMAT;
1073         return img_BAD;
1074      }
1075      if (!read_coord(pimg->fh, p)) return img_BAD;
1076      pimg->pending = 0;
1077      return result;
1078   }
1079
1080   pimg->label = pimg->label_buf;
1081
1082   if (pimg->version > 0) {
1083      static long opt_lookahead = 0;
1084      static img_point pt = { 0.0, 0.0, 0.0 };
1085      long opt;
1086      again: /* label to goto if we get a cross */
1087      pimg->label[0] = '\0';
1088
1089      if (pimg->version == 1) {
1090         if (opt_lookahead) {
1091            opt = opt_lookahead;
1092            opt_lookahead = 0;
1093         } else {
1094            opt = get32(pimg->fh);
1095         }
1096      } else {
1097         opt = GETC(pimg->fh);
1098      }
1099
1100      if (feof(pimg->fh)) {
1101         img_errno = IMG_BADFORMAT;
1102         return img_BAD;
1103      }
1104      if (ferror(pimg->fh)) {
1105         img_errno = IMG_READERROR;
1106         return img_BAD;
1107      }
1108
1109      switch (opt) {
1110       case -1: case 0:
1111         return img_STOP; /* end of data marker */
1112       case 1:
1113         /* skip coordinates */
1114         if (!skip_coord(pimg->fh)) {
1115            img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1116            return img_BAD;
1117         }
1118         goto again;
1119       case 2: case 3: {
1120         char *q;
1121         int ch;
1122         result = img_LABEL;
1123         ch = GETC(pimg->fh);
1124         if (ch == EOF) {
1125            img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1126            return img_BAD;
1127         }
1128         if (ch != '\\') ungetc(ch, pimg->fh);
1129         fgets(pimg->label_buf, 257, pimg->fh);
1130         if (feof(pimg->fh)) {
1131            img_errno = IMG_BADFORMAT;
1132            return img_BAD;
1133         }
1134         if (ferror(pimg->fh)) {
1135            img_errno = IMG_READERROR;
1136            return img_BAD;
1137         }
1138         q = pimg->label_buf + strlen(pimg->label_buf) - 1;
1139         if (*q != '\n') {
1140            img_errno = IMG_BADFORMAT;
1141            return img_BAD;
1142         }
1143         /* Ignore empty labels in some .3d files (caused by a bug) */
1144         if (q == pimg->label_buf) goto again;
1145         *q = '\0';
1146         pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1147         if (opt == 2) goto done;
1148         break;
1149       }
1150       case 6: case 7: {
1151         long len;
1152         result = img_LABEL;
1153
1154         if (opt == 7)
1155            pimg->flags = GETC(pimg->fh);
1156         else
1157            pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1158
1159         len = get32(pimg->fh);
1160
1161         if (feof(pimg->fh)) {
1162            img_errno = IMG_BADFORMAT;
1163            return img_BAD;
1164         }
1165         if (ferror(pimg->fh)) {
1166            img_errno = IMG_READERROR;
1167            return img_BAD;
1168         }
1169
1170         /* Ignore empty labels in some .3d files (caused by a bug) */
1171         if (len == 0) goto again;
1172         if (!check_label_space(pimg, len + 1)) {
1173            img_errno = IMG_OUTOFMEMORY;
1174            return img_BAD;
1175         }
1176         if (fread(pimg->label_buf, len, 1, pimg->fh) != 1) {
1177            img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1178            return img_BAD;
1179         }
1180         pimg->label_buf[len] = '\0';
1181         break;
1182       }
1183       case 4:
1184         result = img_MOVE;
1185         break;
1186       case 5:
1187         result = img_LINE;
1188         break;
1189       default:
1190         switch ((int)opt & 0xc0) {
1191          case 0x80:
1192            pimg->flags = (int)opt & 0x3f;
1193            result = img_LINE;
1194            break;
1195          case 0x40: {
1196            char *q;
1197            pimg->flags = (int)opt & 0x3f;
1198            result = img_LABEL;
1199            if (!fgets(pimg->label_buf, 257, pimg->fh)) {
1200               img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1201               return img_BAD;
1202            }
1203            q = pimg->label_buf + strlen(pimg->label_buf) - 1;
1204            /* Ignore empty-labels in some .3d files (caused by a bug) */
1205            if (q == pimg->label_buf) goto again;
1206            if (*q != '\n') {
1207               img_errno = IMG_BADFORMAT;
1208               return img_BAD;
1209            }
1210            *q = '\0';
1211            break;
1212          }
1213          case 0xc0:
1214            /* use this for an extra leg or station flag if we need it */
1215          default:
1216            img_errno = IMG_BADFORMAT;
1217            return img_BAD;
1218         }
1219         break;
1220      }
1221
1222      if (!read_coord(pimg->fh, &pt)) return img_BAD;
1223
1224      if (result == img_LABEL && pimg->survey_len) {
1225         if (strncmp(pimg->label_buf, pimg->survey, pimg->survey_len + 1) != 0)
1226            goto again;
1227         pimg->label += pimg->survey_len + 1;
1228      }
1229
1230      done:
1231      *p = pt;
1232
1233      if (result == img_MOVE && pimg->version == 1) {
1234         /* peek at next code and see if it's an old-style label */
1235         opt_lookahead = get32(pimg->fh);
1236
1237         if (feof(pimg->fh)) {
1238            img_errno = IMG_BADFORMAT;
1239            return img_BAD;
1240         }
1241         if (ferror(pimg->fh)) {
1242            img_errno = IMG_READERROR;
1243            return img_BAD;
1244         }
1245
1246         if (opt_lookahead == 2) return img_read_item(pimg, p);
1247      }
1248
1249      return result;
1250   } else {
1251      /* We need to set the default locale for fscanf() to work on
1252       * numbers with "." as decimal point. */
1253      char * current_locale = my_strdup(setlocale(LC_NUMERIC, NULL));
1254      setlocale(LC_NUMERIC, "C");
1255      result = img_read_item_ascii(pimg, p);
1256      setlocale(LC_NUMERIC, current_locale);
1257      free(current_locale);
1258      return result;
1259   }
1260}
1261
1262/* Handle all for ASCII formats so we can easily fix up the locale
1263 * so fscanf(), etc work on numbers with "." as decimal point. */
1264static int
1265img_read_item_ascii(img *pimg, img_point *p)
1266{
1267   int result;
1268   if (pimg->version == 0) {
1269      ascii_again:
1270      pimg->label[0] = '\0';
1271      if (feof(pimg->fh)) return img_STOP;
1272      if (pimg->pending) {
1273         pimg->pending = 0;
1274         result = img_LINE;
1275      } else {
1276         char cmd[7];
1277         /* Stop if nothing found */
1278         if (fscanf(pimg->fh, "%6s", cmd) < 1) return img_STOP;
1279         if (strcmp(cmd, "move") == 0)
1280            result = img_MOVE;
1281         else if (strcmp(cmd, "draw") == 0)
1282            result = img_LINE;
1283         else if (strcmp(cmd, "line") == 0) {
1284            /* set flag to indicate to process second triplet as LINE */
1285            pimg->pending = 1;
1286            result = img_MOVE;
1287         } else if (strcmp(cmd, "cross") == 0) {
1288            if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1289               img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1290               return img_BAD;
1291            }
1292            goto ascii_again;
1293         } else if (strcmp(cmd, "name") == 0) {
1294            size_t off = 0;
1295            int ch = GETC(pimg->fh);
1296            if (ch == ' ') ch = GETC(pimg->fh);
1297            while (ch != ' ') {
1298               if (ch == '\n' || ch == EOF) {
1299                  img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1300                  return img_BAD;
1301               }
1302               if (off == pimg->buf_len) {
1303                  if (!check_label_space(pimg, pimg->buf_len * 2)) {
1304                     img_errno = IMG_OUTOFMEMORY;
1305                     return img_BAD;
1306                  }
1307               }
1308               pimg->label_buf[off++] = ch;
1309               ch = GETC(pimg->fh);
1310            }
1311            pimg->label_buf[off] = '\0';
1312
1313            pimg->label = pimg->label_buf;
1314            if (pimg->label[0] == '\\') pimg->label++;
1315
1316            pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1317
1318            result = img_LABEL;
1319         } else {
1320            img_errno = IMG_BADFORMAT;
1321            return img_BAD; /* unknown keyword */
1322         }
1323      }
1324
1325      if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1326         img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1327         return img_BAD;
1328      }
1329
1330      if (result == img_LABEL && pimg->survey_len) {
1331         if (strncmp(pimg->label, pimg->survey, pimg->survey_len + 1) != 0)
1332            goto ascii_again;
1333         pimg->label += pimg->survey_len + 1;
1334      }
1335
1336      return result;
1337   } else if (pimg->version == VERSION_SURVEX_POS) {
1338      /* Survex .pos file */
1339      size_t off;
1340      pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1341      againpos:
1342      off = 0;
1343      while (fscanf(pimg->fh, "(%lf,%lf,%lf ) ", &p->x, &p->y, &p->z) != 3) {
1344         int ch;
1345         if (ferror(pimg->fh)) {
1346            img_errno = IMG_READERROR;
1347            return img_BAD;
1348         }
1349         if (feof(pimg->fh)) return img_STOP;
1350         if (pimg->pending) {
1351            img_errno = IMG_BADFORMAT;
1352            return img_BAD;
1353         }
1354         pimg->pending = 1;
1355         /* ignore rest of line */
1356         do {
1357            ch = GETC(pimg->fh);
1358         } while (ch != '\n' && ch != '\r' && ch != EOF);
1359      }
1360
1361      pimg->label_buf[0] = '\0';
1362      while (!feof(pimg->fh)) {
1363         if (!fgets(pimg->label_buf + off, pimg->buf_len - off, pimg->fh)) {
1364            img_errno = IMG_READERROR;
1365            return img_BAD;
1366         }
1367
1368         off += strlen(pimg->label_buf + off);
1369         if (off && pimg->label_buf[off - 1] == '\n') {
1370            pimg->label_buf[off - 1] = '\0';
1371            break;
1372         }
1373         if (!check_label_space(pimg, pimg->buf_len * 2)) {
1374            img_errno = IMG_OUTOFMEMORY;
1375            return img_BAD;
1376         }
1377      }
1378
1379      pimg->label = pimg->label_buf;
1380
1381      if (pimg->label[0] == '\\') pimg->label++;
1382
1383      if (pimg->survey_len) {
1384         size_t l = pimg->survey_len + 1;
1385         if (strncmp(pimg->survey, pimg->label, l) != 0) goto againpos;
1386         pimg->label += l;
1387      }
1388
1389      return img_LABEL;
1390   } else if (pimg->version == VERSION_COMPASS_PLT) {
1391      /* Compass .plt file */
1392      if (pimg->pending > 0) {
1393         /* -1 signals we've entered the first survey we want to
1394          * read, and need to fudge lots if the first action is 'D'...
1395          */
1396         /* pending MOVE or LINE */
1397         int r = pimg->pending - 4;
1398         pimg->pending = 0;
1399         pimg->flags = 0;
1400         pimg->label[pimg->label_len] = '\0';
1401         return r;
1402      }
1403
1404      while (1) {
1405         char *line;
1406         char *q;
1407         size_t len = 0;
1408         int ch = GETC(pimg->fh);
1409
1410         switch (ch) {
1411            case '\x1a': case EOF: /* Don't insist on ^Z at end of file */
1412               return img_STOP;
1413            case 'X': case 'F': case 'S':
1414               /* bounding boX (marks end of survey), Feature survey, or
1415                * new Section - skip to next survey */
1416               if (pimg->survey) return img_STOP;
1417skip_to_N:
1418               while (1) {
1419                  do {
1420                     ch = GETC(pimg->fh);
1421                  } while (ch != '\n' && ch != '\r' && ch != EOF);
1422                  while (ch == '\n' || ch == '\r') ch = GETC(pimg->fh);
1423                  if (ch == 'N') break;
1424                  if (ch == '\x1a' || ch == EOF) return img_STOP;
1425               }
1426               /* FALLTHRU */
1427            case 'N':
1428               line = getline_alloc(pimg->fh);
1429               if (!line) {
1430                  img_errno = IMG_OUTOFMEMORY;
1431                  return img_BAD;
1432               }
1433               while (line[len] > 32) ++len;
1434               if (pimg->label_len == 0) pimg->pending = -1;
1435               if (!check_label_space(pimg, len + 1)) {
1436                  osfree(line);
1437                  img_errno = IMG_OUTOFMEMORY;
1438                  return img_BAD;
1439               }
1440               pimg->label_len = len;
1441               pimg->label = pimg->label_buf;
1442               memcpy(pimg->label, line, len);
1443               pimg->label[len] = '\0';
1444               osfree(line);
1445               break;
1446            case 'M': case 'D': {
1447               /* Move or Draw */
1448               long fpos = -1;
1449               if (pimg->survey && pimg->label_len == 0) {
1450                  /* We're only holding onto this line in case the first line
1451                   * of the 'N' is a 'D', so skip it for now...
1452                   */
1453                  goto skip_to_N;
1454               }
1455               if (ch == 'D' && pimg->pending == -1) {
1456                  if (pimg->survey) {
1457                     fpos = ftell(pimg->fh) - 1;
1458                     fseek(pimg->fh, pimg->start, SEEK_SET);
1459                     ch = GETC(pimg->fh);
1460                     pimg->pending = 0;
1461                  } else {
1462                     /* If a file actually has a 'D' before any 'M', then
1463                      * pretend the 'D' is an 'M' - one of the examples
1464                      * in the docs was like this! */
1465                     ch = 'M';
1466                  }
1467               }
1468               line = getline_alloc(pimg->fh);
1469               if (!line) {
1470                  img_errno = IMG_OUTOFMEMORY;
1471                  return img_BAD;
1472               }
1473               /* Compass stores coordinates as North, East, Up = (y,x,z)! */
1474               if (sscanf(line, "%lf%lf%lf", &p->y, &p->x, &p->z) != 3) {
1475                  osfree(line);
1476                  if (ferror(pimg->fh)) {
1477                     img_errno = IMG_READERROR;
1478                  } else {
1479                     img_errno = IMG_BADFORMAT;
1480                  }
1481                  return img_BAD;
1482               }
1483               p->x *= METRES_PER_FOOT;
1484               p->y *= METRES_PER_FOOT;
1485               p->z *= METRES_PER_FOOT;
1486               q = strchr(line, 'S');
1487               if (!q) {
1488                  osfree(line);
1489                  img_errno = IMG_BADFORMAT;
1490                  return img_BAD;
1491               }
1492               ++q;
1493               len = 0;
1494               while (q[len] > ' ') ++len;
1495               q[len] = '\0';
1496               len += 2; /* ' ' and '\0' */
1497               if (!check_label_space(pimg, pimg->label_len + len)) {
1498                  img_errno = IMG_OUTOFMEMORY;
1499                  return img_BAD;
1500               }
1501               pimg->label = pimg->label_buf;
1502               if (pimg->label_len) {
1503                   pimg->label[pimg->label_len] = ' ';
1504                   memcpy(pimg->label + pimg->label_len + 1, q, len - 1);
1505               } else {
1506                   memcpy(pimg->label, q, len - 1);
1507               }
1508               q += len - 1;
1509               /* Now read LRUD.  Technically, this is optional but virtually
1510                * all PLT files have it (with dummy negative values if no LRUD
1511                * was measured) and some versions of Compass can't read PLT
1512                * files without it!
1513                */
1514               while (*q && *q <= ' ') q++;
1515               if (*q == 'P') {
1516                   if (sscanf(q + 1, "%lf%lf%lf%lf",
1517                              &pimg->l, &pimg->r, &pimg->u, &pimg->d) != 4) {
1518                       osfree(line);
1519                       if (ferror(pimg->fh)) {
1520                           img_errno = IMG_READERROR;
1521                       } else {
1522                           img_errno = IMG_BADFORMAT;
1523                       }
1524                       return img_BAD;
1525                   }
1526                   pimg->l *= METRES_PER_FOOT;
1527                   pimg->r *= METRES_PER_FOOT;
1528                   pimg->u *= METRES_PER_FOOT;
1529                   pimg->d *= METRES_PER_FOOT;
1530               } else {
1531                   pimg->l = pimg->r = pimg->u = pimg->d = -1;
1532               }
1533               osfree(line);
1534               pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1535               if (fpos != -1) {
1536                  fseek(pimg->fh, fpos, SEEK_SET);
1537               } else {
1538                  pimg->pending = (ch == 'M' ? img_MOVE : img_LINE) + 4;
1539               }
1540               return img_LABEL;
1541            }
1542            default:
1543               img_errno = IMG_BADFORMAT;
1544               return img_BAD;
1545         }
1546      }
1547   } else {
1548      /* CMAP .xyz file */
1549      char *line = NULL;
1550      char *q;
1551      size_t len;
1552
1553      if (pimg->pending) {
1554         /* pending MOVE or LINE or LABEL or STOP */
1555         int r = pimg->pending - 4;
1556         /* Set label to empty - don't use "" as we adjust label relative
1557          * to label_buf when label_buf is reallocated. */
1558         pimg->label = pimg->label_buf + strlen(pimg->label_buf);
1559         pimg->flags = 0;
1560         if (r == img_LABEL) {
1561            /* nasty magic */
1562            read_xyz_shot_coords(p, pimg->label_buf + 16);
1563            subtract_xyz_shot_deltas(p, pimg->label_buf + 16);
1564            pimg->pending = img_STOP + 4;
1565            return img_MOVE;
1566         }
1567
1568         pimg->pending = 0;
1569
1570         if (r == img_STOP) {
1571            /* nasty magic */
1572            read_xyz_shot_coords(p, pimg->label_buf + 16);
1573            return img_LINE;
1574         }
1575
1576         return r;
1577      }
1578
1579      pimg->label = pimg->label_buf;
1580      do {
1581         osfree(line);
1582         if (feof(pimg->fh)) return img_STOP;
1583         line = getline_alloc(pimg->fh);
1584         if (!line) {
1585            img_errno = IMG_OUTOFMEMORY;
1586            return img_BAD;
1587         }
1588      } while (line[0] == ' ' || line[0] == '\0');
1589      if (line[0] == '\x1a') return img_STOP;
1590
1591      len = strlen(line);
1592      if (pimg->version == VERSION_CMAP_STATION) {
1593         /* station variant */
1594         if (len < 37) {
1595            osfree(line);
1596            img_errno = IMG_BADFORMAT;
1597            return img_BAD;
1598         }
1599         memcpy(pimg->label, line, 6);
1600         q = memchr(pimg->label, ' ', 6);
1601         if (!q) q = pimg->label + 6;
1602         *q = '\0';
1603
1604         read_xyz_station_coords(p, line);
1605
1606         /* FIXME: look at prev for lines (line + 32, 5) */
1607         /* FIXME: duplicate stations... */
1608         return img_LABEL;
1609      } else {
1610         /* Shot variant */
1611         char old[8], new[8];
1612         if (len < 61) {
1613            osfree(line);
1614            img_errno = IMG_BADFORMAT;
1615            return img_BAD;
1616         }
1617
1618         memcpy(old, line, 7);
1619         q = memchr(old, ' ', 7);
1620         if (!q) q = old + 7;
1621         *q = '\0';
1622
1623         memcpy(new, line + 7, 7);
1624         q = memchr(new, ' ', 7);
1625         if (!q) q = new + 7;
1626         *q = '\0';
1627
1628         pimg->flags = img_SFLAG_UNDERGROUND;
1629
1630         if (strcmp(old, new) == 0) {
1631            pimg->pending = img_MOVE + 4;
1632            read_xyz_shot_coords(p, line);
1633            strcpy(pimg->label, new);
1634            osfree(line);
1635            return img_LABEL;
1636         }
1637
1638         if (strcmp(old, pimg->label) == 0) {
1639            pimg->pending = img_LINE + 4;
1640            read_xyz_shot_coords(p, line);
1641            strcpy(pimg->label, new);
1642            osfree(line);
1643            return img_LABEL;
1644         }
1645
1646         pimg->pending = img_LABEL + 4;
1647         read_xyz_shot_coords(p, line);
1648         strcpy(pimg->label, new);
1649         memcpy(pimg->label + 16, line, 70);
1650
1651         osfree(line);
1652         return img_LABEL;
1653      }
1654   }
1655}
1656
1657static int
1658write_v3label(img *pimg, int opt, const char *s)
1659{
1660   size_t len, n, dot;
1661
1662   /* find length of common prefix */
1663   dot = 0;
1664   for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
1665      if (s[len] == '.') dot = len + 1;
1666   }
1667
1668   SVX_ASSERT(len <= pimg->label_len);
1669   n = pimg->label_len - len;
1670   if (len == 0) {
1671      if (pimg->label_len) PUTC(0, pimg->fh);
1672   } else if (n <= 16) {
1673      if (n) PUTC(n + 15, pimg->fh);
1674   } else if (dot == 0) {
1675      if (pimg->label_len) PUTC(0, pimg->fh);
1676      len = 0;
1677   } else {
1678      const char *p = pimg->label_buf + dot;
1679      n = 1;
1680      for (len = pimg->label_len - dot - 17; len; len--) {
1681         if (*p++ == '.') n++;
1682      }
1683      if (n <= 14) {
1684         PUTC(n, pimg->fh);
1685         len = dot;
1686      } else {
1687         if (pimg->label_len) PUTC(0, pimg->fh);
1688         len = 0;
1689      }
1690   }
1691
1692   n = strlen(s + len);
1693   PUTC(opt, pimg->fh);
1694   if (n < 0xfe) {
1695      PUTC(n, pimg->fh);
1696   } else if (n < 0xffff + 0xfe) {
1697      PUTC(0xfe, pimg->fh);
1698      put16((short)(n - 0xfe), pimg->fh);
1699   } else {
1700      PUTC(0xff, pimg->fh);
1701      put32(n, pimg->fh);
1702   }
1703   fwrite(s + len, n, 1, pimg->fh);
1704
1705   n += len;
1706   pimg->label_len = n;
1707   if (!check_label_space(pimg, n + 1))
1708      return 0; /* FIXME: distinguish out of memory... */
1709   memcpy(pimg->label_buf + len, s + len, n - len + 1);
1710
1711   return !ferror(pimg->fh);
1712}
1713
1714static void
1715img_write_item_date(img *pimg)
1716{
1717    int same, unset;
1718    /* Only write dates when they've changed. */
1719#if IMG_API_VERSION == 0
1720    if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
1721        return;
1722
1723    same = (pimg->date1 == pimg->date2);
1724    unset = (pimg->date1 == 0);
1725#else /* IMG_API_VERSION == 1 */
1726    if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
1727        return;
1728
1729    same = (pimg->days1 == pimg->days2);
1730    unset = (pimg->days1 == -1);
1731#endif
1732
1733    if (same) {
1734        if (img_output_version < 7) {
1735            PUTC(0x20, pimg->fh);
1736#if IMG_API_VERSION == 0
1737            put32(pimg->date1, pimg->fh);
1738#else /* IMG_API_VERSION == 1 */
1739            put32((pimg->days1 - 25567) * 86400, pimg->fh);
1740#endif
1741        } else {
1742            if (unset) {
1743                PUTC(0x24, pimg->fh);
1744            } else {
1745                PUTC(0x20, pimg->fh);
1746#if IMG_API_VERSION == 0
1747                put16(pimg->date1 / 86400 + 25567, pimg->fh);
1748#else /* IMG_API_VERSION == 1 */
1749                put16(pimg->days1, pimg->fh);
1750#endif
1751            }
1752        }
1753    } else {
1754        if (img_output_version < 7) {
1755            PUTC(0x21, pimg->fh);
1756#if IMG_API_VERSION == 0
1757            put32(pimg->date1, pimg->fh);
1758            put32(pimg->date2, pimg->fh);
1759#else /* IMG_API_VERSION == 1 */
1760            put32((pimg->days1 - 25567) * 86400, pimg->fh);
1761            put32((pimg->days2 - 25567) * 86400, pimg->fh);
1762#endif
1763        } else {
1764#if IMG_API_VERSION == 0
1765            int diff = (pimg->date2 - pimg->date1) / 86400;
1766            if (diff > 0 && diff <= 256) {
1767                PUTC(0x21, pimg->fh);
1768                put16(pimg->date1 / 86400 + 25567, pimg->fh);
1769                PUTC(diff - 1, pimg->fh);
1770            } else {
1771                PUTC(0x23, pimg->fh);
1772                put16(pimg->date1 / 86400 + 25567, pimg->fh);
1773                put16(pimg->date2 / 86400 + 25567, pimg->fh);
1774            }
1775#else /* IMG_API_VERSION == 1 */
1776            int diff = pimg->days2 - pimg->days1;
1777            if (diff > 0 && diff <= 256) {
1778                PUTC(0x21, pimg->fh);
1779                put16(pimg->days1, pimg->fh);
1780                PUTC(diff - 1, pimg->fh);
1781            } else {
1782                PUTC(0x23, pimg->fh);
1783                put16(pimg->days1, pimg->fh);
1784                put16(pimg->days2, pimg->fh);
1785            }
1786#endif
1787        }
1788    }
1789#if IMG_API_VERSION == 0
1790    pimg->olddate1 = pimg->date1;
1791    pimg->olddate2 = pimg->date2;
1792#else /* IMG_API_VERSION == 1 */
1793    pimg->olddays1 = pimg->days1;
1794    pimg->olddays2 = pimg->days2;
1795#endif
1796}
1797
1798void
1799img_write_item(img *pimg, int code, int flags, const char *s,
1800               double x, double y, double z)
1801{
1802   if (!pimg) return;
1803   if (pimg->version >= 3) {
1804      int opt = 0;
1805      switch (code) {
1806       case img_LABEL:
1807         write_v3label(pimg, 0x40 | flags, s);
1808         opt = 0;
1809         break;
1810       case img_XSECT: {
1811         INT32_T l, r, u, d, max_dim;
1812         /* Need at least version 5 for img_XSECT. */
1813         if (pimg->version < 5) break;
1814         img_write_item_date(pimg);
1815         l = (INT32_T)my_round(pimg->l * 100.0);
1816         r = (INT32_T)my_round(pimg->r * 100.0);
1817         u = (INT32_T)my_round(pimg->u * 100.0);
1818         d = (INT32_T)my_round(pimg->d * 100.0);
1819         if (l < 0) l = -1;
1820         if (r < 0) r = -1;
1821         if (u < 0) u = -1;
1822         if (d < 0) d = -1;
1823         max_dim = max(max(l, r), max(u, d));
1824         flags &= 1;
1825         if (max_dim >= 32768) flags |= 2;
1826         write_v3label(pimg, 0x30 | flags, s);
1827         if (flags & 2) {
1828            /* Big passage!  Need to use 4 bytes. */
1829            put32(l, pimg->fh);
1830            put32(r, pimg->fh);
1831            put32(u, pimg->fh);
1832            put32(d, pimg->fh);
1833         } else {
1834            put16(l, pimg->fh);
1835            put16(r, pimg->fh);
1836            put16(u, pimg->fh);
1837            put16(d, pimg->fh);
1838         }
1839         return;
1840       }
1841       case img_MOVE:
1842         opt = 15;
1843         break;
1844       case img_LINE:
1845         if (pimg->version >= 4) {
1846             img_write_item_date(pimg);
1847         }
1848         write_v3label(pimg, 0x80 | flags, s ? s : "");
1849         opt = 0;
1850         break;
1851       default: /* ignore for now */
1852         return;
1853      }
1854      if (opt) PUTC(opt, pimg->fh);
1855      /* Output in cm */
1856      put32((INT32_T)my_round(x * 100.0), pimg->fh);
1857      put32((INT32_T)my_round(y * 100.0), pimg->fh);
1858      put32((INT32_T)my_round(z * 100.0), pimg->fh);
1859   } else {
1860      size_t len;
1861      INT32_T opt = 0;
1862      SVX_ASSERT(pimg->version > 0);
1863      switch (code) {
1864       case img_LABEL:
1865         if (pimg->version == 1) {
1866            /* put a move before each label */
1867            img_write_item(pimg, img_MOVE, 0, NULL, x, y, z);
1868            put32(2, pimg->fh);
1869            fputsnl(s, pimg->fh);
1870            return;
1871         }
1872         len = strlen(s);
1873         if (len > 255 || strchr(s, '\n')) {
1874            /* long label - not in early incarnations of v2 format, but few
1875             * 3d files will need these, so better not to force incompatibility
1876             * with a new version I think... */
1877            PUTC(7, pimg->fh);
1878            PUTC(flags, pimg->fh);
1879            put32(len, pimg->fh);
1880            fputs(s, pimg->fh);
1881         } else {
1882            PUTC(0x40 | (flags & 0x3f), pimg->fh);
1883            fputsnl(s, pimg->fh);
1884         }
1885         opt = 0;
1886         break;
1887       case img_MOVE:
1888         opt = 4;
1889         break;
1890       case img_LINE:
1891         if (pimg->version > 1) {
1892            opt = 0x80 | (flags & 0x3f);
1893            break;
1894         }
1895         opt = 5;
1896         break;
1897       default: /* ignore for now */
1898         return;
1899      }
1900      if (pimg->version == 1) {
1901         put32(opt, pimg->fh);
1902      } else {
1903         if (opt) PUTC(opt, pimg->fh);
1904      }
1905      /* Output in cm */
1906      put32((INT32_T)my_round(x * 100.0), pimg->fh);
1907      put32((INT32_T)my_round(y * 100.0), pimg->fh);
1908      put32((INT32_T)my_round(z * 100.0), pimg->fh);
1909   }
1910}
1911
1912/* Write error information for the current traverse
1913 * n_legs is the number of legs in the traverse
1914 * length is the traverse length (in m)
1915 * E is the ratio of the observed misclosure to the theoretical one
1916 * H is the ratio of the observed horizontal misclosure to the theoretical one
1917 * V is the ratio of the observed vertical misclosure to the theoretical one
1918 */
1919void
1920img_write_errors(img *pimg, int n_legs, double length,
1921                 double E, double H, double V)
1922{
1923    PUTC(0x22, pimg->fh);
1924    put32(n_legs, pimg->fh);
1925    put32((INT32_T)my_round(length * 100.0), pimg->fh);
1926    put32((INT32_T)my_round(E * 100.0), pimg->fh);
1927    put32((INT32_T)my_round(H * 100.0), pimg->fh);
1928    put32((INT32_T)my_round(V * 100.0), pimg->fh);
1929}
1930
1931int
1932img_close(img *pimg)
1933{
1934   int result = 1;
1935   if (pimg) {
1936      if (pimg->fh) {
1937         if (pimg->fRead) {
1938            osfree(pimg->survey);
1939            osfree(pimg->title);
1940            osfree(pimg->datestamp);
1941         } else {
1942            /* write end of data marker */
1943            switch (pimg->version) {
1944             case 1:
1945               put32((INT32_T)-1, pimg->fh);
1946               break;
1947             case 2:
1948               PUTC(0, pimg->fh);
1949               break;
1950             default:
1951               if (pimg->label_len) PUTC(0, pimg->fh);
1952               PUTC(0, pimg->fh);
1953               break;
1954            }
1955         }
1956         if (ferror(pimg->fh)) result = 0;
1957         if (fclose(pimg->fh)) result = 0;
1958         if (!result) img_errno = pimg->fRead ? IMG_READERROR : IMG_WRITEERROR;
1959      }
1960      osfree(pimg->label_buf);
1961      osfree(pimg->filename_opened);
1962      osfree(pimg);
1963   }
1964   return result;
1965}
Note: See TracBrowser for help on using the repository browser.