source: git/src/img.c @ 7cb7f91

RELEASE/1.0
Last change on this file since 7cb7f91 was 7cb7f91, checked in by Olly Betts <olly@…>, 13 years ago

Backport change from 1.2.0:
configure.in,src/img.c,src/img.h: Sync latest versions of img.c and
img.h from trunk, and configure code to define GETC and PUTC macros.

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