source: git/src/img.c @ 88707e0b

RELEASE/1.1RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-datawalls-data-hanging-as-warning
Last change on this file since 88707e0b was c61aa79, checked in by Olly Betts <olly@…>, 18 years ago

Add "Colour by Error".

git-svn-id: file:///home/survex-svn/survex/branches/survex-1_1@3289 4b37db11-9a0c-4f06-9ece-9ab7cdaee568

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